FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View File

@@ -0,0 +1,522 @@
<?php
/**
* @file
* File administration menu items.
*/
/**
* Form step values.
*/
define('UC_FILE_FORM_FILES' , NULL);
define('UC_FILE_FORM_ACTION', 1 );
/**
* Form builder for file products admin.
*
* @see uc_file_admin_files_form_show_files()
* @see uc_file_admin_files_form_action()
* @ingroup forms
*/
function uc_file_admin_files_form($form, &$form_state) {
if (isset($form_state['storage']['step']) && $form_state['storage']['step'] == UC_FILE_FORM_ACTION) {
return array(
'#validate' => array('uc_file_admin_files_form_action_validate'),
'#submit' => array('uc_file_admin_files_form_action_submit'),
) + $form + uc_file_admin_files_form_action($form, $form_state);
}
else {
// Refresh our file list before display.
uc_file_refresh();
return array(
'#theme' => 'uc_file_admin_files_form_show',
'#validate' => array('uc_file_admin_files_form_show_validate'),
'#submit' => array('uc_file_admin_files_form_show_submit'),
) + $form + uc_file_admin_files_form_show_files($form, $form_state);
}
}
/**
* Displays all files that may be purchased and downloaded for administration.
*
* @see uc_file_admin_files_form()
* @see uc_file_admin_files_form_show_validate()
* @see uc_file_admin_files_form_show_submit()
* @see theme_uc_file_admin_files_form_show()
* @ingroup forms
*/
function uc_file_admin_files_form_show_files($form, &$form_state) {
$form['#tree'] = TRUE;
$header = array(
'filename' => array('data' => t('File'), 'field' => 'f.filename', 'sort' => 'asc'),
'title' => array('data' => t('Product'), 'field' => 'n.title'),
'model' => array('data' => t('SKU'), 'field' => 'fp.model')
);
// Create pager.
$query = db_select('uc_files', 'f')->extend('PagerDefault')->extend('TableSort')
->orderByHeader($header)
->limit(UC_FILE_PAGER_SIZE);
$query->leftJoin('uc_file_products', 'fp', 'f.fid = fp.fid');
$query->leftJoin('uc_product_features', 'pf', 'fp.pfid = pf.pfid');
$query->leftJoin('node', 'n', 'pf.nid = n.nid');
$query->addField('n', 'nid');
$query->addField('f', 'filename');
$query->addField('n', 'title');
$query->addField('fp', 'model');
$query->addField('f', 'fid');
$query->addField('pf', 'pfid');
$count_query = db_select('uc_files');
$count_query->addExpression('COUNT(*)');
$query->setCountQuery($count_query);
$result = $query->execute();
$options = array();
foreach ($result as $file) {
$options[$file->fid] = array(
'filename' => array(
'data' => check_plain($file->filename),
'class' => is_dir(uc_file_qualify_file($file->filename)) ? array('uc-file-directory-view') : array(),
),
'title' => l($file->title, 'node/' . $file->nid),
'model' => check_plain($file->model),
);
}
// Create checkboxes for each file.
$form['file_select'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#empty' => t('No file downloads available.'),
);
$form['uc_file_action'] = array(
'#type' => 'fieldset',
'#title' => t('File options'),
'#collapsible' => FALSE,
'#collapsed' => FALSE,
);
// Set our default actions.
$file_actions = array(
'uc_file_upload' => t('Upload file'),
'uc_file_delete' => t('Delete file(s)'),
);
// Check if any hook_uc_file_action('info', $args) are implemented
foreach (module_implements('uc_file_action') as $module) {
$name = $module . '_uc_file_action';
$result = $name('info', NULL);
if (is_array($result)) {
foreach ($result as $key => $action) {
if ($key != 'uc_file_delete' && $key != 'uc_file_upload') {
$file_actions[$key] = $action;
}
}
}
}
$form['uc_file_action']['action'] = array(
'#type' => 'select',
'#title' => t('Action'),
'#options' => $file_actions,
'#prefix' => '<div class="duration">',
'#suffix' => '</div>',
);
$form['uc_file_actions']['actions'] = array('#type' => 'actions');
$form['uc_file_action']['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Perform action'),
'#prefix' => '<div class="duration">',
'#suffix' => '</div>',
);
return $form;
}
/**
* Ensures at least one file is selected when deleting.
*
* @see uc_file_admin_files_form_show_files()
* @see uc_file_admin_files_form_show_submit()
*/
function uc_file_admin_files_form_show_validate($form, &$form_state) {
switch ($form_state['values']['uc_file_action']['action']) {
case 'uc_file_delete':
$file_ids = array();
if (is_array($form_state['values']['file_select'])) {
foreach ($form_state['values']['file_select'] as $fid => $value) {
if ($value) {
$file_ids[] = $fid;
}
}
}
if (count($file_ids) == 0) {
form_set_error('', t('You must select at least one file to delete.'));
}
break;
}
}
/**
* Moves to the next step of the administration form.
*
* @see uc_file_admin_files_form_show_files()
* @see uc_file_admin_files_form_show_validate()
*/
function uc_file_admin_files_form_show_submit($form, &$form_state) {
// Increment the form step.
$form_state['storage']['step'] = UC_FILE_FORM_ACTION;
$form_state['rebuild'] = TRUE;
}
/**
* Returns HTML for uc_file_admin_files_form_show().
*
* @param $variables
* An associative array containing:
* - form: A render element representing the form.
*
* @see uc_file_admin_files_form_show_files()
* @ingroup themeable
*/
function theme_uc_file_admin_files_form_show($variables) {
$form = $variables['form'];
// Render everything.
$output = '<p>' . t('File downloads can be attached to any Ubercart product as a product feature. For security reasons the <a href="!download_url">file downloads directory</a> is separated from the Drupal <a href="!file_url">file system</a>. Below is the list of files (and their associated Ubercart products, if any) that can be used for file downloads.', array('!download_url' => url('admin/store/settings/products', array('query' => array('destination' => 'admin/store/products/files'))), '!file_url' => url('admin/config/media/file-system'))) . '</p>';
$output .= drupal_render($form['uc_file_action']);
$output .= drupal_render($form['file_select']);
$output .= theme('pager');
$output .= drupal_render_children($form);
return $output;
}
/**
* Performs file action (upload, delete, hooked in actions).
*
* @see uc_file_admin_files_form()
* @see uc_file_admin_files_form_action_validate()
* @see uc_file_admin_files_form_action_submit()
* @ingroup forms
*/
function uc_file_admin_files_form_action($form, &$form_state) {
$file_ids = array_filter($form_state['values']['file_select']);
$form['file_ids'] = array('#type' => 'value', '#value' => $file_ids);
$form['action'] = array('#type' => 'value', '#value' => $form_state['values']['uc_file_action']['action']);
$file_ids = _uc_file_sort_names(_uc_file_get_dir_file_ids($file_ids, FALSE));
switch ($form_state['values']['uc_file_action']['action']) {
case 'uc_file_delete':
$affected_list = _uc_file_build_js_file_display($file_ids);
$has_directory = FALSE;
foreach ($file_ids as $file_id) {
// Gather a list of user-selected filenames.
$file = uc_file_get_by_id($file_id);
$filename = $file->filename;
$file_list[] = (substr($filename, -1) == "/") ? $filename . ' (' . t('directory') . ')' : $filename;
// Determine if there are any directories in this list.
$path = uc_file_qualify_file($filename);
if (is_dir($path)) {
$has_directory = TRUE;
}
}
// Base files/dirs the user selected.
$form['selected_files'] = array(
'#theme' => 'item_list',
'#items' => $file_list,
'#attributes' => array(
'class' => array('selected-file-name'),
),
);
$form = confirm_form(
$form, t('Delete file(s)'),
'admin/store/products/files',
t('Deleting a file will remove all its associated file downloads and product features. Removing a directory will remove any files it contains and their associated file downloads and product features.'),
t('Delete affected files'), t('Cancel')
);
// Don't even show the recursion checkbox unless we have any directories.
if ($has_directory && $affected_list[TRUE] !== FALSE ) {
$form['recurse_directories'] = array(
'#type' => 'checkbox',
'#title' => t('Delete selected directories and their sub directories'),
);
// Default to FALSE. Although we have the JS behavior to update with the
// state of the checkbox on load, this should improve the experience of
// users who don't have JS enabled over not defaulting to any info.
$form['affected_files'] = array(
'#theme' => 'item_list',
'#items' => $affected_list[FALSE],
'#title' => t('Affected files'),
'#attributes' => array(
'class' => array('affected-file-name'),
),
);
}
break;
case 'uc_file_upload':
// Calculate the max size of uploaded files, in bytes.
$max_bytes = trim(ini_get('post_max_size'));
switch (strtolower($max_bytes{strlen($max_bytes)-1})) {
case 'g':
$max_bytes *= 1024;
case 'm':
$max_bytes *= 1024;
case 'k':
$max_bytes *= 1024;
}
// Gather list of directories under the selected one(s).
// '/' is always available.
$directories = array('' => '/');
$files = db_query("SELECT * FROM {uc_files}");
foreach ($files as $file) {
if (is_dir(variable_get('uc_file_base_dir', NULL) . "/" . $file->filename)) {
$directories[$file->filename] = $file->filename;
}
}
$form['upload_dir'] = array(
'#type' => 'select',
'#title' => t('Directory'),
'#description' => t('The directory on the server where the file should be put. The default directory is the root of the file downloads directory.'),
'#options' => $directories,
);
$form['upload'] = array(
'#type' => 'file',
'#title' => t('File'),
'#description' => t('The maximum file size that can be uploaded is %size bytes. You will need to use a different method to upload the file to the directory (e.g. (S)FTP, SCP) if your file exceeds this size. Files you upload using one of these alternate methods will be automatically detected.', array('%size' => number_format($max_bytes))),
);
$form['#attributes']['class'][] = 'foo';
$form = confirm_form(
$form, t('Upload file'),
'admin/store/products/files',
'',
t('Upload file'), t('Cancel')
);
// Must add this after confirm_form, as it runs over $form['#attributes'].
// Issue logged at d#319723
$form['#attributes']['enctype'] = 'multipart/form-data';
break;
default:
// This action isn't handled by us, so check if any
// hook_uc_file_action('form', $args) are implemented.
foreach (module_implements('uc_file_action') as $module) {
$name = $module . '_uc_file_action';
$result = $name('form', array('action' => $form_state['values']['uc_file_action']['action'], 'file_ids' => $file_ids));
$form = (is_array($result)) ? array_merge($form, $result) : $form;
}
break;
}
return $form;
}
/**
* Validation handler for uc_file_admin_files_form_action().
*
* @see uc_file_admin_files_form_action()
* @see uc_file_admin_files_form_action_submit()
*/
function uc_file_admin_files_form_action_validate($form, &$form_state) {
switch ($form_state['values']['action']) {
case 'uc_file_upload':
// Upload the file and get its object.
if ($temp_file = file_save_upload('upload', array('file_validate_extensions' => array()))) {
// Check if any hook_uc_file_action('upload_validate', $args)
// are implemented.
foreach (module_implements('uc_file_action') as $module) {
$name = $module . '_uc_file_action';
$result = $name('upload_validate', array('file_object' => $temp_file, 'form_id' => $form_id, 'form_state' => $form_state));
}
// Save the uploaded file for later processing.
$form_state['storage']['temp_file'] = $temp_file;
}
else {
form_set_error('', t('An error occurred while uploading the file'));
}
break;
default:
// This action isn't handled by us, so check if any
// hook_uc_file_action('validate', $args) are implemented
foreach (module_implements('uc_file_action') as $module) {
$name = $module . '_uc_file_action';
$result = $name('validate', array('form_id' => $form_id, 'form_state' => $form_state));
}
break;
}
}
/**
* Submit handler for uc_file_admin_files_form_action().
*
* @see uc_file_admin_files_form_action()
*/
function uc_file_admin_files_form_action_submit($form, &$form_state) {
switch ($form_state['values']['action']) {
case 'uc_file_delete':
// File deletion status.
$status = TRUE;
// Delete the selected file(s), with recursion if selected.
$status = uc_file_remove_by_id($form_state['values']['file_ids'], !empty($form_state['values']['recurse_directories'])) && $status;
if ($status) {
drupal_set_message(t('The selected file(s) have been deleted.'));
}
else {
drupal_set_message(t('One or more files could not be deleted.'));
}
break;
case 'uc_file_upload':
// Build the destination location. We start with the base directory,
// then add any directory which was explicitly selected.
$dir = variable_get('uc_file_base_dir', NULL) . '/';
$dir = (is_null($form_state['values']['upload_dir'])) ? $dir : $dir . $form_state['values']['upload_dir'];
if (is_dir($dir)) {
// Retrieve our uploaded file.
$file_object = $form_state['storage']['temp_file'];
// Copy the file to its final location.
if (copy($file_object->uri, $dir . '/' . $file_object->filename)) {
// Check if any hook_uc_file_action('upload', $args) are implemented
foreach (module_implements('uc_file_action') as $module) {
$name = $module . '_uc_file_action';
$result = $name('upload', array('file_object' => $file_object, 'form_id' => $form_id, 'form_state' => $form_state));
}
// Update the file list
uc_file_refresh();
drupal_set_message(t('The file %file has been uploaded to %dir', array('%file' => $file_object->filename, '%dir' => $dir)));
}
else {
drupal_set_message(t('An error occurred while copying the file to %dir', array('%dir' => $dir)));
}
}
else {
drupal_set_message(t('Can not move file to %dir', array('%dir' => $dir)));
}
break;
default:
// This action isn't handled by us, so check if any
// hook_uc_file_action('submit', $args) are implemented
foreach (module_implements('uc_file_action') as $module) {
$name = $module . '_uc_file_action';
$result = $name('submit', array('form_id' => $form_id, 'form_state' => $form_state));
}
break;
}
// Return to the original form state.
$form_state['rebuild'] = FALSE;
drupal_goto('admin/store/products/files');
}
/**
* TODO: Replace with == operator?
*/
function _uc_file_display_arrays_equivalent($recur, $no_recur) {
// Different sizes.
if (count($recur) != count($no_recur)) {
return FALSE;
}
// Check the elements.
for ($i = 0; $i < count($recur); $i++) {
if ($recur[$i] != $no_recur[$i]) {
return FALSE;
}
}
return TRUE;
}
/**
* Shows all possible files in selectable list.
*/
function _uc_file_build_js_file_display($file_ids) {
// Gather the files if recursion IS selected.
// Get 'em all ready to be punched into the file list.
$recursion_file_ids = _uc_file_sort_names(_uc_file_get_dir_file_ids($file_ids, TRUE));
foreach ($recursion_file_ids as $file_id) {
$file = uc_file_get_by_id($file_id);
$recursion[] = '<li>' . $file->filename . '</li>';
}
// Gather the files if recursion ISN'T selected.
// Get 'em all ready to be punched into the file list.
$no_recursion_file_ids = $file_ids;
foreach ($no_recursion_file_ids as $file_id) {
$file = uc_file_get_by_id($file_id);
$no_recursion[] = '<li>' . $file->filename . '</li>';
}
// We'll disable the recursion checkbox if they're equal.
$equivalent = _uc_file_display_arrays_equivalent($recursion_file_ids, $no_recursion_file_ids);
// The list to show if the recursion checkbox is $key.
$result[TRUE] = $equivalent ? FALSE : $recursion;
$result[FALSE] = $no_recursion;
// Set up some JS to dynamically update the list based on the
// recursion checkbox state.
drupal_add_js('uc_file_list[false] = ' . drupal_json_encode('<li>' . implode('</li><li>', $no_recursion) . '</li>') . ';', 'inline');
drupal_add_js('uc_file_list[true] = ' . drupal_json_encode('<li>' . implode('</li><li>', $recursion) . '</li>') . ';', 'inline');
return $result;
}

