You are here

commerce_registration_defer.module in Commerce Registration 7.2

Module file for commerce_registration_defer.

Adds a defer link to registrations on the order edit page. The deferral form allows an admin to select which course to defer to and the code does all the magic that would have been a somewhat complicated manual proces otherwise.

File

modules/commerce_registration_defer/commerce_registration_defer.module
View source
<?php

/**
 * @file
 * Module file for commerce_registration_defer.
 *
 * Adds a defer link to registrations on the order edit page. The deferral form
 * allows an admin to select which course to defer to and the code does all the
 * magic that would have been a somewhat complicated manual proces otherwise.
 */

/**
 * Implements hook_permission().
 */
function commerce_registration_defer_permission() {
  $permissions = array();
  $permissions['defer any registration'] = array(
    'title' => t('Defer any registration'),
  );
  foreach (registration_get_types() as $type_info) {
    $type = $type_info->name;
    $label = $type_info->label;
    $permissions["defer own {$type} registration"] = array(
      'title' => t('%type_name: Defer own registrations', array(
        '%type_name' => $label,
      )),
    );
    $permissions["defer any {$type} registration"] = array(
      'title' => t('%type_name: Defer any registrations', array(
        '%type_name' => $label,
      )),
    );
  }
  return $permissions;
}

/**
 * Implements hook_menu().
 *
 * Provides a default page to explain what this module does.
 */
function commerce_registration_defer_menu() {
  $items['admin/commerce/orders/%commerce_order/defer/%registration'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_registration_defer_form',
      5,
      3,
    ),
    'access callback' => 'commerce_registration_defer_defer_registration_access',
    'access arguments' => array(
      'defer',
      5,
    ),
    'title' => 'Registration Deferral',
  );
  return $items;
}

/**
 * Callback to get the registration deferral form.
 */
function commerce_registration_defer_form($form, &$form_state) {
  $original_registration = $form_state['build_info']['args'][0];
  $order = $form_state['build_info']['args'][1];
  $deferred_product = commerce_product_load($original_registration->entity_id);
  $original_line_item = FALSE;

  // Find the line item associated to the provided registration and order.
  if (!empty($order->data['register_entities'])) {
    foreach ($order->data['register_entities'] as $key => $line_item_registrations) {
      $key_parts = explode('prod-', $key);
      if (count($key_parts) === 2) {
        $line_item_id = $key_parts[0];
        foreach ($line_item_registrations as $checkout_reg) {
          if ($checkout_reg->registration_id && $checkout_reg->registration_id == $original_registration->registration_id) {
            $original_line_item = commerce_line_item_load($line_item_id);
            break 2;
          }
        }
      }
    }
  }
  if ($original_line_item) {

    // Load the info object for the line item type associated with the product
    // we wish to defer. Assume we'll be using the same line item type for
    // the new product.
    $line_item_type = commerce_line_item_type_load($original_line_item->type);

    // Store the line item info object in the form array.
    $form['actions']['line_item_type'] = array(
      '#type' => 'value',
      '#value' => $line_item_type,
    );

    // Store the line item for the deferred product in the form array.
    $form['actions']['original_line_item'] = array(
      '#type' => 'value',
      '#value' => $original_line_item,
    );

    // If this type specifies a valid add form callback function...
    if ($callback = commerce_line_item_type_callback($line_item_type, 'add_form')) {

      // Load in the appropriate form elements to the actions array.
      $form['actions'] += $callback($form, $form_state);
    }
  }

  // Submit button.
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Defer registration'),
  );

  // Cancel link.
  $order_uri = commerce_order_ui_order_uri($order);
  $form['cancel'] = array(
    '#markup' => l(t('Cancel'), $order_uri['path'] . '/edit'),
  );

  // Add the confirmation message to the form so that it can be overridden.
  $form['confirmation'] = array(
    '#type' => 'value',
    '#value' => t('The registration has been deferred.'),
  );
  return $form;
}

/**
 * Validation callback for the registration deferral form.
 */
function commerce_registration_defer_form_validate($form, &$form_state) {
  $order = $form_state['build_info']['args'][1];
  $line_item_type = $form['actions']['line_item_type']['#value'];

  // Create the new line item.
  $line_item = commerce_line_item_new($line_item_type['type'], $order->order_id);

  // If this type specifies a valid add form callback function...
  if ($callback = commerce_line_item_type_callback($line_item_type, 'add_form_submit')) {

    // Allow the callback to alter data onto the line item to be saved and
    // to return an error message if something goes wrong.
    if ($error = $callback($line_item, $form, $form_state, $form)) {

      // Unfortunately we can't be certain of what the error is for so add it
      // to the whole form.
      form_set_error('', $error);
    }
    else {

      // Save the new line item and use it in the submit callback.
      $form_state['build_info']['new_line_item'] = $line_item;
    }
  }
}

