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>
245 lines
9.9 KiB
PHP
245 lines
9.9 KiB
PHP
<?php
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class WPDD_Creator {
|
|
|
|
public static function init() {
|
|
add_action('show_user_profile', array(__CLASS__, 'add_profile_fields'));
|
|
add_action('edit_user_profile', array(__CLASS__, 'add_profile_fields'));
|
|
add_action('personal_options_update', array(__CLASS__, 'save_profile_fields'));
|
|
add_action('edit_user_profile_update', array(__CLASS__, 'save_profile_fields'));
|
|
add_action('user_register', array(__CLASS__, 'set_default_fields'));
|
|
add_action('wpdd_order_completed', array(__CLASS__, 'add_earnings_to_balance'));
|
|
}
|
|
|
|
public static function add_profile_fields($user) {
|
|
// Only show for creators and admins
|
|
if (!self::is_creator($user->ID) && !current_user_can('manage_options')) {
|
|
return;
|
|
}
|
|
?>
|
|
<h3><?php _e('Creator Payout Settings', 'wp-digital-download'); ?></h3>
|
|
<table class="form-table">
|
|
<tr>
|
|
<th><label for="wpdd_paypal_email"><?php _e('PayPal Email', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<input type="email" name="wpdd_paypal_email" id="wpdd_paypal_email"
|
|
value="<?php echo esc_attr(get_user_meta($user->ID, 'wpdd_paypal_email', true)); ?>"
|
|
class="regular-text" />
|
|
<p class="description"><?php _e('PayPal email address for receiving payouts', 'wp-digital-download'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<?php if (current_user_can('manage_options')) : ?>
|
|
<tr>
|
|
<th><label><?php _e('Creator Balance', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<?php
|
|
$balance = self::get_creator_balance($user->ID);
|
|
$currency = get_option('wpdd_currency', 'USD');
|
|
echo '<strong>' . wpdd_format_price($balance, $currency) . '</strong>';
|
|
?>
|
|
<p class="description"><?php _e('Current unpaid earnings balance', 'wp-digital-download'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th><label><?php _e('Total Earnings', 'wp-digital-download'); ?></label></th>
|
|
<td>
|
|
<?php
|
|
$total = self::get_creator_total_earnings($user->ID);
|
|
echo '<strong>' . wpdd_format_price($total, $currency) . '</strong>';
|
|
?>
|
|
<p class="description"><?php _e('Total lifetime earnings', 'wp-digital-download'); ?></p>
|
|
</td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</table>
|
|
<?php
|
|
}
|
|
|
|
public static function save_profile_fields($user_id) {
|
|
if (!current_user_can('edit_user', $user_id)) {
|
|
return;
|
|
}
|
|
|
|
if (isset($_POST['wpdd_paypal_email'])) {
|
|
$email = sanitize_email($_POST['wpdd_paypal_email']);
|
|
if (!empty($email) && !is_email($email)) {
|
|
return;
|
|
}
|
|
update_user_meta($user_id, 'wpdd_paypal_email', $email);
|
|
}
|
|
}
|
|
|
|
public static function set_default_fields($user_id) {
|
|
if (self::is_creator($user_id)) {
|
|
update_user_meta($user_id, 'wpdd_creator_balance', 0);
|
|
update_user_meta($user_id, 'wpdd_total_earnings', 0);
|
|
}
|
|
}
|
|
|
|
public static function is_creator($user_id) {
|
|
$user = get_userdata($user_id);
|
|
return $user && in_array('wpdd_creator', (array) $user->roles);
|
|
}
|
|
|
|
public static function get_creator_balance($user_id) {
|
|
global $wpdb;
|
|
|
|
// Get balance from user meta (for backward compatibility and manual adjustments)
|
|
$meta_balance = floatval(get_user_meta($user_id, 'wpdd_creator_balance', true));
|
|
|
|
// If we have the creator_earnings table, calculate from there
|
|
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$wpdb->prefix}wpdd_creator_earnings'") == $wpdb->prefix . 'wpdd_creator_earnings';
|
|
|
|
if ($table_exists) {
|
|
// Check if payout_status column exists
|
|
$columns = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}wpdd_creator_earnings");
|
|
$has_payout_status = false;
|
|
foreach ($columns as $column) {
|
|
if ($column->Field == 'payout_status') {
|
|
$has_payout_status = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($has_payout_status) {
|
|
// Calculate available earnings (not pending, not 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'",
|
|
$user_id
|
|
));
|
|
|
|
// Calculate balance adjustments
|
|
$adjustments_table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$wpdb->prefix}wpdd_balance_adjustments'") == $wpdb->prefix . 'wpdd_balance_adjustments';
|
|
$total_adjustments = 0;
|
|
|
|
if ($adjustments_table_exists) {
|
|
$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",
|
|
$user_id
|
|
));
|
|
}
|
|
|
|
$calculated_balance = floatval($available_earnings) + floatval($total_adjustments);
|
|
|
|
// Update the meta if different
|
|
if (abs($calculated_balance - $meta_balance) > 0.01) {
|
|
update_user_meta($user_id, 'wpdd_creator_balance', $calculated_balance);
|
|
}
|
|
|
|
return $calculated_balance;
|
|
}
|
|
}
|
|
|
|
// Fall back to meta balance
|
|
return $meta_balance;
|
|
}
|
|
|
|
public static function get_creator_total_earnings($user_id) {
|
|
global $wpdb;
|
|
|
|
$total = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT SUM(o.amount)
|
|
FROM {$wpdb->prefix}wpdd_orders o
|
|
INNER JOIN {$wpdb->posts} p ON o.product_id = p.ID
|
|
WHERE p.post_author = %d
|
|
AND o.status = 'completed'",
|
|
$user_id
|
|
));
|
|
|
|
return floatval($total);
|
|
}
|
|
|
|
public static function get_creator_net_earnings($user_id) {
|
|
$total = self::get_creator_total_earnings($user_id);
|
|
$commission_rate = floatval(get_option('wpdd_commission_rate', 0));
|
|
$net = $total * (1 - ($commission_rate / 100));
|
|
return $net;
|
|
}
|
|
|
|
public static function add_earnings_to_balance($order_id) {
|
|
global $wpdb;
|
|
|
|
$order = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
|
|
$order_id
|
|
));
|
|
|
|
if (!$order || $order->status !== 'completed') {
|
|
return;
|
|
}
|
|
|
|
$product = get_post($order->product_id);
|
|
if (!$product) {
|
|
return;
|
|
}
|
|
|
|
$creator_id = $product->post_author;
|
|
$commission_rate = floatval(get_option('wpdd_commission_rate', 0));
|
|
$creator_share = $order->amount * (1 - ($commission_rate / 100));
|
|
|
|
// Update creator balance
|
|
$current_balance = self::get_creator_balance($creator_id);
|
|
update_user_meta($creator_id, 'wpdd_creator_balance', $current_balance + $creator_share);
|
|
|
|
// Calculate when earnings will be available (holding period)
|
|
$holding_days = intval(get_option('wpdd_earnings_holding_days', 15));
|
|
$available_at = ($holding_days > 0) ?
|
|
date('Y-m-d H:i:s', strtotime('+' . $holding_days . ' days')) :
|
|
current_time('mysql');
|
|
|
|
$initial_status = ($holding_days > 0) ? 'pending' : 'available';
|
|
|
|
// Log the earning
|
|
$wpdb->insert(
|
|
$wpdb->prefix . 'wpdd_creator_earnings',
|
|
array(
|
|
'creator_id' => $creator_id,
|
|
'order_id' => $order_id,
|
|
'product_id' => $order->product_id,
|
|
'sale_amount' => $order->amount,
|
|
'commission_rate' => $commission_rate,
|
|
'creator_earning' => $creator_share,
|
|
'payout_status' => $initial_status,
|
|
'available_at' => $available_at,
|
|
'created_at' => current_time('mysql')
|
|
),
|
|
array('%d', '%d', '%d', '%f', '%f', '%f', '%s', '%s', '%s')
|
|
);
|
|
}
|
|
|
|
public static function get_creators_with_balance() {
|
|
global $wpdb;
|
|
|
|
$threshold = floatval(get_option('wpdd_payout_threshold', 0));
|
|
|
|
$query = "SELECT u.ID, u.display_name, u.user_email,
|
|
um1.meta_value as paypal_email,
|
|
um2.meta_value as balance
|
|
FROM {$wpdb->users} u
|
|
INNER JOIN {$wpdb->usermeta} um ON u.ID = um.user_id
|
|
LEFT JOIN {$wpdb->usermeta} um1 ON u.ID = um1.user_id AND um1.meta_key = 'wpdd_paypal_email'
|
|
LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'wpdd_creator_balance'
|
|
WHERE um.meta_key = '{$wpdb->prefix}capabilities'
|
|
AND um.meta_value LIKE '%wpdd_creator%'
|
|
AND CAST(um2.meta_value AS DECIMAL(10,2)) > 0";
|
|
|
|
if ($threshold > 0) {
|
|
$query .= $wpdb->prepare(" AND CAST(um2.meta_value AS DECIMAL(10,2)) >= %f", $threshold);
|
|
}
|
|
|
|
return $wpdb->get_results($query);
|
|
}
|
|
}
|