format_date($file['granted'], 'custom', variable_get('uc_date_format_default', 'm/d/Y')), 'class' => 'date-row', 'id' => 'date-'. $row), array('data' => $file['link'], 'class' => 'filename-row', 'id' => 'filename-'. $row), array('data' => $file['description'], 'class' => 'description-row', 'id' => 'description-'. $row), array('data' => $file['accessed'] .'/'. ($file['download_limit'] ? $file['download_limit'] : t('Unlimited')), 'class' => 'download-row', 'id' => 'download-'. $row), array('data' => count(unserialize($file['addresses'])) .'/'. ($file['address_limit'] ? $file['address_limit'] : t('Unlimited')), 'class' => 'addresses-row', 'id' => 'addresses-'. $row), ); $row++; } if (empty($rows)) { $rows[] = array(array('data' => t('No downloads found'), 'colspan' => 5)); } $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, UC_FILE_PAGER_SIZE, 0); $output .= '

'. t('Once your download is finished, you must refresh the page to download again. (Provided you have permission)') . '
'. t('Downloads will not be counted until the file is finished transferring, even though the number may increment when you click.') . '
'. t('Do not use any download acceleration feature to download the file, or you may lock yourself out of the download.') .''. '

'; 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(); $result = pager_query( "SELECT u.granted, f.filename, u.accessed, u.addresses, p.description, u.file_key, f.fid, u.download_limit, u.address_limit, u.expiration FROM {uc_file_users} as u ". "LEFT JOIN {uc_files} as f ON u.fid = f.fid ". "LEFT JOIN {uc_file_products} as p ON p.pfid = u.pfid WHERE uid = %d". tablesort_sql($header), UC_FILE_PAGER_SIZE, 0, "SELECT COUNT(*) FROM {uc_file_users} WHERE uid = %d", $account->uid ); $row = 0; while ($file = db_fetch_object($result)) { $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 .'/'. $file->file_key, $onclick); } // Expired. elseif (time() > $file->expiration) { $file_link = basename($file->filename); } // Able to be downloaded. else { $file_link = l(basename($file->filename), 'download/'. $file->fid .'/'. $file->file_key, $onclick) .' ('. t('expires on @date', array('@date' => format_date($file->expiration, 'custom', variable_get('uc_date_format_default', 'm/d/Y')))) .')'; } $files[] = array( 'granted' => $file->granted, 'link' => $file_link, 'description' => $file->description, 'accessed' => $file->accessed, 'download_limit' => $file->download_limit, 'addresses' => $file->address, 'address_limit' => $file->address_limit, ); $row++; } return theme('uc_file_user_downloads', $header, $files); } /** * Handle file downloading and error states. * * @see _uc_file_download_validate() * * @param $fid * The fid of the file specified to download. * @param $key * The hash key of a user's download */ function _uc_file_download($fid, $key) { 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(); $file_download = uc_file_get_by_key($key); $file_download->full_path = uc_file_qualify_file($file_download->filename); // If it's ok, we push the file to the user. $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); } /** * Perform first-pass authorization. Call authorization hooks afterwards. * * Called when a user requests a file download, function checks download * limits then checks for any implementation of hook_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' => $user->name)) : 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_file($file_download->full_path)) { cache_set('uc_file_'. $ip, $requests, 'cache', time() + 86400); if ($requests == UC_FILE_REQUEST_LIMIT) { 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) { 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) { 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 && time() >= $file_download->expiration) { 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_download_authorize calls deny the download foreach (module_implements('download_authorize') as $module) { $name = $module .'_download_authorize'; $result = $name($user, $file_download); if (!$result) { return UC_FILE_ERROR_HOOK_ERROR; } } // Everything's ok! watchdog('uc_file', '%username has started download of the file %filename.', array('%username' => $message_user, '%filename' => basename($file_download->filename)), WATCHDOG_NOTICE); } /** * Send the file's binary data to a user via HTTP and update the uc_file_users table. * * @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_file_transfer_alter calls alter the download. foreach (module_implements('file_transfer_alter') as $module) { $name = $module .'_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) if (isset($_SERVER['HTTP_RANGE'])) { list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2); if ($size_unit == '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(',', $range_orig, 2); } else { $range = ''; } } else { $range = ''; } // Figure out download piece from range (if set) 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)); ob_end_clean(); //Only send partial content header if downloading a piece of the file (IE workaround) if ($seek_start > 0 || $seek_end < ($size - 1)) { drupal_set_header('HTTP/1.1 206 Partial Content'); } // Standard headers, including content-range and length drupal_set_header('Pragma: public'); drupal_set_header('Cache-Control: cache, must-revalidate'); drupal_set_header('Accept-Ranges: bytes'); drupal_set_header('Content-Range: bytes '. $seek_start .'-'. $seek_end .'/'. $size); drupal_set_header('Content-Type: '. $mimetype); drupal_set_header('Content-Disposition: attachment; filename="'. $filename .'"'); drupal_set_header('Content-Length: '. ($seek_end - $seek_start + 1)); // Last-Modified is required for content served dynamically drupal_set_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_set_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 set_time_limit(0); // Push the data to the client. print(fread($fp, UC_FILE_BYTE_SIZE)); flush(); 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); } /** * Process 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'); } } }