You are here

commerce_recurring.module in Commerce Recurring Framework 7.2

Same filename and directory in other branches
  1. 8 commerce_recurring.module
  2. 7 commerce_recurring.module

Commerce recurring module file.

File

commerce_recurring.module
View source
<?php

/**
 * @file
 * Commerce recurring module file.
 */

/**
 * Implements hook_entity_info().
 */
function commerce_recurring_entity_info() {
  $return = array(
    'commerce_recurring' => array(
      'label' => t('Commerce recurring entity'),
      'plural label' => t('Commerce recurring entities'),
      'entity class' => 'Entity',
      'controller class' => 'EntityAPIController',
      'views controller class' => 'EntityDefaultViewsController',
      'base table' => 'commerce_recurring',
      'uri callback' => 'commerce_recurring_uri',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'id',
        'bundle' => 'type',
      ),
      'bundle keys' => array(
        'bundle' => 'type',
      ),
      'bundles' => array(
        'product' => array(
          'label' => t('Commerce recurring product'),
        ),
        'order' => array(
          'label' => t('Commerce recurring order'),
        ),
      ),
      'view modes' => array(
        'full' => array(
          'label' => t('Admin display'),
          'custom settings' => FALSE,
        ),
      ),
      'module' => 'commerce_recurring',
      'access arguments' => array(
        'user key' => 'uid',
        'access tag' => 'commerce_recurring_access',
      ),
      'permission labels' => array(
        'singular' => t('recurring entity'),
        'plural' => t('recurring entities'),
      ),
      // Prevent Redirect from altering the line item form.
      'redirect' => FALSE,
    ),
  );
  return $return;
}

/**
 * Bundles of the recurring entity.
 */
function commerce_recurring_types() {
  return array(
    'product',
    'order',
  );
}

/**
 * Ensures that the price and interval fields are created.
 */
function commerce_recurring_configure_product_type() {

  // Create the price instances for the recurring product type.
  commerce_recurring_price_create_instance('commerce_price', 'commerce_product', 'recurring', t('Price'), 0, 'calculated_sell_price');

  // Set the formatter display as hidden by default for the additional price
  // fields.
  commerce_recurring_price_create_instance('commerce_recurring_ini_price', 'commerce_product', 'recurring', t('Initial price'), 0, 'calculated_sell_price', array(
    'type' => 'hidden',
  ));
  commerce_recurring_price_create_instance('commerce_recurring_rec_price', 'commerce_product', 'recurring', t('Recurring price'), 0, 'calculated_sell_price', array(
    'type' => 'hidden',
  ));

  // Create interval fields.
  $fields = array(
    'commerce_recurring_ini_period' => array(
      'field_name' => 'commerce_recurring_ini_period',
      'cardinality' => 1,
      'type' => 'interval',
    ),
    'commerce_recurring_rec_period' => array(
      'field_name' => 'commerce_recurring_rec_period',
      'cardinality' => 1,
      'type' => 'interval',
    ),
    'commerce_recurring_end_period' => array(
      'field_name' => 'commerce_recurring_end_period',
      'cardinality' => 1,
      'type' => 'interval',
    ),
  );

  // Set instance settings for the interval fields.
  $basic_interval_instance = array(
    'entity_type' => 'commerce_product',
    'bundle' => 'recurring',
    'widget' => array(
      'type' => 'interval_default',
    ),
    'display' => array(
      'default' => array(
        'label' => 'hidden',
        'type' => 'interval_default',
      ),
    ),
    'settings' => array(
      'allowed_periods' => array(
        'day' => 'day',
        'week' => 'week',
        'month' => 'month',
        'year' => 'year',
      ),
    ),
    'weight' => 0,
  );
  $instances = array(
    'commerce_recurring_ini_period' => $basic_interval_instance,
    'commerce_recurring_rec_period' => $basic_interval_instance,
    'commerce_recurring_end_period' => $basic_interval_instance,
  );
  $instances['commerce_recurring_ini_period']['label'] = t('Initial period');
  $instances['commerce_recurring_rec_period']['label'] = t('Recurring period');
  $instances['commerce_recurring_end_period']['label'] = t('End period');

  // Create fields & instances if necessary.
  foreach ($fields as $field_name => $field) {
    $info_field = field_info_field($field_name);
    $instance = field_info_instance('commerce_product', $field_name, 'recurring');
    if (empty($info_field)) {
      field_create_field($field);
    }
    if (empty($instance)) {
      $instance = $instances[$field_name];
      $instance['field_name'] = $field_name;
      field_create_instance($instance);
    }
  }
}

