You are here

commerce_registration.module in Commerce Registration 7.2

Same filename and directory in other branches
  1. 7.3 commerce_registration.module
  2. 7 commerce_registration.module

Commerce Registration module code.

File

commerce_registration.module
View source
<?php

/**
 * @file
 * Commerce Registration module code.
 */

/**
 * Implements hook_menu().
 */
function commerce_registration_menu() {
  $menu = array();
  $menu['admin/commerce/registrations'] = array(
    'title' => t('Registrations'),
    'description' => t('Manage product registrations that have been purchased.'),
    'access arguments' => array(
      'administer registration',
    ),
    'page callback' => 'commerce_registration_admin_overview',
    'file' => 'includes/commerce_registration.admin.inc',
  );
  $menu['admin/commerce/registrations/complete'] = array(
    'title' => t('Completed Registrations'),
    'description' => t('Manage product registrations that have been purchased.'),
    'access arguments' => array(
      'administer registration',
    ),
    'page callback' => 'commerce_registration_admin_overview',
    'file' => 'includes/commerce_registration.admin.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -1,
  );
  $menu['admin/commerce/registrations/all'] = array(
    'title' => t('All Registrations'),
    'description' => t('Manage product registrations that have been purchased.'),
    'access arguments' => array(
      'administer registration',
    ),
    'page callback' => 'commerce_registration_admin_overview_all',
    'file' => 'includes/commerce_registration.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 0,
  );
  return $menu;
}

/**
 * Implements hook_menu_alter().
 *
 * @param $menu
 */
function commerce_registration_menu_alter(&$menu) {
  foreach (commerce_info_fields('commerce_product_reference') as $field_name => $field) {
    foreach ($field['bundles'] as $type => $bundles) {
      if ($type == "commerce_line_item" || $type == "commerce_product") {
        continue;
      }
      $menu[$type . '/%entity_object/registrations'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => t('Manage Registrations'),
        'page callback' => 'commerce_registration_registration_page',
        'page arguments' => array(
          0,
          1,
        ),
        'access callback' => 'commerce_registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'type' => MENU_LOCAL_TASK,
      );
      $menu[$type . '/%entity_object/registrations/list'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => t('Registrations'),
        'page callback' => 'commerce_registration_registration_page',
        'page arguments' => array(
          0,
          1,
        ),
        'access callback' => 'commerce_registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'type' => MENU_DEFAULT_LOCAL_TASK,
      );
      $menu[$type . '/%entity_object/registrations/settings'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => t('Settings'),
        'page callback' => 'drupal_get_form',
        'page arguments' => array(
          'commerce_registration_node_registrations_settings_form',
          0,
          1,
        ),
        'access callback' => 'commerce_registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'file path' => drupal_get_path('module', 'commerce_registration'),
        'file' => 'includes/commerce_registration.forms.inc',
        'weight' => 9,
        'type' => MENU_LOCAL_TASK,
      );
      $menu[$type . '/%entity_object/registrations/broadcast'] = array(
        'load arguments' => array(
          $type,
        ),
        'title' => t('Email Registrants'),
        'page callback' => 'drupal_get_form',
        'page arguments' => array(
          'commerce_registration_registrations_broadcast_form',
          0,
          1,
        ),
        'access callback' => 'commerce_registration_administer_registrations_access',
        'access arguments' => array(
          0,
          1,
        ),
        'file path' => drupal_get_path('module', 'commerce_registration'),
        'file' => 'includes/commerce_registration.forms.inc',
        'weight' => 10,
        'type' => MENU_LOCAL_TASK,
      );
    }
  }
}

/**
 * Implements hook_theme().
 *
 * Provides the theme function for displaying the registration on the
 * commerce order view page
 */
