🔧 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>
		
			
				
	
	
		
			399 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			399 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * Plugin Name: WP Digital Download
 | 
						|
 * Plugin URI: https://example.com/wp-digital-download
 | 
						|
 * Description: A comprehensive digital download marketplace plugin allowing creators to sell digital products with PayPal integration
 | 
						|
 * Version: {auto_update_value_on_deploy}
 | 
						|
 * Author: Josh Knapp
 | 
						|
 * License: GPL v2 or later
 | 
						|
 * Text Domain: wp-digital-download
 | 
						|
 * Domain Path: /languages
 | 
						|
 */
 | 
						|
 | 
						|
if (!defined('ABSPATH')) {
 | 
						|
    exit;
 | 
						|
}
 | 
						|
 | 
						|
define('WPDD_VERSION', '{auto_update_value_on_deploy}');
 | 
						|
define('WPDD_PLUGIN_URL', plugin_dir_url(__FILE__));
 | 
						|
define('WPDD_PLUGIN_PATH', plugin_dir_path(__FILE__));
 | 
						|
define('WPDD_PLUGIN_BASENAME', plugin_basename(__FILE__));
 | 
						|
define('WPDD_UPLOADS_DIR', 'wp-digital-downloads');
 | 
						|
 | 
						|
final class WP_Digital_Download {
 | 
						|
    
 | 
						|
    private static $instance = null;
 | 
						|
    
 | 
						|
    public static function instance() {
 | 
						|
        if (is_null(self::$instance)) {
 | 
						|
            self::$instance = new self();
 | 
						|
        }
 | 
						|
        return self::$instance;
 | 
						|
    }
 | 
						|
    
 | 
						|
    private function __construct() {
 | 
						|
        $this->load_dependencies();
 | 
						|
        $this->init_hooks();
 | 
						|
    }
 | 
						|
    
