You are here

commerce_price_savings_formatter.module in Commerce Price Savings Formatter 7

Adds a display formatter for the price field showing the amount of savings.

File

commerce_price_savings_formatter.module
View source
<?php

// $Id$

/**
 * @file
 * Adds a display formatter for the price field showing the amount of savings.
 */

/**
 * Implements hook_hook_info().
 */
function commerce_price_savings_formatter_hook_info() {
  $hooks = array(
    'commerce_price_savings_formatter_prices_alter' => array(
      'group' => 'field',
    ),
  );
  return $hooks;
}

/**
 * Implements hook_field_formatter_info().
 */
function commerce_price_savings_formatter_field_formatter_info() {
  return array(
    'commerce_price_savings_formatter_formatter' => array(
      'label' => t('Formatted amount with savings'),
      'field types' => array(
        'commerce_price',
      ),
      'settings' => array(
        'calculation' => 'calculated_sell_price',
        'prices' => array(
          'list',
          'price',
          'savings',
        ),
        'savings' => 'both',
        'show_labels' => TRUE,
      ),
    ),
    'commerce_price_savings_formatter_inline' => array(
      'label' => t('Formatted amount with savings (inline)'),
      'field types' => array(
        'commerce_price',
      ),
      'settings' => array(
        'calculation' => 'calculated_sell_price',
        'prices' => array(
          'list',
          'price',
        ),
        'savings' => 'percentage',
        'show_labels' => FALSE,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function commerce_price_savings_formatter_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'] + field_info_formatter_settings($display['type']);

  // Add the default calculation type selection option.
  $form = commerce_price_field_formatter_settings_form($field, $instance, $view_mode, $form, $form_state);

  // Add the price display settings form.
  $form['prices'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Prices to display'),
    '#options' => array(
      'list' => t('List price'),
      'price' => t('Price'),
      'savings' => t('You save'),
    ),
    '#default_value' => $settings['prices'],
  );
  $form['savings'] = array(
    '#type' => 'radios',
    '#title' => t('Savings display method'),
    '#options' => array(
      'amount' => t('The amount saved'),
      'percentage' => t('The percentage saved'),
      'both' => t('Both the amount and percentage saved'),
    ),
    '#default_value' => $settings['savings'],
  );
  $form['show_labels'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show Price Labels'),
    '#default_value' => $settings['show_labels'],
  );
  return $form;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function commerce_price_savings_formatter_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'] + field_info_formatter_settings($display['type']);
  $summary = array();

  // Add the default calculation text to the summary.
  $summary[] = commerce_price_field_formatter_settings_summary($field, $instance, $view_mode);

  // Add rows for specific settings.
  $prices = array(
    'list' => t('List price'),
    'price' => t('Price'),
    'savings' => t('You save'),
  );
  $prices_shown = implode(', ', array_intersect_key($prices, drupal_map_assoc(array_values($settings['prices']))));
  if (!empty($prices_shown)) {
    $summary[] = t('Showing: !prices', array(
      '!prices' => $prices_shown,
    ));
  }
  else {
    $summary[] = t('Not showing any prices.');
  }
  if (!empty($settings['show_labels'])) {
    $summary[] = t('Labels shown');
  }
  else {
    $summary[] = t('No labels');
  }
  switch ($settings['savings']) {
    case 'amount':
      $summary[] = t('Savings displayed as the amount.');
      break;
    case 'percentage':
      $summary[] = t('Savings displayed as the percentage.');
      break;
    case 'both':
      $summary[] = t('Savings displayed as the amount and percentage.');
      break;
  }
  return implode('<br />', $summary);
}

/**
 * Implements hook_field_formatter_prepare_view().
 */
function commerce_price_savings_formatter_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
  commerce_price_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, $items, $displays);
}

/**
 * Implements hook_field_formatter_view().
 */
function commerce_price_savings_formatter_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  $settings = $display['settings'] + field_info_formatter_settings($display['type']);
  $tax_module_exists = module_exists('commerce_tax');

  // Theme the display of the price based on the display type.
  if (in_array($display['type'], array(
    'commerce_price_savings_formatter_formatter',
    'commerce_price_savings_formatter_inline',
  ))) {

    // exit if no display prices
    if (empty($settings['prices'])) {
      return $element;
    }

    // remove prices not selected
    $settings['prices'] = array_filter($settings['prices']);

    // exit if no prices selected
    if (empty($settings['prices'])) {
      return $element;
    }

    // store prices count
    $prices_count = count($settings['prices']);

    // determine if labels are shown
    $show_labels = !empty($settings['show_labels']);

    // determine theme
    $item_theme = $display['type'];

    // Loop through each price value in this field.
    foreach ($items as $delta => $item) {
      $difference = 0;
      $base_price_component = commerce_price_component_total($item, 'base_price');
      $base_currency = commerce_currency_load($base_price_component['currency_code']);
      if ($base_price_component) {

        // calculate included tax amount on base amount
        $included_tax_amount = 0;
        foreach ($item['data']['components'] as $component) {
          if (!empty($component['included']) && !empty($component['price']['data']['tax_rate'])) {
            $included_tax_amount += commerce_currency_convert($component['price']['amount'], $component['price']['currency_code'], $base_price_component['currency_code']);
          }
        }
        $base_price_component['amount'] += $included_tax_amount;

        // Calculate the difference between the base price and calculated price.
        $difference = $base_price_component['amount'] - commerce_currency_convert($item['amount'], $item['currency_code'], $base_price_component['currency_code']);

        // Get decimal value for rounding
        $difference_decimal = commerce_currency_amount_to_decimal($difference, $base_price_component['currency_code']);

        // Round per currency rounding_step
        $difference_decimal = commerce_currency_round($difference_decimal, $base_currency);

        // Update difference amount
        $difference = commerce_currency_decimal_to_amount($difference_decimal, $base_price_component['currency_code']);
      }

      // determine price direction
      $price_direction = $difference > 0 ? 'down' : ($difference < 0 ? 'up' : 'unchanged');

      // format prices
      $formatted_prices = array();
      foreach ($settings['prices'] as $price_type) {
        switch ($price_type) {
          case 'list':

            // Only show the list price if the current price is lower.
            if ($difference > 0 || $prices_count == 1) {
              $formatted_prices[$price_type] = array(
                'label' => t('List price'),
                'amount' => $base_price_component['amount'],
                'currency_code' => $base_price_component['currency_code'],
                'formatted' => commerce_currency_format($base_price_component['amount'], $base_price_component['currency_code'], $entity),
              );
            }
            break;
          case 'price':
            $formatted_prices[$price_type] = array(
              'label' => t('Price'),
              'amount' => $item['amount'],
              'currency_code' => $item['currency_code'],
              'formatted' => commerce_currency_format($item['amount'], $item['currency_code'], $entity),
            );
            break;
          case 'savings':

            // Only show the savings if the current price is lower than the list
            // price; i.e. if there actually are savings.
            if ($difference > 0) {
              $percentage = round($difference / $base_price_component['amount'] * 100) . '%';
              $formatted_prices[$price_type] = array(
                'label' => t('You save'),
                'amount' => $difference,
                'currency_code' => $item['currency_code'],
                'formatted' => commerce_currency_format($difference, $item['currency_code'], $entity),
              );
              if (!empty($base_price_component['amount'])) {
                $difference_percent = $difference / $base_price_component['amount'];
                $formatted_prices[$price_type]['percent'] = array(
                  'value' => $difference_percent,
                  'formatted' => round($difference_percent * 100) . '%',
                );
              }
            }
            break;
        }
      }

      // /settings price type loop
      // allow other to alter the formatted prices
      $context = array(
        'settings' => $settings,
        'entity_type' => $entity_type,
        'entity' => $entity,
        'field' => $field,
        'instance' => $instance,
        'langcode' => $langcode,
        'display' => $display,
      );
      drupal_alter('commerce_price_savings_formatter_prices', $formatted_prices, $context);

      // set savings
      if (!empty($formatted_prices['savings']) && !empty($settings['prices']['savings'])) {
        switch ($settings['savings']) {
          case 'percentage':
            if (!empty($formatted_prices['savings']['percent']['formatted'])) {
              $formatted_prices['savings']['formatted'] = $formatted_prices['savings']['percent']['formatted'];
            }
            else {
              $formatted_prices['savings']['formatted'] = '';
            }
            break;
          case 'both':
            if (!empty($formatted_prices['savings']['percent']['formatted'])) {
              $formatted_prices['savings']['formatted'] .= ' (' . $formatted_prices['savings']['percent']['formatted'] . ')';
            }
            break;
        }
      }

      // build price elements only formatted values
      $price_elements = array();
      foreach ($formatted_prices as $price_type => $price_data) {

        // build prices array with rendered price
        if (isset($price_data['formatted'])) {
          $price_elements[$price_type] = array(
            '#markup' => $price_data['formatted'],
          );

          // add label
          if ($show_labels) {
            $price_elements[$price_type]['#price_label'] = !empty($price_data['label']) ? $price_data['label'] : '';
          }
        }
      }
      $element[$delta] = array(
        '#theme' => $item_theme,
        '#prices' => $price_elements,
        '#price_direction' => $price_direction,
        '#attached' => array(
          'css' => array(
            drupal_get_path('module', 'commerce_price_savings_formatter') . '/theme/commerce_price_savings_formatter.css',
          ),
        ),
      );
    }

    // /item loop
  }

  // /type switch
  return $element;
}