/**
 * Submit callback for the registration deferral form.
 */
function commerce_registration_defer_form_submit($form, &$form_state) {
  $original_registration = $form_state['build_info']['args'][0];
  $line_item_type = $form_state['values']['line_item_type'];
  $original_line_item = $form_state['values']['original_line_item'];

  // The selected product.
  $product = commerce_product_load_by_sku($form_state['values']['product_sku']);
  $order = $form_state['build_info']['args'][1];

  // Wrap the order for easy access to field data.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

  // Get the line item we created in the validation callback.
  $line_item = $form_state['build_info']['new_line_item'];

  // Wrap the line item too, we'll need it.
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);

  // Copy all the existing registration's data and update it with the new
  // product info.
  $new_registration = clone $original_registration;
  $new_registration->entity_id = $product->product_id;
  unset($new_registration->created);
  $new_registration->registration_id = NULL;
  $new_registration
    ->save();

  // Now set the state of the original registration to deferred.
  $original_registration->state = 'deferred';
  $original_registration
    ->save();
  $quantity = $original_registration->count;

  // Reduce the original line item quantity by the registration count if the
  // line item quantity is synced with the number of associated registrations.
  $sync_original_quantity = isset($original_line_item->data['sync_registrations']) ? (bool) $original_line_item->data['sync_registrations'] : TRUE;
  if ($sync_original_quantity && $original_line_item->quantity > 0) {
    $original_line_item->quantity = $original_line_item->quantity < $quantity ? 0 : $original_line_item->quantity - $quantity;
    commerce_line_item_save($original_line_item);
  }

  // If we should try and combine line items look for an existing line item to
  // add the new registration to.
  $combine = isset($original_line_item->data['context']['add_to_cart_combine']) ? $original_line_item->data['context']['add_to_cart_combine'] : TRUE;

  // Determine if the product already exists on the order and increment its
  // quantity instead of adding a new line if it does.
  $matching_line_item = NULL;

  // If we are supposed to look for a line item to combine into...
  if ($combine) {

    // Generate an array of properties and fields to compare.
    $comparison_properties = array(
      'type',
      'commerce_product',
    );

    // Add any field that was exposed on the Add to Cart form to the array.
    // TODO: Bypass combination when an exposed field is no longer available but
    // the same base product is added to the cart.
    foreach (field_info_instances('commerce_line_item', $line_item->type) as $info) {
      if (!empty($info['commerce_cart_settings']['field_access'])) {
        $comparison_properties[] = $info['field_name'];
      }
    }

    // Allow other modules to specify what properties should be compared when
    // determining whether or not to combine line items.
    drupal_alter('commerce_cart_product_comparison_properties', $comparison_properties, clone $line_item);

    // Loop over each line item on the order.
    foreach ($order_wrapper->commerce_line_items as $delta => $matching_line_item_wrapper) {

      // Unwrap the line item so we can compare its data parameter.
      $existing_line_item = $matching_line_item_wrapper
        ->value();

      // Check if the original line item and any matching line item share the
      // same 'sync_registrations' settings.
      $sync_matching_quantity = isset($existing_line_item->data['sync_registrations']) ? (bool) $existing_line_item->data['sync_registrations'] : TRUE;
      if ($sync_original_quantity !== $sync_matching_quantity) {
        continue;
      }

      // Examine each of the comparison properties on the line item.
      foreach ($comparison_properties as $property) {

        // If the property is not present on either line item, bypass it.
        if (!isset($matching_line_item_wrapper
          ->value()->{$property}) && !isset($line_item_wrapper
          ->value()->{$property})) {
          continue;
        }

        // If any property does not match the same property on the incoming line
        // item or exists on one line item but not the other...
        if (!isset($matching_line_item_wrapper
          ->value()->{$property}) && isset($line_item_wrapper
          ->value()->{$property}) || isset($matching_line_item_wrapper
          ->value()->{$property}) && !isset($line_item_wrapper
          ->value()->{$property}) || $matching_line_item_wrapper->{$property}
          ->raw() != $line_item_wrapper->{$property}
          ->raw()) {

          // Continue the loop with the next line item.
          continue 2;
        }
      }

      // If every comparison line item matched, combine into this line item.
      $matching_line_item = $existing_line_item;
      break;
    }
  }

  // If no matching line item was found...
  if (empty($matching_line_item)) {

    // Always use the quantity defined by the registration count...
    $line_item->quantity = $quantity;

    // However if the original line item was not set to sync its quantity with
    // its number of registrations then we need someway to balance it out. So
    // we will set the line items unit price to zero.
    if (!$sync_original_quantity) {
      $line_item->commerce_unit_price[$product->language][0]['amount'] = 0;
      $line_item->commerce_unit_price[$product->language][0]['data']['components'] = array();
    }

    // Should the line item quantity be synced?
    $line_item->data['sync_registrations'] = $sync_original_quantity;

    // Save the incoming line item now so we get its ID.
    commerce_line_item_save($line_item);

    // Add it to the order's line item reference value.
    $order_wrapper->commerce_line_items[] = $line_item;
  }
  else {

    // Increment the quantity of the matching line item if its set to sync.
    if ($sync_matching_quantity) {
      $matching_line_item->quantity += $quantity;
    }

    // Update the data array and save it.
    $matching_line_item->data = array_merge($line_item->data, $matching_line_item->data);
    commerce_line_item_save($matching_line_item);

    // Update the line item variable for use in the invocation and return value.
    $line_item = $matching_line_item;
  }

  // Update the order with the new registration info.
  $key = $line_item->line_item_id . 'prod-' . $product->sku;
  $order->data['register_entities'][$key][] = $new_registration;

  // Record the deferral in the order revision log.
  $order->log = "Registration deferred: {$original_registration->registration_id} ==> {$new_registration->registration_id}.";
  $order->revision = true;

  // Save the updated order.
  commerce_order_save($order);
  $context = array(
    'original' => array(
      'registration' => $original_registration,
      'line_item' => $original_line_item,
    ),
    'new' => array(
      'registration' => $new_registration,
      'line_item' => $line_item,
    ),
    'order' => $order,
  );

  // Invoke the hook.
  module_invoke_all('commerce_registration_defer', $context);

  // Invoke rules.
  if (module_exists('rules')) {
    rules_invoke_event('commerce_registration_defer', $original_registration, $new_registration);
  }

  // Display the confirmation message.
  if (!empty($form['confirmation']['#value'])) {
    drupal_set_message(filter_xss($form['confirmation']['#value']));
  }

  // Log the deferral to Drupal's watchdog.
  $uri_options = entity_uri('commerce_order', $order);
  watchdog('commerce registration', "Registration Deferred for order #!oid: !orig ==> !new.", array(
    '!oid' => $order->order_id,
    '!orig' => $original_registration->registration_id,
    '!new' => $new_registration->registration_id,
  ), WATCHDOG_INFO, l(t('view order'), $uri_options['path'], $uri_options['options']));
}

