You are here

commerce_coupon.module in Commerce Coupon 7.2

Same filename and directory in other branches
  1. 7 commerce_coupon.module

Provides coupon functionality for Drupal Commerce.

File

commerce_coupon.module
View source
<?php

/**
 * @file
 * Provides coupon functionality for Drupal Commerce.
 */

/**
 * Implements hook_flush_caches().
 */
function commerce_coupon_flush_caches() {
  module_load_install('commerce_coupon');
  commerce_coupon_install_helper();
}

/**
 * Implements hook_entity_info().
 */
function commerce_coupon_entity_info() {
  $entity_info['commerce_coupon'] = array(
    'label' => t('Commerce Coupon'),
    'plural label' => t('Commerce Coupons'),
    'controller class' => 'CommerceCouponEntityController',
    'metadata controller class' => 'CommerceCouponMetadataController',
    'base table' => 'commerce_coupon',
    'fieldable' => TRUE,
    'entity keys' => array(
      'id' => 'coupon_id',
      'label' => 'code',
      'bundle' => 'type',
    ),
    'module' => 'commerce_coupon',
    'token type' => 'commerce-coupon',
    'permission labels' => array(
      'singular' => t('coupon'),
      'plural' => t('coupons'),
    ),
    'access callback' => 'commerce_entity_access',
    'access arguments' => array(
      'user key' => 'uid',
      'access tag' => 'commerce_coupon_access',
    ),
  );
  return $entity_info;
}

/**
 * Implements hook_entity_info_alter().
 */
function commerce_coupon_entity_info_alter(&$entity_info) {

  // Expose the admin UI for coupon fields.
  foreach (commerce_coupon_get_types() as $type => $info) {
    $entity_info['commerce_coupon']['bundles'][$type] = array(
      'admin' => array(
        'path' => 'admin/commerce/coupons/types/' . strtr($type, '_', '-'),
        'access arguments' => array(
          'administer coupon types',
        ),
      ),
      'label' => $info['label'],
    );
  }
}

/**
 * Gets a list of all coupon types by invoking a hook.
 */
function commerce_coupon_get_types($reset = FALSE) {
  $cache =& drupal_static('commerce_coupon_type_info');
  if (!isset($cache) || $reset) {
    $cache = module_invoke_all('commerce_coupon_type_info');
  }
  return $cache;
}

/**
 * Returns the name of the specified coupon type.
 *
 * It returns all coupon type labels keyed by type if no type is specified.
 *
 * @param string $type
 *   Optional parameter specifying the type whose name to return.
 *
 * @return string|array
 *   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_coupon_type_get_name($type = NULL) {
  $coupon_types = commerce_coupon_get_types();

  // Return a type name if specified and it exists.
  if (!empty($type)) {
    if (isset($coupon_types[$type])) {
      return $coupon_types[$type]['label'];
    }
    else {

      // Return FALSE if it does not exist.
      return FALSE;
    }
  }

  // Otherwise turn the array values into the type name only.
  foreach ($coupon_types as $key => $value) {
    $coupon_types[$key] = $value['label'];
  }
  return $coupon_types;
}

/**
 * Implements hook_commerce_coupon_type_info().
 */
function commerce_coupon_commerce_coupon_type_info() {
  $types['discount_coupon'] = array(
    'label' => t('Discount coupon'),
  );
  return $types;
}

/**
 * Implements hook_menu().
 */
function commerce_coupon_menu() {

  // Find coupon auto-complete
  $items['commerce/coupons/find'] = array(
    'title' => 'Find coupon',
    'page callback' => 'commerce_coupon_find_coupon_autocomplete',
    'type' => MENU_CALLBACK,
    'access arguments' => array(
      'View any commerce_coupon of any type',
    ),
  );

  // Remove coupon from order.
  $items['commerce/coupons/order/remove/%commerce_coupon/%commerce_order'] = array(
    'title' => 'Delete coupon from order',
    'page callback' => 'commerce_coupon_remove_coupon_from_order_callback',
    'page arguments' => array(
      4,
      5,
    ),
    'access arguments' => array(
      'access checkout',
    ),
    'type' => MENU_CALLBACK,
  );

  // Edit & Delete coupon forms.
  $items['admin/commerce/coupons/%commerce_coupon'] = array(
    'title' => 'Edit',
    'page callback' => 'commerce_coupon_coupon_form_wrapper',
    'page arguments' => array(
      3,
      'edit',
    ),
    'access callback' => 'commerce_coupon_access',
    'access arguments' => array(
      'update',
      3,
    ),
    'weight' => 0,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );
  $items['admin/commerce/coupons/%commerce_coupon/edit'] = array(
    'title' => 'Edit',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'access callback' => 'commerce_coupon_access',
    'access arguments' => array(
      'update',
      3,
    ),
  );
  $items['admin/commerce/coupons/%commerce_coupon/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'commerce_coupon_coupon_delete_form_wrapper',
    'page arguments' => array(
      3,
    ),
    'access callback' => 'commerce_coupon_access',
    'access arguments' => array(
      'delete',
      3,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 20,
    'context' => MENU_CONTEXT_INLINE,
  );

  // Coupon types.
  $items['admin/commerce/coupons/types'] = array(
    'title' => 'Coupon types',
    'description' => 'Manage coupon types for your store.',
    'file' => 'includes/commerce_coupon.admin.inc',
    'page callback' => 'commerce_coupon_types_overview_page',
    'access arguments' => array(
      'administer coupon types',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 0,
  );

  // Add coupon.
  $items['admin/commerce/coupons/add'] = array(
    'title' => 'Create Coupon',
    'description' => 'Create a new coupon',
    'page callback' => 'commerce_coupon_add_page',
    'page arguments' => array(
      commerce_coupon_create('discount_coupon'),
    ),
    'weight' => 10,
    'access callback' => 'commerce_coupon_access',
    'access arguments' => array(
      'create',
      commerce_coupon_create('discount_coupon'),
    ),
    'file' => 'includes/commerce_coupon.admin.inc',
  );
  foreach (commerce_coupon_get_types(TRUE) as $type => $coupon_type) {
    $coupon_type['type'] = $type;

    // Convert underscores to hyphens for the menu item argument.
    $type_arg = strtr($type, '_', '-');

    // Edit page.
    $items['admin/commerce/coupons/types/' . $type_arg] = array(
      'title' => $coupon_type['label'],
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'commerce_coupon_type_settings_form',
        $coupon_type,
      ),
      'access arguments' => array(
        'administer commerce_coupon entities',
      ),
      'file' => 'includes/commerce_coupon.admin.inc',
    );
    $items['admin/commerce/coupons/types/' . $type_arg . '/edit'] = array(
      'title' => 'Edit',
      'access arguments' => array(
        'administer commerce_coupon entities',
      ),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    );

    // Add pages.
    $items['admin/commerce/coupons/add/' . $type_arg] = array(
      'title' => 'Create !label',
      'title arguments' => array(
        '!label' => $coupon_type['label'],
      ),
      'description' => isset($coupon_type['description']) ? $coupon_type['description'] : '',
      'page callback' => 'commerce_coupon_coupon_form_wrapper',
      'page arguments' => array(
        commerce_coupon_create($type),
      ),
      'access callback' => 'commerce_coupon_access',
      'access arguments' => array(
        'create',
        commerce_coupon_create($type),
      ),
      'file' => 'includes/commerce_coupon.admin.inc',
    );

    // Edit conditions component - redirects to the normal component url.
    $items['admin/commerce/coupons/types/' . $type_arg . '/conditions'] = array(
      'title' => 'Edit conditions component',
      'description' => 'Add or remove conditions from the component that is evaluated to determine coupon eligibility',
      'access arguments' => array(
        'administer commerce_coupon entities',
      ),
      'page callback' => 'drupal_goto',
      'page arguments' => array(
        'admin/config/workflow/rules/components/manage/' . commerce_coupon_conditions_component_name($type),
      ),
      'type' => MENU_LOCAL_TASK,
      'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
      'weight' => 100,
    );
  }
  return $items;
}

/**
 * Finds the component name for a given coupon type.
 *
 * @param string $type
 *   Coupon bundle name.
 *
 * @return string
 *   The name of the coupon condition component.
 */
function commerce_coupon_conditions_component_name($type) {
  return 'coupon_type_' . $type . '_conditions';
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function commerce_coupon_form_commerce_discount_operation_form_alter(&$form, &$form_state) {
  if ($form_state['op'] == 'delete') {

    // See if there are any coupons referencing this discount.
    $discount_wrapper = entity_metadata_wrapper('commerce_discount', $form_state['commerce_discount']);
    if ($discount_wrapper->coupons
      ->count()) {
      $form['description']['#markup'] = t('This discount cannot be deleted because it has coupons referencing it. Try disabling it instead, or deleting the coupons first.');
      unset($form['actions']['submit']);
    }
  }
}

/**
 * Implements hook_theme().
 */
function commerce_coupon_theme() {
  return array(
    'commerce_coupon_type_admin_overview' => array(
      'variables' => array(
        'coupon_type' => NULL,
      ),
    ),
    'commerce_coupon_manage_discount_coupons' => array(
      'render element' => 'elements',
    ),
    'commerce_coupon_discount_coupons_summary' => array(
      'render element' => 'element',
    ),
    'commerce_coupon_add_list' => array(
      'render element' => 'content',
    ),
  );
}

/**
 * Menu callback: Find a coupon by code autocomplete.
 *
 * @param string $string
 *   String that autocomplete is searching for.
 */
function commerce_coupon_find_coupon_autocomplete($string = '') {
  $return = array();
  $query = new EntityFieldQuery();
  $results = $query
    ->entityCondition('entity_type', 'commerce_coupon')
    ->propertyCondition('type', 'discount_coupon')
    ->propertyCondition('code', $string, 'CONTAINS')
    ->propertyOrderBy('code')
    ->range(0, 3)
    ->execute();
  if (!empty($results['commerce_coupon'])) {
    $coupons = commerce_coupon_load_multiple(array_keys($results['commerce_coupon']));
    foreach ($coupons as $coupon) {
      $return[$coupon->code] = $coupon->code;
    }
  }
  drupal_json_output($return);
}

/**
 * Implements hook_menu_alter().
 */
function commerce_coupon_menu_alter(&$items) {

  // Transform the field UI tabs into contextual links.
  foreach (commerce_coupon_get_types() as $type => $coupon_type) {

    // Convert underscores to hyphens for the menu item argument.
    $type_arg = strtr($type, '_', '-');
    $items['admin/commerce/coupons/types/' . $type_arg . '/fields']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
    $items['admin/commerce/coupons/types/' . $type_arg . '/display']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function commerce_coupon_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/coupons') {
    $item = menu_get_item('admin/commerce/coupons/add');
    if ($item['access']) {
      $data['actions']['output'][] = array(
        '#theme' => 'menu_local_action',
        '#link' => $item,
      );
    }
  }
}

/**
 * Implements hook_permission().
 */
function commerce_coupon_permission() {
  $permissions = array();

  // Add "redeem" permissions.
  $permissions['redeem any coupon'] = array(
    'title' => t('Redeem any coupon'),
  );
  $permissions['administer coupon types'] = array(
    'title' => t('Administer coupon types'),
  );
  foreach (commerce_coupon_get_types() as $type => $info) {
    $permissions['redeem coupons of type ' . $type] = array(
      'title' => t('Redeem any %type coupon', array(
        '%type' => $info['label'],
      )),
    );
  }
  $permissions += commerce_entity_access_permissions('commerce_coupon');
  return $permissions;
}

/**
 * Implements hook_query_TAG_alter().
 */
function commerce_coupon_query_commerce_coupon_access_alter(QueryAlterableInterface $query) {
  $tables =& $query
    ->getTables();

  // Find the commerce_coupon table.
  foreach ($tables as $table) {
    if ($table['table'] == 'commerce_coupon') {
      $coupon_alias = $table['alias'];
      commerce_coupon_apply_access_query_substitute($query, $coupon_alias);
      break;
    }
  }
}

/**
 * Call implementations of hook_commerce_coupon_access_query_substitute.
 *
 * If no implementing modules found, apply the standard
 * commerce_entity_access_query_alter function.
 *
 * @param QueryAlterableInterface $query
 *   The query object that we are operating on.
 * @param string $coupon_alias
 *   A table alias that represents the commerce_coupon table.
 */
function commerce_coupon_apply_access_query_substitute(QueryAlterableInterface $query, $coupon_alias) {

  // Only modules that need to change the query itself (see Commerce Coupon
  // User) should implement this. Simple changes to just the query conditions
  // can be implemented using
  // hook_commerce_entity_access_condition_commerce_coupon_alter().
  $modules = module_implements('commerce_coupon_access_query_substitute');
  if ($modules) {
    foreach ($modules as $module) {

      // Each implementing module gets the original query as its argument to
      // avoid having to worry about undoing changes that previous implementers
      // add in. The last implementing module is the "winner".
      $new_query = module_invoke($module, 'commerce_coupon_access_query_substitute', $query, $coupon_alias);
    }
    if (isset($new_query)) {
      $query = $new_query;
    }
  }
  else {
    commerce_entity_access_query_alter($query, 'commerce_coupon', $coupon_alias);
  }
}

/**
 * Checks coupon access for various operations.
 *
 * @param string $op
 *   The operation being performed. One of 'view', 'update', 'create', 'redeem'
 *   or 'delete'.
 * @param object $coupon
 *   Optionally a coupon to check access for. If nothing is given access
 *   permissions for all coupons are returned.
 * @param object $account
 *   The user to check for. Leave it to NULL to check for the current user.
 *
 * @return bool
 *   Whether or not we are granting access.
 */
function commerce_coupon_access($op, $coupon = NULL, $account = NULL) {

  // If there are modules that implement the coupon access hook:
  $hook = 'commerce_coupon_access_' . $op;
  $modules = module_implements($hook);
  if ($modules) {
    foreach ($modules as $module) {

      // Last module wins.
      $access = module_invoke($module, $hook, $coupon, $account);
    }
    return $access;
  }

  // Otherwise route to defaults.
  switch ($op) {
    case 'view':
    case 'update':
    case 'create':
    case 'delete':
      return commerce_entity_access($op, $coupon, $account, 'commerce_coupon');
    case 'redeem':

      // If the user can redeem all coupons of this type:
      return user_access('redeem coupons of type ' . $coupon->type) || user_access('redeem any coupon');
  }
}

/**
 * Page wrapper: coupon add/edit form.
 *
 * @param object $coupon
 *   A coupon object.
 *
 * @return array
 *   Drupal form array.
 */
function commerce_coupon_coupon_form_wrapper($coupon) {
  module_load_include('inc', 'commerce_coupon', 'includes/commerce_coupon.admin');
  return drupal_get_form('commerce_coupon_form', $coupon);
}

/**
 * Page wrapper: coupon delete confirmation form.
 *
 * @param object $coupon
 *   A coupon object.
 *
 * @return array
 *   Drupal form array.
 */
function commerce_coupon_coupon_delete_form_wrapper($coupon) {
  module_load_include('inc', 'commerce_coupon', 'includes/commerce_coupon.admin');
  return drupal_get_form('commerce_coupon_delete_form', $coupon);
}

/**
 * Page callback: remove coupon from order.
 *
 * @param object $coupon
 *   A coupon object.
 * @param object $order
 *   The order that the coupon belongs to.
 *
 * @return int|void
 *   Access denied bit or void.
 */
function commerce_coupon_remove_coupon_from_order_callback($coupon, $order) {
  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'commerce_coupon_remove_checkout:' . $coupon->coupon_id . ':' . $order->order_id) || !commerce_checkout_access($order)) {
    return MENU_ACCESS_DENIED;
  }
  commerce_coupon_remove_coupon_from_order($order, $coupon);
  drupal_set_message(t('Coupon removed from order'));
  drupal_goto();
}

