Skip to content

Instantly share code, notes, and snippets.

@rfay
Created February 14, 2011 19:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rfay/826357 to your computer and use it in GitHub Desktop.
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) {
$order_statuses = commerce_order_statuses();
// Return a status title if specified and it exists.
if (!empty($name)) {
if (isset($order_statuses[$name])) {
return $order_statuses[$name]['title'];
}
else {
// Return FALSE if it does not exist.
return FALSE;
}
}
// Otherwise turn the array values into the status title only.
foreach ($order_statuses as $key => $value) {
$order_statuses[$key] = $value['title'];
}
return $order_statuses;
}
/**
* Wraps commerce_order_status_get_title() for use by the Entity module.
*/
function commerce_order_status_options_list() {
return commerce_order_status_get_title();
}
/**
* Updates the status of an order to the specified status.
*
* @param $order
* The fully loaded order object to update.
* @param $name
* The machine readable name string of the status to update to.
* @param $skip_save
* TRUE to skip saving the order after updating the status; used when the
* order would be saved elsewhere after the update.
*
* @return
* The updated order.
*/
function commerce_order_status_update($order, $name) {
// Do not update the status if the order is already at it.
if ($order->status != $name) {
$order->status = $name;
// TODO: Alert modules via a hook / Rules that the order was updated.
$order = commerce_order_save($order);
}
return $order;
}
/**
* Ensures the line item field is present on the default order bundle.
*/
function commerce_order_configure_order_type($type = 'commerce_order') {
// If a field type we know should exist isn't found, clear the Field cache.
if (!field_info_field_types('commerce_line_item_reference') ||
!field_info_field_types('commerce_customer_profile_reference')) {
field_cache_clear();
}
// Look for or add a line item reference field to the order type.
$field_name = 'commerce_line_items';
$field = field_info_field($field_name);
$instance = field_info_instance('commerce_order', $field_name, $type);
if (empty($field)) {
$field = array(
'field_name' => $field_name,
'type' => 'commerce_line_item_reference',
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
'entity_types' => array('commerce_order'),
'translatable' => FALSE,
'locked' => TRUE,
);
$field = field_create_field($field);
}
if (empty($instance)) {
$instance = array(
'field_name' => $field_name,
'entity_type' => 'commerce_order',
'bundle' => $type,
'label' => t('Line items'),
'settings' => array(),
'widget' => array(
'type' => 'commerce_line_item_manager',
'weight' => -10,
),
'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' => 'above',
'type' => 'commerce_line_item_reference_view',
'weight' => -10,
);
}
field_create_instance($instance);
}
// Add the customer profile reference fields for each profile type.
$entity_info = entity_get_info('commerce_customer_profile');
foreach ($entity_info['bundles'] as $profile_type => $data) {
$field_name = 'commerce_customer_' . $profile_type;
$field = field_info_field($field_name);
$instance = field_info_instance('commerce_order', $field_name, $type);
if (empty($field)) {
$field = array(
'field_name' => $field_name,
'type' => 'commerce_customer_profile_reference',
'cardinality' => 1,
'entity_types' => array('commerce_order'),
'translatable' => FALSE,
);
$field = field_create_field($field);
}
if (empty($instance)) {
$instance = array(
'field_name' => $field_name,
'entity_type' => 'commerce_order',
'bundle' => $type,
'label' => check_plain($data['label']),
'settings' => array(
'profile_type' => array($profile_type),
),
'widget' => array(
'type' => 'commerce_customer_profile_manager',
'weight' => -5,
),
'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' => 'above',
'type' => 'commerce_customer_profile_reference_display',
'weight' => -5,
);
}
field_create_instance($instance);
variable_set('commerce_customer_profile_' . $profile_type . '_field', $field_name);
}
}
// Add the order total price field.
commerce_price_create_instance('commerce_order_total', 'commerce_order', $type, t('Order total'), -8, FALSE, array('label' => 'inline'));
}
/**
* Callback for getting order properties.
* @see commerce_order_entity_property_info()
*/
function commerce_order_get_properties($order, array $options, $name) {
switch ($name) {
case 'owner':
return $order->uid;
case 'view-url':
return url('order/' . $order->order_id, $options);
case 'admin-url':
return url('admin/commerce/orders/' . $order->order_id, $options);
case 'edit-url':
return url('admin/commerce/orders/' . $order->order_id . '/edit', $options);
case 'state':
$order_status = commerce_order_status_load($order->status);
return $order_status['state'];
}
}
/**
* Callback for setting order properties.
* @see commerce_order_entity_property_info()
*/
function commerce_order_set_properties($order, $name, $value) {
if ($name == 'owner') {
$order->uid = $value;
}
}
<?php
// $Id$
/**
* @file
* Rules integration for orders.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function commerce_order_rules_event_info() {
$events = array();
$events['commerce_order_presave'] = array(
'label' => t('Before saving an order'),
'group' => t('Commerce Order'),
'variables' => commerce_order_rules_event_variables(t('Order', array(), array('context' => 'a drupal commerce order')), TRUE, TRUE),
'access callback' => 'commerce_order_rules_access',
);
$events['commerce_order_insert'] = array(
'label' => t('After saving a new order'),
'group' => t('Commerce Order'),
'variables' => commerce_order_rules_event_variables(t('Created order'), TRUE),
'access callback' => 'commerce_order_rules_access',
);
$events['commerce_order_update'] = array(
'label' => t('After updating an existing order'),
'group' => t('Commerce Order'),
'variables' => commerce_order_rules_event_variables(t('Updated order'), TRUE),
'access callback' => 'commerce_order_rules_access',
);
$events['commerce_order_delete'] = array(
'label' => t('After deleting an order'),
'group' => t('Commerce Order'),
'variables' => commerce_order_rules_event_variables(t('Deleted order')),
'access callback' => 'commerce_order_rules_access',
);
return $events;
}
/**
* Returns a variables array for order events.
*
* @param $label
* The label for the primary order variable.
* @param $unchanged
* Boolean indicating whether or not to include the unchanged order.
* @param $skip_save
* Boolean indicating whether or not the primary order variable should skip
* saving after event execution.
*/
function commerce_order_rules_event_variables($label, $unchanged = FALSE, $skip_save = FALSE) {
$variables = array(
'order' => array(
'type' => 'commerce_order',
'label' => $label,
'skip save' => $skip_save,
),
);
if ($unchanged) {
$variables['order_unchanged'] = array(
'type' => 'commerce_order',
'label' => t('Unchanged order'),
'skip save' => TRUE,
'handler' => 'rules_events_entity_unchanged',
);
}
return $variables;
}
/**
* Rules integration access callback.
*/
function commerce_order_rules_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return commerce_order_access('view');
}
}
/**
* Implements hook_rules_action_info().
*/
function commerce_order_rules_action_info() {
$actions = array();
$actions['commerce_order_update_state'] = array(
'label' => t('Update the order state'),
'parameter' => array(
'order' => array(
'type' => 'commerce_order',
'label' => t('Order to update'),
),
'order_state' => array(
'type' => 'text',
'label' => t('Order state'),
'description' => t('Select the order state whose default status the order will be updated to.'),
'options list' => 'commerce_order_state_options_list',
),
),
'group' => t('Commerce Order'),
'callbacks' => array(
'execute' => 'commerce_order_rules_update_state',
),
);
$actions['commerce_order_update_status'] = array(
'label' => t('Update the order status'),
'parameter' => array(
'order' => array(
'type' => 'commerce_order',
'label' => t('Order to update'),
),
'order_status' => array(
'type' => 'text',
'label' => t('Order status'),
'options list' => 'commerce_order_status_options_list',
),
),
'group' => t('Commerce Order'),
'callbacks' => array(
'execute' => 'commerce_order_rules_update_status',
),
);
return $actions;
}
/**
* Rules action: updates an order's status to the default status of the given
* order state.
*/
function commerce_order_rules_update_state($order, $name) {
$order_state = commerce_order_state_load($name);
commerce_order_status_update($order, $order_state['default_status']);
}
/**
* Rules action: updates an order's status using the Order API.
*/
function commerce_order_rules_update_status($order, $name) {
commerce_order_status_update($order, $name);
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Builds placeholder replacement tokens for order-related data.
*/
/**
* Implements hook_token_info().
*/
function commerce_order_token_info() {
$type = array(
'name' => t('Orders', array(), array('context' => 'a drupal commerce order')),
'description' => t('Tokens related to individual orders.'),
'needs-data' => 'order',
);
// Tokens for orders.
$order = array();
$order['order-id'] = array(
'name' => t('Order ID', array(), array('context' => 'a drupal commerce order')),
'description' => t('The unique numeric ID of the order.'),
);
$order['order-number'] = array(
'name' => t('Order number', array(), array('context' => 'a drupal commerce order')),
'description' => t('The order number displayed to the customer.'),
);
$order['revision-id'] = array(
'name' => t('Revision ID'),
'description' => t("The unique ID of the order's latest revision."),
);
$order['type'] = array(
'name' => t('Order type'),
'description' => t('The type of the order.'),
);
$order['type-name'] = array(
'name' => t('Order type name'),
'description' => t('The human-readable name of the order type.'),
);
$order['mail'] = array(
'name' => t('Order e-mail'),
'description' => t('The e-mail address associated with the order.'),
);
$order['status'] = array(
'name' => t('Order status'),
'description' => t('The current status of the order.'),
);
$order['status-title'] = array(
'name' => t('Order status title'),
'description' => t('The human-readable title of the order status.'),
);
$order['state'] = array(
'name' => t('Order state'),
'description' => t('The current state of the order.'),
);
$order['state-title'] = array(
'name' => t('Order state title'),
'description' => t('The human-readable title of the order state.'),
);
$order['url'] = array(
'name' => t('URL'),
'description' => t('The URL of the order.'),
);
$order['edit-url'] = array(
'name' => t('Edit URL'),
'description' => t("The URL of the order's edit page."),
);
// Chained tokens for orders.
$order['owner'] = array(
'name' => t('Owner'),
'description' => t('The owner of the order.'),
'type' => 'user',
);
$order['created'] = array(
'name' => t('Date created'),
'description' => t('The date the order was created.'),
'type' => 'date',
);
$order['changed'] = array(
'name' => t('Date changed'),
'description' => t('The date the order was last updated.'),
'type' => 'date',
);
return array(
'types' => array('order' => $type),
'tokens' => array('order' => $order),
);
}
/**
* Implements hook_tokens().
*/
function commerce_order_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 == 'order' && !empty($data['order'])) {
$order = $data['order'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Simple key values on the order.
case 'order-id':
$replacements[$original] = $order->order_id;
break;
case 'order-number':
$replacements[$original] = $sanitize ? check_plain($order->order_number) : $order->order_number;
break;
case 'revision_id':
$replacements[$original] = $order->revision_id;
break;
case 'type':
$replacements[$original] = $sanitize ? check_plain($order->type) : $order->type;
break;
case 'type-name':
$type_name = commerce_order_type_get_name($order->type);
$replacements[$original] = $sanitize ? check_plain($type_name) : $type_name;
break;
case 'mail':
$replacements[$original] = $sanitize ? check_plain($order->mail) : $order->mail;
break;
case 'status':
$replacements[$original] = $sanitize ? check_plain($order->status) : $order->status;
break;
case 'status-title':
$replacements[$original] = $sanitize ? check_plain(commerce_order_status_get_title($order->status)) : commerce_order_status_get_title($order->status);
break;
case 'state':
$order_status = commerce_order_status_load($order->status);
$replacements[$original] = $sanitize ? check_plain($order_status['state']) : $order_status['state'];
break;
case 'state-title':
$order_status = commerce_order_status_load($order->status);
$replacements[$original] = $sanitize ? check_plain(commerce_order_state_get_title($order_status['state'])) : commerce_order_state_get_title($order_status['state']);
break;
case 'url':
$replacements[$original] = url('user/' . $order->uid . '/orders/' . $order->order_id, $url_options);
break;
case 'edit-url':
$replacements[$original] = url('admin/commerce/orders/' . $order->order_id . '/edit', $url_options);
break;
// Default values for the chained tokens handled below.
case 'owner':
if ($order->uid == 0) {
$name = variable_get('anonymous', t('Anonymous'));
}
else {
$account = user_load($order->uid);
$name = $account->name;
}
$replacements[$original] = $sanitize ? filter_xss($name) : $name;
break;
case 'created':
$replacements[$original] = format_date($order->created, 'medium', '', NULL, $language_code);
break;
case 'changed':
$replacements[$original] = format_date($order->changed, 'medium', '', NULL, $language_code);
break;
}
}
if ($owner_tokens = token_find_with_prefix($tokens, 'owner')) {
$owner = user_load($order->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' => $order->{$date}), $options);
}
}
}
return $replacements;
}
<?php
// $Id$
/**
* Export Drupal Commerce orders to Views.
*/
/**
* Implements hook_views_data()
*/
function commerce_order_views_data() {
$data = array();
$data['commerce_order']['table']['group'] = t('Commerce Order');
$data['commerce_order']['table']['base'] = array(
'field' => 'order_number',
'title' => t('Commerce Order'),
'help' => t('Order placed in the store.'),
);
// Expose the order ID.
$data['commerce_order']['order_id'] = array(
'title' => t('Order ID', array(), array('context' => 'a drupal commerce order')),
'help' => t('The unique internal identifier of the 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',
),
);
// Expose the order number.
$data['commerce_order']['order_number'] = array(
'title' => t('Order number', array(), array('context' => 'a drupal commerce order')),
'help' => t('The unique customer facing number of the order.'),
'field' => array(
'handler' => 'commerce_order_handler_field_order',
'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 order type.
$data['commerce_order']['type'] = array(
'title' => t('Order type', array(), array('context' => 'a drupal commerce order')),
'help' => t('The type of the order.'),
'field' => array(
'handler' => 'commerce_order_handler_field_order_type',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_order_handler_filter_order_type',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the owner uid.
$data['commerce_order']['uid'] = array(
'title' => t('Owner'),
'help' => t('Relate an order to its owner.'),
'relationship' => array(
'handler' => 'views_handler_relationship',
'base' => 'users',
'field' => 'uid',
'label' => t('Order owner'),
),
);
// Expose the order status.
$data['commerce_order']['status'] = array(
'title' => t('Order status'),
'help' => t('The workflow status of the order.'),
'field' => array(
'handler' => 'commerce_order_handler_field_order_status',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_order_handler_filter_order_status',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the order state.
$data['commerce_order']['state'] = array(
'title' => t('Order state'),
'help' => t('The workflow state of the order.'),
'field' => array(
'handler' => 'commerce_order_handler_field_order_state',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_order_handler_filter_order_state',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the created and changed timestamps.
$data['commerce_order']['created'] = array(
'title' => t('Created date'),
'help' => t('The date the order 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_order']['changed'] = array(
'title' => t('Updated date'),
'help' => t('The date the order 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 order.
$data['commerce_order']['view_order'] = array(
'field' => array(
'title' => t('Link'),
'help' => t('Provide a simple link to the administrator view of the order.'),
'handler' => 'commerce_order_handler_field_order_link',
),
);
$data['commerce_order']['edit_order'] = array(
'field' => array(
'title' => t('Edit link'),
'help' => t('Provide a simple link to edit the order.'),
'handler' => 'commerce_order_handler_field_order_link_edit',
),
);
$data['commerce_order']['delete_order'] = array(
'field' => array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete the order.'),
'handler' => 'commerce_order_handler_field_order_link_delete',
),
);
$data['commerce_order']['operations'] = array(
'field' => array(
'title' => t('Operations links'),
'help' => t('Display all the available operations links for the order.'),
'handler' => 'commerce_order_handler_field_order_operations',
),
);
return $data;
}
/**
* Implements hook_views_plugins
*/
function commerce_order_views_plugins() {
return array(
'argument validator' => array(
'current_user_or_role' => array(
'title' => t('Current user or role'),
'handler' => 'commerce_order_plugin_argument_validate_user',
),
),
);
}
<?php
// $Id$
/**
* @file
* Provide an order order_id argument handler.
*/
/**
* Argument handler to accept an order_id.
*/
class commerce_order_handler_argument_order_order_id extends views_handler_argument_numeric {
/**
* Override the behavior of title(). Get the number of the order.
*/
function title_query() {
$titles = array();
$result = db_query("SELECT o.order_number FROM {commerce_order} o WHERE o.order_id IN (:order_ids)", array(':order_ids' => $this->value));
foreach ($result as $order) {
$titles[] = check_plain($order->order_number);
}
return $titles;
}
}
<?php
// $Id$
/**
* @file
* Contains the basic order field handler.
*/
/**
* Field handler to provide simple renderer that allows linking to an order.
*/
class commerce_order_handler_field_order extends views_handler_field {
function init(&$view, &$options) {
parent::init($view, $options);
if (!empty($this->options['link_to_order'])) {
$this->additional_fields['order_id'] = 'order_id';
}
}
function option_definition() {
$options = parent::option_definition();
$options['link_to_order'] = array('default' => FALSE);
return $options;
}
/**
* Provide the link to order option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['link_to_order'] = array(
'#title' => t("Link this field to the order's administrative view page"),
'#description' => t('This will override any other link you have set.'),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['link_to_order']),
);
}
/**
* Render whatever the data is as a link to the order.
*
* Data should be made XSS safe prior to calling this function.
*/
function render_link($data, $values) {
if (!empty($this->options['link_to_order']) && $data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = 'admin/commerce/orders/' . $values->{$this->aliases['order_id']};
}
return $data;
}
function render($values) {
return $this->render_link(check_plain($values->{$this->field_alias}), $values);
}
}
<?php
// $Id$
/**
* Field handler to present an order's operations links.
*/
class commerce_order_handler_field_order_operations extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['order_id'] = 'order_id';
}
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
$order_id = $values->{$this->aliases['order_id']};
$links = menu_contextual_links('commerce-order', 'admin/commerce/orders', array($order_id));
if (!empty($links)) {
drupal_add_css(drupal_get_path('module', 'commerce_order') . '/theme/commerce_order_views.css');
return theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline', 'operations'))));
}
}
}
<?php
// $Id$
/**
* Field handler to translate an order state into its readable form.
*/
class commerce_order_handler_field_order_state extends commerce_order_handler_field_order {
function construct() {
parent::construct();
$this->additional_fields['status'] = 'status';
}
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
$order_status = commerce_order_status_load($values->{$this->aliases['status']});
if (isset($order_status['state'])) {
$order_state = commerce_order_state_load($order_status['state']);
// Only attempt to render a valid order state.
if (!is_array($order_state['title'])) {
return $this->render_link((check_plain($order_state['title'])), $values);
}
}
}
}
<?php
// $Id$
/**
* Field handler to translate an order status into its readable form.
*/
class commerce_order_handler_field_order_status extends commerce_order_handler_field_order {
function render($values) {
$value = commerce_order_status_get_title($values->{$this->field_alias});
// Only attempt to render a valid order status.
if (!is_array($value)) {
return $this->render_link((check_plain($value)), $values);
}
}
}
<?php
// $Id$
/**
* Field handler to translate an order type into its readable form.
*/
class commerce_order_handler_field_order_type extends commerce_order_handler_field_order {
function render($values) {
if (!empty($values->{$this->field_alias})) {
return commerce_order_type_get_name($values->{$this->field_alias});
}
}
}
<?php
// $Id$
/**
* Filter by order state.
*/
class commerce_order_handler_filter_order_state extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('State');
$this->value_options = commerce_order_state_get_title();
}
}
function op_simple() {
if (empty($this->value)) {
return;
}
$this->ensure_my_table();
// For each specified state, build an array of order statuses to check for.
$order_statuses = array();
foreach ($this->value as $state) {
$order_statuses += commerce_order_statuses(array('state' => $state));
}
$this->query->add_where($this->options['group'], "$this->table_alias.status", array_keys($order_statuses), $this->operator);
}
function op_empty() {
$this->ensure_my_table();
if ($this->operator == 'empty') {
$operator = "IS NULL";
}
else {
$operator = "IS NOT NULL";
}
$this->query->add_where($this->options['group'], "$this->table_alias.status", NULL, $operator);
}
}
<?php
// $Id$
/**
* Filter by order status.
*/
class commerce_order_handler_filter_order_status extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Status');
$this->value_options = commerce_order_status_get_title();
}
}
}
<?php
// $Id$
/**
* Filter by order type
*/
class commerce_order_handler_filter_order_type extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Type');
$this->value_options = commerce_order_type_get_name();
}
}
}
<?php
// $Id$
/**
* Validate whether an argument is a valid user.
*
* This supports either numeric arguments (UID) or strings (username) and
* converts either one into the user's UID. This validator also sets the
* argument's title to the username.
*/
class commerce_order_plugin_argument_validate_user extends views_plugin_argument_validate_user {
function option_definition() {
$options = parent::option_definition();
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['restrict_roles'] = array(
'#type' => 'checkbox',
'#title' => t('If the current user does not match the argument, validate the argument for users with specific roles.'),
'#default_value' => $this->options['restrict_roles'],
);
$form['roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Grant access for the selected roles'),
'#description' => t('If no roles are selected, additional access will not be granted.'),
'#options' => user_roles(TRUE),
'#default_value' => $this->options['roles'],
'#multiple' => TRUE,
'#process' => array('form_process_checkboxes', 'ctools_dependent_process'),
'#dependency' => array(
'edit-options-argument-validate-current-user-or-role-restrict-roles' => array(1),
),
'#prefix' => '<div id="edit-options-argument-validate-current-user-or-role-roles-wrapper">',
'#suffix' => '</div>',
);
}
function validate_argument($argument) {
global $user;
$type = $this->options['type'];
// If the argument is an integer and we're accepting the argument as a uid...
if (is_numeric($argument) && $argument == (int) $argument
&& ($type == 'uid' || $type == 'either')) {
// Build the where clause for the argument.
$where = 'uid = :argument';
// If the argument represents the current user...
if ($argument == $user->uid) {
// Set the account variable to the current user.
$account = clone $user;
}
}
// Otherwise accept the argument as a user name if specified.
elseif ($type == 'name' || $type == 'either') {
// Build the where clause for the argument.
$where = "name = :argument";
// If the argument represents the current user...
if ($argument == $user->name) {
// Set the account variable to the current user.
$account = clone $user;
}
}
// If we don't have a where clause, the argument is invalid.
if (empty($where)) {
return FALSE;
}
// If the argument doesn't represent the current user account...
if (empty($account)) {
// Load a pseudo-account object based on the validator's where clause.
$account = db_query("SELECT uid, name FROM {users} WHERE " . $where, array(':argument' => $argument))->fetchObject();
// If the account wasn't found, the argument is invalid.
if (empty($account)) {
return FALSE;
}
}
// If the current user is not the account specified by the argument...
if ($user->uid != $account->uid) {
// And if we're granting access for non-matching users of specific roles...
if (!empty($this->options['restrict_roles']) && !empty($this->options['roles'])) {
// Build an array of role names based on the selected options.
$roles = array();
foreach ($this->options['roles'] as $rid) {
$role = user_role_load($rid);
$roles[$rid] = $role->name;
}
// Look for matching roles on the current user.
$matching_roles = array_intersect($user->roles, $roles);
// Invalidate the argument if the user does not match any of the roles.
if (empty($matching_roles)) {
return FALSE;
}
}
else {
// Otherwise return FALSE if the role based fallback isn't enabled or no
// roles are selected.
return FALSE;
}
}
$this->argument->argument = $account->uid;
$this->argument->validated_title = check_plain($account->name);
return TRUE;
}
}
; $Id$
name = Order UI
description = Exposes a default UI for Orders through order edit forms and default Views.
package = Commerce
core = 7.x
dependencies[] = contextual
dependencies[] = field_ui
dependencies[] = commerce_ui
dependencies[] = commerce_line_item
dependencies[] = commerce_order
dependencies[] = views
<?php
// $Id$
/**
* @file
*/
/**
* Implements hook_menu().
*/
function commerce_order_ui_menu() {
$items = array();
// Note: admin/commerce/orders is defined by a default View.
// Create an order.
$items['admin/commerce/orders/add'] = array(
'title' => 'Create an order',
'description' => 'Create a new order.',
'page callback' => 'commerce_order_ui_order_form_wrapper',
'page arguments' => array(commerce_order_new()),
'access callback' => 'commerce_order_access',
'access arguments' => array('create'),
'weight' => 10,
'file' => 'includes/commerce_order_ui.orders.inc',
);
$items['admin/commerce/orders/add/%user'] = array(
'title' => 'Create an order',
'description' => 'Create a new order for the specified user.',
'page callback' => 'commerce_order_ui_order_form_wrapper',
'page arguments' => array(commerce_order_new(), 4),
'access callback' => 'commerce_order_access',
'access arguments' => array('create'),
'file' => 'includes/commerce_order_ui.orders.inc',
);
$items['admin/commerce/orders/%commerce_order'] = array(
'title callback' => 'commerce_order_ui_order_title',
'title arguments' => array(3),
'page callback' => 'commerce_order_ui_order_view',
'page arguments' => array(3),
'access callback' => 'commerce_order_access',
'access arguments' => array('view', 3),
);
$items['admin/commerce/orders/%commerce_order/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
$items['admin/commerce/orders/%commerce_order/edit'] = array(
'title' => 'Edit',
'page callback' => 'commerce_order_ui_order_form_wrapper',
'page arguments' => array(3),
'access callback' => 'commerce_order_access',
'access arguments' => array('update', 3),
'type' => MENU_LOCAL_TASK,
'weight' => -5,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
'file' => 'includes/commerce_order_ui.orders.inc',
);
$items['admin/commerce/orders/%commerce_order/delete'] = array(
'title' => 'Delete',
'page callback' => 'commerce_order_ui_order_delete_form_wrapper',
'page arguments' => array(3),
'access callback' => 'commerce_order_access',
'access arguments' => array('update', 3),
'type' => MENU_LOCAL_TASK,
'weight' => 20,
'context' => MENU_CONTEXT_INLINE,
'file' => 'includes/commerce_order_ui.orders.inc',
);
$items['admin/commerce/config/order'] = array(
'title' => 'Order settings',
'description' => 'Configure general order settings, fields, and displays.',
'page callback' => 'drupal_get_form',
'page arguments' => array('commerce_order_settings_form'),
'access arguments' => array('administer orders'),
'file' => 'includes/commerce_order_ui.orders.inc',
);
$items['admin/commerce/config/order/settings'] = array(
'title' => 'Settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['user/%user/orders/%commerce_order'] = array(
'title callback' => 'commerce_order_ui_order_title',
'title arguments' => array(3),
'page callback' => 'commerce_order_ui_order_view',
'page arguments' => array(3, 'customer', FALSE),
'access callback' => 'commerce_order_access',
'access arguments' => array('view', 3),
);
return $items;
}
/**
* Menu item title callback: returns the number of an order for its pages.
*
* @param $order
* The order object as loaded via the URL wildcard.
* @return
* A page title of the format "Order ##".
*/
function commerce_order_ui_order_title($order) {
return t('Order @number', array('@number' => $order->order_number));
}
/**
* Implements hook_menu_alter().
*/
function commerce_order_ui_menu_alter(&$items) {
// Transform the field UI tabs into contextual links.
$items['admin/commerce/config/order/settings/fields']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
$items['admin/commerce/config/order/settings/display']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function commerce_order_ui_menu_local_tasks_alter(&$data, $router_item, $root_path) {
// Add action link 'admin/commerce/orders/add' on 'admin/commerce/orders'.
if ($root_path == 'admin/commerce/orders') {
$item = menu_get_item('admin/commerce/orders/add');
if ($item['access']) {
$data['actions']['output'][] = array(
'#theme' => 'menu_local_action',
'#link' => $item,
);
}
}
}
/**
* Implements hook_help().
*/
function commerce_order_ui_help($path, $arg) {
// Display a user configurable help text on the order add page.
if (strpos($path, 'admin/commerce/orders/add') === 0) {
$help = variable_get('commerce_order_help_text', '');
if (!empty($help)) {
return '<p>' . filter_xss_admin($help) . '</p>';
}
}
}
/**
* Implements hook_entity_info_alter().
*/
function commerce_order_ui_entity_info_alter(&$entity_info) {
// Add a URI callback to the order entity.
$entity_info['commerce_order']['uri callback'] = 'commerce_order_ui_uri';
// Expose the order UI for order fields.
$entity_info['commerce_order']['bundles']['commerce_order']['admin'] = array(
'path' => 'admin/commerce/config/order',
'real path' => 'admin/commerce/config/order',
'access arguments' => array('administer orders'),
);
}
/**
* Entity uri callback: points to the admin view page of the given order.
*/
function commerce_order_ui_uri($order) {
// Only return a value if the user has permission to view the order.
if (commerce_order_access('view', $order)) {
return array(
'path' => 'admin/commerce/orders/' . $order->order_id,
);
}
return NULL;
}
/**
* Implements hook_forms().
*/
function commerce_order_ui_forms($form_id, $args) {
$forms = array();
// Define a wrapper ID for the order add / edit form.
$forms['commerce_order_ui_order_form'] = array(
'callback' => 'commerce_order_order_form',
);
// Define a wrapper ID for the order delete confirmation form.
$forms['commerce_order_ui_order_delete_form'] = array(
'callback' => 'commerce_order_order_delete_form',
);
return $forms;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Order UI module instantiates the Order 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.
*
* @see commerce_order_ui_order_form()
*/
function commerce_order_ui_form_commerce_order_ui_order_form_alter(&$form, &$form_state) {
// Add a submit handler to the save button to add a redirect.
$form['actions']['submit']['#submit'][] = 'commerce_order_ui_order_form_submit';
}
/**
* Submit callback for commerce_order_ui_order_form().
*
* @see commerce_order_ui_form_commerce_order_ui_order_form_alter()
*/
function commerce_order_ui_order_form_submit($form, &$form_state) {
// Apply the redirect based on the clicked button.
if ($form_state['clicked_button']['#value'] == t('Save order', array(), array('context' => 'a drupal commerce order'))) {
drupal_set_message(t('Order saved.'));
$form_state['redirect'] = 'admin/commerce/orders/' . $form_state['commerce_order']->order_id . '/edit';
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Order UI module instantiates the Order 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_order_ui_order_delete_form()
*/
function commerce_order_ui_form_commerce_order_ui_order_delete_form_alter(&$form, &$form_state) {
$form['actions']['cancel']['#markup'] = l(t('Cancel'), 'admin/commerce/orders');
$form['#submit'][] = 'commerce_order_ui_order_delete_form_submit';
}
/**
* Submit callback for commerce_order_ui_order_delete_form().
*
* @see commerce_order_ui_form_commerce_order_ui_order_delete_form_alter()
*/
function commerce_order_ui_order_delete_form_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/commerce/orders';
}
/**
* Implements hook_views_api().
*/
function commerce_order_ui_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_order_ui') . '/includes/views',
);
}
/**
* Sets the breadcrumb for order pages.
*
* @param $view_mode
* The view mode for the current order page, 'administrator' or 'customer'.
*/
function commerce_order_ui_set_breadcrumb($view_mode = 'administrator') {
$breadcrumb = array();
// Create the breadcrumb array based on the view mode.
if ($view_mode == 'administrator') {
$breadcrumb = array(
l(t('Home'), '<front>'),
l(t('Administration'), 'admin'),
l(t('Store'), 'admin/commerce'),
l(t('Orders', array(), array('context' => 'a drupal commerce order')), 'admin/commerce/orders'),
);
}
drupal_set_breadcrumb($breadcrumb);
}
/**
* 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_ui_order_view($order, $view_mode = 'administrator', $breadcrumb = TRUE) {
// Set the breadcrumb for the appropriate view mode if specified.
if ($breadcrumb) {
commerce_order_ui_set_breadcrumb();
}
return commerce_order_build_content($order, $view_mode);
}
<?php
// $Id$
/**
* @file
* Page callbacks and form builder functions for administering orders.
*/
/**
* Form callback: edit the global order settings.
*/
function commerce_order_settings_form($form, &$form_state) {
$form['commerce_order_help_text'] = array(
'#type' => 'textarea',
'#title' => t('Order creation help text'),
'#description' => t('Supply an optional help message to be displayed above the order add form.'),
'#default_value' => variable_get('commerce_order_help_text', ''),
);
return system_settings_form($form);
}
/**
* Form callback wrapper: create or edit an order.
*
* @param $order
* The order object to edit through the form.
* @param $account
* For new orders, the customer's user account.
*
* @see commerce_order_order_form()
*/
function commerce_order_ui_order_form_wrapper($order, $account = NULL) {
// Add the breadcrumb for the form's location.
commerce_order_ui_set_breadcrumb();
// Set the page title and a default customer if necessary.
if (empty($order->order_id)) {
drupal_set_title(t('Create an order'));
if (!empty($account)) {
$order->uid = $account->uid;
}
}
// Include the forms file from the Order module.
module_load_include('inc', 'commerce_order', 'includes/commerce_order.forms');
return drupal_get_form('commerce_order_ui_order_form', $order);
}
/**
* Form callback wrapper: confirmation form for deleting an order.
*
* @param $order
* The order object to delete through the form.
*
* @see commerce_order_order_delete_form()
*/
function commerce_order_ui_order_delete_form_wrapper($order) {
// Add the breadcrumb for the form's location.
commerce_order_ui_set_breadcrumb();
// Include the forms file from the Order module.
module_load_include('inc', 'commerce_order', 'includes/commerce_order.forms');
return drupal_get_form('commerce_order_ui_order_delete_form', $order);
}
<?php
// $Id$
/**
* Views for the default order UI.
*/
/**
* Implements hook_views_default_views().
*/
function commerce_order_ui_views_default_views() {
$views = array();
// Order admin list at admin/commerce/orders.
$view = new view;
$view->name = 'commerce_orders';
$view->description = 'Display a list of orders for the store admin.';
$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'] = 'Orders';
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['access']['perm'] = 'administer orders';
$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['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'order_number' => 'order_number',
'created' => 'created',
'name' => 'name',
'entity_id' => 'entity_id',
'status' => 'status',
'operations' => 'operations',
);
$handler->display->display_options['style_options']['default'] = 'created';
$handler->display->display_options['style_options']['info'] = array(
'order_number' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'created' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'name' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'entity_id' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'status' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
),
'operations' => array(
'align' => '',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
$handler->display->display_options['style_options']['order'] = 'desc';
/* Empty text: Global: Text area */
$handler->display->display_options['empty']['text']['id'] = 'area';
$handler->display->display_options['empty']['text']['table'] = 'views';
$handler->display->display_options['empty']['text']['field'] = 'area';
$handler->display->display_options['empty']['text']['empty'] = FALSE;
$handler->display->display_options['empty']['text']['content'] = 'No orders have been created yet.';
/* Relationship: Commerce Order: Owner */
$handler->display->display_options['relationships']['uid']['id'] = 'uid';
$handler->display->display_options['relationships']['uid']['table'] = 'commerce_order';
$handler->display->display_options['relationships']['uid']['field'] = 'uid';
$handler->display->display_options['relationships']['uid']['required'] = 0;
/* Field: Commerce Order: Order number */
$handler->display->display_options['fields']['order_number']['id'] = 'order_number';
$handler->display->display_options['fields']['order_number']['table'] = 'commerce_order';
$handler->display->display_options['fields']['order_number']['field'] = 'order_number';
$handler->display->display_options['fields']['order_number']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['trim'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['order_number']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['order_number']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['html'] = 0;
$handler->display->display_options['fields']['order_number']['hide_empty'] = 0;
$handler->display->display_options['fields']['order_number']['empty_zero'] = 0;
$handler->display->display_options['fields']['order_number']['link_to_order'] = 1;
/* Field: Commerce Order: Created date */
$handler->display->display_options['fields']['created']['id'] = 'created';
$handler->display->display_options['fields']['created']['table'] = 'commerce_order';
$handler->display->display_options['fields']['created']['field'] = 'created';
$handler->display->display_options['fields']['created']['label'] = 'Created';
$handler->display->display_options['fields']['created']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['created']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['created']['alter']['trim'] = 0;
$handler->display->display_options['fields']['created']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['created']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['created']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['created']['alter']['html'] = 0;
$handler->display->display_options['fields']['created']['hide_empty'] = 0;
$handler->display->display_options['fields']['created']['empty_zero'] = 0;
/* 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'] = 'Owner';
$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']['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: Fields: commerce_order_total */
$handler->display->display_options['fields']['entity_id']['id'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['table'] = 'field_data_commerce_order_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']['external'] = 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,
);
$handler->display->display_options['fields']['entity_id']['field_api_classes'] = 0;
/* Field: Commerce Order: Order status */
$handler->display->display_options['fields']['status']['id'] = 'status';
$handler->display->display_options['fields']['status']['table'] = 'commerce_order';
$handler->display->display_options['fields']['status']['field'] = 'status';
$handler->display->display_options['fields']['status']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['status']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['status']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['status']['alter']['external'] = 0;
$handler->display->display_options['fields']['status']['alter']['trim'] = 0;
$handler->display->display_options['fields']['status']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['status']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['status']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['status']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['status']['alter']['html'] = 0;
$handler->display->display_options['fields']['status']['element_label_colon'] = 1;
$handler->display->display_options['fields']['status']['element_default_classes'] = 1;
$handler->display->display_options['fields']['status']['hide_empty'] = 0;
$handler->display->display_options['fields']['status']['empty_zero'] = 0;
$handler->display->display_options['fields']['status']['link_to_order'] = 0;
/* Field: Commerce Order: Operations links */
$handler->display->display_options['fields']['operations']['id'] = 'operations';
$handler->display->display_options['fields']['operations']['table'] = 'commerce_order';
$handler->display->display_options['fields']['operations']['field'] = 'operations';
$handler->display->display_options['fields']['operations']['label'] = 'Operations';
$handler->display->display_options['fields']['operations']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['operations']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['operations']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['operations']['alter']['trim'] = 0;
$handler->display->display_options['fields']['operations']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['operations']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['operations']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['operations']['alter']['html'] = 0;
$handler->display->display_options['fields']['operations']['hide_empty'] = 0;
$handler->display->display_options['fields']['operations']['empty_zero'] = 0;
/* Display: Admin page */
$handler = $view->new_display('page', 'Admin page', 'admin_page');
$handler->display->display_options['path'] = 'admin/commerce/orders/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'] = 'Orders';
$handler->display->display_options['tab_options']['description'] = 'Manage orders in the store.';
$handler->display->display_options['tab_options']['weight'] = '';
$handler->display->display_options['tab_options']['name'] = 'management';
$translatables['commerce_orders'] = array(
t('Defaults'),
t('Orders'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort By'),
t('Asc'),
t('Desc'),
t('Items per page'),
t('- All -'),
t('Offset'),
t('No orders have been created yet.'),
t('Order owner'),
t('Order number'),
t('Created'),
t('Owner'),
t('Total'),
t('Order status'),
t('Operations'),
t('Admin page'),
);
$views[$view->name] = $view;
// User order history displayed at user/%/orders.
$view = new view;
$view->name = 'commerce_user_orders';
$view->description = 'Display a list of completed orders for a user.';
$view->tag = 'commerce';
$view->base_table = 'commerce_order';
$view->human_name = 'User Orders';
$view->core = 7;
$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'] = 'Orders';
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['access']['perm'] = 'view own orders';
$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'] = 25;
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'order_number' => 'order_number',
'name' => 'name',
'created' => 'created',
'status' => 'status',
'operations' => 'operations',
);
$handler->display->display_options['style_options']['default'] = 'created';
$handler->display->display_options['style_options']['info'] = array(
'order_number' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'name' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'created' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'status' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'operations' => array(
'align' => '',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
$handler->display->display_options['style_options']['order'] = 'desc';
/* Empty text: Global: Text area */
$handler->display->display_options['empty']['text']['id'] = 'area';
$handler->display->display_options['empty']['text']['table'] = 'views';
$handler->display->display_options['empty']['text']['field'] = 'area';
$handler->display->display_options['empty']['text']['empty'] = FALSE;
$handler->display->display_options['empty']['text']['content'] = 'You have not placed any orders with us yet.';
/* Relationship: Commerce Order: Owner */
$handler->display->display_options['relationships']['uid']['id'] = 'uid';
$handler->display->display_options['relationships']['uid']['table'] = 'commerce_order';
$handler->display->display_options['relationships']['uid']['field'] = 'uid';
$handler->display->display_options['relationships']['uid']['required'] = 0;
/* Field: User: Uid */
$handler->display->display_options['fields']['uid']['id'] = 'uid';
$handler->display->display_options['fields']['uid']['table'] = 'users';
$handler->display->display_options['fields']['uid']['field'] = 'uid';
$handler->display->display_options['fields']['uid']['relationship'] = 'uid';
$handler->display->display_options['fields']['uid']['exclude'] = TRUE;
$handler->display->display_options['fields']['uid']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['uid']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['uid']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['uid']['alter']['trim'] = 0;
$handler->display->display_options['fields']['uid']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['uid']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['uid']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['uid']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['uid']['alter']['html'] = 0;
$handler->display->display_options['fields']['uid']['element_label_colon'] = 1;
$handler->display->display_options['fields']['uid']['element_default_classes'] = 1;
$handler->display->display_options['fields']['uid']['hide_empty'] = 0;
$handler->display->display_options['fields']['uid']['empty_zero'] = 0;
$handler->display->display_options['fields']['uid']['link_to_user'] = 1;
/* Field: Commerce Order: Order ID */
$handler->display->display_options['fields']['order_id']['id'] = 'order_id';
$handler->display->display_options['fields']['order_id']['table'] = 'commerce_order';
$handler->display->display_options['fields']['order_id']['field'] = 'order_id';
$handler->display->display_options['fields']['order_id']['exclude'] = TRUE;
$handler->display->display_options['fields']['order_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['order_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['order_id']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['order_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['order_id']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['order_id']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['order_id']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['order_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['order_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['order_id']['element_label_colon'] = 1;
$handler->display->display_options['fields']['order_id']['element_default_classes'] = 1;
$handler->display->display_options['fields']['order_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['order_id']['empty_zero'] = 0;
$handler->display->display_options['fields']['order_id']['link_to_order'] = 0;
/* Field: Commerce Order: Order number */
$handler->display->display_options['fields']['order_number']['id'] = 'order_number';
$handler->display->display_options['fields']['order_number']['table'] = 'commerce_order';
$handler->display->display_options['fields']['order_number']['field'] = 'order_number';
$handler->display->display_options['fields']['order_number']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['make_link'] = 1;
$handler->display->display_options['fields']['order_number']['alter']['path'] = 'user/[uid]/orders/[order_id]';
$handler->display->display_options['fields']['order_number']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['trim'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['order_number']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['order_number']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['order_number']['alter']['html'] = 0;
$handler->display->display_options['fields']['order_number']['element_label_colon'] = 1;
$handler->display->display_options['fields']['order_number']['element_default_classes'] = 1;
$handler->display->display_options['fields']['order_number']['hide_empty'] = 0;
$handler->display->display_options['fields']['order_number']['empty_zero'] = 0;
$handler->display->display_options['fields']['order_number']['link_to_order'] = 0;
/* Field: Commerce Order: Created date */
$handler->display->display_options['fields']['created']['id'] = 'created';
$handler->display->display_options['fields']['created']['table'] = 'commerce_order';
$handler->display->display_options['fields']['created']['field'] = 'created';
$handler->display->display_options['fields']['created']['label'] = 'Created';
$handler->display->display_options['fields']['created']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['created']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['created']['alter']['trim'] = 0;
$handler->display->display_options['fields']['created']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['created']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['created']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['created']['alter']['html'] = 0;
$handler->display->display_options['fields']['created']['hide_empty'] = 0;
$handler->display->display_options['fields']['created']['empty_zero'] = 0;
/* Field: Commerce Order: Updated date */
$handler->display->display_options['fields']['changed']['id'] = 'changed';
$handler->display->display_options['fields']['changed']['table'] = 'commerce_order';
$handler->display->display_options['fields']['changed']['field'] = 'changed';
$handler->display->display_options['fields']['changed']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['changed']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['changed']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['changed']['alter']['trim'] = 0;
$handler->display->display_options['fields']['changed']['alter']['nl2br'] = 0;
$handler->display->display_options['fields']['changed']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['changed']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['changed']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['changed']['alter']['html'] = 0;
$handler->display->display_options['fields']['changed']['element_label_colon'] = 1;
$handler->display->display_options['fields']['changed']['element_default_classes'] = 1;
$handler->display->display_options['fields']['changed']['hide_empty'] = 0;
$handler->display->display_options['fields']['changed']['empty_zero'] = 0;
/* Field: Commerce Order: Order status */
$handler->display->display_options['fields']['status']['id'] = 'status';
$handler->display->display_options['fields']['status']['table'] = 'commerce_order';
$handler->display->display_options['fields']['status']['field'] = 'status';
$handler->display->display_options['fields']['status']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['status']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['status']['alter']['trim'] = 0;
$handler->display->display_options['fields']['status']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['status']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['status']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['status']['alter']['html'] = 0;
$handler->display->display_options['fields']['status']['hide_empty'] = 0;
$handler->display->display_options['fields']['status']['empty_zero'] = 0;
$handler->display->display_options['fields']['status']['link_to_order'] = 0;
/* Argument: User: Uid */
$handler->display->display_options['arguments']['uid']['id'] = 'uid';
$handler->display->display_options['arguments']['uid']['table'] = 'users';
$handler->display->display_options['arguments']['uid']['field'] = 'uid';
$handler->display->display_options['arguments']['uid']['relationship'] = 'uid';
$handler->display->display_options['arguments']['uid']['default_action'] = 'not found';
$handler->display->display_options['arguments']['uid']['style_plugin'] = 'default_summary';
$handler->display->display_options['arguments']['uid']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['uid']['validate_type'] = 'current_user_or_role';
$handler->display->display_options['arguments']['uid']['validate_options']['restrict_roles'] = '1';
$handler->display->display_options['arguments']['uid']['validate_options']['roles'] = array(
3 => '3',
);
$handler->display->display_options['arguments']['uid']['break_phrase'] = 0;
$handler->display->display_options['arguments']['uid']['not'] = 0;
/* Filter: Commerce Order: Order state */
$handler->display->display_options['filters']['state']['id'] = 'state';
$handler->display->display_options['filters']['state']['table'] = 'commerce_order';
$handler->display->display_options['filters']['state']['field'] = 'state';
$handler->display->display_options['filters']['state']['operator'] = 'not in';
$handler->display->display_options['filters']['state']['value'] = array(
'cart' => 'cart',
'checkout' => 'checkout',
);
/* Display: User Orders */
$handler = $view->new_display('page', 'User Orders', 'order_page');
$handler->display->display_options['path'] = 'user/%/orders';
$handler->display->display_options['menu']['type'] = 'tab';
$handler->display->display_options['menu']['title'] = 'Orders';
$handler->display->display_options['menu']['weight'] = '15';
$handler->display->display_options['tab_options']['type'] = 'normal';
$handler->display->display_options['tab_options']['title'] = 'Orders';
$handler->display->display_options['tab_options']['description'] = 'User orders in the store.';
$handler->display->display_options['tab_options']['weight'] = '';
$handler->display->display_options['tab_options']['name'] = 'user-menu';
$translatables['commerce_user_orders'] = array(
t('Defaults'),
t('Orders'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort By'),
t('Asc'),
t('Desc'),
t('Items per page'),
t('- All -'),
t('Offset'),
t('You have not placed any orders with us yet.'),
t('Order owner'),
t('Uid'),
t('Order ID'),
t('Order number'),
t('user/[uid]/orders/[order_id]'),
t('Created'),
t('Updated date'),
t('Order status'),
t('All'),
t('User Orders'),
);
$views[$view->name] = $view;
return $views;
}
/* $Id$ */
.views-field-operations .links.operations {
text-transform: lowercase;
margin-left: 0;
}
<?php
// $Id$
/**
* @file
* Hooks provided by the Payment module.
*/
/**
* Defines rows for use in payment totals area handlers on Views.
*
* The payment totals area handler totals the amount of payments received by
* currency for all the payment transactions in a View. The array of totals are
* is used to build a table containing rows for each of the totals and/or the
* remaining balance of the order by default. Other modules may use this hook to
* add additional rows to the table.
*
* @param $totals
* An array of payment totals whose keys are currency codes and values are the
* total amount paid in each currency.
* @param $order
* If available, the order object to which the payments apply.
*
* @return
* An array of table row data as expected by theme_table(). Row arrays may
* contain an additional weight key with the value being an integer used to
* sort the rows prior to display.
*
* @see commerce_payment_commerce_payment_totals_rows()
* @see commerce_payment_commerce_payment_totals_row_info()
* @see theme_table()
*/
function hook_commerce_payment_totals_row_info($totals, $order) {
$rows = array();
if (count($totals) <= 1) {
// Add a row for the remaining balance on the order.
if ($order) {
$balance = commerce_payment_order_balance($order, $totals);
$rows[] = array(
'data' => array(
array('data' => t('Order balance'), 'class' => array('label')),
array('data' => commerce_currency_format($balance['amount'], $balance['currency_code']), 'class' => array('balance')),
),
'class' => array('order-balance'),
'weight' => 10,
);
}
}
return $rows;
}
/**
* Allows you to alter payment totals rows.
*
* @param $rows
* Array of payment totals rows exposed by
* hook_commerce_payment_totals_row_info() implementations.
* @param $totals
* An array of payment totals whose keys are currency codes and values are the
* total amount paid in each currency.
* @param $order
* If available, the order object to which the payments apply.
*
* @see hook_commerce_payment_totals_row_info()
*/
function hook_commerce_payment_totals_row_info_alter(&$rows, $totals, $order) {
// Alter the weight of order balance rows to appear first.
foreach ($rows as $key => &$row) {
if (in_array('order-balance', $row['class'])) {
$row['weight'] = -10;
}
}
}
/**
* Populates an order's data array with payment methods available in checkout.
*
* The Payment module primarily depends on Rules to populate the payment method
* checkout pane with options using an action that enables a particular payment
* method for use. The action adds payment method instance information to the
* order's data array that is used by the pane form to add options to the radio
* select element. This hook may be used to do the same thing, meaning it should
* not return any information but update the order object's data array just like
* the payment method enabling action.
*
* It should be noted that using Rules is the preferred method, as this hook is
* being made available secondarily through the use of rules_invoke_all().
*
* @param $order
* The order object represented on the checkout form.
*
* @see commerce_payment_pane_checkout_form()
* @see commerce_payment_enable_method()
* @see rules_invoke_all()
*/
function hook_commerce_payment_methods(&$order) {
// No example. See commerce_payment_enable_method() for a guide to what you
// must add to the order's data array.
}
/**
* Allows you to prepare payment transaction data before it is saved.
*
* @param $transaction
* The payment transaction object to be saved.
*
* @see rules_invoke_all()
*/
function hook_commerce_payment_transaction_presave(&$transaction) {
// No example.
}
<?php
// $Id$
/**
* @file
* Callback functions for the Payment module's checkout panes.
*/
/**
* Payment pane: form callback.
*/
function commerce_payment_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
$pane_form = array();
// Invoke the payment methods event that will populate the order with
// an array of method IDs for available payment methods.
$order->payment_methods = array();
rules_invoke_all('commerce_payment_methods', $order);
// Sort the payment methods array by the enabling Rules' weight values.
uasort($order->payment_methods, 'drupal_sort_weight');
// Generate an array of payment method options for the checkout form.
$options = array();
foreach ($order->payment_methods as $instance_id => $method_info) {
// Ensure we've received a valid payment method.
if ($payment_method = commerce_payment_method_load($method_info['method_id'])) {
$options[$instance_id] = $payment_method['display_title'];
}
}
// If no payment methods were found, return the empty form.
if (empty($options)) {
return $pane_form;
}
// Store the payment methods in the form for validation purposes.
$pane_form['payment_methods'] = array(
'#type' => 'value',
'#value' => $order->payment_methods,
);
// If at least one payment option is available...
if (!empty($options)) {
// Add a radio select widget to specify the payment method.
$pane_form['payment_method'] = array(
'#type' => 'radios',
'#options' => $options,
'#ajax' => array(
'callback' => 'commerce_payment_pane_checkout_form_details_refresh',
'wrapper' => 'payment-details',
),
);
// Find the default payment method using either the preselected value stored
// in the order / checkout pane or the first available method.
$pane_values = !empty($form_state['values']) ? $form_state['values'][$checkout_pane['pane_id']] : array();
if (isset($pane_values['payment_method']) && isset($options[$pane_values['payment_method']])) {
$default_value = $pane_values['payment_method'];
}
elseif (isset($order->data['payment_method']) && isset($options[$order->data['payment_method']])) {
$default_value = $order->data['payment_method'];
}
else {
reset($options);
$default_value = key($options);
}
// Set the default value for the payment method radios.
$pane_form['payment_method']['#default_value'] = $default_value;
// Add the payment method specific form elements.
$method_info = $order->payment_methods[$pane_form['payment_method']['#default_value']];
$payment_method = commerce_payment_method_load($method_info['method_id']);
$payment_method['settings'] = $method_info['settings'];
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form')) {
$pane_form['payment_details'] = $callback($payment_method, $pane_values, $checkout_pane, $order);
}
else {
$pane_form['payment_details'] = array();
}
$pane_form['payment_details']['#prefix'] = '<div id="payment-details">';
$pane_form['payment_details']['#suffix'] = '</div>';
}
return $pane_form;
}
/**
* Returns the payment details element for display via AJAX.
*/
function commerce_payment_pane_checkout_form_details_refresh($form, $form_state) {
return $form['commerce_payment']['payment_details'];
}
/**
* Payment pane: validation callback.
*/
function commerce_payment_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) {
$pane_form = $form[$checkout_pane['pane_id']];
$pane_values = $form_state['values'][$checkout_pane['pane_id']];
// Only attempt validation if there were payment methods available.
if ($pane_values['payment_methods']) {
// If the selected payment method was changed...
if ($pane_values['payment_method'] != $pane_form['payment_method']['#default_value']) {
// And the newly selected method has a valid form callback...
if ($payment_method = commerce_payment_method_instance_load($pane_values['payment_method'])) {
if (commerce_payment_method_callback($payment_method, 'submit_form')) {
// Fail validation so the form is rebuilt to include the payment method
// specific form elements.
return FALSE;
}
}
}
// Delegate validation to the payment method callback.
$payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_validate')) {
$result = $callback($payment_method, $pane_form['payment_details'], $pane_values['payment_details'], $order, array($checkout_pane['pane_id'], 'payment_details'));
// To prevent payment method validation routines from having to return TRUE
// explicitly, only return FALSE if it was specifically returned. Otherwise
// default to TRUE.
return $result === FALSE ? FALSE : TRUE;
}
}
// Nothing to validate.
return TRUE;
}
/**
* Payment pane: submit callback.
*/
function commerce_payment_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
$pane_form = $form[$checkout_pane['pane_id']];
$pane_values = $form_state['values'][$checkout_pane['pane_id']];
// Only process if there were payment methods available.
if ($pane_values['payment_methods']) {
$order->data['payment_method'] = $pane_values['payment_method'];
// If we can calculate a single order total for the order...
if ($balance = commerce_payment_order_balance($order)) {
// Delegate submit to the payment method callback.
$payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_submit')) {
// If payment fails, rebuild the checkout form without progressing.
if ($callback($payment_method, $pane_form['payment_details'], $pane_values['payment_details'], $order, $balance) === FALSE) {
$form_state['rebuild'] = TRUE;
}
}
}
}
}
/**
* Payment redirect pane: form callback.
*/
function commerce_payment_redirect_pane_checkout_form(&$form, &$form_state, $checkout_pane, $order) {
// First load the order's specified payment method instance.
if (!empty($order->data['payment_method'])) {
$payment_method = commerce_payment_method_instance_load($order->data['payment_method']);
}
else {
$payment_method = FALSE;
}
// If the payment method doesn't exist or does not require a redirect...
if (!$payment_method || !$payment_method['offsite']) {
// Advance the customer to the next step of the checkout process.
_commerce_payment_redirect_pane_next_page($form, $form_state, $order);
}
// If the user came to the cancel URL...
if (arg(3) == 'back' && arg(4) == $order->data['payment_redirect_key']) {
// Send the customer to the previous step of the checkout process.
_commerce_payment_redirect_pane_prev_page($form, $form_state, $order);
}
// If the user came to the return URL...
if (arg(3) == 'return' && arg(4) == $order->data['payment_redirect_key']) {
// Check for a validate handler on return.
$validate_callback = commerce_payment_method_callback($payment_method, 'redirect_form_validate');
// If there is no validate handler or if there is and it passess...
if (!$validate_callback || $validate_callback($order, $payment_method)) {
// Perform any submit functions if necessary.
if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_submit')) {
$callback($order, $payment_method);
}
// Send the customer on to the next checkout page.
_commerce_payment_redirect_pane_next_page($form, $form_state, $order);
}
else {
// Otherwise display the failure message and send the customer back.
drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
_commerce_payment_redirect_pane_prev_page($form, $form_state, $order);
}
}
// If the function to build the redirect form exists...
if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form')) {
// Generate a key to use in the return URL from the redirected service.
$order->data['payment_redirect_key'] = drupal_hash_base64(time());
commerce_order_save($order);
// Merge the new form into the current form array, preserving the help text
// if it exists. We also add a wrapper so the form can be easily submitted.
$form += drupal_get_form($callback, $order, $payment_method);
$form['#prefix'] = '<div class="payment-redirect-form">';
$form['#suffix'] = '</div>';
}
else {
// Alert the administrator that the module does not provide a required form.
drupal_set_message(t('The %title payment method indicates it is offsite but does not define the necessary form to process the redirect.', array('%title' => $payment_method['title'])), 'error');
}
}
/**
* Utility function: return a payment redirect page for POST.
*
* @param $action
* The destination URL the values should be posted to.
* @param $values
* An associative array of values that will be posted to the destination URL.
* @return
* A renderable array.
*/
function commerce_payment_post_redirect_form($action, array $values = array()) {
$form = array(
'#type' => 'form',
'#action' => $action,
'#method' => 'POST',
'#id' => '',
'#attributes' => array(),
);
foreach ($values as $key => $value) {
$form[$value] = array(
'#type' => 'hidden',
'#name' => $key,
'#value' => $value,
'#id' => '',
'#attributes' => array(),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#id' => '',
'#value' => t('Proceed to payment'),
);
return array(
'form' => array(
'#type' => 'markup',
'#markup' => drupal_render($form),
),
);
}
/**
* Moves an order ahead to the next page via an order update and redirect.
*/
function _commerce_payment_redirect_pane_next_page($form, &$form_state, $order) {
$next_page = $form_state['checkout_page']['next_page'];
$order = commerce_order_status_update($order, 'checkout_' . $next_page);
// Inform modules of checkout completion if the next page is Completed.
if ($next_page == 'complete') {
commerce_checkout_complete($order);
}
drupal_goto('checkout/' . $order->order_id . '/' . $next_page);
}
/**
* Moves an order back to the previous page via an order update and redirect.
*/
function _commerce_payment_redirect_pane_prev_page($form, &$form_state, $order) {
$prev_page = $form_state['checkout_page']['prev_page'];
$order = commerce_order_status_update($order, 'checkout_' . $prev_page);
drupal_goto('checkout/' . $order->order_id . '/' . $prev_page);
}
<?php
// $Id$
/**
* @file
* Credit-card helper functions for Drupal commerce.
*/
/**
* Returns a set of credit card form elements that payment method modules can
* incorporate into their submission form callbacks.
*
* @param $default
* An array of default values for the available CC fields.
* @param $fields
* An array specifying the CC fields that should be included on the form; the
* card number and expiration date fields are always present.
*/
function commerce_payment_credit_card_form($fields = array(), $default = array()) {
// Merge default values into the default array.
$default += array(
'type' => '',
'owner' => '',
'number' => '',
'start_month' => '',
'start_year' => '',
'exp_month' => '',
'exp_year' => '',
'issue' => '',
'code' => '',
'bank' => '',
);
$form['credit_card'] = array(
'#tree' => TRUE,
);
// Add a card type selector if specified.
if (isset($fields['type'])) {
$form['credit_card']['type'] = array(
'#type' => 'select',
'#title' => t('Card type'),
'#options' => array_intersect_key(commerce_payment_credit_card_types(), drupal_map_assoc((array) $fields['type'])),
'#default_value' => $default['type'],
);
}
// Add a field for the credit card owner if specified.
if (isset($fields['owner'])) {
$form['credit_card']['owner'] = array(
'#type' => 'textfield',
'#title' => t('Card owner'),
'#default_value' => $default['owner'],
'#attributes' => array('autocomplete' => 'off'),
'#required' => TRUE,
'#maxlength' => 64,
'#size' => 32,
);
}
// Always add a field for the credit card number.
$form['credit_card']['number'] = array(
'#type' => 'textfield',
'#title' => t('Card number'),
'#default_value' => $default['number'],
'#attributes' => array('autocomplete' => 'off'),
'#required' => TRUE,
'#maxlength' => 19,
'#size' => 20,
);
// Add fields for the credit card start date if specified.
if (isset($fields['start_date'])) {
$form['credit_card']['start_month'] = array(
'#type' => 'select',
'#title' => t('Start month'),
'#options' => commerce_months(),
'#default_value' => $default['start_month'],
);
$form['credit_card']['start_year'] = array(
'#type' => 'select',
'#title' => t('Start year'),
'#options' => drupal_map_assoc(range(date('Y') - 10, date('Y'))),
'#default_value' => $default['start_year'],
);
}
// Always add fields for the credit card expiration date.
$form['credit_card']['exp_month'] = array(
'#type' => 'select',
'#title' => t('Expiration month'),
'#options' => commerce_months(),
'#default_value' => $default['exp_month'],
);
$form['credit_card']['exp_year'] = array(
'#type' => 'select',
'#title' => t('Expiration Year'),
'#options' => drupal_map_assoc(range(date('Y'), date('Y') + 20)),
'#default_value' => $default['exp_year'],
);
// Add a field for the card issue number if specified.
if (isset($fields['issue'])) {
$form['credit_card']['issue'] = array(
'#type' => 'textfield',
'#title' => t('Issue number'),
'#default_value' => $default['issue'],
'#attributes' => array('autocomplete' => 'off'),
'#required' => empty($fields['issue']) ? FALSE : TRUE,
'#maxlength' => 2,
'#size' => 2,
);
}
// Add a field for the security code if specified.
if (isset($fields['code'])) {
$form['credit_card']['code'] = array(
'#type' => 'textfield',
'#title' => !empty($fields['code']) ? $fields['code'] : t('Security code'),
'#default_value' => $default['code'],
'#attributes' => array('autocomplete' => 'off'),
'#required' => TRUE,
'#maxlength' => 4,
'#size' => 4,
);
}
// Add a field for the issuing bank if specified.
if (isset($fields['bank'])) {
$form['credit_card']['bank'] = array(
'#type' => 'textfield',
'#title' => t('Issuing bank'),
'#default_value' => $default['bank'],
'#attributes' => array('autocomplete' => 'off'),
'#required' => TRUE,
'#maxlength' => 64,
'#size' => 32,
);
}
return $form;
}
/**
* Validates a set of credit card details entered via the credit card form.
*
* @param $details
* An array of credit card details as retrieved from the credit card array in
* the form values of a form containing the credit card form.
*
* @return
* TRUE or FALSE indicating the validity of all the data.
*
* @see commerce_payment_credit_card_form()
*/
function commerce_payment_credit_card_validate($details, $settings) {
$prefix = implode('][', $settings['form_parents']) . '][';
$valid = TRUE;
// Validate the credit card number.
if (!commerce_payment_validate_credit_card_number($details['number'])) {
form_set_error($prefix . 'number', t('You have entered an invalid credit card number.'));
$valid = FALSE;
}
// Validate the expiration date.
if (($invalid = commerce_payment_validate_credit_card_exp_date($details['exp_month'], $details['exp_year'])) !== TRUE) {
form_set_error($prefix . 'exp_' . $invalid, t('You have entered an expired credit card.'));
$valid = FALSE;
}
// Validate the security code if present.
if (isset($details['code']) && !commerce_payment_validate_credit_card_security_code($details['number'], $details['code'])) {
form_set_error($prefix . 'code', t('You have entered an invalid card security code.'));
$valid = FALSE;
}
// Validate the start date if present.
if (isset($details['start_date']) && ($invalid = commerce_payment_validate_credit_card_start_date($details['exp_month'], $details['exp_year'])) !== TRUE) {
form_set_error($prefix . 'start_' . $invalid, t('Your have entered an invalid start date.'));
$valid = FALSE;
}
// Validate the issue number if present.
if (isset($details['issue']) && !commerce_payment_validate_credit_card_issue($details['issue'])) {
form_set_error($prefix . 'issue', t('You have entered an invalid issue number.'));
$valid = FALSE;
}
return $valid;
}
/**
* Validates a credit card number using card types and the Luhn algorithm.
*
* @param $number
* The credit card number to validate.
* @param $card_types
* An array of credit card types containing any of the keys from the array
* returned by commerce_payment_credit_card_types(). If any are specified,
* only those card types will pass validation.
*
* @return
* TRUE or FALSE indicating the number's validity.
*
* @see http://www.merriampark.com/anatomycc.htm
* @see commerce_payment_credit_card_types()
*/
function commerce_payment_validate_credit_card_number($number, $card_types = array()) {
// Ensure every character in the number is numeric.
if (!ctype_digit($number)) {
return FALSE;
}
// Check for valid credit card types if specified.
if (!empty($card_types)) {
switch (substr($number, 0, 1)) {
case '3':
$card_type = 'amex';
break;
case '4':
$card_type = 'visa';
break;
case '5':
$card_type = 'mastercard';
break;
case '6':
$card_type = 'discover';
break;
}
if (!in_array($card_type, $card_types)) {
return FALSE;
}
}
// Validate the number using the Luhn algorithm.
$total = 0;
for ($i = 0; $i < strlen($number); $i++) {
$digit = substr($number, $i, 1);
if ((strlen($number) - $i - 1) % 2) {
$digit *= 2;
if ($digit > 9) {
$digit -= 9;
}
}
$total += $digit;
}
if ($total % 10 != 0) {
return FALSE;
}
return TRUE;
}
/**
* Validates a credit card start date.
*
* @param $month
* The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12.
* @param $year
* The 4-digit numeric representation of the year, i.e. 2010.
*
* @return
* TRUE for cards whose start date is blank (both month and year) or in the
* past, 'year' or 'month' for expired cards indicating which value should
* receive the error.
*/
function commerce_payment_validate_credit_card_start_date($month, $year) {
if (empty($month) && empty($year)) {
return TRUE;
}
if (empty($month) || empty($year)) {
return empty($month) ? 'month' : 'year';
}
if ($year > date('Y')) {
return 'year';
}
elseif ($year == date('Y')) {
if ($month > date('n')) {
return 'month';
}
}
return TRUE;
}
/**
* Validates a credit card expiration date.
*
* @param $month
* The 1 or 2-digit numeric representation of the month, i.e. 1, 6, 12.
* @param $year
* The 4-digit numeric representation of the year, i.e. 2010.
*
* @return
* TRUE for non-expired cards, 'year' or 'month' for expired cards indicating
* which value should receive the error.
*/
function commerce_payment_validate_credit_card_exp_date($month, $year) {
if ($year < date('Y')) {
return 'year';
}
elseif ($year == date('Y')) {
if ($month < date('n')) {
return 'month';
}
}
return TRUE;
}
/**
* Validates that an issue number is numeric if present.
*/
function commerce_payment_validate_credit_card_issue($issue) {
if (empty($issue) || (is_numeric($issue) && $issue > 0)) {
return TRUE;
}
return FALSE;
}
/**
* Validates a card security code based on the type of the credit card.
*
* @param $number
* The number of the credit card to validate the security code against.
* @param $code
* The card security code to validate with the given number.
*
* @return
* TRUE or FALSE indicating the security code's validity.
*/
function commerce_payment_validate_credit_card_security_code($number, $code) {
// Ensure the code is numeric.
if (!ctype_digit($code)) {
return FALSE;
}
// Check the length based on the type of the credit card.
switch (substr($number, 0, 1)) {
case '3':
return strlen($code) == 4;
case '4':
case '5':
case '6':
return strlen($code) == 3;
}
}
/**
* Returns an associative array of credit card types.
*/
function commerce_payment_credit_card_types() {
return array(
'visa' => t('Visa'),
'mastercard' => t('MasterCard'),
'discover' => t('Discover'),
'amex' => t('American Express'),
'maestro' => t('Maestro'),
'delta' => t('Visa Debit / Delta'),
'dc' => t("Diner's Club"),
'jcb' => t('JCB'),
'laser' => t('Laser')
);
}
/* $Id$ */
.views-field-status img.pending {
position: relative;
top: 3px;
}
.payment-totals-table {
width: 33%;
float: right;
}
.payment-totals-table tr.order-balance {
background-color: #D3E9F4;
font-weight: bold;
}
.payment-totals-table .total,
.payment-totals-table .balance {
text-align: right;
}
.add-payment .form-item-payment-method {
display: inline;
}
.views-field-operations .links.operations {
text-transform: lowercase;
margin-left: 0;
}
.payment-terminal {
clear: right;
}
.payment-terminal-amount div {
display: inline;
}
table.payment-transaction td {
vertical-align: top;
}
<?php
// $Id$
/**
* @file
* Defines forms for creating and administering payment transactions.
*/
/**
* Allows an administrator to choose a payment method type and add a transaction
* for a specific order.
*
* @param $order
* The order to add the transaction to.
*/
function commerce_payment_order_transaction_add_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_payment') . '/includes/commerce_payment.forms.inc';
// Store the initial order in the form state.
$form_state['order'] = $order;
$form['#access'] = commerce_payment_transaction_access('create', $order);
// If a payment method has already been selected...
if (!empty($form_state['payment_method'])) {
$payment_method = $form_state['payment_method'];
$form['payment_terminal'] = array(
'#type' => 'fieldset',
'#title' => t('Payment terminal: @title', array('@title' => $payment_method['title'])),
'#attributes' => array('class' => array('payment-terminal')),
);
// Establish defaults for the amount if possible.
if ($balance = commerce_payment_order_balance($order)) {
$default_amount = $balance['amount'] > 0 ? $balance['amount'] : '';
$default_currency_code = $balance['currency_code'];
}
else {
$default_amount = '';
$default_currency_code = commerce_default_currency();
}
$form['payment_terminal']['amount'] = array(
'#type' => 'textfield',
'#title' => t('Amount'),
'#default_value' => $default_amount,
'#size' => 10,
'#prefix' => '<div class="payment-terminal-amount">',
);
// Build a currency options list from all enabled currencies.
$options = array();
foreach (commerce_currencies(TRUE) as $currency_code => $currency) {
$options[$currency_code] = check_plain($currency['code']);
}
$form['payment_terminal']['currency_code'] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => $default_currency_code,
'#suffix' => '</div>',
);
// Find the values already submitted for the payment terminal.
$terminal_values = !empty($form_state['values']['payment_details']) ? $form_state['values']['payment_details'] : array();
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form')) {
$form['payment_terminal']['payment_details'] = $callback($payment_method, $terminal_values, NULL, $order);
}
else {
$form['payment_terminal']['payment_details'] = array();
}
$form['payment_terminal']['payment_details']['#tree'] = TRUE;
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
}
else {
// Otherwise present the payment method selection form.
$event = rules_get_cache('event_commerce_payment_methods');
// Build an options array of all available payment methods that can setup
// transactions using the local terminal. If there is more than one instance
// of any payment method available on site, list them in optgroups using the
// payment method title.
$instances = array();
$options = array();
$optgroups = FALSE;
// Only build the options array if payment method Rules are enabled.
if (!empty($event)) {
foreach (commerce_payment_methods() as $method_id => $payment_method) {
// Only check payment methods that should appear on the terminal.
if ($payment_method['terminal']) {
// Look for a Rule enabling this payment method.
foreach ($event->getIterator() as $rule) {
foreach ($rule->actions() as $action) {
// If an action is found, add its instance to the options array.
if ($action->getElementName() == 'commerce_payment_enable_' . $method_id) {
$instances[check_plain($payment_method['title'])][] = array(
'instance_id' => commerce_payment_method_instance_id($method_id, $rule),
'label' => check_plain($rule->label()),
);
// If this is the second instance for this payment method, turn
// on optgroups.
if (count($instances[check_plain($payment_method['title'])]) > 1) {
$optgroups = TRUE;
}
}
}
}
}
}
// Build an options array based on whether or not optgroups are necessary.
foreach ($instances as $optgroup => $values) {
foreach ($values as $value) {
if ($optgroups) {
$options[$optgroup][$value['instance_id']] = $value['label'];
}
else {
$options[$value['instance_id']] = $value['label'];
}
}
}
}
if (!empty($options)) {
$form['payment_method'] = array(
'#type' => 'select',
'#options' => $options,
'#prefix' => '<div class="add-payment">',
);
$form['add_payment'] = array(
'#type' => 'submit',
'#value' => t('Add payment'),
'#suffix' => '</div>',
'#ajax' => array(
'callback' => 'commerce_payment_order_transaction_add_form_add_refresh',
'wrapper' => 'commerce-payment-order-transaction-add-form',
),
);
}
else {
$form['payment_method'] = array(
'#markup' => t('No payment methods available to add payments.'),
);
}
}
return $form;
}
/**
* Returns the full payment terminal form when a payment method is selected.
*/
function commerce_payment_order_transaction_add_form_add_refresh($form, $form_state) {
return $form;
}
/**
* Validation callback for commerce_payment_order_transaction_add_form().
*/
function commerce_payment_order_transaction_add_form_validate($form, &$form_state) {
// If the button used to submit was not the "Add payment" button, give the
// payment method a chance to validate the input.
if ($form_state['values']['op'] != t('Add payment')) {
$payment_method = $form_state['payment_method'];
$order = $form_state['order'];
// Find out if the payment details are valid before attempting to process.
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_validate')) {
$callback($payment_method, $form['payment_terminal']['payment_details'], $form_state['values']['payment_details'], $order, array('payment_details'));
}
}
}
/**
* Submit callback for commerce_payment_order_transaction_add_form().
*/
function commerce_payment_order_transaction_add_form_submit($form, &$form_state) {
// If the "Add payment" button was clicked...
if ($form_state['values']['op'] == t('Add payment')) {
// Store the payment method in the form state and rebuild the form.
$form_state['payment_method'] = commerce_payment_method_instance_load($form_state['values']['payment_method']);
$form_state['rebuild'] = TRUE;
}
else {
$payment_method = $form_state['payment_method'];
$order = $form_state['order'];
// Delegate submit to the payment method callback.
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_submit')) {
$charge = array(
'amount' => $form_state['values']['amount'],
'currency_code' => $form_state['values']['currency_code'],
);
$callback($payment_method, $form['payment_terminal']['payment_details'], $form_state['values']['payment_details'], $order, $charge);
}
drupal_set_message(t('Payment transaction created.'));
}
}
/**
* Form callback: confirmation form for deleting a transaction.
*
* @param $transaction
* The payment transaction object to be deleted.
*
* @see confirm_form()
*/
function commerce_payment_payment_transaction_delete_form($form, &$form_state, $order, $transaction) {
$form_state['order'] = $order;
$form_state['transaction'] = $transaction;
// Load and store the payment method.
$payment_method = commerce_payment_method_load($transaction->payment_method);
$form_state['payment_method'] = $payment_method;
// 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_payment') . '/includes/commerce_payment.forms.inc';
$form['#submit'][] = 'commerce_payment_payment_transaction_delete_form_submit';
$form = confirm_form($form,
t('Are you sure you want to delete this transaction?'),
'',
'<p>' . t('@amount paid via %method on @date. Deleting this transaction cannot be undone.', array('@amount' => commerce_currency_format($transaction->amount, $transaction->currency_code), '%method' => $payment_method['title'], '@date' => format_date($transaction->created, 'short'))) . '</p>',
t('Delete'),
t('Cancel'),
'confirm'
);
return $form;
}
/**
* Submit callback for commerce_payment_transaction_delete_form().
*/
function commerce_payment_payment_transaction_delete_form_submit($form, &$form_state) {
$transaction = $form_state['transaction'];
if (commerce_payment_transaction_delete($transaction->transaction_id)) {
drupal_set_message(t('Payment transaction deleted.'));
watchdog('commerce_payment', 'Deleted payment transaction @transaction.', array('@transaction' => $transaction->transaction_id), WATCHDOG_NOTICE);
}
else {
drupal_set_message(t('The payment transaction could not be deleted.'), 'error');
}
}
; $Id$
name = Payment
description = Implement core payment features for Drupal commerce.
package = Commerce
dependencies[] = rules
dependencies[] = commerce
dependencies[] = commerce_order
core = 7.x
; Module includes
files[] = commerce_payment.rules.inc
files[] = includes/commerce_payment_transaction.controller.inc
; Views handlers
files[] = includes/views/handlers/commerce_payment_handler_area_totals.inc
files[] = includes/views/handlers/commerce_payment_handler_field_amount.inc
files[] = includes/views/handlers/commerce_payment_handler_field_currency_code.inc
files[] = includes/views/handlers/commerce_payment_handler_field_message.inc
files[] = includes/views/handlers/commerce_payment_handler_field_payment_method.inc
files[] = includes/views/handlers/commerce_payment_handler_field_payment_transaction_link.inc
files[] = includes/views/handlers/commerce_payment_handler_field_payment_transaction_link_delete.inc
files[] = includes/views/handlers/commerce_payment_handler_field_payment_transaction_operations.inc
files[] = includes/views/handlers/commerce_payment_handler_field_status.inc
files[] = includes/views/handlers/commerce_payment_handler_filter_payment_method.inc
files[] = includes/views/handlers/commerce_payment_handler_filter_payment_transaction_status.inc
files[] = includes/views/handlers/commerce_payment_handler_filter_currency_code.inc
files[] = includes/views/handlers/commerce_payment_handler_field_balance.inc
; Tests
;files[] = tests/commerce_payment.test
<?php
// $Id$
/**
* @file
* Provides metadata for the payment transaction entity.
*/
/**
* Implements hook_entity_property_info().
*/
function commerce_payment_entity_property_info() {
$info = array();
// Add meta-data about the basic commerce_payment_transaction properties.
$properties = &$info['commerce_payment_transaction']['properties'];
$properties['transaction_id'] = array(
'label' => t('Transaction ID'),
'description' => t('The internal numeric ID of the transaction.'),
'type' => 'integer',
);
$properties['uid'] = array(
'label' => t('User ID'),
'type' => 'integer',
'description' => t("The unique ID of the user who created the transaction."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer payment transactions',
'clear' => array('user'),
'query callback' => 'entity_metadata_table_query',
);
$properties['user'] = array(
'label' => t('User'),
'type' => 'user',
'description' => t("The user who created the transaction."),
'getter callback' => 'commerce_payment_transaction_get_properties',
'setter callback' => 'commerce_payment_transaction_set_properties',
'setter permission' => 'administer payment transactions',
'required' => TRUE,
'clear' => array('uid'),
);
$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 transaction belongs to."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer payment transactions',
'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 transaction belongs to."),
'getter callback' => 'commerce_payment_transaction_get_properties',
'setter callback' => 'commerce_payment_transaction_set_properties',
'setter permission' => 'administer payment transactions',
'required' => TRUE,
'clear' => array('order_id'),
);
$properties['payment_method'] = array(
'label' => t('Payment method'),
'description' => t('The payment method of the transaction.'),
'type' => 'token',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer payment transactions',
'options list' => 'commerce_payment_method_options_list',
'required' => TRUE,
'query callback' => 'entity_metadata_table_query',
);
$properties['created'] = array(
'label' => t('Date created'),
'description' => t('The date the payment was created.'),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer payment transactions',
);
$properties['changed'] = array(
'label' => t('Date updated'),
'description' => t('The date the payment was last updated.'),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer payment transactions',
);
$properties['remote_id'] = array(
'label' => t('Remote id'),
'description' => t('The remote identifier for this transaction.'),
'type' => 'text',
'query callback' => 'entity_metadata_table_query',
);
$properties['amount'] = array(
'label' => t('Amount'),
'description' => t('The amount for this transaction.'),
'type' => 'decimal',
'query callback' => 'entity_metadata_table_query',
);
$properties['currency_code'] = array(
'label' => t('Currency Code'),
'description' => t('The currency code for this transaction.'),
'type' => 'text',
'query callback' => 'entity_metadata_table_query',
);
$properties['message'] = array(
'label' => t('Message'),
'description' => t('Message for this transaction.'),
'type' => 'text',
'getter callback' => 'commerce_payment_transaction_get_properties',
);
$properties['status'] = array(
'label' => t('Status'),
'description' => t('Status of the payment transaction.'),
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer payment transactions',
);
$properties['remote_status'] = array(
'label' => t('Remote status'),
'description' => t('Remote status of the payment transaction.'),
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer payment transactions',
);
return $info;
}
<?php
// $Id$
/**
* @file
* Installation functions for Drupal Commerce Payment.
*/
/**
* Implements hook_schema().
*/
function commerce_payment_schema() {
$schema = array();
$schema['commerce_payment_transaction'] = array(
'description' => 'Transaction information for every attempted payment.',
'fields' => array(
'transaction_id' => array(
'description' => 'The primary identifier for a transaction.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'revision_id' => array(
'description' => 'The current {commerce_payment_transaction_revision}.revision_id version identifier.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'uid' => array(
'description' => 'The {users}.uid that created this transaction.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'order_id' => array(
'description' => 'The {commerce_order}.order_id of the order this payment is for.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'payment_method' => array(
'description' => 'The payment method method_id for this transaction.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
),
'instance_id' => array(
'description' => 'The payment method instance ID for this transaction.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'remote_id' => array(
'description' => 'The remote identifier for this transaction.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'message' => array(
'description' => 'The human-readable message associated to this transaction.',
'type' => 'text',
'size' => 'big',
'not null' => TRUE,
),
'message_variables' => array(
'description' => 'The variables associated with the human-readable message.',
'type' => 'blob',
'size' => 'big',
'not null' => TRUE,
'serialize' => TRUE,
),
'amount' => array(
'description' => 'The amount of this transaction.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'currency_code' => array(
'description' => 'The currency code for the price.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'status' => array(
'description' => 'The status of this transaction (pending, success, or failure).',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
),
'remote_status' => array(
'description' => 'The status of the transaction at the payment provider.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
),
'payload' => array(
'description' => 'The payment-gateway specific payload associated with this transaction.',
'type' => 'blob',
'size' => 'big',
'not null' => TRUE,
'serialize' => TRUE,
),
'created' => array(
'description' => 'The Unix timestamp when this transaction was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'changed' => array(
'description' => 'The Unix timestamp when this transaction was last changed.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('transaction_id'),
'unique keys' => array(
'revision_id' => array('revision_id'),
),
'indexes' => array(
'payment_method' => array('payment_method'),
),
'foreign keys' => array(
'payment_transaction_revision' => array(
'table' => 'commerce_payment_transaction_revision',
'columns'=> array('revision_id' => 'revision_id'),
),
'creator' => array(
'table' => 'users',
'columns' => array('uid' => 'uid'),
),
'order_id' => array(
'table' => 'commerce_order',
'columns'=> array('order_id' => 'order_id'),
),
),
);
$schema['commerce_payment_transaction_revision'] = array(
'description' => 'Saves information about each saved revision of a {commerce_payment_transaction}.',
'fields' => array(
'transaction_id' => array(
'description' => 'The primary identifier for a transaction.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'revision_id' => array(
'description' => 'The current {commerce_payment_transaction_revision}.revision_id version identifier.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'revision_uid' => array(
'description' => 'The {users}.uid that created this revision.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'remote_id' => array(
'description' => 'The remote identifier for this transaction.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'message' => array(
'description' => 'The human-readable message associated to this transaction.',
'type' => 'text',
'size' => 'big',
'not null' => TRUE,
),
'message_variables' => array(
'description' => 'The variables associated with the human-readable message.',
'type' => 'blob',
'size' => 'big',
'not null' => TRUE,
'serialize' => TRUE,
),
'amount' => array(
'description' => 'The amount of this transaction.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'currency_code' => array(
'description' => 'The currency code for the price.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'status' => array(
'description' => 'The status of this transaction (pending, success, or failure).',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
),
'remote_status' => array(
'description' => 'The status of the transaction at the payment provider.',
'type' => 'varchar',
'length' => 128,
'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,
),
),
'primary key' => array('revision_id'),
'foreign keys' => array(
'payment_transaction' => array(
'table' => 'commerce_payment_transaction',
'columns'=> array('transaction_id' => 'transaction_id'),
),
'creator' => array(
'table' => 'users',
'columns' => array('revision_uid' => 'uid'),
),
),
);
return $schema;
}
<?php
// $Id$
/**
* @file
* Defines the payment system and checkout integration.
*/
// Local payment transaction status definitions:
// Pending is used when a transaction has been initialized but is still awaiting
// resolution; e.g. a CC authorization awaiting capture or an e-check payment
// pending at the payment provider.
define('COMMERCE_PAYMENT_STATUS_PENDING', 'pending');
// Success is used when a transaction has completed resulting in money being
// transferred from the customer to the store or vice versa.
define('COMMERCE_PAYMENT_STATUS_SUCCESS', 'success');
// Failure is used when a transaction cannot be completed or is rejected.
define('COMMERCE_PAYMENT_STATUS_FAILURE', 'failure');
// Credit card transaction types definitions:
// Used to just authorize an amount on a credit card account.
define('COMMERCE_CREDIT_AUTH_ONLY', 'authorize');
// Used to capture funds from a prior authorization.
define('COMMERCE_CREDIT_PRIOR_AUTH_CAPTURE', 'prior_auth_capture');
// Used to authorize and capture money all at once.
define('COMMERCE_CREDIT_AUTH_CAPTURE', 'auth_capture');
// Used to set up a credit card reference through the payment gateway.
define('COMMERCE_CREDIT_REFERENCE_SET', 'reference_set');
// Used to capture funds using a credit card reference.
define('COMMERCE_CREDIT_REFERENCE_TXN', 'reference_txn');
// Used to remove a reference from the payment gateway.
define('COMMERCE_CREDIT_REFERENCE_REMOVE', 'reference_remove');
// Used to credit funds to a reference at the payment gateway.
define('COMMERCE_CREDIT_REFERENCE_CREDIT', 'reference_credit');
// Used to credit funds to a credit card account.
define('COMMERCE_CREDIT_CREDIT', 'credit');
// Used to void a transaction before the transaction clears.
define('COMMERCE_CREDIT_VOID', 'void');
/**
* Implements of hook_entity_info().
*/
function commerce_payment_entity_info() {
$return = array(
'commerce_payment_transaction' => array(
'label' => t('Payment transaction'),
'controller class' => 'CommercePaymentTransactionEntityController',
'base table' => 'commerce_payment_transaction',
'revision table' => 'commerce_payment_transaction_revision',
'fieldable' => FALSE,
'entity keys' => array(
'id' => 'transaction_id',
'revision' => 'revision_id',
'bundle' => 'payment_method',
'label' => 'transaction_id', // TODO: Update to use a custom callback.
),
'bundle keys' => array(
'bundle' => 'payment_method',
),
'bundles' => array(),
'load hook' => 'commerce_payment_transaction_load',
'view modes' => array(
'administrator' => array(
'label' => t('Administrator'),
'custom settings' => FALSE,
),
),
'creation callback' => '_commerce_payment_transaction_create',
'save callback' => 'commerce_payment_transaction_save',
'deletion callback' => 'commerce_payment_transaction_delete',
'access callback' => 'commerce_payment_transaction_access',
'token type' => 'payment',
),
);
foreach (commerce_payment_methods() as $method_id => $payment_method) {
$return['commerce_payment_transaction']['bundles'][$method_id] = array(
'label' => $payment_method['title'],
);
}
return $return;
}
/**
* Implements hook_permission().
*/
function commerce_payment_permission() {
return array(
'administer payments' => array(
'title' => t('Administer payments'),
'description' => t('Allows users to perform any payment action for any order and view transaction payloads.'),
'restrict access' => TRUE,
),
'view payments' => array(
'title' => t('View payments'),
'description' => t('Allows users to view the payments made to an order.'),
'restrict access' => TRUE,
),
'create payments' => array(
'title' => t('Create payments'),
'description' => t('Allows users to create new payments for orders.'),
'restrict access' => TRUE,
),
'update payments' => array(
'title' => t('Update payments'),
'description' => t('Allows users to update payments via payment method specific operations links.'),
'restrict access' => TRUE,
),
'delete payments' => array(
'title' => t('Delete payments'),
'description' => t('Allows users to delete payments on orders they can access.'),
'restrict access' => TRUE,
),
);
}
/**
* Implements hook_theme().
*/
function commerce_payment_theme() {
return array(
'commerce_payment_transaction' => array(
'variables' => array('order' => NULL, 'transaction' => NULL, 'view_mode' => NULL),
),
'commerce_payment_transaction_status_text' => array(
'variables' => array('text' => NULL, 'transaction_status' => NULL),
),
'commerce_payment_transaction_status_icon' => array(
'variables' => array('transaction_status' => NULL),
),
'commerce_payment_totals' => array(
'variables' => array('rows' => array(), 'form' => NULL, 'totals' => array(), 'view' => NULL),
'path' => drupal_get_path('module', 'commerce_payment') . '/theme',
'template' => 'commerce-payment-totals',
),
);
}
/**
* Adds the necessary CSS for the line item summary template.
*/
function template_preprocess_commerce_payment_totals(&$variables) {
drupal_add_css(drupal_get_path('module', 'commerce_payment') . '/theme/commerce_payment.css');
}
/**
* Implements hook_commerce_checkout_page_info().
*/
function commerce_payment_commerce_checkout_page_info() {
$checkout_pages = array();
$checkout_pages['payment'] = array(
'title' => t('Payment'),
'help' => t('Use the button below to proceed to the payment server.'),
'status_cart' => FALSE,
'locked' => TRUE,
'buttons' => FALSE,
'weight' => 20,
);
return $checkout_pages;
}
/**
* Implements hook_commerce_checkout_pane_info().
*/
function commerce_payment_commerce_checkout_pane_info() {
$checkout_panes = array();
$checkout_panes['commerce_payment'] = array(
'title' => t('Payment'),
'page' => 'review',
'locked' => TRUE,
'file' => 'includes/commerce_payment.checkout_pane.inc',
'base' => 'commerce_payment_pane',
'weight' => 10,
);
$checkout_panes['commerce_payment_redirect'] = array(
'title' => t('Off-site payment redirect'),
'page' => 'payment',
'locked' => TRUE,
'file' => 'includes/commerce_payment.checkout_pane.inc',
'base' => 'commerce_payment_redirect_pane',
);
return $checkout_panes;
}
/**
* Implements hook_commerce_payment_totals_row_info().
*/
function commerce_payment_commerce_payment_totals_row_info($totals, $order) {
$rows = array();
if (count($totals) == 0) {
// Add a row for the remaining balance on the order.
if ($order) {
$balance = commerce_payment_order_balance($order, $totals);
$rows[] = array(
'data' => array(
array('data' => t('Order balance'), 'class' => array('label')),
array('data' => commerce_currency_format($balance['amount'], $balance['currency_code']), 'class' => array('balance')),
),
'class' => array('order-balance'),
'weight' => 10,
);
}
}
elseif (count($totals) == 1) {
// Otherwise if there's only a single currency total...
$currency_code = key($totals);
// Add a row for the total amount paid.
$rows[] = array(
'data' => array(
array('data' => t('Total paid'), 'class' => array('label')),
array('data' => commerce_currency_format($totals[$currency_code], $currency_code), 'class' => array('total')),
),
'class' => array('total-paid'),
'weight' => 0,
);
// Add a row for the remaining balance on the order.
if ($order) {
$balance = commerce_payment_order_balance($order, $totals);
$rows[] = array(
'data' => array(
array('data' => t('Order balance'), 'class' => array('label')),
array('data' => commerce_currency_format($balance['amount'], $balance['currency_code']), 'class' => array('balance')),
),
'class' => array('order-balance'),
'weight' => 10,
);
}
}
else {
$weight = 0;
foreach ($totals as $currency_code => $amount) {
$rows[] = array(
'data' => array(
array('data' => t('Total paid (@currency_code)', array('@currency_code' => $currency_code)), 'class' => array('label')),
array('data' => commerce_currency_format($amount, $currency_code), 'class' => array('total')),
),
'class' => array('total-paid', 'total-' . $currency_code),
'weight' => $weight++,
);
}
}
return $rows;
}
/**
* Implements hook_views_api().
*/
function commerce_payment_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_payment') . '/includes/views',
);
}
/**
* Returns an array of payment methods defined by enabled modules.
*
* @return
* An associative array of payment method objects keyed by the method_id.
*/
function commerce_payment_methods() {
$payment_methods = &drupal_static(__FUNCTION__);
// If the payment methods haven't been defined yet, do so now.
if (!isset($payment_methods)) {
$payment_methods = module_invoke_all('commerce_payment_method_info');
drupal_alter('commerce_payment_method_info', $payment_methods);
foreach ($payment_methods as $method_id => &$payment_method) {
$defaults = array(
'method_id' => $method_id,
'base' => $method_id,
'title' => '',
'description' => '',
'active' => FALSE,
'terminal' => TRUE,
'offsite' => FALSE,
'callbacks' => array(),
);
$payment_method += $defaults;
// Default the display title to the title if necessary. The display title
// is used in instances where the payment method has an official name used
// as the title (i.e. PayPal WPS) but a different method of display on
// selection forms (like some text and a set of images).
if (empty($payment_method['display_title'])) {
$payment_method['display_title'] = $payment_method['title'];
}
// Default the short title to the title if necessary. Like the display
// title, the short title is an alternate way of displaying the title to
// the user consisting of plain text but with unnecessary information
// stripped off. The payment method title might be PayPal WPS as it
// distinguishes itself from other PayPal payment services, but you would
// only want to display PayPal to the customer as their means of payment.
if (empty($payment_methods[$method_id]['short_title'])) {
$payment_method['short_title'] = $payment_method['title'];
}
// Merge in default callbacks.
foreach (array('settings_form', 'submit_form', 'submit_form_validate', 'submit_form_submit', 'redirect_form', 'redirect_form_validate', 'redirect_form_submit') as $callback) {
if (!isset($payment_method['callbacks'][$callback])) {
$payment_method['callbacks'][$callback] = $payment_method['base'] . '_' . $callback;
}
}
}
}
return $payment_methods;
}
/**
* Returns a payment method array.
*
* @param $method_id
* The ID of the payment method to return.
*
* @return
* The fully loaded payment method object or FALSE if not found.
*/
function commerce_payment_method_load($method_id) {
$payment_methods = commerce_payment_methods();
return isset($payment_methods[$method_id]) ? $payment_methods[$method_id] : FALSE;
}
/**
* Returns the title of any or all payment methods.
*
* @param $title
* String indicating which title to return, either 'title', 'display_title',
* or 'short_title'.
* @param $method
* Optional parameter specifying the payment method whose title to return.
*
* @return
* Either an array of all payment method titles keyed by the machine name or a
* string containing the title for the specified type. If a type is specified
* that does not exist, this function returns FALSE.
*/
function commerce_payment_method_get_title($title = 'title', $method = NULL) {
$payment_methods = commerce_payment_methods();
// Return a method title if specified and it exists.
if (!empty($method)) {
if (isset($payment_methods[$method])) {
return $payment_methods[$method][$title];
}
else {
// Return FALSE if it does not exist.
return FALSE;
}
}
// Otherwise turn the array values into the specified title only.
foreach ($payment_methods as $key => $value) {
$payment_methods[$key] = $value[$title];
}
return $payment_methods;
}
/**
* Wraps commerce_payment_method_get_title() for the Entity module.
*/
function commerce_payment_method_options_list() {
return commerce_payment_method_get_title();
}
/**
* Returns the specified callback for the given payment method if it exists.
*
* @param $payment_method
* The payment method object.
* @param $callback
* The callback function to return, one of:
* - settings_form
* - submit_form
* - submit_form_validate
* - submit_form_submit
* - redirect_form
* - redirect_form_validate
* - redirect_form_submit
*
* @return
* A string containing the name of the callback function or FALSE if it could
* not be found.
*/
function commerce_payment_method_callback($payment_method, $callback) {
// If the specified callback function exists, return it.
if (!empty($payment_method['callbacks'][$callback]) &&
function_exists($payment_method['callbacks'][$callback])) {
return $payment_method['callbacks'][$callback];
}
// Otherwise return FALSE.
return FALSE;
}
/**
* Returns a payment method instance ID given a payment method ID and the Rule
* containing an enabling action with settings.
*
* @param $method_id
* The ID of the payment method.
* @param $rule
* The Rules configuration object used to provide settings for the method.
*
* @return
* A string used as the payment method instance ID.
*/
function commerce_payment_method_instance_id($method_id, $rule) {
$parts = array($method_id, $rule->name);
return implode('|', $parts);
}
/**
* Returns a payment method instance object which includes the settings specific
* to the context of the instance.
*
* @param $instance_id
* A payment method instance ID in the form of a pipe delimited string
* containing the method_id and the enabling Rule's name.
*
* @return
* The payment method instance object which is identical to the payment method
* object with the addition of the settings array.
*/
function commerce_payment_method_instance_load($instance_id) {
// Return FALSE if there is no pipe delimeter in the instance ID.
if (strpos($instance_id, '|') === FALSE) {
return FALSE;
}
// Explode the method key into its component parts.
list($method_id, $rule_name) = explode('|', $instance_id);
// Return FALSE if we didn't receive a proper instance ID.
if (empty($method_id) || empty($rule_name)) {
return FALSE;
}
// First load the payment method and add the instance ID.
$payment_method = commerce_payment_method_load($method_id);
$payment_method['instance_id'] = $instance_id;
// Then load the Rule configuration that enables the method.
$rule = rules_config_load($rule_name);
// Iterate over its actions to find one with the matching element ID and pull
// its settings into the payment method object.
$payment_method['settings'] = array();
foreach ($rule->actions() as $action) {
if ($action->getElementName() == 'commerce_payment_enable_' . $method_id) {
if (is_array($action->settings['payment_method']) && !empty($action->settings['payment_method']['settings'])) {
$payment_method['settings'] = $action->settings['payment_method']['settings'];
}
}
}
return $payment_method;
}
/**
* Returns an array of transaction payment status objects for the defined
* payment statuses.
*
* This function invokes hook_commerce_payment_transaction_status_info() so
* other payment modules can define statuses if necessary. However, it doesn't
* allow for altering so that existing payment methods cannot be unset. It still
* does perform an array merge in such a way that the properties for the three
* core statuses defined by this module may be overridden if the same keys are
* used in another module's implementation of the info hook.
*/
function commerce_payment_transaction_statuses() {
$transaction_statuses = &drupal_static(__FUNCTION__);
// If the statuses haven't been defined yet, do so now.
if (!isset($transaction_statuses)) {
$transaction_statuses = module_invoke_all('commerce_payment_transaction_status_info');
$transaction_statuses += array(
COMMERCE_PAYMENT_STATUS_PENDING => array(
'status' => COMMERCE_PAYMENT_STATUS_PENDING,
'title' => t('Pending'),
'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-pending.png',
'total' => FALSE,
),
COMMERCE_PAYMENT_STATUS_SUCCESS => array(
'status' => COMMERCE_PAYMENT_STATUS_SUCCESS,
'title' => t('Success'),
'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-success.png',
'total' => TRUE,
),
COMMERCE_PAYMENT_STATUS_FAILURE => array(
'status' => COMMERCE_PAYMENT_STATUS_FAILURE,
'title' => t('Failure'),
'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-failure.png',
'total' => FALSE,
),
);
}
return $transaction_statuses;
}
/**
* Themes the icon representing a payment transaction status.
*/
function theme_commerce_payment_transaction_status_icon($variables) {
$transaction_status = $variables['transaction_status'];
return '<img src="' . url($transaction_status['icon'], array('absolute' => TRUE)) . '" class="' . drupal_html_class($transaction_status['status']) . '" title="' . $transaction_status['title'] . '" alt="' . $transaction_status['title'] . '" />';
}
/**
* Themes a text value related to a payment transaction status.
*/
function theme_commerce_payment_transaction_status_text($variables) {
$transaction_status = $variables['transaction_status'];
return '<span class="' . drupal_html_class($transaction_status['status']) . '">' . $variables['text'] . '</span>';
}
/**
* Returns the payment transaction status object for the specified status.
*
* @param $status
* The transaction status string.
*
* @return
* An object containing the transaction status information or FALSE if the
* requested status is not found.
*/
function commerce_payment_transaction_status_load($status) {
$transaction_statuses = commerce_payment_transaction_statuses();
return !empty($transaction_statuses[$status]) ? $transaction_statuses[$status] : FALSE;
}
/**
* Returns an initialized payment transaction object.
*
* @param $method_id
* The method_id of the payment method for the transaction.
*
* @return
* A transaction object with all default fields initialized.
*/
function commerce_payment_transaction_new($method_id = '', $order_id = 0) {
return entity_get_controller('commerce_payment_transaction')->create($method_id, $order_id);
}
/**
* Creation callback for the Entity module.
*/
function _commerce_payment_transaction_create($values = array()) {
// Create a new transaction of the specified type.
$transaction = commerce_payment_transaction_new($values['method_id']);
unset($values['method_id']);
$wrapper = entity_metadata_wrapper('commerce_payment_transaction', $transaction);
foreach ($values as $name => $value) {
$wrapper->$name->set($value);
}
return $wrapper->value();
}
/**
* Saves a payment transaction.
*
* @param $transaction
* The full transaction object to save.
*
* @return
* The saved transaction object.
*/
function commerce_payment_transaction_save($transaction) {
// Convert the transaction amount to an integer.
$transaction->amount = commerce_currency_amount_to_integer($transaction->amount, $transaction->currency_code);
$transaction = entity_get_controller('commerce_payment_transaction')->save($transaction);
// Convert the amount back from an integer.
$transaction->amount = commerce_currency_integer_to_amount($transaction->amount, $transaction->currency_code);
return $transaction;
}
/**
* Loads a payment transaction by ID.
*/
function commerce_payment_transaction_load($transaction_id) {
$transactions = commerce_payment_transaction_load_multiple(array($transaction_id), array());
return $transactions ? reset($transactions) : FALSE;
}
/**
* Loads multiple payment transaction by ID or based on a set of matching conditions.
*
* @see entity_load()
*
* @param $transaction_ids
* An array of transaction IDs.
* @param $conditions
* An array of conditions on the {commerce_payment_transaction} table in the
* form 'field' => $value.
* @param $reset
* Whether to reset the internal transaction loading cache.
*
* @return
* An array of transaction objects indexed by transaction_id.
*/
function commerce_payment_transaction_load_multiple($transaction_ids = array(), $conditions = array(), $reset = FALSE) {
$transactions = entity_load('commerce_payment_transaction', $transaction_ids, $conditions, $reset);
// Convert the loaded amount integers to price decimal amounts.
foreach ($transactions as $transaction_id => &$transaction) {
// Ensure it happens only once per entity.
if (empty($transaction->amount_converted)) {
$transaction->amount = commerce_currency_integer_to_amount($transaction->amount, $transaction->currency_code);
$transaction->amount_converted = TRUE;
}
}
return $transactions;
}
/**
* Deletes a payment transaction by ID.
*
* @param $transaction_id
* The ID of the transaction to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_payment_transaction_delete($transaction_id) {
return commerce_payment_transaction_delete_multiple(array($transaction_id));
}
/**
* Deletes multiple payment transactions by ID.
*
* @param $transaction_ids
* An array of transaction IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_payment_transaction_delete_multiple($transaction_ids) {
return entity_get_controller('commerce_payment_transaction')->delete($transaction_ids);
}
/**
* Generate an array for rendering the given payment transaction.
*
* @param $transaction
* A fully loaded transaction object.
* @param $view_mode
* The view mode for displaying the order, 'administrator' being the only one
* available by default.
*
* @return
* An array as expected by drupal_render().
*/
function commerce_payment_transaction_build_content($order, $transaction, $view_mode = 'administrator') {
// Populate $transaction->content with a render() array.
// Remove previously built content, if it exists.
$transaction->content = array();
// Add the default fields inherent to the transaction entity.
if (!empty($transaction->instance_id) && $payment_method = commerce_payment_method_instance_load($transaction->instance_id)) {
list($method_id, $rule_name) = explode('|', $payment_method['instance_id']);
$title = l(check_plain($payment_method['title']), 'admin/config/workflow/rules/config/' . $rule_name);
}
else {
$payment_method = commerce_payment_method_load($transaction->payment_method);
$title = check_plain($payment_method['title']);
}
$transaction_statuses = commerce_payment_transaction_statuses();
$rows = array(
array(t('Transaction ID'), $transaction->transaction_id),
array(t('Order', array(), array('context' => 'a drupal commerce order')), l(check_plain($order->order_number), 'admin/commerce/orders/' . $order->order_id)),
array(t('Payment method'), $title),
array(t('Remote ID'), check_plain($transaction->remote_id)),
array(t('Message'), t($transaction->message, $transaction->message_variables)),
array(t('Amount'), commerce_currency_format($transaction->amount, $transaction->currency_code)),
array(t('Status'), check_plain($transaction_statuses[$transaction->status]['title'])),
array(t('Remote status'), check_plain($transaction->remote_status)),
array(t('Created'), format_date($transaction->created)),
);
if ($transaction->changed > $transaction->created) {
$rows[] = array(t('Last changed'), format_date($transaction->changed));
}
if (user_access('administer payments')) {
if (!empty($transaction->payload)) {
$rows[] = array(t('Payload'), '<pre>' . check_plain(print_r($transaction->payload, TRUE)) . '</pre>');
}
}
$transaction->content['transaction_table'] = array(
'#attached' => array(
'css' => array(
drupal_get_path('module', 'commerce_payment') . '/theme/commerce_payment.css',
),
),
'#markup' => theme('table', array('rows' => $rows, 'attributes' => array('class' => array('payment-transaction')))),
);
// Build fields content.
field_attach_prepare_view('commerce_payment_transaction', array($transaction->transaction_id => $transaction), $view_mode);
entity_prepare_view('commerce_payment_transaction', array($transaction->transaction_id => $transaction));
$transaction->content += field_attach_view('commerce_payment_transaction', $transaction, $view_mode);
// Allow modules to make their own additions to the transaction content.
module_invoke_all('commerce_transaction_view', $transaction, $view_mode);
// Remove the content array from the transaction to avoid duplicate rendering.
$build = $transaction->content;
unset($transaction->content);
$build += array(
'#theme' => 'commerce_payment_transaction',
'#order' => $order,
'#transaction' => $transaction,
'#view_mode' => $view_mode,
);
// Allow modules to modify the final build array.
drupal_alter('commerce_payment_transaction_view', $build);
return $build;
}
/**
* Determines access for a variety of operations on payment transactions.
*
* @param $op
* The operation being performed, one of view, update, create, or delete.
* @param $order
* The order object the payment operation applies to.
* @param $transaction
* The payment transaction to check. Required for updating and deleting.
* @param $account
* The user account attempting the operation; defaults to the current user.
*
* @return
* TRUE or FALSE indicating access for the operation.
*/
function commerce_payment_transaction_access($op, $order, $transaction = NULL, $account = NULL) {
global $user;
// Grant administrators access to do anything.
if (user_access('administer payments')) {
return TRUE;
}
if (empty($order)) {
$order = commerce_order_load($transaction->order_id);
}
if (empty($account)) {
$account = clone($user);
}
switch ($op) {
// Creating new payment transactions.
case 'create':
if (user_access('create payments')) {
if (commerce_order_access('update', $order, $account)) {
return TRUE;
}
}
break;
// Viewing payment transactions.
case 'view':
if (user_access('view payments')) {
if (commerce_order_access('view', $order, $account)) {
return TRUE;
}
}
break;
case 'update':
if (user_access('update payments')) {
if (commerce_order_access('view', $order, $account)) {
return TRUE;
}
}
case 'delete':
if (user_access('delete payments')) {
if (commerce_order_access('update', $order, $account)) {
return TRUE;
}
}
break;
}
// Allow other modules to provide other cases for access grants.
foreach (module_invoke_all('commerce_payment_transaction_access', $op, $order, $transaction, $account) as $access) {
if ($access) {
return TRUE;
}
}
return FALSE;
}
/**
* Calculates the balance of an order by subtracting the total of all successful
* transactions from the total of all the line items on the order.
*
* @param $order
* The fully loaded order object whose balance should be calculated.
* @param $totals
* Optionally submit an array of transaction totals keyed by currency code
* with the amount as the value.
*
* @return
* An array containing the amount and currency code representing the balance
* of the order or FALSE if it is impossible to calculate.
*/
function commerce_payment_order_balance($order, $totals = array()) {
$wrapper = entity_metadata_wrapper('commerce_order', $order);
$order_total = commerce_line_items_total($wrapper->commerce_line_items);
// Calculate the transaction totals if not supplied.
if (empty($totals)) {
$transaction_statuses = commerce_payment_transaction_statuses();
foreach (commerce_payment_transaction_load_multiple(array(), array('order_id' => $order->order_id)) as $transaction) {
// If the payment transaction status indicates it should include the
// current transaction in the total...
if ($transaction_statuses[$transaction->status]['total']) {
// Add the transaction to its currency's running total if it exists...
if (isset($totals[$transaction->currency_code])) {
$totals[$transaction->currency_code] += $transaction->amount;
}
else {
// Or begin a new running total for the currency.
$totals[$transaction->currency_code] = $transaction->amount;
}
}
}
}
// Only return a balance if the totals array contains a single matching currency.
if (count($totals) == 1 && isset($totals[$order_total['currency_code']])) {
return array('amount' => $order_total['amount'] - $totals[$order_total['currency_code']], 'currency_code' => $order_total['currency_code']);
}
elseif (empty($totals)) {
return array('amount' => $order_total['amount'], 'currency_code' => $order_total['currency_code']);
}
else {
return FALSE;
}
}
/**
* Returns a sorted array of payment totals table rows.
*
* @param $totals
* An array of payment totals whose keys are currency codes and values are the
* total amount paid in each currency.
* @param $order
* If available, the order object to which the payments apply.
*
* @return
* An array of table row data as expected by theme_table().
*
* @see hook_commerce_payment_totals_row_info()
*/
function commerce_payment_totals_rows($totals, $order) {
// Retrieve rows defined by the hook and allow other modules to alter them.
$rows = module_invoke_all('commerce_payment_totals_row_info', $totals, $order);
drupal_alter('commerce_payment_totals_row_info', $rows, $totals, $order);
// Sort the rows by weight and return the array.
uasort($rows, 'drupal_sort_weight');
return $rows;
}
/**
* Callback for getting payment transaction properties.
*
* @see commerce_payment_entity_property_info()
*/
function commerce_payment_transaction_get_properties($transaction, array $options, $name) {
switch ($name) {
case 'user':
return $transaction->uid;
case 'order':
return $transaction->order_id;
case 'message':
if ($transaction->message) {
return t($transaction->message, is_array($transaction->message_variables) ? $transaction->message_variables : array());
}
else {
return '';
}
}
}
/**
* Callback for setting payment transaction properties.
*
* @see commerce_payment_entity_property_info()
*/
function commerce_payment_transaction_set_properties($transaction, $name, $value) {
switch ($name) {
case 'user':
$transaction->uid = $value;
break;
case 'order':
$transaction->order_id = $value;
break;
}
}
<?php
// $Id$
/**
* @file
* Rules integration for payments.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function commerce_payment_rules_event_info() {
$events = array();
$events['commerce_payment_methods'] = array(
'label' => t('Select available payment methods for an order'),
'group' => t('Commerce Payment'),
'variables' => commerce_order_rules_event_variables(t('Order', array(), array('context' => 'a drupal commerce order'))),
'access callback' => 'commerce_order_rules_access',
);
$events['commerce_payment_transaction_presave'] = array(
'label' => t('Before saving a payment transaction'),
'group' => t('Commerce Payment'),
'variables' => commerce_payment_rules_event_variables(t('Payment transaction'), TRUE, TRUE),
'access callback' => 'commerce_payment_rules_access',
);
$events['commerce_payment_transaction_insert'] = array(
'label' => t('After saving a new payment transaction'),
'group' => t('Commerce Payment'),
'variables' => commerce_payment_rules_event_variables(t('Created payment transaction'), TRUE),
'access callback' => 'commerce_payment_rules_access',
);
$events['commerce_payment_transaction_update'] = array(
'label' => t('After updating an existing payment transaction'),
'group' => t('Commerce Payment'),
'variables' => commerce_payment_rules_event_variables(t('Updated payment transaction'), TRUE),
'access callback' => 'commerce_payment_rules_access',
);
$events['commerce_payment_transaction_delete'] = array(
'label' => t('After deleting a payment transaction'),
'group' => t('Commerce Payment'),
'variables' => commerce_payment_rules_event_variables(t('Deleted payment transaction')),
'access callback' => 'commerce_payment_rules_access',
);
return $events;
}
/**
* Returns a variables array for payment transaction events.
*
* @param $label
* The label for the primary payment transaction variable.
* @param $unchanged
* Boolean indicating whether or not to include the unchanged transaction.
* @param $skip_save
* Boolean indicating whether or not the primary transaction variable should
* skip saving after event execution.
*/
function commerce_payment_rules_event_variables($label, $unchanged = FALSE, $skip_save = FALSE) {
$variables = array(
'payment_transaction' => array(
'type' => 'commerce_payment_transaction',
'label' => $label,
'skip save' => $skip_save,
),
);
if ($unchanged) {
$variables['payment_transaction_unchanged'] = array(
'type' => 'commerce_payment_transaction',
'label' => t('Unchanged payment transaction'),
'skip save' => TRUE,
'handler' => 'rules_events_entity_unchanged',
);
}
return $variables;
}
/**
* Rules integration access callback.
*/
function commerce_payment_rules_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return TRUE;
}
}
/**
* Implements hook_rules_action_info().
*/
function commerce_payment_rules_action_info() {
$actions = array();
// Add an action for each available payment method.
foreach (commerce_payment_methods() as $payment_method) {
$actions['commerce_payment_enable_' . $payment_method['method_id']] = array(
'label' => t('Enable payment method: @method', array('@method' => $payment_method['title'])),
'parameter' => array(
'commerce_order' => array('type' => 'commerce_order', 'label' => t('Order', array(), array('context' => 'a drupal commerce order'))),
'payment_method' => array(
'type' => 'commerce_payment_settings',
'restriction' => 'input',
'label' => t('Payment settings'),
'payment_method' => $payment_method['method_id'],
),
),
'group' => t('Commerce Payment'),
'base' => $payment_method['base'],
'callbacks' => array(
'execute' => 'commerce_payment_enable_method',
),
);
}
$actions['commerce_payment_capture'] = array(
'label' => t('Capture from a prior authorization'),
'parameter' => array(
'commerce_order' => array('type' => 'commerce_order', 'label' => t('Order', array(), array('context' => 'a drupal commerce order'))),
'amount' => array('type' => 'text', 'label' => t('Amount')),
),
'group' => t('Commerce Payment'),
);
return $actions;
}
/**
* Generic execution callback for the payment method.
*/
function commerce_payment_enable_method($order, $payment_method, $action_settings, $rule_state, $action, $callback_type) {
// Find the Rule that contains this action.
$rule = $action->parentElement();
while ($rule->getPluginName() != 'reaction rule') {
$rule = $rule->parentElement();
}
// Initialize variables for the payment method ID and settings.
if (is_array($payment_method)) {
$method_id = $payment_method['method_id'];
$settings = !empty($payment_method['settings']) ? $payment_method['settings'] : array();
}
else {
$method_id = $payment_method;
$settings = array();
}
// Create a unique key for the instance of the payment method represented by
// this action.
$instance_id = commerce_payment_method_instance_id($method_id, $rule);
// Set the payment method to the order along with its settings and context.
$order->payment_methods[$instance_id] = array(
'method_id' => $method_id,
'settings' => $settings,
'rule_name' => $rule->name,
'weight' => $rule->weight,
);
}
/**
* Rules action: capture a payment from a previous authorization.
*/
function commerce_payment_capture($order, $amount) {
}
/**
* Implements hook_rules_data_info().
*/
function commerce_payment_rules_data_info() {
$data['commerce_payment_settings'] = array(
'label' => t('Payment settings'),
'ui class' => 'RulesDataUIPaymentSettings',
);
return $data;
}
/**
* Adds a payment method settings form to the enabling action.
*/
class RulesDataUIPaymentSettings extends RulesDataUI implements RulesDataDirectInputFormInterface {
public static function getDefaultMode() {
return 'input';
}
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
// If the specified payment method exists...
if ($payment_method = commerce_payment_method_load($info['payment_method'])) {
$form[$name]['method_id'] = array('#type' => 'value', '#value' => $info['payment_method']);
// If the payment method has a settings callback...
if ($callback = commerce_payment_method_callback($payment_method, 'settings_form')) {
// Prepare an array of payment method settings defaults.
$method_settings = !empty($settings[$name]) ? $settings[$name]['settings'] : array();
// Add the settings form elements to the action form.
$form[$name]['settings'] = $callback($method_settings);
}
else {
// Otherwise add an appropriate message.
$form[$name]['settings']['no_settings']['#markup'] = t('No settings for this payment method.');
}
}
else {
$form[$name]['invalid']['#markup'] = t('Invalid payment method.');
}
return $form;
}
public static function render($value) {
return array();
}
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Default rules configuration for Payment.
*/
/**
* Implements hook_default_rules_configuration().
*/
function commerce_payment_default_rules_configuration() {
$rules = array();
// Add a default rule for each available payment method.
foreach (commerce_payment_methods() as $method_id => $payment_method) {
$rule = rules_reaction_rule();
$rule->label = $payment_method['title'];
$rule->active = $payment_method['active'];
$rule
->event('commerce_payment_methods')
->action('commerce_payment_enable_' . $method_id, array(
'commerce_order:select' => 'order',
'payment_method' => $method_id,
));
$rules['commerce_payment_' . $method_id] = $rule;
}
return $rules;
}
<?php
// $Id$
/**
* @file
* Builds placeholder replacement tokens for payment-related data.
*/
/**
* Implements hook_token_info().
*/
function commerce_payment_token_info() {
$type = array(
'name' => t('Payment transactions', array(), array('context' => 'a drupal commerce payment transaction')),
'description' => t('Tokens related to payment transactions.'),
'needs-data' => 'transaction',
);
// Tokens for payments.
$transaction = array();
$transaction['transaction-id'] = array(
'name' => t('Transaction ID'),
'description' => t('The primary identifier for a payment transaction.'),
);
$transaction['revision-id'] = array(
'name' => t('Revision ID'),
'description' => t('The unique ID for the latest revision of a payment transaction.'),
);
$transaction['payment-method'] = array(
'name' => t('Payment method'),
'description' => t('The payment method method_id for the payment transaction.'),
);
$transaction['payment-method-title'] = array(
'name' => t('Payment method title'),
'description' => t('The administrative title of the payment method for the payment transaction.'),
);
$transaction['payment-method-short-title'] = array(
'name' => t('Payment method short title'),
'description' => t('The customer oriented short title of the payment method for the payment transaction.'),
);
$transaction['remote-id'] = array(
'name' => t('Remote ID'),
'description' => t('The remote identifier for the payment transaction.'),
);
$transaction['message'] = array(
'name' => t('Message'),
'description' => t('The human-readable message associated to the payment transaction.'),
);
$transaction['amount-raw'] = array(
'name' => t('Raw amount'),
'description' => t('The raw amount of the payment transaction.'),
);
$transaction['amount-formatted'] = array(
'name' => t('Formatted amount'),
'description' => t('The formatted amount of the payment transaction.'),
);
$transaction['currency-code'] = array(
'name' => t('Currency code'),
'description' => t('The currency code for the payment.'),
);
$transaction['currency-symbol'] = array(
'name' => t('Currency symbol'),
'description' => t('The currency symbol for the payment.'),
);
$transaction['status'] = array(
'name' => t('Status'),
'description' => t('The status of this transaction (pending, success, or failure).'),
);
$transaction['remote-status'] = array(
'name' => t('Remote status'),
'description' => t('The status of the transaction at the payment provider.'),
);
// Chained tokens for payment transactions.
$transaction['order'] = array(
'name' => t('Order'),
'description' => t('The order related to the payment transaction.'),
'type' => 'order',
);
$transaction['creator'] = array(
'name' => t('Creator'),
'description' => t('The creator of the payment transaction.'),
'type' => 'user',
);
$transaction['created'] = array(
'name' => t('Date created'),
'description' => t('The date the payment transaction was created.'),
'type' => 'date',
);
$transaction['changed'] = array(
'name' => t('Date changed'),
'description' => t('The date the payment transaction was last updated.'),
'type' => 'date',
);
return array(
'types' => array('transaction' => $type),
'tokens' => array('transaction' => $transaction),
);
}
/**
* Implements hook_tokens().
*/
function commerce_payment_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 == 'transaction' && !empty($data['transaction'])) {
$transaction = $data['transaction'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'transaction-id':
$replacements[$original] = $transaction->transaction_id;
break;
case 'revision-id':
$replacements[$original] = $transaction->revision_id;
break;
case 'payment-method':
$replacements[$original] = $sanitize ? check_plain($transaction->payment_method) : $transaction->payment_method;
break;
case 'payment-method-title':
$title = commerce_payment_method_get_title('title', $transaction->payment_method);
$replacements[$original] = $sanitize ? check_plain($title) : $title;
break;
case 'payment-method-short-title':
$title = commerce_payment_method_get_title('short_title', $transaction->payment_method);
$replacements[$original] = $sanitize ? check_plain($title) : $title;
break;
case 'remote-id':
$replacements[$original] = $sanitize ? check_plain($transaction->remote_id) : $transaction->remote_id;
break;
case 'message':
$replacements[$original] = t($transaction->message, is_array($transaction->message_variables) ? $transaction->message_variables : array());
break;
case 'amount-raw':
$replacements[$original] = $sanitize ? check_plain($transaction->amount) : $transaction->amount;
break;
case 'amount-formatted':
$replacements[$original] = commerce_currency_format($transaction->amount, $transaction->currency_code);
break;
case 'currency-code':
$replacements[$original] = $sanitize ? check_plain($transaction->currency_code) : $transaction->currency_code;
break;
case 'currency-symbol':
$replacements[$original] = commerce_currency_get_symbol($transaction->currency_code);
break;
case 'status':
$transaction_status = commerce_payment_transaction_status_load($transaction->status);
$replacements[$original] = $sanitize ? check_plain($transaction_status['title']) : $transaction_status['title'];
break;
case 'remote-status':
$replacements[$original] = $sanitize ? check_plain($transaction->remote_status) : $transaction->remote_status;
break;
// Default values for the chained tokens handled below.
case 'order':
if ($transaction->order_id) {
$order = commerce_order_load($transaction->order_id);
$replacements[$original] = $sanitize ? check_plain($order->order_number) : $order->order_number;
}
break;
case 'creator':
if ($order->uid == 0) {
$name = variable_get('anonymous', t('Anonymous'));
}
else {
$creator = user_load($transaction->uid);
$name = $creator->name;
}
$replacements[$original] = $sanitize ? filter_xss($name) : $name;
break;
case 'created':
$replacements[$original] = format_date($transaction->created, 'medium', '', NULL, $language_code);
break;
case 'changed':
$replacements[$original] = format_date($transaction->changed, 'medium', '', NULL, $language_code);
break;
}
}
if ($order_tokens = token_find_with_prefix($tokens, 'order')) {
$order = commerce_order_load($transaction->order_id);
$replacements += token_generate('order', $order_tokens, array('order' => $order), $options);
}
if ($creator_tokens = token_find_with_prefix($tokens, 'creator')) {
$creator = user_load($transaction->uid);
$replacements += token_generate('user', $creator_tokens, array('user' => $creator), $options);
}
foreach (array('created', 'changed') as $date) {
if ($created_tokens = token_find_with_prefix($tokens, $date)) {
$replacements += token_generate('date', $created_tokens, array('date' => $transaction->{$date}), $options);
}
}
}
return $replacements;
}
<?php
// $Id$
/**
* Export Drupal Commerce Payment transactions to Views.
*/
/**
* Implements hook_views_data()
*/
function commerce_payment_views_data() {
$data = array();
$data['commerce_payment_transaction']['table']['group'] = t('Commerce Payment Transaction');
$data['commerce_payment_transaction']['table']['base'] = array(
'field' => 'transaction_id',
'title' => t('Commerce Payment Transaction'),
'help' => t('The receipt of a payment transaction.'),
);
// Expose the transaction ID.
$data['commerce_payment_transaction']['transaction_id'] = array(
'title' => t('Transaction ID'),
'help' => t('The unique internal identifier of the transaction.'),
'field' => array(
'handler' => 'views_handler_field_numeric',
'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 order ID.
$data['commerce_payment_transaction']['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 transaction payment method.
$data['commerce_payment_transaction']['payment_method'] = array(
'title' => t('Payment method'),
'help' => t('The payment method of the transaction.'),
'field' => array(
'handler' => 'commerce_payment_handler_field_payment_method',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_payment_handler_filter_payment_method',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the transaction's remote ID.
$data['commerce_payment_transaction']['remote_id'] = array(
'title' => t('Remote ID'),
'help' => t('The remote identifier of this transaction at the payment provider.'),
'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 transaction amount.
$data['commerce_payment_transaction']['amount'] = array(
'title' => t('Amount'),
'help' => t('The amount of the transaction.'),
'field' => array(
'handler' => 'commerce_payment_handler_field_amount',
'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 transaction currency.
$data['commerce_payment_transaction']['currency_code'] = array(
'title' => t('Currency'),
'help' => t('The currency of the transaction.'),
'field' => array(
'handler' => 'commerce_payment_handler_field_currency_code',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_payment_handler_filter_currency_code',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the transaction message.
$data['commerce_payment_transaction']['message'] = array(
'title' => t('Message'),
'help' => t('The message associated with the transaction.'),
'field' => array(
'handler' => 'commerce_payment_handler_field_message',
'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 transaction status.
$data['commerce_payment_transaction']['status'] = array(
'title' => t('Status'),
'help' => t('The status of this transaction.'),
'field' => array(
'handler' => 'commerce_payment_handler_field_status',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_payment_handler_filter_payment_transaction_status',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the transaction's remote status.
$data['commerce_payment_transaction']['remote_status'] = array(
'title' => t('Remote status'),
'help' => t('The status of this transaction at the payment provider.'),
'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 created and changed timestamps.
$data['commerce_payment_transaction']['created'] = array(
'title' => t('Created date'),
'help' => t('The date the transaction 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_payment_transaction']['changed'] = array(
'title' => t('Changed date'),
'help' => t('The date the transaction was last changed.'),
'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_payment_transaction']['delete_transaction'] = array(
'field' => array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete the payment transaction.'),
'handler' => 'commerce_payment_handler_field_payment_transaction_link_delete',
),
);
$data['commerce_payment_transaction']['operations'] = array(
'field' => array(
'title' => t('Operations links'),
'help' => t('Display all the available operations links for the transaction.'),
'handler' => 'commerce_payment_handler_field_payment_transaction_operations',
),
);
$data['commerce_payment_transaction']['totals'] = array(
'title' => t('Totals'),
'help' => t('Display transaction total and order balance information for all transactions in the View.'),
'area' => array(
'handler' => 'commerce_payment_handler_area_totals',
),
);
return $data;
}
/**
* Implements hook_views_data_alter()
*/
function commerce_payment_views_data_alter(&$data) {
$data['commerce_order']['balance'] = array(
'field' => array(
'title' => t('Order Balance'),
'help' => t('Total transaction payment balance for the order.'),
'handler' => 'commerce_payment_handler_field_balance',
),
);
}
; $Id$
name = Payment Method Example
description = Provides an example payment method for testing and development.
package = Commerce
dependencies[] = commerce
dependencies[] = commerce_payment
core = 7.x
; Base module files
files[] = commerce_payment_example.module
<?php
// $Id$
/**
* @file
* Provides an example payment method for Drupal Commerce for testing and
* development.
*/
/**
* Implements hook_commerce_payment_method_info().
*/
function commerce_payment_example_commerce_payment_method_info() {
$payment_methods = array();
$payment_methods['commerce_payment_example'] = array(
'title' => t('Example payment'),
'description' => t('Demonstrates complete payment during checkout and serves as a development example.'),
'active' => TRUE,
);
return $payment_methods;
}
/**
* Payment method callback: checkout form.
*/
function commerce_payment_example_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
$form = array();
// Merge in values from the order.
if (!empty($order->data['commerce_payment_example'])) {
$pane_values += $order->data['commerce_payment_example'];
}
// Merge in default values.
$pane_values += array(
'name' => '',
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#description' => t('This is a demonstration field coded to fail validation for single character values.'),
'#default_value' => $pane_values['name'],
'#required' => TRUE,
);
return $form;
}
/**
* Payment method callback: checkout form validation.
*/
function commerce_payment_example_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) {
// Throw an error if a long enough name was not provided.
if (strlen($pane_values['name']) < 2) {
form_set_error(implode('][', array_merge($form_parents, array('name'))), t('You must enter a name two or more characters long.'));
// Even though the form error is enough to stop the submission of the form,
// it's not enough to stop it from a Commerce standpoint because of the
// combined validation / submission going on per-pane in the checkout form.
return FALSE;
}
}
/**
* Payment method callback: checkout form submission.
*/
function commerce_payment_example_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {
$order->data['commerce_payment_example'] = $pane_values;
commerce_payment_example_transaction($payment_method, $order, $charge, $pane_values['name']);
}
/**
* Creates an example payment transaction for the specified charge amount.
*
* @param $payment_method
* The payment method instance object used to charge this payment.
* @param $order
* The order object the payment applies to.
* @param $charge
* An array indicating the amount and currency code to charge.
* @param $name
* The name entered on the submission form.
*/
function commerce_payment_example_transaction($payment_method, $order, $charge, $name) {
$transaction = commerce_payment_transaction_new('commerce_payment_example', $order->order_id);
$transaction->instance_id = $payment_method['instance_id'];
$transaction->amount = $charge['amount'];
$transaction->currency_code = $charge['currency_code'];
$transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
$transaction->message = 'Name: @name';
$transaction->message_variables = array('@name' => $name);
commerce_payment_transaction_save($transaction);
}
<?php
// $Id$
/**
* Defines a handler area that provides payment totals, the order balance, and
* a form to add new payments.
*/
class commerce_payment_handler_area_totals extends views_handler_area {
function init(&$view, &$options) {
parent::init($view, $options);
$this->additional_fields['amount'] = 'amount';
$this->additional_fields['currency_code'] = 'currency_code';
$this->additional_fields['status'] = 'status';
}
function option_definition() {
$options = parent::option_definition();
$options['add_payment_form'] = array('default' => TRUE);
return $options;
}
/**
* Provide the checkbox for enabling the Add payment form..
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['add_payment_form'] = array(
'#type' => 'checkbox',
'#title' => t('Display an add payment form in the totals area when using a single order argument.'),
'#description' => t('The argument should be setup using a Views relationship on the transaction Order ID.'),
'#default_value' => $this->options['add_payment_form'],
);
}
function render($empty = FALSE) {
// Load an order object for the View if a single order argument is present.
if (in_array('order_id', array_keys($this->view->argument)) &&
!in_array('order_id_1', array_keys($this->view->argument)) &&
!empty($this->view->args[$this->view->argument['order_id']->position])) {
// Load the specified order.
$order = commerce_order_load($this->view->args[$this->view->argument['order_id']->position]);
}
else {
// Otherwise indicate a valid order is not present.
$order = FALSE;
}
// Prepare the proper aliases for finding data in the result set.
$status_alias = $this->view->query->fields['commerce_payment_transaction_status']['alias'];
$currency_code_alias = $this->view->query->fields['commerce_payment_transaction_currency_code']['alias'];
$amount_alias = $this->view->query->fields['commerce_payment_transaction_amount']['alias'];
// Calculate a total of successful payments for each currency.
$transaction_statuses = commerce_payment_transaction_statuses();
$totals = array();
foreach ($this->view->result as $result) {
// If the payment transaction status indicates it should include the
// current transaction in the total...
if (!empty($transaction_statuses[$result->{$status_alias}]) && $transaction_statuses[$result->{$status_alias}]['total']) {
// Add the transaction to its currency's running total if it exists...
if (isset($totals[$result->{$currency_code_alias}])) {
$totals[$result->{$currency_code_alias}] += $result->{$amount_alias};
}
else {
// Or begin a new running total for the currency.
$totals[$result->{$currency_code_alias}] = $result->{$amount_alias};
}
}
}
// Build and render the form to add a payment if the View contains a valid
// order argument.
if ($this->options['add_payment_form'] && $order) {
module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.forms');
$content = drupal_get_form('commerce_payment_order_transaction_add_form', $order);
$form = drupal_render($content);
}
else {
$form = NULL;
}
// Prepare variables for use in the theme function.
$variables = array(
'rows' => commerce_payment_totals_rows($totals, $order),
'form' => $form,
'view' => $this->view,
'totals' => $totals,
'order' => $order,
);
return theme('commerce_payment_totals', $variables);
}
}
<?php
// $Id$
/**
* @file
* Contains the basic amount field handler.
*/
/**
* Field handler to allow rendering of the amount using currency formatting.
*/
class commerce_payment_handler_field_amount extends views_handler_field {
function init(&$view, &$options) {
parent::init($view, $options);
$this->additional_fields['currency_code'] = 'currency_code';
}
function option_definition() {
$options = parent::option_definition();
$options['display_format'] = array('default' => 'formatted');
return $options;
}
/**
* Provide the currency format option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['display_format'] = array(
'#type' => 'select',
'#title' => t('Display format'),
'#options' => array(
'formatted' => t('Currency formatted amount'),
'raw' => t('Raw amount'),
),
'#default_value' => $this->options['display_format'],
);
}
/**
* Convert the amount integer to a price decimal amount.
*/
function pre_render(&$values) {
foreach ($values as $key => &$value) {
$value->{$this->field_alias} = commerce_currency_integer_to_amount($value->{$this->field_alias}, $value->{$this->aliases['currency_code']});
}
}
function render($values) {
switch ($this->options['display_format']) {
case 'formatted':
return commerce_currency_format($values->{$this->field_alias}, $values->{$this->aliases['currency_code']});
case 'raw':
// First load the full currency array.
$currency = commerce_currency_load($values->{$this->aliases['currency_code']});
// Format the price as a number.
return number_format(commerce_currency_round($values->{$this->field_alias}, $currency), $currency['decimals']);
}
}
}
<?php
//$Id$
/**
* Field handler to be able to show the balance order with currency.
*/
class commerce_payment_handler_field_balance extends views_handler_field {
function init(&$view, &$options) {
parent::init($view, $options);
$this->additional_fields['order_id'] = 'order_id';
}
function option_definition() {
$options = parent::option_definition();
$options['display_format'] = array('default' => 'formatted');
return $options;
}
/**
* Provide the currency format option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['display_format'] = array(
'#type' => 'select',
'#title' => t('Display format'),
'#options' => array(
'formatted' => t('Currency formatted amount'),
'raw' => t('Raw amount'),
),
'#default_value' => $this->options['display_format'],
);
}
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
switch ($this->options['display_format']) {
case 'formatted':
// Get the order object and the balance.
$order = commerce_order_load($values->{$this->aliases['order_id']});
$balance = commerce_payment_order_balance($order);
// Format the amount with currency.
return commerce_currency_format($balance['amount'], $balance['currency_code']);
case 'raw':
// Get the order object and the balance.
$order = commerce_order_load($values->{$this->aliases['order_id']});
$balance = commerce_payment_order_balance($order);
// Then load the currency array.
$currency = commerce_currency_load();
// Format the price as a number.
return number_format(commerce_currency_round($balance['amount'], $currency), $currency['decimals']);
}
}
}
<?php
// $Id$
/**
* @file
* Contains the basic currency code field handler.
*/
/**
* Field handler to allow rendering of the currency code in different formats.
*/
class commerce_payment_handler_field_currency_code extends views_handler_field {
function option_definition() {
$options = parent::option_definition();
$options['display_format'] = array('default' => 'code');
return $options;
}
/**
* Provide the currency code format option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['display_format'] = array(
'#title' => t('Display format'),
'#type' => 'select',
'#options' => array(
'code' => t('Three letter code'),
'numeric_code' => t('Numeric code'),
'name' => t('Currency name'),
'symbol' => t('Currency symbol'),
),
'#default_value' => $this->options['display_format'],
);
}
function render($values) {
$currency = commerce_currency_load($values->{$this->field_alias});
return check_plain($currency[$this->options['display_format']]);
}
}
<?php
// $Id$
/**
* Field handler to display a transaction message with variable replacement.
*/
class commerce_payment_handler_field_message extends views_handler_field {
function init(&$view, &$options) {
parent::init($view, $options);
$this->additional_fields['message_variables'] = 'message_variables';
}
function render($values) {
$variables = unserialize($values->{$this->aliases['message_variables']});
return t($values->{$this->field_alias}, is_array($variables) ? $variables : array());
}
}
<?php
// $Id$
/**
* Field handler to translate a payment method ID into its readable form.
*/
class commerce_payment_handler_field_payment_method extends views_handler_field {
function option_definition() {
$options = parent::option_definition();
$options['title'] = array('default' => 'title');
return $options;
}
/**
* Provide the link to order option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['title'] = array(
'#title' => t('Display the payment method using the following title'),
'#type' => 'radios',
'#options' => array(
'title' => t('The full administrative title of the payment method'),
'short_title' => t('A short version of the title safe to display to all'),
'display_title' => t('The title displayed on the checkout form (may include HTML)'),
),
'#default_value' => $this->options['title'],
);
}
function render($values) {
if ($payment_method = commerce_payment_method_load($values->{$this->field_alias})) {
return check_plain($payment_method[$this->options['title']]);
}
else {
return t('Unknown');
}
}
}
<?php
// $Id$
/**
* Field handler to present a payment transaction's operations links.
*/
class commerce_payment_handler_field_payment_transaction_operations extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['transaction_id'] = 'transaction_id';
$this->additional_fields['order_id'] = 'order_id';
}
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
$transaction_id = $values->{$this->aliases['transaction_id']};
$order_id = $values->{$this->aliases['order_id']};
$links = menu_contextual_links('commerce-payment-transaction', 'admin/commerce/orders/' . $order_id . '/payment', array($transaction_id));
if (!empty($links)) {
drupal_add_css(drupal_get_path('module', 'commerce_payment') . '/theme/commerce_payment.css');
return theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline', 'operations'))));
}
}
}
<?php
// $Id$
/**
* Field handler for the payment transaction status.
*/
class commerce_payment_handler_field_status extends views_handler_field {
function option_definition() {
$options = parent::option_definition();
$options['display_style'] = array('default' => 'icon');
return $options;
}
/**
* Provide the checkbox for enabling the Add payment form..
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['display_style'] = array(
'#type' => 'select',
'#title' => t('Display style'),
'#options' => array(
'icon' => t('Status icon'),
'title' => t('Status title'),
'raw' => t('Raw status'),
),
'#default_value' => $this->options['display_style'],
);
}
function render($values) {
$transaction_status = commerce_payment_transaction_status_load($values->{$this->field_alias});
if (!$transaction_status) {
return '?';
}
$variables = array(
'transaction_status' => $transaction_status,
);
switch ($this->options['display_style']) {
case 'icon':
return theme('commerce_payment_transaction_status_icon', $variables);
case 'title':
return theme('commerce_payment_transaction_status_text', $variables + array('text' => check_plain($transaction_status['title'])));
case 'raw':
return theme('commerce_payment_transaction_status_text', $variables + array('text' => check_plain($transaction_status['status'])));
}
}
}
<?php
// $Id$
/**
* Filter by currency code.
*/
class commerce_payment_handler_filter_currency_code extends views_handler_filter_in_operator {
var $value_form_type = 'select';
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Currency Code');
$currencies = commerce_currencies();
foreach ($currencies as $currency => $info) {
$options[$currency] = t('@code - !name - @symbol', array('@code' => $info['code'], '@symbol' => $info['symbol'], '!name' => $info['name']'));
}
$this->value_options = $options;
}
}
}
<?php
//$Id$
/**
* Filter by payment method.
*/
class commerce_payment_handler_filter_payment_method extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Payment Method');
$methods = commerce_payment_methods();
foreach ($methods as $method => $info) {
$options[$method] = t($info['title']);
}
$this->value_options = $options;
}
}
}
<?php
//$Id$
/**
* Filter by payment transaction status.
*/
class commerce_payment_handler_filter_payment_transaction_status extends views_handler_filter_in_operator {
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Payment Status');
$statuses = commerce_payment_transaction_statuses();
foreach ($statuses as $status => $info) {
$options[$status] = t($info['title']);
}
$this->value_options = $options;
}
}
}
<?php
// $Id$
/**
* @file
* The controller for the payment transaction entity containing the CRUD operations.
*/
/**
* The controller class for payment transactions contains methods for the
* transaction CRUD operations. The load method is inherited from the default
* controller.
*/
class CommercePaymentTransactionEntityController extends DrupalDefaultEntityController {
/**
* Create a default payment transaction.
*
* @param $method_id
* The ID of the payment method for the transaction.
* @param $order_id
* The ID of the order the transaction is for.
*
* @return
* A payment transaction object with all default fields initialized.
*/
public function create($method_id = '', $order_id = 0) {
global $user;
return (object) array(
'transaction_id' => '',
'revision_id' => '',
'uid' => $user->uid,
'order_id' => $order_id,
'payment_method' => $method_id,
'instance_id' => '',
'remote_id' => '',
'message' => '',
'message_variables' => array(),
'amount' => 0,
'currency_code' => '',
'status' => '',
'remote_status' => '',
'payload' => array(),
'created' => '',
'changed' => '',
);
}
/**
* Saves a payment transaction.
*
* When saving a transaction without an ID, this function will create a new
* transaction at that time. Subsequent transactions that should be saved as
* new revisions should set $transaction->revision to TRUE and include a log
* string in $transaction->log.
*
* @param $transaction
* The full transaction object to save.
*
* @return
* The saved transaction object.
*/
public function save($transaction) {
$db_transaction = db_transaction();
try {
// Determine if we will be inserting a new transaction.
$transaction->is_new = empty($transaction->transaction_id);
// Set the timestamp fields.
if (empty($transaction->created)) {
$transaction->created = REQUEST_TIME;
}
$transaction->changed = REQUEST_TIME;
$transaction->revision_timestamp = REQUEST_TIME;
$update_transaction = TRUE;
// Give modules the opportunity to prepare field data for saving.
rules_invoke_all('commerce_payment_transaction_presave', $transaction);
field_attach_presave('commerce_payment_transaction', $transaction);
if ($transaction->is_new || !empty($transaction->revision)) {
// When inserting either a new transaction or revision, $transaction->log
// must be set because {commerce_payment_transaction_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($transaction->log)) {
$transaction->log = '';
}
}
elseif (empty($transaction->log)) {
// If we are updating an existing transaction without adding a new
// revision, we need to make sure $transaction->log is unset whenever it
// is empty. As long as $transaction->log is unset, drupal_write_record()
// will not attempt to update the existing database column when re-saving
// the revision.
unset($transaction->log);
}
// When saving a new transaction revision, unset any existing
// $transaction->revision_id to ensure a new revision will actually be
// created and store the old revision ID in a separate property for
// transaction hook implementations.
if (!$transaction->is_new && !empty($transaction->revision) && $transaction->revision_id) {
$transaction->old_revision_id = $transaction->revision_id;
unset($transaction->revision_id);
}
// If this is a new transaction...
if ($transaction->is_new) {
// Save the new transaction and fields.
drupal_write_record('commerce_payment_transaction', $transaction);
// Save the initial revision.
$this->saveRevision($transaction);
$op = 'insert';
}
else {
// Save the updated transaction and fields.
drupal_write_record('commerce_payment_transaction', $transaction, 'transaction_id');
// If a new transaction revision was requested, save a new record for
// that; otherwise, update the transaction revision record that matches
// the value of $transaction->revision_id.
if (!empty($transaction->revision)) {
$this->saveRevision($transaction);
}
else {
$this->saveRevision($transaction, TRUE);
$update_transaction = FALSE;
}
$op = 'update';
}
// If the revision ID is new or updated, save it to the transaction.
if ($update_transaction) {
db_update('commerce_payment_transaction')
->fields(array('revision_id' => $transaction->revision_id))
->condition('transaction_id', $transaction->transaction_id)
->execute();
}
// Save fields.
$function = 'field_attach_' . $op;
$function('commerce_payment_transaction', $transaction);
module_invoke_all('commerce_payment_transaction_' . $op, $transaction);
module_invoke_all('entity_' . $op, $transaction, 'commerce_payment_transaction');
rules_invoke_event('commerce_payment_transaction_' . $op, $transaction);
// Clear internal properties.
unset($transaction->is_new);
$this->resetCache();
// Ignore slave server temporarily to give time for the
// saved transaction to be propagated to the slave.
db_ignore_slave();
return $transaction;
}
catch (Exception $e) {
$db_transaction->rollback();
watchdog_exception('commerce_payment', $e);
throw $e;
}
}
/**
* Saves a payment transaction revision.
*
* @param $transaction
* The fully loaded transaction object.
* @param $update
* TRUE or FALSE indicating whether or not the existing revision should be
* updated instead of a new one created.
*/
function saveRevision($transaction, $update = FALSE) {
global $user;
$transaction->revision_uid = $user->uid;
// Update the existing revision if specified.
if ($update) {
drupal_write_record('commerce_payment_transaction_revision', $transaction, 'revision_id');
}
else {
// Otherwise insert a new revision. This will automatically update $transaction
// to include the revision_id.
drupal_write_record('commerce_payment_transaction_revision', $transaction);
}
}
/**
* Unserializes the message_variables and payload properties of loaded payment
* transactions.
*/
public function attachLoad(&$queried_transactions, $revision_id = FALSE) {
foreach ($queried_transactions as $transaction_id => $transaction) {
$queried_transactions[$transaction_id]->message_variables = unserialize($transaction->message_variables);
$queried_transactions[$transaction_id]->payload = unserialize($transaction->payload);
}
// Call the default attachLoad() method. This will add fields and call
// hook_user_load().
parent::attachLoad($queried_transactions, $revision_id);
}
/**
* Deletes multiple payment transactions by ID.
*
* @param $transaction_ids
* An array of transaction IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
public function delete($transaction_ids) {
if (!empty($transaction_ids)) {
$transactions = $this->load($transaction_ids, array());
$db_transaction = db_transaction();
try {
db_delete('commerce_payment_transaction')
->condition('transaction_id', $transaction_ids, 'IN')
->execute();
foreach ($transactions as $transaction_id => $transaction) {
module_invoke_all('commerce_payment_transaction_delete', $transaction);
field_attach_delete('commerce_payment_transaction', $transaction);
rules_invoke_event('commerce_payment_transaction_delete', $transaction);
}
// Ignore slave server temporarily to give time for the
// saved transaction to be propagated to the slave.
db_ignore_slave();
}
catch (Exception $e) {
$db_transaction->rollback();
watchdog_exception('commerce_payment', $e);
throw $e;
}
// Clear the page and block and commerce_payment_transaction_load_multiple caches.
cache_clear_all();
$this->resetCache();
}
return TRUE;
}
}
<?php
// $Id$
/**
* @file
* Administrative page callbacks for the Payment UI module.
*/
/**
* Displays the payment transaction View in an order's payment tab.
*/
function commerce_payment_ui_order_tab($order) {
// Set the breadcrumb and display the payments View.
commerce_order_ui_set_breadcrumb();
return commerce_embed_view('commerce_payment_order', 'defaults', array($order->order_id));
}
/**
* Builds the payment settings page using the Rules UI overview table filtered
* to display payment method rules.
*/
function commerce_payment_ui_admin_page() {
RulesPluginUI::$basePath = 'admin/commerce/config/payment-methods';
$content['enabled']['title']['#markup'] = '<h3>' . t('Enabled payment method rules') . '</h3>';
$conditions = array('event' => 'commerce_payment_methods', 'plugin' => 'reaction rule', 'active' => TRUE);
$content['enabled']['rules'] = RulesPluginUI::overviewTable($conditions);
$content['enabled']['rules']['#empty'] = t('There are no active payment methods.');
$content['disabled']['title']['#markup'] = '<h3>' . t('Disabled payment method rules') . '</h3>';
$conditions['active'] = FALSE;
$content['disabled']['rules'] = RulesPluginUI::overviewTable($conditions);
$content['disabled']['rules']['#empty'] = t('There are no disabled payment methods.');
return $content;
}
/**
* Displays the full details of a payment transaction.
*/
function commerce_payment_ui_payment_transaction_view($order, $transaction, $view_mode, $breadcrumb = TRUE) {
// Set the breadcrumb for the appropriate view mode if specified.
if ($breadcrumb) {
commerce_payment_ui_set_order_breadcrumb($order, $view_mode);
}
return commerce_payment_transaction_build_content($order, $transaction, $view_mode);
}
/**
* Form callback wrapper: confirmation form for deleting a payment transaction.
*
* @param $order
* The order object containing the transaction being deleted by the form.
* @param $transaction
* The actual payment transaction that will be deleted.
*
* @see commerce_payment_payment_transaction_delete_form()
*/
function commerce_payment_ui_payment_transaction_delete_form_wrapper($order, $transaction) {
// Add the breadcrumb for the form's location.
commerce_payment_ui_set_order_breadcrumb($order);
// Include the forms file from the Payment module.
module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.forms');
return drupal_get_form('commerce_payment_ui_payment_transaction_delete_form', $order, $transaction);
}
; $Id$
name = Payment UI
description = Exposes a default UI for payment method configuration and payment transaction administration.
package = Commerce
dependencies[] = rules_admin
dependencies[] = commerce
dependencies[] = commerce_order
dependencies[] = commerce_order_ui
dependencies[] = commerce_payment
core = 7.x
<?php
// $Id$
/**
* @file
* Default Payment UI for Drupal Commerce.
*/
/**
* Implements hook_menu().
*/
function commerce_payment_ui_menu() {
$items = array();
// Payment tab on orders.
$items['admin/commerce/orders/%commerce_order/payment'] = array(
'title' => 'Payment',
'page callback' => 'commerce_payment_ui_order_tab',
'page arguments' => array(3),
'access callback' => 'commerce_payment_transaction_access',
'access arguments' => array('view', 3),
'type' => MENU_LOCAL_TASK,
'weight' => 10,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
'file' => 'includes/commerce_payment_ui.admin.inc',
);
// Payment transaction operations links.
$items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction'] = array(
'title callback' => 'commerce_payment_ui_payment_transaction_title',
'title arguments' => array(5),
'page callback' => 'commerce_payment_ui_payment_transaction_view',
'page arguments' => array(3, 5, 'administrator'),
'access callback' => 'commerce_payment_transaction_access',
'access arguments' => array('view', 3),
'file' => 'includes/commerce_payment_ui.admin.inc',
);
$items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/view'] = array(
'title' => 'View',
'page callback' => 'commerce_payment_ui_payment_transaction_view',
'page arguments' => array(3, 5, 'administrator'),
'access callback' => 'commerce_payment_transaction_access',
'access arguments' => array('view', 3),
'type' => MENU_DEFAULT_LOCAL_TASK,
'context' => MENU_CONTEXT_INLINE,
'weight' => 0,
'file' => 'includes/commerce_payment_ui.admin.inc',
);
$items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/delete'] = array(
'title' => 'Delete',
'page callback' => 'commerce_payment_ui_payment_transaction_delete_form_wrapper',
'page arguments' => array(3, 5),
'access callback' => 'commerce_payment_transaction_access',
'access arguments' => array('delete', 3),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_INLINE,
'weight' => 10,
'file' => 'includes/commerce_payment_ui.admin.inc',
);
// Payment method Rules administration page.
$items['admin/commerce/config/payment-methods'] = array(
'title' => 'Payment methods',
'description' => 'Enable and configure payment method rule configurations.',
'page callback' => 'commerce_payment_ui_admin_page',
'access arguments' => array('administer payments'),
'file' => 'includes/commerce_payment_ui.admin.inc',
);
// Add the menu items for the various Rules forms.
$controller = new RulesUIController();
$items += $controller->config_menu('admin/commerce/config/payment-methods');
$items['admin/commerce/config/payment-methods/add'] = array(
'title' => 'Add a payment method rule',
'description' => 'Adds an additional payment method rule configuration.',
'page callback' => 'drupal_get_form',
'page arguments' => array('commerce_payment_ui_add_payment_rule_form', 'admin/commerce/config/payment-methods'),
'access arguments' => array('administer payments'),
'file path' => drupal_get_path('module', 'rules_admin'),
'file' => 'rules_admin.inc',
);
return $items;
}
/**
* Menu item title callback: returns the transaction ID for its pages.
*
* @param $transaction
* The transaction object as loaded via the URL wildcard.
* @return
* A page title of the format "Transaction ##".
*/
function commerce_payment_ui_payment_transaction_title($transaction) {
return t('Transaction @transaction_id', array('@transaction_id' => $transaction->transaction_id));
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function commerce_payment_ui_menu_local_tasks_alter(&$data, $router_item, $root_path) {
// Add action link 'admin/commerce/config/payment-methods/add' on
// 'admin/commerce/config/payment-methods'.
if ($root_path == 'admin/commerce/config/payment-methods') {
$item = menu_get_item('admin/commerce/config/payment-methods/add');
if ($item['access']) {
$data['actions']['output'][] = array(
'#theme' => 'menu_local_action',
'#link' => $item,
);
}
}
}
/**
* Implements hook_help().
*/
function commerce_payment_ui_help($path, $arg) {
switch ($path) {
case 'admin/commerce/config/payment-methods':
return t("Payment methods are enabled for use by the rule configurations listed below. An enabled payment rule can specify a payment method to enable using one of the available <em>Enable payment method</em> actions. The action's settings form will contain any necessary settings for the payment method that must be configured before it may be used.");
case 'admin/commerce/config/payment-methods/add':
return t('After setting the label for this rule configuration, you will be redirected to its empty edit page. There you must add an action enabling a particular payment method for use and any conditions that must be met for a customer to be able to choose the payment method during checkout.');
}
}
/**
* Implements hook_views_api().
*/
function commerce_payment_ui_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_payment_ui') . '/includes/views',
);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Adds a Cancel link to the Save button for the payment terminal form that is
* part of the View used as the order Payment tab.
*/
function commerce_payment_ui_form_commerce_payment_order_transaction_add_form_alter(&$form, &$form_state) {
if (!empty($form_state['payment_method'])) {
$form['actions']['submit']['#suffix'] = l(t('Cancel'), 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment');
}
}
/**
* Implements hook_forms().
*/
function commerce_payment_ui_forms($form_id, $args) {
$forms = array();
// Define a wrapper ID for the payment transaction delete confirmation form.
$forms['commerce_payment_ui_payment_transaction_delete_form'] = array(
'callback' => 'commerce_payment_payment_transaction_delete_form',
);
$forms['commerce_payment_ui_add_payment_rule_form'] = array(
'callback' => 'rules_admin_add_reaction_rule',
);
return $forms;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Payment UI module instantiates the payment transaction 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_payment_ui_payment_transaction_delete_form()
*/
function commerce_payment_ui_form_commerce_payment_ui_payment_transaction_delete_form_alter(&$form, &$form_state) {
$form['actions']['cancel']['#markup'] = l(t('Cancel'), 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment');
$form['#submit'][] = 'commerce_payment_ui_payment_transaction_delete_form_submit';
}
/**
* Submit callback for commerce_payment_ui_payment_transaction_delete_form().
*
* @see commerce_payment_ui_form_commerce_payment_ui_payment_transaction_delete_form_alter()
*/
function commerce_payment_ui_payment_transaction_delete_form_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/commerce/orders/' . $form_state['order']->order_id . '/payment';
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Payment UI 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_payment_ui_form_commerce_payment_ui_add_payment_rule_form_alter(&$form, &$form_state) {
unset($form['settings']['help']);
$form['settings']['event']['#type'] = 'value';
$form['settings']['event']['#value'] = 'commerce_payment_methods';
$form['submit']['#suffix'] = l(t('Cancel'), 'admin/commerce/config/payment-methods');
}
/**
* Sets the breadcrumb for transaction pages.
*
* @param $order
* The order object the transaction is for.
* @param $view_mode
* The view mode for the current order page, 'administrator' only for now.
*/
function commerce_payment_ui_set_order_breadcrumb($order, $view_mode = 'administrator') {
$breadcrumb = array();
// Create the breadcrumb array based on the view mode.
if ($view_mode == 'administrator') {
$breadcrumb = array(
l(t('Home'), '<front>'),
l(t('Administration'), 'admin'),
l(t('Store'), 'admin/commerce'),
l(t('Orders', array(), array('context' => 'a drupal commerce order')), 'admin/commerce/orders'),
l(t('Order @order-number', array('@order-number' => $order->order_number)), 'admin/commerce/orders/' . $order->order_id),
l(t('Payment'), 'admin/commerce/orders/' . $order->order_id . '/payment'),
);
}
drupal_set_breadcrumb($breadcrumb);
}
<?php
// $Id$
/**
* Views for the payment UI.
*/
/**
* Implements hook_views_default_views().
*/
function commerce_payment_ui_views_default_views() {
$views = array();
// Payment transaction View with the totals area handler in the footer.
$view = new view;
$view->name = 'commerce_payment_order';
$view->description = 'Display and total an order\'s payment transaction history.';
$view->tag = 'commerce';
$view->base_table = 'commerce_payment_transaction';
$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'] = 'perm';
$handler->display->display_options['access']['perm'] = 'administer payments';
$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['pager']['options']['offset'] = '0';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'status' => 'status',
'created' => 'created',
'payment_method' => 'payment_method',
'remote_id' => 'remote_id',
'message' => 'message',
'amount' => 'amount',
);
$handler->display->display_options['style_options']['default'] = 'created';
$handler->display->display_options['style_options']['info'] = array(
'status' => array(
'sortable' => 0,
'align' => 'views-align-center',
'separator' => '',
),
'created' => array(
'sortable' => 1,
'align' => 'views-align-left',
'separator' => '',
),
'payment_method' => array(
'sortable' => 1,
'align' => 'views-align-left',
'separator' => '',
),
'remote_id' => array(
'sortable' => 0,
'align' => 'views-align-left',
'separator' => '',
),
'message' => array(
'sortable' => 0,
'align' => 'views-align-left',
'separator' => '',
),
'amount' => array(
'sortable' => 1,
'align' => 'views-align-right',
'separator' => '',
),
'operations' => array(
'align' => 'views-align-left',
'separator' => '',
),
);
$handler->display->display_options['style_options']['override'] = 1;
$handler->display->display_options['style_options']['sticky'] = 0;
/* Footer: Commerce Payment Transaction: Totals */
$handler->display->display_options['footer']['totals']['id'] = 'totals';
$handler->display->display_options['footer']['totals']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['footer']['totals']['field'] = 'totals';
$handler->display->display_options['footer']['totals']['empty'] = TRUE;
$handler->display->display_options['footer']['totals']['add_payment_form'] = 1;
/* Field: Commerce Payment Transaction: Status */
$handler->display->display_options['fields']['status']['id'] = 'status';
$handler->display->display_options['fields']['status']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['fields']['status']['field'] = 'status';
$handler->display->display_options['fields']['status']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['status']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['status']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['status']['alter']['trim'] = 0;
$handler->display->display_options['fields']['status']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['status']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['status']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['status']['alter']['html'] = 0;
$handler->display->display_options['fields']['status']['hide_empty'] = 0;
$handler->display->display_options['fields']['status']['empty_zero'] = 0;
/* Field: Commerce Payment Transaction: Created date */
$handler->display->display_options['fields']['created']['id'] = 'created';
$handler->display->display_options['fields']['created']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['fields']['created']['field'] = 'created';
$handler->display->display_options['fields']['created']['label'] = 'Date';
$handler->display->display_options['fields']['created']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['created']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['created']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['created']['alter']['trim'] = 0;
$handler->display->display_options['fields']['created']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['created']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['created']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['created']['alter']['html'] = 0;
$handler->display->display_options['fields']['created']['hide_empty'] = 0;
$handler->display->display_options['fields']['created']['empty_zero'] = 0;
$handler->display->display_options['fields']['created']['date_format'] = 'custom';
$handler->display->display_options['fields']['created']['custom_date_format'] = 'm/d/Y';
/* Field: Commerce Payment Transaction: Payment method */
$handler->display->display_options['fields']['payment_method']['id'] = 'payment_method';
$handler->display->display_options['fields']['payment_method']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['fields']['payment_method']['field'] = 'payment_method';
$handler->display->display_options['fields']['payment_method']['label'] = 'Method';
$handler->display->display_options['fields']['payment_method']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['payment_method']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['payment_method']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['payment_method']['alter']['trim'] = 0;
$handler->display->display_options['fields']['payment_method']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['payment_method']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['payment_method']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['payment_method']['alter']['html'] = 0;
$handler->display->display_options['fields']['payment_method']['hide_empty'] = 0;
$handler->display->display_options['fields']['payment_method']['empty_zero'] = 0;
/* Field: Commerce Payment Transaction: Remote ID */
$handler->display->display_options['fields']['remote_id']['id'] = 'remote_id';
$handler->display->display_options['fields']['remote_id']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['fields']['remote_id']['field'] = 'remote_id';
$handler->display->display_options['fields']['remote_id']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['remote_id']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['remote_id']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['remote_id']['alter']['trim'] = 0;
$handler->display->display_options['fields']['remote_id']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['remote_id']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['remote_id']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['remote_id']['alter']['html'] = 0;
$handler->display->display_options['fields']['remote_id']['empty'] = '-';
$handler->display->display_options['fields']['remote_id']['hide_empty'] = 0;
$handler->display->display_options['fields']['remote_id']['empty_zero'] = 0;
/* Field: Commerce Payment Transaction: Message */
$handler->display->display_options['fields']['message']['id'] = 'message';
$handler->display->display_options['fields']['message']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['fields']['message']['field'] = 'message';
$handler->display->display_options['fields']['message']['label'] = 'Result message';
$handler->display->display_options['fields']['message']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['message']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['message']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['message']['alter']['trim'] = 0;
$handler->display->display_options['fields']['message']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['message']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['message']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['message']['alter']['html'] = 0;
$handler->display->display_options['fields']['message']['empty'] = '-';
$handler->display->display_options['fields']['message']['hide_empty'] = 0;
$handler->display->display_options['fields']['message']['empty_zero'] = 0;
/* Field: Commerce Payment Transaction: Amount */
$handler->display->display_options['fields']['amount']['id'] = 'amount';
$handler->display->display_options['fields']['amount']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['fields']['amount']['field'] = 'amount';
$handler->display->display_options['fields']['amount']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['amount']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['amount']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['amount']['alter']['trim'] = 0;
$handler->display->display_options['fields']['amount']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['amount']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['amount']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['amount']['alter']['html'] = 0;
$handler->display->display_options['fields']['amount']['hide_empty'] = 0;
$handler->display->display_options['fields']['amount']['empty_zero'] = 0;
/* Field: Commerce Payment Transaction: Operations links */
$handler->display->display_options['fields']['operations']['id'] = 'operations';
$handler->display->display_options['fields']['operations']['table'] = 'commerce_payment_transaction';
$handler->display->display_options['fields']['operations']['field'] = 'operations';
$handler->display->display_options['fields']['operations']['label'] = 'Operations';
$handler->display->display_options['fields']['operations']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['operations']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['operations']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['operations']['alter']['trim'] = 0;
$handler->display->display_options['fields']['operations']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['operations']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['operations']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['operations']['alter']['html'] = 0;
$handler->display->display_options['fields']['operations']['hide_empty'] = 0;
$handler->display->display_options['fields']['operations']['empty_zero'] = 0;
/* Argument: Commerce Payment Transaction: Order ID */
$handler->display->display_options['arguments']['order_id']['id'] = 'order_id';
$handler->display->display_options['arguments']['order_id']['table'] = 'commerce_payment_transaction';
$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']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['order_id']['break_phrase'] = 0;
$handler->display->display_options['arguments']['order_id']['not'] = 0;
$views[$view->name] = $view;
return $views;
}
/* $Id$ */
.commerce-price-full .form-item {
display: inline;
margin-right: 1em;
}
#edit-parameter-value .commerce-price-full {
margin: 1em 0;
}
; $Id$
name = Price
description = Defines the price field and a price alteration system.
package = Commerce
dependencies[] = commerce
dependencies[] = rules
core = 7.x
; Module file includes
files[] = commerce_price.rules.inc
<?php
// $Id$
/**
* Implements hook_schema().
*/
function commerce_price_schema() {
$schema = array();
$schema['commerce_calculated_price'] = array(
'description' => 'Stores pre-calculated dynamic prices.',
'fields' => array(
'module' => array(
'description' => 'The name of the module performing the calculation.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'module_key' => array(
'description' => 'A module specific key useful for indicating the context of a particular calculation, e.g. the IDs of Rules evaluated to produce the calculated price.',
'type' => 'text',
'size' => 'medium',
'not null' => TRUE,
),
'entity_type' => array(
'description' => 'The type of entity this price belongs to.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'entity_id' => array(
'description' => 'The entity ID of the object this price belongs to.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'field_name' => array(
'description' => 'The name of the field the calculated price relates to.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'language' => array(
'description' => 'The {languages}.language of the entity.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'delta' => array(
'description' => 'The sequence number for this data item, used for multi-value fields',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'amount' => array(
'description' => 'The price amount.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'currency_code' => array(
'description' => 'The currency code for the price.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'data' => array(
'description' => 'A serialized array of additional price data.',
'type' => 'text',
'size' => 'big',
'serialize' => TRUE,
),
'created' => array(
'description' => 'The Unix timestamp when the price was calculated.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'indexes' => array(
'module' => array('module'),
'entity_type' => array('entity_type'),
'entity_id' => array('entity_id'),
),
);
return $schema;
}
/**
* Implements hook_field_schema().
*/
function commerce_price_field_schema($field) {
if ($field['type'] == 'commerce_price') {
return array(
'columns' => array(
'amount' => array(
'description' => 'The price amount.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'currency_code' => array(
'description' => 'The currency code for the price.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'data' => array(
'description' => 'A serialized array of additional price data.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
'serialize' => TRUE,
),
),
'indexes' => array(
'currency_price' => array('amount', 'currency_code'),
),
);
}
}
<?php
// $Id$
/**
* @file
* Defines the Price field with widgets and formatters used to add prices with
* currency codes to various Commerce entities.
*/
/**
* Implements hook_field_info().
*/
function commerce_price_field_info() {
return array(
'commerce_price' => array(
'label' => t('Price'),
'description' => t('This field stores prices for products consisting of an amount and a currency.'),
'settings' => array(),
'instance_settings' => array(),
'default_widget' => 'commerce_price_simple',
'default_formatter' => 'commerce_price_default',
'property_type' => 'commerce_price',
'property_callbacks' => array('commerce_price_property_info_callback'),
),
);
}
/**
* Implements hook_field_validate().
*/
function commerce_price_field_validate($entity_type, $entity, $field, $instance, $langcode, &$items, &$errors) {
// Ensure only numeric values are entered in price fields.
foreach ($items as $delta => $item) {
if (!empty($item['amount']) && !is_numeric($item['amount'])) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'price_numeric',
'message' => t('%name: you must enter a numeric value for the price.', array('%name' => check_plain($instance['label']))),
);
}
}
}
/**
* Implements hook_field_load().
*/
function commerce_price_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
// Convert amounts to their floating point values and deserialize data arrays.
foreach ($entities as $id => $entity) {
foreach ($items[$id] as $delta => $item) {
// Convert the loaded amount integer to a price amount based on the currency.
$items[$id][$delta]['amount'] = commerce_currency_integer_to_amount(
$items[$id][$delta]['amount'],
$items[$id][$delta]['currency_code']
);
// Unserialize the data array if necessary.
if (!empty($items[$id][$delta]['data'])) {
$items[$id][$delta]['data'] = unserialize($items[$id][$delta]['data']);
}
else {
$items[$id][$delta]['data'] = array();
}
}
}
}
/**
* Implements hook_field_presave().
*/
function commerce_price_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
// Convert amounts to integers and serialize data arrays before saving.
foreach ($items as $delta => $item) {
// Convert the price amount to an integer based on the currency.
$items[$delta]['amount'] = commerce_currency_amount_to_integer(
$items[$delta]['amount'],
$items[$delta]['currency_code']
);
// Serialize an existing data array.
if (isset($item['data']) && is_array($item['data'])) {
$items[$delta]['data'] = serialize($item['data']);
}
}
}
/**
* Implementation of hook_field_is_empty().
*/
function commerce_price_field_is_empty($item, $field) {
return !isset($item['amount']) || (string) $item['amount'] == '';
}
/**
* Creates a required, locked instance of a price field on the specified bundle.
*
* @param $field_name
* The name of the field; if it already exists, a new instance of the existing
* field will be created. For fields governed by the Commerce modules, this
* should begin with commerce_.
* @param $entity_type
* The type of entity the field instance will be attached to.
* @param $bundle
* The bundle name of the entity the field instance will be attached to.
* @param $label
* The label of the field instance.
* @param $weight
* The default weight of the field instance widget and display.
* @param $calculation
* A string indicating the default value of the display formatter's calculation
* setting.
* @param $display
* An array of default display data used for the entity's current view modes.
*/
function commerce_price_create_instance($field_name, $entity_type, $bundle, $label, $weight = 0, $calculation = FALSE, $display = array()) {
// If a field type we know should exist isn't found, clear the Field cache.
if (!field_info_field_types('commerce_price')) {
field_cache_clear();
}
// Look for or add the specified price field to the requested entity bundle.
$field = field_info_field($field_name);
$instance = field_info_instance($entity_type, $field_name, $bundle);
if (empty($field)) {
$field = array(
'field_name' => $field_name,
'type' => 'commerce_price',
'cardinality' => 1,
'entity_types' => array($entity_type),
'translatable' => FALSE,
'locked' => TRUE,
);
$field = field_create_field($field);
}
if (empty($instance)) {
$instance = array(
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'label' => $label,
'required' => TRUE,
'settings' => array(),
// Because this widget is locked, we need it to use the full price widget
// since the currency option can't be adjusted at the moment.
'widget' => array(
'type' => 'commerce_price_full',
'weight' => $weight,
'settings' => array(
'currency_code' => 'default',
),
),
'display' => array(),
);
$entity_info = entity_get_info($entity_type);
// Spoof the default view mode so its display type is set.
$entity_info['view modes']['default'] = array();
foreach ($entity_info['view modes'] as $view_mode => $data) {
$instance['display'][$view_mode] = $display + array(
'label' => 'hidden',
'type' => 'commerce_price_formatted_amount',
'settings' => array(
'calculation' => $calculation,
),
'weight' => $weight,
);
}
field_create_instance($instance);
}
}
/**
* Implements hook_field_formatter_info().
*/
function commerce_price_field_formatter_info() {
return array(
'commerce_price_raw_amount' => array(
'label' => t('Raw amount'),
'field types' => array('commerce_price'),
'settings' => array(
'calculation' => FALSE,
),
),
'commerce_price_formatted_amount' => array(
'label' => t('Formatted amount'),
'field types' => array('commerce_price'),
'settings' => array(
'calculation' => FALSE,
),
),
);
}
/**
* Implements hook_field_formatter_settings_form().
*/
function commerce_price_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
$options = module_invoke_all('commerce_price_field_calculation_options', $field, $instance, $view_mode);
if (empty($options)) {
$element['calculation'] = array(
'#type' => 'value',
'#value' => FALSE,
);
$element['help'] = array(
'#markup' => '<p>' . t('No configuration is necessary. The original price will be displayed as loaded.') . '</p>',
);
}
else {
// Add the option to display the original price; unshifting will give it a
// key of 0 which will equate to FALSE with an Equal operator.
array_unshift($options, t('Display the original price as loaded.'));
$element['calculation'] = array(
'#type' => 'radios',
'#options' => $options,
'#default_value' => empty($settings['calculation']) ? '0' : $settings['calculation'],
);
}
return $element;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function commerce_price_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = array();
if ($settings['calculation'] == FALSE) {
$summary[] = t('Displaying the original price');
}
else {
$summary[] = t('Displaying a calculated price');
}
return implode('<br />', $summary);
}
/**
* Implements hook_field_formatter_prepare_view().
*/
function commerce_price_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
// Allow other modules to prepare the item values prior to formatting.
foreach(module_implements('commerce_price_field_formatter_prepare_view') as $module) {
$function = $module . '_commerce_price_field_formatter_prepare_view';
$function($entity_type, $entities, $field, $instances, $langcode, $items, $displays);
}
}
/**
* Implements hook_field_formatter_view().
*/
function commerce_price_field_formatter_view($object_type, $object, $field, $instance, $langcode, $items, $display) {
$element = array();
// Loop through each price value in this field.
foreach ($items as $delta => $item) {
// Theme the display of the price based on the display type.
if ($display['type'] == 'commerce_price_raw_amount') {
$element[$delta] = array(
'#markup' => check_plain($item['amount']),
);
}
elseif ($display['type'] == 'commerce_price_formatted_amount') {
$element[$delta] = array(
'#markup' => commerce_currency_format($item['amount'], $item['currency_code'], $object),
);
}
}
return $element;
}
/**
* Implements hook_field_widget_info().
*/
function commerce_price_field_widget_info() {
return array(
'commerce_price_simple' => array(
'label' => t('Price textfield'),
'field types' => array('commerce_price'),
'settings' => array(
'currency_code' => 'default',
),
),
'commerce_price_full' => array(
'label' => t('Price with currency'),
'field types' => array('commerce_price'),
'settings' => array(
'currency_code' => 'default',
),
),
);
}
/**
* Implements hook_field_widget_settings_form().
*/
function commerce_price_field_widget_settings_form($field, $instance) {
$form = array();
// Build an options array of allowed currency values including the option for
// the widget to always use the store's default currency.
$options = array(
'default' => t('- Default store currency -'),
);
foreach (commerce_currencies(TRUE) as $currency_code => $currency) {
$options[$currency_code] = t('@code - @name', array('@code' => $currency['code'], '@name' => $currency['name']));
}
$form['currency_code'] = array(
'#type' => 'select',
'#title' => ($instance['widget']['type'] == 'commerce_price_simple') ? t('Currency') : t('Default currency'),
'#options' => $options,
'#default_value' => $instance['widget']['settings']['currency_code'],
);
return $form;
}
/**
* Implements hook_field_widget_form().
*/
function commerce_price_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
// Use the default currency if the setting is not present.
if (empty($instance['widget']['settings']['currency_code']) || $instance['widget']['settings']['currency_code'] == 'default') {
$default_currency_code = NULL;
}
else {
$default_currency_code = $instance['widget']['settings']['currency_code'];
}
// If a price has already been set for this instance prepare default values.
if (isset($items[$delta]['amount'])) {
$currency = commerce_currency_load($items[$delta]['currency_code']);
// Round the default value.
$default_amount = round($items[$delta]['amount'], 2);
// Run it through number_format() to add the decimal places in if necessary.
if (strpos($default_amount, '.') === FALSE || strpos($default_amount, '.') > strlen($default_amount) - $currency['decimals']) {
$default_amount = number_format($default_amount, $currency['decimals'], '.', '');
}
$default_currency_code = $items[$delta]['currency_code'];
}
else {
$default_amount = NULL;
}
// Load the default currency for this instance.
$default_currency = commerce_currency_load($default_currency_code);
$element['#attached']['css'][] = drupal_get_path('module', 'commerce_price') . '/theme/commerce_price.css';
// Build the form based on the type of price widget.
switch ($instance['widget']['type']) {
// The simple widget is just a textfield with a non-changeable currency.
case 'commerce_price_simple':
$element['amount'] = array(
'#type' => 'textfield',
'#title' => $element['#title'],
'#default_value' => $default_amount,
'#size' => 10,
'#field_suffix' => $default_currency['code'],
);
$element['currency_code'] = array(
'#type' => 'value',
'#default_value' => $default_currency['code'],
);
break;
// The full widget is a textfield with a currency select list.
case 'commerce_price_full':
$element['amount'] = array(
'#type' => 'textfield',
'#title' => $element['#title'],
'#default_value' => $default_amount,
'#size' => 10,
);
// Build a currency options list from all enabled currencies.
$options = array();
foreach (commerce_currencies(TRUE) as $currency_code => $currency) {
$options[$currency_code] = check_plain($currency['code']);
}
// If the current currency value is not available, add it now with a
// message in the help text explaining it.
if (empty($options[$default_currency['code']])) {
$options[$default_currency['code']] = check_plain($default_currency['code']);
$description = t('The currency set for this price is not currently enabled. If you change it now, you will not be able to set it back.');
}
else {
$description = '';
}
// If only one currency option is available, don't use a select list.
if (count($options) == 1) {
$currency_code = key($options);
$element['amount']['#field_suffix'] = $currency_code;
$element['currency_code'] = array(
'#type' => 'value',
'#default_value' => $currency_code,
);
}
else {
$element['amount']['#prefix'] = '<div class="commerce-price-full">';
$element['currency_code'] = array(
'#type' => 'select',
'#description' => $description,
'#options' => $options,
'#default_value' => isset($items[$delta]['currency_code']) ? $items[$delta]['currency_code'] : $default_currency['code'],
'#suffix' => '</div>',
);
}
break;
}
return $element;
}
/**
* Implements hook_field_widget_error().
*/
function commerce_price_field_widget_error($element, $error, $form, &$form_state) {
form_error($element['amount'], $error['message']);
}
/**
* Callback to alter the property info of price fields.
*
* @see commerce_price_field_info().
*/
function commerce_price_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
$name = $field['field_name'];
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
$property['type'] = ($field['cardinality'] != 1) ? 'list<commerce_price>' : 'commerce_price';
$property['getter callback'] = 'entity_metadata_field_verbatim_get';
$property['setter callback'] = 'entity_metadata_field_verbatim_set';
$property['auto creation'] = 'commerce_price_field_data_auto_creation';
$property['property info'] = commerce_price_field_data_property_info();
unset($property['query callback']);
}
/**
* Returns the default array structure for a Price field for use when creating
* new data arrays through an entity metadata wrapper.
*/
function commerce_price_field_data_auto_creation() {
return array('amount' => 0, 'currency_code' => commerce_default_currency(), 'data' => array());
}
/**
* Defines info for the properties of the Price field data structure.
*/
function commerce_price_field_data_property_info($name = NULL) {
return array(
'amount' => array(
'label' => t('Amount'),
'description' => !empty($name) ? t('Amount value of field %name', array('%name' => $name)) : '',
'type' => 'decimal',
'getter callback' => 'entity_property_verbatim_get',
'setter callback' => 'entity_property_verbatim_set',
),
'currency_code' => array(
'label' => t('Currency'),
'description' => !empty($name) ? t('Currency code of field %name', array('%name' => $name)) : '',
'type' => 'text',
'getter callback' => 'entity_property_verbatim_get',
'setter callback' => 'entity_property_verbatim_set',
'options list' => 'commerce_currency_code_options_list',
),
'data' => array(
'label' => t('Data'),
'description' => !empty($name) ? t('Data array of field %name', array('%name' => $name)) : '',
'type' => 'struct',
'getter callback' => 'entity_property_verbatim_get',
'setter callback' => 'entity_property_verbatim_set',
),
);
}
<?php
// $Id$
/**
* @file
* Rules integration for the Price module.
*
* @addtogroup rules
* @{
*/
/**
* Returns parameters suitable for using Rules to alter prices.
*/
function commerce_price_rules_variables() {
$args = array(
'price' => array(
'type' => 'commerce_price',
'label' => t('Price'),
),
);
return $args;
}
/**
* Implements hook_rules_data_info().
*/
function commerce_price_rules_data_info() {
return array(
'commerce_price' => array(
'label' => t('price'),
'ui class' => 'RulesDataUICommercePrice',
'wrap' => TRUE,
'property info' => commerce_price_field_data_property_info(),
),
);
}
/**
* Defines a commerce_price input form for Rules actions altering price fields.
*/
class RulesDataUICommercePrice extends RulesDataUI implements RulesDataDirectInputFormInterface {
public static function getDefaultMode() {
return 'input';
}
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
// Use the default currency if the setting is not present.
if (empty($settings[$name]['currency_code']) || $settings[$name]['currency_code'] == 'default') {
$default_currency_code = NULL;
}
else {
$default_currency_code = $settings[$name]['currency_code'];
}
// If a price has already been set for this instance prepare default values.
if (isset($settings[$name]['amount'])) {
$currency = commerce_currency_load($settings[$name]['currency_code']);
// Round the default value.
$default_amount = round($settings[$name]['amount'], 2);
// Run it through number_format() to add the decimal places in if necessary.
if (strpos($default_amount, '.') === FALSE || strpos($default_amount, '.') > strlen($default_amount) - $currency['decimals']) {
$default_amount = number_format($default_amount, $currency['decimals'], '.', '');
}
$default_currency_code = $settings[$name]['currency_code'];
}
else {
$default_amount = NULL;
}
// Load the default currency for this instance.
$default_currency = commerce_currency_load($default_currency_code);
$form[$name]['#attached']['css'][] = drupal_get_path('module', 'commerce_price') . '/theme/commerce_price.css';
$form[$name]['amount'] = array(
'#type' => 'textfield',
'#default_value' => $default_amount,
'#size' => 10,
);
// Build a currency options list from all enabled currencies.
$options = array();
foreach (commerce_currencies(TRUE) as $currency_code => $currency) {
$options[$currency_code] = check_plain($currency['code']);
}
// If the current currency value is not available, add it now with a
// message in the help text explaining it.
if (empty($options[$default_currency['code']])) {
$options[$default_currency['code']] = check_plain($default_currency['code']);
$description = t('The currency set for this action is not currently enabled. If you change it now, you will not be able to set it back.');
}
else {
$description = '';
}
// If only one currency option is available, don't use a select list.
if (count($options) == 1) {
$currency_code = key($options);
$form[$name]['amount']['#field_suffix'] = $currency_code;
$form[$name]['currency_code'] = array(
'#type' => 'value',
'#default_value' => $currency_code,
);
}
else {
$form[$name]['amount']['#prefix'] = '<div class="commerce-price-full">';
$form[$name]['currency_code'] = array(
'#type' => 'select',
'#description' => $description,
'#options' => $options,
'#default_value' => isset($settings[$name]['currency_code']) ? $settings[$name]['currency_code'] : $default_currency['code'],
'#suffix' => '</div>',
);
}
return $form;
}
public static function render($value) {
return array(
'content' => array(
'#markup' => commerce_currency_format($value['amount'], $value['currency_code']),
),
);
}
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Hooks provided by the Product module.
*/
/**
* Lets modules specify the path information expected by a uri callback.
*
* The Product module defines a uri callback for the product entity even though
* it doesn't actually define any product menu items. The callback invokes this
* hook and will return the first set of path information it finds. If the
* Product UI module is enabled, it will alter the product entity definition to
* use its own uri callback that checks commerce_product_uri() for a return
* value and defaults to an administrative link defined by that module.
*
* This hook is used as demonstrated below by the Product Reference module to
* direct modules to link the product to the page where it is actually displayed
* to the user. Currently this is specific to nodes, but the system should be
* beefed up to accommodate even non-entity paths.
*
* @param $product
* The product object whose uri information should be returned.
*
* @return
* Implementations of this hook should return an array of information as
* expected to be returned to entity_uri() by a uri callback function.
*
* @see commerce_product_uri()
* @see entity_uri()
*/
function hook_commerce_product_uri($product) {
// If the product has a display context, use it entity_uri().
if (!empty($product->display_context)) {
return entity_uri($product->display_context['entity_type'], $product->display_context['entity']);
}
}
/**
* Allows you to prepare product data before it is saved.
*
* @param $product
* The product object to be saved.
*
* @see rules_invoke_all()
*/
function hook_commerce_product_presave(&$product) {
// No example.
}
/**
* Lets modules prevent the deletion of a particular product.
*
* Before a product can be deleted, other modules are given the chance to say
* whether or not the action should be allowed. Modules implementing this hook
* can check for reference data or any other reason to prevent a product from
* being deleted and return FALSE to prevent the action.
*
* This is an API level hook, so implementations should not display any messages
* to the user (although logging to the watchdog is fine).
*
* @param $product
* The product to be deleted.
*
* @return
* TRUE or FALSE indicating whether or not the given product can be deleted.
*
* @see commerce_product_reference_commerce_product_can_delete()
*/
function hook_commerce_product_can_delete($product) {
// Use EntityFieldQuery to look for line items referencing this product and do
// not allow the delete to occur if one exists.
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'commerce_line_item', '=')
->entityCondition('bundle', 'product', '=')
->fieldCondition('product', 'product_id', $product->product_id, '=')
->count();
return $query->execute() > 0 ? FALSE : TRUE;
}
<?php
// $Id$
/**
* @file
* The controller for the product entity containing the CRUD operations.
*/
/**
* The controller class for products contains methods for the product CRUD
* operations. The load method is inherited from the default controller.
*/
class CommerceProductEntityController extends DrupalDefaultEntityController {
/**
* Create a default product.
*
* @param $type
* The machine-readable type of the product.
*
* @return
* A product object with all default fields initialized.
*/
public function create($type = '') {
return (object) array(
'product_id' => '',
'is_new' => TRUE,
'sku' => '',
'type' => $type,
'title' => '',
'uid' => '',
'status' => 1,
'created' => '',
'changed' => '',
);
}
/**
* Saves a product.
*
* @param $product
* The full product object to save.
*
* @return
* The saved product object.
*/
public function save($product) {
$transaction = db_transaction();
try {
$product->changed = REQUEST_TIME;
// Give modules the opportunity to prepare field data for saving.
rules_invoke_all('commerce_product_presave', $product);
field_attach_presave('commerce_product', $product);
// If this is a new product...
if ((isset($product->is_new) && $product->is_new) || empty($product->product_id)) {
// Set the creation timestamp if not set.
if (!isset($product->created) || empty($product->created)) {
$product->created = REQUEST_TIME;
}
// Save the new product and fields.
drupal_write_record('commerce_product', $product);
field_attach_insert('commerce_product', $product);
$op = 'insert';
}
else {
// Invoke presave to let modules react before the save.
rules_invoke_all('commerce_product_presave', $product);
// Save the updated product and fields.
drupal_write_record('commerce_product', $product, 'product_id');
field_attach_update('commerce_product', $product);
$op = 'update';
}
module_invoke_all('commerce_product_' . $op, $product);
module_invoke_all('entity_' . $op, $product, 'commerce_product');
rules_invoke_event('commerce_product_' . $op, $product);
// Ignore slave server temporarily to give time for the
// saved product to be propagated to the slave.
db_ignore_slave();
return $product;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_product', $e);
throw $e;
}
}
/**
* Deletes multiple products by ID.
*
* @param $product_ids
* An array of product IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
public function delete($product_ids) {
if (!empty($product_ids)) {
$products = $this->load($product_ids, array());
// Ensure the products can actually be deleted.
foreach ((array) $products as $product_id => $product) {
if (in_array(FALSE, module_invoke_all('commerce_product_can_delete', $product))) {
unset($products[$product_id]);
}
}
// If none of the specified products can be deleted, return FALSE.
if (empty($products)) {
return FALSE;
}
$transaction = db_transaction();
try {
db_delete('commerce_product')
->condition('product_id', $product_ids, 'IN')
->execute();
foreach ($products as $product_id => $product) {
module_invoke_all('commerce_product_delete', $product);
field_attach_delete('commerce_product', $product);
rules_invoke_event('commerce_product_delete', $product);
}
// Ignore slave server temporarily to give time for the
// saved product to be propagated to the slave.
db_ignore_slave();
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('commerce_product', $e);
throw $e;
}
// Clear the page and block and product_load_multiple caches.
cache_clear_all();
$this->resetCache();
}
return TRUE;
}
}
/* $Id$ */
.sku-label, .title-label, .status-label {
font-weight: bold;
}
<?php
// $Id$
/**
* @file
* Forms for creating, editing, and deleting products.
*/
/**
* Form callback: create or edit a product.
*
* @param $product
* The product object to edit or for a create form an empty product object
* with only a product type defined.
*/
function commerce_product_product_form($form, &$form_state, $product) {
// 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_product') . '/includes/commerce_product.forms.inc';
// Add the default field elements.
// TODO: Update description to include the acceptable product tokens.
$form['sku'] = array(
'#type' => 'textfield',
'#title' => t('Product SKU'),
'#description' => t('Supply a unique identifier for this product using letters, numbers, hypens, and underscores.'),
'#default_value' => $product->sku,
'#maxlength' => 128,
'#required' => TRUE,
'#weight' => -10,
);
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $product->title,
'#maxlength' => 255,
'#required' => TRUE,
'#weight' => -5,
);
// Add the field related form elements.
$form_state['commerce_product'] = $product;
field_attach_form('commerce_product', $product, $form, $form_state);
$form['status'] = array(
'#type' => 'radios',
'#title' => t('Status'),
'#description' => t('Disabled products cannot be added to shopping carts and may be hidden in administrative product lists.'),
'#options' => array(
'1' => t('Active'),
'0' => t('Disabled'),
),
'#default_value' => $product->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 product'),
'#submit' => $submit + array('commerce_product_product_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_product_product_form_validate';
return $form;
}
/**
* Validation callback for commerce_product_product_form().
*/
function commerce_product_product_form_validate($form, &$form_state) {
$product = $form_state['commerce_product'];
// TODO: Resolve workflow issues pertaining to token replacement in SKUs.
// Perform token replacement on the entered SKU.
// $sku = commerce_product_replace_sku_tokens($form_state['values']['sku'], $product);
// Until the above is resolved, simply use the SKU as entered with no tokens.
$sku = $form_state['values']['sku'];
// If invalid tokens were specified, throw an error.
if ($sku === FALSE) {
form_set_error('sku', t('The SKU contains invalid tokens.'));
}
else {
// Validate the remaining characters of the string.
if (!commerce_product_validate_sku_characters($sku)) {
form_set_error('sku', t('The SKU must contain only letters, numbers, hyphens, and underscores.'));
}
// Ensure the proposed SKU is unique.
if (!commerce_product_validate_sku_unique($sku, $product->product_id)) {
form_set_error('sku', t('This SKU is already in use and must be unique. Please supply another value.'));
}
}
// Notify field widgets to validate their data.
field_attach_form_validate('commerce_product', $product, $form, $form_state);
}
/**
* Submit callback for commerce_product_product_form().
*/
function commerce_product_product_form_submit($form, &$form_state) {
global $user;
$product = &$form_state['commerce_product'];
// Save default parameters back into the $product object.
$product->sku = $form_state['values']['sku'];
$product->title = $form_state['values']['title'];
$product->status = $form_state['values']['status'];
// Set the product's uid if it's being created at this time.
if (empty($product->product_id)) {
$product->uid = $user->uid;
}
// Notify field widgets.
field_attach_submit('commerce_product', $product, $form, $form_state);
// Save the product.
commerce_product_save($product);
// Redirect based on the button clicked.
drupal_set_message(t('Product saved.'));
}
/**
* Form callback: confirmation form for deleting a product.
*
* @param $product
* The product object to be deleted.
*
* @see confirm_form()
*/
function commerce_product_product_delete_form($form, &$form_state, $product) {
$form_state['product'] = $product;
// 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_product') . '/includes/commerce_product.forms.inc';
$form['#submit'][] = 'commerce_product_product_delete_form_submit';
$form = confirm_form($form,
t('Are you sure you want to delete %title?', array('%title' => $product->title)),
'',
drupal_render(commerce_product_build_content($product)) . '<p>' . t('Deleting this product cannot be undone.', array('@sku' => $product->sku)) . '</p>',
t('Delete'),
t('Cancel'),
'confirm'
);
return $form;
}
/**
* Submit callback for commerce_product_product_delete_form().
*/
function commerce_product_product_delete_form_submit($form, &$form_state) {
$product = $form_state['product'];
if (commerce_product_delete($product->product_id)) {
drupal_set_message(t('%title has been deleted.', array('%title' => $product->title)));
watchdog('commerce_product', 'Deleted product %title (SKU: @sku).', array('%title' => $product->title, '@sku' => $product->sku), WATCHDOG_NOTICE);
}
else {
drupal_set_message(t('%title could not be deleted.', array('%title' => $product->title)), 'error');
}
}
; $Id$
name = Product
description = Defines the Product entity and associated features.
package = Commerce
dependencies[] = rules
dependencies[] = commerce_price
core = 7.x
; Module includes
files[] = includes/commerce_product.controller.inc
; Views handlers
files[] = includes/views/handlers/commerce_product_handler_argument_product_id.inc
files[] = includes/views/handlers/commerce_product_handler_field_product.inc
files[] = includes/views/handlers/commerce_product_handler_field_product_type.inc
files[] = includes/views/handlers/commerce_product_handler_field_product_link.inc
files[] = includes/views/handlers/commerce_product_handler_field_product_link_delete.inc
files[] = includes/views/handlers/commerce_product_handler_field_product_link_edit.inc
files[] = includes/views/handlers/commerce_product_handler_field_product_operations.inc
files[] = includes/views/handlers/commerce_product_handler_filter_product_type.inc
; Simple tests
files[] = tests/commerce_product.test
<?php
// $Id$
/**
* @file
* Provides metadata for the product entity.
*/
/**
* Implements hook_entity_property_info().
*/
function commerce_product_entity_property_info() {
$info = array();
// Add meta-data about the basic commerce_product properties.
$properties = &$info['commerce_product']['properties'];
$properties['product_id'] = array(
'label' => t('Product ID'),
'description' => t('The internal numeric ID of the product.'),
'type' => 'integer',
);
$properties['sku'] = array(
'label' => t('SKU'),
'description' => t('The human readable product SKU.'),
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
'required' => TRUE,
'query callback' => 'entity_metadata_table_query',
);
$properties['type'] = array(
'label' => t('Type'),
'description' => t('The type of the product.'),
'type' => 'token',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer products',
'options list' => 'commerce_product_type_options_list',
'required' => TRUE,
'query callback' => 'entity_metadata_table_query',
);
$properties['title'] = array(
'label' => t('Title'),
'description' => t('The title of the product.'),
'type' => 'text',
'setter callback' => 'entity_property_verbatim_set',
'required' => TRUE,
'query callback' => 'entity_metadata_table_query',
);
$properties['edit_url'] = array(
'label' => t('Edit URL'),
'description' => t("The URL of the product's edit page."),
'getter callback' => 'commerce_product_get_properties',
'type' => 'uri',
);
$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 products',
);
$properties['changed'] = array(
'label' => t('Date changed'),
'description' => t('The date the product was most recently updated.'),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'query callback' => 'entity_metadata_table_query',
'setter permission' => 'administer products',
);
$properties['uid'] = array(
'label' => t("Creator ID"),
'type' => 'integer',
'description' => t("The unique ID of the product creator."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer products',
'clear' => array('creator'),
'query callback' => 'entity_metadata_table_query',
);
$properties['creator'] = array(
'label' => t("Creator"),
'type' => 'user',
'description' => t("The creator of the product."),
'getter callback' => 'commerce_product_get_properties',
'setter callback' => 'commerce_product_set_properties',
'setter permission' => 'administer products',
'required' => TRUE,
'clear' => array('uid'),
);
return $info;
}
/**
* Implements hook_entity_property_info_alter() on top of the Product module.
*/
function commerce_product_entity_property_info_alter(&$info) {
// Move the default price property to the product by default; as it is a
// required default field, this makes dealing with it more convenient.
$properties = array();
foreach ($info['commerce_product']['bundles'] as $bundle => $bundle_info) {
$bundle_info += array('properties' => array());
$properties += $bundle_info['properties'];
}
$info['commerce_product']['properties']['commerce_price'] = $properties['commerce_price'];
}
<?php
// $Id$
/**
* Implements hook_schema().
*/
function commerce_product_schema() {
$schema = array();
$schema['commerce_product'] = array(
'description' => 'The base table for products.',
'fields' => array(
'product_id' => array(
'description' => 'The primary identifier for a product, used internally only.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'sku' => array(
'description' => 'The unique, human-readable identifier for a product.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'type' => array(
'description' => 'The {commerce_product_type}.type of this product.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'language' => array(
'description' => 'The {languages}.language of this product.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'title' => array(
'description' => 'The title of this product, always treated as non-markup plain text.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'uid' => array(
'description' => 'The {users}.uid that created this product.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'status' => array(
'description' => 'Boolean indicating whether or not the product appears in lists and may be added to orders.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 1,
),
'created' => array(
'description' => 'The Unix timestamp when the product was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'changed' => array(
'description' => 'The Unix timestamp when the product was most recently saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('product_id'),
'indexes' => array(
'type' => array('type'),
),
'unique keys' => array(
'sku' => array('sku'),
),
'foreign keys' => array(
'creator' => array(
'table' => 'users',
'columns' => array('uid' => 'uid'),
),
),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function commerce_product_uninstall() {
field_delete_field('commerce_price');
}
<?php
// $Id$
/**
* @file
* Defines the core Commerce product entity, including the entity itself, the
* bundle definitions (product types), and various API functions to manage
* products and interact with them through forms and autocompletes.
*/
/**
* Implements hook_menu().
*/
function commerce_product_menu() {
$items = array();
$items['commerce_product/autocomplete'] = array(
'title' => 'commerce_product autocomplete',
'page callback' => 'commerce_product_autocomplete',
'access arguments' => array('access products'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_entity_info().
*/
function commerce_product_entity_info() {
$return = array(
'commerce_product' => array(
'label' => t('Product'),
'controller class' => 'CommerceProductEntityController',
'base table' => 'commerce_product',
'fieldable' => TRUE,
'entity keys' => array(
'id' => 'product_id',
'bundle' => 'type',
'label' => 'title',
),
'bundle keys' => array(
'bundle' => 'type',
),
'bundles' => array(),
'load hook' => 'commerce_product_load',
'view modes' => array(
'full' => array(
'label' => t('Admin display'),
'custom settings' => FALSE,
),
'node_teaser' => array(
'label' => t('Node: Teaser'),
'custom settings' => TRUE,
),
'node_full' => array(
'label' => t('Node: Full content'),
'custom settings' => FALSE,
),
'node_rss' => array(
'label' => t('Node: RSS'),
'custom settings' => FALSE,
),
),
'uri callback' => 'commerce_product_uri',
'creation callback' => '_commerce_product_create',
'save callback' => 'commerce_product_save',
'deletion callback' => 'commerce_product_delete',
'access callback' => 'commerce_product_access',
'token type' => 'product',
),
);
foreach (commerce_product_type_get_name() as $type => $name) {
$return['commerce_product']['bundles'][$type] = array(
'label' => $name,
);
}
return $return;
}
/**
* Entity uri callback: gives modules a chance to specify a path for a product.
*/
function commerce_product_uri($product) {
// Allow modules to specify a path, returning the first one found.
foreach (module_implements('commerce_product_uri') as $module) {
$uri = module_invoke($module, 'commerce_product_uri', $product);
// If the implementation returned data, use that now.
if (!empty($uri)) {
return $uri;
}
}
}
/**
* Implements hook_field_extra_fields().
*/
function commerce_product_field_extra_fields() {
$extra = array();
foreach (commerce_product_types() as $type => $product_type) {
$extra['commerce_product'][$type] = array(
'form' => array(
'sku' => array(
'label' => 'Product SKU',
'description' => t('Product module SKU form element'),
'weight' => -10,
),
'title' => array(
'label' => 'Title',
'description' => t('Product module title form element'),
'weight' => -5,
),
'status' => array(
'label' => 'Status',
'description' => t('Product module status form element'),
'weight' => 35,
),
),
'display' => array(
'sku' => array(
'label' => 'Product SKU',
'description' => t('The human readable identifier of the product'),
'weight' => -10,
),
'title' => array(
'label' => 'Title',
'description' => t('Full product title'),
'weight' => -5,
),
),
);
}
return $extra;
}
/**
* Implements hook_views_api().
*/
function commerce_product_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_product') . '/includes/views',
);
}
/**
* Implements hook_permission().
*/
function commerce_product_permission() {
$permissions = array(
'administer products' => array(
'title' => t('Administer products'),
'description' => t('Allows users to perform any action on products of any type.'),
'restrict access' => TRUE,
),
'administer product types' => array(
'title' => t('Administer product types'),
'description' => t('Allows users to configure product types and their fields.'),
'restrict access' => TRUE,
),
'access products' => array(
'title' => t('Access products'),
'description' => t('Allows users to view lists of products in the Store admin and reference lists.'),
),
);
// Add product type specific permissions. Note that users with administer
// products permission should have access to do anything the permissions below
// grant a user to do.
foreach (commerce_product_type_get_name() as $type => $name) {
$permissions['create ' . $type . ' products'] = array(
'title' => t('Create %type products', array('%type' => $name)),
);
$permissions['edit any ' . $type . ' product'] = array(
'title' => t('Edit or delete any %type product', array('%type' => $name)),
);
$permissions['edit own ' . $type . ' products'] = array(
'title' => t('Edit or delete own %type products', array('%type' => $name)),
);
}
return $permissions;
}
/**
* Implements hook_enable().
*/
function commerce_product_enable() {
// Loop through and configure all the currently defined product types.
foreach (commerce_product_types() as $type => $product_type) {
commerce_product_configure_product_type($type);
}
}
/**
* Implements hook_modules_enabled().
*/
function commerce_product_modules_enabled($modules) {
// Loop through all the enabled modules.
foreach ($modules as $module) {
// If the module implements hook_commerce_product_type_info()...
if (module_hook($module, 'commerce_product_type_info')) {
$product_types = module_invoke($module, 'commerce_product_type_info');
// Loop through and configure the product types defined by the module.
foreach ($product_types as $type => $product_type) {
commerce_product_configure_product_type($type);
}
}
}
}
/**
* Returns an array of product type arrays keyed by type.
*/
function commerce_product_types() {
// First check the static cache for a product types array.
$product_types = &drupal_static(__FUNCTION__);
// If it did not exist, fetch the types now.
if (!isset($product_types)) {
$product_types = array();
// Find product types defined by hook_commerce_product_type_info().
foreach (module_implements('commerce_product_type_info') as $module) {
foreach (module_invoke($module, 'commerce_product_type_info') as $type => $product_type) {
// Set the module each product type is defined by.
$product_type['module'] = $module;
$product_types[$type] = $product_type;
}
}
// Last allow the info to be altered by other modules.
drupal_alter('commerce_product_type_info', $product_types);
}
return $product_types;
}
/**
* Resets the cached list of product types.
*/
function commerce_product_types_reset() {
$product_types = &drupal_static('commerce_product_types');
$product_types = NULL;
}
/**
* Loads a product type.
*
* @param $type
* The machine-readable name of the product type; accepts normal machine names
* and URL prepared machine names with underscores replaced by hyphens.
*/
function commerce_product_type_load($type) {
$type = strtr($type, array('-' => '_'));
$product_types = commerce_product_types();
return !empty($product_types[$type]) ? $product_types[$type] : FALSE;
}
/**
* Returns the human readable name of any or all product types.
*
* @param $type
* Optional parameter specifying the type whose name to return.
*
* @return
* Either an array of all product 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_product_type_get_name($type = NULL) {
$product_types = commerce_product_types();
// Return a type name if specified and it exists.
if (!empty($type)) {
if (isset($product_types[$type])) {
return $product_types[$type]['name'];
}
else {
// Return FALSE if it does not exist.
return FALSE;
}
}
// Otherwise turn the array values into the type name only.
foreach ($product_types as $key => $value) {
$product_types[$key] = $value['name'];
}
return $product_types;
}
/**
* Wraps commerce_product_type_get_name() for the Entity module.
*/
function commerce_product_type_options_list() {
return commerce_product_type_get_name();
}
/**
* Title callback: return the human-readable product type name.
*/
function commerce_product_type_title($product_type) {
return $product_type['name'];
}
/**
* Returns a path argument from a product type.
*/
function commerce_product_type_to_arg($type) {
return strtr($type, '_', '-');
}
/**
* Ensures a base price field is present on a product type bundle.
*/
function commerce_product_configure_product_type($type) {
commerce_price_create_instance('commerce_price', 'commerce_product', $type, t('Price'), 0, 'calculated_sell_price');
}
/**
* Returns an initialized product object.
*
* @param $type
* The machine-readable type of the product.
*
* @return
* A product object with all default fields initialized.
*/
function commerce_product_new($type = '') {
return entity_get_controller('commerce_product')->create($type);
}
/**
* Creation callback for the Entity module.
*/
function _commerce_product_create($values = array()) {
// Create a new product of the specified type.
$product = commerce_product_new($values['type']);
unset($values['type']);
$wrapper = entity_metadata_wrapper('commerce_product', $product);
foreach ($values as $name => $value) {
$wrapper->$name->set($value);
}
return $wrapper->value();
}
/**
* Saves a product.
*
* @param $product
* The full product object to save.
*
* @return
* The saved product object.
*/
function commerce_product_save($product) {
return entity_get_controller('commerce_product')->save($product);
}
/**
* Loads a product by ID.
*/
function commerce_product_load($product_id) {
if (empty($product_id)) {
return FALSE;
}
$products = commerce_product_load_multiple(array($product_id), array());
return $products ? reset($products) : FALSE;
}
/**
* Loads a product by SKU.
*/
function commerce_product_load_by_sku($sku) {
$products = commerce_product_load_multiple(array(), array('sku' => $sku));
return $products ? reset($products) : FALSE;
}
/**
* Loads multiple products by ID or based on a set of matching conditions.
*
* @see entity_load()
*
* @param $product_ids
* An array of product IDs.
* @param $conditions
* An array of conditions on the {commerce_product} table in the form
* 'field' => $value.
* @param $reset
* Whether to reset the internal product loading cache.
*
* @return
* An array of product objects indexed by product_id.
*/
function commerce_product_load_multiple($product_ids = array(), $conditions = array(), $reset = FALSE) {
if (empty($product_ids) && empty($conditions)) {
return array();
}
return entity_load('commerce_product', $product_ids, $conditions, $reset);
}
/**
* Generate an array for rendering the given product.
*
* @param $product
* A fully loaded product object.
* @param $view_mode
* The view mode for displaying the product, 'full', 'node_full',
* 'node_teaser', or 'node_rss'.
*
* @return
* An array as expected by drupal_render().
*/
function commerce_product_build_content($product, $view_mode = 'full', $breadcrumb = TRUE) {
// Remove previously built content, if it exists.
$product->content = array();
// Prepare a reusable array representing the CSS file to attach to the view.
$attached = array(
'css' => array(drupal_get_path('module', 'commerce_product_ui') . '/theme/commerce_product.css'),
);
// Add the default fields inherent to the product entity.
$product->content['sku'] = array(
'#markup' => theme('commerce_product_sku', array('sku' => check_plain($product->sku), 'label' => t('SKU:'), 'product' => $product)),
'#attached' => $attached,
);
// Only display the title and status on the full page view.
if ($view_mode == 'full') {
$product->content['title'] = array(
'#markup' => theme('commerce_product_title', array('title' => check_plain($product->title), 'label' => t('Title:'), 'product' => $product)),
'#attached' => $attached,
);
$product->content['status'] = array(
'#markup' => theme('commerce_product_status', array('status' => $product->status ? t('Active') : t('Disabled'), 'label' => t('Status:'), 'product' => $product)),
'#attached' => $attached,
);
}
// Build fields content.
field_attach_prepare_view('commerce_product', array($product->product_id => $product), $view_mode);
entity_prepare_view('commerce_product', array($product->product_id => $product));
$product->content += field_attach_view('commerce_product', $product, $view_mode);
// Allow modules to make their own additions to the product content.
module_invoke_all('commerce_product_view', $product, $view_mode);
// Remove the content array from the product to avoid duplicate rendering.
$build = $product->content;
unset($product->content);
$build += array(
'#product' => $product,
'#view_mode' => $view_mode,
);
// Allow modules to modify the final build array.
drupal_alter('commerce_product_view', $build);
return $build;
}
/**
* Deletes a product by ID.
*
* @param $product_id
* The ID of the product to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_product_delete($product_id) {
return commerce_product_delete_multiple(array($product_id));
}
/**
* Deletes multiple products by ID.
*
* @param $product_ids
* An array of product IDs to delete.
*
* @return
* TRUE on success, FALSE otherwise.
*/
function commerce_product_delete_multiple($product_ids) {
return entity_get_controller('commerce_product')->delete($product_ids);
}
/**
* Checks product access for various operations.
*
* @param $op
* The operation being performed. One of 'view', 'update', 'create' or
* 'delete'.
* @param $product
* Optionally a product to check access for or for the create operation the
* product type. If nothing is given access permissions for all products are returned.
* @param $account
* The user to check for. Leave it to NULL to check for the current user.
*/
function commerce_product_access($op, $product = NULL, $account = NULL) {
global $user;
$account = isset($account) ? $account : $user;
if (user_access('administer products', $account)) {
return TRUE;
}
if ($op == 'view' && user_access('access products', $account)) {
return TRUE;
}
if (isset($product) && is_string($product) && $op == 'create' && user_access('create ' . $product . ' products', $account)) {
return TRUE;
}
if (isset($product) && ($op == 'update' || $op == 'delete')) {
if (user_access('edit any ' . $product->type . ' product', $account)) {
return TRUE;
}
// Others either don't have any access or must match the product uid.
if ($account->uid && user_access('edit own ' . $product->type . ' products', $account) && $product->uid == $account->uid) {
return TRUE;
}
}
return FALSE;
}
/**
* Performs token replacement on a SKU for valid tokens only.
*
* TODO: This function currently limits acceptable Tokens to Product ID and type
* with no ability to use Tokens for the Fields attached to the product. 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 $sku
* The raw SKU string including any tokens as entered.
* @param $product
* A product object used to perform token replacement on the SKU.
*
* @return
* The SKU with tokens replaced or else FALSE if it included invalid tokens.
*/
function commerce_product_replace_sku_tokens($sku, $product) {
// Build an array of valid SKU tokens.
$valid_tokens = array('product-id', 'type');
// Ensure that only valid tokens were used.
$invalid_tokens = FALSE;
foreach (token_scan($sku) as $type => $token) {
if ($type !== 'product') {
$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 $sku;
}
/**
* Validates a SKU string for acceptable characters.
*
* @param $sku
* The SKU string to validate.
*
* @return
* TRUE or FALSE indicating whether or not the SKU contains valid characters.
*/
function commerce_product_validate_sku_characters($sku) {
return preg_match('!^[A-Za-z0-9_-]+$!', $sku);
}
/**
* Checks to see if a given SKU already exists for another product.
*
* @param $sku
* The string to match against existing SKUs.
* @param $product_id
* The ID of the product the SKU is for; an empty value represents the SKU is
* meant for a new product.
*
* @return
* TRUE or FALSE indicating whether or not the SKU exists for another product.
*/
function commerce_product_validate_sku_unique($sku, $product_id) {
// Look for an ID of a product matching the supplied SKU.
if ($match_id = db_query('SELECT product_id FROM {commerce_product} WHERE sku = :sku', array(':sku' => $sku))->fetchField()) {
// If this SKU is supposed to be for a new product or a product other than
// the one that matched...
if (empty($product_id) || $match_id != $product_id) {
return FALSE;
}
}
return TRUE;
}
/**
* Callback for getting product properties.
* @see commerce_product_entity_property_info()
*/
function commerce_product_get_properties($product, array $options, $name) {
switch ($name) {
case 'creator':
return $product->uid;
case 'edit_url':
return url('admin/commerce/products/' . $product->product_id . '/edit', $options);
}
}
/**
* Callback for setting product properties.
* @see commerce_product_entity_property_info()
*/
function commerce_product_set_properties($product, $name, $value) {
if ($name == 'creator') {
$product->uid = $value;
}
}
/**
* Returns output for product autocompletes.
*
* The values returned will be keyed by SKU and appear as such in the textfield,
* even though the preview in the autocomplete list shows "SKU: Title".
*/
function commerce_product_autocomplete($entity_type, $field_name, $bundle, $string = '') {
$field = field_info_field($field_name);
$instance = field_info_instance($entity_type, $field_name, $bundle);
$matches = array();
// The user enters a comma-separated list of tags. We only autocomplete the last tag.
$tags_typed = drupal_explode_tags($string);
$tag_last = drupal_strtolower(array_pop($tags_typed));
if (!empty($tag_last)) {
$prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
// Determine the type of autocomplete match to use when searching for products.
$match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains';
// Get an array of matching products.
$products = commerce_product_match_products($field, $instance, $tag_last, $match, array(), 10);
// Loop through the products and convert them into autocomplete output.
foreach ($products as $product_id => $data) {
// Add a class wrapper for a few required CSS overrides.
$matches[$prefix . $data['sku']] = '<div class="reference-autocomplete">' . $data['rendered'] . '</div>';
}
}
drupal_json_output($matches);
}
/**
* Fetches an array of all products matching the given parameters.
*
* This info is used in various places (allowed values, autocomplete results,
* input validation...). Some of them only need the product_ids, others
* product_id + titles, others yet product_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 $string
* Optional string to filter SKUs and titles on (used by autocomplete).
* @param $match
* Operator to match filtered SKUs and titles against, can be any of:
* 'contains', 'equals', 'starts_with'
* @param $ids
* Optional product ids to lookup (used when $string and $match arguments are
* not given).
* @param $limit
* If non-zero, limit the size of the result set.
*
* @return
* An array of valid products in the form:
* array(
* product_id => array(
* 'product_sku' => The product SKU,
* 'title' => The product title,
* 'rendered' => The text to display in widgets (can be HTML)
* ),
* ...
* )
*/
function commerce_product_match_products($field, $instance = NULL, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
$results = &drupal_static(__FUNCTION__, array());
// Create unique id for static cache.
$cid = implode(':', array(
$field['field_name'],
$match,
($string !== '' ? $string : implode('-', $ids)),
$limit,
));
if (!isset($results[$cid])) {
$matches = _commerce_product_match_products_standard($instance, $string, $match, $ids, $limit);
// Store the results.
$results[$cid] = !empty($matches) ? $matches : array();
}
return $results[$cid];
}
/**
* Helper function for commerce_product_match_products().
*
* Returns an array of products matching the specific parameters.
*/
function _commerce_product_match_products_standard($instance, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
// Build the query object with the necessary fields.
$query = db_select('commerce_product', 'cp');
$product_id_alias = $query->addField('cp', 'product_id');
$product_sku_alias = $query->addField('cp', 'sku');
$product_title_alias = $query->addField('cp', 'title');
$product_type_alias = $query->addField('cp', 'type');
// Add a condition to the query to filter by matching product types.
if (!empty($instance['settings']['referenceable_types'])) {
$types = array_diff(array_values($instance['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 ($string !== '') {
$args = array();
// Build a where clause matching on either the SKU or title.
switch ($match) {
case 'contains':
$where = '(cp.sku LIKE :sku_match OR cp.title LIKE :title_match)';
$args['sku_match'] = '%' . $string . '%';
$args['title_match'] = '%' . $string . '%';
break;
case 'starts_with':
$where = '(cp.sku LIKE :sku_match OR cp.title LIKE :title_match)';
$args['sku_match'] = $string . '%';
$args['title_match'] = $string . '%';
break;
case 'equals':
default:
$where = '(cp.sku = :match OR cp.title = :match)';
$args['sku_match'] = $string;
$args['title_match'] = $string;
break;
}
$query->where($where, $args);
}
elseif ($ids) {
// Otherwise add a product_id specific condition if specified.
$query->condition($product_id_alias, $ids, 'IN', $ids);
}
// Order the results by SKU, title, and then product type.
$query
->orderBy($product_sku_alias)
->orderBy($product_title_alias)
->orderBy($product_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 $product) {
$matches[$product->product_id] = array(
'sku' => $product->sku,
'type' => $product->type,
'title' => $product->title,
'rendered' => t('@sku: @title', array('@sku' => $product->sku, '@title' => $product->title)),
);
}
return $matches;
}
<?php
// $Id$
/**
* @file
* Rules integration for products.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_event_info().
*/
function commerce_product_rules_event_info() {
$events = array();
$events['commerce_product_presave'] = array(
'label' => t('Before saving a product'),
'group' => t('Commerce Product'),
'variables' => commerce_product_rules_event_variables(t('Product'), TRUE, TRUE),
'access callback' => 'commerce_product_rules_access',
);
$events['commerce_product_insert'] = array(
'label' => t('After saving a new product'),
'group' => t('Commerce Product'),
'variables' => commerce_product_rules_event_variables(t('Created product'), TRUE),
'access callback' => 'commerce_product_rules_access',
);
$events['commerce_product_update'] = array(
'label' => t('After updating an existing product'),
'group' => t('Commerce Product'),
'variables' => commerce_product_rules_event_variables(t('Updated product'), TRUE),
'access callback' => 'commerce_product_rules_access',
);
$events['commerce_product_delete'] = array(
'label' => t('After deleting a product'),
'group' => t('Commerce Product'),
'variables' => commerce_product_rules_event_variables(t('Deleted product')),
'access callback' => 'commerce_product_rules_access',
);
return $events;
}
/**
* Returns a variables array for product events.
*
* @param $label
* The label for the primary product variable.
* @param $unchanged
* Boolean indicating whether or not to include the unchanged product.
* @param $skip_save
* Boolean indicating whether or not the primary product variable should skip
* saving after event execution.
*/
function commerce_product_rules_event_variables($label, $unchanged = FALSE, $skip_save = FALSE) {
$variables = array(
'product' => array(
'type' => 'commerce_product',
'label' => $label,
'skip save' => $skip_save,
),
);
if ($unchanged) {
$variables['product_unchanged'] = array(
'type' => 'commerce_product',
'label' => t('Unchanged product'),
'skip save' => TRUE,
'handler' => 'rules_events_entity_unchanged',
);
}
return $variables;
}
/**
* Rules integration access callback.
*/
function commerce_product_rules_access($type, $name) {
if ($type == 'event' || $type == 'condition') {
return commerce_product_access('view');
}
}
/**
* @}
*/
<?php
// $Id$
/**
* @file
* Unit tests for the commerce product module.
*/
/**
* Test the product and product type CRUD.
*/
class CommerceProductCRUDTestCase extends CommerceBaseTestCase {
public static function getInfo() {
return array(
'name' => 'Commerce product CRUD',
'description' => 'Tests the product CRUD functions.',
'group' => 'Drupal Commerce',
);
}
function setUp() {
$modules = parent::setUpHelper('all');
parent::setUp($modules);
$this->site_admin = $this->createSiteAdmin();
cache_clear_all(); // Just in case
}
/**
* Ensure the default product types are created.
*/
function testCommerceProductDefaultProducts() {
$default_types = array(
'product' => 'Product',
);
// Load the default product types.
$types_created = commerce_product_types();
// Ensure each type exists.
foreach ($default_types as $type => $name) {
$this->assertTrue(!empty($types_created[$type]), 'Product type ' . check_plain($type) . ' has been created.');
}
}
/**
* Test the product type CRUD functions.
*/
function testCommerceProductTypeCrud() {
// Ensure commerce_product_ui_product_type_new() returns a valid empty product type.
$new_product_type = commerce_product_ui_product_type_new();
$this->assertNotNull($new_product_type['type'], 'commerce_product_ui_product_type_new() instantiated the type property.');
$this->assertNotNull($new_product_type['name'], 'commerce_product_ui_product_type_new() instantiated the help property.');
$this->assertNotNull($new_product_type['description'], 'commerce_product_ui_product_type_new() instantiated the help property.');
$this->assertNotNull($new_product_type['help'], 'commerce_product_ui_product_type_new() instantiated the help property');
// Supply customer values for the product type properties.
$type = $this->randomName(20);
$name = $this->randomName(40);
$description = $this->randomString(128);
$help = $this->randomString(128);
// Add the values to the new content type.
$new_product_type['type'] = $type;
$new_product_type['name'] = $name;
$new_product_type['description'] = $description;
$new_product_type['help'] = $help;
$new_product_type['is_new'] = TRUE;
// Ensure content_product_ui_product_type_save() returns the proper value when inserting.
$return = commerce_product_ui_product_type_save($new_product_type);
$this->assertEqual($return, SAVED_NEW, 'commerce_product_ui_product_type_save() returned SAVED_NEW when saving a new product type.');
// Load the newly saved content type.
$saved_product_type = commerce_product_type_load($type);
// Ensure the values that were saved match the values that we created.
$this->assertTrue($saved_product_type, 'commerce_product_type_load() loaded the new product type.');
$this->assertEqual($type, $saved_product_type['type'], 'The new product type type was properly saved and loaded.');
$this->assertEqual($name, $saved_product_type['name'], 'The new product type name was properly saved and loaded.');
$this->assertEqual($description, $saved_product_type['description'], 'The new product type description text was properly saved and loaded.');
$this->assertEqual($help, $saved_product_type['help'], 'The new product type help text was properly saved and loaded.');
// Alter the title, to ensure the update function works.
$altered_name = $this->randomName(40);
$saved_product_type['name'] = $altered_name;
// Ensure commerce_product_ui_product_type_save() returns the proper value when updating.
$return = commerce_product_ui_product_type_save($saved_product_type);
$this->assertEqual($return, SAVED_UPDATED, 'commerce_product_ui_product_type_save() returned SAVED_UPDATED when saving an updated product type.');
// Reset the cached product types, and verify commerce_product_types load the saved type.
commerce_product_types_reset();
$types = commerce_product_types();
$this->assertNotNull($types[$type], 'commerce_product_types_reset() successfully reset the product types.');
$this->assertEqual($saved_product_type['name'], $altered_name, 'commerce_product_ui_product_type_save() successfully updated the product type name.');
// Ensure commerce_product_ui_product_type_delete() deletes a content type.
commerce_product_ui_product_type_delete($type);
$deleted_type = commerce_product_type_load($type);
$this->assertFalse($deleted_type, 'commerce_product_ui_product_type_delete() successfully removed a product type.');
}
/**
* Test the product CRUD functions.
*/
function testCommerceProductCrud() {
// Ensure commerce_product_new() returns a new product.
$new_product = commerce_product_new('product');
$fields = array('product_id', 'sku', 'type', 'title', 'uid');
foreach ($fields as $field) {
$this->assertNotNull($new_product->{$field}, 'commerce_product_new() instantiated the ' . check_plain($field) . ' property.');
}
$new_product->sku = $sku = $this->randomName(10);
$new_product->type = $type = 'product';
$new_product->title = $title = $this->randomName(10);
$new_product->uid = $uid = 1;
// Ensure commerce_product_save() returns TRUE when saving a new product
$return = commerce_product_save($new_product);
$this->assertNotEqual($return, FALSE, 'commerce_product_save() successfully saved the new product.');
// Ensure commerce_product_load() loaded the saved product.
$loaded_product = commerce_product_load($new_product->product_id);
foreach ($fields as $field) {
$this->assertEqual($loaded_product->{$field}, $new_product->{$field}, 'The ' . check_plain($field) . ' value loaded by commerce_product_load() matches the value saved by commerce_product_save()');
}
$this->assertTrue($loaded_product->created > 0, 'commerce_product_save() added a created date to the product');
$this->assertTrue($loaded_product->changed > 0, 'commerce_product_save() added a changed date to the product');
// Ensure commerce_product_load_by_sku() can load a product by SKU.
$loaded_product_by_sku = commerce_product_load_by_sku($sku);
$this->assertEqual($loaded_product_by_sku->product_id, $new_product->product_id, 'The ID of the product loaded via commerce_product_load_by_sku() matches the saved product ID.');
// Ensure commerce_product_load_multiple() can load multiple multiple products.
$saved_product_ids = array();
// First create and save multiple new products.
for ($i = 0; $i < 3; $i++) {
$product = commerce_product_new('product');
$product->type = 'product';
$product->sku = $this->randomName(10);
$product->title = $this->randomName(10);
$product->uid = 1;
commerce_product_save($product);
// Save the ID and title of the newly created product.
$saved_products[$product->product_id] = $product->title;
}
$loaded_products = commerce_product_load_multiple(array_keys($saved_products));
$this->assertEqual(count($saved_products), count($loaded_products), 'commerce_product_load_multiple() loaded the proper number of the products.');
foreach ($loaded_products as $loaded_product) {
$this->assertEqual($loaded_product->title, $saved_products[$loaded_product->product_id], 'commerce_product_load_multiple() successfully loaded a product.');
}
// Ensure commerce_product_delete() can remove a product.
$return = commerce_product_delete($new_product->product_id);
$this->assertTrue($return, 'commerce_product_delete() returned TRUE when deleting a product.');
$deleted_product_load = commerce_product_load_multiple(array($new_product->product_id), array(), TRUE);
$this->assertFalse($deleted_product_load, 'commerce_product_load_multiple() could not load the deleted product.');
// Ensure commerce_product_delete_multiple() can delete multiple products.
$return = commerce_product_delete_multiple(array_keys($saved_products));
$this->assertTrue($return, 'commerce_product_delete_multiple() returned TRUE when deleting a product.');
$deleted_products_load = commerce_product_load_multiple(array_keys($saved_products), array(), TRUE);
$this->assertFalse($deleted_product_load, 'commerce_product_load_multiple() could not load the deleted products.');
}
/**
* Test product Token replacement.
*/
function testCommerceProductTokens() {
$product = commerce_product_new('product');
$creator = $this->drupalCreateUser();
$product->sku = $this->randomName(10);
$product->title = $this->randomName(10);
$product->uid = $creator->uid;
$product = commerce_product_save($product);
$this->assertEqual(token_replace('[product:product-id]', array('product' => $product)), $product->product_id, '[product:product-id] was replaced with the product ID.');
$this->assertEqual(token_replace('[product:sku]', array('product' => $product)), $product->sku, '[product:sku] was replaced with the SKU.');
$this->assertEqual(token_replace('[product:type]', array('product' => $product)), commerce_product_type_get_name($product->type), '[product:type] was replaced with the product type.');
$this->assertEqual(token_replace('[product:title]', array('product' => $product)), $product->title, '[product:title] was replaced with the title.');
$this->assertNotIdentical(strpos(token_replace('[product:edit-url]', array('product' => $product)), url('admin/commerce/products/' . $product->product_id . '/edit')), FALSE, '[product:edit-url] was replaced with the edit URL.');
$this->assertEqual(token_replace('[product:creator:uid]', array('product' => $product)), $product->uid, '[product:creator:uid] was replaced with the uid of the creator.');
$this->assertEqual(token_replace('[product:creator:name]', array('product' => $product)), check_plain(format_username($creator)), '[product:creator:name] was replaced with the name of the author.');
$this->assertEqual(token_replace('[product:created]', array('product' => $product)), format_date($product->created, 'medium'), '[product:created] was replaced with the created date.');
$this->assertEqual(token_replace('[product:changed]', array('product' => $product)), format_date($product->changed, 'medium'), '[product:changed] was replaced with the changed date.');
}
}
/**
* Test the Rules and Entity integration.
*/
class CommerceProductRulesTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Commerce product rules integration',
'description' => 'Tests the commerce product rules integration',
'group' => 'Drupal Commerce',
);
}
function setUp() {
parent::setUp('commerce_product', 'rules');
}
/**
* Calculates the output of t() given an array of placeholders to replace.
*/
static function t($text, $strings) {
$placeholders = array();
foreach ($strings as $string) {
$placeholders['%' . $string] = drupal_placeholder($string);
}
return strtr($text, $placeholders);
}
/**
* Tests rules CRUD actions for products.
*/
function testRulesCRUD() {
// Test creation.
$action = rules_action('entity_create', array(
'type' => 'commerce_product',
'param_type' => 'product',
'param_sku' => 'foo',
'param_title' => 'bar',
'param_creator' => $GLOBALS['user'],
));
// Test running access() and execute.
$action->access();
$action->execute();
$text = RulesLog::logger()->render();
$pos = strpos($text, self::t('Added the provided variable %entity_created of type %commerce_product', array('entity_created', 'commerce_product')));
$pos = ($pos !== FALSE) ? strpos($text, self::t('Saved %entity_created of type %commerce_product.', array('entity_created', 'commerce_product')), $pos) : FALSE;
$this->assertTrue($pos !== FALSE, 'Product has been created and saved.');
$product = commerce_product_new('product');
commerce_product_save($product);
$rule = rule();
$rule->action('entity_fetch', array('type' => 'commerce_product', 'id' => $product->product_id, 'entity_fetched:var' => 'product'));
$rule->action('entity_save', array('data:select' => 'product', 'immediate' => TRUE));
$rule->action('entity_delete', array('data:select' => 'product'));
// Test running access and integrtiy check + execute.
$rule->access();
$rule->integrityCheck()->execute();
$text = RulesLog::logger()->render();
$pos = strpos($text, RulesTestCase::t('Evaluating the action %entity_fetch.', array('entity_fetch')));
$pos = ($pos !== FALSE) ? strpos($text, self::t('Added the provided variable %product of type %commerce_product', array('product', 'commerce_product')), $pos) : FALSE;
$pos = ($pos !== FALSE) ? strpos($text, self::t('Saved %product of type %commerce_product.', array('product', 'commerce_product')), $pos) : FALSE;
$pos = ($pos !== FALSE) ? strpos($text, self::t('Evaluating the action %entity_delete.', array('entity_delete')), $pos) : FALSE;
$this->assertTrue($pos !== FALSE, 'Product has been fetched, saved and deleted.');
//debug(RulesLog::logger()->render());
$this->assertFalse(commerce_product_load($product->product_id), 'Product has been deleted.');
}
/**
* Tests making use of product metadata.
*/
function testProductPropertyInfo() {
// Populate $values with all values that are setable. They will be set
// with an metadata wrapper, so we also test setting that way.
$values = array();
$wrapper = entity_metadata_wrapper('commerce_product');
foreach ($wrapper as $name => $child) {
$info = $wrapper->$name->info();
if (!empty($info['setter callback'])) {
$info += array('type' => 'text');
$values[$name] = $this->createValue($info['type'], $info);
}
}
$values['type'] = 'product';
$product = entity_create('commerce_product', $values)->value();
$this->assertTrue($product, "Created a product and set all setable values.");
$wrapper = entity_metadata_wrapper('commerce_product', $product);
foreach ($wrapper as $key => $child) {
$this->assertValue($wrapper, $key);
}
}
/**
* Assert the value for the given property is returned.
*/
protected function assertValue($wrapper, $key) {
$this->assertTrue($wrapper->$key->value() !== NULL, check_plain($key) . ' property returned.');
$info = $wrapper->$key->info();
if (!empty($info['raw getter callback'])) {
// Also test getting the raw value
$this->assertTrue($wrapper->$key->raw() !== NULL, check_plain($key) . ' raw value returned.');
}
}
/**
* Creates a value for the given data type.
*/
protected function createValue($type, $info) {
if (!isset($this->node)) {
// Create some entities to use the first time this runs.
$this->node = $this->drupalCreateNode(array('type' => 'article'));
$this->user = $this->drupalCreateUser();
}
if (isset($info['options list'])) {
$options = array_keys($info['options list']());
return entity_property_list_extract_type($type) ? array(reset($options)) : reset($options);
}
switch ($type) {
case 'decimal':
case 'integer':
case 'duration':
return 1;
case 'date':
return REQUEST_TIME;
case 'boolean':
return TRUE;
case 'text':
return drupal_strtolower($this->randomName(8));
case 'text_formatted':
return array('value' => $this->randomName(16));
default:
return $this->$type;
}
}
/**
* Tests making use of the product 'presave' event and loading the unchanged
* product.
*
* TODO: replace this with a simpler event test that doesn't depend on the odd
* product loading / saving interplay. I don't even think the unchanged
* condition will ever work as is in here.
*
public function testProductEvent() {
$rule = rules_reaction_rule();
$rule->event('commerce_product_presave')
->condition('data_is', array('data:select' => 'product:type', 'value' => 'product'))
->condition(rules_condition('data_is', array('data:select' => 'product:sku', 'value:select' => 'product_unchanged:sku'))->negate())
->action('entity_delete', array('data:select' => 'product'));
// Try running access and integrity checks.
$rule->access();
$rule->integrityCheck();
// Save it.
$rule->save('commerce_product_test1', 'commerce_product');
// Force immediate cache clearing so we can test the rule *now*.
rules_clear_cache(TRUE);
// Create initial product.
$product = commerce_product_new('product');
$product->sku = 'foo';
commerce_product_save($product);
$this->assertTrue(commerce_product_load($product->product_id), 'Reaction rule not fired.');
// Now update, so that the rule fires.
$product->sku = 'bar';
unset($product->is_new);
commerce_product_save($product);
$this->assertFalse(commerce_product_load($product->product_id), 'Reaction rule fired.');
RulesLog::logger()->checkLog();
}
*/
}
<?php
// $Id$
/**
* @file
* Builds placeholder replacement tokens for product-related data.
*/
/**
* Implements hook_token_info().
*/
function commerce_product_token_info() {
$type = array(
'name' => t('Products'),
'description' => t('Tokens related to individual products.'),
'needs-data' => 'product',
);
// Tokens for products.
$product = array();
$product['product-id'] = array(
'name' => t('Product ID'),
'description' => t('The internal numeric ID of the product.'),
);
$product['sku'] = array(
'name' => t('SKU'),
'description' => t('The human readable product SKU.'),
);
$product['type'] = array(
'name' => t('Type'),
'description' => t('The human readable name of the product type.'),
);
$product['title'] = array(
'name' => t('Title'),
'description' => t('The title of the product.'),
);
$product['edit-url'] = array(
'name' => t('Edit URL'),
'description' => t("The URL of the product's edit page."),
);
// Chained tokens for products.
$product['creator'] = array(
'name' => t('Creator'),
'description' => t('The creator of the product.'),
'type' => 'user',
);
$product['created'] = array(
'name' => t('Date created'),
'description' => t('The date the product was created.'),
'type' => 'date',
);
$product['changed'] = array(
'name' => t('Date updated'),
'description' => t('The date the product was last updated.'),
'type' => 'date',
);
return array(
'types' => array('product' => $type),
'tokens' => array('product' => $product),
);
}
/**
* Implements hook_tokens().
*/
function commerce_product_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 == 'product' && !empty($data['product'])) {
$product = $data['product'];
foreach ($tokens as $name => $original) {
switch ($name) {
// Simple key values on the product.
case 'product-id':
$replacements[$original] = $product->product_id;
break;
case 'sku':
$replacements[$original] = $sanitize ? check_plain($product->sku) : $product->sku;
break;
case 'type':
$replacements[$original] = $sanitize ? check_plain(commerce_product_type_get_name($product->type)) : commerce_product_type_get_name($product->type);
break;
case 'title':
$replacements[$original] = $sanitize ? check_plain($product->title) : $product->title;
break;
case 'edit-url':
$replacements[$original] = url('admin/commerce/products/' . $product->product_id . '/edit', $url_options);
break;
// Default values for the chained tokens handled below.
case 'creator':
if (!$product->uid) {
$name = variable_get('anonymous', t('Anonymous'));
}
else {
$creator = user_load($product->uid);
$name = $creator->name;
}
$replacements[$original] = $sanitize ? filter_xss($name) : $name;
break;
case 'created':
$replacements[$original] = format_date($product->created, 'medium', '', NULL, $language_code);
break;
case 'changed':
$replacements[$original] = format_date($product->changed, 'medium', '', NULL, $language_code);
break;
}
}
if ($creator_tokens = token_find_with_prefix($tokens, 'creator')) {
$creator = user_load($product->uid);
$replacements += token_generate('user', $creator_tokens, array('user' => $creator), $options);
}
foreach (array('created', 'changed') as $date) {
if ($created_tokens = token_find_with_prefix($tokens, $date)) {
$replacements += token_generate('date', $created_tokens, array('date' => $product->{$date}), $options);
}
}
}
return $replacements;
}
<?php
// $Id$
/**
* Export Drupal Commerce products to Views.
*/
/**
* Implements hook_views_data()
*/
function commerce_product_views_data() {
$data = array();
$data['commerce_product']['table']['group'] = t('Commerce Product');
$data['commerce_product']['table']['base'] = array(
'field' => 'product_id',
'title' => t('Commerce Product'),
'help' => t('Products from the store.'),
);
// Expose the product ID.
$data['commerce_product']['product_id'] = array(
'title' => t('Product ID'),
'help' => t('The unique internal identifier of the product.'),
'field' => array(
'handler' => 'commerce_product_handler_field_product',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'commerce_product_handler_argument_product_id',
),
);
// Expose the product SKU.
$data['commerce_product']['sku'] = array(
'title' => t('SKU'),
'help' => t('The unique human-readable identifier of the product.'),
'field' => array(
'handler' => 'commerce_product_handler_field_product',
'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 product type.
$data['commerce_product']['type'] = array(
'title' => t('Type'),
'help' => t('The human-readable name of the type of the product.'),
'field' => array(
'handler' => 'commerce_product_handler_field_product_type',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'commerce_product_handler_filter_product_type',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
// Expose the product title.
$data['commerce_product']['title'] = array(
'title' => t('Title'),
'help' => t('The title of the product used for administrative display.'),
'field' => array(
'handler' => 'commerce_product_handler_field_product',
'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 creator uid.
$data['commerce_product']['uid'] = array(
'title' => t('Creator'),
'help' => t('Relate a product to the user who created it.'),
'relationship' => array(
'handler' => 'views_handler_relationship',
'base' => 'users',
'field' => 'uid',
'label' => t('Product creator'),
),
);
// Expose the product status.
$data['commerce_product']['status'] = array(
'title' => t('Status'),
'help' => t('Whether or not the product 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_product']['created'] = array(
'title' => t('Created date'),
'help' => t('The date the product 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_product']['changed'] = array(
'title' => t('Updated date'),
'help' => t('The date the product 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 product.
$data['commerce_product']['view_product'] = array(
'field' => array(
'title' => t('Link'),
'help' => t('Provide a simple link to the administrator view of the product.'),
'handler' => 'commerce_product_handler_field_product_link',
),
);
$data['commerce_product']['edit_product'] = array(
'field' => array(
'title' => t('Edit link'),
'help' => t('Provide a simple link to edit the product.'),
'handler' => 'commerce_product_handler_field_product_link_edit',
),
);
$data['commerce_product']['delete_product'] = array(
'field' => array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete the product.'),
'handler' => 'commerce_product_handler_field_product_link_delete',
),
);
$data['commerce_product']['operations'] = array(
'field' => array(
'title' => t('Operations links'),
'help' => t('Display all the available operations links for the product.'),
'handler' => 'commerce_product_handler_field_product_operations',
),
);
return $data;
}
<?php
// $Id$
/**
* Argument handler to display product titles in View using product arguments.
*/
class commerce_product_handler_argument_product_id extends views_handler_argument_numeric {
function title_query() {
$titles = array();
$result = db_select('commerce_product', 'cp')
->fields('cp', array('title'))
->condition('cp.product_id', $this->value)
->execute();
foreach ($result as $product) {
$titles[] = check_plain($product->title);
}
return $titles;
}
}
<?php
// $Id$
/**
* @file
* Contains the basic product field handler.
*/
/**
* Field handler to provide simple renderer that allows linking to a product.
*/
class commerce_product_handler_field_product extends views_handler_field {
function init(&$view, &$options) {
parent::init($view, $options);
if (!empty($this->options['link_to_product'])) {
$this->additional_fields['product_id'] = 'product_id';
if (module_exists('translation')) {
$this->additional_fields['language'] = array('table' => 'commerce_product', 'field' => 'language');
}
}
}
function option_definition() {
$options = parent::option_definition();
$options['link_to_product'] = array('default' => FALSE);
return $options;
}
/**
* Provide the link to product option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['link_to_product'] = array(
'#title' => t("Link this field to the product's administrative view page"),
'#description' => t('This will override any other link you have set.'),
'#type' => 'checkbox',
'#default_value' => !empty($this->options['link_to_product']),
);
}
/**
* Render whatever the data is as a link to the product.
*
* Data should be made XSS safe prior to calling this function.
*/
function render_link($data, $values) {
if (!empty($this->options['link_to_product']) && $data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = 'admin/commerce/products/' . $values->{$this->aliases['product_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 present a product's operations links.
*/
class commerce_product_handler_field_product_operations extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['product_id'] = 'product_id';
}
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
$product_id = $values->{$this->aliases['product_id']};
$links = menu_contextual_links('commerce-product', 'admin/commerce/products', array($product_id));
if (!empty($links)) {
drupal_add_css(drupal_get_path('module', 'commerce_product') . '/theme/commerce_product_views.css');
return theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline', 'operations'))));
}
}
}
<?php
// $Id$
/**
* Field handler to translate a product type into its readable form.
*/
class commerce_product_handler_field_product_type extends commerce_product_handler_field_product {
function render($values) {
$value = commerce_product_type_get_name($values->{$this->field_alias});
return $this->render_link((check_plain($value)), $values);
}
}
<?php
// $Id$
/**
* Filter by product type.
*/
class commerce_product_handler_filter_product_type extends views_handler_filter_in_operator {
// Display a list of product types in the filter's options.
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Product type');
$this->value_options = commerce_product_type_get_name();
}
}
}
<?php
// $Id$
/**
* @file
* Forms for creating / editing and deleting products.
*/
/**
* Form callback: create or edit a product type.
*
* @param $product_type
* The product type array to edit or for a create form an empty product type
* array with properties instantiated but not populated.
*/
function commerce_product_ui_product_type_form($form, &$form_state, $product_type) {
// 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_product_ui') . '/includes/commerce_product_ui.forms.inc';
// Store the initial product type in the form state.
$form_state['product_type'] = $product_type;
$form['product_type'] = array(
'#tree' => TRUE,
);
$form['product_type']['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#default_value' => $product_type['name'],
'#description' => t('The human-readable name of this product type. It is recommended that this name begin with a capital letter and contain only letters, numbers, and spaces. This name must be unique.'),
'#required' => TRUE,
'#size' => 32,
'#field_suffix' => ' <small id="edit-product-type-name-suffix">' . t('Machine name: @type', array('@type' => $product_type['type'])) . '</small>',
);
if (empty($product_type['type'])) {
$form['product_type']['type'] = array(
'#type' => 'machine_name',
'#title' => t('Machine name'),
'#default_value' => $product_type['type'],
'#maxlength' => 32,
'#required' => TRUE,
'#machine_name' => array(
'exists' => 'commerce_product_type_load',
'source' => array('product_type', 'name'),
),
'#description' => t('The machine-readable name of this product type. This name must contain only lowercase letters, numbers, and underscores, it must be unique.'),
);
}
$form['product_type']['description'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#description' => t('Describe this product type. The text will be displayed on the <em>Add new content</em> page.'),
'#default_value' => $product_type['description'],
'#rows' => 3,
);
$form['product_type']['help'] = array(
'#type' => 'textarea',
'#title' => t('Explanation or submission guidelines'),
'#description' => t('This text will be displayed at the top of the page when creating or editing products of this type.'),
'#default_value' => $product_type['help'],
'#rows' => 3,
);
$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 product type'),
'#submit' => $submit + array('commerce_product_ui_product_type_form_submit'),
);
if (!empty($form_state['product_type']['type'])) {
$form['actions']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete product type'),
'#suffix' => l('Cancel', 'admin/commerce/products/types'),
'#submit' => $submit + array('commerce_product_ui_product_type_form_delete_submit'),
'#weight' => 45,
);
}
else {
$form['actions']['save_continue'] = array(
'#type' => 'submit',
'#value' => t('Save and add fields'),
'#suffix' => l('Cancel', 'admin/commerce/products/types'),
'#submit' => $submit + array('commerce_product_ui_product_type_form_submit'),
'#weight' => 45,
);
}
$form['#validate'][] = 'commerce_product_ui_product_type_form_validate';
return $form;
}
/**
* Validation callback for commerce_product_product_type_form().
*/
function commerce_product_ui_product_type_form_validate($form, &$form_state) {
$product_type = $form_state['product_type'];
// If saving a new product type, ensure it has a unique machine name.
if (empty($product_type['type'])) {
if (!commerce_product_ui_validate_product_type_unique($form_state['values']['product_type']['type'])) {
form_set_error('product_type][type', t('The machine name specified is already in use.'));
}
}
}
/**
* Form submit handler: save a product type.
*/
function commerce_product_ui_product_type_form_submit($form, &$form_state) {
$product_type = $form_state['product_type'];
$updated = !empty($product_type['type']);
foreach ($form_state['values']['product_type'] as $key => $value) {
$product_type[$key] = $value;
}
// Write the product type to the database.
$product_type['is_new'] = !$updated;
commerce_product_ui_product_type_save($product_type);
// Redirect based on the button clicked.
drupal_set_message(t('Product type saved.'));
if ($form_state['clicked_button']['#parents'][0] == 'save_continue') {
$form_state['redirect'] = 'admin/commerce/products/types/' . strtr($product_type['type'], '_', '-') . '/fields';
}
else {
$form_state['redirect'] = 'admin/commerce/products/types';
}
}
/**
* Submit callback for delete button on commerce_product_ui_product_type_form().
*
* @see commerce_product_ui_product_type_form()
*/
function commerce_product_ui_product_type_form_delete_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/commerce/products/types/' . strtr($form_state['product_type']['type'], '_', '-') . '/delete';
}
/**
* Form callback: confirmation form for deleting a product type.
*
* @param $product_type
* The product type array to be deleted.
*
* @see confirm_form()
*/
function commerce_product_ui_product_type_delete_form($form, &$form_state, $product_type) {
$form_state['product_type'] = $product_type;
// 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_product_ui') . '/includes/commerce_product_ui.forms.inc';
$form['#submit'][] = 'commerce_product_ui_product_type_delete_form_submit';
$form = confirm_form($form,
t('Are you sure you want to delete the %name product type?', array('%name' => $product_type['name'])),
'admin/commerce/products/types',
'<p>' . t('This action cannot be undone.') . '</p>',
t('Delete'),
t('Cancel'),
'confirm'
);
return $form;
}
/**
* Submit callback for commerce_product_product_type_delete_form().
*/
function commerce_product_ui_product_type_delete_form_submit($form, &$form_state) {
$product_type = $form_state['product_type'];
commerce_product_ui_product_type_delete($product_type['type']);
drupal_set_message(t('The product type %name has been deleted.', array('%name' => $product_type['name'])));
watchdog('commerce_product', 'Deleted product type %name.', array('%name' => $product_type['name']), WATCHDOG_NOTICE);
$form_state['redirect'] = 'admin/commerce/products/types';
}
; $Id$
name = Product UI
description = Exposes a default UI for Products through product edit forms and default Views.
package = Commerce
core = 7.x
dependencies[] = contextual
dependencies[] = field_ui
dependencies[] = commerce_ui
dependencies[] = commerce_product
dependencies[] = views
<?php
// $Id$
/**
* Implements hook_install().
*/
function commerce_product_ui_install() {
// Create the basic product type.
$product_type = commerce_product_ui_product_type_new();
$product_type['type'] = 'product';
$product_type['name'] = t('Product');
$product_type['description'] = t('A basic product type.');
$product_type['is_new'] = TRUE;
commerce_product_ui_product_type_save($product_type, FALSE);
}
/**
* Implements hook_schema().
*/
function commerce_product_ui_schema() {
$schema = array();
$schema['commerce_product_type'] = array(
'description' => 'Stores information about {commerce_product} types created via Product UI.',
'fields' => array(
'type' => array(
'description' => 'The machine-readable name of this type.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'name' => array(
'description' => 'The human-readable name of this type.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'description' => array(
'description' => 'A brief description of this type.',
'type' => 'text',
'not null' => TRUE,
'size' => 'medium',
),
'help' => array(
'description' => 'Help information shown to the user when creating a {commerce_product} of this type.',
'type' => 'text',
'not null' => TRUE,
'size' => 'medium',
),
),
'primary key' => array('type'),
);
return $schema;
}
<?php
// $Id$
/**
* @file
*/
/**
* Implements hook_menu().
*/
function commerce_product_ui_menu() {
$items = array();
// Note: admin/commerce/products is defined by a default View.
// Add a product.
$items['admin/commerce/products/add'] = array(
'title' => 'Add a product',
'description' => 'Add a new product for sale.',
'page callback' => 'commerce_product_ui_add_page',
'access callback' => 'commerce_product_ui_product_add_any_access',
'weight' => 10,
'file' => 'includes/commerce_product_ui.products.inc',
);
foreach (commerce_product_types() as $type => $product_type) {
$items['admin/commerce/products/add/' . strtr($type, array('_' => '-'))] = array(
'title' => 'Create @name',
'title arguments' => array('@name' => $product_type['name']),
'description' => $product_type['description'],
'page callback' => 'commerce_product_ui_product_form_wrapper',
'page arguments' => array(commerce_product_new($type)),
'access callback' => 'commerce_product_access',
'access arguments' => array('create', $type),
'file' => 'includes/commerce_product_ui.products.inc',
);
}
$items['admin/commerce/products/%commerce_product'] = array(
'title callback' => 'commerce_product_ui_product_title',
'title arguments' => array(3),
'page callback' => 'commerce_product_ui_product_form_wrapper',
'page arguments' => array(3),
'access callback' => 'commerce_product_access',
'access arguments' => array('update', 3),
'weight' => 0,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
'file' => 'includes/commerce_product_ui.products.inc',
);
$items['admin/commerce/products/%commerce_product/edit'] = array(
'title' => 'Edit',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
$items['admin/commerce/products/%commerce_product/delete'] = array(
'title' => 'Delete',
'page callback' => 'commerce_product_ui_product_delete_form_wrapper',
'page arguments' => array(3),
'access callback' => 'commerce_product_access',
'access arguments' => array('update', 3),
'type' => MENU_LOCAL_TASK,
'weight' => 20,
'context' => MENU_CONTEXT_INLINE,
'file' => 'includes/commerce_product_ui.products.inc',
);
$items['admin/commerce/products/types'] = array(
'title' => 'Product types',
'description' => 'Manage products types for your store.',
'page callback' => 'commerce_product_ui_types_overview',
'access arguments' => array('administer product types'),
'type' => MENU_LOCAL_TASK,
'weight' => 0,
'file' => 'includes/commerce_product_ui.types.inc',
);
$items['admin/commerce/products/types/add'] = array(
'title' => 'Add product type',
'page callback' => 'commerce_product_ui_product_type_form_wrapper',
'page arguments' => array(commerce_product_ui_product_type_new()),
'access arguments' => array('administer product types'),
'type' => MENU_LOCAL_ACTION,
'file' => 'includes/commerce_product_ui.types.inc',
);
foreach (commerce_product_types() as $type => $product_type) {
// Convert underscores to hyphens for the menu item argument.
$type_arg = strtr($type, '_', '-');
$items['admin/commerce/products/types/' . $type_arg] = array(
'title callback' => 'commerce_product_type_title',
'title arguments' => array($product_type),
'page callback' => 'commerce_product_ui_product_type_form_wrapper',
'page arguments' => array($product_type),
'access arguments' => array('administer product types'),
'file' => 'includes/commerce_product_ui.types.inc',
);
$items['admin/commerce/products/types/' . $type_arg . '/edit'] = array(
'title' => 'Edit',
'access callback' => 'commerce_product_ui_product_type_update_access',
'access arguments' => array($product_type),
'type' => MENU_DEFAULT_LOCAL_TASK,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
$items['admin/commerce/products/types/' . $type_arg . '/delete'] = array(
'title' => 'Delete',
'page callback' => 'commerce_product_ui_product_type_delete_form_wrapper',
'page arguments' => array($product_type),
'access callback' => 'commerce_product_ui_product_type_update_access',
'access arguments' => array($product_type),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_INLINE,
'weight' => 10,
'file' => 'includes/commerce_product_ui.types.inc',
);
}
return $items;
}
/**
* Menu item title callback: returns the SKU of a product for its pages.
*
* @param $product
* The product object as loaded via the URL wildcard.
* @return
* A page title of the format "Product: [SKU]".
*/
function commerce_product_ui_product_title($product) {
return t('Product: @sku', array('@sku' => $product->sku));
}
/**
* Access callback: determines if the user can create any type of product.
*/
function commerce_product_ui_product_add_any_access() {
// Grant automatic access to users with administer products permission.
if (user_access('administer products')) {
return TRUE;
}
// Check the user's access on a product type basis.
foreach (commerce_product_types() as $type => $product_type) {
if (commerce_product_access('create', $type)) {
return TRUE;
}
}
return FALSE;
}
/**
* Access callback: determines if the user can edit or delete a product type.
*/
function commerce_product_ui_product_type_update_access($product_type) {
if ($product_type['module'] == 'commerce_product_ui') {
return user_access('administer product types');
}
return FALSE;
}
/**
* Implements hook_menu_alter().
*/
function commerce_product_ui_menu_alter(&$items) {
// Transform the field UI tabs into contextual links.
foreach (commerce_product_types() as $type => $product_type) {
// Convert underscores to hyphens for the menu item argument.
$type_arg = strtr($type, '_', '-');
$items['admin/commerce/products/types/' . $type_arg . '/fields']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
$items['admin/commerce/products/types/' . $type_arg . '/display']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
}
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function commerce_product_ui_menu_local_tasks_alter(&$data, $router_item, $root_path) {
// Add action link 'admin/commerce/products/add' on 'admin/commerce/products'.
if ($root_path == 'admin/commerce/products') {
$item = menu_get_item('admin/commerce/products/add');
if ($item['access']) {
$data['actions']['output'][] = array(
'#theme' => 'menu_local_action',
'#link' => $item,
);
}
}
}
/**
* Implements hook_admin_menu_map().
*/
function commerce_product_ui_admin_menu_map() {
// Add awareness to the administration menu of the various product types so
// they are included in the dropdown menu.
$type_args = array();
foreach (array_keys(commerce_product_types()) as $type) {
$type_args[] = strtr($type, '_', '-');
}
$map['admin/commerce/products/types/%'] = array(
'parent' => 'admin/commerce/products/types',
'arguments' => array(
array('%' => $type_args),
),
);
return $map;
}
/**
* Implements hook_help().
*/
function commerce_product_ui_help($path, $arg) {
switch ($path) {
case 'admin/commerce/products/types/add':
return '<p>' . t('Individual product types can have different fields assigned to them.') . '</p>';
}
// Return the user defined help text per product type when adding products.
if ($arg[1] == 'commerce' && $arg[2] == 'products' && $arg[3] == 'add' && $arg[4]) {
$product_type = commerce_product_type_load($arg[4]);
return (!empty($product_type['help']) ? '<p>' . filter_xss_admin($product_type['help']) . '</p>' : '');
}
}
/**
* Implements hook_theme().
*/
function commerce_product_ui_theme() {
return array(
'product_add_list' => array(
'variables' => array('content' => array()),
'file' => 'includes/commerce_product_ui.products.inc',
),
'product_type_admin_overview' => array(
'variables' => array('type' => NULL),
'file' => 'includes/commerce_product_ui.types.inc',
),
'commerce_product_sku' => array(
'variables' => array('sku' => NULL, 'label' => NULL, 'product' => NULL),
'path' => drupal_get_path('module', 'commerce_product_ui') . '/theme',
'template' => 'commerce-product-sku',
),
'commerce_product_title' => array(
'variables' => array('title' => NULL, 'label' => NULL, 'product' => NULL),
'path' => drupal_get_path('module', 'commerce_product_ui') . '/theme',
'template' => 'commerce-product-title',
),
'commerce_product_status' => array(
'variables' => array('status' => NULL, 'label' => NULL, 'product' => NULL),
'path' => drupal_get_path('module', 'commerce_product_ui') . '/theme',
'template' => 'commerce-product-status',
),
);
}
/**
* Implements hook_entity_info_alter().
*/
function commerce_product_ui_entity_info_alter(&$entity_info) {
// Add a URI callback to the product entity.
$entity_info['commerce_product']['uri callback'] = 'commerce_product_ui_uri';
// Expose the admin UI for product fields.
foreach ($entity_info['commerce_product']['bundles'] as $type => &$bundle) {
$bundle['admin'] = array(
'path' => 'admin/commerce/products/types/' . strtr($type, '_', '-'),
'access arguments' => array('administer product types'),
);
}
}
/**
* Entity uri callback: points to the edit form of the given product if no other
* URI is specified.
*/
function commerce_product_ui_uri($product) {
// First look for a return value in the default entity uri callback.
$uri = commerce_product_uri($product);
// If a value was found, return it now.
if (!empty($uri)) {
return $uri;
}
// Otherwise return an admin URI if the user has permission.
if (commerce_product_access('view', $product)) {
return array(
'path' => 'admin/commerce/products/' . $product->product_id,
);
}
}
/**
* Implements hook_commerce_product_type_info().
*/
function commerce_product_ui_commerce_product_type_info() {
return db_query('SELECT * FROM {commerce_product_type}')->fetchAllAssoc('type', PDO::FETCH_ASSOC);
}
/**
* Returns an initialized product type array.
*/
function commerce_product_ui_product_type_new() {
return array(
'type' => '',
'name' => '',
'description' => '',
'help' => '',
);
}
/**
* Saves a product type.
*
* This function will either insert a new product type if $product_type['is_new']
* is set or attempt to update an existing product type if it is not. It does
* not currently support changing the machine-readable name of the product type,
* nor is this possible through the form supplied by the Product UI module.
*
* @param $product_type
* The product type array containing the basic properties as initialized in
* commerce_product_ui_product_type_new().
* @param $configure
* Boolean indicating whether or not product type configuration should be
* performed in the event of a new product type being saved.
*
* @return
* The return value of the call to drupal_write_record() to save the product
* type, either FALSE on failure or SAVED_NEW or SAVED_UPDATED indicating
* the type of query performed to save the product type.
*/
function commerce_product_ui_product_type_save($product_type, $configure = TRUE) {
$op = drupal_write_record('commerce_product_type', $product_type, empty($product_type['is_new']) ? 'type' : array());
// If this is a new product type and the insert did not fail...
if (!empty($product_type['is_new']) && $op !== FALSE) {
// Notify the field API that a new bundle has been created.
field_attach_create_bundle('commerce_product', $product_type['type']);
// Add the default price field to the product type.
if ($configure) {
commerce_product_configure_product_type($product_type['type']);
}
// Notify other modules that a new product type has been created.
module_invoke_all('commerce_product_type_insert', $product_type);
}
elseif ($op !== FALSE) {
// Notify other modules that an existing product type has been updated.
module_invoke_all('commerce_product_type_update', $product_type);
}
commerce_product_types_reset();
menu_rebuild();
return $op;
}
/**
* Deletes a product type.
*
* @param $type
* The machine-readable name of the product type.
*/
function commerce_product_ui_product_type_delete($type) {
$product_type = commerce_product_type_load($type);
db_delete('commerce_product_type')
->condition('type', $type)
->execute();
// Rebuild the menu to get rid of this type's product add menu item.
commerce_product_types_reset();
menu_rebuild();
// Notify the field API that this bundle has been destroyed.
field_attach_delete_bundle('commerce_product', $type);
// Notify other modules that this product type has been deleted.
module_invoke_all('commerce_product_type_delete', $product_type);
}
/**
* Checks to see if a given product type already exists.
*
* @param $type
* The string to match against existing types.
*
* @return
* TRUE or FALSE indicating whether or not the product type exists.
*/
function commerce_product_ui_validate_product_type_unique($type) {
// Look for a match of the type.
if ($match_id = db_query('SELECT type FROM {commerce_product_type} WHERE type = :type', array(':type' => $type))->fetchField()) {
return FALSE;
}
return TRUE;
}
/**
* Implements hook_forms().
*/
function commerce_product_ui_forms($form_id, $args) {
$forms = array();
// Define a wrapper ID for the product add / edit form.
$forms['commerce_product_ui_product_form'] = array(
'callback' => 'commerce_product_product_form',
);
// Define a wrapper ID for the product delete confirmation form.
$forms['commerce_product_ui_product_delete_form'] = array(
'callback' => 'commerce_product_product_delete_form',
);
return $forms;
}
/**
* Implements hook_form_alter().
*/
function commerce_product_ui_form_alter(&$form, &$form_state, $form_id) {
// On field administration forms for product 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_product') {
commerce_product_ui_set_breadcrumb(TRUE);
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Product UI module instantiates the Product 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_product_ui_product_form()
*/
function commerce_product_ui_form_commerce_product_ui_product_form_alter(&$form, &$form_state) {
// Add a submit handler to the save button to add a redirect.
$form['actions']['submit']['#submit'][] = 'commerce_product_ui_product_form_submit';
// Add the save and continue button for new products.
if (empty($form_state['commerce_product']->product_id)) {
$form['actions']['save_continue'] = array(
'#type' => 'submit',
'#value' => t('Save and add another'),
'#submit' => $form['actions']['submit']['#submit'],
'#suffix' => l('Cancel', 'admin/commerce/products'),
'#weight' => 45,
);
}
else {
$form['actions']['submit']['#suffix'] = l('Cancel', 'admin/commerce/products');
}
}
/**
* Submit callback for commerce_product_ui_product_form().
*
* @see commerce_product_ui_form_commerce_product_ui_product_form_alter()
*/
function commerce_product_ui_product_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/products/add/' . strtr($form_state['commerce_product']->type, array('_' => '-'));
}
elseif (arg(2) == 'products' && arg(3) == 'add') {
$form_state['redirect'] = 'admin/commerce/products';
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* The Product UI module instantiates the Product 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_product_ui_product_delete_form()
*/
function commerce_product_ui_form_commerce_product_ui_product_delete_form_alter(&$form, &$form_state) {
$form['actions']['cancel']['#markup'] = l(t('Cancel'), 'admin/commerce/products');
$form['#submit'][] = 'commerce_product_ui_product_delete_form_submit';
}
/**
* Submit callback for commerce_product_ui_product_delete_form().
*
* @see commerce_product_ui_form_commerce_product_ui_product_delete_form_alter()
*/
function commerce_product_ui_product_delete_form_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/commerce/products';
}
/**
* Implements hook_views_api().
*/
function commerce_product_ui_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'commerce_product_ui') . '/includes/views',
);
}
/**
* Sets the breadcrumb for administrative product pages.
*
* @param $product_types
* TRUE or FALSE indicating whether or not the breadcrumb should include the
* product types administrative page.
*/
function commerce_product_ui_set_breadcrumb($product_types = FALSE) {
$breadcrumb = array(
l(t('Home'), '<front>'),
l(t('Administration'), 'admin'),
l(t('Store'), 'admin/commerce'),
l(t('Products'), 'admin/commerce/products'),
);
if ($product_types) {
$breadcrumb[] = l(t('Product types'), 'admin/commerce/products/types');
}
drupal_set_breadcrumb($breadcrumb);
}
<?php
// $Id$
/**
* @file
* Page callbacks and form builder functions for administering products.
*/
/**
* Menu callback: display a list of product types that the user can create.
*/
function commerce_product_ui_add_page() {
$item = menu_get_item();
$content = system_admin_menu_block($item);
// Bypass the admin/commerce/products/add listing if only one product type is
// available.
if (count($content) == 1) {
$item = array_shift($content);
drupal_goto($item['href']);
}
return theme('product_add_list', array('content' => $content));
}
/**
* Displays the list of available product types for product creation.
*
* @ingroup themeable
*/
function theme_product_add_list($variables) {
$content = $variables['content'];
$output = '';
if ($content) {
$output = '<dl class="commerce-product-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 product types')) {
$output = '<p>' . t('You have not created any product types yet. Go to the <a href="@create-product-type">product type creation page</a> to add a new product type.', array('@create-product-type' => url('admin/commerce/products/types/add'))) . '</p>';
}
else {
$output = '<p>' . t('No product types have been created yet for you to use.') . '</p>';
}
}
return $output;
}
/**
* Form callback wrapper: create or edit a product.
*
* @param $product
* The product object being edited by this form.
*
* @see commerce_product_product_form()
*/
function commerce_product_ui_product_form_wrapper($product) {
// Add the breadcrumb for the form's location.
commerce_product_ui_set_breadcrumb();
// Include the forms file from the Product module.
module_load_include('inc', 'commerce_product', 'includes/commerce_product.forms');
return drupal_get_form('commerce_product_ui_product_form', $product);
}
/**
* Form callback wrapper: confirmation form for deleting a product.
*
* @param $product
* The product object being deleted by this form.
*
* @see commerce_product_product_delete_form()
*/
function commerce_product_ui_product_delete_form_wrapper($product) {
// Add the breadcrumb for the form's location.
commerce_product_ui_set_breadcrumb();
// Include the forms file from the Product module.
module_load_include('inc', 'commerce_product', 'includes/commerce_product.forms');
return drupal_get_form('commerce_product_ui_product_delete_form', $product);
}
/* $Id$ */
.links.operations {
text-transform: lowercase;
}
<?php
// $Id$
/**
* @file
*/
/**
* Menu callback: display an overview of available types.
*/
function commerce_product_ui_types_overview() {
drupal_add_css(drupal_get_path('module', 'commerce_product_ui') . '/theme/commerce_product_ui.types.css');
$header = array(
t('Name'),
t('Operations'),
);
$rows = array();
// Loop through all defined product types.
foreach (commerce_product_types() as $type => $product_type) {
// Build the operation links for the current product type.
$links = menu_contextual_links('commerce-product-type', 'admin/commerce/products/types', array(strtr($type, array('_' => '-'))));
// Add the product type's row to the table's rows array.
$rows[] = array(
theme('product_type_admin_overview', array('product_type' => $product_type)),
theme('links', array('links' => $links, 'attributes' => array('class' => 'links inline operations'))),
);
}
// If no product types are defined...
if (empty($rows)) {
// Add a standard empty row with a link to add a new product type.
$rows[] = array(
array(
'data' => t('There are no product types yet. <a href="@link">Add product type</a>.', array('@link' => url('admin/commerce/products/types/add'))),
'colspan' => 2,
)
);
}
return theme('table', array('header' => $header, 'rows' => $rows));
}
/**
* Builds an overview of a product 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 product type array.
*
* @ingroup themeable
*/
function theme_product_type_admin_overview($variables) {
$product_type = $variables['product_type'];
$output = check_plain($product_type['name']);
$output .= ' <small> (Machine name: ' . check_plain($product_type['type']) . ')</small>';
$output .= '<div class="description">' . filter_xss_admin($product_type['description']) . '</div>';
return $output;
}
/**
* Form callback wrapper: create or edit a product type.
*
* @param $product_type
* The product type array being created or edited by this form.
*
* @see commerce_product_product_type_form()
*/
function commerce_product_ui_product_type_form_wrapper($product_type) {
// Add the breadcrumb for the form's location.
commerce_product_ui_set_breadcrumb(TRUE);
// Return a message if the product type is not governed by Product UI.
if (!empty($product_type['type']) && $product_type['module'] != 'commerce_product_ui') {
return t('This product type cannot be edited, because it is not defined by the Product UI module.');
}
// Include the forms file from the Product module.
module_load_include('inc', 'commerce_product_ui', 'includes/commerce_product_ui.forms');
return drupal_get_form('commerce_product_ui_product_type_form', $product_type);
}
/**
* Form callback wrapper: confirmation form for deleting a product type.
*
* @param $product_type
* The product type array being deleted by this form.
*
* @see commerce_product_product_type_delete_form()
*/
function commerce_product_ui_product_type_delete_form_wrapper($product_type) {
// Add the breadcrumb for the form's location.
commerce_product_ui_set_breadcrumb(TRUE);
// Return a message if the product type is not governed by Product UI.
if ($product_type['module'] != 'commerce_product_ui') {
return t('This product type cannot be deleted, because it is not defined by the Product UI module.');
}
// Don't allow deletion of product types that have products already.
if (($count = db_query("SELECT product_id FROM {commerce_product} WHERE type = :product_type", array(':product_type' => $product_type['type']))->rowCount()) > 0) {
drupal_set_title(t('Cannot delete the %name product type', array('%name' => $product_type['name'])), PASS_THROUGH);
return format_plural($count,
'There is 1 product of this type. It cannot be deleted.',
'There are @count products of this type. It cannot be deleted.'
);
}
// Include the forms file from the Product module.
module_load_include('inc', 'commerce_product_ui', 'includes/commerce_product_ui.forms');
return drupal_get_form('commerce_product_ui_product_type_delete_form', $product_type);
}
<?php
// $Id$
/**
* Views for the default product UI.
*/
/**
* Implements hook_views_default_views().
*/
function commerce_product_ui_views_default_views() {
$views = array();
// Products admin list at admin/commerce/products.
$view = new view;
$view->name = 'commerce_products';
$view->description = 'Display a list of products for store admin.';
$view->tag = 'commerce';
$view->view_php = '';
$view->base_table = 'commerce_product';
$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'] = 'Products';
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['access']['perm'] = 'administer products';
$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['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'sku' => 'sku',
'title' => 'title',
'type' => 'type',
'entity_id' => 'entity_id',
'operations' => 'operations',
);
$handler->display->display_options['style_options']['default'] = 'sku';
$handler->display->display_options['style_options']['info'] = array(
'sku' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'title' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'type' => array(
'sortable' => 1,
'align' => '',
'separator' => '',
),
'entity_id' => array(
'sortable' => 0,
'align' => '',
'separator' => '',
),
'operations' => array(
'align' => '',
'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']['text']['id'] = 'area';
$handler->display->display_options['empty']['text']['table'] = 'views';
$handler->display->display_options['empty']['text']['field'] = 'area';
$handler->display->display_options['empty']['text']['empty'] = FALSE;
$handler->display->display_options['empty']['text']['content'] = 'No products have been created yet.';
$handler->display->display_options['empty']['text']['format'] = 'filtered_html';
/* Field: Commerce Product: SKU */
$handler->display->display_options['fields']['sku']['id'] = 'sku';
$handler->display->display_options['fields']['sku']['table'] = 'commerce_product';
$handler->display->display_options['fields']['sku']['field'] = 'sku';
$handler->display->display_options['fields']['sku']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['sku']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['sku']['alter']['trim'] = 0;
$handler->display->display_options['fields']['sku']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['sku']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['sku']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['sku']['alter']['html'] = 0;
$handler->display->display_options['fields']['sku']['hide_empty'] = 0;
$handler->display->display_options['fields']['sku']['empty_zero'] = 0;
$handler->display->display_options['fields']['sku']['link_to_product'] = 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']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['title']['alter']['trim'] = 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']['hide_empty'] = 0;
$handler->display->display_options['fields']['title']['empty_zero'] = 0;
$handler->display->display_options['fields']['title']['link_to_product'] = 1;
/* Field: Commerce Product: Type */
$handler->display->display_options['fields']['type']['id'] = 'type';
$handler->display->display_options['fields']['type']['table'] = 'commerce_product';
$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']['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: Fields: commerce_price */
$handler->display->display_options['fields']['entity_id']['id'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['table'] = 'field_data_commerce_price';
$handler->display->display_options['fields']['entity_id']['field'] = 'entity_id';
$handler->display->display_options['fields']['entity_id']['label'] = 'Price';
$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';
/* Field: Commerce Product: Operations links */
$handler->display->display_options['fields']['operations']['id'] = 'operations';
$handler->display->display_options['fields']['operations']['table'] = 'commerce_product';
$handler->display->display_options['fields']['operations']['field'] = 'operations';
$handler->display->display_options['fields']['operations']['label'] = 'Operations';
$handler->display->display_options['fields']['operations']['alter']['alter_text'] = 0;
$handler->display->display_options['fields']['operations']['alter']['make_link'] = 0;
$handler->display->display_options['fields']['operations']['alter']['absolute'] = 0;
$handler->display->display_options['fields']['operations']['alter']['trim'] = 0;
$handler->display->display_options['fields']['operations']['alter']['word_boundary'] = 1;
$handler->display->display_options['fields']['operations']['alter']['ellipsis'] = 1;
$handler->display->display_options['fields']['operations']['alter']['strip_tags'] = 0;
$handler->display->display_options['fields']['operations']['alter']['html'] = 0;
$handler->display->display_options['fields']['operations']['hide_empty'] = 0;
$handler->display->display_options['fields']['operations']['empty_zero'] = 0;
/* Sort criterion: Commerce Product: SKU */
$handler->display->display_options['sorts']['sku']['id'] = 'sku';
$handler->display->display_options['sorts']['sku']['table'] = 'commerce_product';
$handler->display->display_options['sorts']['sku']['field'] = 'sku';
/* Display: Admin page */
$handler = $view->new_display('page', 'Admin page', 'admin_page');
$handler->display->display_options['path'] = 'admin/commerce/products/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'] = 'Products';
$handler->display->display_options['tab_options']['description'] = 'Manage products and product types in the store.';
$handler->display->display_options['tab_options']['weight'] = '';
$handler->display->display_options['tab_options']['name'] = 'management';
$views[$view->name] = $view;
return $views;
}
/* $Id$ */
.views-field-operations .links.operations {
text-transform: lowercase;
margin-left: 0;
}
<?php
// $Id$
/**
* @file
* Administrative callbacks for the Commerce UI module.
*/
/**
* Builds the currency settings form.
*/
function commerce_currency_settings_form($form, &$form_state) {
// Build a currency options list from all defined currencies.
$options = array();
foreach (commerce_currencies(FALSE, TRUE) as $currency_code => $currency) {
$options[$currency_code] = t('@code - !name', array('@code' => $currency['code'], '@symbol' => $currency['symbol'], '!name' => $currency['name']));
if (!empty($currency['symbol'])) {
$options[$currency_code] .= ' - ' . check_plain($currency['symbol']);
}
}
$form['commerce_default_currency'] = array(
'#type' => 'select',
'#title' => t('Default store currency'),
'#description' => t('The default store currency will be used as the default for all price fields.'),
'#options' => $options,
'#default_value' => commerce_default_currency(),
);
// Place the enabled currencies checkboxes in a fieldset so the full list
// doesn't spam the administrator when viewing the page.
$form['enabled_currencies'] = array(
'#type' => 'fieldset',
'#title' => t('Enabled currencies'),
'#description' => t('Only enabled currencies will be visible to users when entering prices. The default currency will always be enabled.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['enabled_currencies']['commerce_enabled_currencies'] = array(
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => variable_get('commerce_enabled_currencies', array('USD' => 'USD')),
);
$form['#validate'][] = 'commerce_currency_settings_form_validate';
return system_settings_form($form);
}
/**
* Form validate handler for the currency settings form.
*/
function commerce_currency_settings_form_validate($form, &$form_state) {
// Ensure the default currency is always enabled.
$default = $form_state['values']['commerce_default_currency'];
$form_state['values']['commerce_enabled_currencies'][$default] = $default;
}
; $Id$
name = Commerce UI
description = Defines menu items common to the various Drupal Commerce UI modules.
package = Commerce
dependencies[] = commerce
core = 7.x
<?php
// $Id$
/**
* @file
* Defines menu items common to the various Drupal Commerce UI modules.
*/
/**
* Implements hook_menu().
*/
function commerce_ui_menu() {
$items = array();
// Top level "Store" container.
$items['admin/commerce'] = array(
'title' => 'Store',
'description' => 'Administer your store.',
'page callback' => 'system_admin_menu_block_page',
'access arguments' => array('access administration pages'),
'file path' => drupal_get_path('module', 'system'),
'file' => 'system.admin.inc',
);
$items['admin/commerce/config'] = array(
'title' => 'Configuration',
'description' => 'Configure settings and business rules for your store.',
'page callback' => 'system_admin_menu_block_page',
'access arguments' => array('access administration pages'),
'type' => MENU_NORMAL_ITEM,
'weight' => 10,
'file path' => drupal_get_path('module', 'system'),
'file' => 'system.admin.inc',
);
$items['admin/commerce/config/currency'] = array(
'title' => 'Currency settings',
'description' => 'Configure the default currency and display settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('commerce_currency_settings_form'),
'access arguments' => array('configure store'),
'type' => MENU_NORMAL_ITEM,
'file' => 'includes/commerce_ui.admin.inc',
);
return $items;
}
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

This file has been truncated, but you can view the full file.
<?php
// $Id$
/**
* @file
* Hooks provided by the Product Pricing module.
*/
/**
* Lets modules invalidate a particular product during the sell price pre-
* calculation process.
*
* Because the price table can very quickly accumulate millions of rows on
* complex websites, it is advantageous to prevent any unnecessary products from
* cluttering up the table. This hook allows modules to prevent pre-calculation
* on an individual product, which is especially useful when it is known that
* products meeting certain criteria will never be featured in Views and other
* displays where it might be sorted or filtered based on a calculated price.
*
* @param $product
* The product being considered for sell price pre-calculation.
*
* @return
* TRUE or FALSE indicating whether or not the product is valid.
*
* @see hook_commerce_product_valid_pre_calculation_rule()
*/
function hook_commerce_product_valid_pre_calculation_product($product) {
// Disable sell price pre-calculation for inactive products.
if (!$product->status) {
return FALSE;
}
}
/**
* Lets modules invalidate a particular rule configuration during the sell price
* pre-calculation process.
*
* Because the price table can very quickly accumulate millions of rows on
* complex websites, it is advantageous to prevent any unnecessary rule
* configurati
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment