First Commit

This commit is contained in:
2025-08-28 19:35:28 -07:00
commit 5aa0777fd3
507 changed files with 158447 additions and 0 deletions

View File

@@ -0,0 +1,491 @@
<?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'));
}
}

View File

@@ -0,0 +1,177 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Creator {
public static function init() {
add_action('show_user_profile', array(__CLASS__, 'add_profile_fields'));
add_action('edit_user_profile', array(__CLASS__, 'add_profile_fields'));
add_action('personal_options_update', array(__CLASS__, 'save_profile_fields'));
add_action('edit_user_profile_update', array(__CLASS__, 'save_profile_fields'));
add_action('user_register', array(__CLASS__, 'set_default_fields'));
add_action('wpdd_order_completed', array(__CLASS__, 'add_earnings_to_balance'));
}
public static function add_profile_fields($user) {
// Only show for creators and admins
if (!self::is_creator($user->ID) && !current_user_can('manage_options')) {
return;
}
?>
<h3><?php _e('Creator Payout Settings', 'wp-digital-download'); ?></h3>
<table class="form-table">
<tr>
<th><label for="wpdd_paypal_email"><?php _e('PayPal Email', 'wp-digital-download'); ?></label></th>
<td>
<input type="email" name="wpdd_paypal_email" id="wpdd_paypal_email"
value="<?php echo esc_attr(get_user_meta($user->ID, 'wpdd_paypal_email', true)); ?>"
class="regular-text" />
<p class="description"><?php _e('PayPal email address for receiving payouts', 'wp-digital-download'); ?></p>
</td>
</tr>
<?php if (current_user_can('manage_options')) : ?>
<tr>
<th><label><?php _e('Creator Balance', 'wp-digital-download'); ?></label></th>
<td>
<?php
$balance = self::get_creator_balance($user->ID);
$currency = get_option('wpdd_currency', 'USD');
echo '<strong>' . wpdd_format_price($balance, $currency) . '</strong>';
?>
<p class="description"><?php _e('Current unpaid earnings balance', 'wp-digital-download'); ?></p>
</td>
</tr>
<tr>
<th><label><?php _e('Total Earnings', 'wp-digital-download'); ?></label></th>
<td>
<?php
$total = self::get_creator_total_earnings($user->ID);
echo '<strong>' . wpdd_format_price($total, $currency) . '</strong>';
?>
<p class="description"><?php _e('Total lifetime earnings', 'wp-digital-download'); ?></p>
</td>
</tr>
<?php endif; ?>
</table>
<?php
}
public static function save_profile_fields($user_id) {
if (!current_user_can('edit_user', $user_id)) {
return;
}
if (isset($_POST['wpdd_paypal_email'])) {
$email = sanitize_email($_POST['wpdd_paypal_email']);
if (!empty($email) && !is_email($email)) {
return;
}
update_user_meta($user_id, 'wpdd_paypal_email', $email);
}
}
public static function set_default_fields($user_id) {
if (self::is_creator($user_id)) {
update_user_meta($user_id, 'wpdd_creator_balance', 0);
update_user_meta($user_id, 'wpdd_total_earnings', 0);
}
}
public static function is_creator($user_id) {
$user = get_userdata($user_id);
return $user && in_array('wpdd_creator', (array) $user->roles);
}
public static function get_creator_balance($user_id) {
return floatval(get_user_meta($user_id, 'wpdd_creator_balance', true));
}
public static function get_creator_total_earnings($user_id) {
global $wpdb;
$total = $wpdb->get_var($wpdb->prepare(
"SELECT SUM(o.total)
FROM {$wpdb->prefix}wpdd_orders o
INNER JOIN {$wpdb->posts} p ON o.product_id = p.ID
WHERE p.post_author = %d
AND o.status = 'completed'",
$user_id
));
return floatval($total);
}
public static function get_creator_net_earnings($user_id) {
$total = self::get_creator_total_earnings($user_id);
$commission_rate = floatval(get_option('wpdd_commission_rate', 0));
$net = $total * (1 - ($commission_rate / 100));
return $net;
}
public static function add_earnings_to_balance($order_id) {
global $wpdb;
$order = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
$order_id
));
if (!$order || $order->status !== 'completed') {
return;
}
$product = get_post($order->product_id);
if (!$product) {
return;
}
$creator_id = $product->post_author;
$commission_rate = floatval(get_option('wpdd_commission_rate', 0));
$creator_share = $order->total * (1 - ($commission_rate / 100));
// Update creator balance
$current_balance = self::get_creator_balance($creator_id);
update_user_meta($creator_id, 'wpdd_creator_balance', $current_balance + $creator_share);
// Log the earning
$wpdb->insert(
$wpdb->prefix . 'wpdd_creator_earnings',
array(
'creator_id' => $creator_id,
'order_id' => $order_id,
'product_id' => $order->product_id,
'sale_amount' => $order->total,
'commission_rate' => $commission_rate,
'creator_earning' => $creator_share,
'created_at' => current_time('mysql')
),
array('%d', '%d', '%d', '%f', '%f', '%f', '%s')
);
}
public static function get_creators_with_balance() {
global $wpdb;
$threshold = floatval(get_option('wpdd_payout_threshold', 0));
$query = "SELECT u.ID, u.display_name, u.user_email,
um1.meta_value as paypal_email,
um2.meta_value as balance
FROM {$wpdb->users} u
INNER JOIN {$wpdb->usermeta} um ON u.ID = um.user_id
LEFT JOIN {$wpdb->usermeta} um1 ON u.ID = um1.user_id AND um1.meta_key = 'wpdd_paypal_email'
LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'wpdd_creator_balance'
WHERE um.meta_key = '{$wpdb->prefix}capabilities'
AND um.meta_value LIKE '%wpdd_creator%'
AND CAST(um2.meta_value AS DECIMAL(10,2)) > 0";
if ($threshold > 0) {
$query .= $wpdb->prepare(" AND CAST(um2.meta_value AS DECIMAL(10,2)) >= %f", $threshold);
}
return $wpdb->get_results($query);
}
}

View File