/**
 * Implements hook_commerce_discount_rule_build().
 */
function commerce_coupon_commerce_discount_rule_build($rule, $discount) {
  $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount);

  // Cache the discount's coupon count so that calls to it during checkout don't
  // have to.
  commerce_coupon_cache_coupon_count($discount);

  // Determine whether the discount has coupon references.
  if ($discount_wrapper->coupon_count
    ->value()) {

    // Product level discounts must pass the line item's order.
    $map = array(
      'order_discount' => 'commerce-order',
      'product_discount' => 'commerce-line-item:order',
    );
    if (isset($map[$discount->type])) {
      $rule
        ->condition('commerce_coupon_discount_coupon_codes_exist_on_order', array(
        'commerce_order:select' => $map[$discount->type],
        'commerce_discount' => $discount->name,
      ));

      // If this discount uses the multi strategy we need a different structure.
      // Get all coupons related to this discount and apply the discount action
      // for every coupon.
      if ($discount->type == 'order_discount' && !empty($discount->commerce_coupon_strategy) && $discount_wrapper->commerce_coupon_strategy
        ->value() == 'multi') {

        // Fetch the existing actions to wrap them later into a loop.
        $existing_actions = $rule
          ->actions();
        $rule
          ->action('commerce_coupon_discount_get_coupons_list', array(
          'commerce_order:select' => $map[$discount->type],
          'commerce_discount' => $discount->name,
          'weight' => -1,
        ));
        $coupon_loop = rules_loop(array(
          'list:select' => 'discount_coupons',
          'item:var' => 'coupon',
          'item:label' => 'Coupon',
        ));
        $coupon_loop
          ->setParent($rule);
        foreach ($existing_actions as $existing_action) {
          $existing_action
            ->setParent($coupon_loop);
        }
      }
    }
  }
}

/**
 * Evaluate the inline conditions found on a coupon.
 *
 * If there are no inline conditions, this function returns TRUE. The idea is to
 * build and evaluate a condition set dynamically based on the inline conditions
 * for a given coupon.
 *
 * @param \EntityDrupalWrapper $coupon_wrapper
 *   A wrapped coupon entity.
 *
 * @return array|bool|void
 *   Whether or not the conditions evaluate as true, or true if no evaluation is
 *   needed.
 */
function commerce_coupon_evaluate_inline_conditions(EntityDrupalWrapper $coupon_wrapper) {
  $wrapper_properties = $coupon_wrapper
    ->getPropertyInfo();
  if (!module_exists('inline_conditions') || empty($wrapper_properties['commerce_coupon_conditions']) || !$coupon_wrapper->commerce_coupon_conditions
    ->value()) {
    return TRUE;
  }
  $execute = FALSE;

  // Add an input parameter for a Coupon entity.
  $component_parameter = array(
    'commerce_coupon' => array(
      'type' => 'commerce_coupon',
      'label' => t('Coupon'),
      'description' => t('The coupon entity whose inline conditions are being evaluated.'),
    ),
  );
  $rule = rules_and($component_parameter);

  // Build the inline conditions for this phase.
  foreach ($coupon_wrapper->commerce_coupon_conditions
    ->value() as $value) {

    // Get the condition info.
    $condition = inline_conditions_get_info($value['condition_name']);

    // Check for a valid condition. Also we are running only continuous inline
    // conditions, make sure the current condition is continuous.
    if (!$condition || empty($value['condition_settings'])) {
      continue;
    }
    $execute = TRUE;

    // Give a chance to others module to alter the current field value.
    drupal_alter('inline_conditions_build', $value);

    // Build the condition parameters and add the condition.
    $parameters = array(
      'entity:select' => $condition['entity type'],
    ) + $value['condition_settings'];
    $rule
      ->condition($value['condition_name'], $parameters);
  }

  // Run this method to manually to prepare the variables as Rules normally
  // would.
  $rule
    ->processSettings(TRUE);

  // Evaluate the condition set if necessary. Feed the coupon wrapper in as the
  // first argument. All other arguments are part of the condition settings.
  return $execute ? $rule
    ->executeByArgs(array(
    $coupon_wrapper
      ->value(),
  )) : TRUE;
}

