You are here

commerce_multicurrency.module in Commerce Multicurrency 7

Enhancements for the commerce currency support.

File

commerce_multicurrency.module
View source
<?php

/**
 * @file
 * Enhancements for the commerce currency support.
 */

/**
 * Implements hook_menu().
 */
function commerce_multicurrency_menu() {
  $items = array();
  $items['admin/commerce/config/currency/conversion'] = array(
    'title' => 'Currency conversion',
    'description' => 'Configure currency conversion.',
    'page provider' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_multicurrency_conversion_settings_form',
    ),
    'access arguments' => array(
      'configure store',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'commerce_multicurrency.admin.inc',
    'weight' => 1,
  );
  $items['admin/commerce/config/currency/handling'] = array(
    'title' => 'Currency handling',
    'description' => 'Configure currency handling.',
    'page provider' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_multicurrency_handling_settings_form',
    ),
    'access arguments' => array(
      'configure store',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'commerce_multicurrency.admin.inc',
    'weight' => 2,
  );
  $items['commerce_currency_select/%'] = array(
    'title' => 'Set Active Currency',
    'page callback' => 'commerce_multicurrency_set_user_currency_code_callback',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
    'page arguments' => array(
      1,
    ),
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function commerce_multicurrency_menu_alter(&$items) {
  $items['admin/commerce/config/currency/settings'] = $items['admin/commerce/config/currency'];
  $items['admin/commerce/config/currency/settings']['type'] = MENU_DEFAULT_LOCAL_TASK;
}

/**
 * Implements hook_hook_info().
 */
function commerce_multicurrency_hook_info() {
  $hooks = array(
    'commerce_multicurrency_exchange_rate_sync_provider_info' => array(
      'group' => 'commerce_multicurrency',
    ),
    'commerce_multicurrency_exchange_rate_sync_provider_info_alter' => array(
      'group' => 'commerce_multicurrency',
    ),
  );
  return $hooks;
}

/**
 * Implements hook_theme().
 */
function commerce_multicurrency_theme() {
  return array(
    'commerce_multicurrency_selector_menu' => array(
      'variables' => array(
        'current_currency' => commerce_multicurrency_get_user_currency_code(),
        'enabled_currencies' => commerce_currencies(TRUE),
      ),
      'path' => drupal_get_path('module', 'commerce_multicurrency') . '/theme',
      'template' => 'commerce-multicurrency-selector-menu',
    ),
  );
}

/**
 * Returns currency exchange rate sync providers.
 *
 * Returns all available  currency exchange rate sync providers or as single
 * provider if a code is defined. If no matching provider was found FALSE is
 * returned.
 *
 * @param string $code
 *   The code of the currency exchange rate sync provider to return.
 *
 * @return array|FALSE
 *   List of providers, provider or FALSE on failure.
 */
function commerce_multicurrency_commerce_multicurrency_exchange_rate_sync_provider($code = NULL) {
  $providers = module_invoke_all('commerce_multicurrency_exchange_rate_sync_provider_info');
  drupal_alter('commerce_multicurrency_exchange_rate_sync_provider_info', $providers);
  if ($code) {
    if (isset($providers[$code])) {
      return $providers[$code];
    }
    return FALSE;
  }
  return $providers;
}

/**
 * Implements hook_commerce_multicurrency_exchange_rate_sync_provider_info().
 */
function commerce_multicurrency_commerce_multicurrency_exchange_rate_sync_provider_info() {
  return array(
    'ecb' => array(
      'title' => t('European Central Bank'),
      'callback' => 'commerce_multicurrency_exchange_rate_sync_provider_ecb',
      'file' => drupal_get_path('module', 'commerce_multicurrency') . '/commerce_multicurrency.ecb.inc',
    ),
    'google' => array(
      'title' => t('Google exchange'),
      'callback' => 'commerce_multicurrency_exchange_rate_sync_provider_google',
      'file' => drupal_get_path('module', 'commerce_multicurrency') . '/commerce_multicurrency.google.inc',
    ),
  );
}

/**
 * Implements hook_commerce_currency_info_alter().
 *
 * On disabled cross conversion inject dedicated currency conversion callback.
 * Set's the synced conversion rates into the default conversion_rate setting.
 */
function commerce_multicurrency_commerce_currency_info_alter(&$currencies, $langcode) {
  $default_currency_code = commerce_default_currency();
  $conversion_callback = NULL;
  $conversion_settings = FALSE;
  if (!variable_get('commerce_multicurrency_use_cross_conversion', TRUE)) {
    $conversion_callback = 'commerce_multicurrency_conversion';
  }
  else {
    $conversion_settings = variable_get('commerce_multicurrency_conversion_settings', array());
  }
  foreach ($currencies as $currency_code => &$currency_info) {
    $currency_info['conversion_callback'] = $conversion_callback;
    if ($conversion_settings && !empty($conversion_settings[$default_currency_code]['rates'][$currency_code]) && $conversion_settings[$default_currency_code]['rates'][$currency_code]['rate'] != 0) {
      $currency_info['conversion_rate'] = 1 / $conversion_settings[$default_currency_code]['rates'][$currency_code]['rate'];
    }
  }
}

/**
 * Converts a currency amount into another.
 *
 * @param integer $amount
 *   The amount to convert.
 * @param string $currency_code
 *   The currency code of the amount.
 * @param string $target_currency_code
 *   The currency code to convert the amount to.
 *
 * @return integer|FALSE
 *   The converted amount or FALSE on failure.
 */
function commerce_multicurrency_conversion($amount, $currency_code, $target_currency_code) {
  $conversion_settings =& drupal_static(__FUNCTION__, FALSE);

  // Skip - makes no sense to calculate here.
  if ($currency_code == $target_currency_code) {
    return $amount;
  }

  // Check if there are conversion settings.
  if ($conversion_settings == FALSE && !($conversion_settings = variable_get('commerce_multicurrency_conversion_settings', FALSE))) {
    watchdog('commerce_multicurrency', 'No conversion rates found - please configure them!', array(), WATCHDOG_ERROR, url('admin/commerce/config/currency/conversion'));
    return FALSE;
  }

  // If there's a conversion rate available use it right away.
  if (!empty($conversion_settings[$currency_code]['rates'][$target_currency_code]['rate'])) {
    return $amount * $conversion_settings[$currency_code]['rates'][$target_currency_code]['rate'];
  }

  // Run cross conversion processing if enabled.
  if (variable_get('commerce_multicurrency_use_cross_conversion', TRUE)) {
    $default_currency_code = commerce_default_currency();

    // If target currency is equal to default currency, we can use reciprocal
    // rate.
    if ($target_currency_code == $default_currency_code && !empty($conversion_settings[$default_currency_code]['rates'][$currency_code]['rate'])) {
      return $amount / $conversion_settings[$default_currency_code]['rates'][$currency_code]['rate'];
    }

    // Using cross rate basing on default currency.
    if (!empty($conversion_settings[$default_currency_code]['rates'][$currency_code]['rate']) && !empty($conversion_settings[$default_currency_code]['rates'][$target_currency_code]['rate'])) {
      $currencies = commerce_currencies();
      if (isset($currencies[$currency_code]['conversion_rate']) && !empty($currencies[$target_currency_code]['conversion_rate'])) {
        return $amount * $currencies[$currency_code]['conversion_rate'] / $currencies[$target_currency_code]['conversion_rate'];
      }
    }
  }

  // If there are no conversion settings for the specified currencies.
  watchdog('commerce_multicurrency', 'No conversion rate from %source_currency to  %target_currency found - please configure it!', array(
    '%source_currency' => $currency_code,
    '%target_currency' => $target_currency_code,
  ), WATCHDOG_ERROR, url('admin/commerce/config/currency/conversion'));
  return FALSE;
}

/**
 * Implements hook_cron().
 *
 * Queues currencies for updates.
 */
function commerce_multicurrency_cron() {
  $currencies = commerce_currencies(TRUE);
  if (!($conversion_settings = variable_get('commerce_multicurrency_conversion_settings', FALSE))) {
    return;
  }
  $queue = DrupalQueue::get('commerce_multicurrency_sync_exchange_rates');
  foreach ($currencies as $currency_code => $currency) {
    $currencies_to_sync = $currencies;
    unset($currencies_to_sync[$currency_code]);
    $currency_sync_item = array(
      'currency_code' => $currency_code,
      'target_currencies' => empty($currencies_to_sync) ? array() : array_combine(array_keys($currencies_to_sync), array_keys($currencies_to_sync)),
    );

    // Check if there are individual settings per currency to currency rate.
    if (!empty($conversion_settings[$currency_code]) && empty($conversion_settings[$currency_code]['sync'])) {
      foreach ($conversion_settings[$currency_code]['rates'] as $target_currency_code => $settings) {

        // If this combination is excluded from autosync remove it.
        if (empty($settings['sync'])) {
          unset($currency_sync_item['target_currencies'][$target_currency_code]);
        }
      }
    }
    $queue
      ->createItem($currency_sync_item);
  }
  $queue
    ->createItem('finish');
}

/**
 * Implements hook_cron_queue_info().
 */
function commerce_multicurrency_cron_queue_info() {
  $queues['commerce_multicurrency_sync_exchange_rates'] = array(
    'worker callback' => 'commerce_multicurrency_sync_exchange_rates',
    'time' => 60,
  );
  return $queues;
}

/**
 * Update the currency exchange rates.
 *
 * Use the configured sync provider to do so.
 *
 * @see commerce_multicurrency_cron()
 * @see commerce_multicurrency_cron_queue_info()
 */
function commerce_multicurrency_sync_exchange_rates($currency_sync_item) {
  if ($currency_sync_item == 'finish') {

    // Make sure the core commerce settings are in sync.
    commerce_currencies(FALSE, TRUE);
    module_invoke_all('commerce_multicurrency_sync_finish');
    return;
  }
  $sync_provider = commerce_multicurrency_commerce_multicurrency_exchange_rate_sync_provider(variable_get('commerce_multicurrency_sync_provider', 'ecb'));
  if (!empty($sync_provider['file'])) {
    require_once $sync_provider['file'];
  }
  $rates = $sync_provider['callback']($currency_sync_item['currency_code'], $currency_sync_item['target_currencies']);
  $conversion_settings = variable_get('commerce_multicurrency_conversion_settings', array());
  foreach ($rates as $target_currency_code => $rate) {
    $conversion_settings[$currency_sync_item['currency_code']]['rates'][$target_currency_code]['rate'] = $rate;
  }
  variable_set('commerce_multicurrency_conversion_settings', $conversion_settings);
}

/**
 * Function to trigger the currency exchange rate synchronization.
 */
function commerce_multicurrency_sync_exchange_rates_now() {
  $queue = DrupalQueue::get('commerce_multicurrency_sync_exchange_rates');
  $queue
    ->createQueue();
  commerce_multicurrency_cron();

  // Build batch.
  $batch = array(
    'title' => t('Synchronize currency exchange rates.'),
    'operations' => array(),
    'init_message' => t('Synchronisation is starting.'),
    'progress_message' => t('Processed @current out of @total currencies.'),
    'error_message' => t('Synchronisation has encountered an error.'),
    'file' => drupal_get_path('module', 'commerce_multicurrency') . '/commerce_multicurrency.module',
  );

  // Register queue items to process in batch.
  while ($item = $queue
    ->claimItem()) {
    $batch['operations'][] = array(
      'commerce_multicurrency_sync_exchange_rates',
      array(
        $item->data,
      ),
    );
  }
  $queue
    ->deleteQueue();
  batch_set($batch);
}

/**
 * Implements hook_help().
 */
function commerce_multicurrency_help($path, $arg) {
  switch ($path) {

    // Main module help for the block module.
    case 'admin/commerce/config/currency/handling':
      $text_1 = t('There are two fundamental different ways of how to handle multiple currencies:');
      $list_item_1 = t('Use a conversion to get the appropriate amount for a currency.');
      $list_item_2 = t('Use a dedicated price field for each currency.');
      $text_2 = t('While the <a href="!conversion_settings_page_url">conversion settings page</a> of this modules help you to manage  your conversion this settings are dedicated to the second approach. If you enable the dedicated currency price fields you\'ll also get a new rule to set the appropriate currency price on the rules event "Calculating the sell price of a product"', array(
        '!conversion_settings_page_url' => url('admin/commerce/config/currency/conversion'),
      ));
      return '<p>' . $text_1 . '<ul><li>' . $list_item_1 . '</li><li>' . $list_item_2 . '</li></ul>' . $text_2 . '</p>';
  }
}

/**
 * Implements hook_block_info().
 */
function commerce_multicurrency_block_info() {
  $blocks = array();
  $blocks['currency_selector'] = array(
    'info' => t('Currency Selector'),
    'cache' => DRUPAL_NO_CACHE,
  );
  $blocks['currency_menu'] = array(
    'info' => t('Currency Menu Selector'),
    'cache' => DRUPAL_NO_CACHE,
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function commerce_multicurrency_block_view($delta) {
  $block = array();
  switch ($delta) {
    case 'currency_selector':
      $block['subject'] = t('Select currency');
      $block['content'] = drupal_get_form('commerce_multicurrency_selector_form');
      break;
    case 'currency_menu':
      $block['subject'] = t('Select currency');
      $block['content'] = theme('commerce_multicurrency_selector_menu', array(
        'user_currency' => commerce_multicurrency_get_user_currency_code(),
        'enabled_currencies' => commerce_currencies(TRUE),
      ));
      break;
  }
  return $block;
}

/**
 * Form for the currency selector block.
 */
function commerce_multicurrency_selector_form($form, &$form_state) {
  $form['#attached']['js'] = array(
    drupal_get_path('module', 'commerce_multicurrency') . '/commerce_multicurrency.js',
  );
  $form['selected_currency'] = array(
    '#type' => 'select',
    '#options' => commerce_currency_code_options_list(),
    '#default_value' => commerce_multicurrency_get_user_currency_code(),
  );
  $form['save_selected_currency'] = array(
    '#value' => t('Save'),
    '#type' => 'submit',
  );
  return $form;
}

/**
 * Submit handler for the currency selector block form.
 */
function commerce_multicurrency_selector_form_submit($form, &$form_state) {
  commerce_multicurrency_set_user_currency_code($form_state['values']['selected_currency']);
}

/**
 * Handles requests to the currency set menu callback.
 *
 * @param string $currency_code
 *   The currency code to set the user currency to.
 */
function commerce_multicurrency_set_user_currency_code_callback($currency_code) {

  // Ensure this page isn't cached.
  drupal_page_is_cacheable(FALSE);
  commerce_multicurrency_set_user_currency_code($currency_code);
  drupal_goto('<front>');
}

/**
 * Store the currency_code to use for the user.
 *
 * Invokes the rules event commerce_multicurrency_user_currency_set.
 *
 * @param string $currency_code
 *   The currency code to set the user currency to.
 * @param boolean $overwrite_cookie
 *   Set to FALSE if the currency shouldn't be changed if the cookie already
 *   exists.
 */
function commerce_multicurrency_set_user_currency_code($currency_code, $overwrite_cookie = TRUE) {
  if ($overwrite_cookie || empty($_COOKIE['Drupal_visitor_commerce_currency'])) {
    $enabled_currencies = commerce_currencies(TRUE);
    if (isset($enabled_currencies[$currency_code])) {
      $old_currency_code = commerce_multicurrency_get_user_currency_code();

      // Inject currency into the static cache.
      $current_currency_code =& drupal_static('commerce_multicurrency_get_user_currency_code', FALSE);
      $current_currency_code = $currency_code;

      // Set cookie.
      user_cookie_save(array(
        'commerce_currency' => $currency_code,
      ));
      rules_invoke_event('commerce_multicurrency_user_currency_set', $currency_code, $old_currency_code);

      // Refresh the order. Triggers also the update of line item prices.
      // @see commerce_multicurrency_commerce_cart_line_item_refresh().
      if (module_exists('commerce_cart')) {
        global $user;
        if ($order = commerce_cart_order_load($user->uid)) {
          commerce_cart_order_refresh($order);
        }
      }
    }
  }
}

/**
 * Implements hook_commerce_cart_line_item_refresh().
 */
function commerce_multicurrency_commerce_cart_line_item_refresh($line_item, $order_wrapper) {
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
  $currency_code = commerce_multicurrency_get_user_currency_code();
  if ($line_item_wrapper->commerce_total->currency_code
    ->value() != $currency_code) {
    $amount = commerce_currency_convert($line_item_wrapper->commerce_total->amount
      ->value(), $line_item_wrapper->commerce_total->currency_code
      ->value(), $currency_code);
    $line_item_wrapper->commerce_total->amount = $amount;
    $line_item_wrapper->commerce_total->currency_code = $currency_code;
  }
}

/**
 * Returns the currency code to use for the current user.
 * @return string
 *   The currency code.
 */
function commerce_multicurrency_get_user_currency_code() {
  $currency_code =& drupal_static(__FUNCTION__, FALSE);
  if ($currency_code) {
    return $currency_code;
  }

  // If there's a cookie with a selected currency ensure it's a available one.
  if (isset($_COOKIE['Drupal_visitor_commerce_currency'])) {
    $enabled_currencies = commerce_currencies(TRUE);
    if (!empty($enabled_currencies[$_COOKIE['Drupal_visitor_commerce_currency']])) {
      return $currency_code = $_COOKIE['Drupal_visitor_commerce_currency'];
    }
  }
  return $currency_code = commerce_default_currency();
}

/**
 * Implements hook_entity_property_info_alter().
 */
function commerce_multicurrency_entity_property_info_alter(&$info) {

  // Add the current user's currency to the site information.
  $info['site']['properties']['commerce_currency'] = array(
    'label' => t("User's currency"),
    'description' => t('The currency to use for the current user.'),
    'getter callback' => 'commerce_multicurrency_get_properties',
    'type' => 'text',
  );
}

/**
 * Entity metadata callback: returns the current user's currency.
 *
 * @see commerce_multicurrency_entity_property_info_alter()
 */
function commerce_multicurrency_get_properties($data, array $options, $name) {
  switch ($name) {
    case 'commerce_currency':
      return commerce_multicurrency_get_user_currency_code();
  }
}

/**
 * Implements hook_commerce_price_field_calculation_options().
 *
 * @see commerce_multicurrency_commerce_price_field_formatter_prepare_view()
 */
function commerce_multicurrency_commerce_price_field_calculation_options($field, $instance, $view_mode) {

  // If this is a single value custom price field attached to a product.
  if (($instance['entity_type'] == 'commerce_product' || $field['entity_types'] == array(
    'commerce_product',
  )) && $field['field_name'] != 'commerce_price' && $field['cardinality'] == 1) {
    return array(
      'currency_specific_price' => t('Commerce Multicurrency: Display the price in a specific currency.'),
    );
  }
}

/**
 * Implements hook_commerce_price_field_formatter_prepare_view().
 *
 * Allows to configure which fields are send to the rules processing.
 *
 * @see commerce_product_pricing_commerce_price_field_formatter_prepare_view()
 */
function commerce_multicurrency_commerce_price_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {

  // If this is a single value purchase price field attached to a product...
  if ($entity_type == 'commerce_product' && $field['module'] == 'commerce_price' && $field['cardinality'] == 1 && $field['field_name'] != 'commerce_price') {

    // Prepare the items for each entity passed in.
    foreach ($entities as $product_id => $product) {

      // If this price should be converted...
      if (!empty($displays[$product_id]['settings']['calculation']) && $displays[$product_id]['settings']['calculation'] == 'currency_specific_price') {

        // If this price has already been converted, reset it to its original
        // value so it can be re-converted afresh in the current context.
        if (isset($items[$product_id][0]['original'])) {
          $original = $items[$product_id][0]['original'];
          $items[$product_id] = array(
            0 => $original,
          );

          // Reset the price field value on the product object used to perform
          // the conversion.
          foreach ($product->{$field['field_name']} as $langcode => $value) {
            $product->{$field['field_name']}[$langcode] = $items[$product_id];
          }
        }
        else {

          // Save the original value for use in subsequent conversions.
          $original = isset($items[$product_id][0]) ? $items[$product_id][0] : NULL;
        }

        // First create a pseudo product line item that we will pass to Rules.
        $line_item = commerce_product_line_item_new($product);

        // Overwrite the unit price by the value of the current field.
        $line_item->commerce_unit_price = $product->{$field['field_name']};

        // If the current field doesn't contain a value - fake an empty one.
        $field_lang = field_language('commerce_line_item', $line_item, 'commerce_unit_price');
        if (empty($line_item->commerce_unit_price)) {
          $line_item->commerce_unit_price = array(
            $field_lang => array(
              array(
                'amount' => 0,
                'currency_code' => commerce_default_currency(),
                'data' => array(),
              ),
            ),
          );
        }
        if (!empty($line_item->commerce_unit_price[$field_lang][0])) {

          // Add the base price to the components array.
          if (!commerce_price_component_load($line_item->commerce_unit_price[$field_lang][0], 'base_price')) {
            $line_item->commerce_unit_price[$field_lang][0]['data'] = commerce_price_component_add($line_item->commerce_unit_price[$field_lang][0], 'base_price', $line_item->commerce_unit_price[$field_lang][0], TRUE);
          }
        }

        // Fire the rules event to handle the display currency.
        rules_invoke_event('commerce_multicurrency_set_display_price', $line_item, $field['field_name']);

        // Replace the data being displayed with data from the converted price.
        $items[$product_id] = array();
        $items[$product_id][0] = entity_metadata_wrapper('commerce_line_item', $line_item)->commerce_unit_price
          ->value();
        $items[$product_id][0]['original'] = $original;
      }
    }
  }
}

Functions

Namesort descending Description
commerce_multicurrency_block_info Implements hook_block_info().
commerce_multicurrency_block_view Implements hook_block_view().
commerce_multicurrency_commerce_cart_line_item_refresh Implements hook_commerce_cart_line_item_refresh().
commerce_multicurrency_commerce_currency_info_alter Implements hook_commerce_currency_info_alter().
commerce_multicurrency_commerce_multicurrency_exchange_rate_sync_provider Returns currency exchange rate sync providers.
commerce_multicurrency_commerce_multicurrency_exchange_rate_sync_provider_info Implements hook_commerce_multicurrency_exchange_rate_sync_provider_info().
commerce_multicurrency_commerce_price_field_calculation_options Implements hook_commerce_price_field_calculation_options().
commerce_multicurrency_commerce_price_field_formatter_prepare_view Implements hook_commerce_price_field_formatter_prepare_view().
commerce_multicurrency_conversion Converts a currency amount into another.
commerce_multicurrency_cron Implements hook_cron().
commerce_multicurrency_cron_queue_info Implements hook_cron_queue_info().
commerce_multicurrency_entity_property_info_alter Implements hook_entity_property_info_alter().
commerce_multicurrency_get_properties Entity metadata callback: returns the current user's currency.
commerce_multicurrency_get_user_currency_code Returns the currency code to use for the current user.
commerce_multicurrency_help Implements hook_help().
commerce_multicurrency_hook_info Implements hook_hook_info().
commerce_multicurrency_menu Implements hook_menu().
commerce_multicurrency_menu_alter Implements hook_menu_alter().
commerce_multicurrency_selector_form Form for the currency selector block.
commerce_multicurrency_selector_form_submit Submit handler for the currency selector block form.
commerce_multicurrency_set_user_currency_code Store the currency_code to use for the user.
commerce_multicurrency_set_user_currency_code_callback Handles requests to the currency set menu callback.
commerce_multicurrency_sync_exchange_rates Update the currency exchange rates.
commerce_multicurrency_sync_exchange_rates_now Function to trigger the currency exchange rate synchronization.
commerce_multicurrency_theme Implements hook_theme().