@@ -0,0 +1,377 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Customer {
public static function init() {
add_action('wp_dashboard_setup', array(__CLASS__, 'add_dashboard_widgets'));
add_filter('login_redirect', array(__CLASS__, 'login_redirect'), 10, 3);
add_action('show_user_profile', array(__CLASS__, 'add_customer_fields'));
add_action('edit_user_profile', array(__CLASS__, 'add_customer_fields'));
// Block wp-admin access for customers
add_action('admin_init', array(__CLASS__, 'restrict_admin_access'));
// Add frontend logout and account management
add_action('wp_footer', array(__CLASS__, 'add_customer_scripts'));
}
public static function add_dashboard_widgets() {
if (current_user_can('wpdd_view_purchases')) {
wp_add_dashboard_widget(
'wpdd_customer_recent_purchases',
__('Recent Purchases', 'wp-digital-download'),
array(__CLASS__, 'recent_purchases_widget')
);
}
if (current_user_can('wpdd_view_own_sales')) {
wp_add_dashboard_widget(
'wpdd_creator_sales_summary',
__('Sales Summary', 'wp-digital-download'),
array(__CLASS__, 'sales_summary_widget')
);
}
}
public static function recent_purchases_widget() {
global $wpdb;
$current_user = wp_get_current_user();
$recent_orders = $wpdb->get_results($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.customer_id = %d
AND o.status = 'completed'
ORDER BY o.purchase_date DESC
LIMIT 5",
$current_user->ID
));
if ($recent_orders) {
echo '<ul>';
foreach ($recent_orders as $order) {
printf(
'<li>%s - <a href="%s">%s</a> ($%s)</li>',
date_i18n(get_option('date_format'), strtotime($order->purchase_date)),
get_permalink($order->product_id),
esc_html($order->product_name),
number_format($order->amount, 2)
);
}
echo '</ul>';
printf(
'<p><a href="%s" class="button">%s</a></p>',
get_permalink(get_option('wpdd_purchases_page_id')),
__('View All Purchases', 'wp-digital-download')
);
} else {
echo '<p>' . __('No purchases yet.', 'wp-digital-download') . '</p>';
printf(
'<p><a href="%s" class="button button-primary">%s</a></p>',
get_permalink(get_option('wpdd_shop_page_id')),
__('Browse Products', 'wp-digital-download')
);
}
}
public static function sales_summary_widget() {
global $wpdb;
$current_user = wp_get_current_user();
$stats = $wpdb->get_row($wpdb->prepare(
"SELECT
COUNT(*) as total_sales,
SUM(amount) as total_revenue,
COUNT(DISTINCT product_id) as products_sold
FROM {$wpdb->prefix}wpdd_orders
WHERE creator_id = %d
AND status = 'completed'
AND purchase_date >= DATE_SUB(NOW(), INTERVAL 30 DAY)",
$current_user->ID
));
$recent_sales = $wpdb->get_results($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.creator_id = %d
AND o.status = 'completed'
ORDER BY o.purchase_date DESC
LIMIT 5",
$current_user->ID
));
?>
<div class="wpdd-sales-summary">
<div class="wpdd-stats-grid">
<div class="wpdd-stat">
<span class="wpdd-stat-value"><?php echo intval($stats->total_sales); ?></span>
<span class="wpdd-stat-label"><?php _e('Sales (30 days)', 'wp-digital-download'); ?></span>
</div>
<div class="wpdd-stat">
<span class="wpdd-stat-value">$<?php echo number_format($stats->total_revenue ?: 0, 2); ?></span>
<span class="wpdd-stat-label"><?php _e('Revenue (30 days)', 'wp-digital-download'); ?></span>
</div>
</div>
<?php if ($recent_sales) : ?>
<h4><?php _e('Recent Sales', 'wp-digital-download'); ?></h4>
<ul>
<?php foreach ($recent_sales as $sale) : ?>
<li>
<?php echo date_i18n(get_option('date_format'), strtotime($sale->purchase_date)); ?> -
<?php echo esc_html($sale->product_name); ?>
($<?php echo number_format($sale->amount, 2); ?>)
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<p>
<a href="<?php echo admin_url('edit.php?post_type=wpdd_product'); ?>" class="button">
<?php _e('Manage Products', 'wp-digital-download'); ?>
</a>
</p>
</div>
<style>
.wpdd-stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 20px;
}
.wpdd-stat {
text-align: center;
padding: 10px;
background: #f0f0f1;
border-radius: 4px;
}
.wpdd-stat-value {
display: block;
font-size: 24px;
font-weight: 600;
color: #2271b1;
}
.wpdd-stat-label {
display: block;
font-size: 12px;
color: #646970;
margin-top: 5px;
}
</style>
<?php
}
public static function login_redirect($redirect_to, $requested_redirect_to, $user) {
if (!is_wp_error($user) && in_array('wpdd_customer', $user->roles)) {
$purchases_page = get_option('wpdd_purchases_page_id');
if ($purchases_page) {
return get_permalink($purchases_page);
}
}
return $redirect_to;
}
public static function add_customer_fields($user) {
if (!in_array('wpdd_customer', $user->roles)) {
return;
}
global $wpdb;
$total_purchases = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_orders
WHERE customer_id = %d AND status = 'completed'",
$user->ID
));
$total_spent = $wpdb->get_var($wpdb->prepare(
"SELECT SUM(amount) FROM {$wpdb->prefix}wpdd_orders
WHERE customer_id = %d AND status = 'completed'",
$user->ID
));
?>
<h3><?php _e('Customer Information', 'wp-digital-download'); ?></h3>
<table class="form-table">
<tr>
<th><?php _e('Total Purchases', 'wp-digital-download'); ?></th>
<td><?php echo intval($total_purchases); ?></td>
</tr>
<tr>
<th><?php _e('Total Spent', 'wp-digital-download'); ?></th>
<td>$<?php echo number_format($total_spent ?: 0, 2); ?></td>
</tr>
</table>
<?php
}
public static function get_customer_purchases($customer_id) {
global $wpdb;
return $wpdb->get_results($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.customer_id = %d
AND o.status = 'completed'
ORDER BY o.purchase_date DESC",
$customer_id
));
}
public static function can_download_product($customer_id, $product_id) {
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",
$customer_id,
$product_id
));
if (!$order) {
return false;
}
$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) {
return false;
}
}
if ($download_limit > 0 && $order->download_count >= $download_limit) {
return false;
}
return true;
}
/**
* Block wp-admin access for customers
*/
public static function restrict_admin_access() {
$current_user = wp_get_current_user();
// Only block for wpdd_customer role, allow creators and admins
if (in_array('wpdd_customer', $current_user->roles) && !current_user_can('manage_options')) {
// Allow AJAX requests
if (defined('DOING_AJAX') && DOING_AJAX) {
return;
}
// Redirect to purchases page
$purchases_page = get_option('wpdd_purchases_page_id');
$redirect_url = $purchases_page ? get_permalink($purchases_page) : home_url();
wp_redirect($redirect_url);
exit;
}
}
/**
* Add frontend customer scripts and functionality
*/
public static function add_customer_scripts() {
if (is_user_logged_in()) {
$current_user = wp_get_current_user();
// Only for customers
if (in_array('wpdd_customer', $current_user->roles)) {
?>
<script>
// Add logout functionality to customer pages
document.addEventListener('DOMContentLoaded', function() {
// Add logout link to customer navigation if it exists
var customerNav = document.querySelector('.wpdd-customer-nav, .wpdd-shop-filters, .wpdd-customer-purchases');
if (customerNav && !document.querySelector('.wpdd-customer-logout')) {
var logoutLink = document.createElement('div');
logoutLink.className = 'wpdd-customer-logout';
logoutLink.style.cssText = 'margin-top: 10px; padding: 10px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;';
logoutLink.innerHTML = '<strong>Welcome, <?php echo esc_js($current_user->display_name); ?>!</strong> | ' +
'<a href="<?php echo wp_logout_url(get_permalink()); ?>" style="color: #dc3545;">Logout</a> | ' +
'<a href="#" onclick="wpdd_show_password_form()" style="color: #007cba;">Change Password</a>';
customerNav.appendChild(logoutLink);
}
});
// Password change functionality
function wpdd_show_password_form() {
var passwordForm = document.getElementById('wpdd-password-form');
if (passwordForm) {
passwordForm.style.display = passwordForm.style.display === 'none' ? 'block' : 'none';
return;
}
var formHtml = '<div id="wpdd-password-form" style="margin-top: 15px; padding: 15px; background: white; border: 2px solid #007cba; border-radius: 4px;">' +
'<h4>Change Password</h4>' +
'<form id="wpdd-change-password" onsubmit="wpdd_change_password(event)">' +
'<p><input type="password" name="current_password" placeholder="Current Password" required style="width: 100%; margin-bottom: 10px; padding: 8px;"></p>' +
'<p><input type="password" name="new_password" placeholder="New Password" required style="width: 100%; margin-bottom: 10px; padding: 8px;"></p>' +
'<p><input type="password" name="confirm_password" placeholder="Confirm New Password" required style="width: 100%; margin-bottom: 10px; padding: 8px;"></p>' +
'<p><button type="submit" style="background: #007cba; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Update Password</button> ' +
'<button type="button" onclick="wpdd_hide_password_form()" style="background: #6c757d; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Cancel</button></p>' +
'</form></div>';
var logoutDiv = document.querySelector('.wpdd-customer-logout');
if (logoutDiv) {
logoutDiv.insertAdjacentHTML('afterend', formHtml);
}
}
function wpdd_hide_password_form() {
var passwordForm = document.getElementById('wpdd-password-form');
if (passwordForm) {
passwordForm.remove();
}
}
function wpdd_change_password(event) {
event.preventDefault();
var form = event.target;
var formData = new FormData(form);
if (formData.get('new_password') !== formData.get('confirm_password')) {
alert('New passwords do not match!');
return;
}
formData.append('action', 'wpdd_change_password');
formData.append('nonce', '<?php echo wp_create_nonce('wpdd_change_password'); ?>');
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Password changed successfully!');
wpdd_hide_password_form();
} else {
alert('Error: ' + (data.data || 'Failed to change password'));
}
})
.catch(error => {
alert('Error: ' + error.message);
});
}
</script>
<?php
}
}
}
}

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);
}
}

View File