/**
 * Determine whether a coupon code grants a particular discount.
 *
 * @param string $code
 *   A coupon code.
 * @param int $discount_id
 *   A discount id.
 *
 * @return bool
 *   Whether or not the code is related to the provided discount
 */
function commerce_coupon_code_grants_discount($code, $discount_id) {
  $query = new EntityFieldQuery();
  $results = $query
    ->entityCondition('entity_type', 'commerce_coupon')
    ->propertyCondition('type', 'discount_coupon')
    ->propertyCondition('code', $code)
    ->propertyCondition('status', TRUE)
    ->fieldCondition('commerce_discount_reference', 'target_id', $discount_id)
    ->execute();
  return !empty($results['commerce_coupon']);
}

/**
 * Entity metadata getter: coupon properties on discounts.
 */
function commerce_coupon_get_discount_properties($discount, $options, $name) {
  switch ($name) {
    case 'coupons':
      if (!empty($discount->discount_id)) {

        // Load coupons that reference this discount.
        $query = new EntityFieldQuery();
        $results = $query
          ->entityCondition('entity_type', 'commerce_coupon')
          ->fieldCondition('commerce_discount_reference', 'target_id', $discount->discount_id)
          ->execute();
        if (isset($results['commerce_coupon'])) {
          return array_keys($results['commerce_coupon']);
        }
      }
      return array();
    case 'coupon_count':
      if (!empty($discount->discount_id)) {

        // First, look for a value in the cache.
        $cid = 'discount_coupon_count_' . $discount->discount_id;
        $cache = cache_get($cid);
        if (!empty($cache)) {
          return (int) $cache->data;
        }

        // If nothing has been cached, run the query. NOTE: when EFQ tries to
        // put an equivalent query together, it ends up super slow, so we use
        // db_select and joins here. It still is not particularly quick.
        $query = db_select('commerce_coupon', 'c')
          ->fields('c', array(
          'coupon_id',
        ));
        $query
          ->join('field_data_commerce_discount_reference', 'd', 'c.coupon_id=d.entity_id');
        $query
          ->condition('d.commerce_discount_reference_target_id', $discount->discount_id)
          ->condition('d.deleted', 0)
          ->condition('d.entity_type', 'commerce_coupon');
        $value = (int) $query
          ->countQuery()
          ->execute()
          ->fetchField();

        // Set cache to speed up next call.
        cache_set($cid, $value);
        return $value;
      }
      return 0;
  }
}

/**
 * Implements hook_commerce_checkout_pane_info().
 */
function commerce_coupon_commerce_checkout_pane_info() {
  $panes['commerce_coupon'] = array(
    'title' => t('Coupons'),
    'file' => 'includes/commerce_coupon.checkout_pane.inc',
    'base' => 'commerce_coupon_pane',
    'page' => 'checkout',
    'fieldset' => TRUE,
    'locked' => FALSE,
  );
  return $panes;
}

/**
 * Generates a new unique coupon code.
 *
 * @param string $type
 *   Coupon type.
 * @param int $length
 *   Optional The length of the new code.
 *
 * @return string
 *   The new coupon code.
 */
function commerce_coupon_generate_coupon_code($type, $length = NULL) {

  // We define the possible characters. No 'l','1', 'i' to prevent
  // reconisation problems.
  $characters = array(
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
    'G',
    'H',
    'J',
    'K',
    'L',
    'M',
    'N',
    'P',
    'Q',
    'R',
    'S',
    'T',
    'U',
    'V',
    'W',
    'X',
    'Y',
    'Z',
    'a',
    'b',
    'c',
    'd',
    'e',
    'f',
    'g',
    'h',
    'j',
    'k',
    'm',
    'n',
    'o',
    'p',
    'q',
    'r',
    's',
    't',
    'u',
    'v',
    'w',
    'x',
    'y',
    'z',
    '2',
    '3',
    '4',
    '5',
    '6',
    '7',
    '8',
    '9',
  );
  $number_of_characters = count($characters);
  $code_found = FALSE;
  if ($length == NULL) {
    $length = variable_get('commerce_coupon_' . $type . '_default_code_size', 8);
  }

  // We need to check if the produced coupon code is already in the
  // database. We try this for 1000 iteration. If we then not found a
  // a code, we stop. There must be an error in this case.
  for ($i = 0; $i < 1000 && $code_found == FALSE; $i++) {
    $code = '';

    // Create the code per character.
    for ($c = 0; $c < $length; $c++) {
      $rand_index = mt_rand(0, $number_of_characters - 1);
      $code .= $characters[$rand_index];
    }

    // Check in the database if the generated code is already defined.
    if (commerce_coupon_code_exists($code) == FALSE) {
      $code_found = TRUE;
    }
  }
  return $code;
}

/**
 * Apply a coupon to an order and return success or failure.
 *
 * @param string $code
 *   Coupon code to reedem.
 * @param object $order
 *   The order on which the coupon should be redeemed.
 * @param string $error
 *   Passed by reference. Any resulting error messages will change this
 *   variable.
 *
 * @return void|object
 *   Void if the coupon was not successfully redeemed; otherwise the coupon
 *   entity.
 */
function commerce_coupon_redeem_coupon_code($code, $order, &$error) {
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  if (!commerce_coupon_order_allows_coupons($order)) {
    return;
  }

  // Trim trailing spaces.
  $code = trim($code);
  if (!$code) {
    $error = t('Please enter a code.');
    return;
  }
  $coupon = commerce_coupon_load_by_code($code);
  if ($coupon && $coupon->status) {

    // The same coupon cannot be added twice.
    foreach ($order_wrapper->commerce_coupons as $order_coupon_wrapper) {
      if ($order_coupon_wrapper
        ->value() && $order_coupon_wrapper->coupon_id
        ->value() == $coupon->coupon_id) {
        $error = t('The coupon you have entered has already been applied to your order');
        return;
      }
    }
    $coupon_wrapper = entity_metadata_wrapper('commerce_coupon', $coupon);
    if (empty($error) && commerce_coupon_evaluate_conditions($coupon_wrapper, $order_wrapper)) {

      // Add the coupon to the order.
      $order_wrapper->commerce_coupons[] = $coupon_wrapper
        ->value();
      commerce_order_save($order_wrapper
        ->value());
      return $coupon;
    }
    else {

      // If the coupon was not added, check the static error variable - one of
      // the coupon conditions may have set something specific.
      $error = drupal_static('commerce_coupon_error_' . strtolower($code));
      if (!$error) {

        // If no condition has specified an error message, set a default one.
        $error = t('Unable to redeem coupon.');
      }
    }
  }
  else {
    $error = t('Your coupon code is not valid.');
  }
}

/**
 * Determine whether an order has the commerce coupon field.
 *
 * @param $order
 * @return mixed
 */
function commerce_coupon_order_allows_coupons($order) {
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  return $order_wrapper
    ->__isset('commerce_coupons');
}

/**
 * Run the condition component for a coupon.
 *
 * @param EntityDrupalWrapper $coupon_wrapper
 *   A wrapped coupon entity.
 * @param EntityDrupalWrapper $order_wrapper
 *   The wrapped order entity that the coupon belongs to.
 * @param array $data
 *   An array of extra information to pass into the coupon conditions.
 *
 * @return bool
 *   Whether or not the conditions pass.
 */
function commerce_coupon_evaluate_conditions(EntityDrupalWrapper $coupon_wrapper, EntityDrupalWrapper $order_wrapper, $data = array()) {
  $outcome = rules_invoke_component('coupon_type_' . $coupon_wrapper->type
    ->value() . '_conditions', $coupon_wrapper, $order_wrapper, $data);
  $context = array(
    'coupon' => $coupon_wrapper,
    'order' => $order_wrapper,
    'data' => $data,
  );

  // Allow other modules to alter the outcome.
  drupal_alter('commerce_coupon_condition_outcome', $outcome, $context);
  return $outcome;
}

/**
 * Loads a coupon by its coupon code.
 *
 * @param string $code
 *   A coupon code.
 * @param null|string $type
 *   A coupon type.
 *
 * @return object|void
 *   A coupon entity.
 */
function commerce_coupon_load_by_code($code, $type = NULL) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_coupon')
    ->propertyCondition('code', $code);
  if ($type) {
    $query
      ->propertyCondition('type', $type);
  }
  $result = $query
    ->execute();
  if (empty($result)) {
    return;
  }
  $commerce_coupon = reset($result['commerce_coupon']);
  return commerce_coupon_load($commerce_coupon->coupon_id);
}

/**
 * Load multiple function (should be added to commerce discounts).
 *
 * @param array $discount_ids
 *   A list of discount ids.
 * @param array $conditions
 *   An array of conditions @see entity_load().
 * @param bool $reset
 *   Whether or not to reset the entity cache.
 *
 * @return array
 *   A list of discount entities.
 */
function commerce_coupon_discount_load_multiple(array $discount_ids, $conditions = array(), $reset = FALSE) {
  if (empty($discount_ids) && empty($conditions)) {
    return array();
  }
  return entity_load('commerce_discount', $discount_ids, $conditions, $reset);
}

/**
 * Create a stub coupon.
 *
 * @param string $type
 *   A coupon type name.
 *
 * @return object|null
 *   A coupon object.
 */
function commerce_coupon_create($type) {
  return entity_get_controller('commerce_coupon')
    ->create(array(
    'type' => $type,
  ));
}

/**
 * Fetch a coupon entity.
 *
 * @param int $coupon_id
 *   A coupon id.
 * @param bool $reset
 *   Whether or not to reset the entity cache.
 *
 * @return object|bool
 *   A fully-loaded $commerce_coupon object or FALSE if it cannot be loaded.
 *
 * @see commerce_coupon_load_multiple()
 */
