Major improvements: Fix download limits, enhance license display, fix software filenames
Some checks failed
Create Release / build (push) Failing after 3s
Some checks failed
Create Release / build (push) Failing after 3s
🔧 Bug Fixes: - Fixed download limits defaulting to 5 instead of 0 for unlimited downloads - Fixed software license filename sanitization (spaces→dashes, dots→underscores, proper .zip extension) - Software downloads now show as "Test-Plugin-v2-2-0.zip" instead of "Test Plugin v2.2.0" ✨ UI/UX Enhancements: - Redesigned license key display to span full table width with FontAwesome copy icons - Added responsive CSS styling for license key rows - Integrated FontAwesome CDN for modern copy icons 🏗️ Architecture Improvements: - Added comprehensive filename sanitization in both download handler and API paths - Enhanced software license product handling for local package files - Improved error handling and logging throughout download processes 📦 Infrastructure: - Added Gitea workflows for automated releases on push to main - Created comprehensive .gitignore excluding test files and browser automation - Updated documentation with all recent improvements and technical insights 🔍 Technical Details: - Software license products served from wp-content/uploads/wpdd-packages/ - Download flow: token → process_download_by_token() → process_download() → deliver_file() - Dual path coverage for both API downloads and regular file delivery - Version placeholder system for automated deployment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -92,7 +92,13 @@ class WPDD_Admin_Payouts {
|
||||
</div>
|
||||
<?php elseif ($_GET['message'] === 'error') : ?>
|
||||
<div class="notice notice-error is-dismissible">
|
||||
<p><?php _e('Error processing payout. Please try again.', 'wp-digital-download'); ?></p>
|
||||
<p>
|
||||
<?php _e('Error processing payout:', 'wp-digital-download'); ?>
|
||||
<?php
|
||||
$error_detail = isset($_GET['error_detail']) ? sanitize_text_field(urldecode($_GET['error_detail'])) : '';
|
||||
echo $error_detail ? '<strong>' . esc_html($error_detail) . '</strong>' : __('Unknown error occurred. Please try again.', 'wp-digital-download');
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php elseif ($_GET['message'] === 'balance_adjusted') : ?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
@@ -166,19 +172,26 @@ class WPDD_Admin_Payouts {
|
||||
<tr>
|
||||
<th><label for="creator_id"><?php _e('Creator', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<select name="creator_id" id="creator_id" required style="width: 100%;">
|
||||
<select name="creator_id" id="creator_id" class="wpdd-creator-select" required style="width: 100%;">
|
||||
<option value=""><?php _e('Select Creator', 'wp-digital-download'); ?></option>
|
||||
<?php
|
||||
$all_creators = get_users(array('role' => 'wpdd_creator'));
|
||||
foreach ($all_creators as $creator) :
|
||||
$paypal_email = get_user_meta($creator->ID, 'wpdd_paypal_email', true);
|
||||
$balance = WPDD_Creator::get_creator_balance($creator->ID);
|
||||
?>
|
||||
<option value="<?php echo esc_attr($creator->ID); ?>" <?php echo empty($paypal_email) ? 'disabled' : ''; ?>>
|
||||
<option value="<?php echo esc_attr($creator->ID); ?>"
|
||||
data-balance="<?php echo esc_attr($balance); ?>"
|
||||
data-paypal="<?php echo esc_attr($paypal_email); ?>"
|
||||
<?php echo empty($paypal_email) ? 'disabled' : ''; ?>>
|
||||
<?php echo esc_html($creator->display_name); ?>
|
||||
<?php echo empty($paypal_email) ? ' (' . __('No PayPal email', 'wp-digital-download') . ')' : ' (' . esc_html($paypal_email) . ')'; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div id="creator_balance_display" style="margin-top: 10px; display: none;">
|
||||
<strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong> <span id="creator_balance_amount"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -217,14 +230,19 @@ class WPDD_Admin_Payouts {
|
||||
<tr>
|
||||
<th><label for="adj_creator_id"><?php _e('Creator', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<select name="creator_id" id="adj_creator_id" required style="width: 100%;">
|
||||
<select name="creator_id" id="adj_creator_id" class="wpdd-creator-select" required style="width: 100%;">
|
||||
<option value=""><?php _e('Select Creator', 'wp-digital-download'); ?></option>
|
||||
<?php foreach ($all_creators as $creator) : ?>
|
||||
<option value="<?php echo esc_attr($creator->ID); ?>">
|
||||
<?php foreach ($all_creators as $creator) :
|
||||
$balance = WPDD_Creator::get_creator_balance($creator->ID);
|
||||
?>
|
||||
<option value="<?php echo esc_attr($creator->ID); ?>" data-balance="<?php echo esc_attr($balance); ?>">
|
||||
<?php echo esc_html($creator->display_name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div id="adj_creator_balance_display" style="margin-top: 10px; display: none;">
|
||||
<strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong> <span id="adj_creator_balance_amount"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -497,10 +515,11 @@ class WPDD_Admin_Payouts {
|
||||
|
||||
$result = self::create_payout($creator_id);
|
||||
|
||||
if ($result) {
|
||||
if ($result['success']) {
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=success'));
|
||||
} else {
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=error'));
|
||||
$error_message = urlencode($result['error'] ?? 'Unknown error occurred');
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=error&error_detail=' . $error_message));
|
||||
}
|
||||
exit;
|
||||
}
|
||||
@@ -543,11 +562,28 @@ class WPDD_Admin_Payouts {
|
||||
public static function create_payout($creator_id, $method = 'manual') {
|
||||
global $wpdb;
|
||||
|
||||
// Validate creator
|
||||
$creator = get_userdata($creator_id);
|
||||
if (!$creator) {
|
||||
return array('success' => false, 'error' => 'Creator not found');
|
||||
}
|
||||
|
||||
$balance = WPDD_Creator::get_creator_balance($creator_id);
|
||||
$paypal_email = get_user_meta($creator_id, 'wpdd_paypal_email', true);
|
||||
|
||||
if ($balance <= 0 || empty($paypal_email)) {
|
||||
return false;
|
||||
if ($balance <= 0) {
|
||||
return array('success' => false, 'error' => 'Creator has zero balance to payout');
|
||||
}
|
||||
|
||||
if (empty($paypal_email)) {
|
||||
return array('success' => false, 'error' => 'Creator has no PayPal email configured');
|
||||
}
|
||||
|
||||
// Validate PayPal credentials are configured
|
||||
$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 in settings');
|
||||
}
|
||||
|
||||
$currency = get_option('wpdd_currency', 'USD');
|
||||
@@ -571,6 +607,10 @@ class WPDD_Admin_Payouts {
|
||||
|
||||
$payout_id = $wpdb->insert_id;
|
||||
|
||||
if (!$payout_id) {
|
||||
return array('success' => false, 'error' => 'Failed to create payout record in database');
|
||||
}
|
||||
|
||||
// Try to process via PayPal API
|
||||
$result = WPDD_PayPal_Payouts::process_payout($payout_id);
|
||||
|
||||
@@ -588,10 +628,25 @@ class WPDD_Admin_Payouts {
|
||||
array('%d')
|
||||
);
|
||||
|
||||
// Mark all available earnings for this creator as paid
|
||||
$wpdb->update(
|
||||
$wpdb->prefix . 'wpdd_creator_earnings',
|
||||
array(
|
||||
'payout_id' => $payout_id,
|
||||
'payout_status' => 'paid'
|
||||
),
|
||||
array(
|
||||
'creator_id' => $creator_id,
|
||||
'payout_status' => 'available'
|
||||
),
|
||||
array('%d', '%s'),
|
||||
array('%d', '%s')
|
||||
);
|
||||
|
||||
// Reset creator balance
|
||||
update_user_meta($creator_id, 'wpdd_creator_balance', 0);
|
||||
|
||||
return true;
|
||||
return array('success' => true, 'message' => 'Payout processed successfully');
|
||||
} else {
|
||||
// Update with error
|
||||
$wpdb->update(
|
||||
@@ -605,7 +660,7 @@ class WPDD_Admin_Payouts {
|
||||
array('%d')
|
||||
);
|
||||
|
||||
return false;
|
||||
return array('success' => false, 'error' => $result['error'] ?? 'PayPal payout processing failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,7 +868,7 @@ class WPDD_Admin_Payouts {
|
||||
}
|
||||
|
||||
// Get current balance
|
||||
$current_balance = floatval(get_user_meta($creator_id, 'wpdd_balance', true));
|
||||
$current_balance = floatval(get_user_meta($creator_id, 'wpdd_creator_balance', true));
|
||||
|
||||
// Calculate new balance
|
||||
if ($adjustment_type === 'add') {
|
||||
@@ -823,7 +878,7 @@ class WPDD_Admin_Payouts {
|
||||
}
|
||||
|
||||
// Update the balance
|
||||
update_user_meta($creator_id, 'wpdd_balance', $new_balance);
|
||||
update_user_meta($creator_id, 'wpdd_creator_balance', $new_balance);
|
||||
|
||||
// Create a record of this adjustment
|
||||
global $wpdb;
|
||||
|
@@ -14,6 +14,8 @@ class WPDD_Admin {
|
||||
add_action('pre_get_posts', array(__CLASS__, 'sort_products_by_column'));
|
||||
add_action('pre_get_posts', array(__CLASS__, 'filter_creator_products'));
|
||||
add_action('admin_init', array(__CLASS__, 'handle_admin_actions'));
|
||||
add_action('wp_ajax_wpdd_sync_software_product', array(__CLASS__, 'handle_sync_software_product'));
|
||||
add_action('wp_ajax_wpdd_regenerate_licenses', array(__CLASS__, 'handle_regenerate_licenses'));
|
||||
|
||||
// Initialize admin payouts
|
||||
if (class_exists('WPDD_Admin_Payouts')) {
|
||||
@@ -145,7 +147,17 @@ class WPDD_Admin {
|
||||
case 'wpdd_files':
|
||||
$files = get_post_meta($post_id, '_wpdd_files', true);
|
||||
$count = is_array($files) ? count($files) : 0;
|
||||
$product_type = get_post_meta($post_id, '_wpdd_product_type', true);
|
||||
|
||||
echo $count;
|
||||
|
||||
// Add sync button for software license products with no files
|
||||
if ($product_type === 'software_license' && $count === 0) {
|
||||
$nonce = wp_create_nonce('wpdd_sync_product_' . $post_id);
|
||||
echo '<br><button type="button" class="button button-small wpdd-sync-product" data-product-id="' . $post_id . '" data-nonce="' . $nonce . '">';
|
||||
echo __('Sync Files', 'wp-digital-download');
|
||||
echo '</button>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1232,4 +1244,64 @@ class WPDD_Admin {
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-creator-payouts&message=payout_requested'));
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function handle_sync_software_product() {
|
||||
if (!current_user_can('edit_wpdd_products')) {
|
||||
wp_die('Unauthorized');
|
||||
}
|
||||
|
||||
if (!isset($_POST['product_id']) || !wp_verify_nonce($_POST['nonce'], 'wpdd_sync_product_' . $_POST['product_id'])) {
|
||||
wp_die('Invalid nonce');
|
||||
}
|
||||
|
||||
$product_id = intval($_POST['product_id']);
|
||||
|
||||
if (!class_exists('WPDD_API')) {
|
||||
require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-api.php';
|
||||
}
|
||||
|
||||
$result = WPDD_API::sync_software_product($product_id, true);
|
||||
|
||||
wp_send_json($result);
|
||||
}
|
||||
|
||||
public static function handle_regenerate_licenses() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die('Unauthorized');
|
||||
}
|
||||
|
||||
if (!wp_verify_nonce($_POST['nonce'], 'wpdd_regenerate_licenses')) {
|
||||
wp_die('Invalid nonce');
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// Find completed orders for software license products that don't have license keys
|
||||
$orders = $wpdb->get_results("
|
||||
SELECT o.*, pm.meta_value as product_type
|
||||
FROM {$wpdb->prefix}wpdd_orders o
|
||||
LEFT JOIN {$wpdb->postmeta} pm ON o.product_id = pm.post_id AND pm.meta_key = '_wpdd_product_type'
|
||||
LEFT JOIN {$wpdb->prefix}wpdd_licenses l ON o.id = l.order_id
|
||||
WHERE o.status = 'completed'
|
||||
AND pm.meta_value = 'software_license'
|
||||
AND l.license_key IS NULL
|
||||
");
|
||||
|
||||
$generated = 0;
|
||||
|
||||
foreach ($orders as $order) {
|
||||
if (class_exists('WPDD_License_Manager')) {
|
||||
$license_key = WPDD_License_Manager::create_license($order->id);
|
||||
if ($license_key) {
|
||||
$generated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json(array(
|
||||
'success' => true,
|
||||
'message' => sprintf('Generated %d license keys for existing orders.', $generated),
|
||||
'generated' => $generated
|
||||
));
|
||||
}
|
||||
}
|
470
admin/class-wpdd-order-manager.php
Normal file
470
admin/class-wpdd-order-manager.php
Normal file
@@ -0,0 +1,470 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WPDD_Order_Manager {
|
||||
|
||||
public static function init() {
|
||||
add_action('admin_menu', array(__CLASS__, 'add_menu_page'));
|
||||
add_action('admin_post_wpdd_cancel_order', array(__CLASS__, 'handle_cancel_order'));
|
||||
add_action('admin_post_wpdd_refund_order', array(__CLASS__, 'handle_refund_order'));
|
||||
add_action('admin_post_wpdd_release_earnings', array(__CLASS__, 'handle_release_earnings'));
|
||||
add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_scripts'));
|
||||
}
|
||||
|
||||
public static function add_menu_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_submenu_page(
|
||||
'edit.php?post_type=wpdd_product',
|
||||
__('Order Management', 'wp-digital-download'),
|
||||
__('Order Manager', 'wp-digital-download'),
|
||||
'manage_options',
|
||||
'wpdd-order-manager',
|
||||
array(__CLASS__, 'render_page')
|
||||
);
|
||||
}
|
||||
|
||||
public static function enqueue_scripts($hook) {
|
||||
if ($hook !== 'wpdd_product_page_wpdd-order-manager') {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script('wpdd-order-manager', WPDD_PLUGIN_URL . 'assets/js/admin-order-manager.js', array('jquery'), WPDD_VERSION, true);
|
||||
wp_localize_script('wpdd-order-manager', 'wpdd_order_manager', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('wpdd_order_manager'),
|
||||
'confirm_cancel' => __('Are you sure you want to cancel this order? This action cannot be undone.', 'wp-digital-download'),
|
||||
'confirm_refund' => __('Are you sure you want to process this refund? The customer will lose access to the product.', 'wp-digital-download'),
|
||||
'confirm_release' => __('Are you sure you want to release these earnings immediately?', 'wp-digital-download')
|
||||
));
|
||||
}
|
||||
|
||||
public static function render_page() {
|
||||
global $wpdb;
|
||||
|
||||
// Get filter parameters
|
||||
$status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : 'all';
|
||||
$creator_filter = isset($_GET['creator']) ? intval($_GET['creator']) : 0;
|
||||
$date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : date('Y-m-d', strtotime('-30 days'));
|
||||
$date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
|
||||
|
||||
// Build query
|
||||
$where_conditions = array('1=1');
|
||||
$query_params = array();
|
||||
|
||||
if ($status_filter !== 'all') {
|
||||
$where_conditions[] = 'o.status = %s';
|
||||
$query_params[] = $status_filter;
|
||||
}
|
||||
|
||||
if ($creator_filter > 0) {
|
||||
$where_conditions[] = 'p.post_author = %d';
|
||||
$query_params[] = $creator_filter;
|
||||
}
|
||||
|
||||
if ($date_from) {
|
||||
$where_conditions[] = 'DATE(o.purchase_date) >= %s';
|
||||
$query_params[] = $date_from;
|
||||
}
|
||||
|
||||
if ($date_to) {
|
||||
$where_conditions[] = 'DATE(o.purchase_date) <= %s';
|
||||
$query_params[] = $date_to;
|
||||
}
|
||||
|
||||
$where_clause = implode(' AND ', $where_conditions);
|
||||
|
||||
// Get orders with earnings status
|
||||
$orders = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT o.*,
|
||||
p.post_title as product_name,
|
||||
u.display_name as creator_name,
|
||||
e.payout_status,
|
||||
e.available_at,
|
||||
e.creator_earning,
|
||||
e.id as earning_id
|
||||
FROM {$wpdb->prefix}wpdd_orders o
|
||||
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
|
||||
LEFT JOIN {$wpdb->users} u ON p.post_author = u.ID
|
||||
LEFT JOIN {$wpdb->prefix}wpdd_creator_earnings e ON o.id = e.order_id
|
||||
WHERE $where_clause
|
||||
ORDER BY o.purchase_date DESC
|
||||
LIMIT 100",
|
||||
$query_params
|
||||
));
|
||||
|
||||
// Get creators for filter
|
||||
$creators = get_users(array('role' => 'wpdd_creator'));
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php _e('Order Management', 'wp-digital-download'); ?></h1>
|
||||
|
||||
<?php if (isset($_GET['message'])) : ?>
|
||||
<?php if ($_GET['message'] === 'cancelled') : ?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><?php _e('Order cancelled successfully.', 'wp-digital-download'); ?></p>
|
||||
</div>
|
||||
<?php elseif ($_GET['message'] === 'refunded') : ?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><?php _e('Refund processed successfully.', 'wp-digital-download'); ?></p>
|
||||
</div>
|
||||
<?php elseif ($_GET['message'] === 'released') : ?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><?php _e('Earnings released successfully.', 'wp-digital-download'); ?></p>
|
||||
</div>
|
||||
<?php elseif ($_GET['message'] === 'error') : ?>
|
||||
<div class="notice notice-error is-dismissible">
|
||||
<p><?php _e('Error processing request. Please try again.', 'wp-digital-download'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Filter Form -->
|
||||
<div class="wpdd-filter-box" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
||||
<h3><?php _e('Filters', 'wp-digital-download'); ?></h3>
|
||||
<form method="get" action="">
|
||||
<input type="hidden" name="post_type" value="wpdd_product">
|
||||
<input type="hidden" name="page" value="wpdd-order-manager">
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th><label for="status"><?php _e('Order Status', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<select name="status" id="status">
|
||||
<option value="all"><?php _e('All Statuses', 'wp-digital-download'); ?></option>
|
||||
<option value="completed" <?php selected($status_filter, 'completed'); ?>><?php _e('Completed', 'wp-digital-download'); ?></option>
|
||||
<option value="pending" <?php selected($status_filter, 'pending'); ?>><?php _e('Pending', 'wp-digital-download'); ?></option>
|
||||
<option value="failed" <?php selected($status_filter, 'failed'); ?>><?php _e('Failed', 'wp-digital-download'); ?></option>
|
||||
<option value="cancelled" <?php selected($status_filter, 'cancelled'); ?>><?php _e('Cancelled', 'wp-digital-download'); ?></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="creator"><?php _e('Creator', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<select name="creator" id="creator">
|
||||
<option value="0"><?php _e('All Creators', 'wp-digital-download'); ?></option>
|
||||
<?php foreach ($creators as $creator) : ?>
|
||||
<option value="<?php echo esc_attr($creator->ID); ?>" <?php selected($creator_filter, $creator->ID); ?>>
|
||||
<?php echo esc_html($creator->display_name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="date_from"><?php _e('Date Range', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<input type="date" name="date_from" id="date_from" value="<?php echo esc_attr($date_from); ?>">
|
||||
<?php _e('to', 'wp-digital-download'); ?>
|
||||
<input type="date" name="date_to" id="date_to" value="<?php echo esc_attr($date_to); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="submit">
|
||||
<input type="submit" class="button button-primary" value="<?php _e('Filter Orders', 'wp-digital-download'); ?>">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Orders Table -->
|
||||
<div class="wpdd-orders-table" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
||||
<h3><?php _e('Orders', 'wp-digital-download'); ?> (<?php echo count($orders); ?>)</h3>
|
||||
|
||||
<?php if (empty($orders)) : ?>
|
||||
<p><?php _e('No orders found for the selected criteria.', 'wp-digital-download'); ?></p>
|
||||
<?php else : ?>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Order', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Customer', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Product', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Creator', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Amount', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Date', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Status', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Earnings Status', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Actions', 'wp-digital-download'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($orders as $order) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?php echo esc_html($order->order_number); ?></strong><br>
|
||||
<small>#<?php echo esc_html($order->id); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<strong><?php echo esc_html($order->customer_name); ?></strong><br>
|
||||
<small><?php echo esc_html($order->customer_email); ?></small>
|
||||
</td>
|
||||
<td>
|
||||
<strong><?php echo esc_html($order->product_name); ?></strong><br>
|
||||
<small>ID: <?php echo esc_html($order->product_id); ?></small>
|
||||
</td>
|
||||
<td><?php echo esc_html($order->creator_name); ?></td>
|
||||
<td><strong><?php echo wpdd_format_price($order->amount); ?></strong></td>
|
||||
<td><?php echo esc_html(date_i18n(get_option('date_format'), strtotime($order->purchase_date))); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$status_class = '';
|
||||
switch($order->status) {
|
||||
case 'completed':
|
||||
$status_class = 'notice-success';
|
||||
break;
|
||||
case 'cancelled':
|
||||
case 'failed':
|
||||
$status_class = 'notice-error';
|
||||
break;
|
||||
case 'pending':
|
||||
$status_class = 'notice-warning';
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<span class="notice <?php echo esc_attr($status_class); ?>" style="padding: 2px 8px; display: inline-block;">
|
||||
<?php echo esc_html(ucfirst($order->status)); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($order->payout_status) : ?>
|
||||
<?php
|
||||
$earnings_class = '';
|
||||
$earnings_text = ucfirst($order->payout_status);
|
||||
switch($order->payout_status) {
|
||||
case 'pending':
|
||||
$earnings_class = 'notice-info';
|
||||
if ($order->available_at) {
|
||||
$earnings_text .= '<br><small>Until: ' . date('M j', strtotime($order->available_at)) . '</small>';
|
||||
}
|
||||
break;
|
||||
case 'available':
|
||||
$earnings_class = 'notice-warning';
|
||||
break;
|
||||
case 'paid':
|
||||
$earnings_class = 'notice-success';
|
||||
break;
|
||||
case 'cancelled':
|
||||
$earnings_class = 'notice-error';
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<span class="notice <?php echo esc_attr($earnings_class); ?>" style="padding: 2px 8px; display: inline-block;">
|
||||
<?php echo $earnings_text; ?>
|
||||
</span>
|
||||
<?php if ($order->creator_earning > 0) : ?>
|
||||
<br><small><?php echo wpdd_format_price($order->creator_earning); ?></small>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<span class="notice notice-error" style="padding: 2px 8px; display: inline-block;">
|
||||
<?php _e('No Earnings', 'wp-digital-download'); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="button-group">
|
||||
<?php if ($order->status === 'completed') : ?>
|
||||
|
||||
<?php if ($order->payout_status === 'pending') : ?>
|
||||
<!-- Release Earnings Button -->
|
||||
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline;">
|
||||
<input type="hidden" name="action" value="wpdd_release_earnings">
|
||||
<input type="hidden" name="earning_id" value="<?php echo esc_attr($order->earning_id); ?>">
|
||||
<input type="hidden" name="order_id" value="<?php echo esc_attr($order->id); ?>">
|
||||
<?php wp_nonce_field('wpdd_release_earnings_' . $order->earning_id, 'wpdd_nonce'); ?>
|
||||
<button type="submit" class="button button-secondary wpdd-release-btn" title="<?php _e('Release earnings immediately', 'wp-digital-download'); ?>">
|
||||
<span class="dashicons dashicons-unlock"></span> <?php _e('Release', 'wp-digital-download'); ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($order->payout_status !== 'paid') : ?>
|
||||
<!-- Cancel Order Button -->
|
||||
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline;">
|
||||
<input type="hidden" name="action" value="wpdd_cancel_order">
|
||||
<input type="hidden" name="order_id" value="<?php echo esc_attr($order->id); ?>">
|
||||
<?php wp_nonce_field('wpdd_cancel_order_' . $order->id, 'wpdd_nonce'); ?>
|
||||
<button type="submit" class="button button-link-delete wpdd-cancel-btn" title="<?php _e('Cancel order and revoke access', 'wp-digital-download'); ?>">
|
||||
<span class="dashicons dashicons-no"></span> <?php _e('Cancel', 'wp-digital-download'); ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Refund Button -->
|
||||
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline;">
|
||||
<input type="hidden" name="action" value="wpdd_refund_order">
|
||||
<input type="hidden" name="order_id" value="<?php echo esc_attr($order->id); ?>">
|
||||
<?php wp_nonce_field('wpdd_refund_order_' . $order->id, 'wpdd_nonce'); ?>
|
||||
<button type="submit" class="button button-link-delete wpdd-refund-btn" title="<?php _e('Process refund (manual PayPal refund required)', 'wp-digital-download'); ?>">
|
||||
<span class="dashicons dashicons-undo"></span> <?php _e('Refund', 'wp-digital-download'); ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php else : ?>
|
||||
<span class="description"><?php _e('Earnings already paid out', 'wp-digital-download'); ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php else : ?>
|
||||
<span class="description"><?php printf(__('Order is %s', 'wp-digital-download'), $order->status); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.button-group {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.button-group .button {
|
||||
margin: 2px;
|
||||
font-size: 11px;
|
||||
padding: 3px 8px;
|
||||
height: auto;
|
||||
}
|
||||
.button-group .dashicons {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function handle_cancel_order() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die(__('Permission denied', 'wp-digital-download'));
|
||||
}
|
||||
|
||||
$order_id = intval($_POST['order_id']);
|
||||
|
||||
if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_cancel_order_' . $order_id)) {
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=error'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$success = self::cancel_order($order_id);
|
||||
|
||||
$message = $success ? 'cancelled' : 'error';
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=' . $message));
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function handle_refund_order() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die(__('Permission denied', 'wp-digital-download'));
|
||||
}
|
||||
|
||||
$order_id = intval($_POST['order_id']);
|
||||
|
||||
if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_refund_order_' . $order_id)) {
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=error'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$success = self::refund_order($order_id);
|
||||
|
||||
$message = $success ? 'refunded' : 'error';
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=' . $message));
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function handle_release_earnings() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die(__('Permission denied', 'wp-digital-download'));
|
||||
}
|
||||
|
||||
$earning_id = intval($_POST['earning_id']);
|
||||
$order_id = intval($_POST['order_id']);
|
||||
|
||||
if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_release_earnings_' . $earning_id)) {
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=error'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$success = WPDD_Earnings_Processor::release_earning_immediately($earning_id);
|
||||
|
||||
$message = $success ? 'released' : 'error';
|
||||
wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=' . $message));
|
||||
exit;
|
||||
}
|
||||
|
||||
private static function cancel_order($order_id) {
|
||||
global $wpdb;
|
||||
|
||||
// Update order status
|
||||
$result = $wpdb->update(
|
||||
$wpdb->prefix . 'wpdd_orders',
|
||||
array('status' => 'cancelled'),
|
||||
array('id' => $order_id),
|
||||
array('%s'),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
// Revoke download access
|
||||
$wpdb->delete(
|
||||
$wpdb->prefix . 'wpdd_download_links',
|
||||
array('order_id' => $order_id),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
// Cancel associated earnings
|
||||
$earning_id = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT id FROM {$wpdb->prefix}wpdd_creator_earnings WHERE order_id = %d",
|
||||
$order_id
|
||||
));
|
||||
|
||||
if ($earning_id) {
|
||||
WPDD_Earnings_Processor::cancel_earning($earning_id, 'Order cancelled by admin');
|
||||
}
|
||||
}
|
||||
|
||||
return $result > 0;
|
||||
}
|
||||
|
||||
private static function refund_order($order_id) {
|
||||
global $wpdb;
|
||||
|
||||
// Update order status
|
||||
$result = $wpdb->update(
|
||||
$wpdb->prefix . 'wpdd_orders',
|
||||
array('status' => 'refunded'),
|
||||
array('id' => $order_id),
|
||||
array('%s'),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
// Revoke download access
|
||||
$wpdb->delete(
|
||||
$wpdb->prefix . 'wpdd_download_links',
|
||||
array('order_id' => $order_id),
|
||||
array('%d')
|
||||
);
|
||||
|
||||
// Cancel associated earnings
|
||||
$earning_id = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT id FROM {$wpdb->prefix}wpdd_creator_earnings WHERE order_id = %d",
|
||||
$order_id
|
||||
));
|
||||
|
||||
if ($earning_id) {
|
||||
WPDD_Earnings_Processor::cancel_earning($earning_id, 'Order refunded by admin');
|
||||
}
|
||||
}
|
||||
|
||||
return $result > 0;
|
||||
}
|
||||
}
|
@@ -9,6 +9,54 @@ class WPDD_Settings {
|
||||
public static function init() {
|
||||
add_action('admin_menu', array(__CLASS__, 'add_settings_page'));
|
||||
add_action('admin_init', array(__CLASS__, 'register_settings'));
|
||||
|
||||
// Ensure default settings are set
|
||||
self::ensure_default_settings();
|
||||
}
|
||||
|
||||
public static function ensure_default_settings() {
|
||||
$defaults = array(
|
||||
'wpdd_paypal_mode' => 'sandbox',
|
||||
'wpdd_currency' => 'USD',
|
||||
'wpdd_commission_rate' => 0,
|
||||
'wpdd_payout_threshold' => 0,
|
||||
'wpdd_earnings_holding_days' => 15,
|
||||
'wpdd_enable_guest_checkout' => 1,
|
||||
'wpdd_default_download_limit' => 5,
|
||||
'wpdd_default_download_expiry' => 7,
|
||||
'wpdd_enable_watermark' => 0,
|
||||
'wpdd_file_access_method' => 'direct',
|
||||
'wpdd_smtp_enabled' => 0,
|
||||
'wpdd_smtp_port' => 587,
|
||||
'wpdd_smtp_encryption' => 'tls'
|
||||
);
|
||||
|
||||
foreach ($defaults as $option_name => $default_value) {
|
||||
if (get_option($option_name) === false) {
|
||||
add_option($option_name, $default_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure critical settings exist (but don't set default values)
|
||||
$critical_settings = array(
|
||||
'wpdd_paypal_client_id',
|
||||
'wpdd_paypal_secret',
|
||||
'wpdd_admin_email',
|
||||
'wpdd_from_name',
|
||||
'wpdd_from_email',
|
||||
'wpdd_smtp_host',
|
||||
'wpdd_smtp_username',
|
||||
'wpdd_smtp_password',
|
||||
'wpdd_watermark_text',
|
||||
'wpdd_terms_page',
|
||||
'wpdd_privacy_page'
|
||||
);
|
||||
|
||||
foreach ($critical_settings as $setting) {
|
||||
if (get_option($setting) === false) {
|
||||
add_option($setting, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function add_settings_page() {
|
||||
@@ -23,69 +71,80 @@ class WPDD_Settings {
|
||||
}
|
||||
|
||||
public static function register_settings() {
|
||||
register_setting('wpdd_settings', 'wpdd_paypal_mode');
|
||||
register_setting('wpdd_settings', 'wpdd_paypal_client_id');
|
||||
register_setting('wpdd_settings', 'wpdd_paypal_secret');
|
||||
register_setting('wpdd_settings', 'wpdd_paypal_payout_email');
|
||||
register_setting('wpdd_settings', 'wpdd_admin_email');
|
||||
register_setting('wpdd_settings', 'wpdd_from_name');
|
||||
register_setting('wpdd_settings', 'wpdd_from_email');
|
||||
register_setting('wpdd_settings', 'wpdd_smtp_enabled');
|
||||
register_setting('wpdd_settings', 'wpdd_smtp_host');
|
||||
register_setting('wpdd_settings', 'wpdd_smtp_port');
|
||||
register_setting('wpdd_settings', 'wpdd_smtp_username');
|
||||
register_setting('wpdd_settings', 'wpdd_smtp_password');
|
||||
register_setting('wpdd_settings', 'wpdd_smtp_encryption');
|
||||
register_setting('wpdd_settings', 'wpdd_currency');
|
||||
register_setting('wpdd_settings', 'wpdd_enable_guest_checkout');
|
||||
register_setting('wpdd_settings', 'wpdd_default_download_limit');
|
||||
register_setting('wpdd_settings', 'wpdd_default_download_expiry');
|
||||
register_setting('wpdd_settings', 'wpdd_enable_watermark');
|
||||
register_setting('wpdd_settings', 'wpdd_watermark_text');
|
||||
register_setting('wpdd_settings', 'wpdd_terms_page');
|
||||
register_setting('wpdd_settings', 'wpdd_privacy_page');
|
||||
register_setting('wpdd_settings', 'wpdd_commission_rate', array(
|
||||
// General Settings
|
||||
register_setting('wpdd_general_settings', 'wpdd_currency');
|
||||
register_setting('wpdd_general_settings', 'wpdd_enable_guest_checkout');
|
||||
register_setting('wpdd_general_settings', 'wpdd_commission_rate', array(
|
||||
'sanitize_callback' => array(__CLASS__, 'sanitize_commission_rate')
|
||||
));
|
||||
register_setting('wpdd_settings', 'wpdd_payout_threshold', array(
|
||||
register_setting('wpdd_general_settings', 'wpdd_payout_threshold', array(
|
||||
'sanitize_callback' => array(__CLASS__, 'sanitize_payout_threshold')
|
||||
));
|
||||
register_setting('wpdd_settings', 'wpdd_file_access_method');
|
||||
register_setting('wpdd_settings', 'wpdd_disable_admin_bar');
|
||||
register_setting('wpdd_general_settings', 'wpdd_earnings_holding_days', array(
|
||||
'sanitize_callback' => array(__CLASS__, 'sanitize_holding_days')
|
||||
));
|
||||
register_setting('wpdd_general_settings', 'wpdd_terms_page');
|
||||
register_setting('wpdd_general_settings', 'wpdd_privacy_page');
|
||||
|
||||
// PayPal Settings
|
||||
register_setting('wpdd_paypal_settings', 'wpdd_paypal_mode');
|
||||
register_setting('wpdd_paypal_settings', 'wpdd_paypal_client_id');
|
||||
register_setting('wpdd_paypal_settings', 'wpdd_paypal_secret');
|
||||
|
||||
// Email Settings
|
||||
register_setting('wpdd_email_settings', 'wpdd_admin_email');
|
||||
register_setting('wpdd_email_settings', 'wpdd_from_name');
|
||||
register_setting('wpdd_email_settings', 'wpdd_from_email');
|
||||
register_setting('wpdd_email_settings', 'wpdd_smtp_enabled');
|
||||
register_setting('wpdd_email_settings', 'wpdd_smtp_host');
|
||||
register_setting('wpdd_email_settings', 'wpdd_smtp_port');
|
||||
register_setting('wpdd_email_settings', 'wpdd_smtp_username');
|
||||
register_setting('wpdd_email_settings', 'wpdd_smtp_password');
|
||||
register_setting('wpdd_email_settings', 'wpdd_smtp_encryption');
|
||||
|
||||
// Download Settings
|
||||
register_setting('wpdd_download_settings', 'wpdd_default_download_limit');
|
||||
register_setting('wpdd_download_settings', 'wpdd_default_download_expiry');
|
||||
register_setting('wpdd_download_settings', 'wpdd_file_access_method');
|
||||
register_setting('wpdd_download_settings', 'wpdd_disable_admin_bar');
|
||||
|
||||
// Watermark Settings
|
||||
register_setting('wpdd_watermark_settings', 'wpdd_enable_watermark');
|
||||
register_setting('wpdd_watermark_settings', 'wpdd_watermark_text');
|
||||
|
||||
add_settings_section(
|
||||
'wpdd_general_settings',
|
||||
__('General Settings', 'wp-digital-download'),
|
||||
array(__CLASS__, 'general_section_callback'),
|
||||
'wpdd_settings'
|
||||
'wpdd_general_settings'
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'wpdd_paypal_settings',
|
||||
__('PayPal Settings', 'wp-digital-download'),
|
||||
array(__CLASS__, 'paypal_section_callback'),
|
||||
'wpdd_settings'
|
||||
'wpdd_paypal_settings'
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'wpdd_email_settings',
|
||||
__('Email Settings', 'wp-digital-download'),
|
||||
array(__CLASS__, 'email_section_callback'),
|
||||
'wpdd_settings'
|
||||
'wpdd_email_settings'
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'wpdd_download_settings',
|
||||
__('Download Settings', 'wp-digital-download'),
|
||||
array(__CLASS__, 'download_section_callback'),
|
||||
'wpdd_settings'
|
||||
'wpdd_download_settings'
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'wpdd_watermark_settings',
|
||||
__('Watermark Settings', 'wp-digital-download'),
|
||||
array(__CLASS__, 'watermark_section_callback'),
|
||||
'wpdd_settings'
|
||||
'wpdd_watermark_settings'
|
||||
);
|
||||
|
||||
self::add_general_fields();
|
||||
@@ -100,7 +159,7 @@ class WPDD_Settings {
|
||||
'wpdd_currency',
|
||||
__('Currency', 'wp-digital-download'),
|
||||
array(__CLASS__, 'currency_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_general_settings',
|
||||
'wpdd_general_settings',
|
||||
array(
|
||||
'name' => 'wpdd_currency'
|
||||
@@ -111,7 +170,7 @@ class WPDD_Settings {
|
||||
'wpdd_enable_guest_checkout',
|
||||
__('Guest Checkout', 'wp-digital-download'),
|
||||
array(__CLASS__, 'checkbox_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_general_settings',
|
||||
'wpdd_general_settings',
|
||||
array(
|
||||
'name' => 'wpdd_enable_guest_checkout',
|
||||
@@ -123,7 +182,7 @@ class WPDD_Settings {
|
||||
'wpdd_commission_rate',
|
||||
__('Platform Commission Rate (%)', 'wp-digital-download'),
|
||||
array(__CLASS__, 'number_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_general_settings',
|
||||
'wpdd_general_settings',
|
||||
array(
|
||||
'name' => 'wpdd_commission_rate',
|
||||
@@ -138,7 +197,7 @@ class WPDD_Settings {
|
||||
'wpdd_payout_threshold',
|
||||
__('Automatic Payout Threshold ($)', 'wp-digital-download'),
|
||||
array(__CLASS__, 'number_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_general_settings',
|
||||
'wpdd_general_settings',
|
||||
array(
|
||||
'name' => 'wpdd_payout_threshold',
|
||||
@@ -148,11 +207,27 @@ class WPDD_Settings {
|
||||
)
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'wpdd_earnings_holding_days',
|
||||
__('Earnings Holding Period (Days)', 'wp-digital-download'),
|
||||
array(__CLASS__, 'number_field'),
|
||||
'wpdd_general_settings',
|
||||
'wpdd_general_settings',
|
||||
array(
|
||||
'name' => 'wpdd_earnings_holding_days',
|
||||
'description' => __('Number of days to hold earnings before making them available for payout (0 for immediate, 15 recommended for fraud protection)', 'wp-digital-download'),
|
||||
'min' => 0,
|
||||
'max' => 365,
|
||||
'step' => 1,
|
||||
'default' => 15
|
||||
)
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'wpdd_terms_page',
|
||||
__('Terms & Conditions Page', 'wp-digital-download'),
|
||||
array(__CLASS__, 'page_dropdown_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_general_settings',
|
||||
'wpdd_general_settings',
|
||||
array('name' => 'wpdd_terms_page')
|
||||
);
|
||||
@@ -161,7 +236,7 @@ class WPDD_Settings {
|
||||
'wpdd_privacy_page',
|
||||
__('Privacy Policy Page', 'wp-digital-download'),
|
||||
array(__CLASS__, 'page_dropdown_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_general_settings',
|
||||
'wpdd_general_settings',
|
||||
array('name' => 'wpdd_privacy_page')
|
||||
);
|
||||
@@ -172,7 +247,7 @@ class WPDD_Settings {
|
||||
'wpdd_paypal_mode',
|
||||
__('PayPal Mode', 'wp-digital-download'),
|
||||
array(__CLASS__, 'select_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_paypal_settings',
|
||||
'wpdd_paypal_settings',
|
||||
array(
|
||||
'name' => 'wpdd_paypal_mode',
|
||||
@@ -187,7 +262,7 @@ class WPDD_Settings {
|
||||
'wpdd_paypal_client_id',
|
||||
__('PayPal Client ID', 'wp-digital-download'),
|
||||
array(__CLASS__, 'text_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_paypal_settings',
|
||||
'wpdd_paypal_settings',
|
||||
array('name' => 'wpdd_paypal_client_id')
|
||||
);
|
||||
@@ -196,22 +271,11 @@ class WPDD_Settings {
|
||||
'wpdd_paypal_secret',
|
||||
__('PayPal Secret', 'wp-digital-download'),
|
||||
array(__CLASS__, 'password_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_paypal_settings',
|
||||
'wpdd_paypal_settings',
|
||||
array('name' => 'wpdd_paypal_secret')
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'wpdd_paypal_payout_email',
|
||||
__('PayPal Payout Account Email', 'wp-digital-download'),
|
||||
array(__CLASS__, 'email_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_paypal_settings',
|
||||
array(
|
||||
'name' => 'wpdd_paypal_payout_email',
|
||||
'description' => __('PayPal account email that will send payouts to creators', 'wp-digital-download')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static function add_email_fields() {
|
||||
@@ -219,7 +283,7 @@ class WPDD_Settings {
|
||||
'wpdd_admin_email',
|
||||
__('Admin Email', 'wp-digital-download'),
|
||||
array(__CLASS__, 'email_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_admin_email',
|
||||
@@ -231,7 +295,7 @@ class WPDD_Settings {
|
||||
'wpdd_from_name',
|
||||
__('From Name', 'wp-digital-download'),
|
||||
array(__CLASS__, 'text_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_from_name',
|
||||
@@ -243,7 +307,7 @@ class WPDD_Settings {
|
||||
'wpdd_from_email',
|
||||
__('From Email', 'wp-digital-download'),
|
||||
array(__CLASS__, 'email_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_from_email',
|
||||
@@ -255,7 +319,7 @@ class WPDD_Settings {
|
||||
'wpdd_smtp_enabled',
|
||||
__('Enable SMTP', 'wp-digital-download'),
|
||||
array(__CLASS__, 'checkbox_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_smtp_enabled',
|
||||
@@ -267,7 +331,7 @@ class WPDD_Settings {
|
||||
'wpdd_smtp_host',
|
||||
__('SMTP Host', 'wp-digital-download'),
|
||||
array(__CLASS__, 'text_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_smtp_host',
|
||||
@@ -279,7 +343,7 @@ class WPDD_Settings {
|
||||
'wpdd_smtp_port',
|
||||
__('SMTP Port', 'wp-digital-download'),
|
||||
array(__CLASS__, 'number_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_smtp_port',
|
||||
@@ -293,7 +357,7 @@ class WPDD_Settings {
|
||||
'wpdd_smtp_encryption',
|
||||
__('SMTP Encryption', 'wp-digital-download'),
|
||||
array(__CLASS__, 'select_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_smtp_encryption',
|
||||
@@ -310,7 +374,7 @@ class WPDD_Settings {
|
||||
'wpdd_smtp_autodetect',
|
||||
__('Auto-Detect Settings', 'wp-digital-download'),
|
||||
array(__CLASS__, 'smtp_autodetect_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array()
|
||||
);
|
||||
@@ -319,7 +383,7 @@ class WPDD_Settings {
|
||||
'wpdd_smtp_username',
|
||||
__('SMTP Username', 'wp-digital-download'),
|
||||
array(__CLASS__, 'text_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_smtp_username',
|
||||
@@ -331,7 +395,7 @@ class WPDD_Settings {
|
||||
'wpdd_smtp_password',
|
||||
__('SMTP Password', 'wp-digital-download'),
|
||||
array(__CLASS__, 'password_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_email_settings',
|
||||
'wpdd_email_settings',
|
||||
array(
|
||||
'name' => 'wpdd_smtp_password',
|
||||
@@ -345,7 +409,7 @@ class WPDD_Settings {
|
||||
'wpdd_default_download_limit',
|
||||
__('Default Download Limit', 'wp-digital-download'),
|
||||
array(__CLASS__, 'number_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_download_settings',
|
||||
'wpdd_download_settings',
|
||||
array(
|
||||
'name' => 'wpdd_default_download_limit',
|
||||
@@ -358,7 +422,7 @@ class WPDD_Settings {
|
||||
'wpdd_default_download_expiry',
|
||||
__('Default Download Expiry (days)', 'wp-digital-download'),
|
||||
array(__CLASS__, 'number_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_download_settings',
|
||||
'wpdd_download_settings',
|
||||
array(
|
||||
'name' => 'wpdd_default_download_expiry',
|
||||
@@ -371,7 +435,7 @@ class WPDD_Settings {
|
||||
'wpdd_file_access_method',
|
||||
__('File Access Method', 'wp-digital-download'),
|
||||
array(__CLASS__, 'select_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_download_settings',
|
||||
'wpdd_download_settings',
|
||||
array(
|
||||
'name' => 'wpdd_file_access_method',
|
||||
@@ -390,7 +454,7 @@ class WPDD_Settings {
|
||||
'wpdd_enable_watermark',
|
||||
__('Enable Watermarking', 'wp-digital-download'),
|
||||
array(__CLASS__, 'checkbox_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_watermark_settings',
|
||||
'wpdd_watermark_settings',
|
||||
array(
|
||||
'name' => 'wpdd_enable_watermark',
|
||||
@@ -402,7 +466,7 @@ class WPDD_Settings {
|
||||
'wpdd_watermark_text',
|
||||
__('Default Watermark Text', 'wp-digital-download'),
|
||||
array(__CLASS__, 'text_field'),
|
||||
'wpdd_settings',
|
||||
'wpdd_watermark_settings',
|
||||
'wpdd_watermark_settings',
|
||||
array(
|
||||
'name' => 'wpdd_watermark_text',
|
||||
@@ -434,25 +498,30 @@ class WPDD_Settings {
|
||||
<div class="wpdd-settings-content">
|
||||
<form method="post" action="options.php" class="wpdd-settings-form">
|
||||
<?php
|
||||
settings_fields('wpdd_settings');
|
||||
|
||||
// Use appropriate settings group for each tab
|
||||
switch ($active_tab) {
|
||||
case 'general':
|
||||
settings_fields('wpdd_general_settings');
|
||||
self::render_general_tab();
|
||||
break;
|
||||
case 'paypal':
|
||||
settings_fields('wpdd_paypal_settings');
|
||||
self::render_paypal_tab();
|
||||
break;
|
||||
case 'email':
|
||||
settings_fields('wpdd_email_settings');
|
||||
self::render_email_tab();
|
||||
break;
|
||||
case 'downloads':
|
||||
settings_fields('wpdd_download_settings');
|
||||
self::render_downloads_tab();
|
||||
break;
|
||||
case 'watermark':
|
||||
settings_fields('wpdd_watermark_settings');
|
||||
self::render_watermark_tab();
|
||||
break;
|
||||
default:
|
||||
settings_fields('wpdd_general_settings');
|
||||
self::render_general_tab();
|
||||
}
|
||||
|
||||
@@ -951,22 +1020,22 @@ class WPDD_Settings {
|
||||
private static function do_settings_sections_for_tab($section_id) {
|
||||
global $wp_settings_sections, $wp_settings_fields;
|
||||
|
||||
if (!isset($wp_settings_sections['wpdd_settings'][$section_id])) {
|
||||
if (!isset($wp_settings_sections[$section_id][$section_id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$section = $wp_settings_sections['wpdd_settings'][$section_id];
|
||||
$section = $wp_settings_sections[$section_id][$section_id];
|
||||
|
||||
if (isset($section['callback']) && $section['callback']) {
|
||||
call_user_func($section['callback'], $section);
|
||||
}
|
||||
|
||||
if (!isset($wp_settings_fields['wpdd_settings'][$section_id])) {
|
||||
if (!isset($wp_settings_fields[$section_id][$section_id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<table class="form-table" role="presentation">';
|
||||
do_settings_fields('wpdd_settings', $section_id);
|
||||
do_settings_fields($section_id, $section_id);
|
||||
echo '</table>';
|
||||
}
|
||||
|
||||
@@ -990,4 +1059,16 @@ class WPDD_Settings {
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function sanitize_holding_days($input) {
|
||||
$value = intval($input);
|
||||
if ($value < 0) {
|
||||
$value = 15;
|
||||
add_settings_error('wpdd_earnings_holding_days', 'invalid_holding_days', __('Holding days cannot be negative. Set to 15 (recommended).', 'wp-digital-download'));
|
||||
} elseif ($value > 365) {
|
||||
$value = 365;
|
||||
add_settings_error('wpdd_earnings_holding_days', 'invalid_holding_days', __('Holding days cannot exceed 365. Set to maximum (365).', 'wp-digital-download'));
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
781
admin/class-wpdd-transaction-history.php
Normal file
781
admin/class-wpdd-transaction-history.php
Normal file
@@ -0,0 +1,781 @@
|
||||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WPDD_Transaction_History {
|
||||
|
||||
public static function init() {
|
||||
add_action('admin_menu', array(__CLASS__, 'add_menu_page'));
|
||||
add_action('admin_post_wpdd_export_transactions', array(__CLASS__, 'handle_export'));
|
||||
add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_scripts'));
|
||||
}
|
||||
|
||||
public static function add_menu_page() {
|
||||
// Add for admins under Digital Products
|
||||
if (current_user_can('manage_options')) {
|
||||
add_submenu_page(
|
||||
'edit.php?post_type=wpdd_product',
|
||||
__('Transaction History', 'wp-digital-download'),
|
||||
__('Transactions', 'wp-digital-download'),
|
||||
'manage_options',
|
||||
'wpdd-transactions',
|
||||
array(__CLASS__, 'render_admin_page')
|
||||
);
|
||||
}
|
||||
|
||||
// Add for creators (they can only see their own)
|
||||
if (current_user_can('wpdd_creator') && !current_user_can('manage_options')) {
|
||||
add_menu_page(
|
||||
__('My Transactions', 'wp-digital-download'),
|
||||
__('My Transactions', 'wp-digital-download'),
|
||||
'wpdd_creator',
|
||||
'wpdd-my-transactions',
|
||||
array(__CLASS__, 'render_creator_page'),
|
||||
'dashicons-money-alt',
|
||||
30
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function enqueue_scripts($hook) {
|
||||
if (!in_array($hook, array('wpdd_product_page_wpdd-transactions', 'toplevel_page_wpdd-my-transactions'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script('jquery-ui-datepicker');
|
||||
wp_enqueue_style('jquery-ui', 'https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css');
|
||||
}
|
||||
|
||||
public static function render_admin_page() {
|
||||
global $wpdb;
|
||||
|
||||
// Get filter parameters
|
||||
$creator_id = isset($_GET['creator_id']) ? intval($_GET['creator_id']) : 0;
|
||||
$date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : date('Y-m-d', strtotime('-6 months'));
|
||||
$date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
|
||||
$type_filter = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : 'all';
|
||||
|
||||
// Get all creators for dropdown
|
||||
$creators = get_users(array('role' => 'wpdd_creator'));
|
||||
|
||||
// If creator is selected, show their transactions
|
||||
if ($creator_id) {
|
||||
self::render_transactions($creator_id, $date_from, $date_to, $type_filter, true);
|
||||
} else {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php _e('Transaction History', 'wp-digital-download'); ?></h1>
|
||||
|
||||
<div class="wpdd-filter-box" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
||||
<form method="get" action="">
|
||||
<input type="hidden" name="post_type" value="wpdd_product">
|
||||
<input type="hidden" name="page" value="wpdd-transactions">
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th><label for="creator_id"><?php _e('Select Creator', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<select name="creator_id" id="creator_id" required>
|
||||
<option value=""><?php _e('-- Select Creator --', 'wp-digital-download'); ?></option>
|
||||
<?php foreach ($creators as $creator) :
|
||||
$balance = WPDD_Creator::get_creator_balance($creator->ID);
|
||||
$total_earnings = WPDD_Creator::get_creator_total_earnings($creator->ID);
|
||||
?>
|
||||
<option value="<?php echo esc_attr($creator->ID); ?>" <?php selected($creator_id, $creator->ID); ?>>
|
||||
<?php echo esc_html($creator->display_name); ?>
|
||||
(<?php echo esc_html($creator->user_email); ?>) -
|
||||
<?php printf(__('Balance: %s, Total: %s', 'wp-digital-download'),
|
||||
wpdd_format_price($balance),
|
||||
wpdd_format_price($total_earnings)); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="date_from"><?php _e('Date Range', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<input type="date" name="date_from" id="date_from" value="<?php echo esc_attr($date_from); ?>">
|
||||
<?php _e('to', 'wp-digital-download'); ?>
|
||||
<input type="date" name="date_to" id="date_to" value="<?php echo esc_attr($date_to); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="type"><?php _e('Transaction Type', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<select name="type" id="type">
|
||||
<option value="all"><?php _e('All Transactions', 'wp-digital-download'); ?></option>
|
||||
<option value="earnings" <?php selected($type_filter, 'earnings'); ?>><?php _e('Earnings Only', 'wp-digital-download'); ?></option>
|
||||
<option value="payouts" <?php selected($type_filter, 'payouts'); ?>><?php _e('Payouts Only', 'wp-digital-download'); ?></option>
|
||||
<option value="adjustments" <?php selected($type_filter, 'adjustments'); ?>><?php _e('Adjustments Only', 'wp-digital-download'); ?></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="submit">
|
||||
<input type="submit" class="button button-primary" value="<?php _e('View Transactions', 'wp-digital-download'); ?>">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
public static function render_creator_page() {
|
||||
$creator_id = get_current_user_id();
|
||||
$date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : date('Y-m-d', strtotime('-6 months'));
|
||||
$date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
|
||||
$type_filter = isset($_GET['type']) ? sanitize_text_field($_GET['type']) : 'all';
|
||||
|
||||
self::render_transactions($creator_id, $date_from, $date_to, $type_filter, false);
|
||||
}
|
||||
|
||||
private static function render_transactions($creator_id, $date_from, $date_to, $type_filter, $is_admin) {
|
||||
global $wpdb;
|
||||
|
||||
$creator = get_userdata($creator_id);
|
||||
if (!$creator) {
|
||||
echo '<div class="notice notice-error"><p>' . __('Creator not found.', 'wp-digital-download') . '</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch all transactions for this creator
|
||||
$transactions = array();
|
||||
|
||||
// Get earnings (sales)
|
||||
if ($type_filter === 'all' || $type_filter === 'earnings') {
|
||||
$earnings = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT e.*, o.order_number, o.customer_name, o.customer_email, p.post_title as product_name
|
||||
FROM {$wpdb->prefix}wpdd_creator_earnings e
|
||||
LEFT JOIN {$wpdb->prefix}wpdd_orders o ON e.order_id = o.id
|
||||
LEFT JOIN {$wpdb->posts} p ON e.product_id = p.ID
|
||||
WHERE e.creator_id = %d
|
||||
AND DATE(e.created_at) BETWEEN %s AND %s
|
||||
ORDER BY e.created_at DESC",
|
||||
$creator_id, $date_from, $date_to
|
||||
));
|
||||
|
||||
foreach ($earnings as $earning) {
|
||||
$transactions[] = array(
|
||||
'date' => $earning->created_at,
|
||||
'type' => 'earning',
|
||||
'description' => sprintf(__('Sale: %s to %s', 'wp-digital-download'),
|
||||
$earning->product_name, $earning->customer_name),
|
||||
'order_number' => $earning->order_number,
|
||||
'gross_amount' => $earning->sale_amount,
|
||||
'commission' => $earning->sale_amount - $earning->creator_earning,
|
||||
'net_amount' => $earning->creator_earning,
|
||||
'balance_change' => '+' . $earning->creator_earning,
|
||||
'status' => $earning->payout_status,
|
||||
'payout_id' => $earning->payout_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get payouts
|
||||
if ($type_filter === 'all' || $type_filter === 'payouts') {
|
||||
$payouts = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}wpdd_payouts
|
||||
WHERE creator_id = %d
|
||||
AND DATE(created_at) BETWEEN %s AND %s
|
||||
ORDER BY created_at DESC",
|
||||
$creator_id, $date_from, $date_to
|
||||
));
|
||||
|
||||
foreach ($payouts as $payout) {
|
||||
$transactions[] = array(
|
||||
'date' => $payout->created_at,
|
||||
'type' => 'payout',
|
||||
'description' => sprintf(__('Payout to %s', 'wp-digital-download'), $payout->paypal_email),
|
||||
'order_number' => $payout->transaction_id ?: 'N/A',
|
||||
'gross_amount' => 0,
|
||||
'commission' => 0,
|
||||
'net_amount' => -$payout->amount,
|
||||
'balance_change' => '-' . $payout->amount,
|
||||
'status' => $payout->status,
|
||||
'payout_id' => $payout->id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get adjustments
|
||||
if ($type_filter === 'all' || $type_filter === 'adjustments') {
|
||||
$adjustments = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT a.*, u.display_name as adjusted_by_name
|
||||
FROM {$wpdb->prefix}wpdd_balance_adjustments a
|
||||
LEFT JOIN {$wpdb->users} u ON a.adjusted_by = u.ID
|
||||
WHERE a.creator_id = %d
|
||||
AND DATE(a.created_at) BETWEEN %s AND %s
|
||||
ORDER BY a.created_at DESC",
|
||||
$creator_id, $date_from, $date_to
|
||||
));
|
||||
|
||||
foreach ($adjustments as $adjustment) {
|
||||
$amount_change = $adjustment->adjustment_type === 'add' ? $adjustment->amount : -$adjustment->amount;
|
||||
$transactions[] = array(
|
||||
'date' => $adjustment->created_at,
|
||||
'type' => 'adjustment',
|
||||
'description' => sprintf(__('Manual adjustment: %s (by %s)', 'wp-digital-download'),
|
||||
$adjustment->reason, $adjustment->adjusted_by_name),
|
||||
'order_number' => 'ADJ-' . $adjustment->id,
|
||||
'gross_amount' => 0,
|
||||
'commission' => 0,
|
||||
'net_amount' => $amount_change,
|
||||
'balance_change' => ($amount_change >= 0 ? '+' : '') . $amount_change,
|
||||
'status' => 'completed',
|
||||
'payout_id' => null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort transactions by date
|
||||
usort($transactions, function($a, $b) {
|
||||
return strtotime($b['date']) - strtotime($a['date']);
|
||||
});
|
||||
|
||||
// Calculate running balance
|
||||
$current_balance = WPDD_Creator::get_creator_balance($creator_id);
|
||||
$running_balance = $current_balance;
|
||||
|
||||
// Calculate balance by going backwards from current
|
||||
for ($i = 0; $i < count($transactions); $i++) {
|
||||
$transactions[$i]['running_balance'] = $running_balance;
|
||||
if ($i < count($transactions) - 1) {
|
||||
$running_balance -= floatval(str_replace('+', '', $transactions[$i]['balance_change']));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>
|
||||
<?php
|
||||
if ($is_admin) {
|
||||
printf(__('Transaction History for %s', 'wp-digital-download'), $creator->display_name);
|
||||
} else {
|
||||
_e('My Transaction History', 'wp-digital-download');
|
||||
}
|
||||
?>
|
||||
</h1>
|
||||
|
||||
<div class="wpdd-summary" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
||||
<h3><?php _e('Summary', 'wp-digital-download'); ?></h3>
|
||||
<?php
|
||||
$total_earnings = array_sum(array_column(array_filter($transactions, function($t) {
|
||||
return $t['type'] === 'earning';
|
||||
}), 'net_amount'));
|
||||
$total_payouts = abs(array_sum(array_column(array_filter($transactions, function($t) {
|
||||
return $t['type'] === 'payout';
|
||||
}), 'net_amount')));
|
||||
$total_adjustments = array_sum(array_column(array_filter($transactions, function($t) {
|
||||
return $t['type'] === 'adjustment';
|
||||
}), 'net_amount'));
|
||||
?>
|
||||
<table class="widefat">
|
||||
<tr>
|
||||
<td><strong><?php _e('Period:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><?php echo esc_html($date_from); ?> to <?php echo esc_html($date_to); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php _e('Total Earnings:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><?php echo wpdd_format_price($total_earnings); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php _e('Total Payouts:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><?php echo wpdd_format_price($total_payouts); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php _e('Total Adjustments:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><?php echo wpdd_format_price($total_adjustments); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><strong><?php echo wpdd_format_price($current_balance); ?></strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Filter Form -->
|
||||
<div class="wpdd-filter-box" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
||||
<form method="get" action="">
|
||||
<?php if ($is_admin) : ?>
|
||||
<input type="hidden" name="post_type" value="wpdd_product">
|
||||
<input type="hidden" name="page" value="wpdd-transactions">
|
||||
<input type="hidden" name="creator_id" value="<?php echo esc_attr($creator_id); ?>">
|
||||
<?php else : ?>
|
||||
<input type="hidden" name="page" value="wpdd-my-transactions">
|
||||
<?php endif; ?>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th><label for="date_from"><?php _e('Date Range', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<input type="date" name="date_from" id="date_from" value="<?php echo esc_attr($date_from); ?>">
|
||||
<?php _e('to', 'wp-digital-download'); ?>
|
||||
<input type="date" name="date_to" id="date_to" value="<?php echo esc_attr($date_to); ?>">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="type"><?php _e('Transaction Type', 'wp-digital-download'); ?></label></th>
|
||||
<td>
|
||||
<select name="type" id="type">
|
||||
<option value="all"><?php _e('All Transactions', 'wp-digital-download'); ?></option>
|
||||
<option value="earnings" <?php selected($type_filter, 'earnings'); ?>><?php _e('Earnings Only', 'wp-digital-download'); ?></option>
|
||||
<option value="payouts" <?php selected($type_filter, 'payouts'); ?>><?php _e('Payouts Only', 'wp-digital-download'); ?></option>
|
||||
<option value="adjustments" <?php selected($type_filter, 'adjustments'); ?>><?php _e('Adjustments Only', 'wp-digital-download'); ?></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="submit">
|
||||
<input type="submit" class="button button-primary" value="<?php _e('Filter', 'wp-digital-download'); ?>">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<!-- Export Forms -->
|
||||
<div style="margin-top: 10px;">
|
||||
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline-block; margin-right: 10px;">
|
||||
<input type="hidden" name="action" value="wpdd_export_transactions">
|
||||
<input type="hidden" name="creator_id" value="<?php echo esc_attr($creator_id); ?>">
|
||||
<input type="hidden" name="date_from" value="<?php echo esc_attr($date_from); ?>">
|
||||
<input type="hidden" name="date_to" value="<?php echo esc_attr($date_to); ?>">
|
||||
<input type="hidden" name="type" value="<?php echo esc_attr($type_filter); ?>">
|
||||
<input type="hidden" name="format" value="csv">
|
||||
<?php wp_nonce_field('wpdd_export_transactions', 'wpdd_nonce'); ?>
|
||||
<button type="submit" class="button">
|
||||
<span class="dashicons dashicons-media-spreadsheet"></span> <?php _e('Export CSV', 'wp-digital-download'); ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline-block;" target="_blank">
|
||||
<input type="hidden" name="action" value="wpdd_export_transactions">
|
||||
<input type="hidden" name="creator_id" value="<?php echo esc_attr($creator_id); ?>">
|
||||
<input type="hidden" name="date_from" value="<?php echo esc_attr($date_from); ?>">
|
||||
<input type="hidden" name="date_to" value="<?php echo esc_attr($date_to); ?>">
|
||||
<input type="hidden" name="type" value="<?php echo esc_attr($type_filter); ?>">
|
||||
<input type="hidden" name="format" value="pdf">
|
||||
<?php wp_nonce_field('wpdd_export_transactions', 'wpdd_nonce'); ?>
|
||||
<button type="submit" class="button">
|
||||
<span class="dashicons dashicons-pdf"></span> <?php _e('Export PDF', 'wp-digital-download'); ?>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transactions Table -->
|
||||
<div class="wpdd-transactions-table" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
|
||||
<h3><?php _e('Transactions', 'wp-digital-download'); ?> (<?php echo count($transactions); ?>)</h3>
|
||||
|
||||
<?php if (empty($transactions)) : ?>
|
||||
<p><?php _e('No transactions found for the selected period.', 'wp-digital-download'); ?></p>
|
||||
<?php else : ?>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Date', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Type', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Description', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Order/Ref', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Gross', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Commission', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Net', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Balance', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Status', 'wp-digital-download'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($transactions as $transaction) : ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($transaction['date']))); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$type_badge = '';
|
||||
switch($transaction['type']) {
|
||||
case 'earning':
|
||||
$type_badge = '<span class="dashicons dashicons-cart" style="color: #28a745;"></span> ' . __('Sale', 'wp-digital-download');
|
||||
break;
|
||||
case 'payout':
|
||||
$type_badge = '<span class="dashicons dashicons-money-alt" style="color: #007cba;"></span> ' . __('Payout', 'wp-digital-download');
|
||||
break;
|
||||
case 'adjustment':
|
||||
$type_badge = '<span class="dashicons dashicons-admin-tools" style="color: #ffc107;"></span> ' . __('Adjustment', 'wp-digital-download');
|
||||
break;
|
||||
}
|
||||
echo $type_badge;
|
||||
?>
|
||||
</td>
|
||||
<td><?php echo esc_html($transaction['description']); ?></td>
|
||||
<td><?php echo esc_html($transaction['order_number']); ?></td>
|
||||
<td><?php echo $transaction['gross_amount'] > 0 ? wpdd_format_price($transaction['gross_amount']) : '-'; ?></td>
|
||||
<td><?php echo $transaction['commission'] > 0 ? wpdd_format_price($transaction['commission']) : '-'; ?></td>
|
||||
<td>
|
||||
<strong style="color: <?php echo $transaction['net_amount'] >= 0 ? '#28a745' : '#dc3545'; ?>">
|
||||
<?php echo ($transaction['net_amount'] >= 0 ? '+' : '') . wpdd_format_price(abs($transaction['net_amount'])); ?>
|
||||
</strong>
|
||||
</td>
|
||||
<td><strong><?php echo wpdd_format_price($transaction['running_balance']); ?></strong></td>
|
||||
<td>
|
||||
<?php
|
||||
$status_class = '';
|
||||
$status_text = ucfirst($transaction['status']);
|
||||
switch($transaction['status']) {
|
||||
case 'paid':
|
||||
$status_class = 'notice-success';
|
||||
$status_text = __('Paid', 'wp-digital-download');
|
||||
if ($transaction['payout_id']) {
|
||||
$status_text .= ' (#' . $transaction['payout_id'] . ')';
|
||||
}
|
||||
break;
|
||||
case 'unpaid':
|
||||
$status_class = 'notice-warning';
|
||||
$status_text = __('Unpaid', 'wp-digital-download');
|
||||
break;
|
||||
case 'completed':
|
||||
$status_class = 'notice-success';
|
||||
$status_text = __('Completed', 'wp-digital-download');
|
||||
break;
|
||||
case 'pending':
|
||||
$status_class = 'notice-warning';
|
||||
$status_text = __('Pending', 'wp-digital-download');
|
||||
break;
|
||||
case 'failed':
|
||||
$status_class = 'notice-error';
|
||||
$status_text = __('Failed', 'wp-digital-download');
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<span class="notice <?php echo esc_attr($status_class); ?>" style="padding: 2px 8px; display: inline-block;">
|
||||
<?php echo esc_html($status_text); ?>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function handle_export() {
|
||||
// Verify nonce
|
||||
if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_export_transactions')) {
|
||||
wp_die(__('Security check failed', 'wp-digital-download'));
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
$creator_id = intval($_POST['creator_id']);
|
||||
if (!current_user_can('manage_options') && get_current_user_id() !== $creator_id) {
|
||||
wp_die(__('Permission denied', 'wp-digital-download'));
|
||||
}
|
||||
|
||||
$date_from = sanitize_text_field($_POST['date_from']);
|
||||
$date_to = sanitize_text_field($_POST['date_to']);
|
||||
$type_filter = sanitize_text_field($_POST['type']);
|
||||
$format = sanitize_text_field($_POST['format']);
|
||||
|
||||
// Get transactions data (same logic as render_transactions but just data)
|
||||
$transactions = self::get_transactions_data($creator_id, $date_from, $date_to, $type_filter);
|
||||
|
||||
self::handle_export_request($creator_id, $transactions, $date_from, $date_to, $format);
|
||||
}
|
||||
|
||||
private static function get_transactions_data($creator_id, $date_from, $date_to, $type_filter) {
|
||||
global $wpdb;
|
||||
|
||||
$transactions = array();
|
||||
|
||||
// Get earnings (sales)
|
||||
if ($type_filter === 'all' || $type_filter === 'earnings') {
|
||||
$earnings = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT e.*, o.order_number, o.customer_name, o.customer_email, p.post_title as product_name
|
||||
FROM {$wpdb->prefix}wpdd_creator_earnings e
|
||||
LEFT JOIN {$wpdb->prefix}wpdd_orders o ON e.order_id = o.id
|
||||
LEFT JOIN {$wpdb->posts} p ON e.product_id = p.ID
|
||||
WHERE e.creator_id = %d
|
||||
AND DATE(e.created_at) BETWEEN %s AND %s
|
||||
ORDER BY e.created_at DESC",
|
||||
$creator_id, $date_from, $date_to
|
||||
));
|
||||
|
||||
foreach ($earnings as $earning) {
|
||||
$transactions[] = array(
|
||||
'date' => $earning->created_at,
|
||||
'type' => 'earning',
|
||||
'description' => sprintf(__('Sale: %s to %s', 'wp-digital-download'),
|
||||
$earning->product_name, $earning->customer_name),
|
||||
'order_number' => $earning->order_number,
|
||||
'gross_amount' => $earning->sale_amount,
|
||||
'commission' => $earning->sale_amount - $earning->creator_earning,
|
||||
'net_amount' => $earning->creator_earning,
|
||||
'balance_change' => '+' . $earning->creator_earning,
|
||||
'status' => $earning->payout_status,
|
||||
'payout_id' => $earning->payout_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get payouts
|
||||
if ($type_filter === 'all' || $type_filter === 'payouts') {
|
||||
$payouts = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}wpdd_payouts
|
||||
WHERE creator_id = %d
|
||||
AND DATE(created_at) BETWEEN %s AND %s
|
||||
ORDER BY created_at DESC",
|
||||
$creator_id, $date_from, $date_to
|
||||
));
|
||||
|
||||
foreach ($payouts as $payout) {
|
||||
$transactions[] = array(
|
||||
'date' => $payout->created_at,
|
||||
'type' => 'payout',
|
||||
'description' => sprintf(__('Payout to %s', 'wp-digital-download'), $payout->paypal_email),
|
||||
'order_number' => $payout->transaction_id ?: 'N/A',
|
||||
'gross_amount' => 0,
|
||||
'commission' => 0,
|
||||
'net_amount' => -$payout->amount,
|
||||
'balance_change' => '-' . $payout->amount,
|
||||
'status' => $payout->status,
|
||||
'payout_id' => $payout->id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get adjustments
|
||||
if ($type_filter === 'all' || $type_filter === 'adjustments') {
|
||||
$adjustments = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT a.*, u.display_name as adjusted_by_name
|
||||
FROM {$wpdb->prefix}wpdd_balance_adjustments a
|
||||
LEFT JOIN {$wpdb->users} u ON a.adjusted_by = u.ID
|
||||
WHERE a.creator_id = %d
|
||||
AND DATE(a.created_at) BETWEEN %s AND %s
|
||||
ORDER BY a.created_at DESC",
|
||||
$creator_id, $date_from, $date_to
|
||||
));
|
||||
|
||||
foreach ($adjustments as $adjustment) {
|
||||
$amount_change = $adjustment->adjustment_type === 'add' ? $adjustment->amount : -$adjustment->amount;
|
||||
$transactions[] = array(
|
||||
'date' => $adjustment->created_at,
|
||||
'type' => 'adjustment',
|
||||
'description' => sprintf(__('Manual adjustment: %s (by %s)', 'wp-digital-download'),
|
||||
$adjustment->reason, $adjustment->adjusted_by_name),
|
||||
'order_number' => 'ADJ-' . $adjustment->id,
|
||||
'gross_amount' => 0,
|
||||
'commission' => 0,
|
||||
'net_amount' => $amount_change,
|
||||
'balance_change' => ($amount_change >= 0 ? '+' : '') . $amount_change,
|
||||
'status' => 'completed',
|
||||
'payout_id' => null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort transactions by date
|
||||
usort($transactions, function($a, $b) {
|
||||
return strtotime($b['date']) - strtotime($a['date']);
|
||||
});
|
||||
|
||||
// Calculate running balance
|
||||
$current_balance = WPDD_Creator::get_creator_balance($creator_id);
|
||||
$running_balance = $current_balance;
|
||||
|
||||
// Calculate balance by going backwards from current
|
||||
for ($i = 0; $i < count($transactions); $i++) {
|
||||
$transactions[$i]['running_balance'] = $running_balance;
|
||||
if ($i < count($transactions) - 1) {
|
||||
$running_balance -= floatval(str_replace('+', '', $transactions[$i]['balance_change']));
|
||||
}
|
||||
}
|
||||
|
||||
return $transactions;
|
||||
}
|
||||
|
||||
private static function handle_export_request($creator_id, $transactions, $date_from, $date_to, $format) {
|
||||
$creator = get_userdata($creator_id);
|
||||
|
||||
if ($format === 'csv') {
|
||||
self::export_csv($transactions, $creator, $date_from, $date_to);
|
||||
} elseif ($format === 'pdf') {
|
||||
self::export_pdf($transactions, $creator, $date_from, $date_to);
|
||||
}
|
||||
}
|
||||
|
||||
private static function export_csv($transactions, $creator, $date_from, $date_to) {
|
||||
$filename = sprintf('transactions_%s_%s_to_%s.csv',
|
||||
sanitize_title($creator->display_name),
|
||||
$date_from,
|
||||
$date_to
|
||||
);
|
||||
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename=' . $filename);
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
|
||||
// Add BOM for Excel UTF-8 compatibility
|
||||
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||
|
||||
// Header row
|
||||
fputcsv($output, array(
|
||||
'Date',
|
||||
'Type',
|
||||
'Description',
|
||||
'Order/Reference',
|
||||
'Gross Amount',
|
||||
'Commission',
|
||||
'Net Amount',
|
||||
'Running Balance',
|
||||
'Status'
|
||||
));
|
||||
|
||||
// Data rows
|
||||
foreach ($transactions as $transaction) {
|
||||
fputcsv($output, array(
|
||||
date('Y-m-d H:i:s', strtotime($transaction['date'])),
|
||||
ucfirst($transaction['type']),
|
||||
$transaction['description'],
|
||||
$transaction['order_number'],
|
||||
$transaction['gross_amount'] > 0 ? number_format($transaction['gross_amount'], 2) : '',
|
||||
$transaction['commission'] > 0 ? number_format($transaction['commission'], 2) : '',
|
||||
number_format(abs($transaction['net_amount']), 2) . ($transaction['net_amount'] < 0 ? ' (Debit)' : ''),
|
||||
number_format($transaction['running_balance'], 2),
|
||||
ucfirst($transaction['status'])
|
||||
));
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
}
|
||||
|
||||
private static function export_pdf($transactions, $creator, $date_from, $date_to) {
|
||||
// Create a clean PDF-ready HTML document
|
||||
$filename = sprintf('transactions_%s_%s_to_%s.pdf',
|
||||
sanitize_title($creator->display_name),
|
||||
$date_from,
|
||||
$date_to
|
||||
);
|
||||
|
||||
// Set headers for PDF display/download
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
header('Content-Disposition: inline; filename=' . $filename);
|
||||
header('X-Robots-Tag: noindex, nofollow');
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?php printf(__('Transaction History - %s', 'wp-digital-download'), $creator->display_name); ?></title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; font-size: 12px; }
|
||||
h1 { font-size: 18px; }
|
||||
h2 { font-size: 14px; margin-top: 20px; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||
th { background-color: #f2f2f2; font-weight: bold; }
|
||||
.summary { margin: 20px 0; }
|
||||
.summary td { border: none; padding: 5px 0; }
|
||||
.positive { color: green; }
|
||||
.negative { color: red; }
|
||||
@media print {
|
||||
body { font-size: 10px; }
|
||||
h1 { font-size: 14px; }
|
||||
th, td { padding: 4px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><?php printf(__('Transaction History - %s', 'wp-digital-download'), $creator->display_name); ?></h1>
|
||||
<p><?php printf(__('Period: %s to %s', 'wp-digital-download'), $date_from, $date_to); ?></p>
|
||||
|
||||
<div class="summary">
|
||||
<h2><?php _e('Summary', 'wp-digital-download'); ?></h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong><?php _e('Total Earnings:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><?php
|
||||
$total_earnings = array_sum(array_column(array_filter($transactions, function($t) {
|
||||
return $t['type'] === 'earning';
|
||||
}), 'net_amount'));
|
||||
echo wpdd_format_price($total_earnings);
|
||||
?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php _e('Total Payouts:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><?php
|
||||
$total_payouts = abs(array_sum(array_column(array_filter($transactions, function($t) {
|
||||
return $t['type'] === 'payout';
|
||||
}), 'net_amount')));
|
||||
echo wpdd_format_price($total_payouts);
|
||||
?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong></td>
|
||||
<td><strong><?php echo wpdd_format_price(WPDD_Creator::get_creator_balance($creator->ID)); ?></strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2><?php _e('Transaction Details', 'wp-digital-download'); ?></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Date', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Type', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Description', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Order/Ref', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Net Amount', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Balance', 'wp-digital-download'); ?></th>
|
||||
<th><?php _e('Status', 'wp-digital-download'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($transactions as $transaction) : ?>
|
||||
<tr>
|
||||
<td><?php echo date('Y-m-d H:i', strtotime($transaction['date'])); ?></td>
|
||||
<td><?php echo ucfirst($transaction['type']); ?></td>
|
||||
<td><?php echo esc_html($transaction['description']); ?></td>
|
||||
<td><?php echo esc_html($transaction['order_number']); ?></td>
|
||||
<td class="<?php echo $transaction['net_amount'] >= 0 ? 'positive' : 'negative'; ?>">
|
||||
<?php echo ($transaction['net_amount'] >= 0 ? '+' : '') . wpdd_format_price(abs($transaction['net_amount'])); ?>
|
||||
</td>
|
||||
<td><strong><?php echo wpdd_format_price($transaction['running_balance']); ?></strong></td>
|
||||
<td><?php echo ucfirst($transaction['status']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p style="margin-top: 30px; font-size: 10px; color: #666;">
|
||||
<?php printf(__('Generated on %s', 'wp-digital-download'), date_i18n(get_option('date_format') . ' ' . get_option('time_format'))); ?>
|
||||
</p>
|
||||
|
||||
<script>
|
||||
// Auto-trigger print dialog for PDF saving
|
||||
window.onload = function() {
|
||||
// Small delay to ensure page is fully loaded
|
||||
setTimeout(function() {
|
||||
window.print();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Handle after print (close window if opened in new tab)
|
||||
window.onafterprint = function() {
|
||||
// Check if this window was opened by another window
|
||||
if (window.opener) {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
}
|
54
admin/wpdd-db-migrate.php
Normal file
54
admin/wpdd-db-migrate.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Database migration script for WP Digital Download
|
||||
* Adds missing columns and updates database schema
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function wpdd_migrate_database() {
|
||||
global $wpdb;
|
||||
|
||||
// Check if available_at column exists in wpdd_creator_earnings table
|
||||
$column_exists = $wpdb->get_results(
|
||||
"SHOW COLUMNS FROM {$wpdb->prefix}wpdd_creator_earnings LIKE 'available_at'"
|
||||
);
|
||||
|
||||
if (empty($column_exists)) {
|
||||
// Add the missing available_at column
|
||||
$result = $wpdb->query(
|
||||
"ALTER TABLE {$wpdb->prefix}wpdd_creator_earnings
|
||||
ADD COLUMN available_at datetime DEFAULT NULL AFTER payout_status,
|
||||
ADD INDEX available_at (available_at)"
|
||||
);
|
||||
|
||||
if ($result !== false) {
|
||||
error_log('WPDD Migration: Successfully added available_at column to wpdd_creator_earnings table');
|
||||
|
||||
// Update existing pending earnings to have an available_at date
|
||||
$holding_days = intval(get_option('wpdd_earnings_holding_days', 15));
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE {$wpdb->prefix}wpdd_creator_earnings
|
||||
SET available_at = DATE_ADD(created_at, INTERVAL %d DAY)
|
||||
WHERE payout_status = 'pending' AND available_at IS NULL",
|
||||
$holding_days
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
error_log('WPDD Migration Error: Failed to add available_at column - ' . $wpdb->last_error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Column already exists
|
||||
}
|
||||
|
||||
// Run migration if accessed directly (for manual execution)
|
||||
if (basename($_SERVER['SCRIPT_FILENAME']) === 'wpdd-db-migrate.php') {
|
||||
wpdd_migrate_database();
|
||||
}
|
756
admin/wpdd-tools.php
Normal file
756
admin/wpdd-tools.php
Normal file
@@ -0,0 +1,756 @@
|
||||
<?php
|
||||
/**
|
||||
* Database Check and Update Script for WP Digital Download
|
||||
* This script checks and creates missing database tables
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function wpdd_check_and_create_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
$results = array();
|
||||
|
||||
// Check if creator_earnings table exists and has correct structure
|
||||
$table_name = $wpdb->prefix . 'wpdd_creator_earnings';
|
||||
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
|
||||
|
||||
if (!$table_exists) {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
|
||||
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,
|
||||
payout_id bigint(20) DEFAULT NULL,
|
||||
payout_status varchar(20) DEFAULT 'pending',
|
||||
available_at datetime DEFAULT 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),
|
||||
KEY payout_id (payout_id),
|
||||
KEY payout_status (payout_status)
|
||||
) $charset_collate;";
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
dbDelta($sql);
|
||||
$results[] = "Created table: $table_name";
|
||||
} else {
|
||||
// Check if payout_id and payout_status columns exist
|
||||
$columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name");
|
||||
$has_payout_id = false;
|
||||
$has_payout_status = false;
|
||||
|
||||
$has_available_at = false;
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if ($column->Field == 'payout_id') $has_payout_id = true;
|
||||
if ($column->Field == 'payout_status') $has_payout_status = true;
|
||||
if ($column->Field == 'available_at') $has_available_at = true;
|
||||
}
|
||||
|
||||
if (!$has_payout_id) {
|
||||
$wpdb->query("ALTER TABLE $table_name ADD COLUMN payout_id bigint(20) DEFAULT NULL");
|
||||
$wpdb->query("ALTER TABLE $table_name ADD KEY payout_id (payout_id)");
|
||||
$results[] = "Added payout_id column to $table_name";
|
||||
}
|
||||
|
||||
if (!$has_payout_status) {
|
||||
$wpdb->query("ALTER TABLE $table_name ADD COLUMN payout_status varchar(20) DEFAULT 'pending'");
|
||||
$wpdb->query("ALTER TABLE $table_name ADD KEY payout_status (payout_status)");
|
||||
$results[] = "Added payout_status column to $table_name";
|
||||
}
|
||||
|
||||
if (!$has_available_at) {
|
||||
$wpdb->query("ALTER TABLE $table_name ADD COLUMN available_at datetime DEFAULT NULL");
|
||||
$wpdb->query("ALTER TABLE $table_name ADD KEY available_at (available_at)");
|
||||
$results[] = "Added available_at column to $table_name";
|
||||
|
||||
// Update existing pending earnings with available_at dates
|
||||
$holding_days = intval(get_option('wpdd_earnings_holding_days', 15));
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE $table_name
|
||||
SET available_at = DATE_ADD(created_at, INTERVAL %d DAY)
|
||||
WHERE payout_status = 'pending' AND available_at IS NULL",
|
||||
$holding_days
|
||||
)
|
||||
);
|
||||
$results[] = "Updated pending earnings with available_at dates";
|
||||
}
|
||||
|
||||
$results[] = "Table exists: $table_name";
|
||||
}
|
||||
|
||||
// Check if balance_adjustments table exists
|
||||
$table_name = $wpdb->prefix . 'wpdd_balance_adjustments';
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
creator_id bigint(20) NOT NULL,
|
||||
adjustment_type varchar(20) NOT NULL,
|
||||
amount decimal(10,2) NOT NULL,
|
||||
previous_balance decimal(10,2) NOT NULL,
|
||||
new_balance decimal(10,2) NOT NULL,
|
||||
reason text NOT NULL,
|
||||
adjusted_by bigint(20) NOT NULL,
|
||||
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY creator_id (creator_id),
|
||||
KEY adjusted_by (adjusted_by)
|
||||
) $charset_collate;";
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
dbDelta($sql);
|
||||
$results[] = "Created table: $table_name";
|
||||
} else {
|
||||
$results[] = "Table exists: $table_name";
|
||||
}
|
||||
|
||||
// Check existing orders and add missing earnings records
|
||||
$completed_orders = $wpdb->get_results(
|
||||
"SELECT o.*, p.post_author as creator_id
|
||||
FROM {$wpdb->prefix}wpdd_orders o
|
||||
INNER JOIN {$wpdb->posts} p ON o.product_id = p.ID
|
||||
WHERE o.status = 'completed'"
|
||||
);
|
||||
|
||||
$results[] = "Found " . count($completed_orders) . " completed orders";
|
||||
|
||||
// Process each order to ensure earnings are recorded
|
||||
$added_earnings = 0;
|
||||
foreach ($completed_orders as $order) {
|
||||
// Check if earning already recorded
|
||||
$existing = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT id FROM {$wpdb->prefix}wpdd_creator_earnings
|
||||
WHERE order_id = %d",
|
||||
$order->id
|
||||
));
|
||||
|
||||
if (!$existing && $order->amount > 0) {
|
||||
$commission_rate = floatval(get_option('wpdd_commission_rate', 0));
|
||||
$creator_share = $order->amount * (1 - ($commission_rate / 100));
|
||||
|
||||
// Insert earning record
|
||||
$wpdb->insert(
|
||||
$wpdb->prefix . 'wpdd_creator_earnings',
|
||||
array(
|
||||
'creator_id' => $order->creator_id,
|
||||
'order_id' => $order->id,
|
||||
'product_id' => $order->product_id,
|
||||
'sale_amount' => $order->amount,
|
||||
'commission_rate' => $commission_rate,
|
||||
'creator_earning' => $creator_share,
|
||||
'payout_status' => 'pending',
|
||||
'created_at' => $order->purchase_date
|
||||
),
|
||||
array('%d', '%d', '%d', '%f', '%f', '%f', '%s', '%s')
|
||||
);
|
||||
|
||||
$added_earnings++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($added_earnings > 0) {
|
||||
$results[] = "Added $added_earnings missing earning records";
|
||||
}
|
||||
|
||||
// Recalculate all creator balances based on UNPAID earnings only
|
||||
$creators = get_users(array('role' => 'wpdd_creator'));
|
||||
|
||||
foreach ($creators as $creator) {
|
||||
// Calculate available earnings only (not pending or already paid)
|
||||
$available_earnings = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT SUM(creator_earning)
|
||||
FROM {$wpdb->prefix}wpdd_creator_earnings
|
||||
WHERE creator_id = %d
|
||||
AND payout_status = 'available'",
|
||||
$creator->ID
|
||||
));
|
||||
|
||||
// Calculate balance adjustments
|
||||
$total_adjustments = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT SUM(CASE
|
||||
WHEN adjustment_type = 'add' THEN amount
|
||||
WHEN adjustment_type = 'subtract' THEN -amount
|
||||
ELSE 0
|
||||
END)
|
||||
FROM {$wpdb->prefix}wpdd_balance_adjustments
|
||||
WHERE creator_id = %d",
|
||||
$creator->ID
|
||||
));
|
||||
|
||||
$available_earnings = floatval($available_earnings);
|
||||
$total_adjustments = floatval($total_adjustments);
|
||||
|
||||
$balance = $available_earnings + $total_adjustments;
|
||||
|
||||
// Update creator balance
|
||||
update_user_meta($creator->ID, 'wpdd_creator_balance', $balance);
|
||||
|
||||
// Get total earnings for display
|
||||
$total_earnings = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT SUM(creator_earning)
|
||||
FROM {$wpdb->prefix}wpdd_creator_earnings
|
||||
WHERE creator_id = %d",
|
||||
$creator->ID
|
||||
));
|
||||
|
||||
// Get paid out amount
|
||||
$paid_out = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT SUM(creator_earning)
|
||||
FROM {$wpdb->prefix}wpdd_creator_earnings
|
||||
WHERE creator_id = %d
|
||||
AND payout_status = 'paid'",
|
||||
$creator->ID
|
||||
));
|
||||
|
||||
$results[] = sprintf(
|
||||
"Updated balance for %s: Total Earnings: $%.2f, Paid Out: $%.2f, Adjustments: $%.2f, Current Balance: $%.2f",
|
||||
$creator->display_name,
|
||||
floatval($total_earnings),
|
||||
floatval($paid_out),
|
||||
$total_adjustments,
|
||||
$balance
|
||||
);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Add admin page to run this check
|
||||
add_action('admin_menu', function() {
|
||||
add_submenu_page(
|
||||
'edit.php?post_type=wpdd_product',
|
||||
'Tools',
|
||||
'Tools',
|
||||
'manage_options',
|
||||
'wpdd-tools',
|
||||
function() {
|
||||
$active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'system';
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>WP Digital Download - Tools</h1>
|
||||
|
||||
<h2 class="nav-tab-wrapper">
|
||||
<a href="?post_type=wpdd_product&page=wpdd-tools&tab=system"
|
||||
class="nav-tab <?php echo $active_tab == 'system' ? 'nav-tab-active' : ''; ?>">System Tools</a>
|
||||
<a href="?post_type=wpdd_product&page=wpdd-tools&tab=licenses"
|
||||
class="nav-tab <?php echo $active_tab == 'licenses' ? 'nav-tab-active' : ''; ?>">License Tools</a>
|
||||
<a href="?post_type=wpdd_product&page=wpdd-tools&tab=email-test"
|
||||
class="nav-tab <?php echo $active_tab == 'email-test' ? 'nav-tab-active' : ''; ?>">Email Test</a>
|
||||
<a href="?post_type=wpdd_product&page=wpdd-tools&tab=email-logs"
|
||||
class="nav-tab <?php echo $active_tab == 'email-logs' ? 'nav-tab-active' : ''; ?>">Email Logs</a>
|
||||
</h2>
|
||||
|
||||
<?php if ($active_tab == 'system') : ?>
|
||||
<div class="wpdd-tools-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
|
||||
|
||||
<!-- Database Check Tool -->
|
||||
<div class="wpdd-tool-box" style="background: white; border: 1px solid #ccd0d4; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
|
||||
<h2 style="margin-top: 0;">🔧 Database Check & Repair</h2>
|
||||
|
||||
<?php
|
||||
if (isset($_POST['run_check']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_db_check')) {
|
||||
$results = wpdd_check_and_create_tables();
|
||||
echo '<div class="notice notice-success"><p>Database check completed!</p></div>';
|
||||
echo '<h3>Results:</h3>';
|
||||
echo '<ul style="max-height: 200px; overflow-y: auto; background: #f9f9f9; padding: 10px; border: 1px solid #ddd;">';
|
||||
foreach ($results as $result) {
|
||||
echo '<li>' . esc_html($result) . '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
?>
|
||||
|
||||
<p>This tool will:</p>
|
||||
<ul style="font-size: 13px;">
|
||||
<li>Check and create missing database tables</li>
|
||||
<li>Add payout tracking columns if missing</li>
|
||||
<li>Add earning records for completed orders</li>
|
||||
<li>Recalculate creator balances</li>
|
||||
<li>Update pending earnings with proper dates</li>
|
||||
</ul>
|
||||
|
||||
<form method="post">
|
||||
<?php wp_nonce_field('wpdd_db_check'); ?>
|
||||
<p class="submit" style="margin: 10px 0 0 0;">
|
||||
<input type="submit" name="run_check" class="button button-primary" value="Run Database Check">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- System Status Tool -->
|
||||
<div class="wpdd-tool-box" style="background: white; border: 1px solid #ccd0d4; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
|
||||
<h2 style="margin-top: 0;">📊 System Status</h2>
|
||||
<p>Quick overview of your plugin status:</p>
|
||||
|
||||
<?php
|
||||
// PayPal Status
|
||||
$paypal_client_id = get_option('wpdd_paypal_client_id');
|
||||
$paypal_secret = get_option('wpdd_paypal_secret');
|
||||
$paypal_configured = !empty($paypal_client_id) && !empty($paypal_secret);
|
||||
|
||||
echo '<p><strong>PayPal:</strong> <span style="color: ' . ($paypal_configured ? '#46b450' : '#dc3232') . ';">' . ($paypal_configured ? '✓ Configured' : '✗ Not Configured') . '</span></p>';
|
||||
|
||||
if ($paypal_configured) {
|
||||
echo '<p style="font-size: 11px; color: #666;"><strong>Client ID:</strong> ' . substr($paypal_client_id, 0, 10) . '...</p>';
|
||||
}
|
||||
|
||||
// PayPal Mode
|
||||
$paypal_mode = get_option('wpdd_paypal_mode', 'sandbox');
|
||||
echo '<p><strong>PayPal Mode:</strong> ' . ucfirst(esc_html($paypal_mode)) . '</p>';
|
||||
|
||||
// Currency
|
||||
$currency = get_option('wpdd_currency', 'USD');
|
||||
echo '<p><strong>Currency:</strong> ' . esc_html($currency) . '</p>';
|
||||
|
||||
// Commission Rate
|
||||
$commission = get_option('wpdd_commission_rate', 0);
|
||||
echo '<p><strong>Platform Commission:</strong> ' . floatval($commission) . '%</p>';
|
||||
|
||||
// Holding Period
|
||||
$holding = get_option('wpdd_earnings_holding_days', 15);
|
||||
echo '<p><strong>Earnings Hold:</strong> ' . intval($holding) . ' days</p>';
|
||||
|
||||
// Product Count
|
||||
$products = wp_count_posts('wpdd_product');
|
||||
echo '<p><strong>Products:</strong> ' . intval($products->publish) . ' published</p>';
|
||||
|
||||
// Creator Count
|
||||
$creators = count(get_users(array('role' => 'wpdd_creator')));
|
||||
echo '<p><strong>Creators:</strong> ' . $creators . '</p>';
|
||||
?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Settings Debug Section -->
|
||||
<div style="margin-top: 20px; padding: 15px; background: #f9f9f9; border: 1px solid #ddd;">
|
||||
<h3>🔍 Settings Debug Info</h3>
|
||||
<p style="font-size: 12px;">If settings are resetting, check these values:</p>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; font-family: monospace; font-size: 11px;">
|
||||
<?php
|
||||
$debug_settings = array(
|
||||
'wpdd_paypal_mode',
|
||||
'wpdd_paypal_client_id',
|
||||
'wpdd_currency',
|
||||
'wpdd_commission_rate',
|
||||
'wpdd_payout_threshold',
|
||||
'wpdd_earnings_holding_days'
|
||||
);
|
||||
|
||||
foreach ($debug_settings as $setting) {
|
||||
$value = get_option($setting);
|
||||
$display_value = $setting === 'wpdd_paypal_client_id' && !empty($value) ? substr($value, 0, 8) . '...' : $value;
|
||||
echo '<div><strong>' . esc_html($setting) . ':</strong><br>' . esc_html($display_value ?: '(empty)') . '</div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php elseif ($active_tab == 'licenses') : ?>
|
||||
<!-- License Tools Tab -->
|
||||
<div style="background: white; padding: 20px; margin-top: 20px; border: 1px solid #ccd0d4;">
|
||||
<h2>🔑 License Tools</h2>
|
||||
<p>Manage and regenerate license keys for completed orders.</p>
|
||||
|
||||
<?php
|
||||
global $wpdb;
|
||||
|
||||
// Load license manager if not already loaded
|
||||
if (!class_exists('WPDD_License_Manager')) {
|
||||
require_once(WPDD_PLUGIN_PATH . 'includes/class-wpdd-license-manager.php');
|
||||
}
|
||||
|
||||
// Handle license generation
|
||||
if (isset($_POST['generate_license']) && isset($_POST['order_id']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_generate_license')) {
|
||||
$order_id = intval($_POST['order_id']);
|
||||
$result = WPDD_License_Manager::generate_license_for_order($order_id);
|
||||
|
||||
if ($result) {
|
||||
echo '<div class="notice notice-success"><p>✅ License generated successfully for order #' . $order_id . ': <code>' . esc_html($result) . '</code></p></div>';
|
||||
} else {
|
||||
echo '<div class="notice notice-warning"><p>⚠️ License generation completed for order #' . $order_id . '. Check error logs if no license was created.</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle bulk license generation
|
||||
if (isset($_POST['generate_all_missing']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_generate_all_licenses')) {
|
||||
$orders_without_licenses = $wpdb->get_results("
|
||||
SELECT o.id
|
||||
FROM {$wpdb->prefix}wpdd_orders o
|
||||
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
|
||||
LEFT JOIN {$wpdb->prefix}wpdd_licenses l ON o.id = l.order_id
|
||||
WHERE o.status = 'completed'
|
||||
AND l.id IS NULL
|
||||
AND p.post_type = 'wpdd_product'
|
||||
");
|
||||
|
||||
$generated = 0;
|
||||
foreach ($orders_without_licenses as $order) {
|
||||
$result = WPDD_License_Manager::generate_license_for_order($order->id);
|
||||
if ($result) {
|
||||
$generated++;
|
||||
}
|
||||
}
|
||||
|
||||
echo '<div class="notice notice-success"><p>✅ Generated ' . $generated . ' license keys out of ' . count($orders_without_licenses) . ' eligible orders.</p></div>';
|
||||
}
|
||||
|
||||
// Get orders without license keys for software products
|
||||
$orders_without_licenses = $wpdb->get_results("
|
||||
SELECT o.*, p.post_title as product_name,
|
||||
pm_type.meta_value as product_type,
|
||||
pm_git.meta_value as git_repository
|
||||
FROM {$wpdb->prefix}wpdd_orders o
|
||||
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
|
||||
LEFT JOIN {$wpdb->prefix}wpdd_licenses l ON o.id = l.order_id
|
||||
LEFT JOIN {$wpdb->postmeta} pm_type ON (p.ID = pm_type.post_id AND pm_type.meta_key = '_wpdd_product_type')
|
||||
LEFT JOIN {$wpdb->postmeta} pm_git ON (p.ID = pm_git.post_id AND pm_git.meta_key = '_wpdd_git_repository')
|
||||
WHERE o.status = 'completed'
|
||||
AND l.id IS NULL
|
||||
AND p.post_type = 'wpdd_product'
|
||||
AND (pm_type.meta_value = 'software_license' OR pm_git.meta_value IS NOT NULL)
|
||||
ORDER BY o.id DESC
|
||||
");
|
||||
?>
|
||||
|
||||
<?php if (!empty($orders_without_licenses)): ?>
|
||||
<div style="margin-bottom: 20px;">
|
||||
<form method="post" style="display: inline;">
|
||||
<?php wp_nonce_field('wpdd_generate_all_licenses'); ?>
|
||||
<input type="submit" name="generate_all_missing" class="button button-secondary"
|
||||
value="Generate All Missing Licenses (<?php echo count($orders_without_licenses); ?> orders)"
|
||||
onclick="return confirm('Generate license keys for all <?php echo count($orders_without_licenses); ?> orders? This action cannot be undone.');">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h3>Orders Missing License Keys</h3>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 80px;">Order ID</th>
|
||||
<th style="width: 140px;">Order Number</th>
|
||||
<th>Product</th>
|
||||
<th style="width: 100px;">Product Type</th>
|
||||
<th style="width: 200px;">Customer Email</th>
|
||||
<th style="width: 100px;">Date</th>
|
||||
<th style="width: 100px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($orders_without_licenses as $order): ?>
|
||||
<tr>
|
||||
<td><?php echo $order->id; ?></td>
|
||||
<td><?php echo esc_html($order->order_number); ?></td>
|
||||
<td>
|
||||
<strong><?php echo esc_html($order->product_name); ?></strong>
|
||||
<?php if (!empty($order->git_repository)): ?>
|
||||
<br><small style="color: #666;">Git: <?php echo esc_html($order->git_repository); ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html($order->product_type ?: 'digital_download'); ?></td>
|
||||
<td><?php echo esc_html($order->customer_email); ?></td>
|
||||
<td><?php echo date('Y-m-d', strtotime($order->purchase_date)); ?></td>
|
||||
<td>
|
||||
<form method="post" style="display: inline;">
|
||||
<?php wp_nonce_field('wpdd_generate_license'); ?>
|
||||
<input type="hidden" name="order_id" value="<?php echo $order->id; ?>">
|
||||
<input type="submit" name="generate_license" class="button button-primary button-small"
|
||||
value="Generate"
|
||||
onclick="return confirm('Generate license for order #<?php echo $order->id; ?>?');">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<div class="notice notice-info">
|
||||
<p>✅ All eligible software orders have license keys assigned.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr style="margin: 30px 0;">
|
||||
|
||||
<h3>🔍 License Statistics</h3>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;">
|
||||
<?php
|
||||
// Get license statistics
|
||||
$total_licenses = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_licenses");
|
||||
$active_licenses = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_licenses WHERE status = 'active'");
|
||||
$expired_licenses = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_licenses WHERE expires_at IS NOT NULL AND expires_at < NOW()");
|
||||
|
||||
$software_orders = $wpdb->get_var("
|
||||
SELECT COUNT(*)
|
||||
FROM {$wpdb->prefix}wpdd_orders o
|
||||
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
|
||||
LEFT JOIN {$wpdb->postmeta} pm_type ON (p.ID = pm_type.post_id AND pm_type.meta_key = '_wpdd_product_type')
|
||||
LEFT JOIN {$wpdb->postmeta} pm_git ON (p.ID = pm_git.post_id AND pm_git.meta_key = '_wpdd_git_repository')
|
||||
WHERE o.status = 'completed'
|
||||
AND p.post_type = 'wpdd_product'
|
||||
AND (pm_type.meta_value = 'software_license' OR pm_git.meta_value IS NOT NULL)
|
||||
");
|
||||
|
||||
$missing_licenses = count($orders_without_licenses);
|
||||
?>
|
||||
|
||||
<div style="background: #f9f9f9; padding: 15px; border: 1px solid #ddd; text-align: center;">
|
||||
<h4 style="margin: 0 0 10px 0;">Total Licenses</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #0073aa;"><?php echo $total_licenses; ?></div>
|
||||
</div>
|
||||
|
||||
<div style="background: #f9f9f9; padding: 15px; border: 1px solid #ddd; text-align: center;">
|
||||
<h4 style="margin: 0 0 10px 0;">Active Licenses</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #46b450;"><?php echo $active_licenses; ?></div>
|
||||
</div>
|
||||
|
||||
<div style="background: #f9f9f9; padding: 15px; border: 1px solid #ddd; text-align: center;">
|
||||
<h4 style="margin: 0 0 10px 0;">Missing Licenses</h4>
|
||||
<div style="font-size: 24px; font-weight: bold; color: <?php echo $missing_licenses > 0 ? '#dc3232' : '#46b450'; ?>;"><?php echo $missing_licenses; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($expired_licenses > 0): ?>
|
||||
<div style="margin-top: 15px; padding: 10px; background: #fff3cd; border: 1px solid #ffeaa7; border-left: 4px solid #f39c12;">
|
||||
<strong>⚠️ Notice:</strong> <?php echo $expired_licenses; ?> license(s) have expired.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php elseif ($active_tab == 'email-test') : ?>
|
||||
<!-- Email Testing Tab -->
|
||||
<div style="background: white; padding: 20px; margin-top: 20px; border: 1px solid #ccd0d4;">
|
||||
<h2>📧 Email Test</h2>
|
||||
<p>Test your email configuration by sending a test email.</p>
|
||||
|
||||
<?php
|
||||
// Handle email test submission
|
||||
if (isset($_POST['send_test_email']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_test_email')) {
|
||||
$to_email = sanitize_email($_POST['test_email']);
|
||||
$subject = sanitize_text_field($_POST['test_subject']);
|
||||
$message = sanitize_textarea_field($_POST['test_message']);
|
||||
|
||||
if (!empty($to_email) && is_email($to_email)) {
|
||||
// Get SMTP settings
|
||||
$smtp_enabled = get_option('wpdd_smtp_enabled');
|
||||
$from_email = get_option('wpdd_from_email', get_option('admin_email'));
|
||||
$from_name = get_option('wpdd_from_name', get_bloginfo('name'));
|
||||
|
||||
// Set headers
|
||||
$headers = array(
|
||||
'From: ' . $from_name . ' <' . $from_email . '>',
|
||||
'Content-Type: text/html; charset=UTF-8'
|
||||
);
|
||||
|
||||
// Configure SMTP if enabled
|
||||
if ($smtp_enabled) {
|
||||
add_action('phpmailer_init', 'wpdd_configure_smtp');
|
||||
}
|
||||
|
||||
// Send email
|
||||
$sent = wp_mail($to_email, $subject, nl2br($message), $headers);
|
||||
|
||||
if ($sent) {
|
||||
echo '<div class="notice notice-success"><p>✅ Test email sent successfully to ' . esc_html($to_email) . '</p></div>';
|
||||
} else {
|
||||
global $phpmailer;
|
||||
$error_info = '';
|
||||
if (isset($phpmailer) && is_object($phpmailer) && !empty($phpmailer->ErrorInfo)) {
|
||||
$error_info = $phpmailer->ErrorInfo;
|
||||
}
|
||||
echo '<div class="notice notice-error"><p>❌ Failed to send test email. ' . esc_html($error_info) . '</p></div>';
|
||||
}
|
||||
} else {
|
||||
echo '<div class="notice notice-error"><p>Please enter a valid email address.</p></div>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<form method="post" style="max-width: 600px;">
|
||||
<?php wp_nonce_field('wpdd_test_email'); ?>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row"><label for="test_email">To Email</label></th>
|
||||
<td>
|
||||
<input type="email" id="test_email" name="test_email" class="regular-text"
|
||||
value="<?php echo esc_attr(get_option('admin_email')); ?>" required />
|
||||
<p class="description">Email address to send the test email to.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="test_subject">Subject</label></th>
|
||||
<td>
|
||||
<input type="text" id="test_subject" name="test_subject" class="regular-text"
|
||||
value="Test Email from <?php echo esc_attr(get_bloginfo('name')); ?>" required />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="test_message">Message</label></th>
|
||||
<td>
|
||||
<textarea id="test_message" name="test_message" rows="5" cols="50" class="large-text">This is a test email from WP Digital Download plugin.
|
||||
|
||||
If you received this email, your email configuration is working correctly!
|
||||
|
||||
Site: <?php echo esc_html(get_bloginfo('name')); ?>
|
||||
URL: <?php echo esc_html(get_bloginfo('url')); ?>
|
||||
Time: <?php echo current_time('mysql'); ?></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="submit">
|
||||
<input type="submit" name="send_test_email" class="button button-primary" value="Send Test Email" />
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<hr style="margin: 30px 0;">
|
||||
|
||||
<h3>📋 Current Email Configuration</h3>
|
||||
<table class="widefat" style="max-width: 600px;">
|
||||
<tr>
|
||||
<td><strong>SMTP Enabled:</strong></td>
|
||||
<td><?php echo get_option('wpdd_smtp_enabled') ? '✅ Yes' : '❌ No (using default mail)'; ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>From Email:</strong></td>
|
||||
<td><?php echo esc_html(get_option('wpdd_from_email', get_option('admin_email'))); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>From Name:</strong></td>
|
||||
<td><?php echo esc_html(get_option('wpdd_from_name', get_bloginfo('name'))); ?></td>
|
||||
</tr>
|
||||
<?php if (get_option('wpdd_smtp_enabled')) : ?>
|
||||
<tr>
|
||||
<td><strong>SMTP Host:</strong></td>
|
||||
<td><?php echo esc_html(get_option('wpdd_smtp_host')); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>SMTP Port:</strong></td>
|
||||
<td><?php echo esc_html(get_option('wpdd_smtp_port')); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>SMTP Encryption:</strong></td>
|
||||
<td><?php echo esc_html(get_option('wpdd_smtp_encryption')); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>SMTP Username:</strong></td>
|
||||
<td><?php echo get_option('wpdd_smtp_username') ? '✅ Set' : '❌ Not set'; ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php elseif ($active_tab == 'email-logs') : ?>
|
||||
<!-- Email Logs Tab -->
|
||||
<div style="background: white; padding: 20px; margin-top: 20px; border: 1px solid #ccd0d4;">
|
||||
<h2>📨 Email Logs</h2>
|
||||
<p>View the last 100 emails sent by the plugin.</p>
|
||||
|
||||
<?php
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . 'wpdd_email_logs';
|
||||
|
||||
// Check if table exists
|
||||
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
|
||||
echo '<div class="notice notice-warning"><p>Email logs table not found. It will be created when the first email is sent.</p></div>';
|
||||
} else {
|
||||
// Get email logs
|
||||
$logs = $wpdb->get_results(
|
||||
"SELECT * FROM $table_name ORDER BY sent_at DESC LIMIT 100"
|
||||
);
|
||||
|
||||
if (empty($logs)) {
|
||||
echo '<p>No email logs found.</p>';
|
||||
} else {
|
||||
?>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 150px;">Date/Time</th>
|
||||
<th style="width: 200px;">To</th>
|
||||
<th>Subject</th>
|
||||
<th style="width: 100px;">Status</th>
|
||||
<th style="width: 100px;">Type</th>
|
||||
<th style="width: 80px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($logs as $log) : ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($log->sent_at); ?></td>
|
||||
<td><?php echo esc_html($log->to_email); ?></td>
|
||||
<td><?php echo esc_html($log->subject); ?></td>
|
||||
<td>
|
||||
<?php if ($log->status == 'sent') : ?>
|
||||
<span style="color: #46b450;">✅ Sent</span>
|
||||
<?php else : ?>
|
||||
<span style="color: #dc3232;">❌ Failed</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html($log->email_type); ?></td>
|
||||
<td>
|
||||
<button class="button button-small view-email-details"
|
||||
data-id="<?php echo $log->id; ?>">View</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="email-details-<?php echo $log->id; ?>" style="display: none;">
|
||||
<td colspan="6" style="background: #f9f9f9; padding: 10px;">
|
||||
<strong>Message:</strong><br>
|
||||
<div style="border: 1px solid #ddd; padding: 10px; background: white; margin-top: 5px;">
|
||||
<?php echo wp_kses_post($log->message); ?>
|
||||
</div>
|
||||
<?php if (!empty($log->error_message)) : ?>
|
||||
<br><strong>Error:</strong> <?php echo esc_html($log->error_message); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$('.view-email-details').on('click', function() {
|
||||
var id = $(this).data('id');
|
||||
$('#email-details-' + id).toggle();
|
||||
$(this).text($(this).text() == 'View' ? 'Hide' : 'View');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// SMTP configuration function
|
||||
function wpdd_configure_smtp($phpmailer) {
|
||||
$phpmailer->isSMTP();
|
||||
$phpmailer->Host = get_option('wpdd_smtp_host');
|
||||
$phpmailer->Port = get_option('wpdd_smtp_port', 587);
|
||||
$phpmailer->SMTPAuth = true;
|
||||
$phpmailer->Username = get_option('wpdd_smtp_username');
|
||||
$phpmailer->Password = get_option('wpdd_smtp_password');
|
||||
$phpmailer->SMTPSecure = get_option('wpdd_smtp_encryption', 'tls');
|
||||
$phpmailer->From = get_option('wpdd_from_email', get_option('admin_email'));
|
||||
$phpmailer->FromName = get_option('wpdd_from_name', get_bloginfo('name'));
|
||||
}
|
Reference in New Issue
Block a user