🔧 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>
		
			
				
	
	
		
			340 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
if (!defined('ABSPATH')) {
 | 
						|
    exit;
 | 
						|
}
 | 
						|
 | 
						|
class WPDD_Install {
 | 
						|
    
 | 
						|
    public static function activate() {
 | 
						|
        // Load required files if not already loaded
 | 
						|
        if (!class_exists('WPDD_Post_Types')) {
 | 
						|
            require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-post-types.php';
 | 
						|
        }
 | 
						|
        if (!class_exists('WPDD_Roles')) {
 | 
						|
            require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-roles.php';
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Register post types and taxonomies before flushing rules
 | 
						|
        WPDD_Post_Types::register_post_types();
 | 
						|
        WPDD_Post_Types::register_taxonomies();
 | 
						|
        
 | 
						|
        self::create_tables();
 | 
						|
        self::create_upload_protection();
 | 
						|
        
 | 
						|
        // Set flag to show setup notice instead of creating pages automatically
 | 
						|
        if (!get_option('wpdd_setup_completed')) {
 | 
						|
            add_option('wpdd_show_setup_notice', true);
 | 
						|
        }
 | 
						|
        
 | 
						|
        WPDD_Roles::create_roles();
 | 
						|
        
 | 
						|
        // Flush rewrite rules after post types are registered
 | 
						|
        flush_rewrite_rules();
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static function deactivate() {
 | 
						|
        flush_rewrite_rules();
 | 
						|
    }
 | 
						|
    
 | 
						|
    private static function create_tables() {
 | 
						|
        global $wpdb;
 | 
						|
        
 | 
						|
        $charset_collate = $wpdb->get_charset_collate();
 | 
						|
        
 | 
						|
        $sql = array();
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_orders (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            order_number varchar(50) NOT NULL,
 | 
						|
            product_id bigint(20) NOT NULL,
 | 
						|
            customer_id bigint(20) NOT NULL,
 | 
						|
            creator_id bigint(20) NOT NULL,
 | 
						|
            status varchar(20) NOT NULL DEFAULT 'pending',
 | 
						|
            payment_method varchar(50) DEFAULT NULL,
 | 
						|
            transaction_id varchar(100) DEFAULT NULL,
 | 
						|
            amount decimal(10,2) NOT NULL,
 | 
						|
            currency varchar(10) NOT NULL DEFAULT 'USD',
 | 
						|
            customer_email varchar(100) NOT NULL,
 | 
						|
            customer_name varchar(100) DEFAULT NULL,
 | 
						|
            purchase_date datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            download_count int(11) DEFAULT 0,
 | 
						|
            notes text DEFAULT NULL,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY order_number (order_number),
 | 
						|
            KEY product_id (product_id),
 | 
						|
            KEY customer_id (customer_id),
 | 
						|
            KEY creator_id (creator_id),
 | 
						|
            KEY status (status)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_downloads (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            order_id bigint(20) NOT NULL,
 | 
						|
            product_id bigint(20) NOT NULL,
 | 
						|
            customer_id bigint(20) NOT NULL,
 | 
						|
            file_id varchar(100) NOT NULL,
 | 
						|
            download_date datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            ip_address varchar(45) DEFAULT NULL,
 | 
						|
            user_agent text DEFAULT NULL,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY order_id (order_id),
 | 
						|
            KEY product_id (product_id),
 | 
						|
            KEY customer_id (customer_id)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_download_links (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            order_id bigint(20) NOT NULL,
 | 
						|
            token varchar(64) NOT NULL,
 | 
						|
            expires_at datetime NOT NULL,
 | 
						|
            download_count int(11) DEFAULT 0,
 | 
						|
            max_downloads int(11) DEFAULT 5,
 | 
						|
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            UNIQUE KEY token (token),
 | 
						|
            KEY order_id (order_id)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_creator_earnings (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            creator_id bigint(20) NOT NULL,
 | 
						|
            order_id bigint(20) NOT NULL,
 | 
						|
            product_id bigint(20) NOT NULL,
 | 
						|
            sale_amount decimal(10,2) NOT NULL,
 | 
						|
            commission_rate decimal(5,2) NOT NULL,
 | 
						|
            creator_earning decimal(10,2) NOT NULL,
 | 
						|
            payout_id bigint(20) DEFAULT NULL,
 | 
						|
            payout_status varchar(20) DEFAULT 'pending',
 | 
						|
            available_at datetime DEFAULT NULL,
 | 
						|
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY creator_id (creator_id),
 | 
						|
            KEY order_id (order_id),
 | 
						|
            KEY product_id (product_id),
 | 
						|
            KEY payout_id (payout_id),
 | 
						|
            KEY payout_status (payout_status),
 | 
						|
            KEY available_at (available_at)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_payouts (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            creator_id bigint(20) NOT NULL,
 | 
						|
            amount decimal(10,2) NOT NULL,
 | 
						|
            currency varchar(10) NOT NULL,
 | 
						|
            paypal_email varchar(100) NOT NULL,
 | 
						|
            transaction_id varchar(100) DEFAULT NULL,
 | 
						|
            status varchar(20) NOT NULL DEFAULT 'pending',
 | 
						|
            payout_method varchar(20) NOT NULL DEFAULT 'manual',
 | 
						|
            notes text DEFAULT NULL,
 | 
						|
            processed_by bigint(20) DEFAULT NULL,
 | 
						|
            processed_at datetime DEFAULT NULL,
 | 
						|
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY creator_id (creator_id),
 | 
						|
            KEY status (status),
 | 
						|
            KEY transaction_id (transaction_id)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_balance_adjustments (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            creator_id bigint(20) NOT NULL,
 | 
						|
            adjustment_type varchar(20) NOT NULL,
 | 
						|
            amount decimal(10,2) NOT NULL,
 | 
						|
            previous_balance decimal(10,2) NOT NULL,
 | 
						|
            new_balance decimal(10,2) NOT NULL,
 | 
						|
            reason text NOT NULL,
 | 
						|
            adjusted_by bigint(20) NOT NULL,
 | 
						|
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY creator_id (creator_id),
 | 
						|
            KEY adjusted_by (adjusted_by)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        // Software Licensing Tables
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_email_logs (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            to_email varchar(100) NOT NULL,
 | 
						|
            subject varchar(255) NOT NULL,
 | 
						|
            message longtext NOT NULL,
 | 
						|
            status varchar(20) NOT NULL DEFAULT 'sent',
 | 
						|
            email_type varchar(50) DEFAULT 'general',
 | 
						|
            error_message text DEFAULT NULL,
 | 
						|
            sent_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY to_email (to_email),
 | 
						|
            KEY status (status),
 | 
						|
            KEY email_type (email_type),
 | 
						|
            KEY sent_at (sent_at)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_licenses (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            license_key varchar(64) NOT NULL,
 | 
						|
            product_id bigint(20) NOT NULL,
 | 
						|
            order_id bigint(20) NOT NULL,
 | 
						|
            customer_id bigint(20) NOT NULL,
 | 
						|
            customer_email varchar(100) NOT NULL,
 | 
						|
            status varchar(20) NOT NULL DEFAULT 'active',
 | 
						|
            activations_count int(11) DEFAULT 0,
 | 
						|
            max_activations int(11) DEFAULT 1,
 | 
						|
            expires_at datetime DEFAULT NULL,
 | 
						|
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            last_checked datetime DEFAULT NULL,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            UNIQUE KEY license_key (license_key),
 | 
						|
            KEY product_id (product_id),
 | 
						|
            KEY order_id (order_id),
 | 
						|
            KEY customer_id (customer_id),
 | 
						|
            KEY status (status)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_license_activations (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            license_id bigint(20) NOT NULL,
 | 
						|
            license_key varchar(64) NOT NULL,
 | 
						|
            site_url varchar(255) NOT NULL,
 | 
						|
            site_name varchar(255) DEFAULT NULL,
 | 
						|
            activated_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            last_checked datetime DEFAULT NULL,
 | 
						|
            wp_version varchar(20) DEFAULT NULL,
 | 
						|
            php_version varchar(20) DEFAULT NULL,
 | 
						|
            status varchar(20) NOT NULL DEFAULT 'active',
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY license_id (license_id),
 | 
						|
            KEY license_key (license_key),
 | 
						|
            KEY site_url (site_url),
 | 
						|
            KEY status (status)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_software_versions (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            product_id bigint(20) NOT NULL,
 | 
						|
            version varchar(20) NOT NULL,
 | 
						|
            changelog text DEFAULT NULL,
 | 
						|
            release_notes text DEFAULT NULL,
 | 
						|
            download_url text DEFAULT NULL,
 | 
						|
            package_url text DEFAULT NULL,
 | 
						|
            min_wp_version varchar(20) DEFAULT NULL,
 | 
						|
            tested_wp_version varchar(20) DEFAULT NULL,
 | 
						|
            min_php_version varchar(20) DEFAULT NULL,
 | 
						|
            release_date datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            git_tag varchar(100) DEFAULT NULL,
 | 
						|
            git_commit varchar(100) DEFAULT NULL,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY product_id (product_id),
 | 
						|
            KEY version (version),
 | 
						|
            KEY release_date (release_date)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_webhook_events (
 | 
						|
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
						|
            product_id bigint(20) NOT NULL,
 | 
						|
            event_type varchar(50) NOT NULL,
 | 
						|
            payload text DEFAULT NULL,
 | 
						|
            processed varchar(20) NOT NULL DEFAULT 'pending',
 | 
						|
            error_message text DEFAULT NULL,
 | 
						|
            received_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
						|
            processed_at datetime DEFAULT NULL,
 | 
						|
            PRIMARY KEY (id),
 | 
						|
            KEY product_id (product_id),
 | 
						|
            KEY processed (processed),
 | 
						|
            KEY received_at (received_at)
 | 
						|
        ) $charset_collate;";
 | 
						|
        
 | 
						|
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
 | 
						|
        
 | 
						|
        foreach ($sql as $query) {
 | 
						|
            dbDelta($query);
 | 
						|
        }
 | 
						|
        
 | 
						|
        update_option('wpdd_db_version', WPDD_VERSION);
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static function create_pages_optional() {
 | 
						|
        return self::create_pages();
 | 
						|
    }
 | 
						|
    
 | 
						|
    private static function create_pages() {
 | 
						|
        $pages = array(
 | 
						|
            'shop' => array(
 | 
						|
                'title' => __('Shop', 'wp-digital-download'),
 | 
						|
                'content' => '[wpdd_shop]',
 | 
						|
                'option' => 'wpdd_shop_page_id'
 | 
						|
            ),
 | 
						|
            'my-purchases' => array(
 | 
						|
                'title' => __('My Purchases', 'wp-digital-download'),
 | 
						|
                'content' => '[wpdd_customer_purchases]',
 | 
						|
                'option' => 'wpdd_purchases_page_id'
 | 
						|
            ),
 | 
						|
            'checkout' => array(
 | 
						|
                'title' => __('Checkout', 'wp-digital-download'),
 | 
						|
                'content' => '[wpdd_checkout]',
 | 
						|
                'option' => 'wpdd_checkout_page_id'
 | 
						|
            ),
 | 
						|
            'thank-you' => array(
 | 
						|
                'title' => __('Thank You', 'wp-digital-download'),
 | 
						|
                'content' => '[wpdd_thank_you]',
 | 
						|
                'option' => 'wpdd_thank_you_page_id'
 | 
						|
            )
 | 
						|
        );
 | 
						|
        
 | 
						|
        $created_pages = array();
 | 
						|
        
 | 
						|
        foreach ($pages as $slug => $page) {
 | 
						|
            // Check if page already exists
 | 
						|
            $existing_page_id = get_option($page['option']);
 | 
						|
            if ($existing_page_id && get_post($existing_page_id)) {
 | 
						|
                continue; // Page already exists, skip creation
 | 
						|
            }
 | 
						|
            
 | 
						|
            // Check if a page with this slug already exists
 | 
						|
            $existing_page = get_page_by_path($slug);
 | 
						|
            if ($existing_page) {
 | 
						|
                update_option($page['option'], $existing_page->ID);
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            
 | 
						|
            // Create the page
 | 
						|
            $page_id = wp_insert_post(array(
 | 
						|
                'post_title' => $page['title'],
 | 
						|
                'post_content' => $page['content'],
 | 
						|
                'post_status' => 'publish',
 | 
						|
                'post_type' => 'page',
 | 
						|
                'post_name' => $slug
 | 
						|
            ));
 | 
						|
            
 | 
						|
            if ($page_id && !is_wp_error($page_id)) {
 | 
						|
                update_option($page['option'], $page_id);
 | 
						|
                $created_pages[] = $page_id;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        return $created_pages;
 | 
						|
    }
 | 
						|
    
 | 
						|
    private static function create_upload_protection() {
 | 
						|
        $upload_dir = wp_upload_dir();
 | 
						|
        $protection_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
						|
        
 | 
						|
        if (!file_exists($protection_dir)) {
 | 
						|
            wp_mkdir_p($protection_dir);
 | 
						|
        }
 | 
						|
        
 | 
						|
        $htaccess_content = "Options -Indexes\n";
 | 
						|
        $htaccess_content .= "deny from all\n";
 | 
						|
        
 | 
						|
        $htaccess_file = trailingslashit($protection_dir) . '.htaccess';
 | 
						|
        
 | 
						|
        if (!file_exists($htaccess_file)) {
 | 
						|
            file_put_contents($htaccess_file, $htaccess_content);
 | 
						|
        }
 | 
						|
        
 | 
						|
        $index_content = "<?php\n// Silence is golden.\n";
 | 
						|
        $index_file = trailingslashit($protection_dir) . 'index.php';
 | 
						|
        
 | 
						|
        if (!file_exists($index_file)) {
 | 
						|
            file_put_contents($index_file, $index_content);
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |