Files
wp-digital-download/admin/class-wpdd-transaction-history.php
jknapp a160fe3964 Major improvements: Fix download limits, enhance license display, fix software filenames
🔧 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>
2025-09-09 19:16:57 -07:00

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