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>
This commit is contained in:
		
							
								
								
									
										84
									
								
								.gitea/workflows/release.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								.gitea/workflows/release.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
name: Create Release
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.CI_USER }}
 | 
			
		||||
          password: ${{ secrets.CI_TOKEN }}
 | 
			
		||||
          fetch-depth: 0  # Important: Fetch all history for commit messages
 | 
			
		||||
 | 
			
		||||
      - name: Get version
 | 
			
		||||
        id: get_version
 | 
			
		||||
        run: |
 | 
			
		||||
          if [ "${{ github.ref_type }}" = "tag" ]; then
 | 
			
		||||
            echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
 | 
			
		||||
          else
 | 
			
		||||
            echo "version=$(date +'%Y.%m.%d-%H%M')" >> $GITHUB_OUTPUT
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
      - name: Generate release notes
 | 
			
		||||
        id: release_notes
 | 
			
		||||
        run: |
 | 
			
		||||
          # Find the most recent tag
 | 
			
		||||
          LATEST_TAG=$(git describe --tags --abbrev=0 --always 2>/dev/null || echo "none")
 | 
			
		||||
          
 | 
			
		||||
          if [ "$LATEST_TAG" = "none" ]; then
 | 
			
		||||
            # If no previous tag exists, get all commits
 | 
			
		||||
            COMMITS=$(git log --pretty=format:"* %s (%h)" --no-merges)
 | 
			
		||||
          else
 | 
			
		||||
            # Get commits since the last tag
 | 
			
		||||
            COMMITS=$(git log --pretty=format:"* %s (%h)" ${LATEST_TAG}..HEAD --no-merges)
 | 
			
		||||
          fi
 | 
			
		||||
          
 | 
			
		||||
          # Create release notes with header (without encoding newlines)
 | 
			
		||||
          echo "notes<<EOF" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "## What's New in ${{ steps.get_version.outputs.version }}" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "$COMMITS" >> $GITHUB_OUTPUT
 | 
			
		||||
          echo "EOF" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Update plugin version
 | 
			
		||||
        run: |
 | 
			
		||||
          # Replace version placeholder in plugin header
 | 
			
		||||
          sed -i "s/Version: .*/Version: ${{ steps.get_version.outputs.version }}/" wp-digital-download.php
 | 
			
		||||
          # Replace version placeholder in PHP constant
 | 
			
		||||
          sed -i "s/define('WPDD_VERSION', '[^']*');/define('WPDD_VERSION', '${{ steps.get_version.outputs.version }}');/" wp-digital-download.php
 | 
			
		||||
          # Verify the changes were made
 | 
			
		||||
          echo "Plugin header version:"
 | 
			
		||||
          grep "Version:" wp-digital-download.php
 | 
			
		||||
          echo "PHP constant version:"
 | 
			
		||||
          grep "WPDD_VERSION" wp-digital-download.php
 | 
			
		||||
 | 
			
		||||
      - name: Create ZIP archive
 | 
			
		||||
        run: |
 | 
			
		||||
          # Create a temp directory with the correct plugin folder name
 | 
			
		||||
          mkdir -p /tmp/wp-digital-download
 | 
			
		||||
 | 
			
		||||
          # Copy files to the temp directory (excluding git and other unnecessary files)
 | 
			
		||||
          cp -r * /tmp/wp-digital-download/ 2>/dev/null || true
 | 
			
		||||
 | 
			
		||||
          # Exclude .git, .gitea, and .playwright-mcp directories
 | 
			
		||||
          rm -rf /tmp/wp-digital-download/.git /tmp/wp-digital-download/.gitea /tmp/wp-digital-download/.playwright-mcp 2>/dev/null || true
 | 
			
		||||
 | 
			
		||||
          # Create the ZIP file with the proper structure
 | 
			
		||||
          cd /tmp
 | 
			
		||||
          zip -r $GITHUB_WORKSPACE/wp-digital-download.zip wp-digital-download
 | 
			
		||||
 | 
			
		||||
      - name: Create Release
 | 
			
		||||
        uses: softprops/action-gh-release@v2
 | 
			
		||||
        with:
 | 
			
		||||
          token: "${{ secrets.REPO_TOKEN }}"
 | 
			
		||||
          title: "WP Digital Download Release ${{ steps.get_version.outputs.version }}"
 | 
			
		||||
          tag_name: ${{ steps.get_version.outputs.version }}
 | 
			
		||||
          body: "${{ steps.release_notes.outputs.notes }}"
 | 
			
		||||
          files: |
 | 
			
		||||
            wp-digital-download.zip
 | 
			
		||||
							
								
								
									
										57
									
								
								.gitea/workflows/update-version.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								.gitea/workflows/update-version.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
name: Update Plugin Version
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  release:
 | 
			
		||||
    types: [created, edited]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  update-version:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout code
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - name: Get release tag
 | 
			
		||||
        id: get_tag
 | 
			
		||||
        run: echo "TAG=${GITEA_REF#refs/tags/}" >> $GITEA_ENV
 | 
			
		||||
 | 
			
		||||
      - name: Update version in plugin file
 | 
			
		||||
        run: |
 | 
			
		||||
          # Replace version in plugin header
 | 
			
		||||
          sed -i "s/Version: .*/Version: ${{ env.TAG }}/" wp-digital-download.php
 | 
			
		||||
          
 | 
			
		||||
          # Replace version in PHP constant
 | 
			
		||||
          sed -i "s/define('WPDD_VERSION', '[^']*');/define('WPDD_VERSION', '${{ env.TAG }}');/" wp-digital-download.php
 | 
			
		||||
          
 | 
			
		||||
          # Verify changes
 | 
			
		||||
          echo "Plugin header version:"
 | 
			
		||||
          grep "Version:" wp-digital-download.php
 | 
			
		||||
          echo "PHP constant version:"
 | 
			
		||||
          grep "WPDD_VERSION" wp-digital-download.php
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        run: |
 | 
			
		||||
          git config --local user.email "action@gitea.com"
 | 
			
		||||
          git config --local user.name "Gitea Action"
 | 
			
		||||
          git add wp-digital-download.php
 | 
			
		||||
          git commit -m "Update version to ${{ env.TAG }}"
 | 
			
		||||
          git push
 | 
			
		||||
 | 
			
		||||
      - name: Create plugin zip
 | 
			
		||||
        run: |
 | 
			
		||||
          mkdir -p /tmp/wp-digital-download
 | 
			
		||||
          rsync -av --exclude=".git" --exclude=".gitea" --exclude=".playwright-mcp" --exclude="build" . /tmp/wp-digital-download/
 | 
			
		||||
          cd /tmp
 | 
			
		||||
          zip -r $GITEA_WORK_DIR/wp-digital-download.zip wp-digital-download
 | 
			
		||||
 | 
			
		||||
      - name: Upload zip to release
 | 
			
		||||
        uses: actions/upload-release-asset@v1
 | 
			
		||||
        env:
 | 
			
		||||
          GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
 | 
			
		||||
        with:
 | 
			
		||||
          upload_url: ${{ gitea.event.release.upload_url }}
 | 
			
		||||
          asset_path: build/wp-digital-download.zip
 | 
			
		||||
          asset_name: wp-digital-download.zip
 | 
			
		||||
          asset_content_type: application/zip
 | 
			
		||||
							
								
								
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
# Playwright MCP downloads and browser automation files
 | 
			
		||||
.playwright-mcp/
 | 
			
		||||
 | 
			
		||||
# WordPress specific
 | 
			
		||||
wp-config.php
 | 
			
		||||
wp-content/uploads/
 | 
			
		||||
wp-content/upgrade/
 | 
			
		||||
wp-content/backup-db/
 | 
			
		||||
wp-content/advanced-cache.php
 | 
			
		||||
wp-content/wp-cache-config.php
 | 
			
		||||
wp-content/cache/
 | 
			
		||||
wp-content/backups/
 | 
			
		||||
 | 
			
		||||
# Plugin specific
 | 
			
		||||
node_modules/
 | 
			
		||||
*.log
 | 
			
		||||
.DS_Store
 | 
			
		||||
Thumbs.db
 | 
			
		||||
 | 
			
		||||
# IDE files
 | 
			
		||||
.vscode/
 | 
			
		||||
.idea/
 | 
			
		||||
*.swp
 | 
			
		||||
*.swo
 | 
			
		||||
 | 
			
		||||
# Temporary files
 | 
			
		||||
*.tmp
 | 
			
		||||
*.temp
 | 
			
		||||
