First Commit

This commit is contained in:
2025-08-28 19:35:28 -07:00
commit 264e65006a
488 changed files with 155661 additions and 0 deletions

View File

@@ -0,0 +1,752 @@
<?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() {
$download_link_id = intval($_GET['wpdd_download']);
// Debug nonce verification
if (current_user_can('manage_options')) {
error_log('WPDD Debug: Download Link ID: ' . $download_link_id);
error_log('WPDD Debug: Nonce received: ' . ($_GET['_wpnonce'] ?? 'none'));
error_log('WPDD Debug: Expected nonce action: wpdd_download_' . $download_link_id);
}
if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'wpdd_download_' . $download_link_id)) {
if (current_user_can('manage_options')) {
error_log('WPDD Debug: Nonce verification failed!');
}
wp_die(__('Invalid download link.', 'wp-digital-download'));
}
global $wpdb;
// First get the download link to find the associated order
$download_link = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_download_links WHERE id = %d",
$download_link_id
));
if (!$download_link) {
wp_die(__('Invalid download link.', 'wp-digital-download'));
}
$order_id = $download_link->order_id;
// 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);
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);
} else {
self::deliver_protected_file($file_meta['file_path']);
}
} else {
self::deliver_protected_file($file_meta['file_path']);
}
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);
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);
} else {
self::deliver_protected_file($file_meta['file_path']);
}
} else {
self::deliver_protected_file($file_meta['file_path']);
}
return;
}
}
}
// Regular file handling (backward compatibility)
$enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
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, $file['name'], true);
} else {
self::deliver_file($file['url'], $file['name']);
}
} else {
self::deliver_file($file['url'], $file['name']);
}
}
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) {
if (!file_exists($file_path)) {
wp_die(__('File not found.', 'wp-digital-download'));
}
$file_name = basename($file_path);
$file_size = filesize($file_path);
$file_type = wp_check_filetype($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 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);
}
}