/**
 * Ensures that the fields for the recurring entity are created.
 */
function commerce_recurring_configure_recurring_entity_type() {

  // Product bundle.
  // Create a price field to save the price of the product at the moment of
  // the entity creation.
  commerce_recurring_price_create_instance('commerce_recurring_fixed_price', 'commerce_recurring', 'product', t('Fixed price'), 0, 'calculated_sell_price');

  // Entity reference fields for relating the recurring entity to the product
  // that is recurring and also with the orders.
  $fields = array(
    'commerce_recurring_ref_product' => array(
      'field_name' => 'commerce_recurring_ref_product',
      'type' => 'entityreference',
      'cardinality' => 1,
      'entity_types' => array(
        'commerce_recurring',
      ),
      'module' => 'commerce_recurring',
      'translatable' => FALSE,
      'settings' => array(
        'target_type' => 'commerce_product',
        'handler' => 'base',
        'handler_settings' => array(),
      ),
    ),
    'commerce_recurring_order' => array(
      'field_name' => 'commerce_recurring_order',
      'type' => 'entityreference',
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
      'entity_types' => array(
        'commerce_recurring',
      ),
      'module' => 'commerce_recurring',
      'translatable' => FALSE,
      'settings' => array(
        'target_type' => 'commerce_order',
        'handler' => 'base',
        'handler_settings' => array(),
      ),
    ),
  );

  // ER instances.
  $basic_instance = array(
    'entity_type' => 'commerce_recurring',
    'bundle' => 'product',
    'required' => TRUE,
    'settings' => array(),
    'display' => array(),
    'widget' => array(
      'type' => 'entityreference_autocomplete',
      'module' => 'entityreference',
    ),
  );
  $instances = array(
    'commerce_recurring_ref_product' => $basic_instance,
    'commerce_recurring_order' => $basic_instance,
  );
  $instances['commerce_recurring_ref_product']['label'] = t('Referenced product');
  $instances['commerce_recurring_order']['label'] = t('Referenced orders');

  // Create fields & instances if necessary.
  foreach ($fields as $field_name => $field) {
    $info_field = field_info_field($field_name);
    $instance = field_info_instance('commerce_recurring', $field_name, 'product');
    if (empty($info_field)) {
      field_create_field($field);
    }
    if (empty($instance)) {
      $instance = $instances[$field_name];
      $instance['field_name'] = $field_name;
      field_create_instance($instance);
    }
  }

  // @TODO Order bundle.
}

/**
 * Creates a instance of a price field on the specified bundle.
 *
 * @param string $field_name
 *   The name of the field; if it already exists, a new instance of the existing
 *     field will be created. For fields governed by the Commerce modules, this
 *     should begin with commerce_.
 * @param string $entity_type
 *   The type of entity the field instance will be attached to.
 * @param string $bundle
 *   The bundle name of the entity the field instance will be attached to.
 * @param string $label
 *   The label of the field instance.
 * @param int $weight
 *   The default weight of the field instance widget and display.
 * @param bool $calculation
 *   A string indicating the default value of the display formatter's 
 *     calculation setting.
 * @param array $display
 *   An array of default display data used for the entity's current view modes.
 * @param bool $required
 *   Determines if the field is required.
 */