@@ -92,7 +92,13 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
                    </div>
 | 
			
		||||
                <?php elseif ($_GET['message'] === 'error') : ?>
 | 
			
		||||
                    <div class="notice notice-error is-dismissible">
 | 
			
		||||
                        <p><?php _e('Error processing payout. Please try again.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                        <p>
 | 
			
		||||
                            <?php _e('Error processing payout:', 'wp-digital-download'); ?>
 | 
			
		||||
                            <?php 
 | 
			
		||||
                            $error_detail = isset($_GET['error_detail']) ? sanitize_text_field(urldecode($_GET['error_detail'])) : '';
 | 
			
		||||
                            echo $error_detail ? '<strong>' . esc_html($error_detail) . '</strong>' : __('Unknown error occurred. Please try again.', 'wp-digital-download'); 
 | 
			
		||||
                            ?>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <?php elseif ($_GET['message'] === 'balance_adjusted') : ?>
 | 
			
		||||
                    <div class="notice notice-success is-dismissible">
 | 
			
		||||
@@ -166,19 +172,26 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th><label for="creator_id"><?php _e('Creator', 'wp-digital-download'); ?></label></th>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <select name="creator_id" id="creator_id" required style="width: 100%;">
 | 
			
		||||
                                    <select name="creator_id" id="creator_id" class="wpdd-creator-select" required style="width: 100%;">
 | 
			
		||||
                                        <option value=""><?php _e('Select Creator', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                        <?php 
 | 
			
		||||
                                        $all_creators = get_users(array('role' => 'wpdd_creator'));
 | 
			
		||||
                                        foreach ($all_creators as $creator) : 
 | 
			
		||||
                                            $paypal_email = get_user_meta($creator->ID, 'wpdd_paypal_email', true);
 | 
			
		||||
                                            $balance = WPDD_Creator::get_creator_balance($creator->ID);
 | 
			
		||||
                                        ?>
 | 
			
		||||
                                        <option value="<?php echo esc_attr($creator->ID); ?>" <?php echo empty($paypal_email) ? 'disabled' : ''; ?>>
 | 
			
		||||
                                        <option value="<?php echo esc_attr($creator->ID); ?>" 
 | 
			
		||||
                                                data-balance="<?php echo esc_attr($balance); ?>" 
 | 
			
		||||
                                                data-paypal="<?php echo esc_attr($paypal_email); ?>"
 | 
			
		||||
                                                <?php echo empty($paypal_email) ? 'disabled' : ''; ?>>
 | 
			
		||||
                                            <?php echo esc_html($creator->display_name); ?>
 | 
			
		||||
                                            <?php echo empty($paypal_email) ? ' (' . __('No PayPal email', 'wp-digital-download') . ')' : ' (' . esc_html($paypal_email) . ')'; ?>
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                        <?php endforeach; ?>
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                    <div id="creator_balance_display" style="margin-top: 10px; display: none;">
 | 
			
		||||
                                        <strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong> <span id="creator_balance_amount"></span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr>
 | 
			
		||||
@@ -217,14 +230,19 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th><label for="adj_creator_id"><?php _e('Creator', 'wp-digital-download'); ?></label></th>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <select name="creator_id" id="adj_creator_id" required style="width: 100%;">
 | 
			
		||||
                                    <select name="creator_id" id="adj_creator_id" class="wpdd-creator-select" required style="width: 100%;">
 | 
			
		||||
                                        <option value=""><?php _e('Select Creator', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                        <?php foreach ($all_creators as $creator) : ?>
 | 
			
		||||
                                        <option value="<?php echo esc_attr($creator->ID); ?>">
 | 
			
		||||
                                        <?php foreach ($all_creators as $creator) : 
 | 
			
		||||
                                            $balance = WPDD_Creator::get_creator_balance($creator->ID);
 | 
			
		||||
                                        ?>
 | 
			
		||||
                                        <option value="<?php echo esc_attr($creator->ID); ?>" data-balance="<?php echo esc_attr($balance); ?>">
 | 
			
		||||
                                            <?php echo esc_html($creator->display_name); ?>
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                        <?php endforeach; ?>
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                    <div id="adj_creator_balance_display" style="margin-top: 10px; display: none;">
 | 
			
		||||
                                        <strong><?php _e('Current Balance:', 'wp-digital-download'); ?></strong> <span id="adj_creator_balance_amount"></span>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr>
 | 
			
		||||
@@ -497,10 +515,11 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
        
 | 
			
		||||
        $result = self::create_payout($creator_id);
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
        if ($result['success']) {
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=success'));
 | 
			
		||||
        } else {
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=error'));
 | 
			
		||||
            $error_message = urlencode($result['error'] ?? 'Unknown error occurred');
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=error&error_detail=' . $error_message));
 | 
			
		||||
        }
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
@@ -543,11 +562,28 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
    public static function create_payout($creator_id, $method = 'manual') {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Validate creator
 | 
			
		||||
        $creator = get_userdata($creator_id);
 | 
			
		||||
        if (!$creator) {
 | 
			
		||||
            return array('success' => false, 'error' => 'Creator not found');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $balance = WPDD_Creator::get_creator_balance($creator_id);
 | 
			
		||||
        $paypal_email = get_user_meta($creator_id, 'wpdd_paypal_email', true);
 | 
			
		||||
        
 | 
			
		||||
        if ($balance <= 0 || empty($paypal_email)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        if ($balance <= 0) {
 | 
			
		||||
            return array('success' => false, 'error' => 'Creator has zero balance to payout');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (empty($paypal_email)) {
 | 
			
		||||
            return array('success' => false, 'error' => 'Creator has no PayPal email configured');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Validate PayPal credentials are configured
 | 
			
		||||
        $client_id = get_option('wpdd_paypal_client_id');
 | 
			
		||||
        $secret = get_option('wpdd_paypal_secret');
 | 
			
		||||
        if (empty($client_id) || empty($secret)) {
 | 
			
		||||
            return array('success' => false, 'error' => 'PayPal credentials not configured in settings');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $currency = get_option('wpdd_currency', 'USD');
 | 
			
		||||
@@ -571,6 +607,10 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
        
 | 
			
		||||
        $payout_id = $wpdb->insert_id;
 | 
			
		||||
        
 | 
			
		||||
        if (!$payout_id) {
 | 
			
		||||
            return array('success' => false, 'error' => 'Failed to create payout record in database');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Try to process via PayPal API
 | 
			
		||||
        $result = WPDD_PayPal_Payouts::process_payout($payout_id);
 | 
			
		||||
        
 | 
			
		||||
@@ -588,10 +628,25 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
                array('%d')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            // Mark all available earnings for this creator as paid
 | 
			
		||||
            $wpdb->update(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_creator_earnings',
 | 
			
		||||
                array(
 | 
			
		||||
                    'payout_id' => $payout_id,
 | 
			
		||||
                    'payout_status' => 'paid'
 | 
			
		||||
                ),
 | 
			
		||||
                array(
 | 
			
		||||
                    'creator_id' => $creator_id,
 | 
			
		||||
                    'payout_status' => 'available'
 | 
			
		||||
                ),
 | 
			
		||||
                array('%d', '%s'),
 | 
			
		||||
                array('%d', '%s')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            // Reset creator balance
 | 
			
		||||
            update_user_meta($creator_id, 'wpdd_creator_balance', 0);
 | 
			
		||||
            
 | 
			
		||||
            return true;
 | 
			
		||||
            return array('success' => true, 'message' => 'Payout processed successfully');
 | 
			
		||||
        } else {
 | 
			
		||||
            // Update with error
 | 
			
		||||
            $wpdb->update(
 | 
			
		||||
@@ -605,7 +660,7 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
                array('%d')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            return false;
 | 
			
		||||
            return array('success' => false, 'error' => $result['error'] ?? 'PayPal payout processing failed');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@@ -813,7 +868,7 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get current balance
 | 
			
		||||
        $current_balance = floatval(get_user_meta($creator_id, 'wpdd_balance', true));
 | 
			
		||||
        $current_balance = floatval(get_user_meta($creator_id, 'wpdd_creator_balance', true));
 | 
			
		||||
        
 | 
			
		||||
        // Calculate new balance
 | 
			
		||||
        if ($adjustment_type === 'add') {
 | 
			
		||||
@@ -823,7 +878,7 @@ class WPDD_Admin_Payouts {
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Update the balance
 | 
			
		||||
        update_user_meta($creator_id, 'wpdd_balance', $new_balance);
 | 
			
		||||
        update_user_meta($creator_id, 'wpdd_creator_balance', $new_balance);
 | 
			
		||||
        
 | 
			
		||||
        // Create a record of this adjustment
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ class WPDD_Admin {
 | 
			
		||||
        add_action('pre_get_posts', array(__CLASS__, 'sort_products_by_column'));
 | 
			
		||||
        add_action('pre_get_posts', array(__CLASS__, 'filter_creator_products'));
 | 
			
		||||
        add_action('admin_init', array(__CLASS__, 'handle_admin_actions'));
 | 
			
		||||
        add_action('wp_ajax_wpdd_sync_software_product', array(__CLASS__, 'handle_sync_software_product'));
 | 
			
		||||
        add_action('wp_ajax_wpdd_regenerate_licenses', array(__CLASS__, 'handle_regenerate_licenses'));
 | 
			
		||||
        
 | 
			
		||||
        // Initialize admin payouts
 | 
			
		||||
        if (class_exists('WPDD_Admin_Payouts')) {
 | 
			
		||||
@@ -145,7 +147,17 @@ class WPDD_Admin {
 | 
			
		||||
            case 'wpdd_files':
 | 
			
		||||
                $files = get_post_meta($post_id, '_wpdd_files', true);
 | 
			
		||||
                $count = is_array($files) ? count($files) : 0;
 | 
			
		||||
                $product_type = get_post_meta($post_id, '_wpdd_product_type', true);
 | 
			
		||||
                
 | 
			
		||||
                echo $count;
 | 
			
		||||
                
 | 
			
		||||
                // Add sync button for software license products with no files
 | 
			
		||||
                if ($product_type === 'software_license' && $count === 0) {
 | 
			
		||||
                    $nonce = wp_create_nonce('wpdd_sync_product_' . $post_id);
 | 
			
		||||
                    echo '<br><button type="button" class="button button-small wpdd-sync-product" data-product-id="' . $post_id . '" data-nonce="' . $nonce . '">';
 | 
			
		||||
                    echo __('Sync Files', 'wp-digital-download');
 | 
			
		||||
                    echo '</button>';
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -1232,4 +1244,64 @@ class WPDD_Admin {
 | 
			
		||||
        wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-creator-payouts&message=payout_requested'));
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_sync_software_product() {
 | 
			
		||||
        if (!current_user_can('edit_wpdd_products')) {
 | 
			
		||||
            wp_die('Unauthorized');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($_POST['product_id']) || !wp_verify_nonce($_POST['nonce'], 'wpdd_sync_product_' . $_POST['product_id'])) {
 | 
			
		||||
            wp_die('Invalid nonce');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product_id = intval($_POST['product_id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!class_exists('WPDD_API')) {
 | 
			
		||||
            require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-api.php';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = WPDD_API::sync_software_product($product_id, true);
 | 
			
		||||
        
 | 
			
		||||
        wp_send_json($result);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_regenerate_licenses() {
 | 
			
		||||
        if (!current_user_can('manage_options')) {
 | 
			
		||||
            wp_die('Unauthorized');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!wp_verify_nonce($_POST['nonce'], 'wpdd_regenerate_licenses')) {
 | 
			
		||||
            wp_die('Invalid nonce');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Find completed orders for software license products that don't have license keys
 | 
			
		||||
        $orders = $wpdb->get_results("
 | 
			
		||||
            SELECT o.*, pm.meta_value as product_type 
 | 
			
		||||
            FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
            LEFT JOIN {$wpdb->postmeta} pm ON o.product_id = pm.post_id AND pm.meta_key = '_wpdd_product_type'
 | 
			
		||||
            LEFT JOIN {$wpdb->prefix}wpdd_licenses l ON o.id = l.order_id
 | 
			
		||||
            WHERE o.status = 'completed' 
 | 
			
		||||
            AND pm.meta_value = 'software_license'
 | 
			
		||||
            AND l.license_key IS NULL
 | 
			
		||||
        ");
 | 
			
		||||
        
 | 
			
		||||
        $generated = 0;
 | 
			
		||||
        
 | 
			
		||||
        foreach ($orders as $order) {
 | 
			
		||||
            if (class_exists('WPDD_License_Manager')) {
 | 
			
		||||
                $license_key = WPDD_License_Manager::create_license($order->id);
 | 
			
		||||
                if ($license_key) {
 | 
			
		||||
                    $generated++;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_send_json(array(
 | 
			
		||||
            'success' => true,
 | 
			
		||||
            'message' => sprintf('Generated %d license keys for existing orders.', $generated),
 | 
			
		||||
            'generated' => $generated
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										470
									
								
								admin/class-wpdd-order-manager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										470
									
								
								admin/class-wpdd-order-manager.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,470 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Order_Manager {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('admin_menu', array(__CLASS__, 'add_menu_page'));
 | 
			
		||||
        add_action('admin_post_wpdd_cancel_order', array(__CLASS__, 'handle_cancel_order'));
 | 
			
		||||
        add_action('admin_post_wpdd_refund_order', array(__CLASS__, 'handle_refund_order'));
 | 
			
		||||
        add_action('admin_post_wpdd_release_earnings', array(__CLASS__, 'handle_release_earnings'));
 | 
			
		||||
        add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_scripts'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_menu_page() {
 | 
			
		||||
        if (!current_user_can('manage_options')) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        add_submenu_page(
 | 
			
		||||
            'edit.php?post_type=wpdd_product',
 | 
			
		||||
            __('Order Management', 'wp-digital-download'),
 | 
			
		||||
            __('Order Manager', 'wp-digital-download'),
 | 
			
		||||
            'manage_options',
 | 
			
		||||
            'wpdd-order-manager',
 | 
			
		||||
            array(__CLASS__, 'render_page')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function enqueue_scripts($hook) {
 | 
			
		||||
        if ($hook !== 'wpdd_product_page_wpdd-order-manager') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_enqueue_script('wpdd-order-manager', WPDD_PLUGIN_URL . 'assets/js/admin-order-manager.js', array('jquery'), WPDD_VERSION, true);
 | 
			
		||||
        wp_localize_script('wpdd-order-manager', 'wpdd_order_manager', array(
 | 
			
		||||
            'ajax_url' => admin_url('admin-ajax.php'),
 | 
			
		||||
            'nonce' => wp_create_nonce('wpdd_order_manager'),
 | 
			
		||||
            'confirm_cancel' => __('Are you sure you want to cancel this order? This action cannot be undone.', 'wp-digital-download'),
 | 
			
		||||
            'confirm_refund' => __('Are you sure you want to process this refund? The customer will lose access to the product.', 'wp-digital-download'),
 | 
			
		||||
            'confirm_release' => __('Are you sure you want to release these earnings immediately?', 'wp-digital-download')
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_page() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get filter parameters
 | 
			
		||||
        $status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : 'all';
 | 
			
		||||
        $creator_filter = isset($_GET['creator']) ? intval($_GET['creator']) : 0;
 | 
			
		||||
        $date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : date('Y-m-d', strtotime('-30 days'));
 | 
			
		||||
        $date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
 | 
			
		||||
        
 | 
			
		||||
        // Build query
 | 
			
		||||
        $where_conditions = array('1=1');
 | 
			
		||||
        $query_params = array();
 | 
			
		||||
        
 | 
			
		||||
        if ($status_filter !== 'all') {
 | 
			
		||||
            $where_conditions[] = 'o.status = %s';
 | 
			
		||||
            $query_params[] = $status_filter;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($creator_filter > 0) {
 | 
			
		||||
            $where_conditions[] = 'p.post_author = %d';
 | 
			
		||||
            $query_params[] = $creator_filter;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($date_from) {
 | 
			
		||||
            $where_conditions[] = 'DATE(o.purchase_date) >= %s';
 | 
			
		||||
            $query_params[] = $date_from;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($date_to) {
 | 
			
		||||
            $where_conditions[] = 'DATE(o.purchase_date) <= %s';
 | 
			
		||||
            $query_params[] = $date_to;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $where_clause = implode(' AND ', $where_conditions);
 | 
			
		||||
        
 | 
			
		||||
        // Get orders with earnings status
 | 
			
		||||
        $orders = $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, 
 | 
			
		||||
                    p.post_title as product_name, 
 | 
			
		||||
                    u.display_name as creator_name,
 | 
			
		||||
                    e.payout_status,
 | 
			
		||||
                    e.available_at,
 | 
			
		||||
                    e.creator_earning,
 | 
			
		||||
                    e.id as earning_id
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             LEFT JOIN {$wpdb->users} u ON p.post_author = u.ID
 | 
			
		||||
             LEFT JOIN {$wpdb->prefix}wpdd_creator_earnings e ON o.id = e.order_id
 | 
			
		||||
             WHERE $where_clause
 | 
			
		||||
             ORDER BY o.purchase_date DESC
 | 
			
		||||
             LIMIT 100",
 | 
			
		||||
            $query_params
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        // Get creators for filter
 | 
			
		||||
        $creators = get_users(array('role' => 'wpdd_creator'));
 | 
			
		||||
        
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wrap">
 | 
			
		||||
            <h1><?php _e('Order Management', 'wp-digital-download'); ?></h1>
 | 
			
		||||
            
 | 
			
		||||
            <?php if (isset($_GET['message'])) : ?>
 | 
			
		||||
                <?php if ($_GET['message'] === 'cancelled') : ?>
 | 
			
		||||
                    <div class="notice notice-success is-dismissible">
 | 
			
		||||
                        <p><?php _e('Order cancelled successfully.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <?php elseif ($_GET['message'] === 'refunded') : ?>
 | 
			
		||||
                    <div class="notice notice-success is-dismissible">
 | 
			
		||||
                        <p><?php _e('Refund processed successfully.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <?php elseif ($_GET['message'] === 'released') : ?>
 | 
			
		||||
                    <div class="notice notice-success is-dismissible">
 | 
			
		||||
                        <p><?php _e('Earnings released successfully.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <?php elseif ($_GET['message'] === 'error') : ?>
 | 
			
		||||
                    <div class="notice notice-error is-dismissible">
 | 
			
		||||
                        <p><?php _e('Error processing request. Please try again.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <?php endif; ?>
 | 
			
		||||
            <?php endif; ?>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Filter Form -->
 | 
			
		||||
            <div class="wpdd-filter-box" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
 | 
			
		||||
                <h3><?php _e('Filters', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                <form method="get" action="">
 | 
			
		||||
                    <input type="hidden" name="post_type" value="wpdd_product">
 | 
			
		||||
                    <input type="hidden" name="page" value="wpdd-order-manager">
 | 
			
		||||
                    
 | 
			
		||||
                    <table class="form-table">
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th><label for="status"><?php _e('Order Status', 'wp-digital-download'); ?></label></th>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <select name="status" id="status">
 | 
			
		||||
                                    <option value="all"><?php _e('All Statuses', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                    <option value="completed" <?php selected($status_filter, 'completed'); ?>><?php _e('Completed', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                    <option value="pending" <?php selected($status_filter, 'pending'); ?>><?php _e('Pending', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                    <option value="failed" <?php selected($status_filter, 'failed'); ?>><?php _e('Failed', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                    <option value="cancelled" <?php selected($status_filter, 'cancelled'); ?>><?php _e('Cancelled', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th><label for="creator"><?php _e('Creator', 'wp-digital-download'); ?></label></th>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <select name="creator" id="creator">
 | 
			
		||||
                                    <option value="0"><?php _e('All Creators', 'wp-digital-download'); ?></option>
 | 
			
		||||
                                    <?php foreach ($creators as $creator) : ?>
 | 
			
		||||
                                    <option value="<?php echo esc_attr($creator->ID); ?>" <?php selected($creator_filter, $creator->ID); ?>>
 | 
			
		||||
                                        <?php echo esc_html($creator->display_name); ?>
 | 
			
		||||
                                    </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>
 | 
			
		||||
                    </table>
 | 
			
		||||
                    
 | 
			
		||||
                    <p class="submit">
 | 
			
		||||
                        <input type="submit" class="button button-primary" value="<?php _e('Filter Orders', 'wp-digital-download'); ?>">
 | 
			
		||||
                    </p>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Orders Table -->
 | 
			
		||||
            <div class="wpdd-orders-table" style="background: white; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4;">
 | 
			
		||||
                <h3><?php _e('Orders', 'wp-digital-download'); ?> (<?php echo count($orders); ?>)</h3>
 | 
			
		||||
                
 | 
			
		||||
                <?php if (empty($orders)) : ?>
 | 
			
		||||
                    <p><?php _e('No orders found for the selected criteria.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                <?php else : ?>
 | 
			
		||||
                    <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th><?php _e('Order', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Customer', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Product', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Creator', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Amount', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Date', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Status', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Earnings Status', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Actions', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </thead>
 | 
			
		||||
                        <tbody>
 | 
			
		||||
                            <?php foreach ($orders as $order) : ?>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <strong><?php echo esc_html($order->order_number); ?></strong><br>
 | 
			
		||||
                                    <small>#<?php echo esc_html($order->id); ?></small>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <strong><?php echo esc_html($order->customer_name); ?></strong><br>
 | 
			
		||||
                                    <small><?php echo esc_html($order->customer_email); ?></small>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <strong><?php echo esc_html($order->product_name); ?></strong><br>
 | 
			
		||||
                                    <small>ID: <?php echo esc_html($order->product_id); ?></small>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td><?php echo esc_html($order->creator_name); ?></td>
 | 
			
		||||
                                <td><strong><?php echo wpdd_format_price($order->amount); ?></strong></td>
 | 
			
		||||
                                <td><?php echo esc_html(date_i18n(get_option('date_format'), strtotime($order->purchase_date))); ?></td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php 
 | 
			
		||||
                                    $status_class = '';
 | 
			
		||||
                                    switch($order->status) {
 | 
			
		||||
                                        case 'completed':
 | 
			
		||||
                                            $status_class = 'notice-success';
 | 
			
		||||
                                            break;
 | 
			
		||||
                                        case 'cancelled':
 | 
			
		||||
                                        case 'failed':
 | 
			
		||||
                                            $status_class = 'notice-error';
 | 
			
		||||
                                            break;
 | 
			
		||||
                                        case 'pending':
 | 
			
		||||
                                            $status_class = 'notice-warning';
 | 
			
		||||
                                            break;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    ?>
 | 
			
		||||
                                    <span class="notice <?php echo esc_attr($status_class); ?>" style="padding: 2px 8px; display: inline-block;">
 | 
			
		||||
                                        <?php echo esc_html(ucfirst($order->status)); ?>
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php if ($order->payout_status) : ?>
 | 
			
		||||
                                        <?php 
 | 
			
		||||
                                        $earnings_class = '';
 | 
			
		||||
                                        $earnings_text = ucfirst($order->payout_status);
 | 
			
		||||
                                        switch($order->payout_status) {
 | 
			
		||||
                                            case 'pending':
 | 
			
		||||
                                                $earnings_class = 'notice-info';
 | 
			
		||||
                                                if ($order->available_at) {
 | 
			
		||||
                                                    $earnings_text .= '<br><small>Until: ' . date('M j', strtotime($order->available_at)) . '</small>';
 | 
			
		||||
                                                }
 | 
			
		||||
                                                break;
 | 
			
		||||
                                            case 'available':
 | 
			
		||||
                                                $earnings_class = 'notice-warning';
 | 
			
		||||
                                                break;
 | 
			
		||||
                                            case 'paid':
 | 
			
		||||
                                                $earnings_class = 'notice-success';
 | 
			
		||||
                                                break;
 | 
			
		||||
                                            case 'cancelled':
 | 
			
		||||
                                                $earnings_class = 'notice-error';
 | 
			
		||||
                                                break;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        ?>
 | 
			
		||||
                                        <span class="notice <?php echo esc_attr($earnings_class); ?>" style="padding: 2px 8px; display: inline-block;">
 | 
			
		||||
                                            <?php echo $earnings_text; ?>
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                        <?php if ($order->creator_earning > 0) : ?>
 | 
			
		||||
                                            <br><small><?php echo wpdd_format_price($order->creator_earning); ?></small>
 | 
			
		||||
                                        <?php endif; ?>
 | 
			
		||||
                                    <?php else : ?>
 | 
			
		||||
                                        <span class="notice notice-error" style="padding: 2px 8px; display: inline-block;">
 | 
			
		||||
                                            <?php _e('No Earnings', 'wp-digital-download'); ?>
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    <?php endif; ?>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <div class="button-group">
 | 
			
		||||
                                        <?php if ($order->status === 'completed') : ?>
 | 
			
		||||
                                            
 | 
			
		||||
                                            <?php if ($order->payout_status === 'pending') : ?>
 | 
			
		||||
                                            <!-- Release Earnings Button -->
 | 
			
		||||
                                            <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline;">
 | 
			
		||||
                                                <input type="hidden" name="action" value="wpdd_release_earnings">
 | 
			
		||||
                                                <input type="hidden" name="earning_id" value="<?php echo esc_attr($order->earning_id); ?>">
 | 
			
		||||
                                                <input type="hidden" name="order_id" value="<?php echo esc_attr($order->id); ?>">
 | 
			
		||||
                                                <?php wp_nonce_field('wpdd_release_earnings_' . $order->earning_id, 'wpdd_nonce'); ?>
 | 
			
		||||
                                                <button type="submit" class="button button-secondary wpdd-release-btn" title="<?php _e('Release earnings immediately', 'wp-digital-download'); ?>">
 | 
			
		||||
                                                    <span class="dashicons dashicons-unlock"></span> <?php _e('Release', 'wp-digital-download'); ?>
 | 
			
		||||
                                                </button>
 | 
			
		||||
                                            </form>
 | 
			
		||||
                                            <?php endif; ?>
 | 
			
		||||
                                            
 | 
			
		||||
                                            <?php if ($order->payout_status !== 'paid') : ?>
 | 
			
		||||
                                            <!-- Cancel Order Button -->
 | 
			
		||||
                                            <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline;">
 | 
			
		||||
                                                <input type="hidden" name="action" value="wpdd_cancel_order">
 | 
			
		||||
                                                <input type="hidden" name="order_id" value="<?php echo esc_attr($order->id); ?>">
 | 
			
		||||
                                                <?php wp_nonce_field('wpdd_cancel_order_' . $order->id, 'wpdd_nonce'); ?>
 | 
			
		||||
                                                <button type="submit" class="button button-link-delete wpdd-cancel-btn" title="<?php _e('Cancel order and revoke access', 'wp-digital-download'); ?>">
 | 
			
		||||
                                                    <span class="dashicons dashicons-no"></span> <?php _e('Cancel', 'wp-digital-download'); ?>
 | 
			
		||||
                                                </button>
 | 
			
		||||
                                            </form>
 | 
			
		||||
                                            
 | 
			
		||||
                                            <!-- Refund Button -->
 | 
			
		||||
                                            <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline;">
 | 
			
		||||
                                                <input type="hidden" name="action" value="wpdd_refund_order">
 | 
			
		||||
                                                <input type="hidden" name="order_id" value="<?php echo esc_attr($order->id); ?>">
 | 
			
		||||
                                                <?php wp_nonce_field('wpdd_refund_order_' . $order->id, 'wpdd_nonce'); ?>
 | 
			
		||||
                                                <button type="submit" class="button button-link-delete wpdd-refund-btn" title="<?php _e('Process refund (manual PayPal refund required)', 'wp-digital-download'); ?>">
 | 
			
		||||
                                                    <span class="dashicons dashicons-undo"></span> <?php _e('Refund', 'wp-digital-download'); ?>
 | 
			
		||||
                                                </button>
 | 
			
		||||
                                            </form>
 | 
			
		||||
                                            <?php else : ?>
 | 
			
		||||
                                                <span class="description"><?php _e('Earnings already paid out', 'wp-digital-download'); ?></span>
 | 
			
		||||
                                            <?php endif; ?>
 | 
			
		||||
                                            
 | 
			
		||||
                                        <?php else : ?>
 | 
			
		||||
                                            <span class="description"><?php printf(__('Order is %s', 'wp-digital-download'), $order->status); ?></span>
 | 
			
		||||
                                        <?php endif; ?>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <?php endforeach; ?>
 | 
			
		||||
                        </tbody>
 | 
			
		||||
                    </table>
 | 
			
		||||
                <?php endif; ?>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <style>
 | 
			
		||||
                .button-group {
 | 
			
		||||
                    white-space: nowrap;
 | 
			
		||||
                }
 | 
			
		||||
                .button-group .button {
 | 
			
		||||
                    margin: 2px;
 | 
			
		||||
                    font-size: 11px;
 | 
			
		||||
                    padding: 3px 8px;
 | 
			
		||||
                    height: auto;
 | 
			
		||||
                }
 | 
			
		||||
                .button-group .dashicons {
 | 
			
		||||
                    font-size: 12px;
 | 
			
		||||
                    width: 12px;
 | 
			
		||||
                    height: 12px;
 | 
			
		||||
                    vertical-align: middle;
 | 
			
		||||
                }
 | 
			
		||||
            </style>
 | 
			
		||||
        </div>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_cancel_order() {
 | 
			
		||||
        if (!current_user_can('manage_options')) {
 | 
			
		||||
            wp_die(__('Permission denied', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $order_id = intval($_POST['order_id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_cancel_order_' . $order_id)) {
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=error'));
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $success = self::cancel_order($order_id);
 | 
			
		||||
        
 | 
			
		||||
        $message = $success ? 'cancelled' : 'error';
 | 
			
		||||
        wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=' . $message));
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_refund_order() {
 | 
			
		||||
        if (!current_user_can('manage_options')) {
 | 
			
		||||
            wp_die(__('Permission denied', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $order_id = intval($_POST['order_id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_refund_order_' . $order_id)) {
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=error'));
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $success = self::refund_order($order_id);
 | 
			
		||||
        
 | 
			
		||||
        $message = $success ? 'refunded' : 'error';
 | 
			
		||||
        wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=' . $message));
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_release_earnings() {
 | 
			
		||||
        if (!current_user_can('manage_options')) {
 | 
			
		||||
            wp_die(__('Permission denied', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $earning_id = intval($_POST['earning_id']);
 | 
			
		||||
        $order_id = intval($_POST['order_id']);
 | 
			
		||||
        
 | 
			
		||||
        if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_release_earnings_' . $earning_id)) {
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=error'));
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $success = WPDD_Earnings_Processor::release_earning_immediately($earning_id);
 | 
			
		||||
        
 | 
			
		||||
        $message = $success ? 'released' : 'error';
 | 
			
		||||
        wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-order-manager&message=' . $message));
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function cancel_order($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Update order status
 | 
			
		||||
        $result = $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_orders',
 | 
			
		||||
            array('status' => 'cancelled'),
 | 
			
		||||
            array('id' => $order_id),
 | 
			
		||||
            array('%s'),
 | 
			
		||||
            array('%d')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            // Revoke download access
 | 
			
		||||
            $wpdb->delete(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
                array('order_id' => $order_id),
 | 
			
		||||
                array('%d')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            // Cancel associated earnings
 | 
			
		||||
            $earning_id = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
                "SELECT id FROM {$wpdb->prefix}wpdd_creator_earnings WHERE order_id = %d",
 | 
			
		||||
                $order_id
 | 
			
		||||
            ));
 | 
			
		||||
            
 | 
			
		||||
            if ($earning_id) {
 | 
			
		||||
                WPDD_Earnings_Processor::cancel_earning($earning_id, 'Order cancelled by admin');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $result > 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function refund_order($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Update order status 
 | 
			
		||||
        $result = $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_orders',
 | 
			
		||||
            array('status' => 'refunded'),
 | 
			
		||||
            array('id' => $order_id),
 | 
			
		||||
            array('%s'),
 | 
			
		||||
            array('%d')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            // Revoke download access
 | 
			
		||||
            $wpdb->delete(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
                array('order_id' => $order_id),
 | 
			
		||||
                array('%d')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            // Cancel associated earnings
 | 
			
		||||
            $earning_id = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
                "SELECT id FROM {$wpdb->prefix}wpdd_creator_earnings WHERE order_id = %d",
 | 
			
		||||
                $order_id
 | 
			
		||||
            ));
 | 
			
		||||
            
 | 
			
		||||
            if ($earning_id) {
 | 
			
		||||
                WPDD_Earnings_Processor::cancel_earning($earning_id, 'Order refunded by admin');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $result > 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,6 +9,54 @@ class WPDD_Settings {
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('admin_menu', array(__CLASS__, 'add_settings_page'));
 | 
			
		||||
        add_action('admin_init', array(__CLASS__, 'register_settings'));
 | 
			
		||||
        
 | 
			
		||||
        // Ensure default settings are set
 | 
			
		||||
        self::ensure_default_settings();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function ensure_default_settings() {
 | 
			
		||||
        $defaults = array(
 | 
			
		||||
            'wpdd_paypal_mode' => 'sandbox',
 | 
			
		||||
            'wpdd_currency' => 'USD',
 | 
			
		||||
            'wpdd_commission_rate' => 0,
 | 
			
		||||
            'wpdd_payout_threshold' => 0,
 | 
			
		||||
            'wpdd_earnings_holding_days' => 15,
 | 
			
		||||
            'wpdd_enable_guest_checkout' => 1,
 | 
			
		||||
            'wpdd_default_download_limit' => 5,
 | 
			
		||||
            'wpdd_default_download_expiry' => 7,
 | 
			
		||||
            'wpdd_enable_watermark' => 0,
 | 
			
		||||
            'wpdd_file_access_method' => 'direct',
 | 
			
		||||
            'wpdd_smtp_enabled' => 0,
 | 
			
		||||
            'wpdd_smtp_port' => 587,
 | 
			
		||||
            'wpdd_smtp_encryption' => 'tls'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        foreach ($defaults as $option_name => $default_value) {
 | 
			
		||||
            if (get_option($option_name) === false) {
 | 
			
		||||
                add_option($option_name, $default_value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Ensure critical settings exist (but don't set default values)
 | 
			
		||||
        $critical_settings = array(
 | 
			
		||||
            'wpdd_paypal_client_id',
 | 
			
		||||
            'wpdd_paypal_secret',
 | 
			
		||||
            'wpdd_admin_email',
 | 
			
		||||
            'wpdd_from_name',
 | 
			
		||||
            'wpdd_from_email',
 | 
			
		||||
            'wpdd_smtp_host',
 | 
			
		||||
            'wpdd_smtp_username',
 | 
			
		||||
            'wpdd_smtp_password',
 | 
			
		||||
            'wpdd_watermark_text',
 | 
			
		||||
            'wpdd_terms_page',
 | 
			
		||||
            'wpdd_privacy_page'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        foreach ($critical_settings as $setting) {
 | 
			
		||||
            if (get_option($setting) === false) {
 | 
			
		||||
                add_option($setting, '');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_settings_page() {
 | 
			
		||||
@@ -23,69 +71,80 @@ class WPDD_Settings {
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function register_settings() {
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_paypal_mode');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_paypal_client_id');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_paypal_secret');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_paypal_payout_email');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_admin_email');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_from_name');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_from_email');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_smtp_enabled');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_smtp_host');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_smtp_port');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_smtp_username');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_smtp_password');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_smtp_encryption');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_currency');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_enable_guest_checkout');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_default_download_limit');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_default_download_expiry');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_enable_watermark');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_watermark_text');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_terms_page');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_privacy_page');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_commission_rate', array(
 | 
			
		||||
        // General Settings
 | 
			
		||||
        register_setting('wpdd_general_settings', 'wpdd_currency');
 | 
			
		||||
        register_setting('wpdd_general_settings', 'wpdd_enable_guest_checkout');
 | 
			
		||||
        register_setting('wpdd_general_settings', 'wpdd_commission_rate', array(
 | 
			
		||||
            'sanitize_callback' => array(__CLASS__, 'sanitize_commission_rate')
 | 
			
		||||
        ));
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_payout_threshold', array(
 | 
			
		||||
        register_setting('wpdd_general_settings', 'wpdd_payout_threshold', array(
 | 
			
		||||
            'sanitize_callback' => array(__CLASS__, 'sanitize_payout_threshold')
 | 
			
		||||
        ));
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_file_access_method');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_disable_admin_bar');
 | 
			
		||||
        register_setting('wpdd_general_settings', 'wpdd_earnings_holding_days', array(
 | 
			
		||||
            'sanitize_callback' => array(__CLASS__, 'sanitize_holding_days')
 | 
			
		||||
        ));
 | 
			
		||||
        register_setting('wpdd_general_settings', 'wpdd_terms_page');
 | 
			
		||||
        register_setting('wpdd_general_settings', 'wpdd_privacy_page');
 | 
			
		||||
        
 | 
			
		||||
        // PayPal Settings
 | 
			
		||||
        register_setting('wpdd_paypal_settings', 'wpdd_paypal_mode');
 | 
			
		||||
        register_setting('wpdd_paypal_settings', 'wpdd_paypal_client_id');
 | 
			
		||||
        register_setting('wpdd_paypal_settings', 'wpdd_paypal_secret');
 | 
			
		||||
        
 | 
			
		||||
        // Email Settings
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_admin_email');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_from_name');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_from_email');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_smtp_enabled');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_smtp_host');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_smtp_port');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_smtp_username');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_smtp_password');
 | 
			
		||||
        register_setting('wpdd_email_settings', 'wpdd_smtp_encryption');
 | 
			
		||||
        
 | 
			
		||||
        // Download Settings
 | 
			
		||||
        register_setting('wpdd_download_settings', 'wpdd_default_download_limit');
 | 
			
		||||
        register_setting('wpdd_download_settings', 'wpdd_default_download_expiry');
 | 
			
		||||
        register_setting('wpdd_download_settings', 'wpdd_file_access_method');
 | 
			
		||||
        register_setting('wpdd_download_settings', 'wpdd_disable_admin_bar');
 | 
			
		||||
        
 | 
			
		||||
        // Watermark Settings
 | 
			
		||||
        register_setting('wpdd_watermark_settings', 'wpdd_enable_watermark');
 | 
			
		||||
        register_setting('wpdd_watermark_settings', 'wpdd_watermark_text');
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            __('General Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'general_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
            'wpdd_general_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            __('PayPal Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'paypal_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
            'wpdd_paypal_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            __('Email Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'email_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
            'wpdd_email_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            __('Download Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'download_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
            'wpdd_download_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            __('Watermark Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'watermark_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
            'wpdd_watermark_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        self::add_general_fields();
 | 
			
		||||
@@ -100,7 +159,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_currency',
 | 
			
		||||
            __('Currency', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'currency_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_currency'
 | 
			
		||||
@@ -111,7 +170,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_enable_guest_checkout',
 | 
			
		||||
            __('Guest Checkout', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'checkbox_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_enable_guest_checkout',
 | 
			
		||||
@@ -123,7 +182,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_commission_rate',
 | 
			
		||||
            __('Platform Commission Rate (%)', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_commission_rate',
 | 
			
		||||
@@ -138,7 +197,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_payout_threshold',
 | 
			
		||||
            __('Automatic Payout Threshold ($)', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_payout_threshold',
 | 
			
		||||
@@ -148,11 +207,27 @@ class WPDD_Settings {
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_earnings_holding_days',
 | 
			
		||||
            __('Earnings Holding Period (Days)', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_earnings_holding_days',
 | 
			
		||||
                'description' => __('Number of days to hold earnings before making them available for payout (0 for immediate, 15 recommended for fraud protection)', 'wp-digital-download'),
 | 
			
		||||
                'min' => 0,
 | 
			
		||||
                'max' => 365,
 | 
			
		||||
                'step' => 1,
 | 
			
		||||
                'default' => 15
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_terms_page',
 | 
			
		||||
            __('Terms & Conditions Page', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'page_dropdown_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array('name' => 'wpdd_terms_page')
 | 
			
		||||
        );
 | 
			
		||||
@@ -161,7 +236,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_privacy_page',
 | 
			
		||||
            __('Privacy Policy Page', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'page_dropdown_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array('name' => 'wpdd_privacy_page')
 | 
			
		||||
        );
 | 
			
		||||
@@ -172,7 +247,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_paypal_mode',
 | 
			
		||||
            __('PayPal Mode', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'select_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_paypal_mode',
 | 
			
		||||
@@ -187,7 +262,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_paypal_client_id',
 | 
			
		||||
            __('PayPal Client ID', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            array('name' => 'wpdd_paypal_client_id')
 | 
			
		||||
        );
 | 
			
		||||
@@ -196,22 +271,11 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_paypal_secret',
 | 
			
		||||
            __('PayPal Secret', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'password_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            array('name' => 'wpdd_paypal_secret')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_paypal_payout_email',
 | 
			
		||||
            __('PayPal Payout Account Email', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'email_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_paypal_payout_email',
 | 
			
		||||
                'description' => __('PayPal account email that will send payouts to creators', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function add_email_fields() {
 | 
			
		||||
@@ -219,7 +283,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_admin_email',
 | 
			
		||||
            __('Admin Email', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'email_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_admin_email',
 | 
			
		||||
@@ -231,7 +295,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_from_name',
 | 
			
		||||
            __('From Name', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_from_name',
 | 
			
		||||
@@ -243,7 +307,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_from_email',
 | 
			
		||||
            __('From Email', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'email_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_from_email',
 | 
			
		||||
@@ -255,7 +319,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_smtp_enabled',
 | 
			
		||||
            __('Enable SMTP', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'checkbox_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_smtp_enabled',
 | 
			
		||||
@@ -267,7 +331,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_smtp_host',
 | 
			
		||||
            __('SMTP Host', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_smtp_host',
 | 
			
		||||
@@ -279,7 +343,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_smtp_port',
 | 
			
		||||
            __('SMTP Port', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_smtp_port',
 | 
			
		||||
@@ -293,7 +357,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_smtp_encryption',
 | 
			
		||||
            __('SMTP Encryption', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'select_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_smtp_encryption',
 | 
			
		||||
@@ -310,7 +374,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_smtp_autodetect',
 | 
			
		||||
            __('Auto-Detect Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'smtp_autodetect_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array()
 | 
			
		||||
        );
 | 
			
		||||
@@ -319,7 +383,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_smtp_username',
 | 
			
		||||
            __('SMTP Username', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_smtp_username',
 | 
			
		||||
@@ -331,7 +395,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_smtp_password',
 | 
			
		||||
            __('SMTP Password', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'password_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_smtp_password',
 | 
			
		||||
@@ -345,7 +409,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_default_download_limit',
 | 
			
		||||
            __('Default Download Limit', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_default_download_limit',
 | 
			
		||||
@@ -358,7 +422,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_default_download_expiry',
 | 
			
		||||
            __('Default Download Expiry (days)', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_default_download_expiry',
 | 
			
		||||
@@ -371,7 +435,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_file_access_method',
 | 
			
		||||
            __('File Access Method', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'select_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_file_access_method',
 | 
			
		||||
@@ -390,7 +454,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_enable_watermark',
 | 
			
		||||
            __('Enable Watermarking', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'checkbox_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_enable_watermark',
 | 
			
		||||
@@ -402,7 +466,7 @@ class WPDD_Settings {
 | 
			
		||||
            'wpdd_watermark_text',
 | 
			
		||||
            __('Default Watermark Text', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_watermark_text',
 | 
			
		||||
@@ -434,25 +498,30 @@ class WPDD_Settings {
 | 
			
		||||
                <div class="wpdd-settings-content">
 | 
			
		||||
                    <form method="post" action="options.php" class="wpdd-settings-form">
 | 
			
		||||
                        <?php
 | 
			
		||||
                        settings_fields('wpdd_settings');
 | 
			
		||||
                        
 | 
			
		||||
                        // Use appropriate settings group for each tab
 | 
			
		||||
                        switch ($active_tab) {
 | 
			
		||||
                            case 'general':
 | 
			
		||||
                                settings_fields('wpdd_general_settings');
 | 
			
		||||
                                self::render_general_tab();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 'paypal':
 | 
			
		||||
                                settings_fields('wpdd_paypal_settings');
 | 
			
		||||
                                self::render_paypal_tab();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 'email':
 | 
			
		||||
                                settings_fields('wpdd_email_settings');
 | 
			
		||||
                                self::render_email_tab();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 'downloads':
 | 
			
		||||
                                settings_fields('wpdd_download_settings');
 | 
			
		||||
                                self::render_downloads_tab();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case 'watermark':
 | 
			
		||||
                                settings_fields('wpdd_watermark_settings');
 | 
			
		||||
                                self::render_watermark_tab();
 | 
			
		||||
                                break;
 | 
			
		||||
                            default:
 | 
			
		||||
                                settings_fields('wpdd_general_settings');
 | 
			
		||||
                                self::render_general_tab();
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
@@ -951,22 +1020,22 @@ class WPDD_Settings {
 | 
			
		||||
    private static function do_settings_sections_for_tab($section_id) {
 | 
			
		||||
        global $wp_settings_sections, $wp_settings_fields;
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($wp_settings_sections['wpdd_settings'][$section_id])) {
 | 
			
		||||
        if (!isset($wp_settings_sections[$section_id][$section_id])) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $section = $wp_settings_sections['wpdd_settings'][$section_id];
 | 
			
		||||
        $section = $wp_settings_sections[$section_id][$section_id];
 | 
			
		||||
        
 | 
			
		||||
        if (isset($section['callback']) && $section['callback']) {
 | 
			
		||||
            call_user_func($section['callback'], $section);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($wp_settings_fields['wpdd_settings'][$section_id])) {
 | 
			
		||||
        if (!isset($wp_settings_fields[$section_id][$section_id])) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        echo '<table class="form-table" role="presentation">';
 | 
			
		||||
        do_settings_fields('wpdd_settings', $section_id);
 | 
			
		||||
        do_settings_fields($section_id, $section_id);
 | 
			
		||||
        echo '</table>';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -990,4 +1059,16 @@ class WPDD_Settings {
 | 
			
		||||
        }
 | 
			
		||||
        return $value;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function sanitize_holding_days($input) {
 | 
			
		||||
        $value = intval($input);
 | 
			
		||||
        if ($value < 0) {
 | 
			
		||||
            $value = 15;
 | 
			
		||||
            add_settings_error('wpdd_earnings_holding_days', 'invalid_holding_days', __('Holding days cannot be negative. Set to 15 (recommended).', 'wp-digital-download'));
 | 
			
		||||
        } elseif ($value > 365) {
 | 
			
		||||
            $value = 365;
 | 
			
		||||
            add_settings_error('wpdd_earnings_holding_days', 'invalid_holding_days', __('Holding days cannot exceed 365. Set to maximum (365).', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        return $value;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										781
									
								
								admin/class-wpdd-transaction-history.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										781
									
								
								admin/class-wpdd-transaction-history.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,781 @@
 | 
			
		||||
<?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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								admin/wpdd-db-migrate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								admin/wpdd-db-migrate.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Database migration script for WP Digital Download
 | 
			
		||||
 * Adds missing columns and updates database schema
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function wpdd_migrate_database() {
 | 
			
		||||
    global $wpdb;
 | 
			
		||||
    
 | 
			
		||||
    // Check if available_at column exists in wpdd_creator_earnings table
 | 
			
		||||
    $column_exists = $wpdb->get_results(
 | 
			
		||||
        "SHOW COLUMNS FROM {$wpdb->prefix}wpdd_creator_earnings LIKE 'available_at'"
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    if (empty($column_exists)) {
 | 
			
		||||
        // Add the missing available_at column
 | 
			
		||||
        $result = $wpdb->query(
 | 
			
		||||
            "ALTER TABLE {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
             ADD COLUMN available_at datetime DEFAULT NULL AFTER payout_status,
 | 
			
		||||
             ADD INDEX available_at (available_at)"
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($result !== false) {
 | 
			
		||||
            error_log('WPDD Migration: Successfully added available_at column to wpdd_creator_earnings table');
 | 
			
		||||
            
 | 
			
		||||
            // Update existing pending earnings to have an available_at date
 | 
			
		||||
            $holding_days = intval(get_option('wpdd_earnings_holding_days', 15));
 | 
			
		||||
            $wpdb->query(
 | 
			
		||||
                $wpdb->prepare(
 | 
			
		||||
                    "UPDATE {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
                     SET available_at = DATE_ADD(created_at, INTERVAL %d DAY)
 | 
			
		||||
                     WHERE payout_status = 'pending' AND available_at IS NULL",
 | 
			
		||||
                    $holding_days
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            error_log('WPDD Migration Error: Failed to add available_at column - ' . $wpdb->last_error);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return true; // Column already exists
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run migration if accessed directly (for manual execution)
 | 
			
		||||
if (basename($_SERVER['SCRIPT_FILENAME']) === 'wpdd-db-migrate.php') {
 | 
			
		||||
    wpdd_migrate_database();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										756
									
								
								admin/wpdd-tools.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										756
									
								
								admin/wpdd-tools.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,756 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Database Check and Update Script for WP Digital Download
 | 
			
		||||
 * This script checks and creates missing database tables
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function wpdd_check_and_create_tables() {
 | 
			
		||||
    global $wpdb;
 | 
			
		||||
    
 | 
			
		||||
    $charset_collate = $wpdb->get_charset_collate();
 | 
			
		||||
    $results = array();
 | 
			
		||||
    
 | 
			
		||||
    // Check if creator_earnings table exists and has correct structure
 | 
			
		||||
    $table_name = $wpdb->prefix . 'wpdd_creator_earnings';
 | 
			
		||||
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
 | 
			
		||||
    
 | 
			
		||||
    if (!$table_exists) {
 | 
			
		||||
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
 | 
			
		||||
            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)
 | 
			
		||||
        ) $charset_collate;";
 | 
			
		||||
        
 | 
			
		||||
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
 | 
			
		||||
        dbDelta($sql);
 | 
			
		||||
        $results[] = "Created table: $table_name";
 | 
			
		||||
    } else {
 | 
			
		||||
        // Check if payout_id and payout_status columns exist
 | 
			
		||||
        $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name");
 | 
			
		||||
        $has_payout_id = false;
 | 
			
		||||
        $has_payout_status = false;
 | 
			
		||||
        
 | 
			
		||||
        $has_available_at = false;
 | 
			
		||||
        
 | 
			
		||||
        foreach ($columns as $column) {
 | 
			
		||||
            if ($column->Field == 'payout_id') $has_payout_id = true;
 | 
			
		||||
            if ($column->Field == 'payout_status') $has_payout_status = true;
 | 
			
		||||
            if ($column->Field == 'available_at') $has_available_at = true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$has_payout_id) {
 | 
			
		||||
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN payout_id bigint(20) DEFAULT NULL");
 | 
			
		||||
            $wpdb->query("ALTER TABLE $table_name ADD KEY payout_id (payout_id)");
 | 
			
		||||
            $results[] = "Added payout_id column to $table_name";
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$has_payout_status) {
 | 
			
		||||
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN payout_status varchar(20) DEFAULT 'pending'");
 | 
			
		||||
            $wpdb->query("ALTER TABLE $table_name ADD KEY payout_status (payout_status)");
 | 
			
		||||
            $results[] = "Added payout_status column to $table_name";
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$has_available_at) {
 | 
			
		||||
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN available_at datetime DEFAULT NULL");
 | 
			
		||||
            $wpdb->query("ALTER TABLE $table_name ADD KEY available_at (available_at)");
 | 
			
		||||
            $results[] = "Added available_at column to $table_name";
 | 
			
		||||
            
 | 
			
		||||
            // Update existing pending earnings with available_at dates
 | 
			
		||||
            $holding_days = intval(get_option('wpdd_earnings_holding_days', 15));
 | 
			
		||||
            $wpdb->query(
 | 
			
		||||
                $wpdb->prepare(
 | 
			
		||||
                    "UPDATE $table_name 
 | 
			
		||||
                     SET available_at = DATE_ADD(created_at, INTERVAL %d DAY)
 | 
			
		||||
                     WHERE payout_status = 'pending' AND available_at IS NULL",
 | 
			
		||||
                    $holding_days
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
            $results[] = "Updated pending earnings with available_at dates";
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $results[] = "Table exists: $table_name";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Check if balance_adjustments table exists
 | 
			
		||||
    $table_name = $wpdb->prefix . 'wpdd_balance_adjustments';
 | 
			
		||||
    if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
 | 
			
		||||
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
 | 
			
		||||
            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;";
 | 
			
		||||
        
 | 
			
		||||
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
 | 
			
		||||
        dbDelta($sql);
 | 
			
		||||
        $results[] = "Created table: $table_name";
 | 
			
		||||
    } else {
 | 
			
		||||
        $results[] = "Table exists: $table_name";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Check existing orders and add missing earnings records
 | 
			
		||||
    $completed_orders = $wpdb->get_results(
 | 
			
		||||
        "SELECT o.*, p.post_author as creator_id 
 | 
			
		||||
         FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
         INNER JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
         WHERE o.status = 'completed'"
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    $results[] = "Found " . count($completed_orders) . " completed orders";
 | 
			
		||||
    
 | 
			
		||||
    // Process each order to ensure earnings are recorded
 | 
			
		||||
    $added_earnings = 0;
 | 
			
		||||
    foreach ($completed_orders as $order) {
 | 
			
		||||
        // Check if earning already recorded
 | 
			
		||||
        $existing = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT id FROM {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
             WHERE order_id = %d",
 | 
			
		||||
            $order->id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$existing && $order->amount > 0) {
 | 
			
		||||
            $commission_rate = floatval(get_option('wpdd_commission_rate', 0));
 | 
			
		||||
            $creator_share = $order->amount * (1 - ($commission_rate / 100));
 | 
			
		||||
            
 | 
			
		||||
            // Insert earning record
 | 
			
		||||
            $wpdb->insert(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_creator_earnings',
 | 
			
		||||
                array(
 | 
			
		||||
                    'creator_id' => $order->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' => 'pending',
 | 
			
		||||
                    'created_at' => $order->purchase_date
 | 
			
		||||
                ),
 | 
			
		||||
                array('%d', '%d', '%d', '%f', '%f', '%f', '%s', '%s')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            $added_earnings++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if ($added_earnings > 0) {
 | 
			
		||||
        $results[] = "Added $added_earnings missing earning records";
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Recalculate all creator balances based on UNPAID earnings only
 | 
			
		||||
    $creators = get_users(array('role' => 'wpdd_creator'));
 | 
			
		||||
    
 | 
			
		||||
    foreach ($creators as $creator) {
 | 
			
		||||
        // Calculate available earnings only (not pending or already 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'",
 | 
			
		||||
            $creator->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        // Calculate balance adjustments
 | 
			
		||||
        $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",
 | 
			
		||||
            $creator->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $available_earnings = floatval($available_earnings);
 | 
			
		||||
        $total_adjustments = floatval($total_adjustments);
 | 
			
		||||
        
 | 
			
		||||
        $balance = $available_earnings + $total_adjustments;
 | 
			
		||||
        
 | 
			
		||||
        // Update creator balance
 | 
			
		||||
        update_user_meta($creator->ID, 'wpdd_creator_balance', $balance);
 | 
			
		||||
        
 | 
			
		||||
        // Get total earnings for display
 | 
			
		||||
        $total_earnings = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT SUM(creator_earning) 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
             WHERE creator_id = %d",
 | 
			
		||||
            $creator->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        // Get paid out amount
 | 
			
		||||
        $paid_out = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT SUM(creator_earning) 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
             WHERE creator_id = %d 
 | 
			
		||||
             AND payout_status = 'paid'",
 | 
			
		||||
            $creator->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $results[] = sprintf(
 | 
			
		||||
            "Updated balance for %s: Total Earnings: $%.2f, Paid Out: $%.2f, Adjustments: $%.2f, Current Balance: $%.2f",
 | 
			
		||||
            $creator->display_name,
 | 
			
		||||
            floatval($total_earnings),
 | 
			
		||||
            floatval($paid_out),
 | 
			
		||||
            $total_adjustments,
 | 
			
		||||
            $balance
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return $results;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Add admin page to run this check
 | 
			
		||||
add_action('admin_menu', function() {
 | 
			
		||||
    add_submenu_page(
 | 
			
		||||
        'edit.php?post_type=wpdd_product',
 | 
			
		||||
        'Tools',
 | 
			
		||||
        'Tools',
 | 
			
		||||
        'manage_options',
 | 
			
		||||
        'wpdd-tools',
 | 
			
		||||
        function() {
 | 
			
		||||
            $active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'system';
 | 
			
		||||
            ?>
 | 
			
		||||
            <div class="wrap">
 | 
			
		||||
                <h1>WP Digital Download - Tools</h1>
 | 
			
		||||
                
 | 
			
		||||
                <h2 class="nav-tab-wrapper">
 | 
			
		||||
                    <a href="?post_type=wpdd_product&page=wpdd-tools&tab=system" 
 | 
			
		||||
                       class="nav-tab <?php echo $active_tab == 'system' ? 'nav-tab-active' : ''; ?>">System Tools</a>
 | 
			
		||||
                    <a href="?post_type=wpdd_product&page=wpdd-tools&tab=licenses" 
 | 
			
		||||
                       class="nav-tab <?php echo $active_tab == 'licenses' ? 'nav-tab-active' : ''; ?>">License Tools</a>
 | 
			
		||||
                    <a href="?post_type=wpdd_product&page=wpdd-tools&tab=email-test" 
 | 
			
		||||
                       class="nav-tab <?php echo $active_tab == 'email-test' ? 'nav-tab-active' : ''; ?>">Email Test</a>
 | 
			
		||||
                    <a href="?post_type=wpdd_product&page=wpdd-tools&tab=email-logs" 
 | 
			
		||||
                       class="nav-tab <?php echo $active_tab == 'email-logs' ? 'nav-tab-active' : ''; ?>">Email Logs</a>
 | 
			
		||||
                </h2>
 | 
			
		||||
                
 | 
			
		||||
                <?php if ($active_tab == 'system') : ?>
 | 
			
		||||
                <div class="wpdd-tools-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
 | 
			
		||||
                    
 | 
			
		||||
                    <!-- Database Check Tool -->
 | 
			
		||||
                    <div class="wpdd-tool-box" style="background: white; border: 1px solid #ccd0d4; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
 | 
			
		||||
                        <h2 style="margin-top: 0;">🔧 Database Check & Repair</h2>
 | 
			
		||||
                        
 | 
			
		||||
                        <?php
 | 
			
		||||
                        if (isset($_POST['run_check']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_db_check')) {
 | 
			
		||||
                            $results = wpdd_check_and_create_tables();
 | 
			
		||||
                            echo '<div class="notice notice-success"><p>Database check completed!</p></div>';
 | 
			
		||||
                            echo '<h3>Results:</h3>';
 | 
			
		||||
                            echo '<ul style="max-height: 200px; overflow-y: auto; background: #f9f9f9; padding: 10px; border: 1px solid #ddd;">';
 | 
			
		||||
                            foreach ($results as $result) {
 | 
			
		||||
                                echo '<li>' . esc_html($result) . '</li>';
 | 
			
		||||
                            }
 | 
			
		||||
                            echo '</ul>';
 | 
			
		||||
                        }
 | 
			
		||||
                        ?>
 | 
			
		||||
                        
 | 
			
		||||
                        <p>This tool will:</p>
 | 
			
		||||
                        <ul style="font-size: 13px;">
 | 
			
		||||
                            <li>Check and create missing database tables</li>
 | 
			
		||||
                            <li>Add payout tracking columns if missing</li>
 | 
			
		||||
                            <li>Add earning records for completed orders</li>
 | 
			
		||||
                            <li>Recalculate creator balances</li>
 | 
			
		||||
                            <li>Update pending earnings with proper dates</li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                        
 | 
			
		||||
                        <form method="post">
 | 
			
		||||
                            <?php wp_nonce_field('wpdd_db_check'); ?>
 | 
			
		||||
                            <p class="submit" style="margin: 10px 0 0 0;">
 | 
			
		||||
                                <input type="submit" name="run_check" class="button button-primary" value="Run Database Check">
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <!-- System Status Tool -->
 | 
			
		||||
                    <div class="wpdd-tool-box" style="background: white; border: 1px solid #ccd0d4; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
 | 
			
		||||
                        <h2 style="margin-top: 0;">📊 System Status</h2>
 | 
			
		||||
                        <p>Quick overview of your plugin status:</p>
 | 
			
		||||
                        
 | 
			
		||||
                        <?php
 | 
			
		||||
                        // PayPal Status
 | 
			
		||||
                        $paypal_client_id = get_option('wpdd_paypal_client_id');
 | 
			
		||||
                        $paypal_secret = get_option('wpdd_paypal_secret');
 | 
			
		||||
                        $paypal_configured = !empty($paypal_client_id) && !empty($paypal_secret);
 | 
			
		||||
                        
 | 
			
		||||
                        echo '<p><strong>PayPal:</strong> <span style="color: ' . ($paypal_configured ? '#46b450' : '#dc3232') . ';">' . ($paypal_configured ? '✓ Configured' : '✗ Not Configured') . '</span></p>';
 | 
			
		||||
                        
 | 
			
		||||
                        if ($paypal_configured) {
 | 
			
		||||
                            echo '<p style="font-size: 11px; color: #666;"><strong>Client ID:</strong> ' . substr($paypal_client_id, 0, 10) . '...</p>';
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        // PayPal Mode
 | 
			
		||||
                        $paypal_mode = get_option('wpdd_paypal_mode', 'sandbox');
 | 
			
		||||
                        echo '<p><strong>PayPal Mode:</strong> ' . ucfirst(esc_html($paypal_mode)) . '</p>';
 | 
			
		||||
                        
 | 
			
		||||
                        // Currency
 | 
			
		||||
                        $currency = get_option('wpdd_currency', 'USD');
 | 
			
		||||
                        echo '<p><strong>Currency:</strong> ' . esc_html($currency) . '</p>';
 | 
			
		||||
                        
 | 
			
		||||
                        // Commission Rate
 | 
			
		||||
                        $commission = get_option('wpdd_commission_rate', 0);
 | 
			
		||||
                        echo '<p><strong>Platform Commission:</strong> ' . floatval($commission) . '%</p>';
 | 
			
		||||
                        
 | 
			
		||||
                        // Holding Period
 | 
			
		||||
                        $holding = get_option('wpdd_earnings_holding_days', 15);
 | 
			
		||||
                        echo '<p><strong>Earnings Hold:</strong> ' . intval($holding) . ' days</p>';
 | 
			
		||||
                        
 | 
			
		||||
                        // Product Count
 | 
			
		||||
                        $products = wp_count_posts('wpdd_product');
 | 
			
		||||
                        echo '<p><strong>Products:</strong> ' . intval($products->publish) . ' published</p>';
 | 
			
		||||
                        
 | 
			
		||||
                        // Creator Count
 | 
			
		||||
                        $creators = count(get_users(array('role' => 'wpdd_creator')));
 | 
			
		||||
                        echo '<p><strong>Creators:</strong> ' . $creators . '</p>';
 | 
			
		||||
                        ?>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Settings Debug Section -->
 | 
			
		||||
                <div style="margin-top: 20px; padding: 15px; background: #f9f9f9; border: 1px solid #ddd;">
 | 
			
		||||
                    <h3>🔍 Settings Debug Info</h3>
 | 
			
		||||
                    <p style="font-size: 12px;">If settings are resetting, check these values:</p>
 | 
			
		||||
                    <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; font-family: monospace; font-size: 11px;">
 | 
			
		||||
                        <?php
 | 
			
		||||
                        $debug_settings = array(
 | 
			
		||||
                            'wpdd_paypal_mode',
 | 
			
		||||
                            'wpdd_paypal_client_id', 
 | 
			
		||||
                            'wpdd_currency',
 | 
			
		||||
                            'wpdd_commission_rate',
 | 
			
		||||
                            'wpdd_payout_threshold',
 | 
			
		||||
                            'wpdd_earnings_holding_days'
 | 
			
		||||
                        );
 | 
			
		||||
                        
 | 
			
		||||
                        foreach ($debug_settings as $setting) {
 | 
			
		||||
                            $value = get_option($setting);
 | 
			
		||||
                            $display_value = $setting === 'wpdd_paypal_client_id' && !empty($value) ? substr($value, 0, 8) . '...' : $value;
 | 
			
		||||
                            echo '<div><strong>' . esc_html($setting) . ':</strong><br>' . esc_html($display_value ?: '(empty)') . '</div>';
 | 
			
		||||
                        }
 | 
			
		||||
                        ?>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <?php elseif ($active_tab == 'licenses') : ?>
 | 
			
		||||
                <!-- License Tools Tab -->
 | 
			
		||||
                <div style="background: white; padding: 20px; margin-top: 20px; border: 1px solid #ccd0d4;">
 | 
			
		||||
                    <h2>🔑 License Tools</h2>
 | 
			
		||||
                    <p>Manage and regenerate license keys for completed orders.</p>
 | 
			
		||||
                    
 | 
			
		||||
                    <?php
 | 
			
		||||
                    global $wpdb;
 | 
			
		||||
                    
 | 
			
		||||
                    // Load license manager if not already loaded
 | 
			
		||||
                    if (!class_exists('WPDD_License_Manager')) {
 | 
			
		||||
                        require_once(WPDD_PLUGIN_PATH . 'includes/class-wpdd-license-manager.php');
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Handle license generation
 | 
			
		||||
                    if (isset($_POST['generate_license']) && isset($_POST['order_id']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_generate_license')) {
 | 
			
		||||
                        $order_id = intval($_POST['order_id']);
 | 
			
		||||
                        $result = WPDD_License_Manager::generate_license_for_order($order_id);
 | 
			
		||||
                        
 | 
			
		||||
                        if ($result) {
 | 
			
		||||
                            echo '<div class="notice notice-success"><p>✅ License generated successfully for order #' . $order_id . ': <code>' . esc_html($result) . '</code></p></div>';
 | 
			
		||||
                        } else {
 | 
			
		||||
                            echo '<div class="notice notice-warning"><p>⚠️ License generation completed for order #' . $order_id . '. Check error logs if no license was created.</p></div>';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Handle bulk license generation
 | 
			
		||||
                    if (isset($_POST['generate_all_missing']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_generate_all_licenses')) {
 | 
			
		||||
                        $orders_without_licenses = $wpdb->get_results("
 | 
			
		||||
                            SELECT o.id
 | 
			
		||||
                            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.status = 'completed' 
 | 
			
		||||
                            AND l.id IS NULL
 | 
			
		||||
                            AND p.post_type = 'wpdd_product'
 | 
			
		||||
                        ");
 | 
			
		||||
                        
 | 
			
		||||
                        $generated = 0;
 | 
			
		||||
                        foreach ($orders_without_licenses as $order) {
 | 
			
		||||
                            $result = WPDD_License_Manager::generate_license_for_order($order->id);
 | 
			
		||||
                            if ($result) {
 | 
			
		||||
                                $generated++;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        echo '<div class="notice notice-success"><p>✅ Generated ' . $generated . ' license keys out of ' . count($orders_without_licenses) . ' eligible orders.</p></div>';
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Get orders without license keys for software products
 | 
			
		||||
                    $orders_without_licenses = $wpdb->get_results("
 | 
			
		||||
                        SELECT o.*, p.post_title as product_name,
 | 
			
		||||
                               pm_type.meta_value as product_type,
 | 
			
		||||
                               pm_git.meta_value as git_repository
 | 
			
		||||
                        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
 | 
			
		||||
                        LEFT JOIN {$wpdb->postmeta} pm_type ON (p.ID = pm_type.post_id AND pm_type.meta_key = '_wpdd_product_type')
 | 
			
		||||
                        LEFT JOIN {$wpdb->postmeta} pm_git ON (p.ID = pm_git.post_id AND pm_git.meta_key = '_wpdd_git_repository')
 | 
			
		||||
                        WHERE o.status = 'completed' 
 | 
			
		||||
                        AND l.id IS NULL
 | 
			
		||||
                        AND p.post_type = 'wpdd_product'
 | 
			
		||||
                        AND (pm_type.meta_value = 'software_license' OR pm_git.meta_value IS NOT NULL)
 | 
			
		||||
                        ORDER BY o.id DESC
 | 
			
		||||
                    ");
 | 
			
		||||
                    ?>
 | 
			
		||||
                    
 | 
			
		||||
                    <?php if (!empty($orders_without_licenses)): ?>
 | 
			
		||||
                        <div style="margin-bottom: 20px;">
 | 
			
		||||
                            <form method="post" style="display: inline;">
 | 
			
		||||
                                <?php wp_nonce_field('wpdd_generate_all_licenses'); ?>
 | 
			
		||||
                                <input type="submit" name="generate_all_missing" class="button button-secondary" 
 | 
			
		||||
                                       value="Generate All Missing Licenses (<?php echo count($orders_without_licenses); ?> orders)"
 | 
			
		||||
                                       onclick="return confirm('Generate license keys for all <?php echo count($orders_without_licenses); ?> orders? This action cannot be undone.');">
 | 
			
		||||
                            </form>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <h3>Orders Missing License Keys</h3>
 | 
			
		||||
                        <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th style="width: 80px;">Order ID</th>
 | 
			
		||||
                                    <th style="width: 140px;">Order Number</th>
 | 
			
		||||
                                    <th>Product</th>
 | 
			
		||||
                                    <th style="width: 100px;">Product Type</th>
 | 
			
		||||
                                    <th style="width: 200px;">Customer Email</th>
 | 
			
		||||
                                    <th style="width: 100px;">Date</th>
 | 
			
		||||
                                    <th style="width: 100px;">Action</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                <?php foreach ($orders_without_licenses as $order): ?>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td><?php echo $order->id; ?></td>
 | 
			
		||||
                                    <td><?php echo esc_html($order->order_number); ?></td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <strong><?php echo esc_html($order->product_name); ?></strong>
 | 
			
		||||
                                        <?php if (!empty($order->git_repository)): ?>
 | 
			
		||||
                                            <br><small style="color: #666;">Git: <?php echo esc_html($order->git_repository); ?></small>
 | 
			
		||||
                                        <?php endif; ?>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td><?php echo esc_html($order->product_type ?: 'digital_download'); ?></td>
 | 
			
		||||
                                    <td><?php echo esc_html($order->customer_email); ?></td>
 | 
			
		||||
                                    <td><?php echo date('Y-m-d', strtotime($order->purchase_date)); ?></td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <form method="post" style="display: inline;">
 | 
			
		||||
                                            <?php wp_nonce_field('wpdd_generate_license'); ?>
 | 
			
		||||
                                            <input type="hidden" name="order_id" value="<?php echo $order->id; ?>">
 | 
			
		||||
                                            <input type="submit" name="generate_license" class="button button-primary button-small" 
 | 
			
		||||
                                                   value="Generate" 
 | 
			
		||||
                                                   onclick="return confirm('Generate license for order #<?php echo $order->id; ?>?');">
 | 
			
		||||
                                        </form>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                <?php endforeach; ?>
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    <?php else: ?>
 | 
			
		||||
                        <div class="notice notice-info">
 | 
			
		||||
                            <p>✅ All eligible software orders have license keys assigned.</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    <?php endif; ?>
 | 
			
		||||
                    
 | 
			
		||||
                    <hr style="margin: 30px 0;">
 | 
			
		||||
                    
 | 
			
		||||
                    <h3>🔍 License Statistics</h3>
 | 
			
		||||
                    <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;">
 | 
			
		||||
                        <?php
 | 
			
		||||
                        // Get license statistics
 | 
			
		||||
                        $total_licenses = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_licenses");
 | 
			
		||||
                        $active_licenses = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_licenses WHERE status = 'active'");
 | 
			
		||||
                        $expired_licenses = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_licenses WHERE expires_at IS NOT NULL AND expires_at < NOW()");
 | 
			
		||||
                        
 | 
			
		||||
                        $software_orders = $wpdb->get_var("
 | 
			
		||||
                            SELECT COUNT(*)
 | 
			
		||||
                            FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
                            LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
                            LEFT JOIN {$wpdb->postmeta} pm_type ON (p.ID = pm_type.post_id AND pm_type.meta_key = '_wpdd_product_type')
 | 
			
		||||
                            LEFT JOIN {$wpdb->postmeta} pm_git ON (p.ID = pm_git.post_id AND pm_git.meta_key = '_wpdd_git_repository')
 | 
			
		||||
                            WHERE o.status = 'completed' 
 | 
			
		||||
                            AND p.post_type = 'wpdd_product'
 | 
			
		||||
                            AND (pm_type.meta_value = 'software_license' OR pm_git.meta_value IS NOT NULL)
 | 
			
		||||
                        ");
 | 
			
		||||
                        
 | 
			
		||||
                        $missing_licenses = count($orders_without_licenses);
 | 
			
		||||
                        ?>
 | 
			
		||||
                        
 | 
			
		||||
                        <div style="background: #f9f9f9; padding: 15px; border: 1px solid #ddd; text-align: center;">
 | 
			
		||||
                            <h4 style="margin: 0 0 10px 0;">Total Licenses</h4>
 | 
			
		||||
                            <div style="font-size: 24px; font-weight: bold; color: #0073aa;"><?php echo $total_licenses; ?></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <div style="background: #f9f9f9; padding: 15px; border: 1px solid #ddd; text-align: center;">
 | 
			
		||||
                            <h4 style="margin: 0 0 10px 0;">Active Licenses</h4>
 | 
			
		||||
                            <div style="font-size: 24px; font-weight: bold; color: #46b450;"><?php echo $active_licenses; ?></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <div style="background: #f9f9f9; padding: 15px; border: 1px solid #ddd; text-align: center;">
 | 
			
		||||
                            <h4 style="margin: 0 0 10px 0;">Missing Licenses</h4>
 | 
			
		||||
                            <div style="font-size: 24px; font-weight: bold; color: <?php echo $missing_licenses > 0 ? '#dc3232' : '#46b450'; ?>;"><?php echo $missing_licenses; ?></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <?php if ($expired_licenses > 0): ?>
 | 
			
		||||
                    <div style="margin-top: 15px; padding: 10px; background: #fff3cd; border: 1px solid #ffeaa7; border-left: 4px solid #f39c12;">
 | 
			
		||||
                        <strong>⚠️ Notice:</strong> <?php echo $expired_licenses; ?> license(s) have expired.
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <?php endif; ?>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <?php elseif ($active_tab == 'email-test') : ?>
 | 
			
		||||
                <!-- Email Testing Tab -->
 | 
			
		||||
                <div style="background: white; padding: 20px; margin-top: 20px; border: 1px solid #ccd0d4;">
 | 
			
		||||
                    <h2>📧 Email Test</h2>
 | 
			
		||||
                    <p>Test your email configuration by sending a test email.</p>
 | 
			
		||||
                    
 | 
			
		||||
                    <?php
 | 
			
		||||
                    // Handle email test submission
 | 
			
		||||
                    if (isset($_POST['send_test_email']) && wp_verify_nonce($_POST['_wpnonce'], 'wpdd_test_email')) {
 | 
			
		||||
                        $to_email = sanitize_email($_POST['test_email']);
 | 
			
		||||
                        $subject = sanitize_text_field($_POST['test_subject']);
 | 
			
		||||
                        $message = sanitize_textarea_field($_POST['test_message']);
 | 
			
		||||
                        
 | 
			
		||||
                        if (!empty($to_email) && is_email($to_email)) {
 | 
			
		||||
                            // Get SMTP settings
 | 
			
		||||
                            $smtp_enabled = get_option('wpdd_smtp_enabled');
 | 
			
		||||
                            $from_email = get_option('wpdd_from_email', get_option('admin_email'));
 | 
			
		||||
                            $from_name = get_option('wpdd_from_name', get_bloginfo('name'));
 | 
			
		||||
                            
 | 
			
		||||
                            // Set headers
 | 
			
		||||
                            $headers = array(
 | 
			
		||||
                                'From: ' . $from_name . ' <' . $from_email . '>',
 | 
			
		||||
                                'Content-Type: text/html; charset=UTF-8'
 | 
			
		||||
                            );
 | 
			
		||||
                            
 | 
			
		||||
                            // Configure SMTP if enabled
 | 
			
		||||
                            if ($smtp_enabled) {
 | 
			
		||||
                                add_action('phpmailer_init', 'wpdd_configure_smtp');
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            // Send email
 | 
			
		||||
                            $sent = wp_mail($to_email, $subject, nl2br($message), $headers);
 | 
			
		||||
                            
 | 
			
		||||
                            if ($sent) {
 | 
			
		||||
                                echo '<div class="notice notice-success"><p>✅ Test email sent successfully to ' . esc_html($to_email) . '</p></div>';
 | 
			
		||||
                            } else {
 | 
			
		||||
                                global $phpmailer;
 | 
			
		||||
                                $error_info = '';
 | 
			
		||||
                                if (isset($phpmailer) && is_object($phpmailer) && !empty($phpmailer->ErrorInfo)) {
 | 
			
		||||
                                    $error_info = $phpmailer->ErrorInfo;
 | 
			
		||||
                                }
 | 
			
		||||
                                echo '<div class="notice notice-error"><p>❌ Failed to send test email. ' . esc_html($error_info) . '</p></div>';
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            echo '<div class="notice notice-error"><p>Please enter a valid email address.</p></div>';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    ?>
 | 
			
		||||
                    
 | 
			
		||||
                    <form method="post" style="max-width: 600px;">
 | 
			
		||||
                        <?php wp_nonce_field('wpdd_test_email'); ?>
 | 
			
		||||
                        
 | 
			
		||||
                        <table class="form-table">
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th scope="row"><label for="test_email">To Email</label></th>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <input type="email" id="test_email" name="test_email" class="regular-text" 
 | 
			
		||||
                                           value="<?php echo esc_attr(get_option('admin_email')); ?>" required />
 | 
			
		||||
                                    <p class="description">Email address to send the test email to.</p>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th scope="row"><label for="test_subject">Subject</label></th>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <input type="text" id="test_subject" name="test_subject" class="regular-text" 
 | 
			
		||||
                                           value="Test Email from <?php echo esc_attr(get_bloginfo('name')); ?>" required />
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th scope="row"><label for="test_message">Message</label></th>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <textarea id="test_message" name="test_message" rows="5" cols="50" class="large-text">This is a test email from WP Digital Download plugin.
 | 
			
		||||
 | 
			
		||||
If you received this email, your email configuration is working correctly!
 | 
			
		||||
 | 
			
		||||
Site: <?php echo esc_html(get_bloginfo('name')); ?>
 | 
			
		||||
URL: <?php echo esc_html(get_bloginfo('url')); ?>
 | 
			
		||||
Time: <?php echo current_time('mysql'); ?></textarea>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </table>
 | 
			
		||||
                        
 | 
			
		||||
                        <p class="submit">
 | 
			
		||||
                            <input type="submit" name="send_test_email" class="button button-primary" value="Send Test Email" />
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </form>
 | 
			
		||||
                    
 | 
			
		||||
                    <hr style="margin: 30px 0;">
 | 
			
		||||
                    
 | 
			
		||||
                    <h3>📋 Current Email Configuration</h3>
 | 
			
		||||
                    <table class="widefat" style="max-width: 600px;">
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong>SMTP Enabled:</strong></td>
 | 
			
		||||
                            <td><?php echo get_option('wpdd_smtp_enabled') ? '✅ Yes' : '❌ No (using default mail)'; ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong>From Email:</strong></td>
 | 
			
		||||
                            <td><?php echo esc_html(get_option('wpdd_from_email', get_option('admin_email'))); ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong>From Name:</strong></td>
 | 
			
		||||
                            <td><?php echo esc_html(get_option('wpdd_from_name', get_bloginfo('name'))); ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <?php if (get_option('wpdd_smtp_enabled')) : ?>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong>SMTP Host:</strong></td>
 | 
			
		||||
                            <td><?php echo esc_html(get_option('wpdd_smtp_host')); ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong>SMTP Port:</strong></td>
 | 
			
		||||
                            <td><?php echo esc_html(get_option('wpdd_smtp_port')); ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong>SMTP Encryption:</strong></td>
 | 
			
		||||
                            <td><?php echo esc_html(get_option('wpdd_smtp_encryption')); ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong>SMTP Username:</strong></td>
 | 
			
		||||
                            <td><?php echo get_option('wpdd_smtp_username') ? '✅ Set' : '❌ Not set'; ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <?php endif; ?>
 | 
			
		||||
                    </table>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <?php elseif ($active_tab == 'email-logs') : ?>
 | 
			
		||||
                <!-- Email Logs Tab -->
 | 
			
		||||
                <div style="background: white; padding: 20px; margin-top: 20px; border: 1px solid #ccd0d4;">
 | 
			
		||||
                    <h2>📨 Email Logs</h2>
 | 
			
		||||
                    <p>View the last 100 emails sent by the plugin.</p>
 | 
			
		||||
                    
 | 
			
		||||
                    <?php
 | 
			
		||||
                    global $wpdb;
 | 
			
		||||
                    $table_name = $wpdb->prefix . 'wpdd_email_logs';
 | 
			
		||||
                    
 | 
			
		||||
                    // Check if table exists
 | 
			
		||||
                    if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
 | 
			
		||||
                        echo '<div class="notice notice-warning"><p>Email logs table not found. It will be created when the first email is sent.</p></div>';
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Get email logs
 | 
			
		||||
                        $logs = $wpdb->get_results(
 | 
			
		||||
                            "SELECT * FROM $table_name ORDER BY sent_at DESC LIMIT 100"
 | 
			
		||||
                        );
 | 
			
		||||
                        
 | 
			
		||||
                        if (empty($logs)) {
 | 
			
		||||
                            echo '<p>No email logs found.</p>';
 | 
			
		||||
                        } else {
 | 
			
		||||
                            ?>
 | 
			
		||||
                            <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                                <thead>
 | 
			
		||||
                                    <tr>
 | 
			
		||||
                                        <th style="width: 150px;">Date/Time</th>
 | 
			
		||||
                                        <th style="width: 200px;">To</th>
 | 
			
		||||
                                        <th>Subject</th>
 | 
			
		||||
                                        <th style="width: 100px;">Status</th>
 | 
			
		||||
                                        <th style="width: 100px;">Type</th>
 | 
			
		||||
                                        <th style="width: 80px;">Actions</th>
 | 
			
		||||
                                    </tr>
 | 
			
		||||
                                </thead>
 | 
			
		||||
                                <tbody>
 | 
			
		||||
                                    <?php foreach ($logs as $log) : ?>
 | 
			
		||||
                                    <tr>
 | 
			
		||||
                                        <td><?php echo esc_html($log->sent_at); ?></td>
 | 
			
		||||
                                        <td><?php echo esc_html($log->to_email); ?></td>
 | 
			
		||||
                                        <td><?php echo esc_html($log->subject); ?></td>
 | 
			
		||||
                                        <td>
 | 
			
		||||
                                            <?php if ($log->status == 'sent') : ?>
 | 
			
		||||
                                                <span style="color: #46b450;">✅ Sent</span>
 | 
			
		||||
                                            <?php else : ?>
 | 
			
		||||
                                                <span style="color: #dc3232;">❌ Failed</span>
 | 
			
		||||
                                            <?php endif; ?>
 | 
			
		||||
                                        </td>
 | 
			
		||||
                                        <td><?php echo esc_html($log->email_type); ?></td>
 | 
			
		||||
                                        <td>
 | 
			
		||||
                                            <button class="button button-small view-email-details" 
 | 
			
		||||
                                                    data-id="<?php echo $log->id; ?>">View</button>
 | 
			
		||||
                                        </td>
 | 
			
		||||
                                    </tr>
 | 
			
		||||
                                    <tr id="email-details-<?php echo $log->id; ?>" style="display: none;">
 | 
			
		||||
                                        <td colspan="6" style="background: #f9f9f9; padding: 10px;">
 | 
			
		||||
                                            <strong>Message:</strong><br>
 | 
			
		||||
                                            <div style="border: 1px solid #ddd; padding: 10px; background: white; margin-top: 5px;">
 | 
			
		||||
                                                <?php echo wp_kses_post($log->message); ?>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            <?php if (!empty($log->error_message)) : ?>
 | 
			
		||||
                                            <br><strong>Error:</strong> <?php echo esc_html($log->error_message); ?>
 | 
			
		||||
                                            <?php endif; ?>
 | 
			
		||||
                                        </td>
 | 
			
		||||
                                    </tr>
 | 
			
		||||
                                    <?php endforeach; ?>
 | 
			
		||||
                                </tbody>
 | 
			
		||||
                            </table>
 | 
			
		||||
                            
 | 
			
		||||
                            <script>
 | 
			
		||||
                            jQuery(document).ready(function($) {
 | 
			
		||||
                                $('.view-email-details').on('click', function() {
 | 
			
		||||
                                    var id = $(this).data('id');
 | 
			
		||||
                                    $('#email-details-' + id).toggle();
 | 
			
		||||
                                    $(this).text($(this).text() == 'View' ? 'Hide' : 'View');
 | 
			
		||||
                                });
 | 
			
		||||
                            });
 | 
			
		||||
                            </script>
 | 
			
		||||
                            <?php
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    ?>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <?php endif; ?>
 | 
			
		||||
                
 | 
			
		||||
            </div>
 | 
			
		||||
            <?php
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// SMTP configuration function
 | 
			
		||||
function wpdd_configure_smtp($phpmailer) {
 | 
			
		||||
    $phpmailer->isSMTP();
 | 
			
		||||
    $phpmailer->Host = get_option('wpdd_smtp_host');
 | 
			
		||||
    $phpmailer->Port = get_option('wpdd_smtp_port', 587);
 | 
			
		||||
    $phpmailer->SMTPAuth = true;
 | 
			
		||||
    $phpmailer->Username = get_option('wpdd_smtp_username');
 | 
			
		||||
    $phpmailer->Password = get_option('wpdd_smtp_password');
 | 
			
		||||
    $phpmailer->SMTPSecure = get_option('wpdd_smtp_encryption', 'tls');
 | 
			
		||||
    $phpmailer->From = get_option('wpdd_from_email', get_option('admin_email'));
 | 
			
		||||
    $phpmailer->FromName = get_option('wpdd_from_name', get_bloginfo('name'));
 | 
			
		||||
}
 | 
			
		||||
@@ -466,4 +466,63 @@
 | 
			
		||||
.wpdd-login-required a {
 | 
			
		||||
    color: #856404;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* License Key Display */
 | 
			
		||||
.wpdd-license-row {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-license-cell {
 | 
			
		||||
    padding: 8px 12px !important;
 | 
			
		||||
    border-top: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-license-info {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 8px;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-license-info small {
 | 
			
		||||
    color: #666;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-license-key {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    padding: 4px 8px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    font-family: monospace;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    color: #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-copy-license {
 | 
			
		||||
    background: #0073aa;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border: none;
 | 
			
		||||
    padding: 4px 8px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    transition: background 0.3s;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    min-width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-copy-license:hover {
 | 
			
		||||
    background: #005a87;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-copy-license i {
 | 
			
		||||
    font-size: 11px;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								assets/js/admin-order-manager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								assets/js/admin-order-manager.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
jQuery(document).ready(function($) {
 | 
			
		||||
    // Handle cancel order button clicks
 | 
			
		||||
    $('.wpdd-cancel-btn').on('click', function(e) {
 | 
			
		||||
        if (!confirm(wpdd_order_manager.confirm_cancel)) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Handle refund order button clicks
 | 
			
		||||
    $('.wpdd-refund-btn').on('click', function(e) {
 | 
			
		||||
        if (!confirm(wpdd_order_manager.confirm_refund)) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Handle release earnings button clicks
 | 
			
		||||
    $('.wpdd-release-btn').on('click', function(e) {
 | 
			
		||||
        if (!confirm(wpdd_order_manager.confirm_release)) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Auto-submit form when filter values change (optional convenience feature)
 | 
			
		||||
    $('#status, #creator').on('change', function() {
 | 
			
		||||
        // Uncomment the next line if you want auto-submit on filter change
 | 
			
		||||
        // $(this).closest('form').submit();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,4 +1,39 @@
 | 
			
		||||
jQuery(document).ready(function($) {
 | 
			
		||||
    // Format currency
 | 
			
		||||
    function formatCurrency(amount) {
 | 
			
		||||
        return '$' + parseFloat(amount).toFixed(2);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Handle creator selection for manual payout
 | 
			
		||||
    $('#creator_id').on('change', function() {
 | 
			
		||||
        var selectedOption = $(this).find('option:selected');
 | 
			
		||||
        var balance = selectedOption.data('balance');
 | 
			
		||||
        var balanceDisplay = $('#creator_balance_display');
 | 
			
		||||
        var balanceAmount = $('#creator_balance_amount');
 | 
			
		||||
        
 | 
			
		||||
        if ($(this).val() && balance !== undefined) {
 | 
			
		||||
            balanceAmount.text(formatCurrency(balance));
 | 
			
		||||
            balanceDisplay.show();
 | 
			
		||||
        } else {
 | 
			
		||||
            balanceDisplay.hide();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Handle creator selection for balance adjustment
 | 
			
		||||
    $('#adj_creator_id').on('change', function() {
 | 
			
		||||
        var selectedOption = $(this).find('option:selected');
 | 
			
		||||
        var balance = selectedOption.data('balance');
 | 
			
		||||
        var balanceDisplay = $('#adj_creator_balance_display');
 | 
			
		||||
        var balanceAmount = $('#adj_creator_balance_amount');
 | 
			
		||||
        
 | 
			
		||||
        if ($(this).val() && balance !== undefined) {
 | 
			
		||||
            balanceAmount.text(formatCurrency(balance));
 | 
			
		||||
            balanceDisplay.show();
 | 
			
		||||
        } else {
 | 
			
		||||
            balanceDisplay.hide();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Handle individual payout processing
 | 
			
		||||
    $('.wpdd-process-payout').on('click', function(e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ jQuery(document).ready(function($) {
 | 
			
		||||
            this.initFileManager();
 | 
			
		||||
            this.initPriceToggle();
 | 
			
		||||
            this.initFormValidation();
 | 
			
		||||
            this.initProductSync();
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initFileManager: function() {
 | 
			
		||||
@@ -324,6 +325,51 @@ jQuery(document).ready(function($) {
 | 
			
		||||
                $('.wpdd-settings-content').hide();
 | 
			
		||||
                $(target).show();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initProductSync: function() {
 | 
			
		||||
            // Handle software product sync
 | 
			
		||||
            $(document).on('click', '.wpdd-sync-product', function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                
 | 
			
		||||
                var $button = $(this);
 | 
			
		||||
                var productId = $button.data('product-id');
 | 
			
		||||
                var nonce = $button.data('nonce');
 | 
			
		||||
                
 | 
			
		||||
                if (!productId || !nonce) {
 | 
			
		||||
                    WPDD_Admin.showAdminNotice('Invalid sync request', 'error');
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Show loading state
 | 
			
		||||
                $button.prop('disabled', true).text('Syncing...');
 | 
			
		||||
                
 | 
			
		||||
                $.ajax({
 | 
			
		||||
                    url: ajaxurl,
 | 
			
		||||
                    type: 'POST',
 | 
			
		||||
                    data: {
 | 
			
		||||
                        action: 'wpdd_sync_software_product',
 | 
			
		||||
                        product_id: productId,
 | 
			
		||||
                        nonce: nonce
 | 
			
		||||
                    },
 | 
			
		||||
                    success: function(response) {
 | 
			
		||||
                        if (response.success) {
 | 
			
		||||
                            WPDD_Admin.showAdminNotice(response.message, 'success');
 | 
			
		||||
                            // Reload the page to show updated file count
 | 
			
		||||
                            setTimeout(function() {
 | 
			
		||||
                                window.location.reload();
 | 
			
		||||
                            }, 1500);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            WPDD_Admin.showAdminNotice(response.message || 'Sync failed', 'error');
 | 
			
		||||
                            $button.prop('disabled', false).text('Sync Files');
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    error: function() {
 | 
			
		||||
                        WPDD_Admin.showAdminNotice('Sync request failed', 'error');
 | 
			
		||||
                        $button.prop('disabled', false).text('Sync Files');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,9 @@ jQuery(document).ready(function($) {
 | 
			
		||||
            
 | 
			
		||||
            // Download status check
 | 
			
		||||
            $(document).on('click', '.wpdd-check-download', this.checkDownloadStatus);
 | 
			
		||||
            
 | 
			
		||||
            // Copy license key
 | 
			
		||||
            $(document).on('click', '.wpdd-copy-license', this.copyLicenseKey);
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initProductCards: function() {
 | 
			
		||||
@@ -358,6 +361,61 @@ jQuery(document).ready(function($) {
 | 
			
		||||
        
 | 
			
		||||
        formatPrice: function(price) {
 | 
			
		||||
            return '$' + parseFloat(price).toFixed(2);
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        copyLicenseKey: function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            var $button = $(this);
 | 
			
		||||
            var licenseKey = $button.data('license');
 | 
			
		||||
            
 | 
			
		||||
            if (!licenseKey) {
 | 
			
		||||
                WPDD.showNotice('No license key to copy', 'error');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Try to use the modern clipboard API
 | 
			
		||||
            if (navigator.clipboard && window.isSecureContext) {
 | 
			
		||||
                navigator.clipboard.writeText(licenseKey).then(function() {
 | 
			
		||||
                    WPDD.showNotice('License key copied to clipboard!', 'success');
 | 
			
		||||
                    $button.text('Copied!');
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        $button.text('Copy');
 | 
			
		||||
                    }, 2000);
 | 
			
		||||
                }).catch(function() {
 | 
			
		||||
                    WPDD.fallbackCopyTextToClipboard(licenseKey, $button);
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                WPDD.fallbackCopyTextToClipboard(licenseKey, $button);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        fallbackCopyTextToClipboard: function(text, $button) {
 | 
			
		||||
            var textArea = document.createElement('textarea');
 | 
			
		||||
            textArea.value = text;
 | 
			
		||||
            textArea.style.position = 'fixed';
 | 
			
		||||
            textArea.style.left = '-999999px';
 | 
			
		||||
            textArea.style.top = '-999999px';
 | 
			
		||||
            document.body.appendChild(textArea);
 | 
			
		||||
            textArea.focus();
 | 
			
		||||
            textArea.select();
 | 
			
		||||
            
 | 
			
		||||
            try {
 | 
			
		||||
                var successful = document.execCommand('copy');
 | 
			
		||||
                if (successful) {
 | 
			
		||||
                    WPDD.showNotice('License key copied to clipboard!', 'success');
 | 
			
		||||
                    $button.text('Copied!');
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        $button.text('Copy');
 | 
			
		||||
                    }, 2000);
 | 
			
		||||
                } else {
 | 
			
		||||
                    WPDD.showNotice('Failed to copy license key', 'error');
 | 
			
		||||
                }
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
                WPDD.showNotice('Failed to copy license key', 'error');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            document.body.removeChild(textArea);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -371,8 +371,22 @@ class WPDD_API {
 | 
			
		||||
            array('%d', '%d', '%d', '%s', '%s', '%s', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Serve file
 | 
			
		||||
        $filename = basename($package_path);
 | 
			
		||||
        // Serve file with proper filename
 | 
			
		||||
        $original_filename = basename($package_path);
 | 
			
		||||
        $original_extension = pathinfo($original_filename, PATHINFO_EXTENSION);
 | 
			
		||||
        
 | 
			
		||||
        // If no extension, assume it's a zip file
 | 
			
		||||
        if (empty($original_extension)) {
 | 
			
		||||
            $original_extension = 'zip';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create sanitized filename using product name and version
 | 
			
		||||
        $product_name = $product->post_title;
 | 
			
		||||
        $version = $latest_version->version;
 | 
			
		||||
        $safe_name = str_replace([' ', '.'], ['-', '_'], $product_name . ' v' . $version);
 | 
			
		||||
        $safe_name = sanitize_file_name($safe_name);
 | 
			
		||||
        $filename = $safe_name . '.' . $original_extension;
 | 
			
		||||
        
 | 
			
		||||
        header('Content-Type: application/zip');
 | 
			
		||||
        header('Content-Disposition: attachment; filename="' . $filename . '"');
 | 
			
		||||
        header('Content-Length: ' . filesize($package_path));
 | 
			
		||||
@@ -632,6 +646,27 @@ class WPDD_API {
 | 
			
		||||
        // Update product version meta
 | 
			
		||||
        update_post_meta($product_id, '_wpdd_current_version', $version);
 | 
			
		||||
        
 | 
			
		||||
        // Update product files to include the new package
 | 
			
		||||
        $files = get_post_meta($product_id, '_wpdd_files', true);
 | 
			
		||||
        if (!is_array($files)) {
 | 
			
		||||
            $files = array();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Add or update the package file in the files list
 | 
			
		||||
        $package_file = array(
 | 
			
		||||
            'id' => 'package_' . $version,
 | 
			
		||||
            'name' => get_the_title($product_id) . ' v' . $version,
 | 
			
		||||
            'url' => $package_url
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Remove any existing package entries and add the new one as the first file
 | 
			
		||||
        $files = array_filter($files, function($file) {
 | 
			
		||||
            return !isset($file['id']) || strpos($file['id'], 'package_') !== 0;
 | 
			
		||||
        });
 | 
			
		||||
        array_unshift($files, $package_file);
 | 
			
		||||
        
 | 
			
		||||
        update_post_meta($product_id, '_wpdd_files', $files);
 | 
			
		||||
        
 | 
			
		||||
        // Notify customers about update (optional)
 | 
			
		||||
        self::notify_customers_about_update($product_id, $version);
 | 
			
		||||
        
 | 
			
		||||
@@ -782,4 +817,128 @@ class WPDD_API {
 | 
			
		||||
        // Optional: Send email notifications to customers with active licenses
 | 
			
		||||
        // This could be a separate scheduled job to avoid timeout issues
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Manually sync a software product with its latest Git release
 | 
			
		||||
     * This can be used to fix products that don't have files or need updates
 | 
			
		||||
     */
 | 
			
		||||
    public static function sync_software_product($product_id, $force_rebuild = false) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Check if product is software license type
 | 
			
		||||
        $product_type = get_post_meta($product_id, '_wpdd_product_type', true);
 | 
			
		||||
        if ($product_type !== 'software_license') {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => 'not_software_license',
 | 
			
		||||
                'message' => __('Product is not a software license product.', 'wp-digital-download')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get Git repository settings
 | 
			
		||||
        $git_url = get_post_meta($product_id, '_wpdd_git_repository', true);
 | 
			
		||||
        $git_username = get_post_meta($product_id, '_wpdd_git_username', true);
 | 
			
		||||
        $git_token = get_post_meta($product_id, '_wpdd_git_token', true);
 | 
			
		||||
        $current_version = get_post_meta($product_id, '_wpdd_current_version', true);
 | 
			
		||||
        
 | 
			
		||||
        if (!$git_url) {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => 'no_git_url',
 | 
			
		||||
                'message' => __('No Git repository URL configured.', 'wp-digital-download')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$current_version) {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => 'no_version',
 | 
			
		||||
                'message' => __('No current version specified. Please set a version in the product settings.', 'wp-digital-download')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Check if we already have this version
 | 
			
		||||
        $existing_version = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_software_versions 
 | 
			
		||||
             WHERE product_id = %d AND version = %s",
 | 
			
		||||
            $product_id,
 | 
			
		||||
            $current_version
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $package_url = null;
 | 
			
		||||
        
 | 
			
		||||
        if (!$existing_version || $force_rebuild) {
 | 
			
		||||
            // Build package from current version
 | 
			
		||||
            $package_url = self::build_package_from_git($product_id, $git_url, 'v' . $current_version, $git_username, $git_token);
 | 
			
		||||
            
 | 
			
		||||
            if (!$package_url) {
 | 
			
		||||
                // Try without 'v' prefix
 | 
			
		||||
                $package_url = self::build_package_from_git($product_id, $git_url, $current_version, $git_username, $git_token);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (!$package_url) {
 | 
			
		||||
                return array(
 | 
			
		||||
                    'success' => false,
 | 
			
		||||
                    'error' => 'build_failed',
 | 
			
		||||
                    'message' => __('Failed to build package from repository.', 'wp-digital-download')
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Insert or update version record
 | 
			
		||||
            if ($existing_version) {
 | 
			
		||||
                $wpdb->update(
 | 
			
		||||
                    $wpdb->prefix . 'wpdd_software_versions',
 | 
			
		||||
                    array(
 | 
			
		||||
                        'package_url' => $package_url,
 | 
			
		||||
                        'release_date' => current_time('mysql')
 | 
			
		||||
                    ),
 | 
			
		||||
                    array('id' => $existing_version->id),
 | 
			
		||||
                    array('%s', '%s'),
 | 
			
		||||
                    array('%d')
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                $wpdb->insert(
 | 
			
		||||
                    $wpdb->prefix . 'wpdd_software_versions',
 | 
			
		||||
                    array(
 | 
			
		||||
                        'product_id' => $product_id,
 | 
			
		||||
                        'version' => $current_version,
 | 
			
		||||
                        'package_url' => $package_url,
 | 
			
		||||
                        'git_tag' => 'v' . $current_version,
 | 
			
		||||
                        'release_date' => current_time('mysql')
 | 
			
		||||
                    ),
 | 
			
		||||
                    array('%d', '%s', '%s', '%s', '%s')
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $package_url = $existing_version->package_url;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Update product files to include the package
 | 
			
		||||
        $files = get_post_meta($product_id, '_wpdd_files', true);
 | 
			
		||||
        if (!is_array($files)) {
 | 
			
		||||
            $files = array();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Add or update the package file in the files list
 | 
			
		||||
        $package_file = array(
 | 
			
		||||
            'id' => 'package_' . $current_version,
 | 
			
		||||
            'name' => get_the_title($product_id) . ' v' . $current_version,
 | 
			
		||||
            'url' => $package_url
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Remove any existing package entries and add the new one as the first file
 | 
			
		||||
        $files = array_filter($files, function($file) {
 | 
			
		||||
            return !isset($file['id']) || strpos($file['id'], 'package_') !== 0;
 | 
			
		||||
        });
 | 
			
		||||
        array_unshift($files, $package_file);
 | 
			
		||||
        
 | 
			
		||||
        update_post_meta($product_id, '_wpdd_files', $files);
 | 
			
		||||
        
 | 
			
		||||
        return array(
 | 
			
		||||
            'success' => true,
 | 
			
		||||
            'message' => __('Product synced successfully.', 'wp-digital-download'),
 | 
			
		||||
            'version' => $current_version,
 | 
			
		||||
            'package_url' => $package_url
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -86,14 +86,72 @@ class WPDD_Creator {
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_creator_balance($user_id) {
 | 
			
		||||
        return floatval(get_user_meta($user_id, 'wpdd_creator_balance', true));
 | 
			
		||||
        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.total) 
 | 
			
		||||
            "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 
 | 
			
		||||
@@ -130,12 +188,20 @@ class WPDD_Creator {
 | 
			
		||||
        
 | 
			
		||||
        $creator_id = $product->post_author;
 | 
			
		||||
        $commission_rate = floatval(get_option('wpdd_commission_rate', 0));
 | 
			
		||||
        $creator_share = $order->total * (1 - ($commission_rate / 100));
 | 
			
		||||
        $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',
 | 
			
		||||
@@ -143,12 +209,14 @@ class WPDD_Creator {
 | 
			
		||||
                'creator_id' => $creator_id,
 | 
			
		||||
                'order_id' => $order_id,
 | 
			
		||||
                'product_id' => $order->product_id,
 | 
			
		||||
                'sale_amount' => $order->total,
 | 
			
		||||
                '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')
 | 
			
		||||
            array('%d', '%d', '%d', '%f', '%f', '%f', '%s', '%s', '%s')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -87,16 +87,16 @@ class WPDD_Download_Handler {
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function process_download_by_order() {
 | 
			
		||||
        $download_link_id = intval($_GET['wpdd_download']);
 | 
			
		||||
        $order_id = intval($_GET['wpdd_download']);
 | 
			
		||||
        
 | 
			
		||||
        // Debug nonce verification
 | 
			
		||||
        if (current_user_can('manage_options')) {
 | 
			
		||||
            error_log('WPDD Debug: Download Link ID: ' . $download_link_id);
 | 
			
		||||
            error_log('WPDD Debug: Order ID: ' . $order_id);
 | 
			
		||||
            error_log('WPDD Debug: Nonce received: ' . ($_GET['_wpnonce'] ?? 'none'));
 | 
			
		||||
            error_log('WPDD Debug: Expected nonce action: wpdd_download_' . $download_link_id);
 | 
			
		||||
            error_log('WPDD Debug: Expected nonce action: wpdd_download_' . $order_id);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'wpdd_download_' . $download_link_id)) {
 | 
			
		||||
        if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'wpdd_download_' . $order_id)) {
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                error_log('WPDD Debug: Nonce verification failed!');
 | 
			
		||||
            }
 | 
			
		||||
@@ -105,18 +105,6 @@ class WPDD_Download_Handler {
 | 
			
		||||
        
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // First get the download link to find the associated order
 | 
			
		||||
        $download_link = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_download_links WHERE id = %d",
 | 
			
		||||
            $download_link_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$download_link) {
 | 
			
		||||
            wp_die(__('Invalid download link.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $order_id = $download_link->order_id;
 | 
			
		||||
        
 | 
			
		||||
        // Check by email if guest
 | 
			
		||||
        if (isset($_GET['customer_email']) && isset($_GET['key'])) {
 | 
			
		||||
            $email = sanitize_email($_GET['customer_email']);
 | 
			
		||||
@@ -436,16 +424,17 @@ class WPDD_Download_Handler {
 | 
			
		||||
            
 | 
			
		||||
            if ($file_meta && file_exists($file_meta['file_path'])) {
 | 
			
		||||
                $enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
 | 
			
		||||
                $product_name = get_the_title($product_id);
 | 
			
		||||
                
 | 
			
		||||
                if ($enable_watermark && self::is_watermarkable($file_meta['file_path'])) {
 | 
			
		||||
                    $watermarked_file = WPDD_Watermark::apply_watermark($file_meta['file_path'], $order);
 | 
			
		||||
                    if ($watermarked_file) {
 | 
			
		||||
                        self::deliver_protected_file($watermarked_file, true);
 | 
			
		||||
                        self::deliver_protected_file($watermarked_file, true, $product_name);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                        self::deliver_protected_file($file_meta['file_path'], false, $product_name);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                    self::deliver_protected_file($file_meta['file_path'], false, $product_name);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -464,16 +453,17 @@ class WPDD_Download_Handler {
 | 
			
		||||
                
 | 
			
		||||
                if ($file_meta && file_exists($file_meta['file_path'])) {
 | 
			
		||||
                    $enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
 | 
			
		||||
                    $product_name = get_the_title($product_id);
 | 
			
		||||
                    
 | 
			
		||||
                    if ($enable_watermark && self::is_watermarkable($file_meta['file_path'])) {
 | 
			
		||||
                        $watermarked_file = WPDD_Watermark::apply_watermark($file_meta['file_path'], $order);
 | 
			
		||||
                        if ($watermarked_file) {
 | 
			
		||||
                            self::deliver_protected_file($watermarked_file, true);
 | 
			
		||||
                            self::deliver_protected_file($watermarked_file, true, $product_name);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                            self::deliver_protected_file($file_meta['file_path'], false, $product_name);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                        self::deliver_protected_file($file_meta['file_path'], false, $product_name);
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
@@ -483,15 +473,32 @@ class WPDD_Download_Handler {
 | 
			
		||||
        // Regular file handling (backward compatibility)
 | 
			
		||||
        $enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
 | 
			
		||||
        
 | 
			
		||||
        // Generate proper filename for software license products
 | 
			
		||||
        $product_type = get_post_meta($product_id, '_wpdd_product_type', true);
 | 
			
		||||
        $final_filename = $file['name'];
 | 
			
		||||
        
 | 
			
		||||
        if ($product_type === 'software_license') {
 | 
			
		||||
            // Check if this is a package file (contains version info)
 | 
			
		||||
            if (strpos($file['url'], 'wpdd-packages/') !== false && preg_match('/package-v([^\/]+)\.zip$/', $file['url'], $matches)) {
 | 
			
		||||
                $version = $matches[1];
 | 
			
		||||
                $product_name = get_the_title($product_id);
 | 
			
		||||
                
 | 
			
		||||
                // Create sanitized filename using product name and version
 | 
			
		||||
                $safe_name = str_replace([' ', '.'], ['-', '_'], $product_name . ' v' . $version);
 | 
			
		||||
                $safe_name = sanitize_file_name($safe_name);
 | 
			
		||||
                $final_filename = $safe_name . '.zip';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($enable_watermark && self::is_watermarkable($file['url'])) {
 | 
			
		||||
            $watermarked_file = WPDD_Watermark::apply_watermark($file['url'], $order);
 | 
			
		||||
            if ($watermarked_file) {
 | 
			
		||||
                self::deliver_file($watermarked_file, $file['name'], true);
 | 
			
		||||
                self::deliver_file($watermarked_file, $final_filename, true);
 | 
			
		||||
            } else {
 | 
			
		||||
                self::deliver_file($file['url'], $file['name']);
 | 
			
		||||
                self::deliver_file($file['url'], $final_filename);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            self::deliver_file($file['url'], $file['name']);
 | 
			
		||||
            self::deliver_file($file['url'], $final_filename);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@@ -606,18 +613,77 @@ class WPDD_Download_Handler {
 | 
			
		||||
        self::deliver_protected_file($protected_path);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function deliver_protected_file($file_path, $is_temp = false) {
 | 
			
		||||
    private static function deliver_protected_file($file_path, $is_temp = false, $product_name = null) {
 | 
			
		||||
        if (!file_exists($file_path)) {
 | 
			
		||||
            wp_die(__('File not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $file_name = basename($file_path);
 | 
			
		||||
        $file_size = filesize($file_path);
 | 
			
		||||
        $file_type = wp_check_filetype($file_path);
 | 
			
		||||
        $file_info = wp_check_filetype($file_path);
 | 
			
		||||
        
 | 
			
		||||
        // If product name is provided, use it with the original extension
 | 
			
		||||
        if ($product_name) {
 | 
			
		||||
            $original_file_name = basename($file_path);
 | 
			
		||||
            $original_extension = pathinfo($original_file_name, PATHINFO_EXTENSION);
 | 
			
		||||
            
 | 
			
		||||
            // If no extension found, try to detect it
 | 
			
		||||
            if (empty($original_extension)) {
 | 
			
		||||
                // Check if it's a zip file by reading the first few bytes
 | 
			
		||||
                $handle = fopen($file_path, 'rb');
 | 
			
		||||
                if ($handle) {
 | 
			
		||||
                    $header = fread($handle, 4);
 | 
			
		||||
                    fclose($handle);
 | 
			
		||||
                    
 | 
			
		||||
                    // ZIP files start with PK (0x504B)
 | 
			
		||||
                    if (substr($header, 0, 2) === 'PK') {
 | 
			
		||||
                        $original_extension = 'zip';
 | 
			
		||||
                        $file_info['type'] = 'application/zip';
 | 
			
		||||
                        $file_info['ext'] = 'zip';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // If still no extension, try wp_check_filetype result
 | 
			
		||||
                if (empty($original_extension) && !empty($file_info['ext'])) {
 | 
			
		||||
                    $original_extension = $file_info['ext'];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Sanitize product name for filename use
 | 
			
		||||
            // Convert spaces to dashes and dots to underscores, then use standard sanitization
 | 
			
		||||
            $safe_product_name = str_replace([' ', '.'], ['-', '_'], $product_name);
 | 
			
		||||
            $safe_product_name = sanitize_file_name($safe_product_name);
 | 
			
		||||
            $file_name = $safe_product_name . ($original_extension ? '.' . $original_extension : '');
 | 
			
		||||
        } else {
 | 
			
		||||
            // Fallback to original logic if no product name provided
 | 
			
		||||
            $file_name = basename($file_path);
 | 
			
		||||
            $has_extension = strpos($file_name, '.') !== false;
 | 
			
		||||
            
 | 
			
		||||
            // If no extension in filename, try to determine from content
 | 
			
		||||
            if (!$has_extension) {
 | 
			
		||||
                // Check if it's a zip file by reading the first few bytes
 | 
			
		||||
                $handle = fopen($file_path, 'rb');
 | 
			
		||||
                if ($handle) {
 | 
			
		||||
                    $header = fread($handle, 4);
 | 
			
		||||
                    fclose($handle);
 | 
			
		||||
                    
 | 
			
		||||
                    // ZIP files start with PK (0x504B)
 | 
			
		||||
                    if (substr($header, 0, 2) === 'PK') {
 | 
			
		||||
                        $file_info['type'] = 'application/zip';
 | 
			
		||||
                        $file_info['ext'] = 'zip';
 | 
			
		||||
                        $file_name .= '.zip';
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // If we still don't have an extension but wp_check_filetype detected one, add it
 | 
			
		||||
            if (!$has_extension && !empty($file_info['ext']) && strpos($file_name, '.' . $file_info['ext']) === false) {
 | 
			
		||||
                $file_name .= '.' . $file_info['ext'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        nocache_headers();
 | 
			
		||||
        
 | 
			
		||||
        header('Content-Type: ' . ($file_type['type'] ?: 'application/octet-stream'));
 | 
			
		||||
        header('Content-Type: ' . ($file_info['type'] ?: 'application/octet-stream'));
 | 
			
		||||
        header('Content-Disposition: attachment; filename="' . $file_name . '"');
 | 
			
		||||
        header('Content-Length: ' . $file_size);
 | 
			
		||||
        header('Content-Transfer-Encoding: binary');
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										189
									
								
								includes/class-wpdd-earnings-processor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								includes/class-wpdd-earnings-processor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Earnings_Processor {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        // Schedule the cron job
 | 
			
		||||
        add_action('wp', array(__CLASS__, 'schedule_earnings_processing'));
 | 
			
		||||
        add_action('wpdd_process_pending_earnings', array(__CLASS__, 'process_pending_earnings'));
 | 
			
		||||
        
 | 
			
		||||
        // Hook to plugin activation/deactivation
 | 
			
		||||
        register_activation_hook(WPDD_PLUGIN_PATH . 'wp-digital-download.php', array(__CLASS__, 'schedule_earnings_processing'));
 | 
			
		||||
        register_deactivation_hook(WPDD_PLUGIN_PATH . 'wp-digital-download.php', array(__CLASS__, 'clear_scheduled_earnings_processing'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function schedule_earnings_processing() {
 | 
			
		||||
        if (!wp_next_scheduled('wpdd_process_pending_earnings')) {
 | 
			
		||||
            // Run every hour to check for earnings that should be made available
 | 
			
		||||
            wp_schedule_event(time(), 'hourly', 'wpdd_process_pending_earnings');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function clear_scheduled_earnings_processing() {
 | 
			
		||||
        wp_clear_scheduled_hook('wpdd_process_pending_earnings');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function process_pending_earnings() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Find all earnings that are pending and past their available_at date
 | 
			
		||||
        $pending_earnings = $wpdb->get_results(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
             WHERE payout_status = 'pending' 
 | 
			
		||||
             AND available_at <= NOW() 
 | 
			
		||||
             AND available_at IS NOT NULL"
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (empty($pending_earnings)) {
 | 
			
		||||
            return; // Nothing to process
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Update all pending earnings to available
 | 
			
		||||
        $updated = $wpdb->query(
 | 
			
		||||
            "UPDATE {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
             SET payout_status = 'available' 
 | 
			
		||||
             WHERE payout_status = 'pending' 
 | 
			
		||||
             AND available_at <= NOW() 
 | 
			
		||||
             AND available_at IS NOT NULL"
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($updated > 0) {
 | 
			
		||||
            // Log the processing
 | 
			
		||||
            error_log("WPDD: Processed $updated pending earnings to available status");
 | 
			
		||||
            
 | 
			
		||||
            // Update creator balances for affected creators
 | 
			
		||||
            $affected_creators = $wpdb->get_col(
 | 
			
		||||
                "SELECT DISTINCT creator_id 
 | 
			
		||||
                 FROM {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
                 WHERE payout_status = 'available' 
 | 
			
		||||
                 AND available_at <= NOW()"
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            foreach ($affected_creators as $creator_id) {
 | 
			
		||||
                // Trigger balance recalculation
 | 
			
		||||
                $current_balance = WPDD_Creator::get_creator_balance($creator_id);
 | 
			
		||||
                update_user_meta($creator_id, 'wpdd_creator_balance', $current_balance);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Manually process a specific earning (for admin override)
 | 
			
		||||
     */
 | 
			
		||||
    public static function release_earning_immediately($earning_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $result = $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_creator_earnings',
 | 
			
		||||
            array(
 | 
			
		||||
                'payout_status' => 'available',
 | 
			
		||||
                'available_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array(
 | 
			
		||||
                'id' => $earning_id,
 | 
			
		||||
                'payout_status' => 'pending'
 | 
			
		||||
            ),
 | 
			
		||||
            array('%s', '%s'),
 | 
			
		||||
            array('%d', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            // Get the creator and update their balance
 | 
			
		||||
            $earning = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
                "SELECT creator_id FROM {$wpdb->prefix}wpdd_creator_earnings WHERE id = %d",
 | 
			
		||||
                $earning_id
 | 
			
		||||
            ));
 | 
			
		||||
            
 | 
			
		||||
            if ($earning) {
 | 
			
		||||
                $current_balance = WPDD_Creator::get_creator_balance($earning->creator_id);
 | 
			
		||||
                update_user_meta($earning->creator_id, 'wpdd_creator_balance', $current_balance);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $result > 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Cancel/refund a specific earning (for order cancellations)
 | 
			
		||||
     */
 | 
			
		||||
    public static function cancel_earning($earning_id, $reason = 'Order cancelled/refunded') {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get the earning details
 | 
			
		||||
        $earning = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_creator_earnings WHERE id = %d",
 | 
			
		||||
            $earning_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$earning) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Only cancel if not already paid out
 | 
			
		||||
        if ($earning->payout_status === 'paid') {
 | 
			
		||||
            return false; // Cannot cancel paid earnings
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Update to cancelled status
 | 
			
		||||
        $result = $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_creator_earnings',
 | 
			
		||||
            array(
 | 
			
		||||
                'payout_status' => 'cancelled',
 | 
			
		||||
                'available_at' => null
 | 
			
		||||
            ),
 | 
			
		||||
            array('id' => $earning_id),
 | 
			
		||||
            array('%s', '%s'),
 | 
			
		||||
            array('%d')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            // Log the cancellation
 | 
			
		||||
            $wpdb->insert(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_balance_adjustments',
 | 
			
		||||
                array(
 | 
			
		||||
                    'creator_id' => $earning->creator_id,
 | 
			
		||||
                    'adjustment_type' => 'subtract',
 | 
			
		||||
                    'amount' => $earning->creator_earning,
 | 
			
		||||
                    'previous_balance' => WPDD_Creator::get_creator_balance($earning->creator_id),
 | 
			
		||||
                    'new_balance' => WPDD_Creator::get_creator_balance($earning->creator_id) - $earning->creator_earning,
 | 
			
		||||
                    'reason' => $reason . ' (Order #' . $earning->order_id . ')',
 | 
			
		||||
                    'adjusted_by' => get_current_user_id(),
 | 
			
		||||
                    'created_at' => current_time('mysql')
 | 
			
		||||
                ),
 | 
			
		||||
                array('%d', '%s', '%f', '%f', '%f', '%s', '%d', '%s')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            // Update creator balance
 | 
			
		||||
            $current_balance = WPDD_Creator::get_creator_balance($earning->creator_id);
 | 
			
		||||
            update_user_meta($earning->creator_id, 'wpdd_creator_balance', $current_balance - $earning->creator_earning);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $result > 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get earnings summary for a creator
 | 
			
		||||
     */
 | 
			
		||||
    public static function get_earnings_summary($creator_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $summary = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT 
 | 
			
		||||
                COUNT(*) as total_earnings,
 | 
			
		||||
                SUM(CASE WHEN payout_status = 'pending' THEN creator_earning ELSE 0 END) as pending_amount,
 | 
			
		||||
                SUM(CASE WHEN payout_status = 'available' THEN creator_earning ELSE 0 END) as available_amount,
 | 
			
		||||
                SUM(CASE WHEN payout_status = 'paid' THEN creator_earning ELSE 0 END) as paid_amount,
 | 
			
		||||
                SUM(CASE WHEN payout_status = 'cancelled' THEN creator_earning ELSE 0 END) as cancelled_amount,
 | 
			
		||||
                SUM(creator_earning) as total_amount
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_creator_earnings 
 | 
			
		||||
             WHERE creator_id = %d",
 | 
			
		||||
            $creator_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        return $summary;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										211
									
								
								includes/class-wpdd-email.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								includes/class-wpdd-email.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,211 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Email handler class for WP Digital Download
 | 
			
		||||
 * Handles email sending with logging and SMTP configuration
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Email {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        // Configure SMTP if enabled
 | 
			
		||||
        if (get_option('wpdd_smtp_enabled')) {
 | 
			
		||||
            add_action('phpmailer_init', array(__CLASS__, 'configure_smtp'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Send email with logging
 | 
			
		||||
     * 
 | 
			
		||||
     * @param string $to Email address to send to
 | 
			
		||||
     * @param string $subject Email subject
 | 
			
		||||
     * @param string $message Email message (HTML)
 | 
			
		||||
     * @param string $email_type Type of email for logging (e.g., 'order_confirmation', 'download_link', etc.)
 | 
			
		||||
     * @param array $headers Optional email headers
 | 
			
		||||
     * @return bool Whether email was sent successfully
 | 
			
		||||
     */
 | 
			
		||||
    public static function send($to, $subject, $message, $email_type = 'general', $headers = array()) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Set default headers if not provided
 | 
			
		||||
        if (empty($headers)) {
 | 
			
		||||
            $from_email = get_option('wpdd_from_email', get_option('admin_email'));
 | 
			
		||||
            $from_name = get_option('wpdd_from_name', get_bloginfo('name'));
 | 
			
		||||
            
 | 
			
		||||
            $headers = array(
 | 
			
		||||
                'From: ' . $from_name . ' <' . $from_email . '>',
 | 
			
		||||
                'Content-Type: text/html; charset=UTF-8'
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Send the email
 | 
			
		||||
        $sent = wp_mail($to, $subject, $message, $headers);
 | 
			
		||||
        
 | 
			
		||||
        // Log the email
 | 
			
		||||
        $table_name = $wpdb->prefix . 'wpdd_email_logs';
 | 
			
		||||
        
 | 
			
		||||
        // Create table if it doesn't exist
 | 
			
		||||
        if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
 | 
			
		||||
            self::create_email_logs_table();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get error message if failed
 | 
			
		||||
        $error_message = '';
 | 
			
		||||
        if (!$sent) {
 | 
			
		||||
            global $phpmailer;
 | 
			
		||||
            if (isset($phpmailer) && is_object($phpmailer) && !empty($phpmailer->ErrorInfo)) {
 | 
			
		||||
                $error_message = $phpmailer->ErrorInfo;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Insert log entry
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $table_name,
 | 
			
		||||
            array(
 | 
			
		||||
                'to_email' => $to,
 | 
			
		||||
                'subject' => $subject,
 | 
			
		||||
                'message' => $message,
 | 
			
		||||
                'status' => $sent ? 'sent' : 'failed',
 | 
			
		||||
                'email_type' => $email_type,
 | 
			
		||||
                'error_message' => $error_message,
 | 
			
		||||
                'sent_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%s', '%s', '%s', '%s', '%s', '%s', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $sent;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Configure SMTP settings for PHPMailer
 | 
			
		||||
     */
 | 
			
		||||
    public static function configure_smtp($phpmailer) {
 | 
			
		||||
        $phpmailer->isSMTP();
 | 
			
		||||
        $phpmailer->Host = get_option('wpdd_smtp_host');
 | 
			
		||||
        $phpmailer->Port = get_option('wpdd_smtp_port', 587);
 | 
			
		||||
        $phpmailer->SMTPAuth = true;
 | 
			
		||||
        $phpmailer->Username = get_option('wpdd_smtp_username');
 | 
			
		||||
        $phpmailer->Password = get_option('wpdd_smtp_password');
 | 
			
		||||
        $phpmailer->SMTPSecure = get_option('wpdd_smtp_encryption', 'tls');
 | 
			
		||||
        $phpmailer->From = get_option('wpdd_from_email', get_option('admin_email'));
 | 
			
		||||
        $phpmailer->FromName = get_option('wpdd_from_name', get_bloginfo('name'));
 | 
			
		||||
        
 | 
			
		||||
        // Enable debugging for failed sends
 | 
			
		||||
        if (defined('WP_DEBUG') && WP_DEBUG) {
 | 
			
		||||
            $phpmailer->SMTPDebug = 2;
 | 
			
		||||
            $phpmailer->Debugoutput = 'error_log';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Create email logs table if it doesn't exist
 | 
			
		||||
     */
 | 
			
		||||
    private static function create_email_logs_table() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $table_name = $wpdb->prefix . 'wpdd_email_logs';
 | 
			
		||||
        $charset_collate = $wpdb->get_charset_collate();
 | 
			
		||||
        
 | 
			
		||||
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
 | 
			
		||||
            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;";
 | 
			
		||||
        
 | 
			
		||||
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
 | 
			
		||||
        dbDelta($sql);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Send order confirmation email
 | 
			
		||||
     */
 | 
			
		||||
    public static function send_order_confirmation($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($order->product_id);
 | 
			
		||||
        if (!$product) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $subject = sprintf(__('Order Confirmation - %s', 'wp-digital-download'), $product->post_title);
 | 
			
		||||
        
 | 
			
		||||
        $message = sprintf(
 | 
			
		||||
            __('<h2>Thank you for your purchase!</h2>
 | 
			
		||||
            <p>Your order has been confirmed.</p>
 | 
			
		||||
            <h3>Order Details:</h3>
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li><strong>Order ID:</strong> #%d</li>
 | 
			
		||||
                <li><strong>Product:</strong> %s</li>
 | 
			
		||||
                <li><strong>Amount:</strong> %s</li>
 | 
			
		||||
                <li><strong>Date:</strong> %s</li>
 | 
			
		||||
            </ul>
 | 
			
		||||
            <p>You can download your purchase from your account page.</p>
 | 
			
		||||
            <p>Thank you for your business!</p>', 'wp-digital-download'),
 | 
			
		||||
            $order->id,
 | 
			
		||||
            esc_html($product->post_title),
 | 
			
		||||
            wpdd_format_price($order->amount, $order->currency),
 | 
			
		||||
            $order->purchase_date
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return self::send($order->customer_email, $subject, $message, 'order_confirmation');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Send download link email
 | 
			
		||||
     */
 | 
			
		||||
    public static function send_download_link($order_id, $download_link) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($order->product_id);
 | 
			
		||||
        if (!$product) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $subject = sprintf(__('Your Download Link - %s', 'wp-digital-download'), $product->post_title);
 | 
			
		||||
        
 | 
			
		||||
        $message = sprintf(
 | 
			
		||||
            __('<h2>Your Download is Ready!</h2>
 | 
			
		||||
            <p>Click the link below to download your purchase:</p>
 | 
			
		||||
            <p><a href="%s" style="display: inline-block; padding: 10px 20px; background: #0073aa; color: white; text-decoration: none; border-radius: 3px;">Download Now</a></p>
 | 
			
		||||
            <h3>Product:</h3>
 | 
			
		||||
            <p>%s</p>
 | 
			
		||||
            <p><em>Note: This download link will expire. Please download your file as soon as possible.</em></p>
 | 
			
		||||
            <p>Thank you!</p>', 'wp-digital-download'),
 | 
			
		||||
            esc_url($download_link),
 | 
			
		||||
            esc_html($product->post_title)
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return self::send($order->customer_email, $subject, $message, 'download_link');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -104,11 +104,17 @@ class WPDD_Install {
 | 
			
		||||
            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 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 (
 | 
			
		||||
@@ -146,6 +152,22 @@ class WPDD_Install {
 | 
			
		||||
        ) $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,
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ class WPDD_License_Manager {
 | 
			
		||||
     * Initialize the license manager
 | 
			
		||||
     */
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('wpdd_order_completed', array(__CLASS__, 'generate_license_for_order'), 10, 2);
 | 
			
		||||
        add_action('wpdd_order_completed', array(__CLASS__, 'generate_license_for_order'), 10, 1);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
@@ -28,12 +28,28 @@ class WPDD_License_Manager {
 | 
			
		||||
    /**
 | 
			
		||||
     * Generate license for completed order
 | 
			
		||||
     */
 | 
			
		||||
    public static function generate_license_for_order($order_id, $order) {
 | 
			
		||||
    public static function generate_license_for_order($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        error_log('WPDD License Debug: generate_license_for_order called for order ID: ' . $order_id);
 | 
			
		||||
        
 | 
			
		||||
        // Get the order details
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            error_log('WPDD License Debug: Order not found for ID: ' . $order_id);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Check if product is software license type
 | 
			
		||||
        $product_type = get_post_meta($order->product_id, '_wpdd_product_type', true);
 | 
			
		||||
        error_log('WPDD License Debug: Product type for product ' . $order->product_id . ': ' . $product_type);
 | 
			
		||||
        
 | 
			
		||||
        if ($product_type !== 'software_license') {
 | 
			
		||||
            error_log('WPDD License Debug: Product type is not software_license, skipping license generation');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
@@ -44,9 +60,12 @@ class WPDD_License_Manager {
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if ($existing) {
 | 
			
		||||
            error_log('WPDD License Debug: License already exists for order ' . $order_id . ', license ID: ' . $existing);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        error_log('WPDD License Debug: Generating new license for order ' . $order_id);
 | 
			
		||||
        
 | 
			
		||||
        // Generate unique license key
 | 
			
		||||
        $license_key = self::generate_license_key();
 | 
			
		||||
        
 | 
			
		||||
@@ -81,6 +100,12 @@ class WPDD_License_Manager {
 | 
			
		||||
            array('%s', '%d', '%d', '%d', '%s', '%s', '%d', '%s', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($wpdb->insert_id) {
 | 
			
		||||
            error_log('WPDD License Debug: License created successfully with ID: ' . $wpdb->insert_id . ', license key: ' . $license_key);
 | 
			
		||||
        } else {
 | 
			
		||||
            error_log('WPDD License Debug: Failed to create license. Last error: ' . $wpdb->last_error);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Send license key to customer
 | 
			
		||||
        self::send_license_email($order, $license_key);
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
@@ -316,7 +316,7 @@ class WPDD_Metaboxes {
 | 
			
		||||
                        <?php _e('Download Limit', 'wp-digital-download'); ?>
 | 
			
		||||
                    </label>
 | 
			
		||||
                    <input type="number" id="wpdd_download_limit" name="wpdd_download_limit" 
 | 
			
		||||
                           value="<?php echo esc_attr($download_limit ?: 5); ?>" min="0" />
 | 
			
		||||
                           value="<?php echo esc_attr($download_limit ?: 0); ?>" min="0" />
 | 
			
		||||
                    <span class="description">
 | 
			
		||||
                        <?php _e('Number of times a customer can download after purchase. 0 = unlimited', 'wp-digital-download'); ?>
 | 
			
		||||
                    </span>
 | 
			
		||||
 
 | 
			
		||||
@@ -180,8 +180,26 @@ class WPDD_Orders {
 | 
			
		||||
    private static function generate_download_link($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get the order to find the product
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get download limit from product settings
 | 
			
		||||
        $download_limit = get_post_meta($order->product_id, '_wpdd_download_limit', true);
 | 
			
		||||
        $download_limit = $download_limit ?: 0; // Default to 0 (unlimited) if not set
 | 
			
		||||
        
 | 
			
		||||
        // Get download expiry from product settings
 | 
			
		||||
        $download_expiry = get_post_meta($order->product_id, '_wpdd_download_expiry', true);
 | 
			
		||||
        $download_expiry = $download_expiry ?: 30; // Default to 30 days if not set
 | 
			
		||||
        
 | 
			
		||||
        $token = wp_hash(uniqid() . $order_id . time());
 | 
			
		||||
        $expires_at = date('Y-m-d H:i:s', strtotime('+7 days'));
 | 
			
		||||
        $expires_at = date('Y-m-d H:i:s', strtotime('+' . $download_expiry . ' days'));
 | 
			
		||||
        
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
@@ -189,7 +207,7 @@ class WPDD_Orders {
 | 
			
		||||
                'order_id' => $order_id,
 | 
			
		||||
                'token' => $token,
 | 
			
		||||
                'expires_at' => $expires_at,
 | 
			
		||||
                'max_downloads' => 5,
 | 
			
		||||
                'max_downloads' => $download_limit,
 | 
			
		||||
                'created_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%s', '%s', '%d', '%s')
 | 
			
		||||
 
 | 
			
		||||
@@ -116,6 +116,9 @@ class WPDD_PayPal_Payouts {
 | 
			
		||||
        
 | 
			
		||||
        $batch_id = 'WPDD_' . $payout->id . '_' . time();
 | 
			
		||||
        
 | 
			
		||||
        // Ensure currency is set, fallback to USD if empty
 | 
			
		||||
        $currency = !empty($payout->currency) ? $payout->currency : 'USD';
 | 
			
		||||
        
 | 
			
		||||
        $payout_data = array(
 | 
			
		||||
            'sender_batch_header' => array(
 | 
			
		||||
                'sender_batch_id' => $batch_id,
 | 
			
		||||
@@ -127,7 +130,7 @@ class WPDD_PayPal_Payouts {
 | 
			
		||||
                    'recipient_type' => 'EMAIL',
 | 
			
		||||
                    'amount' => array(
 | 
			
		||||
                        'value' => number_format($payout->amount, 2, '.', ''),
 | 
			
		||||
                        'currency' => $payout->currency
 | 
			
		||||
                        'currency' => $currency
 | 
			
		||||
                    ),
 | 
			
		||||
                    'receiver' => $payout->paypal_email,
 | 
			
		||||
                    'note' => 'Payout for your sales on ' . get_bloginfo('name'),
 | 
			
		||||
@@ -136,6 +139,9 @@ class WPDD_PayPal_Payouts {
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        // Log the payout data for debugging
 | 
			
		||||
        error_log('WPDD PayPal Payout Data: ' . json_encode($payout_data));
 | 
			
		||||
        
 | 
			
		||||
        $response = wp_remote_post(
 | 
			
		||||
            $base_url . '/v1/payments/payouts',
 | 
			
		||||
            array(
 | 
			
		||||
 
 | 
			
		||||
@@ -109,12 +109,23 @@ class WPDD_PayPal {
 | 
			
		||||
        $paypal_order = self::api_request('/v2/checkout/orders', $order_data, 'POST');
 | 
			
		||||
        
 | 
			
		||||
        if (isset($paypal_order['id'])) {
 | 
			
		||||
            $_SESSION['wpdd_paypal_order_' . $paypal_order['id']] = array(
 | 
			
		||||
                'product_id' => $product_id,
 | 
			
		||||
                'amount' => $final_price,
 | 
			
		||||
                'customer_email' => sanitize_email($_POST['customer_email'] ?? ''),
 | 
			
		||||
                'customer_name' => sanitize_text_field($_POST['customer_name'] ?? '')
 | 
			
		||||
            );
 | 
			
		||||
            // Ensure session is available before storing data
 | 
			
		||||
            if (WP_Digital_Download::ensure_session()) {
 | 
			
		||||
                $_SESSION['wpdd_paypal_order_' . $paypal_order['id']] = array(
 | 
			
		||||
                    'product_id' => $product_id,
 | 
			
		||||
                    'amount' => $final_price,
 | 
			
		||||
                    'customer_email' => sanitize_email($_POST['customer_email'] ?? ''),
 | 
			
		||||
                    'customer_name' => sanitize_text_field($_POST['customer_name'] ?? '')
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                // Fallback: use WordPress options table with short expiry
 | 
			
		||||
                set_transient('wpdd_paypal_order_' . $paypal_order['id'], array(
 | 
			
		||||
                    'product_id' => $product_id,
 | 
			
		||||
                    'amount' => $final_price,
 | 
			
		||||
                    'customer_email' => sanitize_email($_POST['customer_email'] ?? ''),
 | 
			
		||||
                    'customer_name' => sanitize_text_field($_POST['customer_name'] ?? '')
 | 
			
		||||
                ), 600); // 10 minutes
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            wp_send_json_success(array('orderID' => $paypal_order['id']));
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -134,7 +145,17 @@ class WPDD_PayPal {
 | 
			
		||||
        $capture_response = self::api_request('/v2/checkout/orders/' . $paypal_order_id . '/capture', array(), 'POST');
 | 
			
		||||
        
 | 
			
		||||
        if (isset($capture_response['status']) && $capture_response['status'] === 'COMPLETED') {
 | 
			
		||||
            $session_data = $_SESSION['wpdd_paypal_order_' . $paypal_order_id] ?? array();
 | 
			
		||||
            // Try to get data from session first, then fallback to transient
 | 
			
		||||
            $session_data = array();
 | 
			
		||||
            if (WP_Digital_Download::ensure_session() && isset($_SESSION['wpdd_paypal_order_' . $paypal_order_id])) {
 | 
			
		||||
                $session_data = $_SESSION['wpdd_paypal_order_' . $paypal_order_id];
 | 
			
		||||
            } else {
 | 
			
		||||
                // Try transient fallback
 | 
			
		||||
                $session_data = get_transient('wpdd_paypal_order_' . $paypal_order_id);
 | 
			
		||||
                if ($session_data === false) {
 | 
			
		||||
                    $session_data = array();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Add error logging for debugging session issues
 | 
			
		||||
            if (empty($session_data)) {
 | 
			
		||||
@@ -212,7 +233,11 @@ class WPDD_PayPal {
 | 
			
		||||
            update_post_meta($session_data['product_id'], '_wpdd_sales_count', 
 | 
			
		||||
                intval(get_post_meta($session_data['product_id'], '_wpdd_sales_count', true)) + 1);
 | 
			
		||||
            
 | 
			
		||||
            unset($_SESSION['wpdd_paypal_order_' . $paypal_order_id]);
 | 
			
		||||
            // Clean up stored data (both session and transient)
 | 
			
		||||
            if (WP_Digital_Download::ensure_session() && isset($_SESSION['wpdd_paypal_order_' . $paypal_order_id])) {
 | 
			
		||||
                unset($_SESSION['wpdd_paypal_order_' . $paypal_order_id]);
 | 
			
		||||
            }
 | 
			
		||||
            delete_transient('wpdd_paypal_order_' . $paypal_order_id);
 | 
			
		||||
            
 | 
			
		||||
            wp_send_json_success(array(
 | 
			
		||||
                'redirect_url' => add_query_arg(
 | 
			
		||||
@@ -267,8 +292,26 @@ class WPDD_PayPal {
 | 
			
		||||
    private static function generate_download_link($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get the order to find the product
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get download limit from product settings
 | 
			
		||||
        $download_limit = get_post_meta($order->product_id, '_wpdd_download_limit', true);
 | 
			
		||||
        $download_limit = $download_limit ?: 0; // Default to 0 (unlimited) if not set
 | 
			
		||||
        
 | 
			
		||||
        // Get download expiry from product settings
 | 
			
		||||
        $download_expiry = get_post_meta($order->product_id, '_wpdd_download_expiry', true);
 | 
			
		||||
        $download_expiry = $download_expiry ?: 30; // Default to 30 days if not set
 | 
			
		||||
        
 | 
			
		||||
        $token = wp_hash(uniqid() . $order_id . time());
 | 
			
		||||
        $expires_at = date('Y-m-d H:i:s', strtotime('+7 days'));
 | 
			
		||||
        $expires_at = date('Y-m-d H:i:s', strtotime('+' . $download_expiry . ' days'));
 | 
			
		||||
        
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
@@ -276,7 +319,7 @@ class WPDD_PayPal {
 | 
			
		||||
                'order_id' => $order_id,
 | 
			
		||||
                'token' => $token,
 | 
			
		||||
                'expires_at' => $expires_at,
 | 
			
		||||
                'max_downloads' => 5,
 | 
			
		||||
                'max_downloads' => $download_limit,
 | 
			
		||||
                'created_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%s', '%s', '%d', '%s')
 | 
			
		||||
 
 | 
			
		||||
@@ -356,9 +356,11 @@ class WPDD_Shortcodes {
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $orders = $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
                "SELECT o.*, p.post_title as product_name 
 | 
			
		||||
                "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",
 | 
			
		||||
@@ -370,9 +372,11 @@ class WPDD_Shortcodes {
 | 
			
		||||
            
 | 
			
		||||
            // 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 
 | 
			
		||||
                "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",
 | 
			
		||||
@@ -409,6 +413,7 @@ class WPDD_Shortcodes {
 | 
			
		||||
                        <?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;
 | 
			
		||||
                            
 | 
			
		||||
@@ -417,7 +422,9 @@ class WPDD_Shortcodes {
 | 
			
		||||
                                $is_expired = current_time('mysql') > $expiry_date;
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            $can_download = !$is_expired && ($download_limit == 0 || $order->download_count < $download_limit);
 | 
			
		||||
                            // 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>
 | 
			
		||||
@@ -431,9 +438,9 @@ class WPDD_Shortcodes {
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php 
 | 
			
		||||
                                    if ($download_limit > 0) {
 | 
			
		||||
                                        echo sprintf('%d / %d', $order->download_count, $download_limit);
 | 
			
		||||
                                        echo sprintf('%d / %d', $current_downloads, $download_limit);
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        echo $order->download_count;
 | 
			
		||||
                                        echo sprintf('%d / %s', $current_downloads, __('unlimited', 'wp-digital-download'));
 | 
			
		||||
                                    }
 | 
			
		||||
                                    ?>
 | 
			
		||||
                                </td>
 | 
			
		||||
@@ -470,6 +477,19 @@ class WPDD_Shortcodes {
 | 
			
		||||
                                    <?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>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
 * 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: 1.0.0
 | 
			
		||||
 * Version: {auto_update_value_on_deploy}
 | 
			
		||||
 * Author: Josh Knapp
 | 
			
		||||
 * License: GPL v2 or later
 | 
			
		||||
 * Text Domain: wp-digital-download
 | 
			
		||||
@@ -14,7 +14,7 @@ if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
define('WPDD_VERSION', '1.0.2');
 | 
			
		||||
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__));
 | 
			
		||||
@@ -54,7 +54,9 @@ final class WP_Digital_Download {
 | 
			
		||||
            'includes/class-wpdd-watermark.php',
 | 
			
		||||
            'includes/class-wpdd-ajax.php',
 | 
			
		||||
            'includes/class-wpdd-creator.php',
 | 
			
		||||
            'includes/class-wpdd-paypal-payouts.php'
 | 
			
		||||
            'includes/class-wpdd-earnings-processor.php',
 | 
			
		||||
            'includes/class-wpdd-paypal-payouts.php',
 | 
			
		||||
            'includes/class-wpdd-email.php'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        foreach ($files as $file) {
 | 
			
		||||
@@ -70,7 +72,8 @@ final class WP_Digital_Download {
 | 
			
		||||
            $admin_files = array(
 | 
			
		||||
                'admin/class-wpdd-admin.php',
 | 
			
		||||
                'admin/class-wpdd-settings.php',
 | 
			
		||||
                'admin/class-wpdd-admin-payouts.php'
 | 
			
		||||
                'admin/class-wpdd-admin-payouts.php',
 | 
			
		||||
                'admin/class-wpdd-order-manager.php'
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            foreach ($admin_files as $file) {
 | 
			
		||||
@@ -88,8 +91,7 @@ final class WP_Digital_Download {
 | 
			
		||||
        register_activation_hook(__FILE__, array('WPDD_Install', 'activate'));
 | 
			
		||||
        register_deactivation_hook(__FILE__, array('WPDD_Install', 'deactivate'));
 | 
			
		||||
        
 | 
			
		||||
        // Initialize sessions for PayPal integration (must be early priority)
 | 
			
		||||
        add_action('init', array($this, 'start_session'), 1);
 | 
			
		||||
        // 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'));
 | 
			
		||||
        
 | 
			
		||||
@@ -183,6 +185,18 @@ final class WP_Digital_Download {
 | 
			
		||||
            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();
 | 
			
		||||
@@ -201,17 +215,53 @@ final class WP_Digital_Download {
 | 
			
		||||
            } 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();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize PHP sessions for PayPal order data persistence
 | 
			
		||||
     * Called early in WordPress init to ensure sessions work properly
 | 
			
		||||
     * Safely start session if needed
 | 
			
		||||
     * Returns true if session is available, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public function start_session() {
 | 
			
		||||
        if (!session_id()) {
 | 
			
		||||
            session_start();
 | 
			
		||||
    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() {
 | 
			
		||||
@@ -231,6 +281,14 @@ final class WP_Digital_Download {
 | 
			
		||||
            $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',
 | 
			
		||||
@@ -248,8 +306,9 @@ final class WP_Digital_Download {
 | 
			
		||||
    public function admin_enqueue_scripts($hook) {
 | 
			
		||||
        global $post;
 | 
			
		||||
        
 | 
			
		||||
        if (($hook == 'post.php' || $hook == 'post-new.php') && 
 | 
			
		||||
            isset($post) && $post->post_type == 'wpdd_product') {
 | 
			
		||||
        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') 
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user