You are here

commerce_file.module in Commerce File 7.2

Same filename and directory in other branches
  1. 8.2 commerce_file.module
  2. 7 commerce_file.module

Extends Commerce License with the ability to sell access to files.

File

commerce_file.module
View source
<?php

/**
 * @file
 * Extends Commerce License with the ability to sell access to files.
 */

/**
 * Implements hook_ctools_plugin_directory().
 */
function commerce_file_ctools_plugin_directory($owner, $plugin_type) {
  if ($owner == 'commerce_license') {
    return "plugins/{$plugin_type}";
  }
}

/**
 * Implements hook_views_api().
 */
function commerce_file_views_api() {
  return array(
    'version' => 3,
    'path' => drupal_get_path('module', 'commerce_file') . '/includes/views',
  );
}

/**
 * Implements hook_menu().
 */
function commerce_file_menu() {
  $items['admin/commerce/config/license/file'] = array(
    'title' => 'File',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_file_settings_form',
    ),
    'access arguments' => array(
      'administer licenses',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'includes/commerce_file.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 *
 * Provides a file download callback in case file_entity is missing.
 */
function commerce_file_menu_alter(&$items) {

  // Added via hook_menu_alter() in order to override the same path
  // when provided by file_download_count (since that version doesn't force
  // the file to be downloaded).
  if (!module_exists('file_entity')) {
    $items['file/%file/download'] = array(
      'page callback' => 'commerce_file_download_page',
      'page arguments' => array(
        1,
      ),
      'access callback' => 'commerce_file_access',
      'access arguments' => array(
        'download',
        1,
      ),
      'type' => MENU_CALLBACK,
    );
  }
}

/**
 * Implements hook_admin_paths().
 */
function commerce_file_admin_paths() {
  $paths = array(
    'file/*/downloads' => TRUE,
  );
  return $paths;
}

/**
 * Menu callback; download a single file entity.
 */
function commerce_file_download_page($file) {

  // Ensure there is a valid token to download this file.
  if (!isset($_GET['token']) || $_GET['token'] !== commerce_file_get_download_token($file)) {
    return MENU_ACCESS_DENIED;
  }

  // If the file does not exist it can cause problems with file_transfer().
  if (!is_file($file->uri)) {
    return MENU_NOT_FOUND;
  }
  $headers = array(
    'Content-Type' => mime_header_encode($file->filemime),
    'Content-Disposition' => 'attachment; filename="' . $file->filename . '"',
    'Content-Length' => $file->filesize,
    'Content-Transfer-Encoding' => 'binary',
    'Pragma' => 'no-cache',
    'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
    'Expires' => '0',
  );

  // Allow file_download_count to track the download.
  if (module_exists('file_download_count')) {
    file_download_count_track_file_download($file);
  }

  // Allow the S3 integration to kick in.
  commerce_file_file_download_headers_alter($headers, $file);
  file_transfer($file->uri, $headers);
}

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

    // Place this module's implementation of hook_file_download_headers_alter()
    // at the end of the invocation list. This allows other modules (like
    // file_download_count) to do their thing before the user is redirected
    // to S3.
    $group = $implementations['commerce_file'];
    unset($implementations['commerce_file']);
    $implementations['commerce_file'] = $group;
  }
}

/**
 * Implements hook_file_download_headers_alter().
 *
 * If the file is hosted on S3, redirects to the S3 url.
 * This is more optimal than allowing file_transfer() to be called, which would
 * download the file from S3 to the server, and then send it to the user.
 *
 * Doing drupal_goto() from an alter hook is hacky, but it removes
 * the need to override file_entity's download page.
 */
function commerce_file_file_download_headers_alter(array &$headers, $file) {

  // If the file is licensable, ensure that its download gets logged.
  if (commerce_file_is_licensable($file)) {
    commerce_file_set_request_file($file);
  }

  // Redirect to S3 if needed.
  if (file_uri_scheme($file->uri) == 's3') {
    $url = file_create_url($file->uri);
    drupal_goto($url);
  }
}

/**
 * Implements hook_permission().
 */