function commerce_recurring_price_create_instance($field_name, $entity_type, $bundle, $label, $weight = 0, $calculation = FALSE, $display = array(), $required = FALSE) {

  // If a field type we know should exist isn't found, clear the Field cache.
  if (!field_info_field_types('commerce_price')) {
    field_cache_clear();
  }

  // Look for or add the specified price field to the requested entity bundle.
  $field = field_info_field($field_name);
  $instance = field_info_instance($entity_type, $field_name, $bundle);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'commerce_price',
      'cardinality' => 1,
      'entity_types' => array(
        $entity_type,
      ),
      'translatable' => FALSE,
    );
    field_create_field($field);
  }
  if (empty($instance)) {
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => $entity_type,
      'bundle' => $bundle,
      'label' => $label,
      'required' => $required,
      'settings' => array(),
      // Because this widget is locked, we need it to use the full price widget
      // since the currency option can't be adjusted at the moment.
      'widget' => array(
        'type' => 'commerce_price_full',
        'weight' => $weight,
        'settings' => array(
          'currency_code' => 'default',
        ),
      ),
      'display' => array(),
    );
    $entity_info = entity_get_info($entity_type);

    // Spoof the default view mode and node teaser so its display type is set.
    $entity_info['view modes'] += array(
      'default' => array(),
      'node_teaser' => array(),
    );
    foreach ($entity_info['view modes'] as $view_mode => $data) {
      $instance['display'][$view_mode] = $display + array(
        'label' => 'hidden',
        'type' => 'commerce_price_formatted_amount',
        'settings' => array(
          'calculation' => $calculation,
        ),
        'weight' => $weight,
      );
    }
    field_create_instance($instance);
  }
}

/**
 * Returns an initialized Commerce Recurring entity.
 *
 * Initializes all the properties defined plus the two entity reference fields.
 *  - product_id: Commerce product id, populates commerce_recurring_ref_product.
 *  - order_ids: Array of Commerce order ids, populates commerce_recurring_order.
 *
 * @see commerce_recurring_entity_property_info()
 * @see commerce_recurring_configure_recurring_entity_type()
 *
 */
function commerce_recurring_new(array $values = array()) {
  $values += array(
    'id' => NULL,
    'type' => isset($values['type']) ? $values['type'] : 'product',
    'status' => 1,
    'start_date' => '',
    'due_date' => '',
    'end_date' => '',
    'uid' => '',
    'quantity' => 1,
    'data' => array(),
  );

  // Retrieve the properties to get from fields.
  if (isset($values['product_id'])) {
    $values['commerce_recurring_ref_product'] = array(
      LANGUAGE_NONE => array(
        0 => array(
          'target_id' => $values['product_id'],
          'taget_type' => 'commerce_product',
        ),
      ),
    );
    unset($values['product_id']);
  }
  if (isset($values['order_ids']) && is_array($values['order_ids'])) {
    foreach ($values['order_ids'] as $delta => $order_id) {
      $values['commerce_recurring_order'] = array(
        LANGUAGE_NONE => array(
          $delta => array(
            'target_id' => $order_id,
            'taget_type' => 'commerce_order',
          ),
        ),
      );
    }
    unset($values['order_ids']);
  }
  return entity_create('commerce_recurring', $values);
}

/**
 * Generate a new recurring entity from a product.
 * We set the price to the initial price from the line item if available.
 * The start date for the recurring is the current date unless a set one is
 * specified.
 * Due date is start date + initial date.
 * End date is optional, start date + end period.
 *
 * @param array $order
 *   Commerce order to associate the recurring entity with.
 * @param array $product
 *   Commerce product associated with the recurring entity.
 * @param int $fixed_price
 * @param int $quantity
 *
 * @return entity $recurring_entity
 */
function commerce_recurring_new_from_product($order, $product, $fixed_price, $quantity) {
  if (empty($product)) {
    return;
  }
  $product_wrapper = entity_metadata_wrapper('commerce_product', $product);
  $date = new DateObject();
  $start_date = $date
    ->getTimestamp();
  $due_date = clone $date;
  if (!empty($product->commerce_recurring_ini_period)) {
    $initial_interval = $product_wrapper->commerce_recurring_ini_period
      ->value();
  }
  if (empty($initial_interval)) {
    if (!empty($product->commerce_recurring_rec_period)) {
      $initial_interval = $product_wrapper->commerce_recurring_rec_period
        ->value();
    }
  }
  if (!empty($initial_interval)) {
    interval_apply_interval($due_date, $initial_interval, TRUE);
  }
  $due_date = $due_date
    ->getTimestamp();
  if (!empty($product->commerce_recurring_end_period)) {
    $end_interval = $product_wrapper->commerce_recurring_end_period
      ->value();
    interval_apply_interval($date, $end_interval, TRUE);
    $end_date = $date
      ->getTimestamp();
  }
  $values = array(
    'product_id' => $product->product_id,
    'order_ids' => array(
      $order->order_id,
    ),
    'uid' => $order->uid,
    'start_date' => $start_date,
    'due_date' => $due_date,
    'end_date' => isset($end_date) ? $end_date : NULL,
  );
  $recurring_entity = commerce_recurring_new($values);

  // Add the price and quantity at the moment of purchase.
  if (!empty($fixed_price)) {
    $recurring_entity->commerce_recurring_fixed_price[LANGUAGE_NONE][0] = $fixed_price;
    $recurring_entity->quantity = empty($quantity) ? 1 : $quantity;
  }
  entity_save('commerce_recurring', $recurring_entity);
  return $recurring_entity;
}