@@ -0,0 +1,180 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_File_Protection {
public static function init() {
add_action('template_redirect', array(__CLASS__, 'protect_direct_access'));
add_filter('mod_rewrite_rules', array(__CLASS__, 'add_rewrite_rules'));
add_action('init', array(__CLASS__, 'add_download_endpoint'));
}
public static function protect_direct_access() {
$request_uri = $_SERVER['REQUEST_URI'];
$upload_dir = wp_upload_dir();
$protected_dir = '/' . WPDD_UPLOADS_DIR . '/';
if (strpos($request_uri, $protected_dir) !== false) {
wp_die(__('Direct access to this file is not allowed.', 'wp-digital-download'), 403);
}
}
public static function add_rewrite_rules($rules) {
$upload_dir = wp_upload_dir();
$protected_path = str_replace(ABSPATH, '', $upload_dir['basedir']) . '/' . WPDD_UPLOADS_DIR;
$new_rules = "# WP Digital Download Protection\n";
$new_rules .= "<IfModule mod_rewrite.c>\n";
$new_rules .= "RewriteRule ^" . $protected_path . "/.*$ - [F,L]\n";
$new_rules .= "</IfModule>\n\n";
return $new_rules . $rules;
}
public static function add_download_endpoint() {
add_rewrite_endpoint('wpdd-download', EP_ROOT);
}
public static function move_to_protected_directory($attachment_id) {
$upload_dir = wp_upload_dir();
$protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
if (!file_exists($protected_dir)) {
wp_mkdir_p($protected_dir);
self::create_protection_files($protected_dir);
}
$file_path = get_attached_file($attachment_id);
if (!file_exists($file_path)) {
return false;
}
$filename = basename($file_path);
$unique_filename = wp_unique_filename($protected_dir, $filename);
$new_path = trailingslashit($protected_dir) . $unique_filename;
if (copy($file_path, $new_path)) {
$protected_url = trailingslashit($upload_dir['baseurl']) . WPDD_UPLOADS_DIR . '/' . $unique_filename;
update_post_meta($attachment_id, '_wpdd_protected_file', $new_path);
update_post_meta($attachment_id, '_wpdd_protected_url', $protected_url);
return array(
'path' => $new_path,
'url' => $protected_url
);
}
return false;
}
public static function create_protection_files($directory) {
$htaccess_content = "Options -Indexes\n";
$htaccess_content .= "deny from all\n";
$htaccess_file = trailingslashit($directory) . '.htaccess';
if (!file_exists($htaccess_file)) {
file_put_contents($htaccess_file, $htaccess_content);
}
$index_content = "<?php\n// Silence is golden.\n";
$index_file = trailingslashit($directory) . 'index.php';
if (!file_exists($index_file)) {
file_put_contents($index_file, $index_content);
}
$nginx_content = "location ~* ^/wp-content/uploads/" . WPDD_UPLOADS_DIR . " {\n";
$nginx_content .= " deny all;\n";
$nginx_content .= " return 403;\n";
$nginx_content .= "}\n";
$nginx_file = trailingslashit($directory) . 'nginx.conf';
if (!file_exists($nginx_file)) {
file_put_contents($nginx_file, $nginx_content);
}
}
public static function get_protected_file_url($file_path) {
$upload_dir = wp_upload_dir();
$protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
if (strpos($file_path, $protected_dir) === 0) {
$relative_path = str_replace($protected_dir, '', $file_path);
return trailingslashit($upload_dir['baseurl']) . WPDD_UPLOADS_DIR . $relative_path;
}
return $file_path;
}
public static function is_protected_file($file_path) {
$upload_dir = wp_upload_dir();
$protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
return strpos($file_path, $protected_dir) === 0;
}
public static function generate_secure_download_url($file_path, $order_id, $expiry_hours = 24) {
$token = wp_hash($file_path . $order_id . time());
$expiry = time() + ($expiry_hours * 3600);
set_transient('wpdd_download_' . $token, array(
'file_path' => $file_path,
'order_id' => $order_id,
'expiry' => $expiry
), $expiry_hours * 3600);
return add_query_arg(array(
'wpdd_secure_download' => $token
), home_url());
}
public static function handle_secure_download() {
if (!isset($_GET['wpdd_secure_download'])) {
return;
}
$token = sanitize_text_field($_GET['wpdd_secure_download']);
$data = get_transient('wpdd_download_' . $token);
if (!$data) {
wp_die(__('Invalid or expired download link.', 'wp-digital-download'));
}
if ($data['expiry'] < time()) {
delete_transient('wpdd_download_' . $token);
wp_die(__('This download link has expired.', 'wp-digital-download'));
}
if (!file_exists($data['file_path'])) {
wp_die(__('File not found.', 'wp-digital-download'));
}
delete_transient('wpdd_download_' . $token);
self::serve_download($data['file_path']);
}
private static function serve_download($file_path) {
$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);
exit;
}
}

View File