function commerce_coupon_load($coupon_id, $reset = FALSE) {
  $commerce_coupons = commerce_coupon_load_multiple(array(
    $coupon_id,
  ), array(), $reset);
  return reset($commerce_coupons);
}

/**
 * Save the commerce coupon entity.
 *
 * @param object $coupon
 *   A coupon entity.
 */
function commerce_coupon_save($coupon) {
  return entity_get_controller('commerce_coupon')
    ->save($coupon);
}

/**
 * Deletes a coupon.
 *
 * @param int $coupon_id
 *   Id of the coupon to delete.
 */
function commerce_coupon_delete($coupon_id) {
  return commerce_coupon_delete_multiple(array(
    $coupon_id,
  ));
}

/**
 * Delete multiple coupons.
 *
 * @param array $coupon_ids
 *   An array of coupon IDs.
 */
function commerce_coupon_delete_multiple(array $coupon_ids) {
  return entity_get_controller('commerce_coupon')
    ->delete($coupon_ids);
}

/**
 * Load multiple coupons based on certain conditions.
 *
 * @param array $commerce_coupon_ids
 *   An array of coupon IDs.
 * @param array $conditions
 *   An array of conditions to match against the {commerce_coupon} table.
 * @param bool $reset
 *   A boolean indicating that the internal cache should be reset.
 *
 * @return array
 *   An array of coupon objects, indexed by coupon id.
 *
 * @see entity_load()
 * @see commerce_coupon_load()
 */
function commerce_coupon_load_multiple($commerce_coupon_ids = array(), $conditions = array(), $reset = FALSE) {
  if (empty($commerce_coupon_ids) && empty($conditions)) {
    return array();
  }
  return entity_load('commerce_coupon', $commerce_coupon_ids, $conditions, $reset);
}

/**
 * Checks if a given coupon code exists.
 *
 * @param string $code
 *   Coupon code to check.
 *
 * @return bool
 *   Returns TRUE if the coupon exists, otherwise return FALSE.
 */
function commerce_coupon_code_exists($code) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_coupon')
    ->propertyCondition('code', $code);
  $result = $query
    ->execute();
  return !empty($result);
}

/**
 * Returns the number of uses for this coupon.
 *
 * @param int $coupon_id
 *   Coupon id to check.
 *
 * @return int
 *   Returns number of uses of the coupon in all orders.
 */
function commerce_coupon_get_number_of_uses($coupon_id) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_order')
    ->fieldCondition('commerce_coupons', 'target_id', $coupon_id, '=');
  return $query
    ->count()
    ->execute();
}

/**
 * Implements hook_views_api().
 */
function commerce_coupon_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'commerce_coupon') . '/includes/views',
  );
}

/**
 * Implements hook_commerce_coupon_discount_savings_value_alter().
 */
function commerce_coupon_commerce_coupon_discount_value_display_alter(&$text, $discount, $order) {

  // Common variables.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount);
  $offer_wrapper = $discount_wrapper->commerce_discount_offer;
  $offer_type = $offer_wrapper->type
    ->value();
  $discount_type = $discount_wrapper->type
    ->value();

  // Savings value implementations on behalf of commerce discount.
  switch ($discount_type) {
    case 'order_discount':
      switch ($offer_type) {
        case 'fixed_amount':
          $price = $offer_wrapper->commerce_fixed_amount
            ->value();
          $text = commerce_currency_format($price['amount'], $price['currency_code']) . ' ' . t('off order');
          break;
        case 'percentage':
          $text = t('@percentage% off order', array(
            '@percentage' => $offer_wrapper->commerce_percentage
              ->value(),
          ));
          break;
        case 'free_shipping':
          $text = t('Free shipping');
          break;
        case 'free_products':
          foreach ($offer_wrapper->commerce_free_products
            ->value() as $product) {
            $product_names[] = check_plain($product->title);
          }
          if (isset($product_names)) {
            $product_text = implode(', ', $product_names);
          }
          $text = t('Free product(s):') . ' ' . $product_text;
          break;
      }
      break;
    case 'product_discount':

      // By default, product discounts will show the value of the discount as
      // well as what product it is for, as defined in its inline conditions.
      $product_text = t('all products');
      $conditions = $discount_wrapper->inline_conditions
        ->value();

      // Get a list of product ids on the order.
      $order_product_ids = array();
      foreach ($order_wrapper->commerce_line_items as $commerce_line_item) {
        if (isset($commerce_line_item->commerce_product)) {
          $product_id = $commerce_line_item->commerce_product
            ->getIdentifier();
          $order_product_ids[$product_id] = $product_id;
        }
      }
      foreach ($conditions as $condition) {
        if ($condition['condition_name'] == 'commerce_product_contains_products') {
          foreach ($condition['condition_settings']['sku'] as $data) {
            if (isset($order_product_ids[$data['product_id']])) {
              $product = commerce_product_load($data['product_id']);
              if ($product && empty($condition['condition_negate'])) {
                $product_names[] = check_plain($product->title);
              }
            }
          }
          if (isset($product_names)) {
            $product_text = '<em>' . implode('</em>, <em>', $product_names) . '</em>';
          }
          break;
        }
      }
      switch ($offer_type) {
        case 'fixed_amount':
          $price = $offer_wrapper->commerce_fixed_amount
            ->value();
          $offer_text = commerce_currency_format($price['amount'], $price['currency_code']);
          break;
        case 'percentage':
          $offer_text = t('@percentage%', array(
            '@percentage' => $offer_wrapper->commerce_percentage
              ->value(),
          ));
          break;
      }
      $text = $offer_text . ' ' . t('off') . ' ' . $product_text;
      break;
    default:

      // By default, just use the label.
      $text = check_plain($discount->label);
      break;
  }
}

/**
 * Load common discounts that are present in a coupon (by code) and an order.
 *
 * @param string $code
 *   A coupon code.
 * @param object $order
 *   An order entity.
 *
 * @return array
 *   A list of discount entities.
 */
function commerce_coupon_order_coupon_code_discounts($code, $order) {
  $coupon = commerce_coupon_load_by_code($code);
  $coupon_wrapper = entity_metadata_wrapper('commerce_coupon', $coupon);
  if ($coupon->type == 'discount_coupon' && $coupon_wrapper->commerce_discount_reference
    ->value()) {

    // Line item level discounts are not stored on the order, so we have to dig
    // through the price components.
    $order_discount_ids = commerce_coupon_order_discount_ids($order);
    $discount_ids = array_intersect($coupon_wrapper->commerce_discount_reference
      ->raw(), $order_discount_ids);
    return commerce_coupon_discount_load_multiple($discount_ids);
  }
  return array();
}

/**
 * Determine whether an order has a particular discount.
 *
 * @param object $order
 *   An order entity.
 * @param object $discount
 *   A discount entity.
 *
 * @return bool
 *   Whether or not the discount was found.
 */
function commerce_coupon_order_has_discount($order, $discount) {
  $order_discount_ids = commerce_coupon_order_discount_ids($order);
  return in_array($discount->discount_id, $order_discount_ids);
}

/**
 * Determine whether an order has a particular coupon code.
 *
 * @param string $code
 *   A coupon code.
 * @param object $order
 *   An order entity.
 *
 * @return bool
 *   Whether or not the code is present.
 */
function commerce_coupon_order_has_coupon_code($code, $order) {
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  if (!commerce_coupon_order_allows_coupons($order)) {
    return;
  }
  foreach ($order_wrapper->commerce_coupons as $delta => $coupon_wrapper) {
    if (strcasecmp($coupon_wrapper->code
      ->value(), $code) == 0) {
      return TRUE;
    }
  }
}

/**
 * Load all discounts connected to an order.
 *
 * This includes line item level discounts traced through line item unit price
 * components.
 *
 * @param object $order
 *   An order entity.
 *
 * @return array
 *   A list of discount ids found on the order.
 */
function commerce_coupon_order_discount_ids($order) {
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $order_discount_ids = array();
  foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
    if ($line_item_wrapper
      ->value()) {
      $data = $line_item_wrapper->commerce_unit_price->data
        ->value();
      foreach ($data['components'] as $key => $component) {
        if (!empty($component['price']['data']['discount_name'])) {
          $order_discount_name = $component['price']['data']['discount_name'];
          $order_discount_wrapper = entity_metadata_wrapper('commerce_discount', $order_discount_name);

          // Make a list of discounts present via the order's line item price
          // components.
          $order_discount_ids[] = $order_discount_wrapper->discount_id
            ->value();
        }
      }
    }
  }

  // Add the set of discounts directly referenced on the order.
  foreach ($order_wrapper->commerce_discounts
    ->raw() as $discount_id) {
    $order_discount_ids[] = $discount_id;
  }
  $order_discount_ids = array_unique($order_discount_ids);
  return $order_discount_ids;
}

/**
 * Load the discounts associated with a coupon code.
 *
 * @param string $code
 *   Commerce coupon code.
 *
 * @return array
 *   A list of discounts.
 */
function commerce_coupon_load_coupon_code_discounts($code) {
  $discounts = array();
  $coupon = commerce_coupon_load_by_code($code);
  if ($coupon) {

    /** @var \EntityDrupalWrapper $coupon_wrapper */
    $coupon_wrapper = entity_metadata_wrapper('commerce_coupon', $coupon);
    if ($coupon_wrapper
      ->getBundle() == 'discount_coupon') {

      /** @var CommerceDiscount $discount */
      foreach ($coupon_wrapper->commerce_discount_reference
        ->value() as $discount) {
        if ($discount->status) {
          $discounts[] = $discount;
        }
      }
    }
  }
  return $discounts;
}

