First Commit
This commit is contained in:
491
includes/class-wpdd-ajax.php
Normal file
491
includes/class-wpdd-ajax.php
Normal 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'));
|
||||
}
|
||||
}
|
177
includes/class-wpdd-creator.php
Normal file
177
includes/class-wpdd-creator.php
Normal 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);
|
||||
}
|
||||
}
|
377
includes/class-wpdd-customer.php
Normal file
377
includes/class-wpdd-customer.php
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
752
includes/class-wpdd-download-handler.php
Normal file
752
includes/class-wpdd-download-handler.php
Normal 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);
|
||||
}
|
||||
}
|
180
includes/class-wpdd-file-protection.php
Normal file
180
includes/class-wpdd-file-protection.php
Normal 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;
|
||||
}
|
||||
}
|
215
includes/class-wpdd-install.php
Normal file
215
includes/class-wpdd-install.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
308
includes/class-wpdd-metaboxes.php
Normal file
308
includes/class-wpdd-metaboxes.php
Normal 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']));
|
||||
}
|
||||
}
|
||||
}
|
318
includes/class-wpdd-orders.php
Normal file
318
includes/class-wpdd-orders.php
Normal 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);
|
||||
}
|
||||
}
|
231
includes/class-wpdd-paypal-payouts.php
Normal file
231
includes/class-wpdd-paypal-payouts.php
Normal 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');
|
||||
}
|
||||
}
|
302
includes/class-wpdd-paypal.php
Normal file
302
includes/class-wpdd-paypal.php
Normal 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 . '¤cy=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);
|
||||
}
|
||||
}
|
142
includes/class-wpdd-post-types.php
Normal file
142
includes/class-wpdd-post-types.php
Normal 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;
|
||||
}
|
||||
}
|
116
includes/class-wpdd-roles.php
Normal file
116
includes/class-wpdd-roles.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1167
includes/class-wpdd-shortcodes.php
Normal file
1167
includes/class-wpdd-shortcodes.php
Normal file
File diff suppressed because it is too large
Load Diff
249
includes/class-wpdd-watermark.php
Normal file
249
includes/class-wpdd-watermark.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user