🔧 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>
		
			
				
	
	
		
			781 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			781 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?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;
 | 
						|
    }
 | 
						|
}
 |