/**
 * Removes a coupon from an order.
 *
 * @param object $order
 *   Order object to affect in the coupon removal.
 * @param object $coupon
 *   Coupon object to remove.
 * @param bool $save
 *   Whether or not to save the order.
 */
function commerce_coupon_remove_coupon_from_order($order, $coupon, $save = TRUE) {
  if (!commerce_coupon_order_allows_coupons($order)) {
    return;
  }
  $original_order = clone $order;
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

  // Remove the coupons from the order relationship.
  foreach ($order_wrapper->commerce_coupons as $delta => $coupon_wrapper) {
    if ($coupon_wrapper->coupon_id
      ->value() == $coupon->coupon_id) {
      $order_wrapper->commerce_coupons
        ->offsetUnset($delta);
    }
  }
  if ($original_order != $order && $save) {
    commerce_order_save($order);
  }
}

/**
 * Implements hook_module_implements_alter().
 */
function commerce_coupon_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'commerce_cart_order_refresh') {

    // Move our implementation to the end of the list. Its job is to clean up
    // order-coupon references where the coupon's discount no longer exists on
    // the order.
    $group = $implementations['commerce_coupon'];
    unset($implementations['commerce_coupon']);
    $implementations['commerce_coupon'] = $group;
  }
}

/**
 * Implements hook_commerce_checkout_router().
 */
function commerce_coupon_commerce_checkout_router($order) {

  // Rollback transactions if the cancel URL was hit. This must be done before
  // the refresh happens so the checkout_router hook is used.
  if (arg(3) == 'back' && arg(4) == $order->data['payment_redirect_key']) {
    commerce_coupon_rollback_order_transactions($order);
  }
}

/**
 * Rollback coupon-related transactions found within an order.
 *
 * @param object $order
 *   An order entity.
 * @param bool $save
 *   Whether or not to save the order.
 */
function commerce_coupon_rollback_order_transactions($order, $save = FALSE) {
  foreach (module_implements('commerce_coupon_final_checkout_transaction_rollback') as $module) {
    if (!empty($order->data['coupon_transaction_ids'][$module])) {
      foreach ($order->data['coupon_transaction_ids'][$module] as $key => $transaction_id) {

        // Hand off the rollback to each module's hook implementation.
        module_invoke($module, 'commerce_coupon_final_checkout_transaction_rollback', $transaction_id);
        unset($order->data['coupon_transaction_ids'][$module][$key]);
      }
      if ($save) {
        commerce_order_save($order);
      }
    }
  }
}

/**
 * Implements hook_commerce_cart_order_refresh().
 */
function commerce_coupon_commerce_cart_order_refresh($order_wrapper) {
  $order = $order_wrapper
    ->value();
  if (!commerce_coupon_order_allows_coupons($order)) {
    return;
  }
  foreach ($order_wrapper->commerce_coupons
    ->value() as $coupon) {

    // Invalidate coupons that exist on the order without their discount
    // present, meaning an inline condition on the discount was not satisfied.
    // Free shipping discounts often do not apply immediately. For this reason,
    // do not remove coupon codes that exclusively grant free shipping.
    if ($coupon && $coupon->type == 'discount_coupon' && !commerce_coupon_order_coupon_code_discounts($coupon->code, $order) && !_commerce_coupon_free_shipping_single_discount($coupon)) {

      // Remove invalid coupons.
      commerce_coupon_remove_coupon_from_order($order, $coupon, FALSE);
      $error =& drupal_static('commerce_coupon_error_' . strtolower($coupon->code));
      if (!$error) {

        // Set a generic error message unless something has.
        $error = t('Unable to redeem coupon.');
      }
    }
  }
}

/**
 * Determine whether a coupon grants free shipping.
 *
 * @param object $coupon
 *   A coupon entity.
 * @param bool $exclusive
 *   If set, this function will return TRUE only if the caller is requesting
 *   only coupons that exclusively grant free shipping.
 *
 * @return object|bool|void
 *   Either a discount object or FALSE
 */
function _commerce_coupon_free_shipping_single_discount($coupon, $exclusive = TRUE) {
  $coupon_wrapper = entity_metadata_wrapper('commerce_coupon', $coupon);
  if ($coupon->type == 'discount_coupon') {
    foreach ($coupon_wrapper->commerce_discount_reference as $discount_wrapper) {
      if ($discount_wrapper->commerce_discount_offer
        ->value() && $discount_wrapper->commerce_discount_offer->type
        ->value() == 'free_shipping') {
        $discount = $discount_wrapper
          ->value();
      }
      elseif ($discount_wrapper->status
        ->value() && $exclusive) {

        // If the coupon grants any other kind of enabled discount, the purpose
        // of this function is to return FALSE.
        return;
      }
    }
  }
  return isset($discount) ? $discount : FALSE;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function commerce_coupon_form_commerce_discount_form_alter(&$form, &$form_state) {

  // Attach a UI for adding associated coupon entities.
  if (isset($form_state['triggering_element'])) {
    $trigger = end($form_state['triggering_element']['#array_parents']);
  }
  else {
    $trigger = NULL;
  }

  // We do not want to show the management tools if there are more than 100
  // coupons attached to this discount.
  $discount_wrapper = entity_metadata_wrapper('commerce_discount', $form_state['commerce_discount']);
  $coupon_count = isset($form_state['coupons']) ? count($form_state['coupons']) : $discount_wrapper->coupon_count
    ->value();

  // Establish a container.
  $form['coupons'] = array(
    '#type' => 'fieldset',
    '#title' => t('Coupon conditions'),
    '#tree' => TRUE,
    '#prefix' => '<div id="commerce-coupon-discount-coupon-form">',
    '#suffix' => '</div>',
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'commerce_coupon') . '/css/commerce_coupon.css',
      ),
    ),
  );
  if ($coupon_count > 100) {

    // If there are too many coupons on this discount, do not try to manage
    // them all on the discount page.
    $form['coupons']['count_message'] = array(
      '#markup' => t('Currently there are @n coupons attached to this discount. Manage them through the !ui_link', array(
        '@n' => $coupon_count,
        '!ui_link' => l(t('coupons UI'), 'admin/commerce/coupons'),
      )),
    );
    return;
  }
  $form['#validate'][] = 'commerce_coupon_form_attach_coupons_validate';
  $form['#submit'][] = 'commerce_coupon_form_attach_coupons';

  // Keep track of coupons that have been added if there is a reasonably small
  // amount.
  if (!isset($form_state['coupons'])) {
    $form_state['coupons'] = $discount_wrapper->coupons
      ->value();

    // Keep track of these so we know which to get rid of at the end.
    $form_state['original_coupons'] = $form_state['coupons'];
  }
  $coupons =& $form_state['coupons'];

  // Handle certain triggers.
  switch ($trigger) {
    case 'remove_coupon':
      $edit_delta = $form_state['triggering_element']['#delta'];
      unset($coupons[$edit_delta]);
      break;
    case 'edit_coupon':
      $edit_delta = $form_state['triggering_element']['#delta'];

      // Store the index of the coupon that we are editing.
      $form_state['edit_coupon'] = $coupons[$edit_delta];
      $form_state['edit_coupon']->delta = $edit_delta;
      break;
  }

  // Keep track of whether there is a coupon being edited currently.
  if (isset($form_state['edit_coupon'])) {
    $coupon = $form_state['edit_coupon'];
  }

  // Ajax specifications.
  $ajax = array(
    'callback' => 'commerce_coupon_discount_coupon_form_ajax',
    'wrapper' => 'commerce-coupon-discount-coupon-form',
  );

  // Add new coupons.
  $form['coupons']['add'] = array(
    '#type' => 'button',
    '#value' => t('Add new coupon'),
    '#ajax' => $ajax,
    '#limit_validation_errors' => array(
      array(
        'coupons',
        'coupon_form',
      ),
    ),
  );

  // Find/add existing coupon.
  $form['coupons']['add_existing'] = array(
    '#type' => 'button',
    '#value' => t('Add existing coupon'),
    '#ajax' => $ajax,
    '#limit_validation_errors' => array(
      array(
        'coupons',
        'find_coupon_code',
      ),
    ),
  );
  if ($trigger == 'add' || isset($coupon) && !empty($coupon->is_new) && !isset($form_state['edit_coupon']->delta)) {
    if (!isset($coupon)) {
      $coupon = commerce_coupon_create('discount_coupon');
    }
    $form['coupons']['coupon_form'] = array(
      '#type' => 'container',
      '#parents' => array(
        'coupons',
        'coupon_form',
      ),
    );

    // Attach the coupon form.
    commerce_coupon_attach_ajax_coupon_entity_form($form['coupons']['coupon_form'], $form_state, $coupon, $ajax, array(
      array(
        'coupons',
      ),
    ));

    // Add responsive visibility states to the code line.
    $form['coupons']['coupon_form']['code']['#states'] = array(
      'disabled' => array(
        'input[name="coupons[coupon_form][generate]"]' => array(
          'checked' => TRUE,
        ),
      ),
    );
    $form['coupons']['coupon_form']['save_coupon']['#limit_validation_errors'] = array(
      array(
        'coupons',
        'coupon_form',
      ),
    );
    $form_state['edit_coupon'] = $coupon;
  }
  elseif ($trigger == 'add_existing') {
    $form['coupons']['find_coupon_code'] = array(
      '#type' => 'textfield',
      '#title' => t('Add existing coupon code'),
      '#autocomplete_path' => 'commerce/coupons/find',
    );

    // Add existing button.
    $form['coupons']['add_existing_coupon'] = array(
      '#type' => 'button',
      '#value' => t('Add'),
      '#ajax' => $ajax,
      '#limit_validation_errors' => array(
        array(
          'coupons',
          'find_coupon_code',
        ),
      ),
    );

    // Cancel add existing button.
    $form['coupons']['cancel_add_existing'] = array(
      '#type' => 'button',
      '#value' => t('Cancel'),
      '#ajax' => $ajax,
      '#limit_validation_errors' => array(),
    );
  }

  // Attach coupons management table.
  $form['coupons']['manage_coupons'] = array(
    '#theme' => 'commerce_coupon_manage_discount_coupons',
  );
  if (isset($coupons)) {
    foreach ($coupons as $delta => $coupon) {
      $form['coupons']['manage_coupons'][$delta]['#coupon'] = $coupon;
      if (isset($form_state['edit_coupon']->delta) && $delta == $form_state['edit_coupon']->delta) {

        // If this one is selected, attach a coupon form.
        $form['coupons']['manage_coupons'][$delta]['coupon_form'] = array(
          '#type' => 'container',
          '#parents' => array(
            'coupons',
            'manage_coupons',
            $delta,
            'coupon_form',
          ),
        );
        commerce_coupon_attach_ajax_coupon_entity_form($form['coupons']['manage_coupons'][$delta]['coupon_form'], $form_state, $coupon, $ajax, array(
          array(
            'coupons',
            'manage_coupons',
            $delta,
          ),
        ));

        // Add responsive visibility states to the code line.
        $form['coupons']['manage_coupons'][$delta]['coupon_form']['code']['#states'] = array(
          'disabled' => array(
            'input[name="coupons[manage_coupons][' . $delta . '][coupon_form][generate]"]' => array(
              'checked' => TRUE,
            ),
          ),
        );
      }
      else {
        $form['coupons']['manage_coupons'][$delta]['edit_coupon'] = array(
          '#type' => 'button',
          '#ajax' => $ajax,
          '#value' => t('edit'),
          '#limit_validation_errors' => array(),
          '#delta' => $delta,
          '#name' => 'edit-coupon-' . $delta,
        );
        $form['coupons']['manage_coupons'][$delta]['remove_coupon'] = array(
          '#type' => 'button',
          '#ajax' => $ajax,
          '#value' => t('remove'),
          '#limit_validation_errors' => array(),
          '#delta' => $delta,
          '#name' => 'remove-coupon-' . $delta,
        );
      }
    }
  }
  else {

    // This means there are too many coupons for the management widget to
    // handle. Provide a summary.
    $form['coupons']['coupons_summary'] = array(
      '#theme' => 'commerce_coupon_discount_coupons_summary',
      '#count' => $coupon_count,
    );
  }

  // Clear the input if save was successful.
  unset($form_state['values']['coupons']['coupon_form']);
  unset($form_state['input']['coupons']['coupon_form']);
  $form_state['coupons'] = $coupons;
}