 | 
						|
    private function load_dependencies() {
 | 
						|
        $files = array(
 | 
						|
            'includes/class-wpdd-install.php',
 | 
						|
            'includes/class-wpdd-setup.php',
 | 
						|
            'includes/class-wpdd-post-types.php',
 | 
						|
            'includes/class-wpdd-roles.php',
 | 
						|
            'includes/class-wpdd-metaboxes.php',
 | 
						|
            'includes/class-wpdd-shortcodes.php',
 | 
						|
            'includes/class-wpdd-paypal.php',
 | 
						|
            'includes/class-wpdd-download-handler.php',
 | 
						|
            'includes/class-wpdd-customer.php',
 | 
						|
            'includes/class-wpdd-license-manager.php',
 | 
						|
            'includes/class-wpdd-api.php',
 | 
						|
            'includes/class-wpdd-orders.php',
 | 
						|
            'includes/class-wpdd-file-protection.php',
 | 
						|
            'includes/class-wpdd-watermark.php',
 | 
						|
            'includes/class-wpdd-ajax.php',
 | 
						|
            'includes/class-wpdd-creator.php',
 | 
						|
            'includes/class-wpdd-earnings-processor.php',
 | 
						|
            'includes/class-wpdd-paypal-payouts.php',
 | 
						|
            'includes/class-wpdd-email.php'
 | 
						|
        );
 | 
						|
        
 | 
						|
        foreach ($files as $file) {
 | 
						|
            $file_path = WPDD_PLUGIN_PATH . $file;
 | 
						|
            if (file_exists($file_path)) {
 | 
						|
                require_once $file_path;
 | 
						|
            } else {
 | 
						|
                error_log('WPDD Error: File not found: ' . $file_path);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (is_admin()) {
 | 
						|
            $admin_files = array(
 | 
						|
                'admin/class-wpdd-admin.php',
 | 
						|
                'admin/class-wpdd-settings.php',
 | 
						|
                'admin/class-wpdd-admin-payouts.php',
 | 
						|
                'admin/class-wpdd-order-manager.php'
 | 
						|
            );
 | 
						|
            
 | 
						|
            foreach ($admin_files as $file) {
 | 
						|
                $file_path = WPDD_PLUGIN_PATH . $file;
 | 
						|
                if (file_exists($file_path)) {
 | 
						|
                    require_once $file_path;
 | 
						|
                } else {
 | 
						|
                    error_log('WPDD Error: Admin file not found: ' . $file_path);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    private function init_hooks() {
 | 
						|
        register_activation_hook(__FILE__, array('WPDD_Install', 'activate'));
 | 
						|
        register_deactivation_hook(__FILE__, array('WPDD_Install', 'deactivate'));
 | 
						|
        
 | 
						|
        // Sessions will be started only when needed (in PayPal class)
 | 
						|
        add_action('init', array($this, 'init'));
 | 
						|
        add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
 | 
						|
        
 | 
						|
        if (is_admin()) {
 | 
						|
            add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    public function init() {
 | 
						|
        load_plugin_textdomain('wp-digital-download', false, dirname(WPDD_PLUGIN_BASENAME) . '/languages');
 | 
						|
        
 | 
						|
        // Initialize classes with existence checks
 | 
						|
        if (class_exists('WPDD_Roles')) {
 | 
						|
            WPDD_Roles::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Roles class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Post_Types')) {
 | 
						|
            WPDD_Post_Types::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Post_Types class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Metaboxes')) {
 | 
						|
            WPDD_Metaboxes::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Metaboxes class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Shortcodes')) {
 | 
						|
            WPDD_Shortcodes::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Shortcodes class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_PayPal')) {
 | 
						|
            WPDD_PayPal::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_PayPal class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Download_Handler')) {
 | 
						|
            WPDD_Download_Handler::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Download_Handler class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Customer')) {
 | 
						|
            WPDD_Customer::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Customer class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_File_Protection')) {
 | 
						|
            WPDD_File_Protection::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_File_Protection class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Creator')) {
 | 
						|
            WPDD_Creator::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Creator class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_PayPal_Payouts')) {
 | 
						|
            WPDD_PayPal_Payouts::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_PayPal_Payouts class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Fix search functionality to include wpdd_product post type
 | 
						|
        add_filter('pre_get_posts', array($this, 'include_custom_post_types_in_search'));
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Ajax')) {
 | 
						|
            WPDD_Ajax::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Ajax class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_License_Manager')) {
 | 
						|
            WPDD_License_Manager::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_License_Manager class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_API')) {
 | 
						|
            WPDD_API::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_API class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Earnings_Processor')) {
 | 
						|
            WPDD_Earnings_Processor::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Earnings_Processor class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (class_exists('WPDD_Email')) {
 | 
						|
            WPDD_Email::init();
 | 
						|
        } else {
 | 
						|
            error_log('WPDD Error: WPDD_Email class not found');
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (is_admin()) {
 | 
						|
            if (class_exists('WPDD_Setup')) {
 | 
						|
                WPDD_Setup::init();
 | 
						|
            } else {
 | 
						|
                error_log('WPDD Error: WPDD_Setup class not found');
 | 
						|
            }
 | 
						|
            
 | 
						|
            if (class_exists('WPDD_Admin')) {
 | 
						|
                WPDD_Admin::init();
 | 
						|
            } else {
 | 
						|
                error_log('WPDD Error: WPDD_Admin class not found');
 | 
						|
            }
 | 
						|
            
 | 
						|
            if (class_exists('WPDD_Settings')) {
 | 
						|
                WPDD_Settings::init();
 | 
						|
            } else {
 | 
						|
                error_log('WPDD Error: WPDD_Settings class not found');
 | 
						|
            }
 | 
						|
            
 | 
						|
            if (class_exists('WPDD_Order_Manager')) {
 | 
						|
                WPDD_Order_Manager::init();
 | 
						|
            } else {
 | 
						|
                error_log('WPDD Error: WPDD_Order_Manager class not found');
 | 
						|
            }
 | 
						|
            
 | 
						|
            // Include tools page
 | 
						|
            require_once WPDD_PLUGIN_PATH . 'admin/wpdd-tools.php';
 | 
						|
            
 | 
						|
            // Run database migration if needed
 | 
						|
            if (file_exists(WPDD_PLUGIN_PATH . 'admin/wpdd-db-migrate.php')) {
 | 
						|
                require_once WPDD_PLUGIN_PATH . 'admin/wpdd-db-migrate.php';
 | 
						|
                wpdd_migrate_database();
 | 
						|
            }
 | 
						|
            
 | 
						|
            // Initialize transaction history
 | 
						|
            if (file_exists(WPDD_PLUGIN_PATH . 'admin/class-wpdd-transaction-history.php')) {
 | 
						|
                require_once WPDD_PLUGIN_PATH . 'admin/class-wpdd-transaction-history.php';
 | 
						|
                if (class_exists('WPDD_Transaction_History')) {
 | 
						|
                    WPDD_Transaction_History::init();
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Safely start session if needed
 | 
						|
     * Returns true if session is available, false otherwise
 | 
						|
     */
 | 
						|
    public static function ensure_session() {
 | 
						|
        // Don't start sessions in admin or CLI
 | 
						|
        if (is_admin() || defined('WP_CLI')) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Don't start if headers already sent
 | 
						|
        if (headers_sent()) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Start session if we don't have one
 | 
						|
        if (session_status() === PHP_SESSION_NONE) {
 | 
						|
            return session_start();
 | 
						|
        }
 | 
						|
        
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
    
 | 
						|
    public function enqueue_scripts() {
 | 
						|
        // Use file modification time for cache busting
 | 
						|
        $frontend_css_ver = file_exists(WPDD_PLUGIN_PATH . 'assets/css/frontend.css') 
 | 
						|
            ? filemtime(WPDD_PLUGIN_PATH . 'assets/css/frontend.css') 
 | 
						|
            : WPDD_VERSION;
 | 
						|
            
 | 
						|
        $frontend_js_ver = file_exists(WPDD_PLUGIN_PATH . 'assets/js/frontend.js') 
 | 
						|
            ? filemtime(WPDD_PLUGIN_PATH . 'assets/js/frontend.js') 
 | 
						|
            : WPDD_VERSION;
 | 
						|
        
 | 
						|
        wp_enqueue_style(
 | 
						|
            'wpdd-frontend',
 | 
						|
            WPDD_PLUGIN_URL . 'assets/css/frontend.css',
 | 
						|
            array(),
 | 
						|
            $frontend_css_ver
 | 
						|
        );
 | 
						|
        
 | 
						|
        // Enqueue FontAwesome for copy icons
 | 
						|
        wp_enqueue_style(
 | 
						|
            'font-awesome',
 | 
						|
            'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css',
 | 
						|
            array(),
 | 
						|
            '6.0.0'
 | 
						|
        );
 | 
						|
        
 | 
						|
        wp_enqueue_script(
 | 
						|
            'wpdd-frontend',
 | 
						|
            WPDD_PLUGIN_URL . 'assets/js/frontend.js',
 | 
						|
            array('jquery'),
 | 
						|
            $frontend_js_ver,
 | 
						|
            true
 | 
						|
        );
 | 
						|
        
 | 
						|
        wp_localize_script('wpdd-frontend', 'wpdd_ajax', array(
 | 
						|
            'url' => admin_url('admin-ajax.php'),
 | 
						|
            'nonce' => wp_create_nonce('wpdd-ajax-nonce')
 | 
						|
        ));
 | 
						|
    }
 | 
						|
    
 | 
						|
    public function admin_enqueue_scripts($hook) {
 | 
						|
        global $post;
 | 
						|
        
 | 
						|
        if ((($hook == 'post.php' || $hook == 'post-new.php') && 
 | 
						|
            isset($post) && $post->post_type == 'wpdd_product') ||
 | 
						|
            ($hook == 'edit.php' && isset($_GET['post_type']) && $_GET['post_type'] == 'wpdd_product')) {
 | 
						|
            
 | 
						|
            // Use file modification time for cache busting
 | 
						|
            $admin_css_ver = file_exists(WPDD_PLUGIN_PATH . 'assets/css/admin.css') 
 | 
						|
                ? filemtime(WPDD_PLUGIN_PATH . 'assets/css/admin.css') 
 | 
						|
                : WPDD_VERSION;
 | 
						|
                
 | 
						|
            $admin_js_ver = file_exists(WPDD_PLUGIN_PATH . 'assets/js/admin.js') 
 | 
						|
                ? filemtime(WPDD_PLUGIN_PATH . 'assets/js/admin.js') 
 | 
						|
                : WPDD_VERSION;
 | 
						|
            
 | 
						|
            wp_enqueue_media();
 | 
						|
            wp_enqueue_style(
 | 
						|
                'wpdd-admin',
 | 
						|
                WPDD_PLUGIN_URL . 'assets/css/admin.css',
 | 
						|
                array(),
 | 
						|
                $admin_css_ver
 | 
						|
            );
 | 
						|
            
 | 
						|
            wp_enqueue_script(
 | 
						|
                'wpdd-admin',
 | 
						|
                WPDD_PLUGIN_URL . 'assets/js/admin.js',
 | 
						|
                array('jquery', 'jquery-ui-sortable'),
 | 
						|
                $admin_js_ver,
 | 
						|
                true
 | 
						|
            );
 | 
						|
            
 | 
						|
            // Add nonce for admin AJAX
 | 
						|
            wp_localize_script('wpdd-admin', 'wpdd_admin_nonce', wp_create_nonce('wpdd-admin-nonce'));
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Include custom post types in search results
 | 
						|
     * This fixes the search functionality to include wpdd_product posts in frontend searches
 | 
						|
     */
 | 
						|
    public function include_custom_post_types_in_search($query) {
 | 
						|
        // Apply to all frontend searches, not just main query
 | 
						|
        if (!is_admin() && $query->is_search()) {
 | 
						|
            $current_post_types = $query->get('post_type');
 | 
						|
            
 | 
						|
            // If post_type is already set to wpdd_product, don't override it
 | 
						|
            if ($current_post_types === 'wpdd_product') {
 | 
						|
                return $query;
 | 
						|
            }
 | 
						|
            
 | 
						|
            // Otherwise, include wpdd_product in searches
 | 
						|
            if (empty($current_post_types) || $current_post_types === 'post') {
 | 
						|
                $query->set('post_type', array('post', 'wpdd_product'));
 | 
						|
            } elseif (is_array($current_post_types)) {
 | 
						|
                $current_post_types[] = 'wpdd_product';
 | 
						|
                $query->set('post_type', array_unique($current_post_types));
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return $query;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
function wpdd() {
 | 
						|
    return WP_Digital_Download::instance();
 | 
						|
}
 | 
						|
 | 
						|
wpdd();
 | 
						|
 | 
						|
// Helper function for formatting prices
 | 
						|
if (!function_exists('wpdd_format_price')) {
 | 
						|
    function wpdd_format_price($price, $currency = 'USD') {
 | 
						|
        $currencies = array(
 | 
						|
            'USD' => '$',
 | 
						|
            'EUR' => '€',
 | 
						|
            'GBP' => '£',
 | 
						|
            'JPY' => '¥',
 | 
						|
            'AUD' => 'A$',
 | 
						|
            'CAD' => 'C$'
 | 
						|
        );
 | 
						|
        
 | 
						|
        $symbol = isset($currencies[$currency]) ? $currencies[$currency] : $currency . ' ';
 | 
						|
        return $symbol . number_format($price, 2);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Deactivation hook
 | 
						|
register_deactivation_hook(__FILE__, 'wpdd_deactivation');
 | 
						|
 | 
						|
function wpdd_deactivation() {
 | 
						|
    if (class_exists('WPDD_PayPal_Payouts')) {
 | 
						|
        WPDD_PayPal_Payouts::deactivate();
 | 
						|
    }
 | 
						|
} |