function commerce_file_permission() {
  return array(
    'bypass license control' => array(
      'title' => t('Bypass license control'),
      'description' => t('Download files without having a license.'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Checks whether the provided user is allowed to bypass license control.
 *
 * This allows the user to download a file without having a license.
 * Since there's no license, download limits can't be checked and respected.
 *
 * @param $account
 *   The account to check for. If not given, the current user is used instead.
 *
 * @return
 *   TRUE if the account is allowed to bypass license control, FALSE
 *   otherwise.
 */
function commerce_file_bypass_license_control($account = NULL) {
  return user_access('bypass license control', $account) || user_access('administer licenses', $account);
}

/**
 * Implements hook_file_entity_access().
 */
function commerce_file_file_entity_access($op, $file, $account) {
  if (in_array($op, array(
    'download',
    'view',
  ))) {
    $access = commerce_file_access($op, $file, $account);
    return $access ? FILE_ENTITY_ACCESS_ALLOW : FILE_ENTITY_ACCESS_DENY;
  }
  else {
    return FILE_ENTITY_ACCESS_IGNORE;
  }
}

/**
 * Implements hook_file_download().
 */
function commerce_file_file_download($uri) {
  $files = file_load_multiple(array(), array(
    'uri' => $uri,
  ));
  if (count($files)) {
    foreach ($files as $item) {

      // Since some database servers sometimes use a case-insensitive comparison
      // by default, double check that the filename is an exact match.
      if ($item->uri === $uri) {
        $file = $item;
        break;
      }
    }
  }

  // No file found, or a temporary file found, do nothing.
  if (!isset($file) || $file->status != FILE_STATUS_PERMANENT) {
    return;
  }

  // This file is not licensable, do nothing.
  if (!commerce_file_is_licensable($file)) {
    return;
  }
  $op = 'download';

  // If we're checking access for an image derivative, check access for "view",
  // not "download". Derivatives don't increase the download count and work
  // even when the download limit has been reached.
  $menu_item = menu_get_item();
  if ($menu_item['path'] == 'system/files/styles/%') {
    $op = 'view';
  }

  // Perform the access check, and bubble-up any failures.
  if (!commerce_file_access($op, $file)) {
    return -1;
  }

  // Ensure that the download gets logged.
  if ($op == 'download') {
    commerce_file_set_request_file($file);
  }
}

/**
 * Determines if a user may perform the given operation on the licensed file.
 *
 * A file is licensed if the user has a license for the parent product.
 *
 * Note: When checking "view" access for all files of a product, it's more
 * performant to simply check whether commerce_file_get_product_license()
 * returned a license.
 *
 * @param $op
 *   The operation to be performed on the licensed file. Possible values are:
 *   - "view"
 *   - "download"
 * @param $file
 *   The file entity.
 * @param $account
 *   Optional, a user object representing the user for whom the operation is to
 *   be performed. Determines access for a user other than the current user.
 *
 * @return
 *   TRUE if the file is not licensable, or the user has access.
 *   FALSE otherwise.
 */
function commerce_file_access($op, $file, $account = NULL) {
  if (!$account) {
    $account = $GLOBALS['user'];
  }

  // Checkout complete account override for anonymous users.
  if (!empty($_SESSION['commerce_license_uid']) && empty($account->uid)) {
    $account = user_load($_SESSION['commerce_license_uid']);
  }

  // No need to check access.
  if (commerce_file_bypass_license_control($account)) {
    return TRUE;
  }

  // This file is not licensable.
  if (!commerce_file_is_licensable($file)) {
    return TRUE;
  }

  // Look for a valid license.
  $license = commerce_file_get_license($op, $file, $account);
  return !empty($license);
}

/**
 * Checks whether the given file is licensable.
 *
 * A file is licensable if it's referenced from at least one product's
 * commerce_file field.
 *
 * @param $file
 *   The file entity.
 *
 * @return
 *   TRUE if the file is licensable, FALSE otherwise.
 */
function commerce_file_is_licensable($file) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'commerce_product')
    ->fieldCondition('commerce_file', 'fid', $file->fid)
    ->count();
  $result = $query
    ->execute();
  return $result > 0;
}

/**
 * Returns an eligible active license for the given file.
 *
 * A file could be sold from multiple products. The user's active licenses
 * for all of them are loaded, and the first eligible one is returned.
 *
 * @param $op
 *   The operation to be performed on the licensed file. Possible values are:
 *   - "view"
 *   - "download"
 * @param $file
 *   The file entity.
 * @param $account
 *   The account to check for. If not given, the current user is used instead.
 *
 * @return
 *   The license if found, FALSE otherwise.
 */
function commerce_file_get_license($op, $file, $account = NULL) {
  if (!$account) {
    $account = $GLOBALS['user'];
  }

  // Checkout complete account override.
  if (!empty($_SESSION['commerce_license_uid']) && empty($account->uid)) {
    $account = user_load($_SESSION['commerce_license_uid']);
  }

  // Ignore anonymous users, they can't have licenses.
  if (empty($account->uid)) {
    return FALSE;
  }
  $licenses =& drupal_static(__FUNCTION__, array());
  if (!isset($licenses[$file->fid])) {
    $licenses[$file->fid] = FALSE;

    // Get all products that offer the provided file.
    $query = new EntityFieldQuery();
    $query
      ->entityCondition('entity_type', 'commerce_product')
      ->fieldCondition('commerce_file', 'fid', $file->fid);
    $result = $query
      ->execute();
    if (!empty($result['commerce_product'])) {
      $product_ids = array_keys($result['commerce_product']);

      // Get the user's licenses for any of the found product ids.
      // The oldest active licenses are used first.
      $query = new EntityFieldQuery();
      $query
        ->entityCondition('entity_type', 'commerce_license')
        ->entityCondition('bundle', 'file')
        ->propertyCondition('status', COMMERCE_LICENSE_ACTIVE)
        ->propertyCondition('product_id', $product_ids)
        ->propertyCondition('uid', $account->uid)
        ->entityOrderby('entity_id', 'ASC');
      $result = $query
        ->execute();
      if (!empty($result['commerce_license'])) {
        $license_ids = array_keys($result['commerce_license']);
        $loaded_licenses = entity_load('commerce_license', $license_ids);

        // Go through all loaded licenses and check their eligibility.
        foreach ($loaded_licenses as $license) {
          $op_view = $op == 'view';
          $op_download = $op == 'download' && commerce_file_can_download($license, $file, $account);
          if ($op_view || $op_download) {

            // License found, stop the search.
            $licenses[$file->fid] = $license;
            break;
          }
        }
      }
    }
  }
  return $licenses[$file->fid];
}

/**
 * Returns the active file license for the given product.
 *
 * The user can only have one active file license per product.
 *
 * @param $product
 *   The product entity.
 * @param $account
 *   The account to check for. If not given, the current user is used instead.
 *
 * @return
 *   The active license if found, FALSE otherwise.
 */
function commerce_file_get_product_license($product, $account = NULL) {
  if (!$account) {
    $account = $GLOBALS['user'];
  }

  // Checkout complete account override.
  if (!empty($_SESSION['commerce_license_uid']) && empty($account->uid)) {
    $account = user_load($_SESSION['commerce_license_uid']);
  }

  // Ignore anonymous users, they can't have licenses.
  if (empty($account->uid)) {
    return FALSE;
  }
  $licenses =& drupal_static(__FUNCTION__, array());
  if (!isset($licenses[$product->product_id])) {
    $licenses[$product->product_id] = FALSE;
    $query = new EntityFieldQuery();
    $query
      ->entityCondition('entity_type', 'commerce_license')
      ->entityCondition('bundle', 'file')
      ->propertyCondition('status', COMMERCE_LICENSE_ACTIVE)
      ->propertyCondition('product_id', $product->product_id)
      ->propertyCondition('uid', $account->uid);
    $result = $query
      ->execute();
    if (!empty($result['commerce_license'])) {
      $license_ids = array_keys($result['commerce_license']);
      $licenses[$product->product_id] = entity_load_single('commerce_license', reset($license_ids));
    }
  }
  return $licenses[$product->product_id];
}

/**
 * Returns whether the licensed file can be downloaded.
 *
 * Checks download limits (if download limits are configured) and invokes a
 * hook to allow other modules to check their own limits.
 *
 * @param $license
 *   The license entity.
 * @param $file
 *   The file entity
 * @param $account
 *   The user to check for. Leave it to NULL to check for the current user.
 *
 * @return
 *   TRUE if the file can be downloaded, FALSE otherwise.
 */
function commerce_file_can_download($license, $file, $account = NULL) {
  if (!$account) {
    $account = $GLOBALS['user'];
  }

  // Checkout complete account override for anonymous users.
  if (!empty($_SESSION['commerce_license_uid']) && empty($account->uid)) {
    $account = user_load($_SESSION['commerce_license_uid']);
  }

  // No need to check access.
  if (commerce_file_bypass_license_control($account)) {
    return TRUE;
  }
  $enable_limit = variable_get('commerce_file_enable_download_limit', FALSE);
  $download_limit = commerce_file_get_download_limit($license, $file, $account);
  if ($enable_limit) {
    $counts = commerce_file_download_log_get_counts($license, array(
      $file->fid,
    ));
    if ($counts[$file->fid] >= $download_limit) {
      return FALSE;
    }
  }

  // Allow other modules to forbid the download.
  foreach (module_implements('commerce_file_can_download') as $module) {
    $result = module_invoke($module, 'commerce_file_can_download', $license, $file, $account);
    if (!$result) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Returns the file's download limit.
 *
 * Checks for a download limit variable and invokes a
 * hook to allow other modules to set their own limits.
 *
 * @param $license
 *   The license entity.
 * @param $file
 *   The file entity
 * @param $account
 *   The user to check for. Leave it to NULL to check for the current user.
 *
 * @return
 *   $download_limit as an integer.
 */
function commerce_file_get_download_limit($license, $file, $account) {
  $download_limit = variable_get('commerce_file_download_limit', 100);
  foreach (module_implements('commerce_file_get_download_limit') as $module) {
    $download_limit = module_invoke($module, 'commerce_file_get_download_limit', $license, $file, $account);
  }
  return $download_limit;
}

/**
 * Retrieves the file currently being downloaded.
 */
function commerce_file_get_request_file() {
  return commerce_file_set_request_file();
}

/**
 * Sets the file currently being downloaded.
 *
 * @param $file
 *   The file being downloaded, or NULL to return the current file instead.
 */
function commerce_file_set_request_file($file = NULL) {
  $cached_file =& drupal_static(__FUNCTION__);
  if (isset($file)) {
    $cached_file = $file;
  }
  return $cached_file;
}

/**
 * Log the download of a file.
 *
 * @param $license
 *   The license of the downloaded file.
 * @param $file
 *   The downloaded file.
 */
function commerce_file_download_log_insert($license, $file) {
  db_insert('commerce_file_download_log')
    ->fields(array(
    'license_id' => $license->license_id,
    'fid' => $file->fid,
    'uid' => $license->uid,
    'timestamp' => REQUEST_TIME,
    'ip_address' => ip_address(),
  ))
    ->execute();
}

/**
 * Get the download counts for the provided license and its files.
 *
 * @param $license
 *   The license entity.
 * @param $fids
 *   An optional array of file ids. If empty, they are taken from the license.
 *
 * @return
 *   An array of download counts, keed by file id.
 */
function commerce_file_download_log_get_counts($license, $fids = array()) {
  $counts =& drupal_static(__FUNCTION__, array());
  $license_id = $license->license_id;

  // No fids provided, generate our own array with all of them.
  if (empty($fids)) {
    foreach ($license->wrapper->product->commerce_file
      ->raw() as $file_item) {
      $fids[] = $file_item['fid'];
    }
  }
  $requested_counts = array();

  // First get any available counts from the static cache.
  foreach ($fids as $index => $fid) {
    if (isset($counts[$license_id][$fid])) {
      $requested_counts[$fid] = $counts[$license_id][$fid];
      unset($fids[$index]);
    }
  }

  // Query for the remaining requested counts.
  if (count($fids)) {
    $query = "SELECT fid, COUNT(fid) FROM {commerce_file_download_log}\n                WHERE fid IN (:fids) AND license_id = :license_id\n                  GROUP BY fid";
    $args = array(
      ':fids' => $fids,
      ':license_id' => $license->license_id,
    );
    $fetched_counts = db_query($query, $args)
      ->fetchAllKeyed(0);
    foreach ($fids as $fid) {
      $count = isset($fetched_counts[$fid]) ? $fetched_counts[$fid] : 0;
      $counts[$license_id][$fid] = $count;
      $requested_counts[$fid] = $count;
    }
  }
  return $requested_counts;
}

/**
 * Clear the download log.
 *
 * @param $conditions
 *   An array of conditions in the key => $value format.
 *   Possible keys: license_id, uid, fid.
 */
function commerce_file_download_log_clear($conditions) {
  $delete_query = db_delete('commerce_file_download_log');
  foreach ($conditions as $key => $value) {
    $delete_query
      ->condition($key, $value);
  }
  $delete_query
    ->execute();
}

/**
 * Implements hook_exit().
 *
 * Logs the completed file download.
 */
function commerce_file_exit($destination = NULL) {

  // If access was denied after our access checks, the download never happened.
  $status = drupal_get_http_header('Status');
  if ($status && substr($status, 0, 3) == '403') {
    return;
  }
  $file = commerce_file_get_request_file();
  $enable_limit = variable_get('commerce_file_enable_download_limit', FALSE);
  if ($file && $enable_limit) {

    // The license is already in the commerce_file_get_license() static cache.
    $license = commerce_file_get_license('download', $file);

    // Skip logging if the admin is downloading another user's file.
    if ($license && $license->uid == $GLOBALS['user']->uid) {
      commerce_file_download_log_insert($license, $file);
    }
  }
}

/**
 * Implements hook_commerce_license_types_list_alter().
 *
 * Removes the File license type option from those product types that don't
 * have it configured.
 */
function commerce_file_commerce_license_types_list_alter(&$types, $product) {
  if (!empty($product) && !in_array($product->type, commerce_file_product_types())) {
    unset($types['file']);
  }
}

/**
 * Implements hook_flush_caches().
 *
 * Ensures that products have the required commerce_file field.
 */
function commerce_file_flush_caches() {
  $product_types = commerce_file_product_types();
  commerce_file_configure_product_types($product_types);
}

/**
 * Return a list of product types used for file licensing.
 *
 * @return
 *   An array of product type machine names.
 */
function commerce_file_product_types() {
  $file_product_types = variable_get('commerce_file_product_types', array());
  $file_product_types = array_filter($file_product_types);

  // Return only those $file_product_types that are still licensable.
  $license_product_types = commerce_license_product_types();
  return array_intersect($file_product_types, $license_product_types);
}

/**
 * Ensures that the provided product types have the required fields.
 *
 * Fields:
 * - commerce_file: a file field holding the licensable files.
 *
 * @param $types
 *   An array of product type machine names.
 */
function commerce_file_configure_product_types($types) {
  $field = field_info_field('commerce_file');
  if (!$field) {
    $field = array(
      'field_name' => 'commerce_file',
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
      'type' => 'file',
      'entity_types' => array(
        'commerce_product',
      ),
      'settings' => array(
        'uri_scheme' => _commerce_file_default_scheme(),
      ),
    );
    field_create_field($field);
  }
  $existing = array();
  if (!empty($field['bundles']['commerce_product'])) {
    $existing = $field['bundles']['commerce_product'];
  }

  // Create instances on newly configured product types.
  foreach (array_diff($types, $existing) as $new_bundle) {
    $instance = array(
      'field_name' => 'commerce_file',
      'entity_type' => 'commerce_product',
      'bundle' => $new_bundle,
      'label' => t('Files'),
      'required' => TRUE,
      'settings' => array(
        'file_extensions' => 'mp4 m4v flv wmv mp3 wav jpg jpeg png pdf doc docx ppt pptx xls xlsx',
        'description_field' => 1,
      ),
      'widget' => array(
        'type' => 'file_generic',
      ),
      'display' => array(
        'default' => array(
          'label' => 'above',
          'module' => 'commerce_file',
          'type' => 'commerce_file',
          'settings' => array(),
        ),
      ),
    );
    field_create_instance($instance);
  }

  // Remove instances from product types that can no longer have file licenses.
  foreach (array_diff($existing, $types) as $removed_bundle) {
    $instance = field_info_instance('commerce_product', 'commerce_file', $removed_bundle);
    field_delete_instance($instance, TRUE);
  }
}

/**
 * Returns the default scheme to be used for the commerce_file field.
 */
function _commerce_file_default_scheme() {
  $private_schemes = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);

  // Remove the public scheme.
  unset($private_schemes['public']);
  $default_scheme = file_default_scheme();
  if (isset($private_schemes[$default_scheme])) {

    // Try to use the default, if it fits our criteria.
    return $default_scheme;
  }
  elseif (!empty($private_schemes)) {

    // Otherwise try to use the first one that fits our criteria.
    return key($private_schemes);
  }
  else {

    // There are no usable choices. Return "private" so that it's usable
    // once the user configures the private file system.
    return 'private';
  }
}

/**
 * Implements hook_theme().
 */
function commerce_file_theme() {
  return array(
    'commerce_file_download_link' => array(
      'variables' => array(
        'file' => NULL,
        'license' => NULL,
        'icon' => NULL,
        'filesize' => NULL,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_info().
 */
function commerce_file_field_formatter_info() {
  $info['commerce_file'] = array(
    'label' => t('Commerce file'),
    'description' => t('Displays a link that will force the browser to download the file.'),
    'field types' => array(
      'file',
    ),
    'settings' => array(
      'show_icon' => TRUE,
      'show_filesize' => FALSE,
      'check_access' => TRUE,
    ),
  );
  return $info;
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function commerce_file_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $element = array();
  if ($display['type'] = 'commerce_file') {
    $element['show_icon'] = array(
      '#type' => 'checkbox',
      '#title' => t('Show the file icon'),
      '#default_value' => $settings['show_icon'],
    );
    $element['show_filesize'] = array(
      '#type' => 'checkbox',
      '#title' => t('Show filesize'),
      '#description' => t('Include the size of the file in the link.'),
      '#default_value' => $settings['show_filesize'],
    );
    $element['check_access'] = array(
      '#type' => 'checkbox',
      '#title' => t('Check access'),
      '#description' => t('Confirms that the user has a file license.'),
      '#default_value' => $settings['show_icon'],
    );
  }
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function commerce_file_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $show_icon = $display['settings']['show_icon'] ? t('Yes') : t('No');
  $show_filesize = $display['settings']['show_filesize'] ? t('Yes') : t('No');
  $check_access = $display['settings']['check_access'] ? t('Yes') : t('No');
  $summary = array();
  $summary[] = t('Show the file icon') . ': ' . $show_icon;
  $summary[] = t('Show the filesize') . ': ' . $show_filesize;
  $summary[] = t('Check access') . ': ' . $check_access;
  return implode('<br />', $summary);
}

/**
 * Implements hook_field_formatter_view().
 */
function commerce_file_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $settings = $display['settings'];
  $element = array();
  if ($display['type'] == 'commerce_file') {

    // This formatter only works on commerce_file fields attached to products.
    if ($entity_type != 'commerce_product') {
      return $element;
    }
    $license = commerce_file_get_product_license($entity);
    $access = TRUE;
    if (!empty($settings['check_access'])) {
      $access = $license || commerce_file_bypass_license_control();
    }
    foreach ($items as $delta => $item) {
      $file = (object) $item;
      $icon = NULL;
      if (!empty($settings['show_icon'])) {
        $icon = theme('file_icon', array(
          'file' => $file,
        ));
      }
      $filesize = NULL;
      if (!empty($settings['show_filesize'])) {
        $filesize = format_size($file->filesize);
      }
      if ($access) {
        $element[$delta] = array(
          '#theme' => 'commerce_file_download_link',
          '#file' => $file,
          '#filesize' => $filesize,
          '#license' => $license,
          '#icon' => $icon,
        );
      }
    }
  }
  return $element;
}

/**
 * Copy of theme_file_file_link() for linking to the file download URL.
 *
 * @see theme_file_file_link()
 */
function theme_commerce_file_download_link($variables) {
  $file = $variables['file'];
  $uri = array(
    'path' => "file/{$file->fid}/download",
    'options' => array(),
  );
  $uri['options']['query']['token'] = commerce_file_get_download_token($file);

  // Set options as per anchor format described at
  // http://microformats.org/wiki/file-format-examples
  $uri['options']['attributes']['type'] = $file->filemime . '; length=' . $file->filesize;

  // Use the description as link text if found, or fallback to the filename.
  $label = !empty($file->description) ? $file->description : $file->filename;

  // Prepare the icon.
  $icon = ' ';
  if (!empty($variables['icon'])) {
    $icon = $variables['icon'] . ' ';
  }

  // Prepare the filesize
  $filesize = '';
  if (!empty($variables['filesize'])) {
    $filesize = ' (' . $variables['filesize'] . ')';
  }
  if (empty($variables['license'])) {
    $can_download = commerce_file_bypass_license_control();
  }
  else {

    // The file that this theme function receives is just the filefield item
    // array converted to an object (yay, D7!). The limit download check needs
    // the actual file entity.
    $real_file = file_load($file->fid);
    $can_download = commerce_file_can_download($variables['license'], $real_file);
  }
  $output = '<span class="file">' . $icon;
  $output .= $can_download ? l($label, $uri['path'], $uri['options']) : $label;
  $output .= $filesize;
  $output .= ' </span>';
  return $output;
}

/**
 * Generates a token to protect a file download URL.
 *
 * This prevents unauthorized crawling of all file download URLs since the
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
 * to guess or attempt many at once. This can be costly both in CPU time
 * and bandwidth.
 *
 * Copy of file_entity_get_download_token().
 *
 * @param object $file
 *   A file entity object.
 *
 * @return string
 *   An eight-character token which can be used to protect file downloads
 *   against denial-of-service attacks.
 */
function commerce_file_get_download_token($file) {

  // Return the first eight characters.
  return substr(drupal_hmac_base64("file/{$file->fid}/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
}

Functions

Namesort descending Description
commerce_file_access Determines if a user may perform the given operation on the licensed file.
commerce_file_admin_paths Implements hook_admin_paths().
commerce_file_bypass_license_control Checks whether the provided user is allowed to bypass license control.
commerce_file_can_download Returns whether the licensed file can be downloaded.
commerce_file_commerce_license_types_list_alter Implements hook_commerce_license_types_list_alter().
commerce_file_configure_product_types Ensures that the provided product types have the required fields.
commerce_file_ctools_plugin_directory Implements hook_ctools_plugin_directory().
commerce_file_download_log_clear Clear the download log.
commerce_file_download_log_get_counts Get the download counts for the provided license and its files.
commerce_file_download_log_insert Log the download of a file.
commerce_file_download_page Menu callback; download a single file entity.
commerce_file_exit Implements hook_exit().
commerce_file_field_formatter_info Implements hook_field_formatter_info().
commerce_file_field_formatter_settings_form Implements hook_field_formatter_settings_form().
commerce_file_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
commerce_file_field_formatter_view Implements hook_field_formatter_view().
commerce_file_file_download Implements hook_file_download().
commerce_file_file_download_headers_alter Implements hook_file_download_headers_alter().
commerce_file_file_entity_access Implements hook_file_entity_access().
commerce_file_flush_caches Implements hook_flush_caches().
commerce_file_get_download_limit Returns the file's download limit.
commerce_file_get_download_token Generates a token to protect a file download URL.
commerce_file_get_license Returns an eligible active license for the given file.
commerce_file_get_product_license Returns the active file license for the given product.
commerce_file_get_request_file Retrieves the file currently being downloaded.
commerce_file_is_licensable Checks whether the given file is licensable.
commerce_file_menu Implements hook_menu().
commerce_file_menu_alter Implements hook_menu_alter().
commerce_file_module_implements_alter Implements hook_module_implements_alter().
commerce_file_permission Implements hook_permission().
commerce_file_product_types Return a list of product types used for file licensing.
commerce_file_set_request_file Sets the file currently being downloaded.
commerce_file_theme Implements hook_theme().
commerce_file_views_api Implements hook_views_api().
theme_commerce_file_download_link Copy of theme_file_file_link() for linking to the file download URL.
_commerce_file_default_scheme Returns the default scheme to be used for the commerce_file field.