View File

@@ -0,0 +1,206 @@
<?php
/**
* @file
* Hooks provided by the File Downloads module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Gives clearance to a user to download a file.
*
* By default the uc_file module can implement 3 restrictions on downloads: by
* number of IP addresses downloaded from, by number of downloads, and by a set
* expiration date. Developers wishing to add further restrictions can do so by
* implementing this hook. After the 3 aforementioned restrictions are checked,
* the uc_file module will check for implementations of this hook.
*
* @param $user
* The drupal user object that has requested the download
* @param $file_download
* The file download object as defined as a row from the uc_file_users table
* that grants the user the download
*
* @return
* TRUE or FALSE depending on whether the user is to be permitted download of
* the requested files. When a implementation returns FALSE it should set an
* error message in Drupal using drupal_set_message() to inform customers of
* what is going on.
*/
function hook_uc_download_authorize($user, $file_download) {
if (!$user->status) {
drupal_set_message(t("This account has been banned and can't download files anymore."), 'error');
return FALSE;
}
else {
return TRUE;
}
}
/**
* Performs actions on file products.
*
* The uc_file module comes with a file manager (found at Administer » Store
* administration » Products » View file downloads) that provides some basic
* functionality: deletion of multiple files and directories, and upload of
* single files (those looking to upload multiple files should just directly
* upload them to their file download directory then visit the file manager
* which automatically updates new files found in its directory). Developers
* that need to create more advanced actions with this file manager can do so
* by using this hook.
*
* @param $op
* The operation being taken by the hook, possible ops defined below.
* - info: Called before the uc_file module builds its list of possible file
* actions. This op is used to define new actions that will be placed in
* the file action select box.
* - insert: Called after uc_file discovers a new file in the file download
* directory.
* - form: When any defined file action is selected and submitted to the form
* this function is called to render the next form. Because this is called
* whenever a module-defined file action is selected, the variable
* $args['action'] can be used to define a new form or append to an existing
* form.
* - upload: After a file has been uploaded, via the file manager's built in
* file upload function, and moved to the file download directory this op
* can perform any remaining operations it needs to perform on the file
* before its placed into the uc_files table.
* - upload_validate: This op is called to validate the uploaded file that
* was uploaded via the file manager's built in file upload function. At
* this point, the file has been uploaded to PHP's temporary directory.
* Files passing this upload validate function will be moved into the file
* downloads directory.
* - validate: This op is called to validate the file action form.
* - submit: This op is called to submit the file action form.
* @param $args
* A keyed array of values that varies depending on the op being performed,
* possible values defined below.
* - info: None.
* - insert:
* - file_object: The file object of the newly discovered file.
* - form:
* - action: The file action being performed as defined by the key in the
* array sent by hook_uc_file_action($op = 'info').
* - file_ids: The file ids (as defined in the uc_files table) of the
* selected files to perform the action on.
* - upload:
* - file_object: The file object of the file moved into file downloads
* directory.
* - form_id: The form_id variable of the form_submit function.
* - form_values: The form_values variable of the form_submit function.
* - upload_validate:
* - file_object: The file object of the file that has been uploaded into
* PHP's temporary upload directory.
* - form_id: The form_id variable of the form_validate function.
* - form_values: The form_values variable of the form_validate function.
* - validate:
* - form_id: The form_id variable of the form_validate function.
* - form_values: The form_values variable of the form_validate function.
* - submit:
* - form_id: The form_id variable of the form_submit function.
* - form_values: The form_values variable of the form_submit function.
*
* @return
* The return value of hook depends on the op being performed, possible return
* values defined below:
* - info: The associative array of possible actions to perform. The keys are
* unique strings that defines the actions to perform. The values are the
* text to be displayed in the file action select box.
* - insert: None.
* - form: This op should return an array of drupal form elements as defined
* by the drupal form API.
* - upload: None.
* - upload_validate: None.
* - validate: None.
* - submit: None.
*/
function hook_uc_file_action($op, $args) {
switch ($op) {
case 'info':
return array('uc_image_watermark_add_mark' => 'Add Watermark');
case 'insert':
// Automatically adds watermarks to any new files that are uploaded to
// the file download directory.
_add_watermark($args['file_object']->uri);
break;
case 'form':
if ($args['action'] == 'uc_image_watermark_add_mark') {
$form['watermark_text'] = array(
'#type' => 'textfield',
'#title' => t('Watermark text'),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit_watermark'] = array(
'#type' => 'submit',
'#value' => t('Add watermark'),
);
}
return $form;
case 'upload':
_add_watermark($args['file_object']->uri);
break;
case 'upload_validate':
// Given a file path, function checks if file is valid JPEG.
if (!_check_image($args['file_object']->uri)) {
form_set_error('upload', t('Uploaded file is not a valid JPEG'));
}
break;
case 'validate':
if ($args['form_values']['action'] == 'uc_image_watermark_add_mark') {
if (empty($args['form_values']['watermark_text'])) {
form_set_error('watermar_text', t('Must fill in text'));
}
}
break;
case 'submit':
if ($args['form_values']['action'] == 'uc_image_watermark_add_mark') {
foreach ($args['form_values']['file_ids'] as $file_id) {
$filename = db_query("SELECT filename FROM {uc_files} WHERE fid = :fid", array(':fid' => $file_id))->fetchField();
// Function adds watermark to image.
_add_watermark($filename);
}
}
break;
}
}
/**
* Makes changes to a file before it is downloaded by the customer.
*
* Stores, either for customization, copy protection or other reasons, might
* want to send customized downloads to customers. This hook will allow this
* to happen. Before a file is opened to be transferred to a customer, this
* hook will be called to make any altercations to the file that will be used
* to transfer the download to the customer. This, in effect, will allow a
* developer to create a new, personalized, file that will get transferred to
* a customer.
*
* @param $file_user
* The file_user object (i.e. an object containing a row from the
* uc_file_users table) that corresponds with the user download being
* accessed.
* @param $ip
* The IP address from which the customer is downloading the file.
* @param $fid
* The file id of the file being transferred.
* @param $file
* The file path of the file to be transferred.
*
* @return
* The path of the new file to transfer to customer.
*/
function hook_uc_file_transfer_alter($file_user, $ip, $fid, $file) {
// For large files this might be too memory intensive.
$file_data = file_get_contents($file) . " [insert personalized data]";
$new_file = tempnam(file_directory_temp(), 'tmp');
file_put_contents($new_file, $file_data);
return $new_file;
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,30 @@
/**
* @file
* Styles for uc_file module.
*/
.download-table-row {
vertical-align: top;
}
.download-table-index {
display: inline;
}
.download-table-index .form-item {
display: inline;
}
.duration {
display: inline;
white-space: nowrap;
}
.duration .form-item {
display: inline;
white-space: nowrap;
}
.uc-file-directory-view {
font-style: italic;
font-weight: bold;
}

View File

@@ -0,0 +1,16 @@
name = File downloads
description = Allows products to be associated with downloadable files.
dependencies[] = uc_product
dependencies[] = uc_order
package = Ubercart - core (optional)
core = 7.x
stylesheets[all][] = uc_file.css
scripts[] = uc_file.js
; Information added by Drupal.org packaging script on 2013-12-17
version = "7.x-3.6"
core = "7.x"
project = "ubercart"
datestamp = "1387304010"

View File

@@ -0,0 +1,286 @@
<?php
/**
* @file
* Install, update and uninstall functions for the uc_file module.
*/
// -1 is the UC_FILE_LIMIT_SENTINEL constant in uc_file.module, but
// it might not be available (like when upgrading from 5 -> 6.
/**
* Implements hook_schema().
*/
function uc_file_schema() {
$schema = array();
$schema['uc_files'] = array(
'description' => 'Stores information on purchasable files.',
'fields' => array(
'fid' => array(
'description' => 'Primary key: the file ID.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'filename' => array(
'description' => 'The name of the file.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('fid'),
'indexes' => array(
'filename' => array('filename'),
),
);
$schema['uc_file_products'] = array(
'description' => 'Maps file product features to files.',
'fields' => array(
'fpid' => array(
'description' => 'Primary key: the ID for the file-product combination.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'pfid' => array(
'description' => 'The {uc_product_features}.pfid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'fid' => array(
'description' => 'The {uc_files}.fid of the purchasable file.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'model' => array(
'description' => 'The product model.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
'description' => array(
'description' => 'The description of the file.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
'shippable' => array(
'description' => 'Is this file feature shippable? 1 => Yes. 0 => No.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
),
'download_limit' => array(
'description' => 'The number of times the file may be downloaded by a user. -1 indicates the store default will be used.',
'type' => 'int',
'not null' => FALSE,
'default' => -1, // UC_FILE_LIMIT_SENTINEL
),
'address_limit' => array(
'description' => 'The number of different IP addresses from which the file may be downloaded. -1 indicates the store default will be used.',
'type' => 'int',
'not null' => FALSE,
'default' => -1, // UC_FILE_LIMIT_SENTINEL
),
'time_granularity' => array(
'description' => 'The units of time for time_quantity. -1 indicates the store default will be used.',
'type' => 'varchar',
'length' => 16,
'not null' => TRUE,
'default' => '-1', // UC_FILE_LIMIT_SENTINEL
),
'time_quantity' => array(
'description' => 'With time_granularity, the amount of time the file will be available for download. -1 indicates the store default will be used.',
'type' => 'int',
'not null' => TRUE,
'default' => -1, // UC_FILE_LIMIT_SENTINEL
),
),
'indexes' => array(
'pfid' => array('pfid'),
'fid' => array('fid'),
),
'primary key' => array('fpid'),
'foreign keys' => array(
'uc_product_features' => array(
'table' => 'uc_product_features',
'columns' => array('pfid' => 'pfid'),
),
'uc_files' => array(
'table' => 'uc_files',
'columns' => array('fid' => 'fid'),
),
),
);
$schema['uc_file_users'] = array(
'description' => 'The customers and the files they have purchased.',
'fields' => array(
'fuid' => array(
'description' => 'Primary key: the ID of the file-user combination.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'fid' => array(
'description' => 'The {uc_files}.fid that was purchased.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'uid' => array(
'description' => 'The {users}.uid of the user who purchased the file.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'pfid' => array(
'description' => 'The product feature ID of the product that was ordered, from {uc_file_products}.pfid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
'file_key' => array(
'description' => 'A hash of the data in this row.',
'type' => 'varchar',
'length' => 64,
'not null' => TRUE,
'default' => '',
),
'granted' => array(
'description' => 'The Unix timestamp indicating when the file was made available for download.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'expiration' => array(
'description' => 'The Unix timestamp indicating when the file will no longer be available for download.',
'type' => 'int',
'not null' => FALSE,
'default' => 0,
),
'accessed' => array(
'description' => 'The number of times the file has been downloaded by the user.',
'type' => 'int',
'size' => 'small',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'addresses' => array(
'description' => 'The number of different IP addresses the user has used to download the file.',
'type' => 'text',
'serialize' => TRUE,
'not null' => FALSE,
),
'download_limit' => array(
'description' => 'The number of times the user may download the file.',
'type' => 'int',
'not null' => FALSE,
'default' => NULL,
),
'address_limit' => array(
'description' => 'The number of different IP addresses the user may use to download the file.',
'type' => 'int',
'not null' => FALSE,
'default' => NULL,
),
),
'indexes' => array(
'fid_uid' => array('fid', 'uid'),
'uid' => array('uid'),
),
'primary key' => array('fuid'),
'foreign keys' => array(
'uc_product_features' => array(
'table' => 'uc_product_features',
'columns' => array('pfid' => 'pfid'),
),
'uc_files' => array(
'table' => 'uc_files',
'columns' => array('fid' => 'fid'),
),
'users' => array(
'table' => 'users',
'columns' => array('uid' => 'uid'),
),
),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function uc_file_uninstall() {
db_delete('uc_product_features')
->condition('fid', 'file')
->execute();
db_delete('variable')
->condition('name', 'uc_file_%', 'LIKE')
->execute();
}
/**
* Implements hook_update_last_removed().
*/
function uc_file_update_last_removed() {
return 6006;
}
/**
* Change 'uc_file_file_mask' variable to a preg regular expression.
*/
function uc_file_update_7000() {
$mask = '/' . variable_get('uc_file_file_mask', '.*') . '/';
variable_set('uc_file_file_mask', $mask);
return t("Variable 'uc_file_file_mask' changed to @mask.", array('@mask' => $mask));
}
/**
* Increase the length of {uc_file_users}.file_key.
*/
function uc_file_update_7001() {
db_change_field('uc_file_users', 'file_key', 'file_key', array(
'type' => 'varchar',
'length' => 64,
'not null' => TRUE,
'default' => '',
));
}
/**
* Add a index on filename since we query on it fairly frequently.
*/
function uc_file_update_7002() {
// Index may have been added by uc_file_update_6007() in 6.x-2.x.
if (!db_index_exists('uc_files', 'filename')) {
db_add_index('uc_files', 'filename', array('filename'));
}
}
/**
* Change index on uc_file_users.fid to be over fid and uid.
*/
function uc_file_update_7003() {
// Index may have been changed in 6.x-2.x.
if (!db_index_exists('uc_file_users', 'fid_uid')) {
db_add_index('uc_file_users', 'fid_uid', array('fid', 'uid'));
db_drop_index('uc_file_users', 'fid');
}
}

View File

@@ -0,0 +1,50 @@
/**
* @file
* Modifies the file selection and download access expiration interfaces.
*/
var uc_file_list = {};
/**
* Adds files to delete to the list.
*/
function _uc_file_delete_list_populate() {
jQuery('.affected-file-name').empty().append(uc_file_list[jQuery('#edit-recurse-directories').attr('checked')]);
}
jQuery(document).ready(
function() {
_uc_file_delete_list_populate();
}
);
// When you (un)check the recursion option on the file deletion form.
Drupal.behaviors.ucFileDeleteList = {
attach: function(context, settings) {
jQuery('#edit-recurse-directories:not(.ucFileDeleteList-processed)', context).addClass('ucFileDeleteList-processed').change(
function() {
_uc_file_delete_list_populate()
}
);
}
}
/**
* Give visual feedback to the user about download numbers.
*
* TODO: would be to use AJAX to get the new download key and
* insert it into the link if the user hasn't exceeded download limits.
* I dunno if that's technically feasible though.
*/
function uc_file_update_download(id, accessed, limit) {
if (accessed < limit || limit == -1) {
// Handle the max download number as well.
var downloads = '';
downloads += accessed + 1;
downloads += '/';
downloads += limit == -1 ? 'Unlimited' : limit;
jQuery('td#download-' + id).html(downloads);
jQuery('td#download-' + id).attr("onclick", "");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,453 @@
<?php
/**
* @file
* File menu items.
*/
/**
* The number of bogus requests one IP address can make before being banned.
*/
define('UC_FILE_REQUEST_LIMIT', 50);
/**
* Download file chunk.
*/
define('UC_FILE_BYTE_SIZE', 8192);
/**
* Download statuses.
*/
define('UC_FILE_ERROR_OK' , 0);
define('UC_FILE_ERROR_NOT_A_FILE' , 1);
define('UC_FILE_ERROR_TOO_MANY_BOGUS_REQUESTS', 2);
define('UC_FILE_ERROR_INVALID_DOWNLOAD' , 3);
define('UC_FILE_ERROR_TOO_MANY_LOCATIONS' , 4);
define('UC_FILE_ERROR_TOO_MANY_DOWNLOADS' , 5);
define('UC_FILE_ERROR_EXPIRED' , 6);
define('UC_FILE_ERROR_HOOK_ERROR' , 7);
/**
* Themes user file downloads page.
*
* @param $variables
* An associative array containing:
* - header: Table header array, in format required by theme_table().
* - files: Associative array of downloadable files, containing:
* - granted: Timestamp of file grant.
* - link: URL of file.
* - description: File name, as it should appear to user after downloading.
* - accessed: Integer number of times file has been downloaded.
* - download_limit: Integer limit on downloads.
* - addresses: Integer number of IP addresses used.
* - address_limit: Integer limit on IP addresses.
*
* @see theme_table()
* @ingroup themeable
*/
function theme_uc_file_user_downloads($variables) {
$header = $variables['header'];
$files = $variables['files'];
$rows = array();
$row = 0;
foreach ($files as $file) {
$rows[] = array(
array('data' => format_date($file['granted'], 'uc_store'), 'class' => array('date-row'), 'id' => 'date-' . $row),
array('data' => $file['link'], 'class' => array('filename-row'), 'id' => 'filename-' . $row),
array('data' => $file['description'], 'class' => array('description-row'), 'id' => 'description-' . $row),
array('data' => $file['accessed'] . '/' . ($file['download_limit'] ? $file['download_limit'] : t('Unlimited')), 'class' => array('download-row'), 'id' => 'download-' . $row),
array('data' => count(unserialize($file['addresses'])) . '/' . ($file['address_limit'] ? $file['address_limit'] : t('Unlimited')), 'class' => array('addresses-row'), 'id' => 'addresses-' . $row),
);
$row++;
}
$output = theme('table', array(
'header' => $header,
'rows' => $rows,
'empty' => t('No downloads found'),
));
$output .= theme('pager');
$output .= '<div class="form-item"><p class="description">' .
t('Once your download is finished, you must refresh the page to download again. (Provided you have permission)') .
'<br />' . t('Downloads will not be counted until the file is finished transferring, even though the number may increment when you click.') .
'<br /><b>' . t('Do not use any download acceleration feature to download the file, or you may lock yourself out of the download.') . '</b>' .
'</p></div>';
return $output;
}
/**
* Table builder for user downloads.
*/
function uc_file_user_downloads($account) {
// Create a header and the pager it belongs to.
$header = array(
array('data' => t('Purchased' ), 'field' => 'u.granted', 'sort' => 'desc'),
array('data' => t('Filename' ), 'field' => 'f.filename'),
array('data' => t('Description'), 'field' => 'p.description'),
array('data' => t('Downloads' ), 'field' => 'u.accessed'),
array('data' => t('Addresses' )),
);
drupal_set_title(t('File downloads'));
$files = array();
$query = db_select('uc_file_users', 'u')->extend('PagerDefault')->extend('TableSort')
->condition('uid', $account->uid)
->orderByHeader($header)
->limit(UC_FILE_PAGER_SIZE);
$query->leftJoin('uc_files', 'f', 'u.fid = f.fid');
$query->leftJoin('uc_file_products', 'p', 'p.pfid = u.pfid');
$query->fields('u', array(
'granted',
'accessed',
'addresses',
'file_key',
'download_limit',
'address_limit',
'expiration',
))
->fields('f', array(
'filename',
'fid',
))
->fields('p', array('description'));
$count_query = db_select('uc_file_users')
->condition('uid', $account->uid);
$count_query->addExpression('COUNT(*)');
$query->setCountQuery($count_query);
$result = $query->execute();
$row = 0;
foreach ($result as $file) {
$download_limit = $file->download_limit;
// Set the JS behavior when this link gets clicked.
$onclick = array(
'attributes' => array(
'onclick' => 'uc_file_update_download(' . $row . ', ' . $file->accessed . ', ' . ((empty($download_limit)) ? -1 : $download_limit) . ');', 'id' => 'link-' . $row
),
);
// Expiration set to 'never'
if ($file->expiration == FALSE) {
$file_link = l(basename($file->filename), 'download/' . $file->fid, $onclick);
}
// Expired.
elseif (REQUEST_TIME > $file->expiration) {
$file_link = basename($file->filename);
}
// Able to be downloaded.
else {
$file_link = l(basename($file->filename), 'download/' . $file->fid, $onclick) . ' (' . t('expires on @date', array('@date' => format_date($file->expiration, 'uc_store'))) . ')';
}
$files[] = array(
'granted' => $file->granted,
'link' => $file_link,
'description' => $file->description,
'accessed' => $file->accessed,
'download_limit' => $file->download_limit,
'addresses' => $file->addresses,
'address_limit' => $file->address_limit,
);
$row++;
}
$build['downloads'] = array(
'#theme' => 'uc_file_user_downloads',
'#header' => $header,
'#files' => $files,
);
if (user_access('administer users')) {
$build['admin'] = drupal_get_form('uc_file_user_form', $account);
}
return $build;
}
/**
* Handles file downloading and error states.
*
* @param $fid
* The fid of the file specified to download.
* @param $key
* The hash key of a user's download.
*
* @see _uc_file_download_validate()
*/
function _uc_file_download($fid) {
global $user;
// Error messages for various failed download states.
$admin_message = t('Please contact the site administrator if this message has been received in error.');
$error_messages = array(
UC_FILE_ERROR_NOT_A_FILE => t('The file you requested does not exist.'),
UC_FILE_ERROR_TOO_MANY_BOGUS_REQUESTS => t('You have attempted to download an incorrect file URL too many times.'),
UC_FILE_ERROR_INVALID_DOWNLOAD => t('The following URL is not a valid download link.') . ' ',
UC_FILE_ERROR_TOO_MANY_LOCATIONS => t('You have downloaded this file from too many different locations.'),
UC_FILE_ERROR_TOO_MANY_DOWNLOADS => t('You have reached the download limit for this file.'),
UC_FILE_ERROR_EXPIRED => t('This file download has expired.') . ' ',
UC_FILE_ERROR_HOOK_ERROR => t('A hook denied your access to this file.') . ' ',
);
$ip = ip_address();
if (user_access('view all downloads')) {
$file_download = uc_file_get_by_id($fid);
}
else {
$file_download = uc_file_get_by_uid($user->uid, $fid);
}
if (isset($file_download->filename)) {
$file_download->full_path = uc_file_qualify_file($file_download->filename);
}
else {
return MENU_ACCESS_DENIED;
}
// If it's ok, we push the file to the user.
$status = UC_FILE_ERROR_OK;
if (!user_access('view all downloads')) {
$status = _uc_file_download_validate($file_download, $user, $ip);
}
if ($status == UC_FILE_ERROR_OK) {
_uc_file_download_transfer($file_download, $ip);
}
// Some error state came back, so report it.
else {
drupal_set_message($error_messages[$status] . $admin_message, 'error');
// Kick 'em to the curb. >:)
_uc_file_download_redirect($user->uid);
}
drupal_exit();
}
/**
* Performs first-pass authorization. Calls authorization hooks afterwards.
*
* Called when a user requests a file download, function checks download
* limits then checks for any implementation of hook_uc_download_authorize().
* Passing that, the function _uc_file_download_transfer() is called.
*
* @param $fid
* The fid of the file specified to download.
* @param $key
* The hash key of a user's download.
*/
function _uc_file_download_validate($file_download, &$user, $ip) {
$request_cache = cache_get('uc_file_' . $ip);
$requests = ($request_cache) ? $request_cache->data + 1 : 1;
$message_user = ($user->uid) ? t('The user %username', array('%username' => format_username($user))) : t('The IP address %ip', array('%ip' => $ip));
if ($requests > UC_FILE_REQUEST_LIMIT) {
return UC_FILE_ERROR_TOO_MANY_BOGUS_REQUESTS;
}
// Must be a valid file.
if (!$file_download || !is_readable($file_download->full_path)) {
cache_set('uc_file_' . $ip, $requests, 'cache', REQUEST_TIME + 86400);
if ($requests == UC_FILE_REQUEST_LIMIT) {
// $message_user has already been passed through check_plain()
watchdog('uc_file', '!username has been temporarily banned from file downloads.', array('!username' => $message_user), WATCHDOG_WARNING);
}
return UC_FILE_ERROR_INVALID_DOWNLOAD;
}
$addresses = $file_download->addresses;
// Check the number of locations.
if (!empty($file_download->address_limit) && !in_array($ip, $addresses) && count($addresses) >= $file_download->address_limit) {
// $message_user has already been passed through check_plain()
watchdog('uc_file', '!username has been denied a file download by downloading it from too many IP addresses.', array('!username' => $message_user), WATCHDOG_WARNING);
return UC_FILE_ERROR_TOO_MANY_LOCATIONS;
}
// Check the downloads so far.
if (!empty($file_download->download_limit) && $file_download->accessed >= $file_download->download_limit) {
// $message_user has already been passed through check_plain()
watchdog('uc_file', '!username has been denied a file download by downloading it too many times.', array('!username' => $message_user), WATCHDOG_WARNING);
return UC_FILE_ERROR_TOO_MANY_DOWNLOADS;
}
// Check if it's expired.
if ($file_download->expiration && REQUEST_TIME >= $file_download->expiration) {
// $message_user has already been passed through check_plain()
watchdog('uc_file', '!username has been denied an expired file download.', array('!username' => $message_user), WATCHDOG_WARNING);
return UC_FILE_ERROR_EXPIRED;
}
// Check any if any hook_uc_download_authorize() calls deny the download
foreach (module_implements('uc_download_authorize') as $module) {
$name = $module . '_uc_download_authorize';
$result = $name($user, $file_download);
if (!$result) {
return UC_FILE_ERROR_HOOK_ERROR;
}
}
// Everything's ok!
// $message_user has already been passed through check_plain()
watchdog('uc_file', '!username has started download of the file %filename.', array('!username' => $message_user, '%filename' => basename($file_download->filename)), WATCHDOG_NOTICE);
}
/**
* Sends the file's binary data to a user via HTTP and updates the database.
*
* @param $file_user
* The file_user object from the uc_file_users.
* @param $ip
* The string containing the IP address the download is going to.
*/
function _uc_file_download_transfer($file_user, $ip) {
// Check if any hook_uc_file_transfer_alter() calls alter the download.
foreach (module_implements('uc_file_transfer_alter') as $module) {
$name = $module . '_uc_file_transfer_alter';
$file_user->full_path = $name($file_user, $ip, $file_user->fid, $file_user->full_path);
}
// This could get clobbered, so make a copy.
$filename = $file_user->filename;
// Gather relevant info about the file.
$size = filesize($file_user->full_path);
$mimetype = file_get_mimetype($filename);
// Workaround for IE filename bug with multiple periods / multiple dots
// in filename that adds square brackets to filename -
// eg. setup.abc.exe becomes setup[1].abc.exe
if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
$filename = preg_replace('/\./', '%2e', $filename, substr_count($filename, '.') - 1);
}
// Check if HTTP_RANGE is sent by browser (or download manager)
$range = NULL;
if (isset($_SERVER['HTTP_RANGE'])) {
if (substr($_SERVER['HTTP_RANGE'], 0, 6) == 'bytes=') {
// Multiple ranges could be specified at the same time,
// but for simplicity only serve the first range
// See http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list($range, $extra_ranges) = explode(',', substr($_SERVER['HTTP_RANGE'], 6), 2);
}
else {
drupal_add_http_header('Status', '416 Requested Range Not Satisfiable');
drupal_add_http_header('Content-Range', 'bytes */' . $size);
exit;
}
}
// Figure out download piece from range (if set)
if (isset($range)) {
list($seek_start, $seek_end) = explode('-', $range, 2);
}
// Set start and end based on range (if set),
// else set defaults and check for invalid ranges.
$seek_end = intval((empty($seek_end)) ? ($size - 1) : min(abs(intval($seek_end)), ($size - 1)));
$seek_start = intval((empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)), 0));
// Only send partial content header if downloading a piece of the file (IE
// workaround).
if ($seek_start > 0 || $seek_end < ($size - 1)) {
drupal_add_http_header('Status', '206 Partial Content');
}
// Standard headers, including content-range and length
drupal_add_http_header('Pragma', 'public');
drupal_add_http_header('Cache-Control', 'cache, must-revalidate');
drupal_add_http_header('Accept-Ranges', 'bytes');
drupal_add_http_header('Content-Range', 'bytes ' . $seek_start . '-' . $seek_end . '/' . $size);
drupal_add_http_header('Content-Type', $mimetype);
drupal_add_http_header('Content-Disposition', 'attachment; filename="' . $filename . '"');
drupal_add_http_header('Content-Length', $seek_end - $seek_start + 1);
// Last-Modified is required for content served dynamically
drupal_add_http_header('Last-Modified', gmdate("D, d M Y H:i:s", filemtime($file_user->full_path)) . " GMT");
// Etag header is required for Firefox3 and other managers
drupal_add_http_header('ETag', md5($file_user->full_path));
// Open the file and seek to starting byte
$fp = fopen($file_user->full_path, 'rb');
fseek($fp, $seek_start);
// Start buffered download
while (!feof($fp)) {
// Reset time limit for large files
drupal_set_time_limit(0);
// Push the data to the client.
print(fread($fp, UC_FILE_BYTE_SIZE));
flush();
// Suppress PHP notice that occurs when output buffering isn't enabled.
// The ob_flush() is needed because if output buffering *is* enabled,
// clicking on the file download link won't download anything if the buffer
// isn't flushed.
@ob_flush();
}
// Finished serving the file, close the stream and log the download
// to the user table.
fclose($fp);
_uc_file_log_download($file_user, $ip);
}
/**
* Processes a file download.
*/
function _uc_file_log_download($file_user, $ip) {
// Add the address if it doesn't exist.
$addresses = $file_user->addresses;
if (!in_array($ip, $addresses)) {
$addresses[] = $ip;
}
$file_user->addresses = $addresses;
// Accessed again.
$file_user->accessed++;
// Calculate hash
$file_user->file_key = drupal_get_token(serialize($file_user));
drupal_write_record('uc_file_users', $file_user, 'fuid');
}
/**
* Send 'em packin.
*/
function _uc_file_download_redirect($uid = NULL) {
// Shoo away anonymous users.
if ($uid == 0) {
drupal_access_denied();
}
// Redirect users back to their file page.
else {
if (!headers_sent()) {
drupal_goto('user/' . $uid . '/purchased-files');
}
}
}

View File

@@ -0,0 +1,323 @@
<?php
/**
* @file
* Rules hooks and functions for uc_file.module.
*/
/**
* Implements hook_rules_data_info().
*
* An entity is defined in order to get file expiration(s) down to token in the
* email.
*/
function uc_file_rules_data_info() {
$data['uc_file_expiration'] = array(
'label' => t('Ubercart file expiration(s)'),
'wrap' => TRUE,
'property info' => array(
'fuid' => array(
'type' => 'integer',
'label' => t('File-user ID'),
'description' => t('Primary key for user permission to download a file.'),
),
'fid' => array(
'type' => 'integer',
'label' => t('File ID'),
'description' => t('The file that may be downloaded.'),
),
'file' => array(
'type' => 'file',
'label' => t('File'),
'description' => t('The file that may be downloaded.'),
'getter callback' => 'uc_file_get_expiration_properties',
'setter callback' => 'uc_file_set_expiration_properties',
),
'uid' => array(
'type' => 'integer',
'label' => t('User ID'),
'description' => t('The user account ID.'),
),
'user' => array(
'type' => 'user',
'label' => t('User'),
'description' => t('The user account that may download the file.'),
'getter callback' => 'uc_file_get_expiration_properties',
'setter callback' => 'uc_file_set_expiration_properties',
),
'pfid' => array(
'type' => 'integer',
'label' => t('Product feature ID'),
'description' => t('The product feature that granted permission to download the file.'),
),
'file-key' => array(
'type' => 'text',
'label' => t('File key'),
'description' => t('A hash of the permission and expiraiton data.'),
),
'granted' => array(
'type' => 'date',
'label' => t('Granted time'),
'description' => t('The time the permission to download the file was granted.'),
),
'expiration' => array(
'type' => 'date',
'label' => t('Expiration time'),
'description' => t('The time when the permission to download the file will expire.'),
),
'accessed' => array(
'type' => 'integer',
'label' => t('Accesses'),
'description' => t('The number of times the file has been accessed.'),
),
'addresses' => array(
'type' => 'list<text>',
'label' => t('IP addresses'),
'description' => t('List of IP addresses to which the file has been downloaded.'),
),
'download-limit' => array(
'type' => 'integer',
'label' => t('Download limit'),
'description' => t('The numer of times the user may download the file.'),
),
'address-limit' => array(
'type' => 'integer',
'label' => t('Address limit'),
'description' => t('The number of different IP addresses that may download the file.'),
),
),
'group' => t('File download'),
'token type' => 'uc_file',
);
return $data;
}
/**
* Callback for getting download expiration properties.
*
* @see entity_metadata_node_entity_info_alter()
*/
function uc_file_get_expiration_properties($expiration, array $options, $name, $entity_type) {
switch ($name) {
case 'user':
return $expiration->uid;
case 'file':
return $expiration->fid;
}
}
/**
* Callback for setting download expiration properties.
*
* @see entity_metadata_node_entity_info_alter()
*/
function uc_file_set_expiration_properties($expiration, $name, $value) {
if ($name == 'user') {
$expiration->uid = $value;
}
elseif ($name == 'file') {
$expiration->fid = $value;
}
}
/**
* Implements hook_rules_action_info().
*/
function uc_file_rules_action_info() {
// Renew file expirations.
$actions['uc_file_order_renew'] = array(
'label' => t('Renew the files on an order.'),
'group' => t('renewal'),
'base' => 'uc_file_action_order_renew',
'parameter' => array(
'order' => array(
'type' => 'uc_order',
'label' => t('Order'),
),
),
);
// Send an email to an order with a file expiration
$actions['uc_file_order_email'] = array(
'label' => t('Send an order email regarding files.'),
'group' => t('Notification'),
'base' => 'uc_file_action_order_email',
'parameter' => array(
'order' => array(
'type' => 'uc_order',
'label' => t('Order'),
),
'expiration' => array(
'type' => 'uc_file_expiration',
'label' => t('File expiration'),
),
'from' => array(
'type' => 'text',
'label' => t('Sender'),
),
'addresses' => array(
'type' => 'text',
'label' => t('Recipients'),
),
'subject' => array(
'type' => 'text',
'label' => t('Subject'),
),
'message' => array(
'type' => 'text',
'label' => t('Message'),
),
'format' => array(
'type' => 'text',
'label' => t('Message format'),
'options list' => 'uc_file_message_formats',
),
),
);
return $actions;
}
/**
* Options list callback for message formats.
*/
function uc_file_message_formats() {
global $user;
$options = array();
$formats = filter_formats($user);
foreach ($formats as $format) {
$options[$format->format] = $format->name;
}
return $options;
}
/**
* Implements hook_rules_event_info().
*/
function uc_file_rules_event_info() {
$events['uc_file_notify_grant'] = array(
'label' => t('E-mail for granted files'),
'group' => t('Notification'),
'variables' => array(
'order' => array(
'type' => 'uc_order',
'label' => t('Order'),
),
'expiration' => array(
'type' => 'uc_file_expiration',
'label' => t('File expiration'),
),
),
);
return $events;
}
/**
* Send an email with order and file replacement tokens.
*
* The recipients, subject, and message fields take order token replacements.
*/
function uc_file_action_order_email($order, $file_expiration, $from, $addresses, $subject, $message, $format) {
$settings = array(
'from' => $from,
'addresses' => $addresses,
'subject' => $subject,
'message' => $message,
'format' => $format,
);
// Token replacements for the subject and body
$settings['replacements'] = array(
'uc_order' => $order,
'uc_file' => $file_expiration,
);
// Replace tokens and parse recipients.
$recipients = array();
$addresses = token_replace($settings['addresses'], $settings['replacements']);
foreach (explode("\n", $addresses) as $address) {
$recipients[] = trim($address);
}
// Send to each recipient.
foreach ($recipients as $email) {
$sent = drupal_mail('uc_order', 'action-mail', $email, uc_store_mail_recipient_language($email), $settings, $settings['from']);
if (!$sent['result']) {
watchdog('uc_file', 'Attempt to e-mail @email concerning order @order_id failed.', array('@email' => $email, '@order_id' => $order->order_id), WATCHDOG_ERROR);
}
}
}
/**
* Renews an orders product files.
*
* This function updates access limits on all files found on all products
* on a given order. First, the order user is loaded, then the order's products
* are scanned for file product features. An order comment is saved, and the
* user is notified in Drupal, as well as through the email address associated
* with the order.
*
* @param $order
* An Ubercart order object.
*/
function uc_file_action_order_renew($order) {
$user_downloads = array();
// Load user.
if (!$order->uid || !($order_user = user_load($order->uid))) {
return;
}
// Scan products for models matching downloads.
foreach ($order->products as $product) {
$files = db_query("SELECT * FROM {uc_file_products} fp " .
"INNER JOIN {uc_product_features} pf ON pf.pfid = fp.pfid " .
"INNER JOIN {uc_files} f ON f.fid = fp.fid " .
"WHERE nid = :nid", array(':nid' => $product->nid));
foreach ($files as $file) {
// Either they match, or the file was set to any SKU.
if (!empty($file->model) && $file->model != $product->model) {
continue;
}
// Grab any existing privilege so we can calculate the new expiration time
// as an offset of the previous.
$file_user = _uc_file_user_get($order_user, $file->fid);
// Get the limit info from the product feature
$file_modification = array(
'download_limit' => uc_file_get_download_limit($file),
'address_limit' => uc_file_get_address_limit($file),
'expiration' => _uc_file_expiration_date(uc_file_get_time_limit($file), ($file_user ? max($file_user->expiration, REQUEST_TIME) : NULL)),
);
// Add file_user(s) for this file/directory. (No overwrite)
$new_files = uc_file_user_renew($file->fid, $order_user, $file->pfid, $file_modification, FALSE);
// Save for notification.
$user_downloads = array_merge($user_downloads, $new_files);
// Note on the order where the user has gained download permission.
if (is_dir(uc_file_qualify_file($file->filename))) {
$comment = t('User can now download files in the directory %dir.', array('%dir' => $file->filename));
}
else {
$comment = t('User can now download the file %file.', array('%file' => basename($file->filename)));
}
uc_order_comment_save($order->order_id, $order_user->uid, $comment);
}
}
// Notify the user of their download(s).
if ($user_downloads) {
rules_invoke_event('uc_file_notify_grant', $order, $user_downloads);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* @file
* Default Rules configurations for uc_file.module.
*/
/**
* Implements hook_default_rules_configuration().
*/
function uc_file_default_rules_configuration() {
// Renew all the files on an order when the status matches what's set
// in the files admin settings.
$rule = rules_reaction_rule();
$rule->label = t('Renew purchased files');
$rule->active = TRUE;
$rule->event('uc_order_status_update');
$rule->condition('data_is', array('data:select' => 'updated_order:order-status', 'value' => 'payment_received'))
->action('uc_file_order_renew', array(
'order:select' => 'updated_order',
));
$configs['uc_file_renewal'] = $rule;
// Notify the user when a file is granted.
$rule = rules_reaction_rule();
$rule->label = t('Notify customer when a file is granted');
$rule->active = TRUE;
$rule->event('uc_file_notify_grant');
$rule->action('uc_file_order_email', array(
'order:select' => 'order',
'expiration:select' => 'expiration',
'from' => uc_store_email_from(),
'addresses' => '[order:email]',
'subject' => t("File Downloads for Order# [order:order-id]"),
'message' => t("Your order (order# [order:link]) at [store:name] included file download(s). You may access them with the following link(s):\n\n[expiration:downloads]\n\nAfter downloading these files these links will have expired. If you need to download the files again, you can login at [site:login-link] and visit the \"My Account\" section of the site.\n\nThanks again, \n\n[store:name]\n[site:slogan]"),
'format' => filter_default_format(),
));
$configs['uc_file_notify_grant_trigger'] = $rule;
return $configs;
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* @file
* Theme functions for the uc_file module.
*/
/**
* Themes the download table at the user account page.
*
* @param $variables
* An associative array containing:
* - form: A render element representing the form.
*
* @ingroup themeable
*/
function theme_uc_file_hook_user_file_downloads($variables) {
$form = $variables['form'];
$header = array(
array('data' => t('Remove' )),
array('data' => t('Filename' )),
array('data' => t('Expiration')),
array('data' => t('Downloads' )),
array('data' => t('Addresses' )),
);
$rows = array();
$output = '';
foreach (element_children($form['file_download']) as $key) {
if (!isset($form['file_download'][$key]['addresses_in'])) {
continue;
}
$file_download = &$form['file_download'][$key];
$rows[] = array(
'data' => array(
array('data' => drupal_render($file_download['remove'])),
array('data' => drupal_render($file_download['filename'])),
array(
'data' =>
drupal_render($file_download['expires']) . ' <br />' .
'<div class="duration">' .
drupal_render($file_download['time_polarity']) .
drupal_render($file_download['time_quantity']) .
drupal_render($file_download['time_granularity']) .
'</div>',
),
array(
'data' =>
'<div class="download-table-index">' .
drupal_render($file_download['downloads_in']) . '/' . drupal_render($file_download['download_limit']) .
'</div>',
),
array(
'data' =>
'<div class="download-table-index">' .
drupal_render($file_download['addresses_in']) . '/' . drupal_render($file_download['address_limit']) .
'</div>',
),
),
'class' => array('download-table-row'),
);
}
$output .= theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => 'download-table'),
'empty' => t('No files can be downloaded by this user.'),
));
$output .= drupal_render_children($form);
return $output;
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @file
* Token hooks for the uc_file module.
*/
/**
* Implements hook_token_info().
*/
function uc_file_token_info() {
$type = array(
'name' => t('File downloads'),
'description' => t('Tokens for purchased file downloads.'),
'needs-data' => 'uc_file',
);
$tokens['downloads'] = array(
'name' => t('Downloads'),
'description' => t('The list of file download links (if any) associated with an order'),
);
return array(
'types' => array('uc_file' => $type),
'tokens' => array('uc_file' => $tokens),
);
}
/**
* Implements hook_tokens().
*/
function uc_file_tokens($type, $tokens, $data = array(), $options = array()) {
$language_code = NULL;
if (isset($options['language'])) {
$language_code = $options['language']->language;
}
$sanitize = !empty($options['sanitize']);
$replacements = array();
if ($type == 'uc_file' && !empty($data['uc_file'])) {
$files = $data['uc_file'];
if (isset($tokens['downloads'])) {
$replacements[$tokens['downloads']] = theme('uc_file_downloads_token', array('file_downloads' => $files));
}
}
return $replacements;
}
/**
* Themes the file download links token.
*
* @ingroup themeable
*/
function theme_uc_file_downloads_token($variables) {
$file_downloads = $variables['file_downloads'];
$output = '';
foreach ($file_downloads as $file_download) {
// Let's only notify of them of the files, not the directories.
if (is_dir($file_download->filename)) {
continue;
}
$output .= l($file_download->filename, 'download/' . $file_download->fid, array('absolute' => TRUE)) . "\n";
}
return $output;
}