/**
 * Loads a recurring entity by ID.
 */
function commerce_recurring_load($recurring_id) {
  $recurring_entities = commerce_recurring_load_multiple(array(
    $recurring_id,
  ), array());
  return $recurring_entities ? reset($recurring_entities) : FALSE;
}

/**
 * Loads multiple recurring entities by ID or based on a set of matching
 * conditions.
 *
 * @see entity_load()
 *
 * @param $recurring_ids
 *   An array of commerce recurring IDs.
 * @param $conditions
 *   An array of conditions to filter loaded orders by on the
 *   {commerce_recurring} table in the form 'field' => $value.
 * @param $reset
 *   Whether to reset the internal commerce recurring loading cache.
 *
 * @return
 *   An array of order objects indexed by id.
 */
function commerce_recurring_load_multiple(array $recurring_ids = array(), array $conditions = array(), $reset = FALSE) {
  return entity_load('commerce_recurring', $recurring_ids, $conditions, $reset);
}

/**
 * Determine if a product has recurring properties.
 *
 * @param $product
 *   Commerce product.
 * @return bool
 */
function commerce_recurring_product_is_recurring($product) {

  // If the product is of type recurring, we don't need to look anything else.
  if ($product->type == 'recurring') {
    return TRUE;
  }

  // In case of other product types, they still might be recurring ones, check
  // for the presence of any of the recurring fields.
  $fields = array(
    'commerce_recurring_ini_price',
    'commerce_recurring_rec_price',
    'commerce_recurring_ini_period',
    'commerce_recurring_rec_period',
    'commerce_recurring_end_period',
  );
  foreach ($fields as $field_name) {
    if ($items = field_get_items('commerce_product', $product, $field_name)) {
      return TRUE;
    }
  }

  // @TODO: Add a hook to be able to set recurring product types otherwise.
  return FALSE;
}

/**
 * Return all the line items that contain a recurring product in an order
 * context.
 *
 * @param $order
 *   Commerce order.
 *
 * @return array of commerce line items that have recurring capabilities or
 *   empty array if none of the products from the order is recurring.
 */
function commerce_recurring_order_load_recurring_line_items($order) {
  if (empty($order->order_id)) {
    return array();
  }
  $recurring_entities = array();
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
    if (in_array($line_item_wrapper->type
      ->value(), commerce_product_line_item_types())) {
      $product = $line_item_wrapper->commerce_product
        ->value();
      if (commerce_recurring_product_is_recurring($product)) {
        $recurring_entities[$order->order_id][] = $line_item_wrapper
          ->value();
      }
    }
  }
  return $recurring_entities;
}

/**
 * Load all recurring entities in an order context.
 *
 * @param $order
 *   Commerce order.
 * @return array
 */
function commerce_recurring_load_by_order($order) {
  $return = array();
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_recurring')
    ->propertyCondition('status', TRUE)
    ->fieldCondition('commerce_recurring_order', 'target_id', $order->order_id);
  $result = $query
    ->execute();
  if (!empty($result['commerce_recurring'])) {
    foreach ($result['commerce_recurring'] as $recurring) {
      $return[] = entity_load_single('commerce_recurring', $recurring->id);
    }
  }
  return $return;
}

/**
 * Mark a recurring entity as inactive.
 *
 * @param $recurring_entity
 *   A commerce recurring entity.
 */
function commerce_recurring_stop_recurring($recurring_entity) {
  $recurring_entity->status = 0;
  rules_invoke_event('commerce_recurring_stop_recurring', $recurring_entity);
  entity_save('commerce_recurring', $recurring_entity);
}