/**
 * Form submit callback: save a coupon from the discount UI.
 */
function commerce_coupon_form_attach_coupons(&$form, &$form_state) {

  // By default don't rebuild rules config.
  $rebuild_rules = FALSE;
  if (!empty($form_state['coupons']) && !empty($form_state['commerce_discount']->discount_id)) {

    // Save coupons.
    foreach ($form_state['coupons'] as $coupon) {
      if ($coupon->coupon_id || !commerce_coupon_load_by_code($coupon->code)) {

        // Add a reference to this discount if it is not referenced already.
        $coupon_wrapper = entity_metadata_wrapper('commerce_coupon', $coupon);
        if (!commerce_coupon_coupon_has_discount($coupon_wrapper, $form_state['commerce_discount']->name)) {
          $delta = !$coupon_wrapper->commerce_discount_reference
            ->value() ? 0 : $coupon_wrapper->commerce_discount_reference
            ->count();
          $language = field_language('commerce_coupon', $coupon, 'commerce_discount_reference');
          $coupon->commerce_discount_reference[$language][$delta]['target_id'] = $form_state['commerce_discount']->discount_id;
        }

        // Save coupon.
        commerce_coupon_save($coupon);

        // Force rules rebuild.
        $rebuild_rules = TRUE;
      }
    }
  }

  // Remove coupons.
  if (!empty($form_state['original_coupons'])) {
    foreach ($form_state['original_coupons'] as $original_coupon) {
      if (!commerce_coupon_coupon_list_has_code($original_coupon->code, $form_state['coupons'])) {

        // If the coupon code is no longer attached to this discount and it is
        // attached to no other discounts, delete it completely.
        $remove_coupon = commerce_coupon_load_by_code($original_coupon->code);
        $wrapper = entity_metadata_wrapper('commerce_coupon', $remove_coupon);
        if (!empty($form_state['commerce_discount']->discount_id)) {
          foreach ($wrapper->commerce_discount_reference
            ->value() as $delta => $discount) {
            if ($discount->discount_id == $form_state['commerce_discount']->discount_id) {

              // Break the reference from this coupon to the discount currently
              // being edited in the form. @TODO: why is the wrapper not working
              // here?
              $language = field_language('commerce_coupon', $remove_coupon, 'commerce_discount_reference');
              unset($remove_coupon->commerce_discount_reference[$language][$delta]);
            }
          }
        }

        // If no discounts remain, delete the coupon.
        if (empty($remove_coupon->commerce_discount_reference[$language])) {
          commerce_coupon_delete($original_coupon->coupon_id);
        }
        else {

          // If we are not deleting, save the coupon since we eliminated one or
          // more of its discount references.
          $wrapper
            ->save();
        }

        // Force rules rebuild.
        $rebuild_rules = TRUE;
      }
    }
  }

  // Rebuild the rules again if coupon added or deleted.
  if ($rebuild_rules && !empty($form_state['commerce_discount']->discount_id)) {
    if ($discount = entity_load_single('commerce_discount', $form_state['commerce_discount']->discount_id)) {
      _commerce_discount_rebuild_rules_config(array(
        $discount,
      ));
    }
  }
}

/**
 * Calculate and store the coupon count for a discount.
 *
 * For discounts with a very large number of coupons, this operation can be a
 * bit slow to do dynamically so it is better to cache it.
 *
 * @param object $discount
 *   A discount entity.
 *
 * @return int
 *   The number of coupons related to a particular discount.
 */
function commerce_coupon_cache_coupon_count($discount) {

  // Cache a coupon count for this discount.
  $discount_wrapper = entity_metadata_wrapper('commerce_discount', $discount);
  cache_clear_all('discount_coupon_count_' . $discount->discount_id, 'cache');

  // Fill cache with new value.
  $cache = $discount_wrapper->coupon_count
    ->value();
  return $cache;
}

/**
 * Implements hook_commerce_discount_delete().
 */
function commerce_coupon_commerce_discount_delete($discount) {

  // When we delete a coupon, remove the cached coupon count for that discount.
  cache_clear_all('discount_coupon_count_' . $discount->discount_id, 'cache');
}

/**
 * Determine whether a coupon has a particular discount.
 *
 * @param EntityDrupalWrapper $coupon_wrapper
 *   The coupon to check.
 * @param string $discount_name
 *   The discount name to check for.
 *
 * @return void|bool
 *   Whether or not the coupon references a particular discount.
 */
function commerce_coupon_coupon_has_discount(EntityDrupalWrapper $coupon_wrapper, $discount_name) {
  if ($coupon_wrapper->type
    ->value() == 'discount_coupon') {
    foreach ($coupon_wrapper->commerce_discount_reference
      ->value() as $discount) {
      if ($discount && $discount->name == $discount_name) {
        return TRUE;
      }
    }
  }
}

/**
 * Form validate callback: validate a new coupon from the discount UI.
 */
function commerce_coupon_form_attach_coupons_validate(&$form, &$form_state) {
  $trigger = end($form_state['triggering_element']['#array_parents']);
  if (isset($form_state['edit_coupon'])) {
    $coupon = $form_state['edit_coupon'];
  }

  // Determine whether it is the "add new" form or an edit form.
  if (isset($coupon)) {
    if (isset($coupon->delta)) {
      $coupon_fields_form =& $form['coupons']['manage_coupons'][$coupon->delta]['coupon_form']['commerce_coupon_fields'];
      $coupon_values = $form_state['values']['coupons']['manage_coupons'][$coupon->delta]['coupon_form'];
    }
    else {
      $coupon_fields_form =& $form['coupons']['coupon_form']['commerce_coupon_fields'];
      $coupon_values = $form_state['values']['coupons']['coupon_form'];
    }
  }
  if (isset($coupon_fields_form) && $trigger != 'cancel_coupon' && $trigger != 'add_existing') {

    // Validate fields.
    field_attach_form_validate('commerce_coupon', $coupon, $coupon_fields_form, $form_state);
  }
  switch ($trigger) {
    case 'add_coupon':
    case 'save_coupon':

      // If the form has passed validation, prepare the coupon for submission.
      if (!form_get_errors()) {
        if (!empty($coupon_values['generate'])) {

          // Generate a code.
          $code = commerce_coupon_generate_coupon_code('discount_coupon');

          // Make sure the code is not already staged.
          if (!empty($form_state['coupons'])) {
            foreach ($form_state['coupons'] as $staged_coupon) {
              $coupon_codes[] = $staged_coupon->code;
            }
            $n = 0;
            while (in_array($code, $coupon_codes) && $n < 10) {
              $code = commerce_coupon_generate_coupon_code('discount_coupon');
              $n++;
            }
          }
        }
        else {
          $code = $coupon_values['code'];
        }
        $coupon->code = $code;
        $coupon->status = $coupon_values['status'];
        $coupon->uid = $coupon_values['uid'];
        field_attach_submit('commerce_coupon', $coupon, $coupon_fields_form, $form_state);

        // Add it to the list of coupons that we save.
        if (isset($coupon->delta)) {
          $form_state['coupons'][$coupon->delta] = $coupon;

          // If the coupon is already in the database, we must save it
          // immediately. This is to take care of cases where a user could
          // switch the codes of two coupons. If we allow the database to get
          // out of sync with what is in the form, it becomes prohibitively
          // difficult to enforce uniqueness.
          if (isset($coupon->coupon_id)) {
            commerce_coupon_save($coupon);
          }
        }
        else {
          $form_state['coupons'][] = $coupon;
        }

        // Reset the edit coupon tracker.
        unset($form_state['edit_coupon']);
      }
      break;
    case 'cancel_coupon':
    case 'add_existing':
      unset($form_state['edit_coupon']);
      break;
    case 'add_existing_coupon':
      $code = $form_state['values']['coupons']['find_coupon_code'];
      $coupon = commerce_coupon_load_by_code($code, 'discount_coupon');
      if (!$coupon) {
        form_set_error('coupons][find_coupon_code', t('Please enter a valid coupon code'));
      }
      elseif (commerce_coupon_coupon_list_has_code($coupon->code, $form_state['coupons'])) {
        form_set_error('coupons][find_coupon_code', t('The coupon code you entered has already been added to this discount.'));
      }
      else {
        $form_state['coupons'][] = $coupon;
      }
      break;
  }
}