/**
 * Implements hook_theme().
 */
function commerce_price_savings_formatter_theme() {
  return array(
    'commerce_price_savings_formatter_formatter' => array(
      'variables' => array(
        'prices' => array(),
        'price_direction' => NULL,
      ),
    ),
    'commerce_price_savings_formatter_inline' => array(
      'variables' => array(
        'prices' => array(),
        'price_direction' => NULL,
      ),
    ),
  );
}

/**
 * Theme callback: display prices in a table.
 */
function theme_commerce_price_savings_formatter_formatter($vars) {

  // Build an array of table rows based on the prices passed in.
  $rows = array();

  // exit if no prices
  if (empty($vars['prices'])) {
    return '';
  }

  // store prices count
  $prices_count = count($vars['prices']);

  // extract price direction
  $price_direction = !empty($vars['price_direction']) ? $vars['price_direction'] : 'unchanged';

  // build rows
  foreach ($vars['prices'] as $key => $price_element) {
    $row_data = array();

    // add label
    if (isset($price_element['#price_label'])) {
      if (!empty($price_element['#price_label'])) {
        $label = t('@label:', array(
          '@label' => $price_element['#price_label'],
        ));
      }
      else {
        $label = '';
      }
      $row_data[] = array(
        'data' => $label,
        'class' => array(
          'price-label',
        ),
      );
    }

    // add price
    $row_data[] = array(
      'data' => drupal_render($price_element),
      'class' => array(
        'price-amount',
      ),
    );
    $rows[] = array(
      'data' => $row_data,
      'class' => array(
        'commerce-price-savings-formatter-' . $key,
      ),
    );
  }
  return theme('table', array(
    'rows' => $rows,
    'attributes' => array(
      'class' => array(
        'commerce-price-savings-formatter-prices',
        'commerce-price-savings-formatter-prices-count-' . $prices_count,
        'commerce-price-savings-formatter-prices-' . $price_direction,
      ),
    ),
  ));
}

