commerce_file.module in Commerce File 7.2
Same filename and directory in other branches
Extends Commerce License with the ability to sell access to files.
File
commerce_file.moduleView 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);
}