/**
 * Determine whether or not a list of coupons contains a particular code.
 *
 * @param string $code
 *   A coupon code.
 * @param array $coupons
 *   A list of coupon entities.
 *
 * @return bool
 *   Whether or not a particular code appears the set of coupons.
 */
function commerce_coupon_coupon_list_has_code($code, array $coupons) {
  foreach ($coupons as $coupon) {
    if ($coupon->code == $code) {
      return TRUE;
    }
  }
}

/**
 * Form ajax callback: handle ajax for all discount coupon form operations.
 */
function commerce_coupon_discount_coupon_form_ajax(&$form, &$form_state) {
  return $form['coupons'];
}

/**
 * Attach an ajax coupon entity form to a form or form fragment.
 */
function commerce_coupon_attach_ajax_coupon_entity_form(&$form, &$form_state, $coupon, $ajax, $limit_validation_errors, $show_discounts_field = FALSE) {
  commerce_coupon_attach_coupon_entity_form($form, $form_state, $coupon, $show_discounts_field);
  $form['code']['#element_validate'][] = 'commerce_coupon_code_validate_staged';
  $form['cancel_coupon'] = array(
    '#type' => 'button',
    '#value' => t('Cancel'),
    '#ajax' => $ajax,
    '#limit_validation_errors' => array(),
  );
  $form['save_coupon'] = array(
    '#type' => 'button',
    '#value' => t('Save'),
    '#ajax' => $ajax,
    '#limit_validation_errors' => $limit_validation_errors,
  );
}

/**
 * Form element validate callback: check for existing coupon code in form.
 */
function commerce_coupon_code_validate_staged($element, &$form_state) {
  $code = $element['#value'];
  $coupon_form_values = drupal_array_get_nested_value($form_state['values'], array_slice($element['#array_parents'], 0, -1));
  $generate = $coupon_form_values['generate'];
  if (isset($form_state['coupons']) && !$generate) {
    $element_delta = $element['#array_parents'][2];
    foreach ($form_state['coupons'] as $delta => $coupon) {

      // Make sure that this code isn't used in one of the staged coupons.
      if ($code == $coupon->code && $delta != $element_delta) {
        form_set_error(implode('][', $element['#array_parents']), t('The code that you entered already exists.'));
      }
    }
  }
}

/**
 * Attach a coupon entity form to a form or form fragment.
 *
 * @param array $form
 *   Drupal form array.
 * @param array $form_state
 *   Drupal formstate array.
 * @param object $coupon
 *   Coupon entity.
 * @param bool $show_discounts_field
 *   Whether or not to show the discount reference field.
 */
function commerce_coupon_attach_coupon_entity_form(array &$form, array &$form_state, $coupon, $show_discounts_field = FALSE) {
  global $user;

  // Coupon code.
  $form['code'] = array(
    '#type' => 'textfield',
    '#title' => t('Coupon code'),
    '#default_value' => $coupon->code,
    '#coupon_id' => isset($coupon->coupon_id) ? $coupon->coupon_id : '',
    '#element_validate' => array(
      'commerce_coupon_validate_code',
    ),
    '#weight' => -10,
  );

  // Generate code.
  $form['generate'] = array(
    '#type' => 'checkbox',
    '#title' => t('Generate code'),
    '#description' => t('If you check this, a random code will be generated.'),
    '#default_value' => FALSE,
    '#weight' => -8,
  );

  // Set user id.
  $uid = !empty($coupon->is_new) ? $user->uid : $coupon->uid;
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $uid,
    '#weight' => -7,
  );
  $coupon->uid = $uid;
  $field_parents = !empty($form['#parents']) ? $form['#parents'] : array();
  $field_parents[] = 'commerce_coupon_fields';
  $form['commerce_coupon_fields'] = array(
    '#type' => 'container',
    '#parents' => $field_parents,
    '#weight' => -6,
  );

  // Attach fields.
  field_attach_form('commerce_coupon', $coupon, $form['commerce_coupon_fields'], $form_state);

  // Possibly do not show the discount reference field.
  if (!$show_discounts_field) {
    unset($form['commerce_coupon_fields']['commerce_discount_reference']);
  }

  // Status.
  $form['status'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enabled'),
    '#default_value' => $coupon->status,
    '#weight' => -6,
  );

  // Allow other modules to alter this form whereever it appears.
  drupal_alter('commerce_coupon_coupon_entity_form', $form, $form_state, $coupon);
}

/**
 * Theme callback: build discount coupon management table.
 */
function theme_commerce_coupon_manage_discount_coupons($variables) {
  $elements = $variables['elements'];
  $table['header'] = array(
    t('Code'),
    '',
    '',
  );
  $table['rows'] = array();
  foreach (element_children($elements) as $delta) {
    $element = $elements[$delta];
    if (isset($element['edit_coupon']) && isset($element['remove_coupon'])) {
      $edit_button = $element['edit_coupon'];
      $remove_button = $element['remove_coupon'];
      $edit_button_cell = array(
        'data' => drupal_render($edit_button),
        'class' => array(
          'commerce-coupon-discount-coupon-edit-cell',
        ),
      );
      $remove_button_cell = array(
        'data' => drupal_render($remove_button),
        'class' => array(
          'commerce-coupon-discount-coupon-remove-cell',
        ),
      );
      $row = array(
        check_plain($element['#coupon']->code),
        $edit_button_cell,
        $remove_button_cell,
      );
    }
    else {
      $form = $element['coupon_form'];

      // Single cell row.
      $row = array(
        array(
          'data' => drupal_render($form),
          'colspan' => 3,
        ),
      );
    }
    $table['rows'][] = $row;
  }
  if (!empty($table['rows'])) {
    return theme('table', $table);
  }
}

/**
 * Theme callback: render discount coupons summary.
 */
function commerce_coupon_discount_coupons_summary($variables) {
  $output = '<div class="commerce-coupon-discount-coupons-summary"><strong>';
  $output .= t('Attached coupons');
  $output .= ':</strong>';
  $output .= check_plain($variables['element']['#count']) . '</div>';
  return $output;
}

/**
 * Element validate callback: validate coupon code.
 */
function commerce_coupon_validate_code($element, &$form_state) {
  $path = implode('][', $element['#array_parents']);
  $coupon_form_values = drupal_array_get_nested_value($form_state['values'], array_slice($element['#array_parents'], 0, -1));

  // Ensure code is not empty unless we are generating a random one.
  if (empty($coupon_form_values['generate'])) {
    if (empty($element['#value'])) {
      form_set_error($path, t('You must enter a code.'));
      return;
    }
    $code = trim($element['#value']);

    // Do not allow existing coupon codes, unless it is merely saving itself.
    $coupon = commerce_coupon_load_by_code($code);
    if ($coupon && (empty($element['#coupon_id']) || $element['#coupon_id'] != $coupon->coupon_id)) {
      form_set_error($path, t('The code that you entered already exists.'));
    }
  }

  // Ensure unique code if inserting.
  if (!empty($form_state['edit_coupon']->is_new) && commerce_coupon_load_by_code($element['#value'])) {
    form_set_error($path, t('The code that you entered already exists.'));
  }
}

/**
 * Install inline conditions field. Only called by submodules.
 */