@@ -0,0 +1,215 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Install {
public static function activate() {
// Load required files if not already loaded
if (!class_exists('WPDD_Post_Types')) {
require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-post-types.php';
}
if (!class_exists('WPDD_Roles')) {
require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-roles.php';
}
// Register post types and taxonomies before flushing rules
WPDD_Post_Types::register_post_types();
WPDD_Post_Types::register_taxonomies();
self::create_tables();
self::create_pages();
self::create_upload_protection();
WPDD_Roles::create_roles();
// Flush rewrite rules after post types are registered
flush_rewrite_rules();
}
public static function deactivate() {
flush_rewrite_rules();
}
private static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$sql = array();
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_orders (
id bigint(20) NOT NULL AUTO_INCREMENT,
order_number varchar(50) NOT NULL,
product_id bigint(20) NOT NULL,
customer_id bigint(20) NOT NULL,
creator_id bigint(20) NOT NULL,
status varchar(20) NOT NULL DEFAULT 'pending',
payment_method varchar(50) DEFAULT NULL,
transaction_id varchar(100) DEFAULT NULL,
amount decimal(10,2) NOT NULL,
currency varchar(10) NOT NULL DEFAULT 'USD',
customer_email varchar(100) NOT NULL,
customer_name varchar(100) DEFAULT NULL,
purchase_date datetime DEFAULT CURRENT_TIMESTAMP,
download_count int(11) DEFAULT 0,
notes text DEFAULT NULL,
PRIMARY KEY (id),
KEY order_number (order_number),
KEY product_id (product_id),
KEY customer_id (customer_id),
KEY creator_id (creator_id),
KEY status (status)
) $charset_collate;";
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_downloads (
id bigint(20) NOT NULL AUTO_INCREMENT,
order_id bigint(20) NOT NULL,
product_id bigint(20) NOT NULL,
customer_id bigint(20) NOT NULL,
file_id varchar(100) NOT NULL,
download_date datetime DEFAULT CURRENT_TIMESTAMP,
ip_address varchar(45) DEFAULT NULL,
user_agent text DEFAULT NULL,
PRIMARY KEY (id),
KEY order_id (order_id),
KEY product_id (product_id),
KEY customer_id (customer_id)
) $charset_collate;";
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_download_links (
id bigint(20) NOT NULL AUTO_INCREMENT,
order_id bigint(20) NOT NULL,
token varchar(64) NOT NULL,
expires_at datetime NOT NULL,
download_count int(11) DEFAULT 0,
max_downloads int(11) DEFAULT 5,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY order_id (order_id)
) $charset_collate;";
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_creator_earnings (
id bigint(20) NOT NULL AUTO_INCREMENT,
creator_id bigint(20) NOT NULL,
order_id bigint(20) NOT NULL,
product_id bigint(20) NOT NULL,
sale_amount decimal(10,2) NOT NULL,
commission_rate decimal(5,2) NOT NULL,
creator_earning decimal(10,2) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY creator_id (creator_id),
KEY order_id (order_id),
KEY product_id (product_id)
) $charset_collate;";
$sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_payouts (
id bigint(20) NOT NULL AUTO_INCREMENT,
creator_id bigint(20) NOT NULL,
amount decimal(10,2) NOT NULL,
currency varchar(10) NOT NULL,
paypal_email varchar(100) NOT NULL,
transaction_id varchar(100) DEFAULT NULL,
status varchar(20) NOT NULL DEFAULT 'pending',
payout_method varchar(20) NOT NULL DEFAULT 'manual',
notes text DEFAULT NULL,
processed_by bigint(20) DEFAULT NULL,
processed_at datetime DEFAULT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY creator_id (creator_id),
KEY status (status),
KEY transaction_id (transaction_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
foreach ($sql as $query) {
dbDelta($query);
}
update_option('wpdd_db_version', WPDD_VERSION);
}
private static function create_pages() {
$pages = array(
'shop' => array(
'title' => __('Shop', 'wp-digital-download'),
'content' => '[wpdd_shop]',
'option' => 'wpdd_shop_page_id'
),
'my-purchases' => array(
'title' => __('My Purchases', 'wp-digital-download'),
'content' => '[wpdd_customer_purchases]',
'option' => 'wpdd_purchases_page_id'
),
'checkout' => array(
'title' => __('Checkout', 'wp-digital-download'),
'content' => '[wpdd_checkout]',
'option' => 'wpdd_checkout_page_id'
),
'thank-you' => array(
'title' => __('Thank You', 'wp-digital-download'),
'content' => '[wpdd_thank_you]',
'option' => 'wpdd_thank_you_page_id'
)
);
foreach ($pages as $slug => $page) {
// Check if page already exists
$existing_page_id = get_option($page['option']);
if ($existing_page_id && get_post($existing_page_id)) {
continue; // Page already exists, skip creation
}
// Check if a page with this slug already exists
$existing_page = get_page_by_path($slug);
if ($existing_page) {
update_option($page['option'], $existing_page->ID);
continue;
}
// Create the page
$page_id = wp_insert_post(array(
'post_title' => $page['title'],
'post_content' => $page['content'],
'post_status' => 'publish',
'post_type' => 'page',
'post_name' => $slug
));
if ($page_id && !is_wp_error($page_id)) {
update_option($page['option'], $page_id);
}
}
}
private static function create_upload_protection() {
$upload_dir = wp_upload_dir();
$protection_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
if (!file_exists($protection_dir)) {
wp_mkdir_p($protection_dir);
}
$htaccess_content = "Options -Indexes\n";
$htaccess_content .= "deny from all\n";
$htaccess_file = trailingslashit($protection_dir) . '.htaccess';
if (!file_exists($htaccess_file)) {
file_put_contents($htaccess_file, $htaccess_content);
}
$index_content = "<?php\n// Silence is golden.\n";
$index_file = trailingslashit($protection_dir) . 'index.php';
if (!file_exists($index_file)) {
file_put_contents($index_file, $index_content);
}
}
}

View File

@@ -0,0 +1,308 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Metaboxes {
public static function init() {
add_action('add_meta_boxes', array(__CLASS__, 'add_meta_boxes'));
add_action('save_post_wpdd_product', array(__CLASS__, 'save_product_meta'), 10, 2);
}
public static function add_meta_boxes() {
add_meta_box(
'wpdd_product_pricing',
__('Product Pricing', 'wp-digital-download'),
array(__CLASS__, 'render_pricing_metabox'),
'wpdd_product',
'side',
'high'
);
add_meta_box(
'wpdd_product_files',
__('Downloadable Files', 'wp-digital-download'),
array(__CLASS__, 'render_files_metabox'),
'wpdd_product',
'normal',
'high'
);
add_meta_box(
'wpdd_product_settings',
__('Product Settings', 'wp-digital-download'),
array(__CLASS__, 'render_settings_metabox'),
'wpdd_product',
'normal',
'default'
);
add_meta_box(
'wpdd_product_stats',
__('Product Statistics', 'wp-digital-download'),
array(__CLASS__, 'render_stats_metabox'),
'wpdd_product',
'side',
'low'
);
}
public static function render_pricing_metabox($post) {
wp_nonce_field('wpdd_save_product_meta', 'wpdd_product_meta_nonce');
$price = get_post_meta($post->ID, '_wpdd_price', true);
$is_free = get_post_meta($post->ID, '_wpdd_is_free', true);
$sale_price = get_post_meta($post->ID, '_wpdd_sale_price', true);
?>
<div class="wpdd-metabox-content">
<p>
<label>
<input type="checkbox" name="wpdd_is_free" id="wpdd_is_free" value="1" <?php checked($is_free, '1'); ?> />
<?php _e('This is a free product', 'wp-digital-download'); ?>
</label>
</p>
<p class="wpdd-price-field">
<label for="wpdd_price"><?php _e('Regular Price', 'wp-digital-download'); ?> ($)</label>
<input type="number" id="wpdd_price" name="wpdd_price" value="<?php echo esc_attr($price); ?>" step="0.01" min="0" />
</p>
<p class="wpdd-price-field">
<label for="wpdd_sale_price"><?php _e('Sale Price', 'wp-digital-download'); ?> ($)</label>
<input type="number" id="wpdd_sale_price" name="wpdd_sale_price" value="<?php echo esc_attr($sale_price); ?>" step="0.01" min="0" />
<span class="description"><?php _e('Leave blank for no sale', 'wp-digital-download'); ?></span>
</p>
</div>
<?php
}
public static function render_files_metabox($post) {
$files = get_post_meta($post->ID, '_wpdd_files', true);
if (!is_array($files)) {
$files = array();
}
?>
<div class="wpdd-files-container">
<div id="wpdd-files-list">
<?php if (!empty($files)) : ?>
<?php foreach ($files as $index => $file) : ?>
<div class="wpdd-file-item" data-index="<?php echo $index; ?>">
<div class="wpdd-file-header">
<span class="wpdd-file-handle dashicons dashicons-menu"></span>
<input type="text" name="wpdd_files[<?php echo $index; ?>][name]"
value="<?php echo esc_attr($file['name']); ?>"
placeholder="<?php _e('File Name', 'wp-digital-download'); ?>" />
<button type="button" class="button wpdd-remove-file">
<?php _e('Remove', 'wp-digital-download'); ?>
</button>
</div>
<div class="wpdd-file-content">
<div class="wpdd-file-url">
<input type="text" name="wpdd_files[<?php echo $index; ?>][url]"
value="<?php echo esc_url($file['url']); ?>"
placeholder="<?php _e('File URL', 'wp-digital-download'); ?>"
class="wpdd-file-url-input" />
<button type="button" class="button wpdd-upload-file">
<?php _e('Upload File', 'wp-digital-download'); ?>
</button>
</div>
<input type="hidden" name="wpdd_files[<?php echo $index; ?>][id]"
value="<?php echo esc_attr($file['id']); ?>"
class="wpdd-file-id" />
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<button type="button" id="wpdd-add-file" class="button button-primary">
<?php _e('Add File', 'wp-digital-download'); ?>
</button>
<template id="wpdd-file-template">
<div class="wpdd-file-item" data-index="">
<div class="wpdd-file-header">
<span class="wpdd-file-handle dashicons dashicons-menu"></span>
<input type="text" name="wpdd_files[INDEX][name]"
placeholder="<?php _e('File Name', 'wp-digital-download'); ?>" />
<button type="button" class="button wpdd-remove-file">
<?php _e('Remove', 'wp-digital-download'); ?>
</button>
</div>
<div class="wpdd-file-content">
<div class="wpdd-file-url">
<input type="text" name="wpdd_files[INDEX][url]"
placeholder="<?php _e('File URL', 'wp-digital-download'); ?>"
class="wpdd-file-url-input" />
<button type="button" class="button wpdd-upload-file">
<?php _e('Upload File', 'wp-digital-download'); ?>
</button>
</div>
<input type="hidden" name="wpdd_files[INDEX][id]" class="wpdd-file-id" />
</div>
</div>
</template>
</div>
<?php
}
public static function render_settings_metabox($post) {
$download_limit = get_post_meta($post->ID, '_wpdd_download_limit', true);
$download_expiry = get_post_meta($post->ID, '_wpdd_download_expiry', true);
$enable_watermark = get_post_meta($post->ID, '_wpdd_enable_watermark', true);
$watermark_text = get_post_meta($post->ID, '_wpdd_watermark_text', true);
?>
<div class="wpdd-settings-grid">
<div class="wpdd-setting-group">
<h4><?php _e('Download Settings', 'wp-digital-download'); ?></h4>
<p>
<label for="wpdd_download_limit">
<?php _e('Download Limit', 'wp-digital-download'); ?>
</label>
<input type="number" id="wpdd_download_limit" name="wpdd_download_limit"
value="<?php echo esc_attr($download_limit ?: 5); ?>" min="0" />
<span class="description">
<?php _e('Number of times a customer can download after purchase. 0 = unlimited', 'wp-digital-download'); ?>
</span>
</p>
<p>
<label for="wpdd_download_expiry">
<?php _e('Download Expiry (days)', 'wp-digital-download'); ?>
</label>
<input type="number" id="wpdd_download_expiry" name="wpdd_download_expiry"
value="<?php echo esc_attr($download_expiry ?: 30); ?>" min="0" />
<span class="description">
<?php _e('Number of days download link remains valid. 0 = never expires', 'wp-digital-download'); ?>
</span>
</p>
</div>
<div class="wpdd-setting-group">
<h4><?php _e('Watermark Settings', 'wp-digital-download'); ?></h4>
<p>
<label>
<input type="checkbox" name="wpdd_enable_watermark" value="1"
<?php checked($enable_watermark, '1'); ?> />
<?php _e('Enable watermarking for images and PDFs', 'wp-digital-download'); ?>
</label>
</p>
<p>
<label for="wpdd_watermark_text">
<?php _e('Watermark Text', 'wp-digital-download'); ?>
</label>
<input type="text" id="wpdd_watermark_text" name="wpdd_watermark_text"
value="<?php echo esc_attr($watermark_text); ?>"
placeholder="<?php _e('e.g., {customer_email} - {order_id}', 'wp-digital-download'); ?>" />
<span class="description">
<?php _e('Available placeholders: {customer_name}, {customer_email}, {order_id}, {date}', 'wp-digital-download'); ?>
</span>
</p>
</div>
</div>
<?php
}
public static function render_stats_metabox($post) {
global $wpdb;
$total_sales = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_orders
WHERE product_id = %d AND status = 'completed'",
$post->ID
));
$total_revenue = $wpdb->get_var($wpdb->prepare(
"SELECT SUM(amount) FROM {$wpdb->prefix}wpdd_orders
WHERE product_id = %d AND status = 'completed'",
$post->ID
));
$total_downloads = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_downloads
WHERE product_id = %d",
$post->ID
));
?>
<div class="wpdd-stats">
<p>
<strong><?php _e('Total Sales:', 'wp-digital-download'); ?></strong>
<?php echo intval($total_sales); ?>
</p>
<p>
<strong><?php _e('Total Revenue:', 'wp-digital-download'); ?></strong>
$<?php echo number_format(floatval($total_revenue), 2); ?>
</p>
<p>
<strong><?php _e('Total Downloads:', 'wp-digital-download'); ?></strong>
<?php echo intval($total_downloads); ?>
</p>
</div>
<?php
}
public static function save_product_meta($post_id, $post) {
if (!isset($_POST['wpdd_product_meta_nonce']) ||
!wp_verify_nonce($_POST['wpdd_product_meta_nonce'], 'wpdd_save_product_meta')) {
return;
}
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
update_post_meta($post_id, '_wpdd_is_free',
isset($_POST['wpdd_is_free']) ? '1' : '0');
if (isset($_POST['wpdd_price'])) {
update_post_meta($post_id, '_wpdd_price',
floatval($_POST['wpdd_price']));
}
if (isset($_POST['wpdd_sale_price'])) {
update_post_meta($post_id, '_wpdd_sale_price',
floatval($_POST['wpdd_sale_price']));
}
if (isset($_POST['wpdd_files']) && is_array($_POST['wpdd_files'])) {
$files = array();
foreach ($_POST['wpdd_files'] as $file) {
if (!empty($file['url'])) {
$files[] = array(
'id' => sanitize_text_field($file['id']),
'name' => sanitize_text_field($file['name']),
'url' => esc_url_raw($file['url'])
);
}
}
update_post_meta($post_id, '_wpdd_files', $files);
}
if (isset($_POST['wpdd_download_limit'])) {
update_post_meta($post_id, '_wpdd_download_limit',
intval($_POST['wpdd_download_limit']));
}
if (isset($_POST['wpdd_download_expiry'])) {
update_post_meta($post_id, '_wpdd_download_expiry',
intval($_POST['wpdd_download_expiry']));
}
update_post_meta($post_id, '_wpdd_enable_watermark',
isset($_POST['wpdd_enable_watermark']) ? '1' : '0');
if (isset($_POST['wpdd_watermark_text'])) {
update_post_meta($post_id, '_wpdd_watermark_text',
sanitize_text_field($_POST['wpdd_watermark_text']));
}
}
}