/**
 * Implements hook_commerce_order_status_info().
 */
function commerce_recurring_commerce_order_status_info() {
  $order_statuses = array();

  // Create a new status before Pending so we can generate orders in recurring
  // processes without intefering with the current cart for the user.
  $order_statuses['recurring_pending'] = array(
    'name' => 'recurring_pending',
    'title' => t('Pending (Recurring order)'),
    'state' => 'pending',
    'weight' => -1,
  );
  return $order_statuses;
}

/**
 * Implements hook_cron().
 */
function commerce_recurring_cron() {

  // Invoke our cron Rules event.
  rules_invoke_all('commerce_recurring_cron');
}

/**
 * Implements hook_permission().
 */
function commerce_recurring_permission() {
  $permissions = array();
  $permissions += commerce_entity_access_permissions('commerce_recurring');
  return $permissions;
}

/**
 * Access callback for the entity API.
 *
 * @param $op
 *   The operation being performed. One of 'view', 'update', 'create', 'delete'
 *   or just 'edit' (being the same as 'create' or 'update').
 * @param null $type
 * @param $account
 *   The user to check for. Leave it to NULL to check for the global user.
 * @return boolean
 *   Whether access is allowed or not.
 */
function commerce_recurring_access($op, $type = NULL, $account = NULL) {
  return user_access('administer commerce_recurring entities', $account);
}

/**
 * Entity uri callback: gives modules a chance to specify a path for a recurring
 * entity.
 * @param $recurring_entity
 * @return null or $uri
 */
function commerce_recurring_uri($recurring_entity) {

  // Allow modules to specify a path, returning the first one found.
  foreach (module_implements('commerce_recurring_uri') as $module) {
    $uri = module_invoke($module, 'commerce_recurring_uri', $recurring_entity);

    // If the implementation returned data, use that now.
    if (!empty($uri)) {
      return $uri;
    }
  }
  return NULL;
}

/**
 * Implements hook_commerce_checkout_router().
 * @param $order
 * @param $checkout_page
 */
function commerce_recurring_commerce_checkout_router($order, $checkout_page) {

  // Just in the last page of the checkout if the order is not anonymous,
  // we get the recurring entities belonging to the order and save them so
  // they don't end up as anonymous.
  if ($checkout_page['page_id'] == 'complete' && !empty($order->uid)) {
    $recurring_entities = commerce_recurring_load_by_order($order);
    foreach ($recurring_entities as $recurring_entity) {
      if (empty($recurring_entity->uid)) {
        $recurring_entity->uid = $order->uid;
        entity_save('commerce_recurring', $recurring_entity);
      }
    }
  }
}

Functions

Namesort descending Description
commerce_recurring_access Access callback for the entity API.
commerce_recurring_commerce_checkout_router Implements hook_commerce_checkout_router().
commerce_recurring_commerce_order_status_info Implements hook_commerce_order_status_info().
commerce_recurring_configure_product_type Ensures that the price and interval fields are created.
commerce_recurring_configure_recurring_entity_type Ensures that the fields for the recurring entity are created.
commerce_recurring_cron Implements hook_cron().
commerce_recurring_entity_info Implements hook_entity_info().
commerce_recurring_load Loads a recurring entity by ID.
commerce_recurring_load_by_order Load all recurring entities in an order context.
commerce_recurring_load_multiple Loads multiple recurring entities by ID or based on a set of matching conditions.
commerce_recurring_new Returns an initialized Commerce Recurring entity.
commerce_recurring_new_from_product Generate a new recurring entity from a product. We set the price to the initial price from the line item if available. The start date for the recurring is the current date unless a set one is specified. Due date is start date + initial date. End date…
commerce_recurring_order_load_recurring_line_items Return all the line items that contain a recurring product in an order context.
commerce_recurring_permission Implements hook_permission().
commerce_recurring_price_create_instance Creates a instance of a price field on the specified bundle.
commerce_recurring_product_is_recurring Determine if a product has recurring properties.
commerce_recurring_stop_recurring Mark a recurring entity as inactive.
commerce_recurring_types Bundles of the recurring entity.
commerce_recurring_uri Entity uri callback: gives modules a chance to specify a path for a recurring entity.