function _commerce_coupon_install_inline_conditions_field() {
  field_info_cache_clear();
  $fields = field_info_fields();
  $instances = field_info_instances();
  if (empty($fields['commerce_coupon_conditions'])) {

    // Create coupon conditions field.
    $field = array(
      'entity_types' => array(
        'commerce_coupon',
      ),
      'field_name' => 'commerce_coupon_conditions',
      'type' => 'inline_conditions',
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
    );
    field_create_field($field);
  }

  // Add coupon conditions field to discount coupons.
  foreach (commerce_coupon_get_types(TRUE) as $machine_name => $type) {
    if (empty($instances['commerce_coupon'][$machine_name]['commerce_coupon_conditions'])) {
      $instance = array(
        'field_name' => 'commerce_coupon_conditions',
        'entity_type' => 'commerce_coupon',
        'settings' => array(
          'entity_type' => 'commerce_coupon',
        ),
        'bundle' => $machine_name,
        'label' => t('Conditions'),
        'widget' => array(
          'type' => 'inline_conditions',
          'weight' => 10,
        ),
      );
      field_create_instance($instance);
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function commerce_coupon_form_commerce_checkout_form_alter(&$form, &$form_state) {

  // Whenever the payment pane loads, check the order for any transactions that
  // might need to be rolled back. This is to handle when giftcard validation
  // was successful but the payment method or some other form element has thrown
  // an error.
  if (isset($form['commerce_payment']) && isset($form['buttons']['continue'])) {
    $order = commerce_order_load($form_state['order']->order_id);

    // Rollback any transactions if necessary. Pass the "save" argument so that
    // if a rollback is done, it saves the order.
    commerce_coupon_rollback_order_transactions($order, TRUE);

    // Add our handler as the first validate handler.
    array_unshift($form['buttons']['continue']['#validate'], 'commerce_coupon_commerce_checkout_form_review_validate');
  }
}

/**
 * Form validate callback: validate and record coupon transactions.
 */
function commerce_coupon_commerce_checkout_form_review_validate(&$form, &$form_state) {

  // If the form was submitted via the continue button and there are no errors:
  if (end($form_state['triggering_element']['#array_parents']) == 'continue' && !form_get_errors()) {
    $order = commerce_order_load($form_state['order']->order_id);
    $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

    // Allow modules to perform a final validation after checkout is done, but
    // before the payment module processes payment.
    foreach (module_implements('commerce_coupon_final_checkout_validate') as $module) {

      // If a module successfully creates a transaction, it must return any
      // new transaction ids. We store them on the order mostly so that if the
      // payment form loads again, we can do a rollback. Modules implementing
      // this hook must do their own error generation using form_set_error.
      $transaction_ids = module_invoke($module, 'commerce_coupon_final_checkout_validate', $form, $form_state, $order_wrapper);
      if (!empty($transaction_ids)) {
        $order->data['coupon_transaction_ids'][$module] = $transaction_ids;
        commerce_order_save($order);
      }
    }
  }
}

/**
 * Implements hook_commerce_coupon_update_parameters().
 */
function commerce_coupon_commerce_coupon_legacy_mapping($coupon) {
  $coupon_wrapper = entity_metadata_wrapper('commerce_coupon', $coupon);

  // Return all of the necessary parameters for converting the coupon into a
  // discount and an offer.
  switch ($coupon->type) {
    case 'commerce_coupon_pct':
      $offer_value = $coupon_wrapper->commerce_coupon_percent_amount
        ->value();
      $offer_value_safe = str_replace('.', '', $offer_value);
      return array(
        'offer value' => $offer_value,
        'offer value safe' => $offer_value_safe,
        'label' => t('@pct percent discount', array(
          '@pct' => $offer_value,
        )),
        'discount name' => 'pct_discount_' . $offer_value_safe,
        'offer field' => 'commerce_percentage',
        'offer type' => 'percentage',
      );
    case 'commerce_coupon_fixed':
      $offer_price = $coupon_wrapper->commerce_coupon_fixed_amount
        ->value();
      $offer_amount = $offer_price['amount'];
      $offer_price_formatted = commerce_currency_format($offer_amount, $offer_price['currency_code']);
      return array(
        'offer value' => $offer_price,
        'offer value safe' => $offer_amount,
        'label' => t('@amount discount', array(
          '@amount' => $offer_price_formatted,
        )),
        'discount name' => 'fixed_discount_' . $offer_amount,
        'offer field' => 'commerce_fixed_amount',
        'offer type' => 'fixed_amount',
      );
  }
}

/**
 * Stub function so that coupon type modules do not crash when they disable.
 */
function commerce_coupon_type_disable() {
}

/**
 * Implements hook_update_dependencies().
 */
function commerce_coupon_update_dependencies() {

  // We need to have this discount update for a 1->2 upgrade to work because it
  // contains a schema change and we are saving discounts.
  $dependencies['commerce_coupon'][7200] = array(
    'commerce_discount' => 7104,
  );
  return $dependencies;
}

/**
 * Return the available handling strategies for coupons.
 */
function commerce_coupon_handling_strategies() {
  return array(
    'once' => t('Once: Discount applied once.'),
    'multi' => t('Multi: Discount is applied for every matching coupon code.'),
  );
}

/**
 * Implements hook_field_widget_form_alter().
 */
function commerce_coupon_field_widget_form_alter(&$element, &$form_state, $context) {

  // Strategy for coupon handling is only valid for order discounts.
  if (!empty($element['#field_name']) && $element['#field_name'] == 'commerce_coupon_strategy') {
    $element['#states'] = array(
      'visible' => array(
        ':input[name="commerce_discount_type"]' => array(
          'value' => 'order_discount',
        ),
      ),
    );
  }
}

Functions

Namesort descending Description
commerce_coupon_access Checks coupon access for various operations.
commerce_coupon_apply_access_query_substitute Call implementations of hook_commerce_coupon_access_query_substitute.
commerce_coupon_attach_ajax_coupon_entity_form Attach an ajax coupon entity form to a form or form fragment.
commerce_coupon_attach_coupon_entity_form Attach a coupon entity form to a form or form fragment.
commerce_coupon_cache_coupon_count Calculate and store the coupon count for a discount.
commerce_coupon_code_exists Checks if a given coupon code exists.
commerce_coupon_code_grants_discount Determine whether a coupon code grants a particular discount.
commerce_coupon_code_validate_staged Form element validate callback: check for existing coupon code in form.
commerce_coupon_commerce_cart_order_refresh Implements hook_commerce_cart_order_refresh().
commerce_coupon_commerce_checkout_form_review_validate Form validate callback: validate and record coupon transactions.
commerce_coupon_commerce_checkout_pane_info Implements hook_commerce_checkout_pane_info().
commerce_coupon_commerce_checkout_router Implements hook_commerce_checkout_router().
commerce_coupon_commerce_coupon_discount_value_display_alter Implements hook_commerce_coupon_discount_savings_value_alter().
commerce_coupon_commerce_coupon_legacy_mapping Implements hook_commerce_coupon_update_parameters().
commerce_coupon_commerce_coupon_type_info Implements hook_commerce_coupon_type_info().
commerce_coupon_commerce_discount_delete Implements hook_commerce_discount_delete().
commerce_coupon_commerce_discount_rule_build Implements hook_commerce_discount_rule_build().
commerce_coupon_conditions_component_name Finds the component name for a given coupon type.
commerce_coupon_coupon_delete_form_wrapper Page wrapper: coupon delete confirmation form.
commerce_coupon_coupon_form_wrapper Page wrapper: coupon add/edit form.
commerce_coupon_coupon_has_discount Determine whether a coupon has a particular discount.
commerce_coupon_coupon_list_has_code Determine whether or not a list of coupons contains a particular code.
commerce_coupon_create Create a stub coupon.
commerce_coupon_delete Deletes a coupon.
commerce_coupon_delete_multiple Delete multiple coupons.
commerce_coupon_discount_coupons_summary Theme callback: render discount coupons summary.
commerce_coupon_discount_coupon_form_ajax Form ajax callback: handle ajax for all discount coupon form operations.
commerce_coupon_discount_load_multiple Load multiple function (should be added to commerce discounts).
commerce_coupon_entity_info Implements hook_entity_info().
commerce_coupon_entity_info_alter Implements hook_entity_info_alter().
commerce_coupon_evaluate_conditions Run the condition component for a coupon.
commerce_coupon_evaluate_inline_conditions Evaluate the inline conditions found on a coupon.
commerce_coupon_field_widget_form_alter Implements hook_field_widget_form_alter().
commerce_coupon_find_coupon_autocomplete Menu callback: Find a coupon by code autocomplete.
commerce_coupon_flush_caches Implements hook_flush_caches().
commerce_coupon_form_attach_coupons Form submit callback: save a coupon from the discount UI.
commerce_coupon_form_attach_coupons_validate Form validate callback: validate a new coupon from the discount UI.
commerce_coupon_form_commerce_checkout_form_alter Implements hook_form_FORM_ID_alter().
commerce_coupon_form_commerce_discount_form_alter Implements hook_form_FORM_ID_alter().
commerce_coupon_form_commerce_discount_operation_form_alter Implements hook_form_FORM_ID_alter().
commerce_coupon_generate_coupon_code Generates a new unique coupon code.
commerce_coupon_get_discount_properties Entity metadata getter: coupon properties on discounts.
commerce_coupon_get_number_of_uses Returns the number of uses for this coupon.
commerce_coupon_get_types Gets a list of all coupon types by invoking a hook.
commerce_coupon_handling_strategies Return the available handling strategies for coupons.
commerce_coupon_load Fetch a coupon entity.
commerce_coupon_load_by_code Loads a coupon by its coupon code.
commerce_coupon_load_coupon_code_discounts Load the discounts associated with a coupon code.
commerce_coupon_load_multiple Load multiple coupons based on certain conditions.
commerce_coupon_menu Implements hook_menu().
commerce_coupon_menu_alter Implements hook_menu_alter().
commerce_coupon_menu_local_tasks_alter Implements hook_menu_local_tasks_alter().
commerce_coupon_module_implements_alter Implements hook_module_implements_alter().
commerce_coupon_order_allows_coupons Determine whether an order has the commerce coupon field.
commerce_coupon_order_coupon_code_discounts Load common discounts that are present in a coupon (by code) and an order.
commerce_coupon_order_discount_ids Load all discounts connected to an order.
commerce_coupon_order_has_coupon_code Determine whether an order has a particular coupon code.
commerce_coupon_order_has_discount Determine whether an order has a particular discount.
commerce_coupon_permission Implements hook_permission().
commerce_coupon_query_commerce_coupon_access_alter Implements hook_query_TAG_alter().
commerce_coupon_redeem_coupon_code Apply a coupon to an order and return success or failure.
commerce_coupon_remove_coupon_from_order Removes a coupon from an order.
commerce_coupon_remove_coupon_from_order_callback Page callback: remove coupon from order.
commerce_coupon_rollback_order_transactions Rollback coupon-related transactions found within an order.
commerce_coupon_save Save the commerce coupon entity.
commerce_coupon_theme Implements hook_theme().
commerce_coupon_type_disable Stub function so that coupon type modules do not crash when they disable.
commerce_coupon_type_get_name Returns the name of the specified coupon type.
commerce_coupon_update_dependencies Implements hook_update_dependencies().
commerce_coupon_validate_code Element validate callback: validate coupon code.
commerce_coupon_views_api Implements hook_views_api().
theme_commerce_coupon_manage_discount_coupons Theme callback: build discount coupon management table.
_commerce_coupon_free_shipping_single_discount Determine whether a coupon grants free shipping.
_commerce_coupon_install_inline_conditions_field Install inline conditions field. Only called by submodules.