View File

@@ -0,0 +1,318 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Orders {
public static function create_order($product_id, $customer_data, $payment_method = 'free') {
global $wpdb;
$product = get_post($product_id);
if (!$product || $product->post_type !== 'wpdd_product') {
return false;
}
$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);
$amount = $is_free ? 0 : (($sale_price && $sale_price < $price) ? $sale_price : $price);
$order_number = 'WPDD-' . strtoupper(uniqid());
$customer_id = 0;
if (is_user_logged_in()) {
$current_user = wp_get_current_user();
$customer_id = $current_user->ID;
$customer_email = $current_user->user_email;
$customer_name = $current_user->display_name;
} else {
$customer_email = $customer_data['email'];
$customer_name = $customer_data['name'];
}
$result = $wpdb->insert(
$wpdb->prefix . 'wpdd_orders',
array(
'order_number' => $order_number,
'product_id' => $product_id,
'customer_id' => $customer_id,
'creator_id' => $product->post_author,
'status' => ($payment_method === 'free' || $amount == 0) ? 'completed' : 'pending',
'payment_method' => $payment_method,
'amount' => $amount,
'currency' => 'USD',
'customer_email' => $customer_email,
'customer_name' => $customer_name,
'purchase_date' => current_time('mysql')
),
array('%s', '%d', '%d', '%d', '%s', '%s', '%f', '%s', '%s', '%s', '%s')
);
if ($result) {
$order_id = $wpdb->insert_id;
if ($payment_method === 'free' || $amount == 0) {
self::complete_order($order_id);
}
return $order_id;
}
return false;
}
public static function complete_order($order_id, $transaction_id = null) {
global $wpdb;
$order = self::get_order($order_id);
if (!$order) {
return false;
}
$update_data = array(
'status' => 'completed'
);
if ($transaction_id) {
$update_data['transaction_id'] = $transaction_id;
}
$result = $wpdb->update(
$wpdb->prefix . 'wpdd_orders',
$update_data,
array('id' => $order_id),
array('%s', '%s'),
array('%d')
);
if ($result) {
self::generate_download_link($order_id);
self::send_order_emails($order_id);
update_post_meta(
$order->product_id,
'_wpdd_sales_count',
intval(get_post_meta($order->product_id, '_wpdd_sales_count', true)) + 1
);
do_action('wpdd_order_completed', $order_id);
return true;
}
return false;
}
public static function get_order($order_id) {
global $wpdb;
if (is_numeric($order_id)) {
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
$order_id
));
} else {
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE order_number = %s",
$order_id
));
}
}
public static function get_orders($args = array()) {
global $wpdb;
$defaults = array(
'status' => '',
'customer_id' => 0,
'creator_id' => 0,
'product_id' => 0,
'limit' => 20,
'offset' => 0,
'orderby' => 'purchase_date',
'order' => 'DESC'
);
$args = wp_parse_args($args, $defaults);
$where = array('1=1');
if ($args['status']) {
$where[] = $wpdb->prepare("status = %s", $args['status']);
}
if ($args['customer_id']) {
$where[] = $wpdb->prepare("customer_id = %d", $args['customer_id']);
}
if ($args['creator_id']) {
$where[] = $wpdb->prepare("creator_id = %d", $args['creator_id']);
}
if ($args['product_id']) {
$where[] = $wpdb->prepare("product_id = %d", $args['product_id']);
}
$where_clause = implode(' AND ', $where);
$query = $wpdb->prepare(
"SELECT o.*, p.post_title as product_name,
u.display_name as customer_display_name,
c.display_name as creator_display_name
FROM {$wpdb->prefix}wpdd_orders o
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
LEFT JOIN {$wpdb->users} u ON o.customer_id = u.ID
LEFT JOIN {$wpdb->users} c ON o.creator_id = c.ID
WHERE {$where_clause}
ORDER BY {$args['orderby']} {$args['order']}
LIMIT %d OFFSET %d",
$args['limit'],
$args['offset']
);
return $wpdb->get_results($query);
}
private static function generate_download_link($order_id) {
global $wpdb;
$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 send_order_emails($order_id) {
$order = self::get_order($order_id);
if (!$order) {
return;
}
self::send_customer_email($order);
self::send_creator_email($order);
self::send_admin_email($order);
}
private static function send_customer_email($order) {
global $wpdb;
$product = get_post($order->product_id);
$download_link = $wpdb->get_var($wpdb->prepare(
"SELECT token FROM {$wpdb->prefix}wpdd_download_links
WHERE order_id = %d ORDER BY id DESC LIMIT 1",
$order->id
));
$download_url = add_query_arg(array(
'wpdd_download_token' => $download_link
), home_url());
$subject = sprintf(
__('Your purchase of %s from %s', 'wp-digital-download'),
$product->post_title,
get_bloginfo('name')
);
$message = sprintf(
__("Hi %s,\n\nThank you for your purchase!\n\n", 'wp-digital-download'),
$order->customer_name
);
$message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
$message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $product->post_title);
if ($order->amount > 0) {
$message .= sprintf(__("Amount: $%s\n", 'wp-digital-download'), number_format($order->amount, 2));
}
$message .= "\n" . __("Download your product here:\n", 'wp-digital-download');
$message .= $download_url . "\n\n";
$message .= __("This download link will expire in 7 days.\n\n", 'wp-digital-download');
if ($order->customer_id) {
$purchases_url = get_permalink(get_option('wpdd_purchases_page_id'));
$message .= sprintf(
__("You can also access your downloads anytime from your account:\n%s\n\n", 'wp-digital-download'),
$purchases_url
);
}
$message .= sprintf(__("Best regards,\n%s", 'wp-digital-download'), get_bloginfo('name'));
wp_mail($order->customer_email, $subject, $message);
}
private static function send_creator_email($order) {
$creator = get_userdata($order->creator_id);
if (!$creator) {
return;
}
$product = get_post($order->product_id);
$subject = sprintf(
__('New sale: %s', 'wp-digital-download'),
$product->post_title
);
$message = sprintf(
__("Hi %s,\n\nYou have a new sale!\n\n", 'wp-digital-download'),
$creator->display_name
);
$message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $product->post_title);
$message .= sprintf(__("Customer: %s\n", 'wp-digital-download'), $order->customer_name);
$message .= sprintf(__("Amount: $%s\n", 'wp-digital-download'), number_format($order->amount, 2));
$message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
$message .= "\n" . sprintf(
__("View your sales dashboard:\n%s\n", 'wp-digital-download'),
admin_url()
);
wp_mail($creator->user_email, $subject, $message);
}
private static function send_admin_email($order) {
$admin_email = get_option('wpdd_admin_email', get_option('admin_email'));
if (!$admin_email) {
return;
}
$product = get_post($order->product_id);
$subject = sprintf(
__('[%s] New Digital Download Sale', 'wp-digital-download'),
get_bloginfo('name')
);
$message = __("A new digital download sale has been completed.\n\n", 'wp-digital-download');
$message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
$message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $product->post_title);
$message .= sprintf(__("Customer: %s (%s)\n", 'wp-digital-download'), $order->customer_name, $order->customer_email);
$message .= sprintf(__("Amount: $%s\n", 'wp-digital-download'), number_format($order->amount, 2));
$message .= sprintf(__("Payment Method: %s\n", 'wp-digital-download'), $order->payment_method);
if ($order->transaction_id) {
$message .= sprintf(__("Transaction ID: %s\n", 'wp-digital-download'), $order->transaction_id);
}
wp_mail($admin_email, $subject, $message);
}
}

View File

