Files
wp-digital-download/includes/class-wpdd-ajax.php
2025-08-28 19:35:28 -07:00

491 lines
20 KiB
PHP

<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Ajax {
public static function init() {
add_action('wp_ajax_wpdd_process_free_download', array(__CLASS__, 'process_free_download'));
add_action('wp_ajax_nopriv_wpdd_process_free_download', array(__CLASS__, 'process_free_download'));
add_action('wp_ajax_wpdd_add_to_cart', array(__CLASS__, 'add_to_cart'));
add_action('wp_ajax_nopriv_wpdd_add_to_cart', array(__CLASS__, 'add_to_cart'));
add_action('wp_ajax_wpdd_get_product_details', array(__CLASS__, 'get_product_details'));
add_action('wp_ajax_nopriv_wpdd_get_product_details', array(__CLASS__, 'get_product_details'));
add_action('wp_ajax_wpdd_check_download_status', array(__CLASS__, 'check_download_status'));
add_action('wp_ajax_nopriv_wpdd_check_download_status', array(__CLASS__, 'check_download_status'));
add_action('wp_ajax_wpdd_move_to_protected', array(__CLASS__, 'move_to_protected'));
add_action('wp_ajax_wpdd_upload_protected_file', array(__CLASS__, 'upload_protected_file'));
// Customer management actions
add_action('wp_ajax_wpdd_change_password', array(__CLASS__, 'change_password'));
}
public static function process_free_download() {
check_ajax_referer('wpdd-ajax-nonce', 'nonce');
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
if (!$product_id) {
wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
}
$product = get_post($product_id);
if (!$product || $product->post_type !== 'wpdd_product') {
wp_send_json_error(__('Product not found.', 'wp-digital-download'));
}
// IMPORTANT: Always check the actual database values, never trust client input
$is_free = get_post_meta($product_id, '_wpdd_is_free', true);
$price = get_post_meta($product_id, '_wpdd_price', true);
$sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
// Calculate actual price
$actual_price = ($sale_price && $sale_price < $price) ? $sale_price : $price;
// Security check: Verify this is actually a free product
if (!$is_free && $actual_price > 0) {
// Log potential security breach attempt
error_log('WPDD Security: Attempted to download paid product ' . $product_id . ' as free from IP: ' . $_SERVER['REMOTE_ADDR']);
wp_send_json_error(__('This product is not available for free download.', 'wp-digital-download'));
}
$customer_data = array(
'email' => sanitize_email($_POST['customer_email'] ?? ''),
'name' => sanitize_text_field($_POST['customer_name'] ?? '')
);
if (!is_user_logged_in() && empty($customer_data['email'])) {
wp_send_json_error(__('Please provide your email address.', 'wp-digital-download'));
}
// Create account if requested
if (!is_user_logged_in() && isset($_POST['create_account']) && $_POST['create_account'] == '1') {
$user_id = wp_create_user(
$customer_data['email'],
wp_generate_password(),
$customer_data['email']
);
if (!is_wp_error($user_id)) {
wp_update_user(array(
'ID' => $user_id,
'display_name' => $customer_data['name'],
'first_name' => $customer_data['name']
));
// Set customer role (remove default role first)
$user = new WP_User($user_id);
$user->remove_role('subscriber'); // Remove default WordPress role
$user->add_role('wpdd_customer');
// Send new account email
wp_new_user_notification($user_id, null, 'user');
// Log them in
wp_set_current_user($user_id);
wp_set_auth_cookie($user_id);
}
}
$order_id = WPDD_Orders::create_order($product_id, $customer_data, 'free');
if ($order_id) {
$order = WPDD_Orders::get_order($order_id);
wp_send_json_success(array(
'redirect_url' => add_query_arg(
'order_id',
$order->order_number,
get_permalink(get_option('wpdd_thank_you_page_id'))
)
));
} else {
wp_send_json_error(__('Failed to process download. Please try again.', 'wp-digital-download'));
}
}
public static function add_to_cart() {
check_ajax_referer('wpdd-ajax-nonce', 'nonce');
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
if (!$product_id) {
wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
}
$product = get_post($product_id);
if (!$product || $product->post_type !== 'wpdd_product') {
wp_send_json_error(__('Product not found.', 'wp-digital-download'));
}
if (!isset($_SESSION['wpdd_cart'])) {
$_SESSION['wpdd_cart'] = array();
}
if (in_array($product_id, $_SESSION['wpdd_cart'])) {
wp_send_json_error(__('Product already in cart.', 'wp-digital-download'));
}
$_SESSION['wpdd_cart'][] = $product_id;
wp_send_json_success(array(
'message' => __('Product added to cart.', 'wp-digital-download'),
'cart_count' => count($_SESSION['wpdd_cart']),
'checkout_url' => add_query_arg(
'product_id',
$product_id,
get_permalink(get_option('wpdd_checkout_page_id'))
)
));
}
public static function get_product_details() {
check_ajax_referer('wpdd-ajax-nonce', 'nonce');
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
if (!$product_id) {
wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
}
$product = get_post($product_id);
if (!$product || $product->post_type !== 'wpdd_product') {
wp_send_json_error(__('Product not found.', 'wp-digital-download'));
}
$price = get_post_meta($product_id, '_wpdd_price', true);
$sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
$is_free = get_post_meta($product_id, '_wpdd_is_free', true);
$files = get_post_meta($product_id, '_wpdd_files', true);
$download_limit = get_post_meta($product_id, '_wpdd_download_limit', true);
$download_expiry = get_post_meta($product_id, '_wpdd_download_expiry', true);
$creator = get_userdata($product->post_author);
$data = array(
'id' => $product_id,
'title' => $product->post_title,
'description' => $product->post_content,
'excerpt' => $product->post_excerpt,
'price' => $price,
'sale_price' => $sale_price,
'is_free' => $is_free,
'final_price' => $is_free ? 0 : (($sale_price && $sale_price < $price) ? $sale_price : $price),
'creator' => array(
'id' => $creator->ID,
'name' => $creator->display_name,
'avatar' => get_avatar_url($creator->ID)
),
'files_count' => is_array($files) ? count($files) : 0,
'download_limit' => $download_limit ?: __('Unlimited', 'wp-digital-download'),
'download_expiry' => $download_expiry ? sprintf(__('%d days', 'wp-digital-download'), $download_expiry) : __('Never expires', 'wp-digital-download'),
'thumbnail' => get_the_post_thumbnail_url($product_id, 'full'),
'categories' => wp_get_post_terms($product_id, 'wpdd_product_category', array('fields' => 'names')),
'tags' => wp_get_post_terms($product_id, 'wpdd_product_tag', array('fields' => 'names'))
);
if (is_user_logged_in()) {
$current_user = wp_get_current_user();
$data['can_download'] = WPDD_Customer::can_download_product($current_user->ID, $product_id);
}
wp_send_json_success($data);
}
public static function check_download_status() {
check_ajax_referer('wpdd-ajax-nonce', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error(__('Please login to check download status.', 'wp-digital-download'));
}
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
if (!$product_id) {
wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
}
$current_user = wp_get_current_user();
global $wpdb;
$order = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_orders
WHERE customer_id = %d
AND product_id = %d
AND status = 'completed'
ORDER BY purchase_date DESC
LIMIT 1",
$current_user->ID,
$product_id
));
if (!$order) {
wp_send_json_success(array(
'has_purchased' => false,
'message' => __('You have not purchased this product.', 'wp-digital-download')
));
}
$download_limit = get_post_meta($product_id, '_wpdd_download_limit', true);
$download_expiry = get_post_meta($product_id, '_wpdd_download_expiry', true);
$can_download = true;
$message = '';
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) {
$can_download = false;
$message = __('Your download period has expired.', 'wp-digital-download');
}
}
if ($can_download && $download_limit > 0 && $order->download_count >= $download_limit) {
$can_download = false;
$message = __('You have reached the download limit.', 'wp-digital-download');
}
wp_send_json_success(array(
'has_purchased' => true,
'can_download' => $can_download,
'download_count' => $order->download_count,
'download_limit' => $download_limit ?: 0,
'purchase_date' => $order->purchase_date,
'message' => $message ?: __('You can download this product.', 'wp-digital-download'),
'download_url' => $can_download ? wp_nonce_url(
add_query_arg(array('wpdd_download' => $order->id)),
'wpdd_download_' . $order->id
) : ''
));
}
public static function move_to_protected() {
check_ajax_referer('wpdd-admin-nonce', 'nonce');
if (!current_user_can('edit_wpdd_products')) {
wp_send_json_error(__('You do not have permission to perform this action.', 'wp-digital-download'));
}
$attachment_id = isset($_POST['attachment_id']) ? intval($_POST['attachment_id']) : 0;
if (!$attachment_id) {
wp_send_json_error(__('Invalid attachment ID.', 'wp-digital-download'));
}
$file_path = get_attached_file($attachment_id);
if (!$file_path || !file_exists($file_path)) {
wp_send_json_error(__('File not found.', 'wp-digital-download'));
}
// Create protected directory if it doesn't exist
$upload_dir = wp_upload_dir();
$protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
if (!file_exists($protected_dir)) {
wp_mkdir_p($protected_dir);
// Add .htaccess protection
$htaccess_content = "Options -Indexes\ndeny from all\n";
file_put_contents($protected_dir . '/.htaccess', $htaccess_content);
// Add index.php protection
$index_content = "<?php\n// Silence is golden.\n";
file_put_contents($protected_dir . '/index.php', $index_content);
}
// Create subdirectory based on year/month
$time = current_time('mysql');
$y = substr($time, 0, 4);
$m = substr($time, 5, 2);
$subdir = "$y/$m";
$protected_subdir = trailingslashit($protected_dir) . $subdir;
if (!file_exists($protected_subdir)) {
wp_mkdir_p($protected_subdir);
}
// Generate unique filename
$filename = basename($file_path);
$unique_filename = wp_unique_filename($protected_subdir, $filename);
$new_file_path = trailingslashit($protected_subdir) . $unique_filename;
// Move file to protected directory
if (rename($file_path, $new_file_path)) {
// Update attachment metadata
update_attached_file($attachment_id, $new_file_path);
// Store protected path reference
update_post_meta($attachment_id, '_wpdd_protected_path', $new_file_path);
// Generate secure token for download URL
$token = wp_generate_password(32, false);
update_post_meta($attachment_id, '_wpdd_download_token', $token);
wp_send_json_success(array(
'protected_url' => home_url('?wpdd_file_download=' . $attachment_id . '&token=' . $token),
'protected_path' => $new_file_path,
'message' => __('File moved to protected directory.', 'wp-digital-download')
));
} else {
wp_send_json_error(__('Failed to move file to protected directory.', 'wp-digital-download'));
}
}
public static function upload_protected_file() {
check_ajax_referer('wpdd-admin-nonce', 'nonce');
if (!current_user_can('edit_wpdd_products')) {
wp_send_json_error(__('You do not have permission to upload files.', 'wp-digital-download'));
}
if (!isset($_FILES['file'])) {
wp_send_json_error(__('No file uploaded.', 'wp-digital-download'));
}
$uploaded_file = $_FILES['file'];
// Check for upload errors
if ($uploaded_file['error'] !== UPLOAD_ERR_OK) {
wp_send_json_error(__('Upload failed.', 'wp-digital-download'));
}
// Validate file type
$allowed_types = array(
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
'zip', 'rar', '7z', 'tar', 'gz',
'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp',
'mp3', 'wav', 'flac', 'aac', 'ogg',
'mp4', 'avi', 'mkv', 'mov', 'wmv',
'txt', 'rtf', 'epub', 'mobi'
);
$file_ext = strtolower(pathinfo($uploaded_file['name'], PATHINFO_EXTENSION));
if (!in_array($file_ext, $allowed_types)) {
wp_send_json_error(__('File type not allowed.', 'wp-digital-download'));
}
// Create protected directory structure
$upload_dir = wp_upload_dir();
$protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
if (!file_exists($protected_dir)) {
wp_mkdir_p($protected_dir);
// Add .htaccess protection
$htaccess_content = "Options -Indexes\ndeny from all\n";
file_put_contents($protected_dir . '/.htaccess', $htaccess_content);
// Add index.php protection
$index_content = "<?php\n// Silence is golden.\n";
file_put_contents($protected_dir . '/index.php', $index_content);
}
// Create subdirectory based on year/month
$time = current_time('mysql');
$y = substr($time, 0, 4);
$m = substr($time, 5, 2);
$subdir = "$y/$m";
$protected_subdir = trailingslashit($protected_dir) . $subdir;
if (!file_exists($protected_subdir)) {
wp_mkdir_p($protected_subdir);
}
// Generate unique filename
$filename = sanitize_file_name($uploaded_file['name']);
$unique_filename = wp_unique_filename($protected_subdir, $filename);
$file_path = trailingslashit($protected_subdir) . $unique_filename;
// Move uploaded file to protected directory
if (move_uploaded_file($uploaded_file['tmp_name'], $file_path)) {
// Generate secure token for download URL
$token = wp_generate_password(32, false);
$file_id = 'wpdd_' . uniqid();
// Store file metadata
$file_meta = array(
'file_path' => $file_path,
'file_name' => $unique_filename,
'original_name' => $uploaded_file['name'],
'file_size' => filesize($file_path),
'file_type' => $uploaded_file['type'],
'upload_date' => current_time('mysql'),
'token' => $token
);
// Store in options table (in production, consider custom table)
update_option('wpdd_protected_file_' . $file_id, $file_meta);
wp_send_json_success(array(
'protected_url' => home_url('?wpdd_protected_download=' . $file_id . '&token=' . $token),
'file_id' => $file_id,
'file_name' => $unique_filename,
'file_size' => size_format($file_meta['file_size']),
'message' => __('File uploaded successfully to protected directory.', 'wp-digital-download')
));
} else {
wp_send_json_error(__('Failed to save uploaded file.', 'wp-digital-download'));
}
}
/**
* Handle password change for customers
*/
public static function change_password() {
check_ajax_referer('wpdd_change_password', 'nonce');
if (!is_user_logged_in()) {
wp_send_json_error(__('You must be logged in to change your password.', 'wp-digital-download'));
}
$current_user = wp_get_current_user();
// Only allow customers to change their own password this way
if (!in_array('wpdd_customer', $current_user->roles)) {
wp_send_json_error(__('This function is only available for customers.', 'wp-digital-download'));
}
$current_password = sanitize_text_field($_POST['current_password'] ?? '');
$new_password = sanitize_text_field($_POST['new_password'] ?? '');
$confirm_password = sanitize_text_field($_POST['confirm_password'] ?? '');
if (empty($current_password) || empty($new_password) || empty($confirm_password)) {
wp_send_json_error(__('All password fields are required.', 'wp-digital-download'));
}
if ($new_password !== $confirm_password) {
wp_send_json_error(__('New passwords do not match.', 'wp-digital-download'));
}
if (strlen($new_password) < 8) {
wp_send_json_error(__('New password must be at least 8 characters long.', 'wp-digital-download'));
}
// Verify current password
if (!wp_check_password($current_password, $current_user->user_pass, $current_user->ID)) {
wp_send_json_error(__('Current password is incorrect.', 'wp-digital-download'));
}
// Update password
$result = wp_update_user(array(
'ID' => $current_user->ID,
'user_pass' => $new_password
));
if (is_wp_error($result)) {
wp_send_json_error(__('Failed to update password. Please try again.', 'wp-digital-download'));
}
// Re-authenticate user to prevent logout
wp_set_current_user($current_user->ID);
wp_set_auth_cookie($current_user->ID);
wp_send_json_success(__('Password changed successfully!', 'wp-digital-download'));
}
}