/**
 * Implements hook_commerce_registration_order_ops_alter().
 *
 * Adds a defer operation in the registration tables on an order edit page.
 */
function commerce_registration_defer_commerce_registration_order_ops_alter(&$actions, $context) {

  // Don't provide a deferral link if the registration is already deferred.
  if ($context['registration']->state === 'deferred') {
    return;
  }

  // Check that the user has access to defer a registration.
  if (commerce_registration_defer_defer_registration_access('defer', $context['registration'])) {
    $actions['defer'] = l(t('Defer'), 'admin/commerce/orders/' . $context['order']->order_id . '/defer/' . $context['registration']->registration_id, array(
      'query' => drupal_get_destination(),
    ));
  }
}

/**
 * Implements hook_registration_access().
 *
 * Checks if a user has access to defer a given registration.
 */
function commerce_registration_defer_defer_registration_access($op, $registration, $account = null) {
  $account = isset($account) ? $account : $GLOBALS['user'];
  if ($op === 'defer') {

    // Don't allow deferral if the registration is already deferred.
    if ($registration->state === 'deferred') {
      return FALSE;
    }

    // Allow other modules to provide access grants for the 'defer' op through
    // hook_registration_access(). If no module explicitly denies access and at
    // least one grants it then return true.
    $entity_access = entity_access('defer', 'registration', $registration, $account);
    if (!is_null($entity_access)) {
      return $entity_access;
    }
    if (user_access('defer any registration', $account)) {
      return TRUE;
    }
    $type = $registration
      ->bundle();
    $wrapper = entity_metadata_wrapper('registration', $registration);
    $author = $wrapper->author
      ->value();
    $account_own = $author && $author->uid == $account->uid;
    return $account_own && user_access("defer own {$type} registration", $account) || user_access("defer any {$type} registration", $account);
  }
}

/**
 * Rules integration access callback.
 */
function commerce_registration_defer_rules_access($type, $name) {
  global $user;
  if ($type == 'event' || $type == 'condition') {
    return user_access('defer any registration', $user);
  }
}

Functions

Namesort descending Description
commerce_registration_defer_commerce_registration_order_ops_alter Implements hook_commerce_registration_order_ops_alter().
commerce_registration_defer_defer_registration_access Implements hook_registration_access().
commerce_registration_defer_form Callback to get the registration deferral form.
commerce_registration_defer_form_submit Submit callback for the registration deferral form.
commerce_registration_defer_form_validate Validation callback for the registration deferral form.
commerce_registration_defer_menu Implements hook_menu().
commerce_registration_defer_permission Implements hook_permission().
commerce_registration_defer_rules_access Rules integration access callback.