@@ -0,0 +1,231 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_PayPal_Payouts {
public static function init() {
// Schedule cron event for automatic payouts
if (!wp_next_scheduled('wpdd_process_automatic_payouts')) {
wp_schedule_event(time(), 'daily', 'wpdd_process_automatic_payouts');
}
add_action('wpdd_process_automatic_payouts', array(__CLASS__, 'process_automatic_payouts'));
}
public static function process_automatic_payouts() {
$threshold = floatval(get_option('wpdd_payout_threshold', 0));
if ($threshold <= 0) {
return;
}
$creators = WPDD_Creator::get_creators_with_balance();
foreach ($creators as $creator) {
if (floatval($creator->balance) >= $threshold && !empty($creator->paypal_email)) {
WPDD_Admin_Payouts::create_payout($creator->ID, 'automatic');
}
}
}
public static function process_payout($payout_id) {
global $wpdb;
// Get payout details
$payout = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_payouts WHERE id = %d",
$payout_id
));
if (!$payout) {
return array('success' => false, 'error' => 'Payout not found');
}
// Get PayPal credentials
$mode = get_option('wpdd_paypal_mode', 'sandbox');
$client_id = get_option('wpdd_paypal_client_id');
$secret = get_option('wpdd_paypal_secret');
if (empty($client_id) || empty($secret)) {
return array('success' => false, 'error' => 'PayPal credentials not configured');
}
// Get access token
$token_result = self::get_access_token($client_id, $secret, $mode);
if (!$token_result['success']) {
return array('success' => false, 'error' => $token_result['error']);
}
$access_token = $token_result['token'];
// Create payout batch
$batch_result = self::create_payout_batch($payout, $access_token, $mode);
if ($batch_result['success']) {
return array(
'success' => true,
'transaction_id' => $batch_result['batch_id']
);
} else {
return array(
'success' => false,
'error' => $batch_result['error']
);
}
}
private static function get_access_token($client_id, $secret, $mode) {
$base_url = $mode === 'sandbox'
? 'https://api-m.sandbox.paypal.com'
: 'https://api-m.paypal.com';
$response = wp_remote_post(
$base_url . '/v1/oauth2/token',
array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode($client_id . ':' . $secret),
'Content-Type' => 'application/x-www-form-urlencoded'
),
'body' => 'grant_type=client_credentials',
'timeout' => 30
)
);
if (is_wp_error($response)) {
return array('success' => false, 'error' => $response->get_error_message());
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (isset($body['access_token'])) {
return array('success' => true, 'token' => $body['access_token']);
} else {
$error = isset($body['error_description']) ? $body['error_description'] : 'Failed to get access token';
return array('success' => false, 'error' => $error);
}
}
private static function create_payout_batch($payout, $access_token, $mode) {
$base_url = $mode === 'sandbox'
? 'https://api-m.sandbox.paypal.com'
: 'https://api-m.paypal.com';
$batch_id = 'WPDD_' . $payout->id . '_' . time();
$payout_data = array(
'sender_batch_header' => array(
'sender_batch_id' => $batch_id,
'email_subject' => 'You have received a payout!',
'email_message' => 'You have received a payout from ' . get_bloginfo('name')
),
'items' => array(
array(
'recipient_type' => 'EMAIL',
'amount' => array(
'value' => number_format($payout->amount, 2, '.', ''),
'currency' => $payout->currency
),
'receiver' => $payout->paypal_email,
'note' => 'Payout for your sales on ' . get_bloginfo('name'),
'sender_item_id' => 'payout_' . $payout->id
)
)
);
$response = wp_remote_post(
$base_url . '/v1/payments/payouts',
array(
'headers' => array(
'Authorization' => 'Bearer ' . $access_token,
'Content-Type' => 'application/json'
),
'body' => json_encode($payout_data),
'timeout' => 30
)
);
if (is_wp_error($response)) {
return array('success' => false, 'error' => $response->get_error_message());
}
$response_code = wp_remote_retrieve_response_code($response);
$body = json_decode(wp_remote_retrieve_body($response), true);
if ($response_code === 201 && isset($body['batch_header']['payout_batch_id'])) {
return array(
'success' => true,
'batch_id' => $body['batch_header']['payout_batch_id']
);
} else {
$error = 'Failed to create payout batch';
if (isset($body['message'])) {
$error = $body['message'];
} elseif (isset($body['error_description'])) {
$error = $body['error_description'];
}
return array('success' => false, 'error' => $error);
}
}
public static function check_batch_status($batch_id, $mode = null) {
if (!$mode) {
$mode = get_option('wpdd_paypal_mode', 'sandbox');
}
$client_id = get_option('wpdd_paypal_client_id');
$secret = get_option('wpdd_paypal_secret');
if (empty($client_id) || empty($secret)) {
return array('success' => false, 'error' => 'PayPal credentials not configured');
}
// Get access token
$token_result = self::get_access_token($client_id, $secret, $mode);
if (!$token_result['success']) {
return array('success' => false, 'error' => $token_result['error']);
}
$access_token = $token_result['token'];
$base_url = $mode === 'sandbox'
? 'https://api-m.sandbox.paypal.com'
: 'https://api-m.paypal.com';
$response = wp_remote_get(
$base_url . '/v1/payments/payouts/' . $batch_id,
array(
'headers' => array(
'Authorization' => 'Bearer ' . $access_token,
'Content-Type' => 'application/json'
),
'timeout' => 30
)
);
if (is_wp_error($response)) {
return array('success' => false, 'error' => $response->get_error_message());
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (isset($body['batch_header'])) {
return array(
'success' => true,
'status' => $body['batch_header']['batch_status'],
'data' => $body
);
} else {
return array('success' => false, 'error' => 'Failed to get batch status');
}
}
public static function deactivate() {
wp_clear_scheduled_hook('wpdd_process_automatic_payouts');
}
}

View File

@@ -0,0 +1,302 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_PayPal {
public static function init() {
add_action('wp_enqueue_scripts', array(__CLASS__, 'enqueue_paypal_sdk'));
add_action('wp_ajax_wpdd_create_paypal_order', array(__CLASS__, 'create_order'));
add_action('wp_ajax_nopriv_wpdd_create_paypal_order', array(__CLASS__, 'create_order'));
add_action('wp_ajax_wpdd_capture_paypal_order', array(__CLASS__, 'capture_order'));
add_action('wp_ajax_nopriv_wpdd_capture_paypal_order', array(__CLASS__, 'capture_order'));
}
public static function enqueue_paypal_sdk() {
if (!is_page(get_option('wpdd_checkout_page_id'))) {
return;
}
$client_id = get_option('wpdd_paypal_client_id');
$mode = get_option('wpdd_paypal_mode', 'sandbox');
if (!$client_id) {
return;
}
wp_enqueue_script(
'paypal-sdk',
'https://www.paypal.com/sdk/js?client-id=' . $client_id . '&currency=USD',
array(),
null,
true
);
wp_enqueue_script(
'wpdd-paypal',
WPDD_PLUGIN_URL . 'assets/js/paypal.js',
array('jquery', 'paypal-sdk'),
WPDD_VERSION,
true
);
wp_localize_script('wpdd-paypal', 'wpdd_paypal', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wpdd-paypal-nonce'),
'mode' => $mode
));
}
public static function create_order() {
check_ajax_referer('wpdd-paypal-nonce', 'nonce');
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
if (!$product_id) {
wp_send_json_error('Invalid product');
}
$product = get_post($product_id);
if (!$product || $product->post_type !== 'wpdd_product') {
wp_send_json_error('Product not found');
}
$price = get_post_meta($product_id, '_wpdd_price', true);
$sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
$final_price = ($sale_price && $sale_price < $price) ? $sale_price : $price;
$order_data = array(
'intent' => 'CAPTURE',
'purchase_units' => array(
array(
'reference_id' => 'wpdd_' . $product_id . '_' . time(),
'description' => substr($product->post_title, 0, 127),
'amount' => array(
'currency_code' => 'USD',
'value' => number_format($final_price, 2, '.', '')
)
)
),
'application_context' => array(
'brand_name' => get_bloginfo('name'),
'return_url' => add_query_arg('wpdd_paypal_return', '1', get_permalink(get_option('wpdd_thank_you_page_id'))),
'cancel_url' => add_query_arg('wpdd_paypal_cancel', '1', get_permalink(get_option('wpdd_checkout_page_id')))
)
);
$paypal_order = self::api_request('/v2/checkout/orders', $order_data, 'POST');
if (isset($paypal_order['id'])) {
$_SESSION['wpdd_paypal_order_' . $paypal_order['id']] = array(
'product_id' => $product_id,
'amount' => $final_price,
'customer_email' => sanitize_email($_POST['customer_email'] ?? ''),
'customer_name' => sanitize_text_field($_POST['customer_name'] ?? '')
);
wp_send_json_success(array('orderID' => $paypal_order['id']));
} else {
wp_send_json_error('Failed to create PayPal order');
}
}
public static function capture_order() {
check_ajax_referer('wpdd-paypal-nonce', 'nonce');
$paypal_order_id = isset($_POST['orderID']) ? sanitize_text_field($_POST['orderID']) : '';
if (!$paypal_order_id) {
wp_send_json_error('Invalid order ID');
}
$capture_response = self::api_request('/v2/checkout/orders/' . $paypal_order_id . '/capture', array(), 'POST');
if (isset($capture_response['status']) && $capture_response['status'] === 'COMPLETED') {
$session_data = $_SESSION['wpdd_paypal_order_' . $paypal_order_id] ?? array();
// Add error logging for debugging session issues
if (empty($session_data)) {
error_log('WPDD PayPal Error: No session data found for PayPal order ' . $paypal_order_id);
error_log('WPDD PayPal Debug: Session ID: ' . session_id());
error_log('WPDD PayPal Debug: Available sessions: ' . print_r($_SESSION ?? array(), true));
wp_send_json_error('Session data not found - order cannot be processed');
return;
}
$order_number = 'WPDD-' . strtoupper(uniqid());
global $wpdb;
$customer_id = 0;
$customer_email = $session_data['customer_email'];
$customer_name = $session_data['customer_name'];
if (is_user_logged_in()) {
$current_user = wp_get_current_user();
$customer_id = $current_user->ID;
$customer_email = $current_user->user_email;
$customer_name = $current_user->display_name;
} elseif (!empty($_POST['create_account']) && !empty($customer_email)) {
$username = strstr($customer_email, '@', true) . '_' . wp_rand(1000, 9999);
$password = wp_generate_password();
$user_id = wp_create_user($username, $password, $customer_email);
if (!is_wp_error($user_id)) {
$customer_id = $user_id;
wp_update_user(array(
'ID' => $user_id,
'display_name' => $customer_name,
'first_name' => $customer_name
));
$user = new WP_User($user_id);
$user->set_role('wpdd_customer');
wp_new_user_notification($user_id, null, 'both');
}
}
$product = get_post($session_data['product_id']);
$wpdb->insert(
$wpdb->prefix . 'wpdd_orders',
array(
'order_number' => $order_number,
'product_id' => $session_data['product_id'],
'customer_id' => $customer_id,
'creator_id' => $product->post_author,
'status' => 'completed',
'payment_method' => 'paypal',
'transaction_id' => $capture_response['id'],
'amount' => $session_data['amount'],
'currency' => 'USD',
'customer_email' => $customer_email,
'customer_name' => $customer_name,
'purchase_date' => current_time('mysql')
),
array('%s', '%d', '%d', '%d', '%s', '%s', '%s', '%f', '%s', '%s', '%s', '%s')
);
$order_id = $wpdb->insert_id;
self::generate_download_link($order_id);
self::send_purchase_email($order_id);
update_post_meta($session_data['product_id'], '_wpdd_sales_count',
intval(get_post_meta($session_data['product_id'], '_wpdd_sales_count', true)) + 1);
unset($_SESSION['wpdd_paypal_order_' . $paypal_order_id]);
wp_send_json_success(array(
'redirect_url' => add_query_arg(
'order_id',
$order_number,
get_permalink(get_option('wpdd_thank_you_page_id'))
)
));
} else {
wp_send_json_error('Payment capture failed');
}
}
private static function api_request($endpoint, $data = array(), $method = 'GET') {
$mode = get_option('wpdd_paypal_mode', 'sandbox');
$client_id = get_option('wpdd_paypal_client_id');
$secret = get_option('wpdd_paypal_secret');
if (!$client_id || !$secret) {
return false;
}
$base_url = $mode === 'live'
? 'https://api.paypal.com'
: 'https://api.sandbox.paypal.com';
$auth = base64_encode($client_id . ':' . $secret);
$args = array(
'method' => $method,
'headers' => array(
'Authorization' => 'Basic ' . $auth,
'Content-Type' => 'application/json'
),
'timeout' => 30
);
if (!empty($data)) {
$args['body'] = json_encode($data);
}
$response = wp_remote_request($base_url . $endpoint, $args);
if (is_wp_error($response)) {
return false;
}
$body = wp_remote_retrieve_body($response);
return json_decode($body, true);
}
private static function generate_download_link($order_id) {
global $wpdb;
$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 send_purchase_email($order_id) {
global $wpdb;
$order = $wpdb->get_row($wpdb->prepare(
"SELECT o.*, p.post_title as product_name, dl.token
FROM {$wpdb->prefix}wpdd_orders o
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
LEFT JOIN {$wpdb->prefix}wpdd_download_links dl ON o.id = dl.order_id
WHERE o.id = %d",
$order_id
));
if (!$order) {
return;
}
$download_url = add_query_arg(array(
'wpdd_download_token' => $order->token
), home_url());
$subject = sprintf(__('Your Purchase from %s', 'wp-digital-download'), get_bloginfo('name'));
$message = sprintf(
__("Hi %s,\n\nThank you for your purchase!\n\n", 'wp-digital-download'),
$order->customer_name
);
$message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
$message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $order->product_name);
$message .= sprintf(__("Amount: $%s\n\n", 'wp-digital-download'), number_format($order->amount, 2));
$message .= __("Download your product here:\n", 'wp-digital-download');
$message .= $download_url . "\n\n";
$message .= __("This download link will expire in 7 days.\n\n", 'wp-digital-download');
$message .= sprintf(__("Best regards,\n%s", 'wp-digital-download'), get_bloginfo('name'));
wp_mail($order->customer_email, $subject, $message);
}
}

