Files
wp-digital-download/includes/class-wpdd-shortcodes.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

1187 lines
55 KiB
PHP

<?php
if (!defined('ABSPATH')) {
exit;
}
class WPDD_Shortcodes {
public static function init() {
add_shortcode('wpdd_shop', array(__CLASS__, 'shop_shortcode'));
add_shortcode('wpdd_customer_purchases', array(__CLASS__, 'customer_purchases_shortcode'));
add_shortcode('wpdd_checkout', array(__CLASS__, 'checkout_shortcode'));
add_shortcode('wpdd_thank_you', array(__CLASS__, 'thank_you_shortcode'));
add_shortcode('wpdd_product', array(__CLASS__, 'single_product_shortcode'));
add_shortcode('wpdd_buy_button', array(__CLASS__, 'buy_button_shortcode'));
add_shortcode('wpdd_creator_dashboard', array(__CLASS__, 'creator_dashboard_shortcode'));
// Add buy button to single product pages automatically
add_filter('the_content', array(__CLASS__, 'add_buy_button_to_product'));
}
public static function shop_shortcode($atts) {
$atts = shortcode_atts(array(
'columns' => 3,
'per_page' => 12,
'category' => '',
'orderby' => 'date',
'order' => 'DESC',
'show_filters' => 'yes'
), $atts);
ob_start();
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
'post_type' => 'wpdd_product',
'posts_per_page' => $atts['per_page'],
'paged' => $paged,
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'post_status' => 'publish'
);
if (!empty($atts['category'])) {
$args['tax_query'] = array(
array(
'taxonomy' => 'wpdd_product_category',
'field' => 'slug',
'terms' => explode(',', $atts['category'])
)
);
}
// Handle search
$search_performed = false;
if (isset($_GET['wpdd_search']) && !empty($_GET['wpdd_search'])) {
$search_term = sanitize_text_field($_GET['wpdd_search']);
$search_performed = true;
// Bypass WordPress search and do direct query
global $wpdb;
$search_like = '%' . $wpdb->esc_like($search_term) . '%';
$matching_ids = $wpdb->get_col($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts}
WHERE post_type = 'wpdd_product'
AND post_status = 'publish'
AND (post_title LIKE %s OR post_content LIKE %s)",
$search_like,
$search_like
));
// Add debug output
if (current_user_can('manage_options')) {
echo "<!-- DEBUG: Search for '$search_term' found IDs: " . implode(', ', $matching_ids) . " -->";
}
if (!empty($matching_ids)) {
// Found matching products
$args['post__in'] = $matching_ids;
// Only set orderby to post__in if no sort parameter is provided
if (!isset($_GET['wpdd_sort']) || empty($_GET['wpdd_sort'])) {
$args['orderby'] = 'post__in';
}
} else {
// No matches - force empty result
$args['post__in'] = array(-1); // Use -1 instead of 0
}
}
if (isset($_GET['wpdd_category']) && !empty($_GET['wpdd_category'])) {
$args['tax_query'] = array(
array(
'taxonomy' => 'wpdd_product_category',
'field' => 'slug',
'terms' => sanitize_text_field($_GET['wpdd_category'])
)
);
}
// Handle creator filter
if (isset($_GET['wpdd_creator']) && !empty($_GET['wpdd_creator'])) {
$args['author'] = intval($_GET['wpdd_creator']);
}
if (isset($_GET['wpdd_sort']) && !empty($_GET['wpdd_sort'])) {
switch ($_GET['wpdd_sort']) {
case 'price_low':
$args['meta_key'] = '_wpdd_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'ASC';
break;
case 'price_high':
$args['meta_key'] = '_wpdd_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'newest':
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
case 'popular':
$args['meta_key'] = '_wpdd_sales_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
}
}
$query = new WP_Query($args);
// Debug output for admins
if (current_user_can('manage_options') && isset($_GET['debug']) && $_GET['debug'] == '1') {
echo '<div style="background: #fff; border: 1px solid #ccc; padding: 10px; margin: 10px 0;">';
echo '<h4>Debug Info (Admin Only)</h4>';
echo '<strong>GET Parameters:</strong><br>' . print_r($_GET, true) . '<br>';
echo '<strong>Query Args:</strong><br>' . print_r($args, true) . '<br>';
echo '<strong>Found Posts:</strong> ' . $query->found_posts . '<br>';
echo '</div>';
}
?>
<div class="wpdd-shop-container">
<?php if ($atts['show_filters'] === 'yes') : ?>
<div class="wpdd-shop-filters">
<form method="get" class="wpdd-filter-form">
<?php
// Preserve other query parameters but exclude filter and pagination params
$query_args = $_GET;
// Add hidden fields for non-filter params
foreach ($query_args as $key => $value) {
if (!in_array($key, array('wpdd_search', 'wpdd_category', 'wpdd_creator', 'wpdd_sort', 'paged'))) {
if (is_array($value)) {
foreach ($value as $sub_value) {
echo '<input type="hidden" name="' . esc_attr($key) . '[]" value="' . esc_attr($sub_value) . '" />';
}
} else {
echo '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr($value) . '" />';
}
}
}
?>
<div class="wpdd-filter-row">
<input type="text" name="wpdd_search"
placeholder="<?php _e('Search products...', 'wp-digital-download'); ?>"
value="<?php echo isset($_GET['wpdd_search']) ? esc_attr($_GET['wpdd_search']) : ''; ?>" />
<select name="wpdd_category">
<option value=""><?php _e('All Categories', 'wp-digital-download'); ?></option>
<?php
$categories = get_terms(array(
'taxonomy' => 'wpdd_product_category',
'hide_empty' => true
));
if (!is_wp_error($categories) && !empty($categories)) {
foreach ($categories as $category) {
printf(
'<option value="%s" %s>%s</option>',
esc_attr($category->slug),
selected(isset($_GET['wpdd_category']) && $_GET['wpdd_category'] == $category->slug, true, false),
esc_html($category->name)
);
}
}
?>
</select>
<select name="wpdd_sort">
<option value=""><?php _e('Default sorting', 'wp-digital-download'); ?></option>
<option value="newest" <?php selected(isset($_GET['wpdd_sort']) && $_GET['wpdd_sort'] == 'newest'); ?>>
<?php _e('Newest first', 'wp-digital-download'); ?>
</option>
<option value="price_low" <?php selected(isset($_GET['wpdd_sort']) && $_GET['wpdd_sort'] == 'price_low'); ?>>
<?php _e('Price: Low to High', 'wp-digital-download'); ?>
</option>
<option value="price_high" <?php selected(isset($_GET['wpdd_sort']) && $_GET['wpdd_sort'] == 'price_high'); ?>>
<?php _e('Price: High to Low', 'wp-digital-download'); ?>
</option>
<option value="popular" <?php selected(isset($_GET['wpdd_sort']) && $_GET['wpdd_sort'] == 'popular'); ?>>
<?php _e('Most Popular', 'wp-digital-download'); ?>
</option>
</select>
<button type="submit" class="wpdd-filter-submit">
<?php _e('Filter', 'wp-digital-download'); ?>
</button>
</div>
</form>
</div>
<?php endif; ?>
<?php if ($query->have_posts()) : ?>
<div class="wpdd-products-grid wpdd-columns-<?php echo esc_attr($atts['columns']); ?>">
<?php while ($query->have_posts()) : $query->the_post(); ?>
<?php self::render_product_card(get_the_ID()); ?>
<?php endwhile; ?>
</div>
<div class="wpdd-pagination">
<?php
// Build pagination URL with current filter parameters
$pagination_args = array();
if (isset($_GET['wpdd_search']) && $_GET['wpdd_search']) {
$pagination_args['wpdd_search'] = $_GET['wpdd_search'];
}
if (isset($_GET['wpdd_category']) && $_GET['wpdd_category']) {
$pagination_args['wpdd_category'] = $_GET['wpdd_category'];
}
if (isset($_GET['wpdd_creator']) && $_GET['wpdd_creator']) {
$pagination_args['wpdd_creator'] = $_GET['wpdd_creator'];
}
if (isset($_GET['wpdd_sort']) && $_GET['wpdd_sort']) {
$pagination_args['wpdd_sort'] = $_GET['wpdd_sort'];
}
$base_url = get_permalink();
if (!empty($pagination_args)) {
$base_url = add_query_arg($pagination_args, $base_url);
$base_url = add_query_arg('paged', '%#%', $base_url);
} else {
$base_url = add_query_arg('paged', '%#%', $base_url);
}
echo paginate_links(array(
'base' => $base_url,
'total' => $query->max_num_pages,
'current' => $paged,
'format' => '',
'prev_text' => __('&laquo; Previous', 'wp-digital-download'),
'next_text' => __('Next &raquo;', 'wp-digital-download'),
));
?>
</div>
<?php else : ?>
<p class="wpdd-no-products">
<?php _e('No products found.', 'wp-digital-download'); ?>
</p>
<?php endif; ?>
<?php wp_reset_postdata(); ?>
</div>
<?php
return ob_get_clean();
}
private static function render_product_card($product_id) {
$product = get_post($product_id);
$price = get_post_meta($product_id, '_wpdd_price', true);
$sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
$is_free = get_post_meta($product_id, '_wpdd_is_free', true);
$creator = get_userdata($product->post_author);
?>
<div class="wpdd-product-card">
<div class="wpdd-product-image">
<?php if (has_post_thumbnail($product_id)) : ?>
<a href="<?php echo get_permalink($product_id); ?>">
<?php echo get_the_post_thumbnail($product_id, 'medium'); ?>
</a>
<?php else : ?>
<a href="<?php echo get_permalink($product_id); ?>">
<img src="<?php echo WPDD_PLUGIN_URL; ?>assets/images/placeholder.png"
alt="<?php echo esc_attr($product->post_title); ?>" />
</a>
<?php endif; ?>
</div>
<div class="wpdd-product-info">
<h3 class="wpdd-product-title">
<a href="<?php echo get_permalink($product_id); ?>">
<?php echo esc_html($product->post_title); ?>
</a>
</h3>
<div class="wpdd-product-meta">
<span class="wpdd-product-creator">
<?php _e('by', 'wp-digital-download'); ?>
<?php
$shop_page_id = get_option('wpdd_shop_page_id');
$shop_url = $shop_page_id ? get_permalink($shop_page_id) : home_url('/shop/');
$creator_filter_url = add_query_arg('wpdd_creator', $creator->ID, $shop_url);
?>
<a href="<?php echo esc_url($creator_filter_url); ?>"><?php echo esc_html($creator->display_name); ?></a>
</span>
</div>
<div class="wpdd-product-excerpt">
<?php echo wp_trim_words($product->post_excerpt ?: $product->post_content, 20); ?>
</div>
<div class="wpdd-product-price">
<?php if ($is_free) : ?>
<span class="wpdd-price-free"><?php _e('Free', 'wp-digital-download'); ?></span>
<?php elseif ($sale_price && $sale_price < $price) : ?>
<span class="wpdd-price-regular wpdd-price-strike">$<?php echo number_format($price, 2); ?></span>
<span class="wpdd-price-sale">$<?php echo number_format($sale_price, 2); ?></span>
<?php else : ?>
<span class="wpdd-price-regular">$<?php echo number_format($price, 2); ?></span>
<?php endif; ?>
</div>
<div class="wpdd-product-actions">
<a href="<?php echo get_permalink($product_id); ?>" class="wpdd-btn wpdd-btn-view">
<?php _e('View Details', 'wp-digital-download'); ?>
</a>
<?php
if ($is_free) {
$button_text = __('Free Download', 'wp-digital-download');
} else {
$actual_price = ($sale_price && $sale_price < $price) ? $sale_price : $price;
$button_text = sprintf(__('Buy - $%s', 'wp-digital-download'), number_format($actual_price, 2));
}
echo self::render_buy_button($product_id, $button_text, 'wpdd-btn wpdd-btn-buy');
?>
</div>
</div>
</div>
<?php
}
public static function customer_purchases_shortcode($atts) {
global $wpdb;
// Check if viewing by email (for guest purchases)
if (isset($_GET['customer_email']) && isset($_GET['key'])) {
$email = sanitize_email($_GET['customer_email']);
$key = sanitize_text_field($_GET['key']);
// Verify the key (simple hash check)
$expected_key = substr(md5($email . AUTH_KEY), 0, 10);
if ($key !== $expected_key) {
return '<p class="wpdd-error">' . __('Invalid access key.', 'wp-digital-download') . '</p>';
}
$orders = $wpdb->get_results($wpdb->prepare(
"SELECT o.*, p.post_title as product_name, l.license_key,
(SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_downloads d WHERE d.order_id = o.id) as download_count
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.customer_email = %s
AND o.status = 'completed'
ORDER BY o.purchase_date DESC",
$email
));
} elseif (is_user_logged_in()) {
$current_user = wp_get_current_user();
// Get orders by user ID or email (to include guest purchases before account creation)
$orders = $wpdb->get_results($wpdb->prepare(
"SELECT o.*, p.post_title as product_name, l.license_key,
(SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_downloads d WHERE d.order_id = o.id) as download_count
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.customer_id = %d OR o.customer_email = %s)
AND o.status = 'completed'
ORDER BY o.purchase_date DESC",
$current_user->ID,
$current_user->user_email
));
} else {
return '<p class="wpdd-login-required">' .
sprintf(
__('Please <a href="%s">login</a> to view your purchases.', 'wp-digital-download'),
wp_login_url(get_permalink())
) . '</p>';
}
ob_start();
?>
<div class="wpdd-customer-purchases">
<h2><?php _e('My Purchases', 'wp-digital-download'); ?></h2>
<?php if ($orders) : ?>
<table class="wpdd-purchases-table">
<thead>
<tr>
<th><?php _e('Order #', 'wp-digital-download'); ?></th>
<th><?php _e('Product', 'wp-digital-download'); ?></th>
<th><?php _e('Purchase Date', 'wp-digital-download'); ?></th>
<th><?php _e('Amount', 'wp-digital-download'); ?></th>
<th><?php _e('Downloads', 'wp-digital-download'); ?></th>
<th><?php _e('Actions', 'wp-digital-download'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $order) : ?>
<?php
$download_limit = get_post_meta($order->product_id, '_wpdd_download_limit', true);
$download_limit = $download_limit ?: 0; // Convert empty string to 0 (unlimited)
$download_expiry = get_post_meta($order->product_id, '_wpdd_download_expiry', true);
$is_expired = false;
if ($download_expiry > 0) {
$expiry_date = date('Y-m-d H:i:s', strtotime($order->purchase_date . ' + ' . $download_expiry . ' days'));
$is_expired = current_time('mysql') > $expiry_date;
}
// Ensure download_count is a number
$current_downloads = (int) $order->download_count;
$can_download = !$is_expired && ($download_limit == 0 || $current_downloads < $download_limit);
?>
<tr>
<td><?php echo esc_html($order->order_number); ?></td>
<td>
<a href="<?php echo get_permalink($order->product_id); ?>">
<?php echo esc_html($order->product_name); ?>
</a>
</td>
<td><?php echo date_i18n(get_option('date_format'), strtotime($order->purchase_date)); ?></td>
<td>$<?php echo number_format($order->amount, 2); ?></td>
<td>
<?php
if ($download_limit > 0) {
echo sprintf('%d / %d', $current_downloads, $download_limit);
} else {
echo sprintf('%d / %s', $current_downloads, __('unlimited', 'wp-digital-download'));
}
?>
</td>
<td>
<?php if ($can_download) : ?>
<?php
// Build download URL with appropriate parameters
$download_args = array('wpdd_download' => $order->id);
// For guest access, include email and key
if (isset($_GET['customer_email']) && isset($_GET['key'])) {
$download_args['customer_email'] = $_GET['customer_email'];
$download_args['key'] = $_GET['key'];
}
$download_url = wp_nonce_url(
add_query_arg($download_args),
'wpdd_download_' . $order->id
);
?>
<a href="<?php echo esc_url($download_url); ?>" class="wpdd-btn wpdd-btn-download">
<?php _e('Download', 'wp-digital-download'); ?>
</a>
<?php else : ?>
<span class="wpdd-download-expired">
<?php
if ($is_expired) {
_e('Expired', 'wp-digital-download');
} else {
_e('Limit Reached', 'wp-digital-download');
}
?>
</span>
<?php endif; ?>
</td>
</tr>
<?php if (!empty($order->license_key)) : ?>
<tr class="wpdd-license-row">
<td colspan="6" class="wpdd-license-cell">
<div class="wpdd-license-info">
<small><?php _e('License Key:', 'wp-digital-download'); ?></small>
<code class="wpdd-license-key"><?php echo esc_html($order->license_key); ?></code>
<button type="button" class="wpdd-copy-license" data-license="<?php echo esc_attr($order->license_key); ?>" title="<?php _e('Copy to clipboard', 'wp-digital-download'); ?>">
<i class="fas fa-copy"></i>
</button>
</div>
</td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
<?php else : ?>
<p><?php _e('You have not purchased any products yet.', 'wp-digital-download'); ?></p>
<p><a href="<?php echo get_option('wpdd_shop_page_id') ? get_permalink(get_option('wpdd_shop_page_id')) : home_url(); ?>"
class="wpdd-btn wpdd-btn-primary">
<?php _e('Browse Products', 'wp-digital-download'); ?>
</a></p>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
public static function checkout_shortcode($atts) {
if (!isset($_GET['product_id'])) {
return '<p>' . __('No product selected for checkout.', 'wp-digital-download') . '</p>';
}
$product_id = intval($_GET['product_id']);
$product = get_post($product_id);
if (!$product || $product->post_type !== 'wpdd_product') {
return '<p>' . __('Invalid product.', 'wp-digital-download') . '</p>';
}
$price = get_post_meta($product_id, '_wpdd_price', true);
$sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
$is_free = get_post_meta($product_id, '_wpdd_is_free', true);
$final_price = ($sale_price && $sale_price < $price) ? $sale_price : $price;
ob_start();
?>
<div class="wpdd-checkout">
<h2><?php _e('Checkout', 'wp-digital-download'); ?></h2>
<div class="wpdd-checkout-product">
<h3><?php echo esc_html($product->post_title); ?></h3>
<?php if (has_post_thumbnail($product_id)) : ?>
<?php echo get_the_post_thumbnail($product_id, 'thumbnail'); ?>
<?php endif; ?>
<div class="wpdd-checkout-price">
<?php if ($is_free) : ?>
<span><?php _e('Free', 'wp-digital-download'); ?></span>
<?php else : ?>
<span><?php _e('Total:', 'wp-digital-download'); ?> $<?php echo number_format($final_price, 2); ?></span>
<?php endif; ?>
</div>
</div>
<form id="wpdd-checkout-form" method="post" data-product-free="<?php echo $is_free ? '1' : '0'; ?>">
<?php wp_nonce_field('wpdd_checkout', 'wpdd_checkout_nonce'); ?>
<input type="hidden" name="product_id" value="<?php echo $product_id; ?>" />
<?php if (!is_user_logged_in()) : ?>
<div class="wpdd-checkout-section">
<h4><?php _e('Customer Information', 'wp-digital-download'); ?></h4>
<p>
<label for="customer_email"><?php _e('Email Address', 'wp-digital-download'); ?> *</label>
<input type="email" id="customer_email" name="customer_email" required />
</p>
<p>
<label for="customer_name"><?php _e('Full Name', 'wp-digital-download'); ?> *</label>
<input type="text" id="customer_name" name="customer_name" required />
</p>
<p>
<label>
<input type="checkbox" name="create_account" value="1" />
<?php _e('Create an account for future purchases', 'wp-digital-download'); ?>
</label>
</p>
</div>
<?php endif; ?>
<?php if (!$is_free) : ?>
<div class="wpdd-checkout-section">
<h4><?php _e('Payment Method', 'wp-digital-download'); ?></h4>
<div id="wpdd-paypal-button"></div>
</div>
<?php else : ?>
<button type="submit" class="wpdd-btn wpdd-btn-primary wpdd-btn-large wpdd-free-download-btn" data-product-id="<?php echo $product_id; ?>">
<?php _e('Get Free Download', 'wp-digital-download'); ?>
</button>
<?php endif; ?>
</form>
</div>
<?php
return ob_get_clean();
}
public static function thank_you_shortcode($atts) {
if (!isset($_GET['order_id'])) {
return '<p>' . __('Invalid order.', 'wp-digital-download') . '</p>';
}
global $wpdb;
$order_id = sanitize_text_field($_GET['order_id']);
// Get order details along with download token
$order = $wpdb->get_row($wpdb->prepare(
"SELECT o.*, p.post_title as product_name, dl.token as download_token
FROM {$wpdb->prefix}wpdd_orders o
LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
LEFT JOIN {$wpdb->prefix}wpdd_download_links dl ON o.id = dl.order_id
WHERE o.order_number = %s",
$order_id
));
if (!$order) {
return '<p>' . __('Order not found.', 'wp-digital-download') . '</p>';
}
ob_start();
?>
<div class="wpdd-thank-you">
<h2><?php _e('Thank You for Your Purchase!', 'wp-digital-download'); ?></h2>
<div class="wpdd-order-details">
<p><?php _e('Your order has been received and processed successfully.', 'wp-digital-download'); ?></p>
<div class="wpdd-order-info">
<p><strong><?php _e('Order Number:', 'wp-digital-download'); ?></strong> <?php echo esc_html($order->order_number); ?></p>
<p><strong><?php _e('Product:', 'wp-digital-download'); ?></strong> <?php echo esc_html($order->product_name); ?></p>
<p><strong><?php _e('Amount:', 'wp-digital-download'); ?></strong> $<?php echo number_format($order->amount, 2); ?></p>
</div>
<div class="wpdd-download-section">
<h3><?php _e('Download Your Product', 'wp-digital-download'); ?></h3>
<p><?php _e('A download link has been sent to your email address. You can also download your product using the button below:', 'wp-digital-download'); ?></p>
<?php
// Use token-based download URL which works for unregistered users
if ($order->download_token) {
$download_url = add_query_arg(array(
'wpdd_download_token' => $order->download_token
), home_url());
} else {
// For legacy orders without tokens, create one now
if (class_exists('WPDD_Download_Handler')) {
$token = WPDD_Download_Handler::ensure_download_token($order->id);
if ($token) {
$download_url = add_query_arg(array(
'wpdd_download_token' => $token
), home_url());
} else {
// Still fallback to old method if token creation fails
$download_args = array('wpdd_download' => $order->id);
// For guest users, include email and authentication key
if (!is_user_logged_in()) {
$download_args['customer_email'] = $order->customer_email;
$download_args['key'] = substr(md5($order->customer_email . AUTH_KEY), 0, 10);
}
$download_url = wp_nonce_url(
add_query_arg($download_args),
'wpdd_download_' . $order->id
);
}
} else {
// Fallback if WPDD_Download_Handler class not found
$download_args = array('wpdd_download' => $order->id);
// For guest users, include email and authentication key
if (!is_user_logged_in()) {
$download_args['customer_email'] = $order->customer_email;
$download_args['key'] = substr(md5($order->customer_email . AUTH_KEY), 0, 10);
}
$download_url = wp_nonce_url(
add_query_arg($download_args),
'wpdd_download_' . $order->id
);
}
}
?>
<a href="<?php echo esc_url($download_url); ?>" class="wpdd-btn wpdd-btn-primary wpdd-btn-large">
<?php _e('Download Now', 'wp-digital-download'); ?>
</a>
</div>
<?php if (is_user_logged_in()) : ?>
<p>
<?php printf(
__('You can view all your purchases and re-download files from your <a href="%s">purchase history</a>.', 'wp-digital-download'),
get_permalink(get_option('wpdd_purchases_page_id'))
); ?>
</p>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}
public static function single_product_shortcode($atts) {
$atts = shortcode_atts(array(
'id' => 0
), $atts);
if (!$atts['id']) {
return '';
}
ob_start();
self::render_product_card($atts['id']);
return ob_get_clean();
}
public static function buy_button_shortcode($atts) {
$atts = shortcode_atts(array(
'id' => get_the_ID(),
'text' => __('Buy Now', 'wp-digital-download'),
'class' => 'wpdd-buy-button'
), $atts);
if (!$atts['id']) {
return '';
}
return self::render_buy_button($atts['id'], $atts['text'], $atts['class']);
}
public static function render_buy_button($product_id, $text = '', $class = '') {
global $wpdb;
// Check if user already owns this product
$user_owns_product = false;
$existing_order = null;
if (is_user_logged_in()) {
$current_user = wp_get_current_user();
$existing_order = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_orders
WHERE product_id = %d
AND (customer_id = %d OR customer_email = %s)
AND status = 'completed'
ORDER BY purchase_date DESC
LIMIT 1",
$product_id,
$current_user->ID,
$current_user->user_email
));
if ($existing_order) {
$user_owns_product = true;
}
}
// If user already owns the product, show download button
if ($user_owns_product) {
$download_url = wp_nonce_url(
add_query_arg(array('wpdd_download' => $existing_order->id)),
'wpdd_download_' . $existing_order->id
);
return sprintf(
'<a href="%s" class="%s wpdd-owned-product">%s</a>',
esc_url($download_url),
esc_attr($class ?: 'wpdd-download-button'),
esc_html(__('Download (Already Purchased)', 'wp-digital-download'))
);
}
// Regular buy button for products not owned
$price = get_post_meta($product_id, '_wpdd_price', true);
$sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
$is_free = get_post_meta($product_id, '_wpdd_is_free', true);
$actual_price = ($sale_price && $sale_price < $price) ? $sale_price : $price;
if (!$text) {
if ($is_free) {
$text = __('Download Free', 'wp-digital-download');
} else {
$text = sprintf(__('Buy Now - $%s', 'wp-digital-download'), number_format($actual_price, 2));
}
}
$checkout_url = add_query_arg(array(
'wpdd_action' => 'add_to_cart',
'product_id' => $product_id,
'nonce' => wp_create_nonce('wpdd_add_to_cart')
), get_permalink(get_option('wpdd_checkout_page_id')));
$button_html = sprintf(
'<a href="%s" class="%s" data-product-id="%d" data-price="%s">%s</a>',
esc_url($checkout_url),
esc_attr($class ?: 'wpdd-buy-button'),
esc_attr($product_id),
esc_attr($actual_price),
esc_html($text)
);
return $button_html;
}
public static function add_buy_button_to_product($content) {
if (!is_singular('wpdd_product')) {
return $content;
}
global $post;
// Get product details
$price = get_post_meta($post->ID, '_wpdd_price', true);
$sale_price = get_post_meta($post->ID, '_wpdd_sale_price', true);
$is_free = get_post_meta($post->ID, '_wpdd_is_free', true);
$files = get_post_meta($post->ID, '_wpdd_files', true);
// Build product info box
$product_info = '<div class="wpdd-single-product-info">';
// Left section with meta
$product_info .= '<div class="wpdd-single-meta">';
// File count
if (is_array($files)) {
$file_count = count($files);
$product_info .= '<p><strong>' . __('Files:', 'wp-digital-download') . '</strong> ' .
sprintf(_n('%d file', '%d files', $file_count, 'wp-digital-download'), $file_count) . '</p>';
}
// Creator info with shop filter link
$creator = get_userdata($post->post_author);
$shop_page_id = get_option('wpdd_shop_page_id');
$shop_url = $shop_page_id ? get_permalink($shop_page_id) : home_url('/shop/');
$creator_filter_url = add_query_arg('wpdd_creator', $creator->ID, $shop_url);
$product_info .= '<p><strong>' . __('Created by', 'wp-digital-download') . '</strong> ' .
'<a href="' . esc_url($creator_filter_url) . '">' . esc_html($creator->display_name) . '</a></p>';
// Categories
$categories = wp_get_post_terms($post->ID, 'wpdd_product_category', array('fields' => 'names'));
if (!empty($categories)) {
$product_info .= '<p><strong>' . __('Categories:', 'wp-digital-download') . '</strong> ' .
implode(', ', $categories) . '</p>';
}
$product_info .= '</div>'; // close meta
// Right section with buy button
$product_info .= '<div class="wpdd-single-purchase">';
// Buy button (price is included in button text)
$product_info .= '<div class="wpdd-single-buy-button">';
$product_info .= self::render_buy_button($post->ID);
$product_info .= '</div>';
$product_info .= '</div>'; // close purchase section
$product_info .= '</div>'; // close product info box
// Add CSS for the product info box
$product_info .= '<style>
.wpdd-single-product-info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
max-width: 800px;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin: 20px auto;
box-sizing: border-box;
gap: 20px;
}
.wpdd-single-meta {
flex: 1;
padding-right: 20px;
}
.wpdd-single-meta p {
margin: 10px 0;
font-size: 14px;
}
.wpdd-single-meta a {
color: #0073aa;
text-decoration: none;
}
.wpdd-single-meta a:hover {
text-decoration: underline;
}
.wpdd-single-purchase {
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 200px;
}
.wpdd-buy-button {
display: block;
background: #0073aa;
color: white !important;
text-align: center;
padding: 15px 30px;
text-decoration: none;
border-radius: 5px;
font-size: 18px;
font-weight: bold;
transition: background 0.3s;
white-space: nowrap;
min-width: 180px;
}
.wpdd-buy-button:hover {
background: #005a87;
color: white !important;
}
.wpdd-owned-product {
background: #27ae60 !important;
padding: 15px 40px !important;
min-width: 250px;
}
.wpdd-owned-product:hover {
background: #229954 !important;
}
@media (max-width: 768px) {
.wpdd-single-product-info {
flex-direction: column;
width: 100%;
max-width: 100%;
margin: 20px 0;
}
.wpdd-single-meta {
padding-right: 0;
text-align: center;
}
.wpdd-single-purchase {
min-width: auto;
width: 100%;
}
.wpdd-buy-button {
width: 100%;
min-width: auto;
}
}
</style>';
// Add the product info box before the content
return $product_info . $content;
}
public static function creator_dashboard_shortcode($atts) {
if (!is_user_logged_in()) {
return '<p>' . __('Please log in to view your creator dashboard.', 'wp-digital-download') . '</p>';
}
$user_id = get_current_user_id();
// Check if user is a creator
if (!WPDD_Creator::is_creator($user_id)) {
return '<p>' . __('This dashboard is only available for creators.', 'wp-digital-download') . '</p>';
}
global $wpdb;
// Get creator stats
$total_earnings = WPDD_Creator::get_creator_total_earnings($user_id);
$net_earnings = WPDD_Creator::get_creator_net_earnings($user_id);
$current_balance = WPDD_Creator::get_creator_balance($user_id);
$commission_rate = floatval(get_option('wpdd_commission_rate', 0));
$currency = get_option('wpdd_currency', 'USD');
$paypal_email = get_user_meta($user_id, 'wpdd_paypal_email', true);
// Get recent sales
$recent_sales = $wpdb->get_results($wpdb->prepare(
"SELECT o.*, p.post_title as product_name,
(o.total * %f / 100) as platform_fee,
(o.total * (100 - %f) / 100) as creator_earning
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'
ORDER BY o.purchase_date DESC
LIMIT 20",
$commission_rate,
$commission_rate,
$user_id
));
// Get payout history
$payouts = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpdd_payouts
WHERE creator_id = %d
ORDER BY created_at DESC
LIMIT 10",
$user_id
));
ob_start();
?>
<div class="wpdd-creator-dashboard">
<h2><?php _e('Creator Dashboard', 'wp-digital-download'); ?></h2>
<div class="wpdd-creator-stats">
<div class="wpdd-stat-box">
<h3><?php _e('Current Balance', 'wp-digital-download'); ?></h3>
<div class="wpdd-stat-value"><?php echo wpdd_format_price($current_balance, $currency); ?></div>
<p class="wpdd-stat-desc"><?php _e('Available for payout', 'wp-digital-download'); ?></p>
</div>
<div class="wpdd-stat-box">
<h3><?php _e('Total Sales', 'wp-digital-download'); ?></h3>
<div class="wpdd-stat-value"><?php echo wpdd_format_price($total_earnings, $currency); ?></div>
<p class="wpdd-stat-desc"><?php _e('Lifetime gross sales', 'wp-digital-download'); ?></p>
</div>
<div class="wpdd-stat-box">
<h3><?php _e('Net Earnings', 'wp-digital-download'); ?></h3>
<div class="wpdd-stat-value"><?php echo wpdd_format_price($net_earnings, $currency); ?></div>
<p class="wpdd-stat-desc"><?php printf(__('After %s%% platform fee', 'wp-digital-download'), $commission_rate); ?></p>
</div>
</div>
<div class="wpdd-creator-settings">
<h3><?php _e('Payout Settings', 'wp-digital-download'); ?></h3>
<?php if (empty($paypal_email)) : ?>
<div class="wpdd-notice wpdd-warning">
<?php _e('Please add your PayPal email in your profile to receive payouts.', 'wp-digital-download'); ?>
<a href="<?php echo esc_url(get_edit_profile_url($user_id)); ?>"><?php _e('Edit Profile', 'wp-digital-download'); ?></a>
</div>
<?php else : ?>
<p><?php _e('PayPal Email:', 'wp-digital-download'); ?> <strong><?php echo esc_html($paypal_email); ?></strong>
<a href="<?php echo esc_url(get_edit_profile_url($user_id)); ?>"><?php _e('Change', 'wp-digital-download'); ?></a></p>
<?php endif; ?>
<?php
$threshold = floatval(get_option('wpdd_payout_threshold', 0));
if ($threshold > 0) {
echo '<p>' . sprintf(__('Automatic payouts are triggered when balance reaches %s', 'wp-digital-download'), wpdd_format_price($threshold, $currency)) . '</p>';
}
?>
</div>
<?php if (!empty($recent_sales)) : ?>
<div class="wpdd-recent-sales">
<h3><?php _e('Recent Sales', 'wp-digital-download'); ?></h3>
<table class="wpdd-table">
<thead>
<tr>
<th><?php _e('Date', 'wp-digital-download'); ?></th>
<th><?php _e('Product', 'wp-digital-download'); ?></th>
<th><?php _e('Customer', 'wp-digital-download'); ?></th>
<th><?php _e('Sale Amount', 'wp-digital-download'); ?></th>
<th><?php _e('Platform Fee', 'wp-digital-download'); ?></th>
<th><?php _e('Your Earning', 'wp-digital-download'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($recent_sales as $sale) : ?>
<tr>
<td><?php echo esc_html(date_i18n(get_option('date_format'), strtotime($sale->purchase_date))); ?></td>
<td><?php echo esc_html($sale->product_name); ?></td>
<td><?php echo esc_html($sale->customer_name); ?></td>
<td><?php echo wpdd_format_price($sale->total, $currency); ?></td>
<td><?php echo wpdd_format_price($sale->platform_fee, $currency); ?></td>
<td><strong><?php echo wpdd_format_price($sale->creator_earning, $currency); ?></strong></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (!empty($payouts)) : ?>
<div class="wpdd-payout-history">
<h3><?php _e('Payout History', 'wp-digital-download'); ?></h3>
<table class="wpdd-table">
<thead>
<tr>
<th><?php _e('Date', 'wp-digital-download'); ?></th>
<th><?php _e('Amount', 'wp-digital-download'); ?></th>
<th><?php _e('Status', 'wp-digital-download'); ?></th>
<th><?php _e('Transaction ID', 'wp-digital-download'); ?></th>
<th><?php _e('Method', 'wp-digital-download'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($payouts as $payout) : ?>
<tr>
<td><?php echo esc_html(date_i18n(get_option('date_format'), strtotime($payout->created_at))); ?></td>
<td><?php echo wpdd_format_price($payout->amount, $payout->currency); ?></td>
<td>
<span class="wpdd-status wpdd-status-<?php echo esc_attr($payout->status); ?>">
<?php echo esc_html(ucfirst($payout->status)); ?>
</span>
</td>
<td><?php echo esc_html($payout->transaction_id ?: '-'); ?></td>
<td><?php echo esc_html(ucfirst($payout->payout_method)); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<style>
.wpdd-creator-dashboard {
max-width: 1200px;
margin: 20px 0;
}
.wpdd-creator-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.wpdd-stat-box {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
text-align: center;
}
.wpdd-stat-box h3 {
margin: 0 0 10px;
font-size: 14px;
text-transform: uppercase;
color: #6c757d;
}
.wpdd-stat-value {
font-size: 32px;
font-weight: bold;
color: #212529;
margin: 10px 0;
}
.wpdd-stat-desc {
color: #6c757d;
font-size: 12px;
margin: 5px 0 0;
}
.wpdd-creator-settings {
background: #fff;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
}
.wpdd-notice {
padding: 12px;
border-radius: 4px;
margin: 10px 0;
}
.wpdd-warning {
background-color: #fff3cd;
border: 1px solid #ffc107;
color: #856404;
}
.wpdd-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.wpdd-table th,
.wpdd-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
.wpdd-table th {
background-color: #f8f9fa;
font-weight: 600;
}
.wpdd-status {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
}
.wpdd-status-completed {
background-color: #d4edda;
color: #155724;
}
.wpdd-status-pending {
background-color: #fff3cd;
color: #856404;
}
.wpdd-status-failed {
background-color: #f8d7da;
color: #721c24;
}
</style>
</div>
<?php
return ob_get_clean();
}
}