🔧 Bug Fixes: - Fixed download limits defaulting to 5 instead of 0 for unlimited downloads - Fixed software license filename sanitization (spaces→dashes, dots→underscores, proper .zip extension) - Software downloads now show as "Test-Plugin-v2-2-0.zip" instead of "Test Plugin v2.2.0" ✨ UI/UX Enhancements: - Redesigned license key display to span full table width with FontAwesome copy icons - Added responsive CSS styling for license key rows - Integrated FontAwesome CDN for modern copy icons 🏗️ Architecture Improvements: - Added comprehensive filename sanitization in both download handler and API paths - Enhanced software license product handling for local package files - Improved error handling and logging throughout download processes 📦 Infrastructure: - Added Gitea workflows for automated releases on push to main - Created comprehensive .gitignore excluding test files and browser automation - Updated documentation with all recent improvements and technical insights 🔍 Technical Details: - Software license products served from wp-content/uploads/wpdd-packages/ - Download flow: token → process_download_by_token() → process_download() → deliver_file() - Dual path coverage for both API downloads and regular file delivery - Version placeholder system for automated deployment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
818 lines
33 KiB
PHP
818 lines
33 KiB
PHP
<?php
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class WPDD_Download_Handler {
|
|
|
|
public static function init() {
|
|
// Use priority 1 to ensure our handler runs early
|
|
add_action('init', array(__CLASS__, 'handle_download_request'), 1);
|
|
add_action('init', array(__CLASS__, 'handle_secure_file_download'), 1);
|
|
add_action('init', array(__CLASS__, 'handle_protected_download'), 1);
|
|
|
|
// Also hook to template_redirect as a fallback
|
|
add_action('template_redirect', array(__CLASS__, 'handle_download_request'), 1);
|
|
|
|
// Debug: Add a test endpoint for admins
|
|
if (current_user_can('manage_options') && isset($_GET['wpdd_test_download'])) {
|
|
add_action('init', function() {
|
|
wp_die('WPDD Download Handler is loaded and working! Current time: ' . current_time('mysql'));
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
public static function handle_download_request() {
|
|
// TESTING: Add a test parameter to verify changes are taking effect
|
|
if (isset($_GET['test_update_working'])) {
|
|
wp_die('TEST: Changes are working! File updated successfully.');
|
|
}
|
|
|
|
// Early exit if not a download request
|
|
if (!isset($_GET['wpdd_download']) && !isset($_GET['wpdd_download_token'])) {
|
|
return;
|
|
}
|
|
|
|
// Debug logging for admin users
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Download request detected!');
|
|
error_log('WPDD Debug: GET params: ' . print_r($_GET, true));
|
|
error_log('WPDD Debug: Current action: ' . current_action());
|
|
}
|
|
|
|
if (isset($_GET['wpdd_download'])) {
|
|
// Add debug output before processing
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Processing download by order ID: ' . $_GET['wpdd_download']);
|
|
}
|
|
self::process_download_by_order();
|
|
exit; // Make sure we exit after processing
|
|
}
|
|
|
|
if (isset($_GET['wpdd_download_token'])) {
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Processing download by token: ' . $_GET['wpdd_download_token']);
|
|
}
|
|
self::process_download_by_token();
|
|
exit; // Make sure we exit after processing
|
|
}
|
|
}
|
|
|
|
public static function handle_protected_download() {
|
|
if (!isset($_GET['wpdd_protected_download'])) {
|
|
return;
|
|
}
|
|
|
|
$file_id = sanitize_text_field($_GET['wpdd_protected_download']);
|
|
$token = sanitize_text_field($_GET['token'] ?? '');
|
|
|
|
if (!$file_id || !$token) {
|
|
wp_die(__('Invalid download link.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Get file metadata
|
|
$file_meta = get_option('wpdd_protected_file_' . $file_id);
|
|
|
|
if (!$file_meta || $file_meta['token'] !== $token) {
|
|
wp_die(__('Invalid download token.', 'wp-digital-download'));
|
|
}
|
|
|
|
if (!file_exists($file_meta['file_path'])) {
|
|
wp_die(__('File not found.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Deliver the file
|
|
self::deliver_protected_file($file_meta['file_path']);
|
|
}
|
|
|
|
private static function process_download_by_order() {
|
|
$order_id = intval($_GET['wpdd_download']);
|
|
|
|
// Debug nonce verification
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Order ID: ' . $order_id);
|
|
error_log('WPDD Debug: Nonce received: ' . ($_GET['_wpnonce'] ?? 'none'));
|
|
error_log('WPDD Debug: Expected nonce action: wpdd_download_' . $order_id);
|
|
}
|
|
|
|
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'wpdd_download_' . $order_id)) {
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Nonce verification failed!');
|
|
}
|
|
wp_die(__('Invalid download link.', 'wp-digital-download'));
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
// Check by email if guest
|
|
if (isset($_GET['customer_email']) && isset($_GET['key'])) {
|
|
$email = sanitize_email($_GET['customer_email']);
|
|
$key = sanitize_text_field($_GET['key']);
|
|
|
|
// Verify the key
|
|
$expected_key = substr(md5($email . AUTH_KEY), 0, 10);
|
|
if ($key !== $expected_key) {
|
|
wp_die(__('Invalid access key.', 'wp-digital-download'));
|
|
}
|
|
|
|
$order = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}wpdd_orders
|
|
WHERE id = %d AND customer_email = %s AND status = 'completed'",
|
|
$order_id,
|
|
$email
|
|
));
|
|
} elseif (is_user_logged_in()) {
|
|
$current_user = wp_get_current_user();
|
|
|
|
$order = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}wpdd_orders
|
|
WHERE id = %d AND (customer_id = %d OR customer_email = %s) AND status = 'completed'",
|
|
$order_id,
|
|
$current_user->ID,
|
|
$current_user->user_email
|
|
));
|
|
} else {
|
|
// For unregistered users, try to look up order by order number from URL if available
|
|
if (isset($_GET['order_id'])) {
|
|
$order_number = sanitize_text_field($_GET['order_id']);
|
|
|
|
// Look up order by order ID and verify it matches the order number
|
|
$order = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}wpdd_orders
|
|
WHERE id = %d AND order_number = %s AND status = 'completed'",
|
|
$order_id,
|
|
$order_number
|
|
));
|
|
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Guest order lookup - Order ID: ' . $order_id . ', Order Number: ' . $order_number . ', Found: ' . ($order ? 'Yes' : 'No'));
|
|
}
|
|
|
|
if (!$order) {
|
|
wp_die(__('Invalid order or order not found.', 'wp-digital-download'));
|
|
}
|
|
} else {
|
|
// Debug: Show what parameters we have
|
|
$debug_info = '';
|
|
if (current_user_can('manage_options')) {
|
|
$debug_info = '<br><br>Debug info:<br>';
|
|
$debug_info .= 'GET params: ' . print_r($_GET, true);
|
|
$debug_info .= '<br>User logged in: ' . (is_user_logged_in() ? 'Yes' : 'No');
|
|
}
|
|
wp_die(__('You must be logged in to download this product or provide a valid order reference.', 'wp-digital-download') . $debug_info);
|
|
}
|
|
}
|
|
|
|
if (!$order) {
|
|
wp_die(__('Invalid order or you do not have permission to download this product.', 'wp-digital-download'));
|
|
}
|
|
|
|
self::process_download($order);
|
|
}
|
|
|
|
private static function process_download_by_token() {
|
|
$token = sanitize_text_field($_GET['wpdd_download_token']);
|
|
|
|
global $wpdb;
|
|
|
|
$download_link = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT dl.*, o.*
|
|
FROM {$wpdb->prefix}wpdd_download_links dl
|
|
INNER JOIN {$wpdb->prefix}wpdd_orders o ON dl.order_id = o.id
|
|
WHERE dl.token = %s",
|
|
$token
|
|
));
|
|
|
|
if (!$download_link) {
|
|
wp_die(__('Invalid download link.', 'wp-digital-download'));
|
|
}
|
|
|
|
if ($download_link->expires_at < current_time('mysql')) {
|
|
// Check if user still has downloads remaining
|
|
if ($download_link->max_downloads > 0 && $download_link->download_count >= $download_link->max_downloads) {
|
|
wp_die(__('This download link has expired and you have no downloads remaining.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Only send refresh email if this appears to be a real user attempt (not automated)
|
|
// Check if the customer is unregistered (has no user account)
|
|
$is_unregistered_customer = ($download_link->customer_id == 0);
|
|
|
|
if ($is_unregistered_customer) {
|
|
// Generate new token and send refresh email only for unregistered customers
|
|
$new_token = self::refresh_download_token($download_link->order_id, $download_link->customer_email);
|
|
|
|
if ($new_token) {
|
|
wp_die(sprintf(
|
|
__('Your download link has expired. A new download link has been sent to %s. Please check your email and try again.', 'wp-digital-download'),
|
|
esc_html($download_link->customer_email)
|
|
));
|
|
} else {
|
|
wp_die(__('This download link has expired and could not be refreshed. Please contact support.', 'wp-digital-download'));
|
|
}
|
|
} else {
|
|
// For registered users, just show expired message (they can log in to get new links)
|
|
wp_die(__('This download link has expired. Please log in to your account to get a new download link.', 'wp-digital-download'));
|
|
}
|
|
}
|
|
|
|
if ($download_link->max_downloads > 0 && $download_link->download_count >= $download_link->max_downloads) {
|
|
wp_die(__('Download limit exceeded.', 'wp-digital-download'));
|
|
}
|
|
|
|
$wpdb->update(
|
|
$wpdb->prefix . 'wpdd_download_links',
|
|
array('download_count' => $download_link->download_count + 1),
|
|
array('id' => $download_link->id),
|
|
array('%d'),
|
|
array('%d')
|
|
);
|
|
|
|
self::process_download($download_link);
|
|
}
|
|
|
|
/**
|
|
* Refresh an expired download token and send new link via email
|
|
*/
|
|
private static function refresh_download_token($order_id, $customer_email) {
|
|
global $wpdb;
|
|
|
|
// Generate new token with extended expiry (72 hours as suggested)
|
|
$new_token = wp_hash(uniqid() . $order_id . time());
|
|
$new_expires_at = date('Y-m-d H:i:s', strtotime('+72 hours'));
|
|
|
|
// Update the existing download link with new token and expiry
|
|
$updated = $wpdb->update(
|
|
$wpdb->prefix . 'wpdd_download_links',
|
|
array(
|
|
'token' => $new_token,
|
|
'expires_at' => $new_expires_at,
|
|
'refreshed_at' => current_time('mysql')
|
|
),
|
|
array('order_id' => $order_id),
|
|
array('%s', '%s', '%s'),
|
|
array('%d')
|
|
);
|
|
|
|
if (!$updated) {
|
|
return false;
|
|
}
|
|
|
|
// Send refresh email
|
|
self::send_refresh_email($order_id, $new_token, $customer_email);
|
|
|
|
return $new_token;
|
|
}
|
|
|
|
/**
|
|
* Send refresh email with new download link
|
|
*/
|
|
private static function send_refresh_email($order_id, $token, $customer_email) {
|
|
global $wpdb;
|
|
|
|
// Get order details
|
|
$order = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT o.*, p.post_title as product_name
|
|
FROM {$wpdb->prefix}wpdd_orders o
|
|
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
|
|
WHERE o.id = %d",
|
|
$order_id
|
|
));
|
|
|
|
if (!$order) {
|
|
return false;
|
|
}
|
|
|
|
$download_url = add_query_arg(array(
|
|
'wpdd_download_token' => $token
|
|
), home_url());
|
|
|
|
$subject = sprintf(__('New Download Link for %s', 'wp-digital-download'), $order->product_name);
|
|
|
|
$message = sprintf(
|
|
__("Hello %s,\n\nYour download link for \"%s\" has expired, so we've generated a new one for you.\n\nThis new link is valid for 72 hours and can be used for your remaining downloads.\n\nDownload Link: %s\n\nOrder Number: %s\nPurchase Date: %s\n\nIf you have any issues, please contact our support team.\n\nBest regards,\n%s", 'wp-digital-download'),
|
|
$order->customer_name,
|
|
$order->product_name,
|
|
$download_url,
|
|
$order->order_number,
|
|
date_i18n(get_option('date_format'), strtotime($order->purchase_date)),
|
|
get_bloginfo('name')
|
|
);
|
|
|
|
$headers = array('Content-Type: text/plain; charset=UTF-8');
|
|
|
|
return wp_mail($customer_email, $subject, $message, $headers);
|
|
}
|
|
|
|
/**
|
|
* Create download token for orders that don't have one (legacy orders)
|
|
*/
|
|
public static function ensure_download_token($order_id) {
|
|
global $wpdb;
|
|
|
|
// Check if token already exists
|
|
$existing_token = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT token FROM {$wpdb->prefix}wpdd_download_links WHERE order_id = %d",
|
|
$order_id
|
|
));
|
|
|
|
if ($existing_token) {
|
|
return $existing_token;
|
|
}
|
|
|
|
// Create new token for legacy order
|
|
$token = wp_hash(uniqid() . $order_id . time());
|
|
$expires_at = date('Y-m-d H:i:s', strtotime('+7 days'));
|
|
|
|
$wpdb->insert(
|
|
$wpdb->prefix . 'wpdd_download_links',
|
|
array(
|
|
'order_id' => $order_id,
|
|
'token' => $token,
|
|
'expires_at' => $expires_at,
|
|
'max_downloads' => 5,
|
|
'created_at' => current_time('mysql')
|
|
),
|
|
array('%d', '%s', '%s', '%d', '%s')
|
|
);
|
|
|
|
return $token;
|
|
}
|
|
|
|
private static function process_download($order) {
|
|
$product_id = $order->product_id;
|
|
$files = get_post_meta($product_id, '_wpdd_files', true);
|
|
|
|
// Debug output for admins
|
|
if (current_user_can('manage_options') && empty($files)) {
|
|
wp_die(sprintf(__('Debug: No files found for product ID %d. Files data: %s', 'wp-digital-download'),
|
|
$product_id, '<pre>' . print_r($files, true) . '</pre>'));
|
|
}
|
|
|
|
if (empty($files)) {
|
|
wp_die(__('No files available for download.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Debug output for admins
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Files for product ' . $product_id . ': ' . print_r($files, true));
|
|
}
|
|
|
|
$download_limit = get_post_meta($product_id, '_wpdd_download_limit', true);
|
|
$download_expiry = get_post_meta($product_id, '_wpdd_download_expiry', true);
|
|
|
|
if ($download_expiry > 0) {
|
|
$expiry_date = date('Y-m-d H:i:s', strtotime($order->purchase_date . ' + ' . $download_expiry . ' days'));
|
|
if (current_time('mysql') > $expiry_date) {
|
|
wp_die(__('Your download period has expired.', 'wp-digital-download'));
|
|
}
|
|
}
|
|
|
|
if ($download_limit > 0 && $order->download_count >= $download_limit) {
|
|
wp_die(__('You have reached the download limit for this product.', 'wp-digital-download'));
|
|
}
|
|
|
|
global $wpdb;
|
|
$wpdb->update(
|
|
$wpdb->prefix . 'wpdd_orders',
|
|
array('download_count' => $order->download_count + 1),
|
|
array('id' => $order->id),
|
|
array('%d'),
|
|
array('%d')
|
|
);
|
|
|
|
$file_index = isset($_GET['file']) ? intval($_GET['file']) : 0;
|
|
|
|
// Handle array structure - files might be indexed or not
|
|
$file_list = array_values($files); // Reindex to ensure numeric keys
|
|
|
|
if (count($file_list) > 1 && !isset($_GET['file'])) {
|
|
self::show_file_selection($file_list, $order);
|
|
exit;
|
|
}
|
|
|
|
if (!isset($file_list[$file_index])) {
|
|
// Debug output for admins
|
|
if (current_user_can('manage_options')) {
|
|
wp_die(sprintf(__('Debug: File index %d not found. Available files: %s', 'wp-digital-download'),
|
|
$file_index, '<pre>' . print_r($file_list, true) . '</pre>'));
|
|
}
|
|
wp_die(__('File not found.', 'wp-digital-download'));
|
|
}
|
|
|
|
$file = $file_list[$file_index];
|
|
|
|
self::log_download($order, $product_id, $file['id'] ?? $file_index);
|
|
|
|
// Debug for admins
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Processing file - ID: ' . ($file['id'] ?? 'none') . ', URL: ' . ($file['url'] ?? 'none'));
|
|
}
|
|
|
|
// Check if this is a protected file
|
|
if (isset($file['id']) && strpos($file['id'], 'wpdd_') === 0) {
|
|
// This is a protected file, get its metadata
|
|
$file_meta = get_option('wpdd_protected_file_' . $file['id']);
|
|
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Protected file detected - ' . $file['id']);
|
|
error_log('WPDD Debug: File meta exists: ' . ($file_meta ? 'Yes' : 'No'));
|
|
if ($file_meta) {
|
|
error_log('WPDD Debug: File path exists: ' . (file_exists($file_meta['file_path']) ? 'Yes' : 'No'));
|
|
}
|
|
}
|
|
|
|
if ($file_meta && file_exists($file_meta['file_path'])) {
|
|
$enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
|
|
$product_name = get_the_title($product_id);
|
|
|
|
if ($enable_watermark && self::is_watermarkable($file_meta['file_path'])) {
|
|
$watermarked_file = WPDD_Watermark::apply_watermark($file_meta['file_path'], $order);
|
|
if ($watermarked_file) {
|
|
self::deliver_protected_file($watermarked_file, true, $product_name);
|
|
} else {
|
|
self::deliver_protected_file($file_meta['file_path'], false, $product_name);
|
|
}
|
|
} else {
|
|
self::deliver_protected_file($file_meta['file_path'], false, $product_name);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if URL contains protected download parameter (alternative check)
|
|
if (isset($file['url']) && strpos($file['url'], 'wpdd_protected_download=') !== false) {
|
|
// Extract file_id from URL
|
|
if (preg_match('/wpdd_protected_download=([^&]+)/', $file['url'], $matches)) {
|
|
$file_id = $matches[1];
|
|
$file_meta = get_option('wpdd_protected_file_' . $file_id);
|
|
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Protected URL detected - ' . $file_id);
|
|
}
|
|
|
|
if ($file_meta && file_exists($file_meta['file_path'])) {
|
|
$enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
|
|
$product_name = get_the_title($product_id);
|
|
|
|
if ($enable_watermark && self::is_watermarkable($file_meta['file_path'])) {
|
|
$watermarked_file = WPDD_Watermark::apply_watermark($file_meta['file_path'], $order);
|
|
if ($watermarked_file) {
|
|
self::deliver_protected_file($watermarked_file, true, $product_name);
|
|
} else {
|
|
self::deliver_protected_file($file_meta['file_path'], false, $product_name);
|
|
}
|
|
} else {
|
|
self::deliver_protected_file($file_meta['file_path'], false, $product_name);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regular file handling (backward compatibility)
|
|
$enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
|
|
|
|
// Generate proper filename for software license products
|
|
$product_type = get_post_meta($product_id, '_wpdd_product_type', true);
|
|
$final_filename = $file['name'];
|
|
|
|
if ($product_type === 'software_license') {
|
|
// Check if this is a package file (contains version info)
|
|
if (strpos($file['url'], 'wpdd-packages/') !== false && preg_match('/package-v([^\/]+)\.zip$/', $file['url'], $matches)) {
|
|
$version = $matches[1];
|
|
$product_name = get_the_title($product_id);
|
|
|
|
// Create sanitized filename using product name and version
|
|
$safe_name = str_replace([' ', '.'], ['-', '_'], $product_name . ' v' . $version);
|
|
$safe_name = sanitize_file_name($safe_name);
|
|
$final_filename = $safe_name . '.zip';
|
|
}
|
|
}
|
|
|
|
if ($enable_watermark && self::is_watermarkable($file['url'])) {
|
|
$watermarked_file = WPDD_Watermark::apply_watermark($file['url'], $order);
|
|
if ($watermarked_file) {
|
|
self::deliver_file($watermarked_file, $final_filename, true);
|
|
} else {
|
|
self::deliver_file($file['url'], $final_filename);
|
|
}
|
|
} else {
|
|
self::deliver_file($file['url'], $final_filename);
|
|
}
|
|
}
|
|
|
|
private static function show_file_selection($files, $order) {
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html <?php language_attributes(); ?>>
|
|
<head>
|
|
<meta charset="<?php bloginfo('charset'); ?>">
|
|
<title><?php _e('Select File to Download', 'wp-digital-download'); ?></title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
max-width: 600px;
|
|
margin: 50px auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
.container {
|
|
background: white;
|
|
padding: 30px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
h1 {
|
|
color: #333;
|
|
margin-bottom: 20px;
|
|
}
|
|
.file-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
}
|
|
.file-item {
|
|
margin-bottom: 15px;
|
|
padding: 15px;
|
|
background: #f9f9f9;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.file-name {
|
|
font-weight: 500;
|
|
}
|
|
.download-btn {
|
|
padding: 8px 16px;
|
|
background: #2271b1;
|
|
color: white;
|
|
text-decoration: none;
|
|
border-radius: 4px;
|
|
transition: background 0.2s;
|
|
}
|
|
.download-btn:hover {
|
|
background: #135e96;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1><?php _e('Select File to Download', 'wp-digital-download'); ?></h1>
|
|
<ul class="file-list">
|
|
<?php foreach ($files as $index => $file) : ?>
|
|
<li class="file-item">
|
|
<span class="file-name"><?php echo esc_html($file['name'] ?? 'File ' . ($index + 1)); ?></span>
|
|
<a href="<?php echo add_query_arg('file', $index); ?>" class="download-btn">
|
|
<?php _e('Download', 'wp-digital-download'); ?>
|
|
</a>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
}
|
|
|
|
public static function handle_secure_file_download() {
|
|
if (!isset($_GET['wpdd_file_download'])) {
|
|
return;
|
|
}
|
|
|
|
$attachment_id = intval($_GET['wpdd_file_download']);
|
|
$token = sanitize_text_field($_GET['token'] ?? '');
|
|
|
|
if (!$attachment_id || !$token) {
|
|
wp_die(__('Invalid download link.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Verify token
|
|
$stored_token = get_post_meta($attachment_id, '_wpdd_download_token', true);
|
|
if ($token !== $stored_token) {
|
|
wp_die(__('Invalid download token.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Check if user has permission to download
|
|
if (!is_user_logged_in()) {
|
|
wp_die(__('You must be logged in to download this file.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Get protected file path
|
|
$protected_path = get_post_meta($attachment_id, '_wpdd_protected_path', true);
|
|
if (!$protected_path || !file_exists($protected_path)) {
|
|
// Fallback to regular attachment path
|
|
$protected_path = get_attached_file($attachment_id);
|
|
}
|
|
|
|
if (!$protected_path || !file_exists($protected_path)) {
|
|
wp_die(__('File not found.', 'wp-digital-download'));
|
|
}
|
|
|
|
// Deliver the file
|
|
self::deliver_protected_file($protected_path);
|
|
}
|
|
|
|
private static function deliver_protected_file($file_path, $is_temp = false, $product_name = null) {
|
|
if (!file_exists($file_path)) {
|
|
wp_die(__('File not found.', 'wp-digital-download'));
|
|
}
|
|
|
|
$file_size = filesize($file_path);
|
|
$file_info = wp_check_filetype($file_path);
|
|
|
|
// If product name is provided, use it with the original extension
|
|
if ($product_name) {
|
|
$original_file_name = basename($file_path);
|
|
$original_extension = pathinfo($original_file_name, PATHINFO_EXTENSION);
|
|
|
|
// If no extension found, try to detect it
|
|
if (empty($original_extension)) {
|
|
// Check if it's a zip file by reading the first few bytes
|
|
$handle = fopen($file_path, 'rb');
|
|
if ($handle) {
|
|
$header = fread($handle, 4);
|
|
fclose($handle);
|
|
|
|
// ZIP files start with PK (0x504B)
|
|
if (substr($header, 0, 2) === 'PK') {
|
|
$original_extension = 'zip';
|
|
$file_info['type'] = 'application/zip';
|
|
$file_info['ext'] = 'zip';
|
|
}
|
|
}
|
|
|
|
// If still no extension, try wp_check_filetype result
|
|
if (empty($original_extension) && !empty($file_info['ext'])) {
|
|
$original_extension = $file_info['ext'];
|
|
}
|
|
}
|
|
|
|
// Sanitize product name for filename use
|
|
// Convert spaces to dashes and dots to underscores, then use standard sanitization
|
|
$safe_product_name = str_replace([' ', '.'], ['-', '_'], $product_name);
|
|
$safe_product_name = sanitize_file_name($safe_product_name);
|
|
$file_name = $safe_product_name . ($original_extension ? '.' . $original_extension : '');
|
|
} else {
|
|
// Fallback to original logic if no product name provided
|
|
$file_name = basename($file_path);
|
|
$has_extension = strpos($file_name, '.') !== false;
|
|
|
|
// If no extension in filename, try to determine from content
|
|
if (!$has_extension) {
|
|
// Check if it's a zip file by reading the first few bytes
|
|
$handle = fopen($file_path, 'rb');
|
|
if ($handle) {
|
|
$header = fread($handle, 4);
|
|
fclose($handle);
|
|
|
|
// ZIP files start with PK (0x504B)
|
|
if (substr($header, 0, 2) === 'PK') {
|
|
$file_info['type'] = 'application/zip';
|
|
$file_info['ext'] = 'zip';
|
|
$file_name .= '.zip';
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we still don't have an extension but wp_check_filetype detected one, add it
|
|
if (!$has_extension && !empty($file_info['ext']) && strpos($file_name, '.' . $file_info['ext']) === false) {
|
|
$file_name .= '.' . $file_info['ext'];
|
|
}
|
|
}
|
|
|
|
nocache_headers();
|
|
|
|
header('Content-Type: ' . ($file_info['type'] ?: 'application/octet-stream'));
|
|
header('Content-Disposition: attachment; filename="' . $file_name . '"');
|
|
header('Content-Length: ' . $file_size);
|
|
header('Content-Transfer-Encoding: binary');
|
|
|
|
if (ob_get_level()) {
|
|
ob_end_clean();
|
|
}
|
|
|
|
readfile($file_path);
|
|
|
|
if ($is_temp && file_exists($file_path)) {
|
|
unlink($file_path);
|
|
}
|
|
|
|
exit;
|
|
}
|
|
|
|
private static function deliver_file($file_path, $file_name, $is_temp = false) {
|
|
$original_path = $file_path;
|
|
|
|
// Check if this is a protected file URL
|
|
if (strpos($file_path, 'wpdd_file_download=') !== false) {
|
|
// This is already a protected URL, just redirect to it
|
|
wp_redirect($file_path);
|
|
exit;
|
|
}
|
|
|
|
// Check if file is in protected directory
|
|
$upload_dir = wp_upload_dir();
|
|
$protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
|
|
|
|
if (strpos($file_path, $protected_dir) === 0) {
|
|
// File is in protected directory, deliver directly
|
|
if (!file_exists($file_path)) {
|
|
wp_die(__('Protected file not found.', 'wp-digital-download'));
|
|
}
|
|
|
|
self::deliver_protected_file($file_path);
|
|
return;
|
|
}
|
|
|
|
// Convert URL to file path if needed
|
|
if (filter_var($file_path, FILTER_VALIDATE_URL)) {
|
|
$upload_dir = wp_upload_dir();
|
|
$site_url = get_site_url();
|
|
|
|
// Debug logging
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Original URL: ' . $file_path);
|
|
error_log('WPDD Debug: Upload baseurl: ' . $upload_dir['baseurl']);
|
|
error_log('WPDD Debug: Upload basedir: ' . $upload_dir['basedir']);
|
|
}
|
|
|
|
// Handle various URL formats
|
|
if (strpos($file_path, $upload_dir['baseurl']) === 0) {
|
|
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file_path);
|
|
} elseif (strpos($file_path, $site_url) === 0) {
|
|
// Handle site URL paths
|
|
$file_path = str_replace($site_url . '/wp-content/uploads', $upload_dir['basedir'], $file_path);
|
|
} else {
|
|
// External URL - just redirect
|
|
wp_redirect($file_path);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Debug logging
|
|
if (current_user_can('manage_options')) {
|
|
error_log('WPDD Debug: Final file path: ' . $file_path);
|
|
error_log('WPDD Debug: File exists: ' . (file_exists($file_path) ? 'Yes' : 'No'));
|
|
}
|
|
|
|
if (!file_exists($file_path)) {
|
|
// Debug output for admin users
|
|
if (current_user_can('manage_options')) {
|
|
wp_die(sprintf(__('File not found at path: %s<br>Original: %s', 'wp-digital-download'),
|
|
$file_path, $original_path));
|
|
}
|
|
wp_die(__('File not found. Please contact the administrator.', 'wp-digital-download'));
|
|
}
|
|
|
|
$file_size = filesize($file_path);
|
|
$file_type = wp_check_filetype($file_path);
|
|
|
|
if (empty($file_name)) {
|
|
$file_name = basename($file_path);
|
|
}
|
|
|
|
nocache_headers();
|
|
|
|
header('Content-Type: ' . ($file_type['type'] ?: 'application/octet-stream'));
|
|
header('Content-Disposition: attachment; filename="' . $file_name . '"');
|
|
header('Content-Length: ' . $file_size);
|
|
header('Content-Transfer-Encoding: binary');
|
|
|
|
if (ob_get_level()) {
|
|
ob_end_clean();
|
|
}
|
|
|
|
readfile($file_path);
|
|
|
|
if ($is_temp && file_exists($file_path)) {
|
|
unlink($file_path);
|
|
}
|
|
|
|
exit;
|
|
}
|
|
|
|
private static function log_download($order, $product_id, $file_id) {
|
|
global $wpdb;
|
|
|
|
$wpdb->insert(
|
|
$wpdb->prefix . 'wpdd_downloads',
|
|
array(
|
|
'order_id' => $order->id,
|
|
'product_id' => $product_id,
|
|
'customer_id' => $order->customer_id,
|
|
'file_id' => $file_id,
|
|
'download_date' => current_time('mysql'),
|
|
'ip_address' => $_SERVER['REMOTE_ADDR'],
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT']
|
|
),
|
|
array('%d', '%d', '%d', '%s', '%s', '%s', '%s')
|
|
);
|
|
}
|
|
|
|
private static function is_watermarkable($file_url) {
|
|
$supported_types = array('jpg', 'jpeg', 'png', 'gif', 'pdf');
|
|
$file_extension = strtolower(pathinfo($file_url, PATHINFO_EXTENSION));
|
|
return in_array($file_extension, $supported_types);
|
|
}
|
|
}
|