View File

@@ -0,0 +1,142 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Post_Types {
public static function init() {
// Register immediately since we're already in the init hook
self::register_post_types();
self::register_taxonomies();
add_filter('post_type_link', array(__CLASS__, 'product_permalink'), 10, 2);
}
public static function register_post_types() {
$labels = array(
'name' => __('Products', 'wp-digital-download'),
'singular_name' => __('Product', 'wp-digital-download'),
'menu_name' => __('Digital Products', 'wp-digital-download'),
'add_new' => __('Add New', 'wp-digital-download'),
'add_new_item' => __('Add New Product', 'wp-digital-download'),
'edit_item' => __('Edit Product', 'wp-digital-download'),
'new_item' => __('New Product', 'wp-digital-download'),
'view_item' => __('View Product', 'wp-digital-download'),
'view_items' => __('View Products', 'wp-digital-download'),
'search_items' => __('Search Products', 'wp-digital-download'),
'not_found' => __('No products found', 'wp-digital-download'),
'not_found_in_trash' => __('No products found in Trash', 'wp-digital-download'),
'all_items' => __('All Products', 'wp-digital-download'),
'archives' => __('Product Archives', 'wp-digital-download'),
'attributes' => __('Product Attributes', 'wp-digital-download'),
'insert_into_item' => __('Insert into product', 'wp-digital-download'),
'uploaded_to_this_item' => __('Uploaded to this product', 'wp-digital-download'),
'featured_image' => __('Product Image', 'wp-digital-download'),
'set_featured_image' => __('Set product image', 'wp-digital-download'),
'remove_featured_image' => __('Remove product image', 'wp-digital-download'),
'use_featured_image' => __('Use as product image', 'wp-digital-download'),
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array('slug' => 'product'),
'capability_type' => 'post',
'capabilities' => array(
'edit_post' => 'edit_wpdd_product',
'read_post' => 'read_wpdd_product',
'delete_post' => 'delete_wpdd_product',
'edit_posts' => 'edit_wpdd_products',
'edit_others_posts' => 'edit_others_wpdd_products',
'publish_posts' => 'publish_wpdd_products',
'read_private_posts' => 'read_private_wpdd_products',
'delete_posts' => 'delete_wpdd_products',
'delete_private_posts' => 'delete_private_wpdd_products',
'delete_published_posts' => 'delete_published_wpdd_products',
'delete_others_posts' => 'delete_others_wpdd_products',
'edit_private_posts' => 'edit_private_wpdd_products',
'edit_published_posts' => 'edit_published_wpdd_products',
),
'map_meta_cap' => true,
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 25,
'menu_icon' => 'dashicons-download',
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'author'),
'show_in_rest' => true,
);
register_post_type('wpdd_product', $args);
}
public static function register_taxonomies() {
$labels = array(
'name' => __('Product Categories', 'wp-digital-download'),
'singular_name' => __('Product Category', 'wp-digital-download'),
'search_items' => __('Search Categories', 'wp-digital-download'),
'all_items' => __('All Categories', 'wp-digital-download'),
'parent_item' => __('Parent Category', 'wp-digital-download'),
'parent_item_colon' => __('Parent Category:', 'wp-digital-download'),
'edit_item' => __('Edit Category', 'wp-digital-download'),
'update_item' => __('Update Category', 'wp-digital-download'),
'add_new_item' => __('Add New Category', 'wp-digital-download'),
'new_item_name' => __('New Category Name', 'wp-digital-download'),
'menu_name' => __('Categories', 'wp-digital-download'),
);
$args = array(
'labels' => $labels,
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'product-category'),
'show_in_rest' => true,
);
register_taxonomy('wpdd_product_category', 'wpdd_product', $args);
$tag_labels = array(
'name' => __('Product Tags', 'wp-digital-download'),
'singular_name' => __('Product Tag', 'wp-digital-download'),
'search_items' => __('Search Tags', 'wp-digital-download'),
'popular_items' => __('Popular Tags', 'wp-digital-download'),
'all_items' => __('All Tags', 'wp-digital-download'),
'edit_item' => __('Edit Tag', 'wp-digital-download'),
'update_item' => __('Update Tag', 'wp-digital-download'),
'add_new_item' => __('Add New Tag', 'wp-digital-download'),
'new_item_name' => __('New Tag Name', 'wp-digital-download'),
'separate_items_with_commas' => __('Separate tags with commas', 'wp-digital-download'),
'add_or_remove_items' => __('Add or remove tags', 'wp-digital-download'),
'choose_from_most_used' => __('Choose from the most used tags', 'wp-digital-download'),
'menu_name' => __('Tags', 'wp-digital-download'),
);
$tag_args = array(
'labels' => $tag_labels,
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array('slug' => 'product-tag'),
'show_in_rest' => true,
);
register_taxonomy('wpdd_product_tag', 'wpdd_product', $tag_args);
}
public static function product_permalink($permalink, $post) {
if ($post->post_type !== 'wpdd_product') {
return $permalink;
}
return $permalink;
}
}

View File