function commerce_registration_theme($existing, $type, $theme, $path) {
  return array(
    'commerce_registration_order' => array(
      'variables' => array(
        'registrations' => NULL,
      ),
      'file' => '/includes/commerce_registration.theme.inc',
    ),
    'commerce_registration_line_item_registrations' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Access callback.
 * Callback for modifying settings and sending emails for a given product display.
 *
 * @param $type
 * @param $node
 * @return bool
 */
function commerce_registration_administer_registrations_access($type, $node) {

  // Loop through each product on the node, and if it contains a registration
  // type and the user can administer them, then we allow it.
  $fields = field_read_fields(array(
    'type' => 'commerce_product_reference',
  ));
  foreach ($fields as $field) {
    if (isset($node->{$field['field_name']})) {
      foreach ($node->{$field['field_name']} as $product) {
        $product = commerce_product_load($product[0]['product_id']);
        if (registration_get_entity_registration_type('commerce_product', $product) && !commerce_registration_hide_tab($node)) {
          $registration_type = registration_get_entity_registration_type('commerce_product', $product);
          if ($registration_type && !commerce_registration_hide_tab($node)) {
            return user_access('administer registration') || user_access("administer {$registration_type} registration");
          }
        }
      }
    }
  }
  return registration_administer_registrations_access($type, $node);
}

/**
 * Checks if we should hide the Manage Registrations tab for this node.
 */
function commerce_registration_hide_tab($node) {
  $node_settings = db_select('commerce_registration_node_settings', 'ns')
    ->fields('ns', array(
    'settings',
  ))
    ->condition('nid', $node->nid)
    ->execute()
    ->fetchCol();
  $node_settings = isset($node_settings[0]) ? $node_settings[0] : array();
  if (!is_array($node_settings)) {
    $node_settings = unserialize($node_settings);
  }
  if (isset($node_settings['hide_from_display'])) {
    return $node_settings['hide_from_display'];
  }
  return FALSE;
}

/**
 * Registration page callback for all registrations for a given product display.
 */
function commerce_registration_registration_page($type, $node) {
  $out = "";
  $prodids = array();
  $fields = field_read_fields(array(
    'type' => 'commerce_product_reference',
  ));

  // Check we have a product reference field before looping through them.
  if (!empty($fields)) {
    foreach ($fields as $field) {
      if (!empty($node->{$field['field_name']})) {
        if ($field['translatable'] != 0) {
          $lang = $node->language;
        }
        else {
          $lang = LANGUAGE_NONE;
        }
        foreach ($node->{$field['field_name']}[$lang] as $product) {
          if (commerce_registration_product_has_registration_field($product['product_id'])) {
            $prodids[] = (int) $product['product_id'];
          }
        }
      }
    }
  }
  $hasRegField = FALSE;
  $fields = field_read_fields(array(
    'type' => 'registration',
  ));
  foreach ($fields as $field) {
    if (isset($node->{$field['field_name']})) {
      $hasRegField = TRUE;
    }
  }
  if (!empty($prodids)) {
    foreach ($prodids as $product_id) {
      $prod = commerce_product_load($product_id);
      $out .= "<div class='commerce-product commerce-product-{$product_id}'>" . registration_registrations_page('commerce_product', $prod) . "</div><hr>";
    }
  }
  if ($hasRegField) {
    $out .= "<div class='registration'>";
    $out .= registration_registrations_page($type, $node);
    $out .= "</div>";
  }
  return $out;
}

/**
 * Implements hook_commerce_checkout_page_info().
 */
function commerce_registration_commerce_checkout_page_info() {
  $pages = array();
  $pages['registration'] = array(
    'name' => t('Registration'),
    'title' => t('Registration Information'),
    'weight' => -1,
    'status_cart' => TRUE,
    'buttons' => TRUE,
  );
  return $pages;
}

/**
 * Implements hook_commerce_checkout_pane_info().
 */
function commerce_registration_commerce_checkout_pane_info() {
  $panes = array();
  $panes['registration_information'] = array(
    'title' => t('Registration Information'),
    'base' => 'commerce_registration_information',
    'page' => 'registration',
    'enabled' => TRUE,
    'weight' => -50,
    'review' => TRUE,
    'file' => 'includes/commerce_registration.checkout_pane.inc',
  );
  return $panes;
}

/**
 * Implements hook_commerce_line_item_type_info_alter().
 */
function commerce_registration_commerce_line_item_type_info_alter(&$line_item_types) {
  $line_item_types['product']['callbacks']['title'] = 'commerce_registration_product_title';
}

/**
 * Product title line item callback.
 *
 * We alter the title of the default line item to show available slots if the
 * product is register enabled.
 *
 * @param CommerceLineItem $line_item
 *   The line item to get the title for.
 *
 * @return string
 *   Title of the line item product plus available slots, if applicable.
 */
function commerce_registration_product_title($line_item) {
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
  $fieldsettings = field_extra_fields_get_display('commerce_product', $line_item_wrapper->commerce_product
    ->value()->type, 'line_item');
  $showavail = isset($fieldsettings['registration_available']) && isset($fieldsettings['registration_available']['visible']) && $fieldsettings['registration_available']['visible'];
  return commerce_registration_get_product_title($line_item_wrapper->commerce_product
    ->value(), $showavail);
}

/**
 * Returns a product title.
 *
 * @param CommerceProduct $product
 *   Fully formed Commerce Product object.
 *
 * @return string
 *   Product title with remaining capacity if available.
 */
function commerce_registration_get_product_title($product, $showavail = TRUE) {
  $settings = registration_entity_settings('commerce_product', $product->product_id);
  if (!empty($settings) && $settings['status'] == 1 && $showavail) {
    $capacity = (int) $settings['capacity'];
    $filled = (int) registration_event_count('commerce_product', $product->product_id);
    $avail = $capacity - $filled;
    $availtext = $avail > 0 ? format_plural($avail, '1 slot available', '@count slots available') : 'No slots available';
    $capacitytext = $capacity == 0 ? t("Unlimited slots available") : $availtext;
    return t('@title (@capacity)', array(
      '@title' => $product->title,
      '@capacity' => $capacitytext,
    ));
  }
  return $product->title;
}

/**
 * Implements hook_form_alter().
 *
 * Changes the product title in the add to cart form to have the remaining
 * capacity if available.
 */
function commerce_registration_form_alter(&$form, &$form_state, $form_id) {

  // Add slots available text to Add to Cart form.
  if (substr($form_id, 0, 30) == 'commerce_cart_add_to_cart_form') {
    $form['#validate'][] = 'commerce_registration_add_to_cart_validate';
    if (!isset($form['product_id'])) {
      return;
    }
    switch ($form['product_id']['#type']) {
      case "select":
        $count = 0;
        $disabled = 0;
        $total_regs = 0;
        foreach ($form['product_id']['#options'] as $product_id => $title) {
          $product = commerce_product_load($product_id);
          $wrapper = entity_metadata_wrapper('commerce_product', $product);
          $count++;
          if (commerce_registration_product_has_registration_field($product->product_id)) {
            $total_regs++;
            $settings = registration_entity_settings('commerce_product', $product_id);
            if (registration_get_entity_registration_type('commerce_product', $product) && !empty($settings)) {
              if (registration_status('commerce_product', $product_id, TRUE) == 1 && registration_register_page_access('commerce_product', $product)) {
                $fieldsettings = field_extra_fields_get_display('commerce_product', $product->type, 'default');
                $showavail = isset($fieldsettings['registration_available']) && isset($fieldsettings['registration_available']['visible']) && $fieldsettings['registration_available']['visible'];
                $title = $showavail ? commerce_registration_get_product_title($product, $showavail) : $product->title;
                $form['product_id']['#options'][$product_id] = $title;
              }
              else {
                $price = commerce_currency_format($wrapper->commerce_price->amount
                  ->value(), $wrapper->commerce_price->currency_code
                  ->value());
                $selector = $count == count($form['product_id']['#options']) ? "prev" : "next";
                drupal_add_js('(function ($) { $("select[name=\'product_id\'] option[value=\'' . $product_id . '\']").attr(\'disabled\', \'disabled\'); $("select[name=\'product_id\'] option:selected").' . $selector . '(\'option\').attr(\'selected\', \'selected\'); $(window).load(function() { $("select[name=\'product_id\']").change(); }) }) (jQuery);', 'inline');
                $form['unavailable']['product_unavailable_' . $product_id] = array(
                  '#markup' => '<em>' . t('@product (@price)', array(
                    '@product' => $product->title,
                    '@price' => $price,
                  )) . '</em>' . t(' is currently unavailable.'),
                );
                $disabled++;
              }
            }
          }
        }
        if ($disabled === $count && $total_regs == $count) {
          $form['product_id']['#type'] = 'hidden';
          unset($form['submit']);
          $form['unavailable'] = array(
            '#markup' => t('There are no products available for registration at this time.'),
          );
        }
        break;
      case "hidden":
        $product = commerce_product_load($form['product_id']['#value']);
        $settings = registration_entity_settings('commerce_product', $form['product_id']['#value']);
        if (commerce_registration_product_has_registration_field($product->product_id) && !empty($settings) && registration_status('commerce_product', $form['product_id']['#value'], TRUE) && registration_register_page_access('commerce_product', $product)) {
          $fieldsettings = field_extra_fields_get_display('commerce_product', $product->type, 'default');
          $showavail = isset($fieldsettings['registration_available']) && isset($fieldsettings['registration_available']['visible']) && $fieldsettings['registration_available']['visible'];
          $submit = $form['submit'];
          unset($form['submit']);
          $capacity = (int) $settings['capacity'];
          $avail = $capacity - registration_event_count('commerce_product', $product->product_id);
          $availtext = $avail > 0 ? format_plural($avail, '1 slot available', '@count slots available') : t('No slots available');
          $capacity = $settings['capacity'] == 0 ? t("Unlimited slots available") : $availtext;
          $form['capacity'] = array(
            '#type' => 'markup',
            '#markup' => '<em>' . $capacity . '</em>',
          );
          if (!$showavail) {
            unset($form['capacity']);
          }
          if (registration_has_room('commerce_product', $product->product_id)) {
            $form['submit'] = $submit;
          }
          else {
            $form['not_available'] = array(
              '#type' => 'markup',
              '#markup' => '<p><strong>' . t('This item is currently unavailable.') . '</strong></p>',
            );
          }
        }
        break;
    }
  }
  elseif (substr($form_id, 0, 32) == 'commerce_product_ui_product_form') {

    // Add settings to Commerce Product Add/Edit Form.
    $product = $form_state['commerce_product'];
    if (isset($product->product_id)) {
      $settings = registration_entity_settings('commerce_product', $product->product_id);
      if (commerce_registration_product_has_registration_field($product->product_id)) {
        if (!function_exists("registration_entity_settings_form_validate")) {
          form_load_include($form_state, 'inc', 'registration', 'includes/registration.forms');
        }
        if (!function_exists("commerce_registration_registration_settings_form_validate")) {
          form_load_include($form_state, 'inc', 'commerce_registration', 'includes/commerce_registration.forms');
        }

        // Add validation and submission handlers to the form.
        $form['#validate'][] = "commerce_registration_registration_settings_form_validate";
        $form['actions']['submit']['#submit'][] = "commerce_registration_registration_settings_form_submit";
        $collapsed = empty($form['field_registration'][LANGUAGE_NONE]['0']['registration_type']['#default_value']['registration_type']) ? TRUE : FALSE;
        $form['registration_settings'] = array(
          '#title' => t('Registration Settings'),
          '#type' => "fieldset",
          '#collapsible' => TRUE,
          '#collapsed' => $collapsed,
          '#weight' => 45,
          '#tree' => TRUE,
          '#states' => array(
            'collapsed' => array(
              ':input[name="field_registration[und][0][registration_type]"]' => array(
                'value' => '',
              ),
            ),
          ),
        );

        // Grab the registration settings form for this entity.
        $addform = registration_entity_settings_form(array(), $form_state, $settings, "commerce_product", $product);

        // Remove the save button for this form since we have one already.
        unset($addform['save']);

        // We rename the registration settings form's status field to be reg_status
        // so our validation and submission doesn't overwrite the product's status.
        $addform['reg_status'] = $addform['status'];
        $addform['reg_status']['#weight'] = -20;
        unset($addform['status']);

        // Add parents to each registration setting so we can get the values.
        foreach ($addform as $key => $array) {
          $addform[$key]['#parents'] = array(
            'registration_settings',
            $key,
          );
        }

        // Add the registration settings form to the base form.
        $form['registration_settings'] += $addform;
        $form['registration_settings']['reminder']['#parents'] = array(
          'registration_settings',
          'reminder',
        );
        $form['registration_settings']['reminder']['reminder_settings']['#states']['visible'] = array(
          ':input[name="registration_settings[send_reminder]"]' => array(
            'checked' => TRUE,
          ),
        );
        $form['registration_settings']['settings']['hide_from_display'] = array(
          '#type' => 'checkbox',
          '#title' => t('Hide Manage Registrations tab from product display nodes'),
          '#default_value' => isset($settings['settings']['hide_from_display']) ? $settings['settings']['hide_from_display'] : 0,
        );
      }
    }
  }
  elseif (substr($form_id, -9) == 'node_form') {
    $node = NULL;
    if (isset($form_state['node']) && !empty($form_state['node'])) {
      $node = $form_state['node'];
    }
    $fields = field_read_fields(array(
      'type' => 'commerce_product_reference',
    ));
    $has_product_reference = FALSE;
    foreach ($fields as $field) {
      if (isset($node->{$field['field_name']}) && !isset($node->clone_from_original_nid)) {
        $has_product_reference = TRUE;
        break;
      }
    }
    if ($has_product_reference) {
      $node_settings = db_select('commerce_registration_node_settings', 'ns')
        ->fields('ns', array(
        'settings',
      ))
        ->condition('nid', $node->nid)
        ->execute()
        ->fetchCol();
      $node_settings = isset($node_settings[0]) ? $node_settings[0] : array();
      if (!is_array($node_settings)) {
        $node_settings = unserialize($node_settings);
      }
      $form['commerce_registration'] = array(
        '#type' => 'fieldset',
        '#title' => t('Commerce Registration settings'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#group' => 'additional_settings',
        '#tree' => TRUE,
      );
      $form['commerce_registration']['hide_from_display'] = array(
        '#type' => 'checkbox',
        '#title' => t('Hide Manage Registrations tab on this node'),
        '#default_value' => isset($node_settings['hide_from_display']) ? $node_settings['hide_from_display'] : 0,
      );
      $form['#submit'][] = 'commerce_registration_node_settings_form_submit';
    }
  }
}

/**
 * Submit handler for node settings.
 */
function commerce_registration_node_settings_form_submit($form, $form_state) {
  $settings = serialize($form_state['values']['commerce_registration']);
  db_merge('commerce_registration_node_settings')
    ->key(array(
    'nid' => $form_state['node']->nid,
  ))
    ->fields(array(
    'settings' => $settings,
  ))
    ->execute();
}

/**
 * Custom validation handler for add to cart form.
 * Checks if the product being added is available for registration before
 * adding it to the cart. If it is not available, it is not added and a message
 * is shown to the user.
 */
function commerce_registration_add_to_cart_validate($form, &$form_state) {
  global $user;
  $existing_registrations = NULL;
  $product_id = $form_state['values']['product_id'];
  $product = commerce_product_load($product_id);
  if (commerce_registration_product_has_registration_field($product_id)) {

    // Let the user know if they have an existing registration.
    // Check if there are some active states before running this query
    // or it will error out
    $registration_active_states = registration_get_active_states();
    if (!empty($registration_active_states)) {
      $query = new EntityFieldQuery();
      $existing_registrations = $query
        ->entityCondition('entity_type', 'registration')
        ->propertyCondition('entity_id', $product_id)
        ->propertyCondition('user_uid', $user->uid)
        ->propertyCondition('state', $registration_active_states)
        ->range(0, 1)
        ->execute();
    }
    else {
      watchdog('commerce_registration', t('Unable to lookup existing registrations for SKU: !sku because there are no registration active states set.', array(
        '!sku' => $product->sku,
      )));
    }
    if (!empty($existing_registrations)) {
      $message = 'You have already registered for %product.';
      $message_variables = array(
        '%product' => $product->title,
      );

      // If the user has access to view their own registrations, include a link
      // to the registration detail page.
      $registration = registration_load(reset($existing_registrations['registration'])->registration_id);
      if (registration_access('view', $registration)) {
        $message .= ' You can <a href="!uri">view your registration</a>.';
        $uri = $registration
          ->uri();
        $message_variables['!uri'] = url($uri['path']);
      }
      drupal_set_message(t($message, $message_variables));
    }

    // Check that registrations are open and the user has access.
    if (!registration_status('commerce_product', $product_id, TRUE) || !registration_register_page_access('commerce_product', $product)) {
      form_set_error('product_id', t('This product is not available for registration at this time.'));
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Implements hook_entity_property_info_alter().
 */
function commerce_registration_entity_property_info_alter(&$info) {
  $info['registration']['properties']['order_id'] = array(
    'label' => t('Order ID'),
    'type' => 'integer',
    'schema field' => 'order_id',
    'setter callback' => 'entity_property_verbatim_set',
    'description' => t('The order ID associated with this registration.'),
  );
}

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

/**
 * Implements hook_field_extra_fields().
 */
function commerce_registration_field_extra_fields() {
  $items = array();

  // Add registration availability display option to each commerce product type.
  foreach (field_info_instances('commerce_product') as $product_bundle_name => $product_fields) {
    foreach ($product_fields as $product_field_name => $product_field) {
      if ($product_field['widget']['module'] == 'registration') {
        $items['commerce_product'][$product_bundle_name]['display']['registration_available'] = array(
          'label' => t('Registration Availability'),
          'description' => t('The Registration availability text next to the product.'),
          'weight' => -1,
        );
      }
    }
  }

  // Add registration section to commerce_order form.
  $items['commerce_order']['commerce_order']['form']['commerce_registration'] = array(
    'label' => t('Registrations'),
    'weight' => t('Section for managing the product registrations attached to the order.'),
  );
  return $items;
}

/**
 * Implements hook_field_extra_fields_alter().
 */
function commerce_registration_field_extra_fields_alter(&$info) {

  // Loop through the product reference fields.
  foreach (commerce_info_fields('commerce_product_reference') as $field_name => $field) {
    foreach ($field['bundles'] as $entity_type => $bundles) {
      if ($entity_type == 'commerce_line_item' || $entity_type == 'commerce_product') {

        // Skip line items and products, don't want to remove these fields.
        continue;
      }
      foreach ($bundles as $bundle_name) {
        $product_fields = commerce_product_field_extra_fields();
        if (empty($product_fields['commerce_product'])) {

          // Skip if there are no commerce product extra fields.
          continue;
        }

        // Loop through each field on the product, and if it's a Registration,
        // hide it.
        foreach ($product_fields['commerce_product'] as $key => $value) {
          foreach ($value['display'] as $product_extra_field_name => $product_extra_field) {
            if ($product_extra_field['label'] == t('Registration')) {
              $tempfield =& $info[$entity_type][$bundle_name]['display']['product:' . $product_extra_field_name]['display'];
              foreach ($tempfield as $display => $settings) {
                $tempfield[$display]['visible'] = FALSE;
              }
            }
          }
        }

        // Loop through all field instances on products, and if each field is a
        // registration, hide it.
        foreach (field_info_instances('commerce_product') as $product_bundle_name => $product_fields) {
          foreach ($product_fields as $product_field_name => $product_field) {
            if ($product_field['label'] == t('Registration')) {
              $info[$entity_type][$bundle_name]['display']['product:' . $product_field_name]['configurable'] = FALSE;
              $info[$entity_type][$bundle_name]['display']['product:' . $product_field_name]['visible'] = FALSE;
              $info[$entity_type][$bundle_name]['display']['product:' . $product_field_name]['weight'] = 0;
            }
          }
        }
      }
    }
  }
}

/**
 * Implements hook_entity_info_alter().
 */
function commerce_registration_entity_info_alter(&$info) {
  $info['registration']['view modes']['review_pane'] = array(
    'label' => t('Commerce Checkout Review Pane'),
    'custom settings' => TRUE,
  );
}

/**
 * Implements hook_commerce_checkout_complete().
 */
function commerce_registration_commerce_checkout_complete($order) {
  if (!isset($order->data['register_entities'])) {
    return;
  }
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
    if (!in_array($line_item_wrapper->type
      ->value(), commerce_product_line_item_types())) {
      continue;
    }
    $id = $line_item_wrapper->commerce_product->product_id
      ->value();
    $line_item_id = $line_item_wrapper->line_item_id
      ->value();
    $product = $line_item_wrapper->commerce_product;
    if (!isset($order->data['register_entities'][$line_item_id . 'prod-' . $product->sku
      ->value()])) {

      // If we have no registration entities for this product in our order's
      // data array, we skip to the next.
      continue;
    }
    $quantity = (int) $line_item_wrapper->quantity
      ->value();
    for ($i = 0; $i < $quantity; $i++) {
      if (!isset($order->data['register_entities'][$line_item_id . 'prod-' . $product->sku
        ->value()][$i])) {

        // If this registration doesn't exist in our data array, skip to the
        // next entry.
        continue;
      }
      $entity = $order->data['register_entities'][$line_item_id . 'prod-' . $product->sku
        ->value()][$i];
      registration_save($entity);
    }
  }
}

/**
 * Checks if a product has a registration field attached.
 *
 * @param int $product_id The product id.
 *
 * @return boolean
 *   Whether a product has a registration field attached.
 */
function commerce_registration_product_has_registration_field($product_id) {
  $product = commerce_product_load($product_id);
  $fields = field_read_fields(array(
    'type' => 'registration',
  ));
  foreach ($fields as $field) {
    if (isset($product->{$field['field_name']}) && !empty($product->{$field['field_name']}[LANGUAGE_NONE]['0']['registration_type'])) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Implements hook_entity_view_alter().
 */
function commerce_registration_entity_view_alter(&$build, $type) {
  if ($type == "commerce_order") {

    // If we're viewing an order, add registrations to the order if available.
    $regs = db_select('registration', 'r')
      ->fields('r', array(
      'registration_id',
    ))
      ->condition('order_id', (int) $build['#entity']->order_id)
      ->execute();
    $build['commerce_registration'] = array(
      '#title' => t('Registrations'),
    );
    $variables['registrations'] = array();
    foreach ($regs as $row) {
      $variables['registrations'][] = registration_load($row->registration_id);
    }
    if (!empty($variables['registrations'])) {
      $build['commerce_registration'][0]['#markup'] = theme('commerce_registration_order', $variables);
    }
  }
  else {
    if ($type == "registration") {

      // Add the Commerce Order to the registration view.
      $vars = array(
        '!num' => $build['#entity']->order_id,
      );
      $build['commerce_registration_order'][0]['#markup'] = l(t('Order !num', $vars), 'admin/commerce/orders/' . $build['#entity']->order_id);
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters the commerce_order_ui_form so registrations can affect the quantity
 * of line items, select how registrations affect the quantity and to add a
 * a section defined by commerce_registration_field_extra_fields() for
 * managing registrations associated with each registration enabled line item.
 */
function commerce_registration_form_commerce_order_ui_order_form_alter(&$form, &$form_state, $form_id) {
  drupal_add_css(drupal_get_path('module', 'commerce_registration') . '/theme/commerce_registration.admin.css');

  // If the user is editing an order, load a fresh copy, but don't mess with the
  // form_state version because other modules like commerce_line_item depend on
  // staged changes they have made to it until it is saved.
  if ($form_state['commerce_order']->order_id) {
    $order = commerce_order_load($form_state['commerce_order']->order_id);
  }
  else {
    $order = $form_state['commerce_order'];
  }
  $line_item_registrations = array();
  $sync = FALSE;
  $submit = FALSE;

  // Build an array of line item IDs from this field's values.
  if (isset($form['commerce_line_items']) && isset($form['commerce_line_items']['#language']) && isset($form['commerce_line_items'][$form['commerce_line_items']['#language']]['line_items'])) {
    $is_odd = TRUE;
    foreach ($form['commerce_line_items'][$form['commerce_line_items']['#language']]['line_items'] as $line_item_id => &$line_item_form) {
      $line_item = commerce_line_item_load($line_item_id);
      if (isset($line_item->commerce_product) && ($line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item))) {
        $product_id = (int) $line_item_wrapper->commerce_product->product_id
          ->value();
        if (commerce_registration_product_has_registration_field($product_id)) {
          $most_recent = $line_item_id;

          // Build a table for each registration enabled line item.
          $prodkey = $line_item_id . 'prod-' . $line_item_wrapper->commerce_product->sku
            ->value();
          $line_item_title = commerce_line_item_title($line_item);
          $title_var_ary = array(
            '%line_item_title' => $line_item_title,
            '@line_item_id' => $line_item_id,
          );
          $odd_even = $is_odd ? 'odd' : 'even';
          $is_odd = !$is_odd;
          $first = !$line_item_registrations;
          $line_item_registrations[$line_item_id] = array(
            '#type' => 'fieldset',
            '#collapsible' => FALSE,
            '#title' => t('Registration information for %line_item_title - Line item #@line_item_id.', $title_var_ary),
            '#element_validate' => array(
              'commerce_registration_line_item_registrations_validate',
            ),
            '#prefix' => '<div id="li-reg-' . $line_item_id . '" class="commerce-registration-line-item-registrations">',
            '#suffix' => '</div>',
            '#attributes' => array(
              'class' => array(
                $odd_even,
              ),
            ),
          );

          // For aesthetics.
          if ($first) {
            $line_item_registrations[$most_recent]['#attributes']['class'][] = 'first';
          }

          // Add a checkbox for toggling the ability to sync line item quantity
          // with the number of registrations.
          $sync = isset($line_item->data['sync_registrations']) ? (bool) $line_item->data['sync_registrations'] : TRUE;
          $line_item_registrations[$line_item_id]['sync_registrations'] = array(
            // Lets explicitly set the id so we know we can set focus back to
            // it after an ajax call.
            '#id' => 'edit-commerce-registration-line-item-registrations-' . $line_item_id . '-sync-registrations',
            '#title' => t('Sync line item quantity'),
            '#description' => t("When checked, the line item associated with this table of registrations' quantity will be kept in sync with the number of people registered. Uncheck this box if you would like to be able to update the quantity manually. For example to put the order back into checkout state and add additional registrations."),
            '#type' => 'checkbox',
            '#default_value' => $sync,
            '#ajax' => array(
              'callback' => 'commerce_registration_line_item_registrations_refresh',
            ),
            '#limit_validation_errors' => array(
              array(
                'commerce_registration',
                'line_item_registrations',
                $line_item_id,
                'sync_registrations',
              ),
            ),
            '#executes_submit_callback' => TRUE,
            '#submit' => array(
              'commerce_registration_line_item_registrations_sync',
            ),
          );

          // Custom theme element for displaying a table of registrations and
          // actions available for each.
          $line_item_registrations[$line_item_id]['registrations'] = array(
            '#title' => t('Registrations for %line_item.', array(
              '%line_item' => $line_item_title,
            )),
            '#theme' => 'commerce_registration_line_item_registrations',
            '#empty' => t('No registrations associated with this line item yet.'),
            // Pass along the info we need for rendering the registrations.
            '#line_item' => $line_item,
            '#prodkey' => $prodkey,
            '#order' => $order,
          );
          if (isset($order->data) && isset($order->data['register_entities'][$prodkey])) {
            foreach ($order->data['register_entities'][$prodkey] as $delta => $registration) {
              $registration_id = $registration->registration_id;

              // Track the delta from the order->data for later processing.
              $line_item_registrations[$line_item_id]['registrations'][$registration_id]['delta'] = array(
                '#type' => 'value',
                '#value' => $delta,
              );

              // Assign a remove button for deleting a specific registration.
              $line_item_registrations[$line_item_id]['registrations'][$registration_id]['registration_remove'] = array(
                '#type' => 'checkbox',
                '#default_value' => FALSE,
                '#limit_validation_errors' => array(
                  array(
                    'commerce_registration',
                    'line_item_registrations',
                    $line_item_id,
                    'registrations',
                    $registration_id,
                    'registration_remove',
                  ),
                ),
                // TODO: AJAXify the remove checkboxes, but it will require an
                // intermidiate confirmation step so users don't accidentally
                // remove registrations. Consider something like the
                // https://github.com/gdkraus/accessible-modal-dialog modal.

                //'#ajax' => array(

                //  'callback' => 'commerce_registration_line_item_registrations_refresh',

                //),
                '#access' => entity_access('delete', 'registration', $registration),
                '#executes_submit_callback' => TRUE,
                '#submit' => array(
                  'commerce_registration_line_item_registration_delete',
                  'commerce_registration_line_item_registrations_sync',
                ),
              );
            }
          }
          $entity_type = 'commerce_product';

          // Build a parents array for this element's values in the form.
          $line_item_registrations[$line_item_id]['registration_control'] = array(
            '#type' => 'fieldset',
            '#tree' => TRUE,
            '#prefix' => '<div id="reg-new-' . $line_item_id . '">',
            '#suffix' => '</div>',
            '#attached' => array(
              'css' => array(),
            ),
            '#attributes' => array(
              'class' => array(
                'commerce-registration-line-item-registrations-controls',
              ),
            ),
          );
          if (registration_status($entity_type, $product_id)) {

            // If the the form has been instructed to add a new registration for
            // this line item...
            if (!empty($form_state['line_item_registration_add']) && isset($form_state['line_item_registration_add'][$line_item_id])) {

              // Get the langcode of the order.
              $parent_langcode = entity_language('commerce_order', $order);
              $registration_form = array(
                '#type' => 'container',
                '#op' => 'add',
                // Used by Field API and controller methods to find the relevant
                // values in $form_state.
                '#parents' => array(
                  'commerce_registration',
                  'line_item_registrations',
                  $line_item_id,
                  'registration_control',
                  'form',
                ),
                '#parent_language' => $parent_langcode,
              );
              $registration_form = registration_form($registration_form, $form_state, $form_state['line_item_registration_add'][$line_item_id]);

              // We may end up with more then one line item that is adding a
              // new registration so save it in a unique form_state location.
              unset($form_state['registration']);

              // Hijack the submit and cancel elements from the registration
              // form.
              unset($registration_form['actions']);

              // Add the actions.
              $registration_form['actions'] = array(
                '#type' => 'container',
                '#weight' => 100,
              );
              $registration_form['actions']['registration_control_submit'] = array(
                '#type' => 'submit',
                '#value' => t('Save registraton'),
                '#name' => 'reg-add-submit-' . $line_item_id,
                '#limit_validation_errors' => array(
                  $registration_form['#parents'],
                ),
                '#ajax' => array(
                  'callback' => 'commerce_registration_line_item_registrations_refresh',
                ),
                '#submit' => array(
                  'commerce_registration_line_item_control_save',
                  'commerce_registration_line_item_control_reset',
                  'commerce_registration_line_item_registrations_sync',
                ),
              );
              $registration_form['actions']['registration_control_cancel'] = array(
                '#type' => 'submit',
                '#value' => t('Cancel'),
                '#name' => 'reg-add-cancel-' . $line_item_id,
                '#limit_validation_errors' => array(),
                '#ajax' => array(
                  'callback' => 'commerce_registration_line_item_registrations_refresh',
                ),
                '#submit' => array(
                  'commerce_registration_line_item_control_reset',
                ),
              );
              $line_item_registrations[$line_item_id]['registration_control']['form'] = $registration_form;

              // Flag submit so that we remember to add our submit callbacks
              // to the order as well.
              $submit = TRUE;
            }
            else {

              // Add a button for adding a new registration.
              $line_item_registrations[$line_item_id]['registration_control']['registration_control_add'] = array(
                '#type' => 'submit',
                '#value' => t('Add a registration'),
                '#name' => 'reg-add-' . $line_item_id,
                '#ajax' => array(
                  'callback' => 'commerce_registration_line_item_registrations_refresh',
                ),
                '#limit_validation_errors' => array(),
                '#submit' => array(),
              );
            }
          }
          else {
            $line_item_registrations[$line_item_id]['registration_control']['not_open'] = array(
              '#markup' => '<p>' . t('It is not possible to add registrations at this time. Check the registration settings and spaces available for this product.') . '</p>',
            );
          }

          // Make some alterations to the line items table for line items that
          // contain products that are commerce_registration enabled. Includes
          // some accessibility features to link the line item quantity with
          // its associated registrations.
          $line_item_form['quantity']['#suffix'] = '<div><span id="qty-desc-' . $line_item_id . '"><a href="#line-item-reg-' . $line_item_id . '" class="element-invisible element-focusable">' . t('Click here to manage the registrations associated with this line item instead of changing the quantity directly.') . '</a></span></div>';
          $line_item_form['quantity']['#attributes']['aria-describedby'] = 'qty-desc-' . $line_item_id;

          // Keep the line item quantity in sync with the registrations by
          // making the quantity read only.
          if (isset($order->data) && !empty($order->data['register_entities'][$prodkey]) && $line_item->quantity && $sync) {
            $line_item_form['quantity']['#attributes']['readonly'] = '';
          }
        }
      }
    }
  }
  if ($line_item_registrations) {

    // For aesthetics.
    if ($most_recent) {
      $line_item_registrations[$most_recent]['#attributes']['class'][] = 'last';
    }
    $form['commerce_registration'] = array(
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#title' => t('Registrations'),
      '#collapsed' => FALSE,
      '#tree' => TRUE,
      '#prefix' => '<div id="commerce-line-item_registrations"><div aria-live="assertive"><div id="commerce-registration-messages"></div></div>',
      '#suffix' => '</div>',
      'line_item_registrations' => $line_item_registrations,
    );
  }

  // Add submit handlers to the order form as well in case the form is submitted
  // in a way other then by clicking our 'Submit registration' button.
  $submit_callbacks = array(
    'commerce_registration_line_item_registration_delete',
    'commerce_registration_line_item_control_save',
    'commerce_registration_line_item_control_reset',
    'commerce_registration_line_item_registrations_sync',
  );
  $form['#submit'] = array_merge($form['#submit'], $submit_callbacks);
  $form['actions']['submit']['#submit'] = array_merge($form['actions']['submit']['#submit'], $submit_callbacks);
}

/**
 * Validation callback for line item registration elements on the order edit ui.
 */
function commerce_registration_line_item_registrations_validate($element, &$form_state, $form) {

  // If the user is editing an order, load a fresh copy, but don't mess with the
  // form_state version because other modules like commerce_line_item depend on
  // staged changes they have made to it until it is saved.
  if ($form_state['commerce_order']->order_id) {
    $order = commerce_order_load($form_state['commerce_order']->order_id);
  }
  else {
    $order = $form_state['commerce_order'];
  }
  $prodkey = $element['registrations']['#prodkey'];
  $line_item = $element['registrations']['#line_item'];
  $sync = $element['sync_registrations']['#value'];

  // If the the form has been instructed to add a new registration for this
  // line item then validate the registration form.
  if (!empty($form_state['line_item_registration_add']) && isset($form_state['line_item_registration_add'][$line_item->line_item_id])) {

    // Use the existing registration module form validation function.
    $form_state['registration'] = $form_state['line_item_registration_add'][$line_item->line_item_id];
    registration_form_validate($element['registration_control']['form'], $form_state);
    unset($form_state['registration']);
  }
  else {
    if ($sync && (!isset($order->data) || empty($order->data['register_entities'][$prodkey]))) {
      $line_item_title = commerce_line_item_title($line_item);
      form_set_error(implode('][', $element['sync_registrations']['#array_parents']), t('Currently the line item quantity for @line_item_title cannot be synchronized as there are no registrations associated with the line item yet.', array(
        '@line_item_title' => $line_item_title,
      )));
    }
  }

  // Set up a new registration if the AJAXified "Add registration" button was
  // pressed. No need for a submit function since nothing needs to be validated
  // and this can only be done over AJAX really.
  if (!empty($form_state['triggering_element'])) {
    $trigger_parents = array_reverse($form_state['triggering_element']['#array_parents']);
    $triggering_element = $trigger_parents[0];
    if ($triggering_element == 'registration_control_add' && isset($trigger_parents[2]) && (int) $trigger_parents[2] && $trigger_parents[2] == $line_item->line_item_id) {
      $entity_type = 'commerce_product';
      $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
      $product_id = (int) $line_item_wrapper->commerce_product->product_id
        ->value();
      if (registration_status($entity_type, $product_id)) {
        $product = commerce_product_load($product_id);
        $registration_type = registration_get_entity_registration_type($entity_type, $product);
        $registration = entity_get_controller('registration')
          ->create(array(
          'entity_type' => $entity_type,
          'entity_id' => $product_id,
          'type' => $registration_type,
        ));
        $form_state['line_item_registration_add'][$line_item->line_item_id] = $registration;
      }
      else {

        // We don't use form_set_error() here since we limit the validation for
        // the add registration button and the triggering element will not be
        // there after rebuild. So just go ahead and display the error message.
        drupal_set_message(t('It is not possible to add registrations at this time. Check the registration settings and spaces available for this product.'), 'error');
      }
      $form_state['rebuild'] = TRUE;
    }
  }
}

/**
 * Submit callback for line item registration remove checkboxes.
 */
function commerce_registration_line_item_registration_delete($form, &$form_state) {
  if (isset($form['commerce_registration']['line_item_registrations'])) {
    $registrations_to_delete = array();
    $order = NULL;
    $update_order = FALSE;

    // Did we trigger this with an ajax callback?
    $triggered = FALSE;
    if (!empty($form_state['triggering_element'])) {
      $trigger_parents = array_reverse($form_state['triggering_element']['#array_parents']);
      $triggered = (bool) ($trigger_parents[0] === 'registration_remove');
    }

    // Loop through the registration enabled line items and submit any related
    // registration forms.
    foreach (element_children($form['commerce_registration']['line_item_registrations']) as $line_item_id) {
      if (isset($form['commerce_registration']['line_item_registrations'][$line_item_id]['registrations'])) {
        $line_item_registrations = $form['commerce_registration']['line_item_registrations'][$line_item_id]['registrations'];
        $prodkey = $line_item_registrations['#prodkey'];
        $deltas = array();

        // Loop through the registrations for this element/line item.
        foreach (element_children($line_item_registrations) as $registration_id) {

          // Mark the registrations that have been selected for deletion...
          if ($line_item_registrations[$registration_id]['registration_remove']['#value']) {

            // Load the order if we haven't already. No need to do it unless
            // we need to.
            if (!$order) {

              // If the user is editing an order, load a fresh copy.
              if ($form_state['commerce_order']->order_id) {

                // We need to load the order uncached since a cached version may
                // include unsaved field data that may be invalid and cause
                // issues upon saving.
                if ($loaded_orders = entity_load('commerce_order', array(
                  $form_state['commerce_order']->order_id,
                ), array(), TRUE)) {
                  $form_state['commerce_order'] = reset($loaded_orders);
                }
              }

              // Merge changes into the order object in the form state so it is
              // accessible by field handlers.
              $order = $form_state['commerce_order'];
            }
            $delta = $line_item_registrations[$registration_id]['delta']['#value'];
            if (isset($order->data['register_entities'][$prodkey][$delta]) && ($registration = $order->data['register_entities'][$prodkey][$delta])) {
              if ($registration_id == $registration->registration_id) {
                $registrations_to_delete[] = $registration_id;
                $deltas[] = $delta;
              }
            }
          }
        }
        if ($deltas) {

          // Update the registration information in $order->data.
          $order->data['register_entities'][$prodkey] = array_values(array_diff_key($order->data['register_entities'][$prodkey], array_flip($deltas)));
          $update_order = TRUE;

          // No need to rebuild unless we triggered removal via AJAX.
          if ($triggered) {
            $form_state['rebuild'] = TRUE;
          }
        }
      }
    }

    // Delete all the registrations at once.
    if ($registrations_to_delete) {
      registration_delete_multiple($registrations_to_delete);
    }

    // Update an existing order.
    if ($update_order && $order && $order->order_id) {
      try {
        commerce_order_save($order);
      } catch (Exception $e) {
        watchdog('commerce_registration', 'Error when trying to save an order (@order_id) after deleting registration(s).', array(
          '@order_id' => $order->order_id,
        ), WATCHDOG_ERROR);
      }
    }

    // We want to rebuild if this is an ajax submit.
    if ($update_order && $triggered) {
      $form_state['rebuild'] = TRUE;
    }
  }
}

/**
 * Submit callback for an embedded registration form within the order ui.
 */
function commerce_registration_line_item_control_save($form, &$form_state) {
  if (isset($form['commerce_registration']['line_item_registrations'])) {
    $order = NULL;
    $update_order = FALSE;

    // Did we trigger this with an ajax callback?
    $triggered = FALSE;
    if (!empty($form_state['triggering_element'])) {
      $trigger_parents = array_reverse($form_state['triggering_element']['#array_parents']);
      if ($triggered = (bool) ($trigger_parents[0] === 'registration_control_submit')) {
        $triggering_line_item_id = $trigger_parents[4];
      }
    }

    // Loop through the registration enabled line items and submit any related
    // registration forms.
    foreach (element_children($form['commerce_registration']['line_item_registrations']) as $line_item_id) {
      if (!$triggered || $triggering_line_item_id == $line_item_id) {
        if (isset($form['commerce_registration']['line_item_registrations'][$line_item_id]['registration_control']['form']) && isset($form_state['line_item_registration_add'][$line_item_id])) {

          // Load the order if we haven't already. No need to do it unless
          // we need to.
          if (!$order) {

            // If the user is editing an order, load a fresh copy.
            if ($form_state['commerce_order']->order_id) {

              // We need to load the order uncached since a cached version may
              // include unsaved field data that may be invalid and cause issues
              // upon saving.
              if ($loaded_orders = entity_load('commerce_order', array(
                $form_state['commerce_order']->order_id,
              ), array(), TRUE)) {
                $form_state['commerce_order'] = reset($loaded_orders);
              }
            }

            // Merge changes into the order object in the form state so it is
            // kept up-to-date.
            $order = $form_state['commerce_order'];
          }

          // Back up the any existing redirection found in $form_state.
          $redirect = FALSE;
          if (isset($form_state['redirect'])) {
            $redirect = $form_state['redirect'];
          }
          $registration = $form_state['line_item_registration_add'][$line_item_id];

          // If the order id is known, set the order_id on the registraton.
          if ($order->order_id) {
            $registration->order_id = $order->order_id;
          }

          // The registration module expects only one registration form so we
          // fake it.
          $form_state['registration'] = $registration;
          registration_form_submit($form['commerce_registration']['line_item_registrations'][$line_item_id]['registration_control']['form'], $form_state);
          unset($form_state['registration']);

          // Don't redirect to the usual places registration wants to.
          if ($redirect) {
            $form_state['redirect'] = $redirect;
          }
          else {
            unset($form_state['redirect']);
          }

          // Now add data to the order so we can track this registration.
          $prodkey = $form['commerce_registration']['line_item_registrations'][$line_item_id]['registrations']['#prodkey'];
          $order->data['register_entities'][$prodkey][] = $registration;

          // Save the order.
          $update_order = TRUE;
        }
      }
    }

    // Update an existing order.
    if ($update_order && $order && $order->order_id) {
      try {
        commerce_order_save($order);
      } catch (Exception $e) {
        watchdog('commerce_registration', 'Error when trying to save an order (@order_id) after adding new registration(s).', array(
          '@order_id' => $order->order_id,
        ), WATCHDOG_ERROR);
      }
    }

    // We want to rebuild if this is an ajax submit.
    if ($update_order && $triggered) {
      $form_state['rebuild'] = TRUE;
    }

    // Ensure the attached registrations are associated with the order if they
    // do not have an order_id set yet. This may seem redundant, but in the case
    // a new order is being submitted and we have added registrations via AJAX
    // then they will not have their order_id set yet.
    if (!$order) {
      $order = $form_state['commerce_order'];
    }
    if ($order->order_id && isset($order->data['register_entities'])) {
      foreach ($order->data['register_entities'] as $registrations) {
        foreach ($registrations as $registration) {
          if (!isset($registration->order_id) || !$registration->order_id) {
            $registration->order_id = $order->order_id;
            registration_save($registration);
          }
        }
      }
    }
  }
}

/**
 * Submit callback for cleaning up line item registration controls.
 */
function commerce_registration_line_item_control_reset($form, &$form_state) {

  // Reset the registration form for the triggering button's associated
  // line item.
  if (!empty($form_state['triggering_element'])) {
    $trigger_parents = array_reverse($form_state['triggering_element']['#array_parents']);
    $triggering_element = $trigger_parents[0];
    if (isset($trigger_parents[3]) && $trigger_parents[3] === 'registration_control') {
      $line_item_id = $trigger_parents[4];
      if (isset($form_state['line_item_registration_add'][$line_item_id])) {
        unset($form_state['line_item_registration_add'][$line_item_id]);
      }
      $form_state['rebuild'] = TRUE;
      return;
    }
  }

  // Likely normal order form submission is happening in which case this is
  // probably not necessary, but might as well clean up the state anyway.
  if (isset($form['commerce_registration']['line_item_registrations'])) {

    // Loop through the registration enabled line items and reset any related
    // registration form controls.
    foreach (element_children($form['commerce_registration']['line_item_registrations']) as $line_item_id) {
      if (isset($form_state['line_item_registration_add'][$line_item_id])) {
        unset($form_state['line_item_registration_add'][$line_item_id]);
      }
    }
  }
}

/**
 * Submit callback for line item registration sync checkboxes.
 */
function commerce_registration_line_item_registrations_sync($form, &$form_state) {
  if (isset($form['commerce_registration']['line_item_registrations'])) {

    // If the user is editing an order, load a fresh copy.
    if ($form_state['commerce_order']->order_id) {
      $form_state['commerce_order'] = commerce_order_load($form_state['commerce_order']->order_id);
    }
    $order = $form_state['commerce_order'];

    // Did we trigger this with an ajax callback?
    $triggered = FALSE;
    if (!empty($form_state['triggering_element'])) {
      $trigger_parents = array_reverse($form_state['triggering_element']['#array_parents']);
      if ($triggered = (bool) ($trigger_parents[0] === 'sync_registrations')) {
        $triggering_line_item_id = $trigger_parents[1];
        $form_state['rebuild'] = TRUE;
      }
    }

    // Loop through the registration enabled line items and sync the number
    // of registrations.
    foreach (element_children($form['commerce_registration']['line_item_registrations']) as $line_item_id) {
      if (!$triggered || $triggering_line_item_id == $line_item_id) {
        if (isset($form['commerce_registration']['line_item_registrations'][$line_item_id]['registrations'])) {
          if ($line_item = commerce_line_item_load($line_item_id)) {
            $update_line_item = FALSE;
            $element = $form['commerce_registration']['line_item_registrations'][$line_item_id];
            $prodkey = $element['registrations']['#prodkey'];
            $sync = $element['sync_registrations']['#value'];
            if (!isset($line_item->data['sync_registrations']) || $sync != $line_item->data['sync_registrations']) {
              $line_item->data['sync_registrations'] = $sync;
              $update_line_item = TRUE;
            }

            // Sync the line item quantity if there are registrations attached.
            if (isset($order->data) && !empty($order->data['register_entities'][$prodkey])) {
              $quantity = count($order->data['register_entities'][$prodkey]);
              if ($sync && $quantity != $line_item->quantity) {

                // Update line item quantity with the number of registrations.
                $line_item->quantity = $quantity;
                if (isset($form_state['input']['commerce_line_items'][$form['commerce_line_items']['#language']]['line_items'][$line_item_id]['quantity'])) {
                  $form_state['input']['commerce_line_items'][$form['commerce_line_items']['#language']]['line_items'][$line_item_id]['quantity'] = $quantity;
                }
                if (isset($form_state['values']['commerce_line_items'][$form['commerce_line_items']['#language']]['line_items'][$line_item_id]['quantity'])) {
                  $form_state['values']['commerce_line_items'][$form['commerce_line_items']['#language']]['line_items'][$line_item_id]['quantity'] = $quantity;
                }
                $update_line_item = TRUE;
              }
            }
            else {
              $line_item->data['sync_registrations'] = FALSE;
              $update_line_item = TRUE;
            }

            // Update the line item with changes.
            if ($update_line_item) {
              commerce_line_item_save($line_item);
            }
          }
        }
      }
    }
  }
}

/**
 * AJAX callback for registration sections on the order edit ui.
 */
function commerce_registration_line_item_registrations_refresh($form, $form_state) {
  $refresh_line_items = FALSE;
  $commands = array();

  // Reverse the array parents of the triggering element, because we know the
  // parts of the form to return.
  $parents = array_reverse($form_state['triggering_element']['#array_parents']);

  // AJAX commands if triggered by a remove checkbox.
  if ($parents[0] == 'registration_remove') {
    $commands[] = ajax_command_replace('#line-item-reg-' . $parents[3], render($form['commerce_registration']['line_item_registrations'][$parents[3]]['registrations']));

    // Since the registration has been deleted we want to re-focus the client on
    // the registrations table for the current line item.
    $commands[] = ajax_command_invoke('#line-item-reg-' . $parents[3], 'focus');
    $refresh_line_items = TRUE;
  }
  elseif ($parents[0] == 'registration_control_add') {

    // Refresh the registration controls to display the new registration form.
    $commands[] = ajax_command_replace('#reg-new-' . $parents[2], render($form['commerce_registration']['line_item_registrations'][$parents[2]]['registration_control']));

    // Set focus on the first element on the new registration form.
    if (isset($form['commerce_registration']['line_item_registrations'][$parents[2]]['registration_control']['form']) && ($children_ids = element_children($form['commerce_registration']['line_item_registrations'][$parents[2]]['registration_control']['form'], TRUE))) {
      $first_child = reset($children_ids);
      $element_id = $form['commerce_registration']['line_item_registrations'][$parents[2]]['registration_control']['form'][$first_child]['#id'];
    }
    else {
      $element_id = 'line-item-reg-' . $parents[2];
    }
    $commands[] = ajax_command_invoke('#' . $element_id, 'focus');
  }
  elseif (isset($parents[3]) && $parents[3] == 'registration_control' && $parents[0] == 'registration_control_submit') {

    // Reset the registration form controls.
    $commands[] = ajax_command_replace('#li-reg-' . $parents[4], render($form['commerce_registration']['line_item_registrations'][$parents[4]]));

    // Since a new registration was added we want to re-focus the client on
    // the registrations table for the current line item.
    $commands[] = ajax_command_invoke('#line-item-reg-' . $parents[4], 'focus');
    $refresh_line_items = TRUE;
  }
  elseif (isset($parents[3]) && $parents[3] == 'registration_control' && $parents[0] == 'registration_control_cancel') {

    // Reset the registration form controls.
    $commands[] = ajax_command_replace('#reg-new-' . $parents[4], render($form['commerce_registration']['line_item_registrations'][$parents[4]]['registration_control']));

    // Set focus back to the "Add registration" button.
    $commands[] = ajax_command_invoke('#' . $form['commerce_registration']['line_item_registrations'][$parents[4]]['registration_control']['registration_control_add']['#id'], 'focus');
  }
  elseif ($parents[0] == 'sync_registrations') {
    $refresh_line_items = TRUE;

    // For accessibility purposes return focus back on the sync checkbox.
    $commands[] = ajax_command_invoke('#' . $form_state['triggering_element']['#id'], 'focus');
  }

  // AJAX commands for updating the line items if affected by registrations.
  if ($refresh_line_items) {

    // Also if possible refresh the line items since the quantity should have
    // been updated.
    if (isset($form['commerce_line_items']) && isset($form['commerce_line_items']['#language']) && isset($form['commerce_line_items'][$form['commerce_line_items']['#language']])) {
      $commands[] = ajax_command_replace('#line-item-manager', render($form['commerce_line_items'][$form['commerce_line_items']['#language']]));
    }
  }

  // Add the status messages inside the new content's wrapper element, so that
  // on subsequent Ajax requests, it is treated as old content.
  $commands[] = ajax_command_replace('#commerce-registration-messages', '<div id="commerce-registration-messages">' . theme('status_messages') . '</div>');
  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}

/**
 * Implements hook_commerce_cart_order_refresh().
 *
 * Alter the line items to make sure we are not overbooking a registration prod
 * Based on https://www.drupal.org/node/2126417
 *
 * @todo - can we do this in one pass rather than 3?
 */
function commerce_registration_commerce_cart_order_refresh($order_wrapper) {
  $order = $order_wrapper
    ->value();

  // Make sure registration entities still match up to products.
  if (isset($order->data['register_entities'])) {

    // Save a copy of register_entities to find registrations no longer associated with a product.
    $register_entities = $order->data['register_entities'];
    foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
      if (!in_array($line_item_wrapper->type
        ->value(), commerce_product_line_item_types())) {
        continue;
      }
      $product = $line_item_wrapper->commerce_product;
      $line_item_id = $line_item_wrapper->line_item_id
        ->value();
      if (isset($order->data['register_entities'][$line_item_id . 'prod-' . $product->sku
        ->value()])) {

        // This registration still has a product.
        unset($register_entities[$line_item_id . 'prod-' . $product->sku
          ->value()]);
      }
    }

    // Delete registrations which no longer have a matching product in the cart.
    $orphan_registration_ids = array();
    foreach ($register_entities as $key => $registrations) {
      foreach ($registrations as $registration) {
        $orphan_registration_ids[] = $registration->registration_id;
      }
      unset($order->data['register_entities'][$key]);
    }
    registration_delete_multiple($orphan_registration_ids);
  }

  // Stop users overbooking registrations
  if (!commerce_cart_order_is_cart($order_wrapper
    ->value())) {
    return FALSE;
  }

  // Keep an array with the sum of qty
  $qty_sum = array();

  // Keep an array with the list of potentially overbooked line items
  $overbooked_prod_id = array();

  // Keep list of Warning Messages
  $msg_array = array();

  // Set the message we will show to the user if they need to change the quantity in their cart
  $adjust_qty_text = arg(0) == 'cart' ? t('Please adjust your cart quantity.') : l(t('Please adjust your cart quantity.'), 'cart');

  // ** First Pass:
  // Make sure individual line items do not exceed available registrations
  // For each of the line items in the order
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
    if (!in_array($line_item_wrapper->type
      ->value(), commerce_product_line_item_types())) {
      return FALSE;
    }

    // Get the line item  so we can work on it
    $line_item = $line_item_wrapper
      ->value();

    // Gets the quantity of this line item in the cart
    $qty = $line_item_wrapper->quantity
      ->value();

    // Gets the unique product id for the line item
    $product_id = $line_item_wrapper->commerce_product->product_id
      ->value();

    // Attempt to get registration settings for this product
    $settings = registration_entity_settings('commerce_product', $product_id);

    // Check if this product is registration enabled
    if (!empty($settings) && $settings['status'] == 1) {

      // For this product id, see how many are available
      $capacity = (int) $settings['capacity'];
      $filled = (int) registration_event_count('commerce_product', $product_id);

      // Add order registrations offset
      $order_registrations = 0;

      // Get registrations from current order if they have been created
      if (isset($order->data['register_entities'])) {

        // loop through registrations
        foreach ($order->data['register_entities'] as $key => $registrations) {
          foreach ($registrations as $registration) {
            if ($registration->order_id == $order->order_id && $registration->entity_id == $product_id) {

              // this registration belongs to the current order and current product; add to order registrations
              $order_registrations++;
            }
          }
        }
      }

      // Check if $capacity is set to 0 (unlimited) and ensure $avail isn't
      // negative which can cause too many registrations to be removed.
      $avail = 0;
      if ($capacity > 0) {
        $avail = $capacity - $filled + $order_registrations;
      }
      if ($avail < 0) {
        $avail = 0;
      }

      // If the line item qty is more than available, set the
      // line item to availability to avoid over booking
      if ($capacity > 0 && $qty > $avail) {

        // Set line item qty to availability
        _commerce_registration_set_line_item_qty($line_item, $avail);
        if (!array_key_exists($product_id, $qty_sum)) {
          $qty_sum[$product_id] = $avail;
        }
        else {
          $qty_sum[$product_id] += $avail;
        }

        //  Display a warning message.
        $msg_array[$product_id] = t("There are only @num available slots for @title. !adjust_text", array(
          '@num' => $avail,
          '@title' => $line_item_wrapper->commerce_product->title
            ->value(),
          '!adjust_text' => $adjust_qty_text,
        ));
      }
      else {

        // Let's add up the sum of all the same products in the cart
        // needed since  disabled the
        // 'Attempt to combine like products on the same line item in the cart.'
        if (!array_key_exists($product_id, $qty_sum)) {
          $qty_sum[$product_id] = $line_item_wrapper->quantity
            ->value();
        }
        else {
          $qty_sum[$product_id] += $line_item_wrapper->quantity
            ->value();
        }
      }

      // Check if the sum in cart would result in overbooking
      if ($capacity > 0 && $qty_sum[$product_id] > $avail) {
        $overbooked_prod_id[$product_id] = TRUE;
      }
    }
  }

  // ** Second Pass:
  //  if Sum of line item quantities would cause overbooking
  //  First try setting all of those products quantity to the slots available
  //  then re-check qty sum.  if still exceeds, then remove line item
  $qty_sum = array();

  // reset
  // For each of the line items in the order
  if (!empty($overbooked_prod_id)) {
    foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {

      // If this line item product id is in the overbooked array, set qty 1
      $line_item = $line_item_wrapper
        ->value();

      // Gets the unique product id for the line item
      $product_id = $line_item_wrapper->commerce_product->product_id
        ->value();
      if (isset($overbooked_prod_id[$product_id])) {

        // Set line item qty to 1
        _commerce_registration_set_line_item_qty($line_item, $avail);
        if (!array_key_exists($product_id, $qty_sum)) {
          $qty_sum[$product_id] = $avail;
        }
        else {
          $qty_sum[$product_id] += $avail;
        }

        // For this product id, see how many are available
        // Attempt to get registration settings for this product
        $settings = registration_entity_settings('commerce_product', $product_id);
        $capacity = (int) $settings['capacity'];
        $filled = (int) registration_event_count('commerce_product', $product_id);
        $avail = $capacity - $filled;

        // Warning Message
        $msg_array[$product_id] = t("There are only @num available slots for @title. !adjust_text", array(
          '@num' => $avail,
          '@title' => $line_item_wrapper->commerce_product->title
            ->value(),
          '!adjust_text' => $adjust_qty_text,
        ));

        // Check if the sum in cart would result in overbooking
        // This time, we remove it.
        if ($qty_sum[$product_id] > $avail) {

          // Delete the line item
          // do not need to save order, as done after the hook for us
          commerce_cart_order_product_line_item_delete($order_wrapper
            ->value(), $line_item->line_item_id, $skip_save = TRUE);

          // $order_wrapper->value(), $line_item->line_item_id );
          $qty_sum[$product_id] -= 1.0;

          // Warning Message
          $msg_array[$product_id] = t("There are only @num available slots for @title. Item was removed from your cart", array(
            '@num' => $avail,
            '@title' => $line_item_wrapper->commerce_product->title
              ->value(),
          ));
        }
      }
    }
  }

  // ** Third Pass:
  // Verify if registration is possible
  // We have already checked capacity, check open/close dates and reg status
  // if not available, then remove
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
    if (!in_array($line_item_wrapper->type
      ->value(), commerce_product_line_item_types())) {
      continue;
    }

    // Get the line item  so we can work on it
    $line_item = $line_item_wrapper
      ->value();

    // Gets the unique product id for the line item
    $product_id = $line_item_wrapper->commerce_product->product_id
      ->value();

    // Attempt to get registration settings for this product
    $settings = registration_entity_settings('commerce_product', $product_id);

    // Check if this product is registration enabled
    if (!empty($settings)) {
      $status = $settings['status'];
      $open = isset($settings['open']) ? strtotime($settings['open']) : NULL;
      $close = isset($settings['close']) ? strtotime($settings['close']) : NULL;
      $now = REQUEST_TIME;

      // remove line item if not open
      if ($status) {

        // check open date range
        if (isset($open) && $now < $open) {
          $status = FALSE;
        }

        // check close date range
        if (isset($close) && $now >= $close) {
          $status = FALSE;
        }
      }

      // Delete the line item
      if ($status == FALSE) {

        // do not need to save order, as done after the hook for us
        commerce_cart_order_product_line_item_delete($order_wrapper
          ->value(), $line_item->line_item_id, $skip_save = TRUE);

        // Warning Message
        $msg_array[$product_id] = t("Registration is not available for @product.  Item was removed from your cart.", array(
          '@product' => $line_item_wrapper->commerce_product->title
            ->value(),
        ));
      }
    }
  }

  // Display warning messages for any products that were overbooked
  foreach ($msg_array as $msg) {
    drupal_set_message($msg, 'warning', TRUE);
  }
}

/**
 * Helper function to update the line item quantity
 */
function _commerce_registration_set_line_item_qty($line_item, $qty) {

  // force the line item quantity
  $line_item->quantity = $qty;

  // Save the updated line item and reset cache
  commerce_line_item_save($line_item);
  entity_get_controller('commerce_line_item')
    ->resetCache(array(
    $line_item->line_item_id,
  ));
}

/**
 * Implements hook_commerce_checkout_router().
 *
 * Skips the registration form step if there are no registration enabled
 * product line items in the cart.
 */
function commerce_registration_commerce_checkout_router($order, $checkout_page) {

  // Check for registration checkout page.
  if ($checkout_page['page_id'] === "registration") {
    $registration_required = FALSE;
    $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

    // Loop through line items.
    foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
      if (!in_array($line_item_wrapper->type
        ->value(), commerce_product_line_item_types())) {
        continue;
      }
      $product_id = (int) $line_item_wrapper->commerce_product->product_id
        ->value();
      if (commerce_registration_product_has_registration_field($product_id) && registration_status('commerce_product', $product_id, TRUE) == 1) {

        // Registration field found so stay on the page.
        $registration_required = TRUE;
        break;
      }
    }

    // No registration found.
    if (!$registration_required) {

      // Update order status.
      $order = commerce_order_status_update($order, 'checkout_' . $checkout_page['next_page'], FALSE, TRUE, t('Registration not required for these products. Skipping this checkout step.'));

      // Go to the next page in checkout.
      drupal_goto('checkout/' . $order->order_id . '/' . $checkout_page['next_page']);
    }
  }
}

/*******************
 * Theme Functions.
 ******************/

/**
 * Theme function for rendering a table of line item registration elements.
 */
function theme_commerce_registration_line_item_registrations($variables) {
  $element = $variables['element'];
  $order = $element['#order'];
  $prodkey = $element['#prodkey'];
  $header = array(
    array(
      'data' => t('Remove'),
    ),
    array(
      'data' => t('id'),
      'field' => 'registration_id',
      'type' => 'property',
      'specifier' => 'registration_id',
    ),
    array(
      'data' => t('Email'),
    ),
    array(
      'data' => t('User'),
      'field' => 'user_uid',
      'type' => 'property',
      'specifier' => 'user_uid',
    ),
    array(
      'data' => t('Created By'),
      'field' => 'author_uid',
      'type' => 'property',
      'specifier' => 'author_uid',
    ),
    array(
      'data' => t('Count'),
      'field' => 'count',
      'type' => 'property',
      'specifier' => 'count',
    ),
    array(
      'data' => t('Created'),
      'field' => 'created',
      'sort' => 'desc',
      'type' => 'property',
      'specifier' => 'created',
    ),
    array(
      'data' => t('State'),
      'field' => 'state',
      'type' => 'property',
      'specifier' => 'state',
    ),
    array(
      'data' => t('Actions'),
    ),
  );
  $rows = array();

  // Add each registration to the line item's registrations table.
  $registration_ids = element_children($element);
  $registrations = entity_load('registration', $registration_ids);
  foreach ($registration_ids as $registration_id) {
    $data = $element[$registration_id];
    if (isset($data['delta']['#value']) && isset($order->data) && isset($order->data['register_entities'][$prodkey][$data['delta']['#value']])) {

      // Load the registration because what is in $order->data could be wildly
      // out of date.
      if (isset($registrations[$registration_id])) {
        $registration = $registrations[$registration_id];
        $wrapper = entity_metadata_wrapper('registration', $registration);
        $author = $wrapper->author
          ->value();
        $user = $wrapper->user
          ->value();
        $state = $wrapper->state
          ->value();
        $author_col = '';
        if ($registration->author_uid) {
          $author_col = theme('username', array(
            'account' => $author,
          ));
        }
        $user_col = '';
        if ($registration->user_uid) {
          $user_col = theme('username', array(
            'account' => $user,
          ));
        }
        $actions = array();
        if (entity_access('view', 'registration', $registration)) {
          $actions[] = l(t('View'), 'registration/' . $registration->registration_id);
        }
        if (entity_access('update', 'registration', $registration)) {
          $actions[] = l(t('Edit'), 'registration/' . $registration->registration_id . '/edit', array(
            'query' => drupal_get_destination(),
          ));
        }
        $context = array(
          'registration' => clone $registration,
          'order' => clone $order,
          'prodkey' => $prodkey,
        );

        // Allow other modules to add/remove actions for each registration.
        drupal_alter('commerce_registration_order_ops', $actions, $context);
        if ($wrapper->anon_mail
          ->value()) {
          $mail = $wrapper->anon_mail
            ->value();
        }
        else {
          $mail = $wrapper->mail
            ->value();
        }
        $rows[] = array(
          drupal_render($data['registration_remove']),
          l($registration->registration_id, 'registration/' . $registration->registration_id),
          l($mail, 'mailto:' . $mail),
          $user_col,
          $author_col,
          $registration->count,
          format_date($registration->created),
          $state ? filter_xss_admin(entity_label('registration_state', $state)) : '',
          implode(' | ', $actions),
        );
      }
    }
  }
  $line_item = $element['#line_item'];

  // Setup the table's variables array and build the output.
  $table_variables = array(
    'caption' => $element['#title'],
    'header' => $header,
    'rows' => $rows,
    'empty' => $element['#empty'],
    'attributes' => array(
      'id' => 'line-item-reg-' . $line_item->line_item_id,
      'tabindex' => 0,
    ),
  );
  $output = theme('table', $table_variables);
  return $output;
}

Functions

Namesort descending Description
commerce_registration_add_to_cart_validate Custom validation handler for add to cart form. Checks if the product being added is available for registration before adding it to the cart. If it is not available, it is not added and a message is shown to the user.
commerce_registration_administer_registrations_access Access callback. Callback for modifying settings and sending emails for a given product display.
commerce_registration_commerce_cart_order_refresh Implements hook_commerce_cart_order_refresh().
commerce_registration_commerce_checkout_complete Implements hook_commerce_checkout_complete().
commerce_registration_commerce_checkout_page_info Implements hook_commerce_checkout_page_info().
commerce_registration_commerce_checkout_pane_info Implements hook_commerce_checkout_pane_info().
commerce_registration_commerce_checkout_router Implements hook_commerce_checkout_router().
commerce_registration_commerce_line_item_type_info_alter Implements hook_commerce_line_item_type_info_alter().
commerce_registration_entity_info_alter Implements hook_entity_info_alter().
commerce_registration_entity_property_info_alter Implements hook_entity_property_info_alter().
commerce_registration_entity_view_alter Implements hook_entity_view_alter().
commerce_registration_field_extra_fields Implements hook_field_extra_fields().
commerce_registration_field_extra_fields_alter Implements hook_field_extra_fields_alter().
commerce_registration_form_alter Implements hook_form_alter().
commerce_registration_form_commerce_order_ui_order_form_alter Implements hook_form_FORM_ID_alter().
commerce_registration_get_product_title Returns a product title.
commerce_registration_hide_tab Checks if we should hide the Manage Registrations tab for this node.
commerce_registration_line_item_control_reset Submit callback for cleaning up line item registration controls.
commerce_registration_line_item_control_save Submit callback for an embedded registration form within the order ui.
commerce_registration_line_item_registrations_refresh AJAX callback for registration sections on the order edit ui.
commerce_registration_line_item_registrations_sync Submit callback for line item registration sync checkboxes.
commerce_registration_line_item_registrations_validate Validation callback for line item registration elements on the order edit ui.
commerce_registration_line_item_registration_delete Submit callback for line item registration remove checkboxes.
commerce_registration_menu Implements hook_menu().
commerce_registration_menu_alter Implements hook_menu_alter().
commerce_registration_node_settings_form_submit Submit handler for node settings.
commerce_registration_product_has_registration_field Checks if a product has a registration field attached.
commerce_registration_product_title Product title line item callback.
commerce_registration_registration_page Registration page callback for all registrations for a given product display.
commerce_registration_theme Implements hook_theme().
commerce_registration_views_api Implements hook_views_api().
theme_commerce_registration_line_item_registrations Theme function for rendering a table of line item registration elements.
_commerce_registration_set_line_item_qty Helper function to update the line item quantity