/**
 * Theme callback: display prices as a list.
 */
function theme_commerce_price_savings_formatter_inline($vars) {

  // Build an array of table rows based on the prices passed in.
  $rows = array();

  // exit if no prices
  if (empty($vars['prices'])) {
    return '';
  }

  // store prices count
  $prices_count = count($vars['prices']);

  // extract price direction
  $price_direction = !empty($vars['price_direction']) ? $vars['price_direction'] : 'unchanged';

  // build rows individually
  $price_row = array();
  $label_row = array();
  foreach ($vars['prices'] as $key => $price_element) {
    $row_data = array();

    // add label
    if (isset($price_element['#price_label'])) {
      if (!empty($price_element['#price_label'])) {
        $label = t('@label', array(
          '@label' => $price_element['#price_label'],
        ));
      }
      else {
        $label = '';
      }
      $label_row[$key] = array(
        'data' => '<span class="price-label">' . $label . '</span>',
        'class' => array(
          'commerce-price-savings-formatter-' . $key,
        ),
      );
    }

    // add price
    $price_row[$key] = array(
      'data' => '<span class="price-amount">' . drupal_render($price_element) . '</span>',
      'class' => array(
        'commerce-price-savings-formatter-' . $key,
      ),
    );
  }

  // fill out label row
  if (!empty($label_row) && count($label_row) != count($price_row)) {
    foreach ($price_row as $key => $price_cell) {
      if (!isset($label_row[$key])) {
        $label_row[$key] = '';
      }
    }
  }

  // combine rows
  $rows = array(
    array(
      'data' => $price_row,
      'class' => array(
        'commerce-price-savings-formatter-price-row',
      ),
    ),
    array(
      'data' => $label_row,
      'class' => array(
        'commerce-price-savings-formatter-label-row',
      ),
    ),
  );
  return theme('table', array(
    'rows' => $rows,
    'attributes' => array(
      'class' => array(
        'inline',
        'commerce-price-savings-formatter-prices',
        'commerce-price-savings-formatter-prices-inline',
        'commerce-price-savings-formatter-prices-count-' . $prices_count,
        'commerce-price-savings-formatter-prices-' . $price_direction,
      ),
    ),
  ));
}