@@ -0,0 +1,116 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Roles {
public static function init() {
// Call immediately since we're already in init hook
self::maybe_create_roles();
}
public static function maybe_create_roles() {
if (get_option('wpdd_roles_created') !== WPDD_VERSION) {
self::create_roles();
update_option('wpdd_roles_created', WPDD_VERSION);
}
}
public static function create_roles() {
self::create_customer_role();
self::create_creator_role();
self::add_admin_capabilities();
}
private static function create_customer_role() {
add_role(
'wpdd_customer',
__('Digital Customer', 'wp-digital-download'),
array(
'read' => true,
'wpdd_view_purchases' => true,
'wpdd_download_products' => true,
)
);
}
private static function create_creator_role() {
add_role(
'wpdd_creator',
__('Digital Creator', 'wp-digital-download'),
array(
'read' => true,
'upload_files' => true,
'edit_posts' => false,
'delete_posts' => false,
'publish_posts' => false,
'edit_wpdd_products' => true,
'edit_published_wpdd_products' => true,
'publish_wpdd_products' => true,
'delete_wpdd_products' => true,
'delete_published_wpdd_products' => true,
'edit_private_wpdd_products' => true,
'delete_private_wpdd_products' => true,
'wpdd_view_own_sales' => true,
'wpdd_manage_own_products' => true,
'wpdd_upload_product_files' => true,
'wpdd_view_reports' => true,
)
);
}
private static function add_admin_capabilities() {
$role = get_role('administrator');
if ($role) {
$role->add_cap('edit_wpdd_products');
$role->add_cap('edit_others_wpdd_products');
$role->add_cap('edit_published_wpdd_products');
$role->add_cap('publish_wpdd_products');
$role->add_cap('delete_wpdd_products');
$role->add_cap('delete_others_wpdd_products');
$role->add_cap('delete_published_wpdd_products');
$role->add_cap('edit_private_wpdd_products');
$role->add_cap('delete_private_wpdd_products');
$role->add_cap('wpdd_manage_settings');
$role->add_cap('wpdd_view_all_sales');
$role->add_cap('wpdd_manage_all_products');
$role->add_cap('wpdd_view_reports');
$role->add_cap('wpdd_manage_orders');
}
}
public static function remove_roles() {
remove_role('wpdd_customer');
remove_role('wpdd_creator');
$role = get_role('administrator');
if ($role) {
$caps = array(
'edit_wpdd_products',
'edit_others_wpdd_products',
'edit_published_wpdd_products',
'publish_wpdd_products',
'delete_wpdd_products',
'delete_others_wpdd_products',
'delete_published_wpdd_products',
'edit_private_wpdd_products',
'delete_private_wpdd_products',
'wpdd_manage_settings',
'wpdd_view_all_sales',
'wpdd_manage_all_products',
'wpdd_view_reports',
'wpdd_manage_orders'
);
foreach ($caps as $cap) {
$role->remove_cap($cap);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,249 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Watermark {
public static function apply_watermark($file_path, $order) {
$file_extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
if (filter_var($file_path, FILTER_VALIDATE_URL)) {
$upload_dir = wp_upload_dir();
if (strpos($file_path, $upload_dir['baseurl']) === 0) {
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file_path);
} else {
return false;
}
}
if (!file_exists($file_path)) {
return false;
}
$product_id = $order->product_id;
$watermark_text = get_post_meta($product_id, '_wpdd_watermark_text', true);
if (empty($watermark_text)) {
$watermark_text = '{customer_email}';
}
$watermark_text = self::parse_watermark_placeholders($watermark_text, $order);
switch ($file_extension) {
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return self::watermark_image($file_path, $watermark_text);
case 'pdf':
return self::watermark_pdf($file_path, $watermark_text);
default:
return false;
}
}
private static function parse_watermark_placeholders($text, $order) {
$customer = get_userdata($order->customer_id);
$replacements = array(
'{customer_name}' => $order->customer_name,
'{customer_email}' => $order->customer_email,
'{order_id}' => $order->order_number,
'{date}' => date_i18n(get_option('date_format')),
'{site_name}' => get_bloginfo('name')
);
return str_replace(array_keys($replacements), array_values($replacements), $text);
}
private static function watermark_image($file_path, $watermark_text) {
if (!function_exists('imagecreatefrompng')) {
return false;
}
$file_info = pathinfo($file_path);
$extension = strtolower($file_info['extension']);
switch ($extension) {
case 'jpg':
case 'jpeg':
$image = imagecreatefromjpeg($file_path);
break;
case 'png':
$image = imagecreatefrompng($file_path);
break;
case 'gif':
$image = imagecreatefromgif($file_path);
break;
default:
return false;
}
if (!$image) {
return false;
}
$width = imagesx($image);
$height = imagesy($image);
$font_size = max(10, min(30, $width / 40));
$font_file = WPDD_PLUGIN_PATH . 'assets/fonts/arial.ttf';
if (!file_exists($font_file)) {
$font = 5;
$text_width = imagefontwidth($font) * strlen($watermark_text);
$text_height = imagefontheight($font);
} else {
$bbox = imagettfbbox($font_size, 0, $font_file, $watermark_text);
$text_width = $bbox[2] - $bbox[0];
$text_height = $bbox[1] - $bbox[7];
}
$x = ($width - $text_width) / 2;
$y = $height - 50;
$text_color = imagecolorallocatealpha($image, 255, 255, 255, 30);
$shadow_color = imagecolorallocatealpha($image, 0, 0, 0, 50);
if (file_exists($font_file)) {
imagettftext($image, $font_size, 0, $x + 2, $y + 2, $shadow_color, $font_file, $watermark_text);
imagettftext($image, $font_size, 0, $x, $y, $text_color, $font_file, $watermark_text);
} else {
imagestring($image, $font, $x + 2, $y + 2, $watermark_text, $shadow_color);
imagestring($image, $font, $x, $y, $watermark_text, $text_color);
}
$temp_dir = get_temp_dir();
$temp_file = $temp_dir . 'wpdd_watermark_' . uniqid() . '.' . $extension;
switch ($extension) {
case 'jpg':
case 'jpeg':
imagejpeg($image, $temp_file, 90);
break;
case 'png':
imagepng($image, $temp_file, 9);
break;
case 'gif':
imagegif($image, $temp_file);
break;
}
imagedestroy($image);
return $temp_file;
}
private static function watermark_pdf($file_path, $watermark_text) {
if (!class_exists('TCPDF') && !class_exists('FPDF')) {
return self::watermark_pdf_basic($file_path, $watermark_text);
}
if (class_exists('TCPDF')) {
return self::watermark_pdf_tcpdf($file_path, $watermark_text);
}
return false;
}
private static function watermark_pdf_basic($file_path, $watermark_text) {
$temp_dir = get_temp_dir();
$temp_file = $temp_dir . 'wpdd_watermark_' . uniqid() . '.pdf';
if (copy($file_path, $temp_file)) {
return $temp_file;
}
return false;
}
private static function watermark_pdf_tcpdf($file_path, $watermark_text) {
require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
$pdf = new TCPDF();
$pdf->SetProtection(array('print'), '', null, 0, null);
$pagecount = $pdf->setSourceFile($file_path);
for ($i = 1; $i <= $pagecount; $i++) {
$tplidx = $pdf->importPage($i);
$pdf->AddPage();
$pdf->useTemplate($tplidx);
$pdf->SetFont('helvetica', '', 12);
$pdf->SetTextColor(200, 200, 200);
$pdf->SetAlpha(0.5);
$pdf->StartTransform();
$pdf->Rotate(45, $pdf->getPageWidth() / 2, $pdf->getPageHeight() / 2);
$pdf->Text(
$pdf->getPageWidth() / 2 - 50,
$pdf->getPageHeight() / 2,
$watermark_text
);
$pdf->StopTransform();
$pdf->SetAlpha(1);
}
$temp_dir = get_temp_dir();
$temp_file = $temp_dir . 'wpdd_watermark_' . uniqid() . '.pdf';
$pdf->Output($temp_file, 'F');
return $temp_file;
}
public static function add_text_watermark($content, $watermark_text) {
$watermark_html = sprintf(
'<div style="position: fixed; top: 50%%; left: 50%%; transform: translate(-50%%, -50%%) rotate(-45deg);
opacity: 0.1; font-size: 48px; color: #000; z-index: -1; user-select: none;">%s</div>',
esc_html($watermark_text)
);
return $watermark_html . $content;
}
public static function get_watermark_settings() {
return array(
'enabled' => get_option('wpdd_watermark_enabled', false),
'text' => get_option('wpdd_watermark_text', '{customer_email}'),
'position' => get_option('wpdd_watermark_position', 'center'),
'opacity' => get_option('wpdd_watermark_opacity', 30),
'font_size' => get_option('wpdd_watermark_font_size', 'auto'),
'color' => get_option('wpdd_watermark_color', '#ffffff')
);
}
public static function preview_watermark($file_type = 'image') {
$settings = self::get_watermark_settings();
$preview_text = str_replace(
array('{customer_name}', '{customer_email}', '{order_id}', '{date}', '{site_name}'),
array('John Doe', 'john@example.com', 'WPDD-123456', date_i18n(get_option('date_format')), get_bloginfo('name')),
$settings['text']
);
if ($file_type === 'image') {
$width = 600;
$height = 400;
$image = imagecreatetruecolor($width, $height);
$bg_color = imagecolorallocate($image, 240, 240, 240);
imagefill($image, 0, 0, $bg_color);
$text_color = imagecolorallocatealpha($image, 100, 100, 100, 50);
$font_size = 20;
$x = ($width - (strlen($preview_text) * 10)) / 2;
$y = $height / 2;
imagestring($image, 5, $x, $y, $preview_text, $text_color);
header('Content-Type: image/png');
imagepng($image);
imagedestroy($image);
}
}
}