First Commit
This commit is contained in:
		
							
								
								
									
										219
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,219 @@
 | 
			
		||||
# WP Digital Download
 | 
			
		||||
 | 
			
		||||
A comprehensive WordPress plugin for creating a digital download marketplace where creators can sell digital products with PayPal integration.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
### Core Functionality
 | 
			
		||||
- **Custom Post Type**: Products with rich metadata
 | 
			
		||||
- **User Roles**: Separate roles for customers and creators
 | 
			
		||||
- **File Management**: Secure file upload and protection
 | 
			
		||||
- **PayPal Integration**: Complete payment processing
 | 
			
		||||
- **Download Protection**: Secure, time-limited downloads
 | 
			
		||||
- **Watermarking**: Automatic watermarking for images and PDFs
 | 
			
		||||
- **Purchase History**: Customer account management
 | 
			
		||||
- **Admin Dashboard**: Complete order and sales management
 | 
			
		||||
 | 
			
		||||
### Key Components
 | 
			
		||||
 | 
			
		||||
#### Custom Post Types
 | 
			
		||||
- **wpdd_product**: Digital products with pricing, files, and settings
 | 
			
		||||
- **Product Categories & Tags**: Organization and filtering
 | 
			
		||||
- **Creator Attribution**: Products linked to their creators
 | 
			
		||||
 | 
			
		||||
#### User Management
 | 
			
		||||
- **Digital Customer Role**: Purchase and download permissions
 | 
			
		||||
- **Digital Creator Role**: Product management permissions
 | 
			
		||||
- **Admin Capabilities**: Full system management
 | 
			
		||||
 | 
			
		||||
#### Payment Processing
 | 
			
		||||
- **PayPal Integration**: Sandbox and live modes
 | 
			
		||||
- **Free Products**: No-payment downloads
 | 
			
		||||
- **Guest Checkout**: Optional account creation
 | 
			
		||||
- **Order Management**: Complete transaction tracking
 | 
			
		||||
 | 
			
		||||
#### File Protection
 | 
			
		||||
- **Protected Directory**: Files stored outside web root access
 | 
			
		||||
- **Secure Downloads**: Token-based download links
 | 
			
		||||
- **Download Limits**: Configurable per product
 | 
			
		||||
- **Expiry Dates**: Time-limited access
 | 
			
		||||
 | 
			
		||||
#### Watermarking
 | 
			
		||||
- **Image Watermarking**: PNG, JPG, GIF support
 | 
			
		||||
- **PDF Watermarking**: Text overlay on PDF files
 | 
			
		||||
- **Dynamic Content**: Customer info in watermarks
 | 
			
		||||
- **Configurable Settings**: Per-product control
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
1. Upload the plugin files to `/wp-content/plugins/wp-digital-download/`
 | 
			
		||||
2. Activate the plugin through the WordPress admin
 | 
			
		||||
3. Configure PayPal settings in Digital Products > Settings
 | 
			
		||||
4. Create your first product
 | 
			
		||||
5. Add the shop shortcode `[wpdd_shop]` to a page
 | 
			
		||||
 | 
			
		||||
## Configuration
 | 
			
		||||
 | 
			
		||||
### PayPal Setup
 | 
			
		||||
1. Create a PayPal Developer account
 | 
			
		||||
2. Create a new application
 | 
			
		||||
3. Get Client ID and Secret
 | 
			
		||||
4. Configure in Settings > PayPal Settings
 | 
			
		||||
 | 
			
		||||
### Pages Setup
 | 
			
		||||
The plugin automatically creates these pages:
 | 
			
		||||
- **Shop**: `[wpdd_shop]` - Product listing
 | 
			
		||||
- **Checkout**: `[wpdd_checkout]` - Payment processing
 | 
			
		||||
- **My Purchases**: `[wpdd_customer_purchases]` - Customer downloads
 | 
			
		||||
- **Thank You**: `[wpdd_thank_you]` - Order confirmation
 | 
			
		||||
 | 
			
		||||
## Shortcodes
 | 
			
		||||
 | 
			
		||||
### Main Shortcodes
 | 
			
		||||
- `[wpdd_shop]` - Display product storefront
 | 
			
		||||
- `[wpdd_customer_purchases]` - Customer purchase history
 | 
			
		||||
- `[wpdd_checkout]` - Checkout form
 | 
			
		||||
- `[wpdd_thank_you]` - Thank you page
 | 
			
		||||
- `[wpdd_product id="123"]` - Single product display
 | 
			
		||||
 | 
			
		||||
### Shop Shortcode Options
 | 
			
		||||
```
 | 
			
		||||
[wpdd_shop columns="3" per_page="12" category="design" orderby="date" order="DESC" show_filters="yes"]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Database Structure
 | 
			
		||||
 | 
			
		||||
### Tables Created
 | 
			
		||||
- `wp_wpdd_orders` - Purchase records
 | 
			
		||||
- `wp_wpdd_downloads` - Download tracking
 | 
			
		||||
- `wp_wpdd_download_links` - Secure download tokens
 | 
			
		||||
 | 
			
		||||
### Key Meta Fields
 | 
			
		||||
- `_wpdd_price` - Product price
 | 
			
		||||
- `_wpdd_sale_price` - Sale price
 | 
			
		||||
- `_wpdd_is_free` - Free product flag
 | 
			
		||||
- `_wpdd_files` - Associated files
 | 
			
		||||
- `_wpdd_download_limit` - Download restrictions
 | 
			
		||||
- `_wpdd_download_expiry` - Access duration
 | 
			
		||||
- `_wpdd_enable_watermark` - Watermark settings
 | 
			
		||||
- `_wpdd_sales_count` - Sales tracking
 | 
			
		||||
 | 
			
		||||
## File Structure
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
wp-digital-download/
 | 
			
		||||
├── wp-digital-download.php          # Main plugin file
 | 
			
		||||
├── includes/                        # Core functionality
 | 
			
		||||
│   ├── class-wpdd-install.php      # Installation routines
 | 
			
		||||
│   ├── class-wpdd-post-types.php   # Custom post types
 | 
			
		||||
│   ├── class-wpdd-roles.php        # User roles
 | 
			
		||||
│   ├── class-wpdd-metaboxes.php    # Product editing
 | 
			
		||||
│   ├── class-wpdd-shortcodes.php   # Frontend display
 | 
			
		||||
│   ├── class-wpdd-paypal.php       # Payment processing
 | 
			
		||||
│   ├── class-wpdd-download-handler.php # File delivery
 | 
			
		||||
│   ├── class-wpdd-customer.php     # Customer management
 | 
			
		||||
│   ├── class-wpdd-orders.php       # Order processing
 | 
			
		||||
│   ├── class-wpdd-file-protection.php # Security
 | 
			
		||||
│   ├── class-wpdd-watermark.php    # Image processing
 | 
			
		||||
│   └── class-wpdd-ajax.php         # AJAX handlers
 | 
			
		||||
├── admin/                          # Admin interface
 | 
			
		||||
│   ├── class-wpdd-admin.php       # Admin pages
 | 
			
		||||
│   └── class-wpdd-settings.php    # Configuration
 | 
			
		||||
└── assets/                         # Frontend resources
 | 
			
		||||
    ├── css/
 | 
			
		||||
    │   ├── frontend.css           # Public styling
 | 
			
		||||
    │   └── admin.css              # Admin styling
 | 
			
		||||
    └── js/
 | 
			
		||||
        ├── frontend.js            # Public scripts
 | 
			
		||||
        ├── admin.js               # Admin scripts
 | 
			
		||||
        └── paypal.js              # Payment integration
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Hooks & Filters
 | 
			
		||||
 | 
			
		||||
### Actions
 | 
			
		||||
- `wpdd_order_completed` - Fired when order is completed
 | 
			
		||||
- `wpdd_download_started` - Before file delivery
 | 
			
		||||
- `wpdd_customer_registered` - New customer account
 | 
			
		||||
 | 
			
		||||
### Filters
 | 
			
		||||
- `wpdd_product_price` - Modify displayed price
 | 
			
		||||
- `wpdd_download_url` - Customize download URLs
 | 
			
		||||
- `wpdd_watermark_text` - Modify watermark content
 | 
			
		||||
- `wpdd_email_content` - Customize email templates
 | 
			
		||||
 | 
			
		||||
## Security Features
 | 
			
		||||
 | 
			
		||||
### File Protection
 | 
			
		||||
- Files stored in protected directory with .htaccess rules
 | 
			
		||||
- Token-based download authentication
 | 
			
		||||
- Time-limited access links
 | 
			
		||||
- IP and user agent logging
 | 
			
		||||
 | 
			
		||||
### Input Validation
 | 
			
		||||
- All user inputs sanitized
 | 
			
		||||
- Nonce verification on forms
 | 
			
		||||
- Capability checks for admin functions
 | 
			
		||||
- SQL injection prevention
 | 
			
		||||
 | 
			
		||||
### Payment Security
 | 
			
		||||
- PayPal webhook verification
 | 
			
		||||
- Transaction ID tracking
 | 
			
		||||
- Duplicate payment prevention
 | 
			
		||||
- Secure credential storage
 | 
			
		||||
 | 
			
		||||
## Customization
 | 
			
		||||
 | 
			
		||||
### Template Override
 | 
			
		||||
Create templates in your theme:
 | 
			
		||||
```
 | 
			
		||||
your-theme/
 | 
			
		||||
└── wpdd-templates/
 | 
			
		||||
    ├── single-product.php
 | 
			
		||||
    ├── shop.php
 | 
			
		||||
    └── checkout.php
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### CSS Customization
 | 
			
		||||
Override default styles:
 | 
			
		||||
```css
 | 
			
		||||
.wpdd-product-card {
 | 
			
		||||
    /* Your custom styles */
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Adding Payment Methods
 | 
			
		||||
Extend payment options by hooking into:
 | 
			
		||||
```php
 | 
			
		||||
add_action('wpdd_payment_methods', 'add_stripe_payment');
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 | 
			
		||||
- WordPress 5.0+
 | 
			
		||||
- PHP 7.4+
 | 
			
		||||
- MySQL 5.6+
 | 
			
		||||
- cURL extension
 | 
			
		||||
- GD library (for watermarking)
 | 
			
		||||
 | 
			
		||||
## Changelog
 | 
			
		||||
 | 
			
		||||
### Version 1.0.0
 | 
			
		||||
- Initial release
 | 
			
		||||
- PayPal integration
 | 
			
		||||
- File protection system
 | 
			
		||||
- Watermarking capabilities
 | 
			
		||||
- Customer management
 | 
			
		||||
- Admin dashboard
 | 
			
		||||
 | 
			
		||||
## Support
 | 
			
		||||
 | 
			
		||||
For support and feature requests, please create an issue in the GitHub repository.
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
This plugin is licensed under the GPL v2 or later.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
**Note**: This plugin handles financial transactions. Always test thoroughly in a development environment before deploying to production.
 | 
			
		||||
							
								
								
									
										435
									
								
								admin/class-wpdd-admin-payouts.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										435
									
								
								admin/class-wpdd-admin-payouts.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,435 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Admin_Payouts {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('admin_menu', array(__CLASS__, 'add_menu_page'));
 | 
			
		||||
        add_action('admin_post_wpdd_process_payout', array(__CLASS__, 'process_payout'));
 | 
			
		||||
        add_action('admin_post_wpdd_bulk_payouts', array(__CLASS__, 'process_bulk_payouts'));
 | 
			
		||||
        add_action('admin_enqueue_scripts', array(__CLASS__, 'enqueue_scripts'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_menu_page() {
 | 
			
		||||
        add_submenu_page(
 | 
			
		||||
            'edit.php?post_type=wpdd_product',
 | 
			
		||||
            __('Creator Payouts', 'wp-digital-download'),
 | 
			
		||||
            __('Payouts', 'wp-digital-download'),
 | 
			
		||||
            'manage_options',
 | 
			
		||||
            'wpdd-payouts',
 | 
			
		||||
            array(__CLASS__, 'render_page')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function enqueue_scripts($hook) {
 | 
			
		||||
        if ($hook !== 'wpdd_product_page_wpdd-payouts') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_enqueue_script('wpdd-admin-payouts', WPDD_PLUGIN_URL . 'assets/js/admin-payouts.js', array('jquery'), WPDD_VERSION, true);
 | 
			
		||||
        wp_localize_script('wpdd-admin-payouts', 'wpdd_payouts', array(
 | 
			
		||||
            'ajax_url' => admin_url('admin-ajax.php'),
 | 
			
		||||
            'nonce' => wp_create_nonce('wpdd_payouts'),
 | 
			
		||||
            'confirm_payout' => __('Are you sure you want to process this payout?', 'wp-digital-download'),
 | 
			
		||||
            'confirm_bulk' => __('Are you sure you want to process all selected payouts?', 'wp-digital-download')
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_page() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get filter parameters
 | 
			
		||||
        $status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : 'pending';
 | 
			
		||||
        $creator_filter = isset($_GET['creator']) ? intval($_GET['creator']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        // Get creators with balance
 | 
			
		||||
        $creators = WPDD_Creator::get_creators_with_balance();
 | 
			
		||||
        $currency = get_option('wpdd_currency', 'USD');
 | 
			
		||||
        $threshold = floatval(get_option('wpdd_payout_threshold', 0));
 | 
			
		||||
        
 | 
			
		||||
        // Get payout history
 | 
			
		||||
        $query = "SELECT p.*, u.display_name, u.user_email 
 | 
			
		||||
                  FROM {$wpdb->prefix}wpdd_payouts p
 | 
			
		||||
                  INNER JOIN {$wpdb->users} u ON p.creator_id = u.ID
 | 
			
		||||
                  WHERE 1=1";
 | 
			
		||||
        
 | 
			
		||||
        if ($status_filter && $status_filter !== 'all') {
 | 
			
		||||
            $query .= $wpdb->prepare(" AND p.status = %s", $status_filter);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($creator_filter) {
 | 
			
		||||
            $query .= $wpdb->prepare(" AND p.creator_id = %d", $creator_filter);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $query .= " ORDER BY p.created_at DESC LIMIT 100";
 | 
			
		||||
        
 | 
			
		||||
        $payouts = $wpdb->get_results($query);
 | 
			
		||||
        
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wrap">
 | 
			
		||||
            <h1><?php _e('Creator Payouts', 'wp-digital-download'); ?></h1>
 | 
			
		||||
            
 | 
			
		||||
            <?php if (isset($_GET['message'])) : ?>
 | 
			
		||||
                <?php if ($_GET['message'] === 'success') : ?>
 | 
			
		||||
                    <div class="notice notice-success is-dismissible">
 | 
			
		||||
                        <p><?php _e('Payout processed successfully.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    </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>
 | 
			
		||||
                    </div>
 | 
			
		||||
                <?php endif; ?>
 | 
			
		||||
            <?php endif; ?>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-payout-stats">
 | 
			
		||||
                <h2><?php _e('Pending Payouts', 'wp-digital-download'); ?></h2>
 | 
			
		||||
                
 | 
			
		||||
                <?php if (!empty($creators)) : ?>
 | 
			
		||||
                <form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
 | 
			
		||||
                    <input type="hidden" name="action" value="wpdd_bulk_payouts">
 | 
			
		||||
                    <?php wp_nonce_field('wpdd_bulk_payouts', 'wpdd_nonce'); ?>
 | 
			
		||||
                    
 | 
			
		||||
                    <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th class="check-column">
 | 
			
		||||
                                    <input type="checkbox" id="select-all-creators">
 | 
			
		||||
                                </th>
 | 
			
		||||
                                <th><?php _e('Creator', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('PayPal Email', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Current Balance', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Total Sales', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Net Earnings', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Actions', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </thead>
 | 
			
		||||
                        <tbody>
 | 
			
		||||
                            <?php foreach ($creators as $creator) : 
 | 
			
		||||
                                $total_earnings = WPDD_Creator::get_creator_total_earnings($creator->ID);
 | 
			
		||||
                                $net_earnings = WPDD_Creator::get_creator_net_earnings($creator->ID);
 | 
			
		||||
                                $can_payout = !empty($creator->paypal_email) && floatval($creator->balance) > 0;
 | 
			
		||||
                                $auto_eligible = $threshold > 0 && floatval($creator->balance) >= $threshold;
 | 
			
		||||
                            ?>
 | 
			
		||||
                            <tr <?php echo $auto_eligible ? 'style="background-color: #d4edda;"' : ''; ?>>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php if ($can_payout) : ?>
 | 
			
		||||
                                    <input type="checkbox" name="creator_ids[]" value="<?php echo esc_attr($creator->ID); ?>">
 | 
			
		||||
                                    <?php endif; ?>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <strong><?php echo esc_html($creator->display_name); ?></strong><br>
 | 
			
		||||
                                    <small><?php echo esc_html($creator->user_email); ?></small>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php if (!empty($creator->paypal_email)) : ?>
 | 
			
		||||
                                        <?php echo esc_html($creator->paypal_email); ?>
 | 
			
		||||
                                    <?php else : ?>
 | 
			
		||||
                                        <span style="color: #dc3545;"><?php _e('Not set', 'wp-digital-download'); ?></span>
 | 
			
		||||
                                    <?php endif; ?>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <strong><?php echo wpdd_format_price($creator->balance, $currency); ?></strong>
 | 
			
		||||
                                    <?php if ($auto_eligible) : ?>
 | 
			
		||||
                                        <br><span class="dashicons dashicons-yes" style="color: #28a745;"></span>
 | 
			
		||||
                                        <small><?php _e('Auto-payout eligible', 'wp-digital-download'); ?></small>
 | 
			
		||||
                                    <?php endif; ?>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td><?php echo wpdd_format_price($total_earnings, $currency); ?></td>
 | 
			
		||||
                                <td><?php echo wpdd_format_price($net_earnings, $currency); ?></td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php if ($can_payout) : ?>
 | 
			
		||||
                                    <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display: inline;">
 | 
			
		||||
                                        <input type="hidden" name="action" value="wpdd_process_payout">
 | 
			
		||||
                                        <input type="hidden" name="creator_id" value="<?php echo esc_attr($creator->ID); ?>">
 | 
			
		||||
                                        <?php wp_nonce_field('wpdd_process_payout_' . $creator->ID, 'wpdd_nonce'); ?>
 | 
			
		||||
                                        <button type="submit" class="button button-primary wpdd-payout-btn">
 | 
			
		||||
                                            <?php _e('Process Payout', 'wp-digital-download'); ?>
 | 
			
		||||
                                        </button>
 | 
			
		||||
                                    </form>
 | 
			
		||||
                                    <?php else : ?>
 | 
			
		||||
                                        <button class="button" disabled><?php _e('Cannot Process', 'wp-digital-download'); ?></button>
 | 
			
		||||
                                    <?php endif; ?>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                            <?php endforeach; ?>
 | 
			
		||||
                        </tbody>
 | 
			
		||||
                    </table>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="tablenav bottom">
 | 
			
		||||
                        <div class="alignleft actions">
 | 
			
		||||
                            <button type="submit" class="button button-primary" name="bulk_action" value="process">
 | 
			
		||||
                                <?php _e('Process Selected Payouts', 'wp-digital-download'); ?>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            <?php if ($threshold > 0) : ?>
 | 
			
		||||
                            <button type="submit" class="button" name="bulk_action" value="auto">
 | 
			
		||||
                                <?php printf(__('Process All Above %s', 'wp-digital-download'), wpdd_format_price($threshold, $currency)); ?>
 | 
			
		||||
                            </button>
 | 
			
		||||
                            <?php endif; ?>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
                <?php else : ?>
 | 
			
		||||
                    <p><?php _e('No creators with pending payouts.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                <?php endif; ?>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-payout-history">
 | 
			
		||||
                <h2><?php _e('Payout History', 'wp-digital-download'); ?></h2>
 | 
			
		||||
                
 | 
			
		||||
                <div class="tablenav top">
 | 
			
		||||
                    <div class="alignleft actions">
 | 
			
		||||
                        <select name="status_filter" onchange="window.location.href='<?php echo admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts'); ?>&status=' + this.value">
 | 
			
		||||
                            <option value="all" <?php selected($status_filter, 'all'); ?>><?php _e('All Statuses', 'wp-digital-download'); ?></option>
 | 
			
		||||
                            <option value="pending" <?php selected($status_filter, 'pending'); ?>><?php _e('Pending', 'wp-digital-download'); ?></option>
 | 
			
		||||
                            <option value="completed" <?php selected($status_filter, 'completed'); ?>><?php _e('Completed', 'wp-digital-download'); ?></option>
 | 
			
		||||
                            <option value="failed" <?php selected($status_filter, 'failed'); ?>><?php _e('Failed', 'wp-digital-download'); ?></option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <?php if (!empty($payouts)) : ?>
 | 
			
		||||
                <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th><?php _e('Date', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Creator', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Amount', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('PayPal Email', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Transaction ID', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Status', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Method', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Processed By', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                        <?php foreach ($payouts as $payout) : 
 | 
			
		||||
                            $processor = $payout->processed_by ? get_userdata($payout->processed_by) : null;
 | 
			
		||||
                        ?>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($payout->created_at))); ?>
 | 
			
		||||
                                <?php if ($payout->processed_at) : ?>
 | 
			
		||||
                                    <br><small><?php _e('Processed:', 'wp-digital-download'); ?> <?php echo esc_html(date_i18n(get_option('date_format'), strtotime($payout->processed_at))); ?></small>
 | 
			
		||||
                                <?php endif; ?>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <strong><?php echo esc_html($payout->display_name); ?></strong><br>
 | 
			
		||||
                                <small><?php echo esc_html($payout->user_email); ?></small>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td><strong><?php echo wpdd_format_price($payout->amount, $payout->currency); ?></strong></td>
 | 
			
		||||
                            <td><?php echo esc_html($payout->paypal_email); ?></td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php echo esc_html($payout->transaction_id ?: '-'); ?>
 | 
			
		||||
                                <?php if ($payout->notes) : ?>
 | 
			
		||||
                                    <br><small><?php echo esc_html($payout->notes); ?></small>
 | 
			
		||||
                                <?php endif; ?>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php 
 | 
			
		||||
                                $status_class = '';
 | 
			
		||||
                                switch($payout->status) {
 | 
			
		||||
                                    case 'completed':
 | 
			
		||||
                                        $status_class = 'notice-success';
 | 
			
		||||
                                        break;
 | 
			
		||||
                                    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($payout->status)); ?>
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </td>
 | 
			
		||||
                            <td><?php echo esc_html(ucfirst($payout->payout_method)); ?></td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php if ($processor) : ?>
 | 
			
		||||
                                    <?php echo esc_html($processor->display_name); ?>
 | 
			
		||||
                                <?php else : ?>
 | 
			
		||||
                                    <?php echo $payout->payout_method === 'automatic' ? __('System', 'wp-digital-download') : '-'; ?>
 | 
			
		||||
                                <?php endif; ?>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <?php endforeach; ?>
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
                <?php else : ?>
 | 
			
		||||
                    <p><?php _e('No payout history found.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                <?php endif; ?>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <style>
 | 
			
		||||
                .wpdd-payout-stats,
 | 
			
		||||
                .wpdd-payout-history {
 | 
			
		||||
                    margin-top: 30px;
 | 
			
		||||
                    background: #fff;
 | 
			
		||||
                    padding: 20px;
 | 
			
		||||
                    border: 1px solid #ccd0d4;
 | 
			
		||||
                    box-shadow: 0 1px 1px rgba(0,0,0,.04);
 | 
			
		||||
                }
 | 
			
		||||
                .wpdd-payout-btn:hover {
 | 
			
		||||
                    cursor: pointer;
 | 
			
		||||
                }
 | 
			
		||||
                #select-all-creators {
 | 
			
		||||
                    margin: 0;
 | 
			
		||||
                }
 | 
			
		||||
            </style>
 | 
			
		||||
            
 | 
			
		||||
            <script>
 | 
			
		||||
                jQuery(document).ready(function($) {
 | 
			
		||||
                    $('#select-all-creators').on('change', function() {
 | 
			
		||||
                        $('input[name="creator_ids[]"]').prop('checked', this.checked);
 | 
			
		||||
                    });
 | 
			
		||||
                    
 | 
			
		||||
                    $('.wpdd-payout-btn').on('click', function(e) {
 | 
			
		||||
                        if (!confirm(wpdd_payouts.confirm_payout)) {
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    
 | 
			
		||||
                    $('button[name="bulk_action"]').on('click', function(e) {
 | 
			
		||||
                        var checkedCount = $('input[name="creator_ids[]"]:checked').length;
 | 
			
		||||
                        if (checkedCount === 0) {
 | 
			
		||||
                            alert('Please select at least one creator for payout.');
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                        } else if (!confirm(wpdd_payouts.confirm_bulk)) {
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            </script>
 | 
			
		||||
        </div>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function process_payout() {
 | 
			
		||||
        if (!current_user_can('manage_options')) {
 | 
			
		||||
            wp_die(__('You do not have permission to perform this action.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $creator_id = isset($_POST['creator_id']) ? intval($_POST['creator_id']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        if (!$creator_id || !wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_process_payout_' . $creator_id)) {
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=error'));
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = self::create_payout($creator_id);
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            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'));
 | 
			
		||||
        }
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function process_bulk_payouts() {
 | 
			
		||||
        if (!current_user_can('manage_options')) {
 | 
			
		||||
            wp_die(__('You do not have permission to perform this action.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!wp_verify_nonce($_POST['wpdd_nonce'], 'wpdd_bulk_payouts')) {
 | 
			
		||||
            wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=error'));
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $bulk_action = isset($_POST['bulk_action']) ? sanitize_text_field($_POST['bulk_action']) : '';
 | 
			
		||||
        
 | 
			
		||||
        if ($bulk_action === 'auto') {
 | 
			
		||||
            // Process all creators above threshold
 | 
			
		||||
            $threshold = floatval(get_option('wpdd_payout_threshold', 0));
 | 
			
		||||
            if ($threshold > 0) {
 | 
			
		||||
                $creators = WPDD_Creator::get_creators_with_balance();
 | 
			
		||||
                foreach ($creators as $creator) {
 | 
			
		||||
                    if (floatval($creator->balance) >= $threshold && !empty($creator->paypal_email)) {
 | 
			
		||||
                        self::create_payout($creator->ID);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Process selected creators
 | 
			
		||||
            $creator_ids = isset($_POST['creator_ids']) ? array_map('intval', $_POST['creator_ids']) : array();
 | 
			
		||||
            foreach ($creator_ids as $creator_id) {
 | 
			
		||||
                self::create_payout($creator_id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_redirect(admin_url('edit.php?post_type=wpdd_product&page=wpdd-payouts&message=success'));
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function create_payout($creator_id, $method = 'manual') {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $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;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $currency = get_option('wpdd_currency', 'USD');
 | 
			
		||||
        $current_user_id = get_current_user_id();
 | 
			
		||||
        
 | 
			
		||||
        // Create payout record
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_payouts',
 | 
			
		||||
            array(
 | 
			
		||||
                'creator_id' => $creator_id,
 | 
			
		||||
                'amount' => $balance,
 | 
			
		||||
                'currency' => $currency,
 | 
			
		||||
                'paypal_email' => $paypal_email,
 | 
			
		||||
                'status' => 'pending',
 | 
			
		||||
                'payout_method' => $method,
 | 
			
		||||
                'processed_by' => $current_user_id,
 | 
			
		||||
                'created_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%f', '%s', '%s', '%s', '%s', '%d', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $payout_id = $wpdb->insert_id;
 | 
			
		||||
        
 | 
			
		||||
        // Try to process via PayPal API
 | 
			
		||||
        $result = WPDD_PayPal_Payouts::process_payout($payout_id);
 | 
			
		||||
        
 | 
			
		||||
        if ($result['success']) {
 | 
			
		||||
            // Update payout status
 | 
			
		||||
            $wpdb->update(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_payouts',
 | 
			
		||||
                array(
 | 
			
		||||
                    'status' => 'completed',
 | 
			
		||||
                    'transaction_id' => $result['transaction_id'],
 | 
			
		||||
                    'processed_at' => current_time('mysql')
 | 
			
		||||
                ),
 | 
			
		||||
                array('id' => $payout_id),
 | 
			
		||||
                array('%s', '%s', '%s'),
 | 
			
		||||
                array('%d')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            // Reset creator balance
 | 
			
		||||
            update_user_meta($creator_id, 'wpdd_creator_balance', 0);
 | 
			
		||||
            
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            // Update with error
 | 
			
		||||
            $wpdb->update(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_payouts',
 | 
			
		||||
                array(
 | 
			
		||||
                    'status' => 'failed',
 | 
			
		||||
                    'notes' => $result['error']
 | 
			
		||||
                ),
 | 
			
		||||
                array('id' => $payout_id),
 | 
			
		||||
                array('%s', '%s'),
 | 
			
		||||
                array('%d')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										920
									
								
								admin/class-wpdd-admin.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										920
									
								
								admin/class-wpdd-admin.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,920 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Admin {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('admin_menu', array(__CLASS__, 'add_admin_menus'));
 | 
			
		||||
        add_filter('manage_wpdd_product_posts_columns', array(__CLASS__, 'add_product_columns'));
 | 
			
		||||
        add_action('manage_wpdd_product_posts_custom_column', array(__CLASS__, 'render_product_columns'), 10, 2);
 | 
			
		||||
        add_filter('manage_edit-wpdd_product_sortable_columns', array(__CLASS__, 'make_columns_sortable'));
 | 
			
		||||
        add_action('pre_get_posts', array(__CLASS__, 'sort_products_by_column'));
 | 
			
		||||
        add_action('admin_init', array(__CLASS__, 'handle_admin_actions'));
 | 
			
		||||
        
 | 
			
		||||
        // Initialize admin payouts
 | 
			
		||||
        if (class_exists('WPDD_Admin_Payouts')) {
 | 
			
		||||
            WPDD_Admin_Payouts::init();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_admin_menus() {
 | 
			
		||||
        add_submenu_page(
 | 
			
		||||
            'edit.php?post_type=wpdd_product',
 | 
			
		||||
            __('Orders', 'wp-digital-download'),
 | 
			
		||||
            __('Orders', 'wp-digital-download'),
 | 
			
		||||
            'wpdd_manage_orders',
 | 
			
		||||
            'wpdd-orders',
 | 
			
		||||
            array(__CLASS__, 'render_orders_page')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_submenu_page(
 | 
			
		||||
            'edit.php?post_type=wpdd_product',
 | 
			
		||||
            __('Reports', 'wp-digital-download'),
 | 
			
		||||
            __('Reports', 'wp-digital-download'),
 | 
			
		||||
            'wpdd_view_reports',
 | 
			
		||||
            'wpdd-reports',
 | 
			
		||||
            array(__CLASS__, 'render_reports_page')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_submenu_page(
 | 
			
		||||
            'edit.php?post_type=wpdd_product',
 | 
			
		||||
            __('Customers', 'wp-digital-download'),
 | 
			
		||||
            __('Customers', 'wp-digital-download'),
 | 
			
		||||
            'wpdd_manage_orders',
 | 
			
		||||
            'wpdd-customers',
 | 
			
		||||
            array(__CLASS__, 'render_customers_page')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_submenu_page(
 | 
			
		||||
            'edit.php?post_type=wpdd_product',
 | 
			
		||||
            __('Shortcodes', 'wp-digital-download'),
 | 
			
		||||
            __('Shortcodes', 'wp-digital-download'),
 | 
			
		||||
            'edit_wpdd_products',
 | 
			
		||||
            'wpdd-shortcodes',
 | 
			
		||||
            array(__CLASS__, 'render_shortcodes_page')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_product_columns($columns) {
 | 
			
		||||
        $new_columns = array();
 | 
			
		||||
        
 | 
			
		||||
        foreach ($columns as $key => $value) {
 | 
			
		||||
            $new_columns[$key] = $value;
 | 
			
		||||
            
 | 
			
		||||
            if ($key === 'title') {
 | 
			
		||||
                $new_columns['wpdd_price'] = __('Price', 'wp-digital-download');
 | 
			
		||||
                $new_columns['wpdd_sales'] = __('Sales', 'wp-digital-download');
 | 
			
		||||
                $new_columns['wpdd_revenue'] = __('Revenue', 'wp-digital-download');
 | 
			
		||||
                $new_columns['wpdd_files'] = __('Files', 'wp-digital-download');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $new_columns;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_product_columns($column, $post_id) {
 | 
			
		||||
        switch ($column) {
 | 
			
		||||
            case 'wpdd_price':
 | 
			
		||||
                $price = get_post_meta($post_id, '_wpdd_price', true);
 | 
			
		||||
                $sale_price = get_post_meta($post_id, '_wpdd_sale_price', true);
 | 
			
		||||
                $is_free = get_post_meta($post_id, '_wpdd_is_free', true);
 | 
			
		||||
                
 | 
			
		||||
                if ($is_free) {
 | 
			
		||||
                    echo '<span style="color: green;">' . __('Free', 'wp-digital-download') . '</span>';
 | 
			
		||||
                } elseif ($sale_price && $sale_price < $price) {
 | 
			
		||||
                    echo '<del>$' . number_format($price, 2) . '</del> ';
 | 
			
		||||
                    echo '<strong>$' . number_format($sale_price, 2) . '</strong>';
 | 
			
		||||
                } else {
 | 
			
		||||
                    echo '$' . number_format($price, 2);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
                
 | 
			
		||||
            case 'wpdd_sales':
 | 
			
		||||
                global $wpdb;
 | 
			
		||||
                $sales = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
                    "SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
                     WHERE product_id = %d AND status = 'completed'",
 | 
			
		||||
                    $post_id
 | 
			
		||||
                ));
 | 
			
		||||
                echo intval($sales);
 | 
			
		||||
                break;
 | 
			
		||||
                
 | 
			
		||||
            case 'wpdd_revenue':
 | 
			
		||||
                global $wpdb;
 | 
			
		||||
                $revenue = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
                    "SELECT SUM(amount) FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
                     WHERE product_id = %d AND status = 'completed'",
 | 
			
		||||
                    $post_id
 | 
			
		||||
                ));
 | 
			
		||||
                echo '$' . number_format($revenue ?: 0, 2);
 | 
			
		||||
                break;
 | 
			
		||||
                
 | 
			
		||||
            case 'wpdd_files':
 | 
			
		||||
                $files = get_post_meta($post_id, '_wpdd_files', true);
 | 
			
		||||
                $count = is_array($files) ? count($files) : 0;
 | 
			
		||||
                echo $count;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function make_columns_sortable($columns) {
 | 
			
		||||
        $columns['wpdd_price'] = 'wpdd_price';
 | 
			
		||||
        $columns['wpdd_sales'] = 'wpdd_sales';
 | 
			
		||||
        $columns['wpdd_revenue'] = 'wpdd_revenue';
 | 
			
		||||
        return $columns;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function sort_products_by_column($query) {
 | 
			
		||||
        if (!is_admin() || !$query->is_main_query()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($query->get('post_type') !== 'wpdd_product') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $orderby = $query->get('orderby');
 | 
			
		||||
        
 | 
			
		||||
        switch ($orderby) {
 | 
			
		||||
            case 'wpdd_price':
 | 
			
		||||
                $query->set('meta_key', '_wpdd_price');
 | 
			
		||||
                $query->set('orderby', 'meta_value_num');
 | 
			
		||||
                break;
 | 
			
		||||
                
 | 
			
		||||
            case 'wpdd_sales':
 | 
			
		||||
                $query->set('meta_key', '_wpdd_sales_count');
 | 
			
		||||
                $query->set('orderby', 'meta_value_num');
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_orders_page() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
 | 
			
		||||
        $per_page = 20;
 | 
			
		||||
        $offset = ($page - 1) * $per_page;
 | 
			
		||||
        
 | 
			
		||||
        $where = array('1=1');
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_GET['status']) && $_GET['status']) {
 | 
			
		||||
            $where[] = $wpdb->prepare("o.status = %s", sanitize_text_field($_GET['status']));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_GET['product_id']) && $_GET['product_id']) {
 | 
			
		||||
            $where[] = $wpdb->prepare("o.product_id = %d", intval($_GET['product_id']));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_GET['search']) && $_GET['search']) {
 | 
			
		||||
            $search = '%' . $wpdb->esc_like($_GET['search']) . '%';
 | 
			
		||||
            $where[] = $wpdb->prepare(
 | 
			
		||||
                "(o.order_number LIKE %s OR o.customer_email LIKE %s OR o.customer_name LIKE %s)",
 | 
			
		||||
                $search, $search, $search
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $where_clause = implode(' AND ', $where);
 | 
			
		||||
        
 | 
			
		||||
        $total = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_orders o WHERE {$where_clause}");
 | 
			
		||||
        
 | 
			
		||||
        $orders = $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE {$where_clause}
 | 
			
		||||
             ORDER BY o.purchase_date DESC
 | 
			
		||||
             LIMIT %d OFFSET %d",
 | 
			
		||||
            $per_page,
 | 
			
		||||
            $offset
 | 
			
		||||
        ));
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wrap">
 | 
			
		||||
            <h1><?php _e('Orders', 'wp-digital-download'); ?></h1>
 | 
			
		||||
            
 | 
			
		||||
            <form method="get">
 | 
			
		||||
                <input type="hidden" name="post_type" value="wpdd_product" />
 | 
			
		||||
                <input type="hidden" name="page" value="wpdd-orders" />
 | 
			
		||||
                
 | 
			
		||||
                <div class="tablenav top">
 | 
			
		||||
                    <div class="alignleft actions">
 | 
			
		||||
                        <select name="status">
 | 
			
		||||
                            <option value=""><?php _e('All Statuses', 'wp-digital-download'); ?></option>
 | 
			
		||||
                            <option value="pending" <?php selected(isset($_GET['status']) && $_GET['status'] == 'pending'); ?>>
 | 
			
		||||
                                <?php _e('Pending', 'wp-digital-download'); ?>
 | 
			
		||||
                            </option>
 | 
			
		||||
                            <option value="completed" <?php selected(isset($_GET['status']) && $_GET['status'] == 'completed'); ?>>
 | 
			
		||||
                                <?php _e('Completed', 'wp-digital-download'); ?>
 | 
			
		||||
                            </option>
 | 
			
		||||
                            <option value="failed" <?php selected(isset($_GET['status']) && $_GET['status'] == 'failed'); ?>>
 | 
			
		||||
                                <?php _e('Failed', 'wp-digital-download'); ?>
 | 
			
		||||
                            </option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                        
 | 
			
		||||
                        <input type="text" name="search" placeholder="<?php _e('Search orders...', 'wp-digital-download'); ?>" 
 | 
			
		||||
                               value="<?php echo isset($_GET['search']) ? esc_attr($_GET['search']) : ''; ?>" />
 | 
			
		||||
                        
 | 
			
		||||
                        <input type="submit" class="button" value="<?php _e('Filter', 'wp-digital-download'); ?>" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
            
 | 
			
		||||
            <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <th><?php _e('Order', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Product', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Customer', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Amount', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Status', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Date', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Actions', 'wp-digital-download'); ?></th>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                    <?php if ($orders) : ?>
 | 
			
		||||
                        <?php foreach ($orders as $order) : ?>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td><strong>#<?php echo esc_html($order->order_number); ?></strong></td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <a href="<?php echo get_edit_post_link($order->product_id); ?>">
 | 
			
		||||
                                        <?php echo esc_html($order->product_name); ?>
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php echo esc_html($order->customer_name); ?><br>
 | 
			
		||||
                                    <small><?php echo esc_html($order->customer_email); ?></small>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>$<?php echo number_format($order->amount, 2); ?></td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <span class="wpdd-status wpdd-status-<?php echo esc_attr($order->status); ?>">
 | 
			
		||||
                                        <?php echo ucfirst($order->status); ?>
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td><?php echo date_i18n(get_option('date_format'), strtotime($order->purchase_date)); ?></td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <a href="<?php echo wp_nonce_url(
 | 
			
		||||
                                        add_query_arg(array(
 | 
			
		||||
                                            'action' => 'wpdd_view_order',
 | 
			
		||||
                                            'order_id' => $order->id
 | 
			
		||||
                                        )),
 | 
			
		||||
                                        'wpdd_view_order_' . $order->id
 | 
			
		||||
                                    ); ?>" class="button button-small">
 | 
			
		||||
                                        <?php _e('View', 'wp-digital-download'); ?>
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        <?php endforeach; ?>
 | 
			
		||||
                    <?php else : ?>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td colspan="7"><?php _e('No orders found.', 'wp-digital-download'); ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    <?php endif; ?>
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
            
 | 
			
		||||
            <?php
 | 
			
		||||
            $total_pages = ceil($total / $per_page);
 | 
			
		||||
            if ($total_pages > 1) {
 | 
			
		||||
                echo '<div class="tablenav bottom"><div class="tablenav-pages">';
 | 
			
		||||
                echo paginate_links(array(
 | 
			
		||||
                    'base' => add_query_arg('paged', '%#%'),
 | 
			
		||||
                    'format' => '',
 | 
			
		||||
                    'current' => $page,
 | 
			
		||||
                    'total' => $total_pages
 | 
			
		||||
                ));
 | 
			
		||||
                echo '</div></div>';
 | 
			
		||||
            }
 | 
			
		||||
            ?>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <style>
 | 
			
		||||
            .wpdd-status {
 | 
			
		||||
                padding: 3px 8px;
 | 
			
		||||
                border-radius: 3px;
 | 
			
		||||
                font-size: 12px;
 | 
			
		||||
                font-weight: 600;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-status-completed { background: #d4edda; color: #155724; }
 | 
			
		||||
            .wpdd-status-pending { background: #fff3cd; color: #856404; }
 | 
			
		||||
            .wpdd-status-failed { background: #f8d7da; color: #721c24; }
 | 
			
		||||
        </style>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_reports_page() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $date_range = isset($_GET['range']) ? sanitize_text_field($_GET['range']) : '30days';
 | 
			
		||||
        
 | 
			
		||||
        switch ($date_range) {
 | 
			
		||||
            case '7days':
 | 
			
		||||
                $start_date = date('Y-m-d', strtotime('-7 days'));
 | 
			
		||||
                break;
 | 
			
		||||
            case '30days':
 | 
			
		||||
                $start_date = date('Y-m-d', strtotime('-30 days'));
 | 
			
		||||
                break;
 | 
			
		||||
            case '3months':
 | 
			
		||||
                $start_date = date('Y-m-d', strtotime('-3 months'));
 | 
			
		||||
                break;
 | 
			
		||||
            case 'year':
 | 
			
		||||
                $start_date = date('Y-m-d', strtotime('-1 year'));
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                $start_date = date('Y-m-d', strtotime('-30 days'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $stats = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT 
 | 
			
		||||
                COUNT(*) as total_orders,
 | 
			
		||||
                SUM(amount) as total_revenue,
 | 
			
		||||
                COUNT(DISTINCT customer_id) as unique_customers,
 | 
			
		||||
                COUNT(DISTINCT product_id) as products_sold
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders
 | 
			
		||||
             WHERE status = 'completed' 
 | 
			
		||||
             AND purchase_date >= %s",
 | 
			
		||||
            $start_date
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $top_products = $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
            "SELECT 
 | 
			
		||||
                p.ID, 
 | 
			
		||||
                p.post_title,
 | 
			
		||||
                COUNT(o.id) as sales,
 | 
			
		||||
                SUM(o.amount) as revenue
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             INNER JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE o.status = 'completed' 
 | 
			
		||||
             AND o.purchase_date >= %s
 | 
			
		||||
             GROUP BY p.ID
 | 
			
		||||
             ORDER BY revenue DESC
 | 
			
		||||
             LIMIT 10",
 | 
			
		||||
            $start_date
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $top_creators = $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
            "SELECT 
 | 
			
		||||
                u.ID,
 | 
			
		||||
                u.display_name,
 | 
			
		||||
                COUNT(o.id) as sales,
 | 
			
		||||
                SUM(o.amount) as revenue
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             INNER JOIN {$wpdb->users} u ON o.creator_id = u.ID
 | 
			
		||||
             WHERE o.status = 'completed' 
 | 
			
		||||
             AND o.purchase_date >= %s
 | 
			
		||||
             GROUP BY u.ID
 | 
			
		||||
             ORDER BY revenue DESC
 | 
			
		||||
             LIMIT 10",
 | 
			
		||||
            $start_date
 | 
			
		||||
        ));
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wrap">
 | 
			
		||||
            <h1><?php _e('Reports', 'wp-digital-download'); ?></h1>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-date-filter">
 | 
			
		||||
                <form method="get">
 | 
			
		||||
                    <input type="hidden" name="post_type" value="wpdd_product" />
 | 
			
		||||
                    <input type="hidden" name="page" value="wpdd-reports" />
 | 
			
		||||
                    
 | 
			
		||||
                    <select name="range" onchange="this.form.submit()">
 | 
			
		||||
                        <option value="7days" <?php selected($date_range, '7days'); ?>>
 | 
			
		||||
                            <?php _e('Last 7 Days', 'wp-digital-download'); ?>
 | 
			
		||||
                        </option>
 | 
			
		||||
                        <option value="30days" <?php selected($date_range, '30days'); ?>>
 | 
			
		||||
                            <?php _e('Last 30 Days', 'wp-digital-download'); ?>
 | 
			
		||||
                        </option>
 | 
			
		||||
                        <option value="3months" <?php selected($date_range, '3months'); ?>>
 | 
			
		||||
                            <?php _e('Last 3 Months', 'wp-digital-download'); ?>
 | 
			
		||||
                        </option>
 | 
			
		||||
                        <option value="year" <?php selected($date_range, 'year'); ?>>
 | 
			
		||||
                            <?php _e('Last Year', 'wp-digital-download'); ?>
 | 
			
		||||
                        </option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-stats-grid">
 | 
			
		||||
                <div class="wpdd-stat-box">
 | 
			
		||||
                    <h3><?php _e('Total Revenue', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <p class="wpdd-stat-value">$<?php echo number_format($stats->total_revenue ?: 0, 2); ?></p>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="wpdd-stat-box">
 | 
			
		||||
                    <h3><?php _e('Total Orders', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <p class="wpdd-stat-value"><?php echo intval($stats->total_orders); ?></p>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="wpdd-stat-box">
 | 
			
		||||
                    <h3><?php _e('Unique Customers', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <p class="wpdd-stat-value"><?php echo intval($stats->unique_customers); ?></p>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="wpdd-stat-box">
 | 
			
		||||
                    <h3><?php _e('Products Sold', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <p class="wpdd-stat-value"><?php echo intval($stats->products_sold); ?></p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-reports-tables">
 | 
			
		||||
                <div class="wpdd-report-section">
 | 
			
		||||
                    <h2><?php _e('Top Products', 'wp-digital-download'); ?></h2>
 | 
			
		||||
                    <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th><?php _e('Product', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Sales', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Revenue', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </thead>
 | 
			
		||||
                        <tbody>
 | 
			
		||||
                            <?php if ($top_products) : ?>
 | 
			
		||||
                                <?php foreach ($top_products as $product) : ?>
 | 
			
		||||
                                    <tr>
 | 
			
		||||
                                        <td>
 | 
			
		||||
                                            <a href="<?php echo get_edit_post_link($product->ID); ?>">
 | 
			
		||||
                                                <?php echo esc_html($product->post_title); ?>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                        </td>
 | 
			
		||||
                                        <td><?php echo intval($product->sales); ?></td>
 | 
			
		||||
                                        <td>$<?php echo number_format($product->revenue, 2); ?></td>
 | 
			
		||||
                                    </tr>
 | 
			
		||||
                                <?php endforeach; ?>
 | 
			
		||||
                            <?php else : ?>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td colspan="3"><?php _e('No data available.', 'wp-digital-download'); ?></td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            <?php endif; ?>
 | 
			
		||||
                        </tbody>
 | 
			
		||||
                    </table>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="wpdd-report-section">
 | 
			
		||||
                    <h2><?php _e('Top Creators', 'wp-digital-download'); ?></h2>
 | 
			
		||||
                    <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th><?php _e('Creator', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Sales', 'wp-digital-download'); ?></th>
 | 
			
		||||
                                <th><?php _e('Revenue', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        </thead>
 | 
			
		||||
                        <tbody>
 | 
			
		||||
                            <?php if ($top_creators) : ?>
 | 
			
		||||
                                <?php foreach ($top_creators as $creator) : ?>
 | 
			
		||||
                                    <tr>
 | 
			
		||||
                                        <td>
 | 
			
		||||
                                            <a href="<?php echo get_edit_user_link($creator->ID); ?>">
 | 
			
		||||
                                                <?php echo esc_html($creator->display_name); ?>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                        </td>
 | 
			
		||||
                                        <td><?php echo intval($creator->sales); ?></td>
 | 
			
		||||
                                        <td>$<?php echo number_format($creator->revenue, 2); ?></td>
 | 
			
		||||
                                    </tr>
 | 
			
		||||
                                <?php endforeach; ?>
 | 
			
		||||
                            <?php else : ?>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td colspan="3"><?php _e('No data available.', 'wp-digital-download'); ?></td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            <?php endif; ?>
 | 
			
		||||
                        </tbody>
 | 
			
		||||
                    </table>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <style>
 | 
			
		||||
            .wpdd-date-filter {
 | 
			
		||||
                margin: 20px 0;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-stats-grid {
 | 
			
		||||
                display: grid;
 | 
			
		||||
                grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 | 
			
		||||
                gap: 20px;
 | 
			
		||||
                margin: 30px 0;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-stat-box {
 | 
			
		||||
                background: white;
 | 
			
		||||
                padding: 20px;
 | 
			
		||||
                border: 1px solid #ccd0d4;
 | 
			
		||||
                border-radius: 4px;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-stat-box h3 {
 | 
			
		||||
                margin: 0 0 10px 0;
 | 
			
		||||
                color: #23282d;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-stat-value {
 | 
			
		||||
                font-size: 32px;
 | 
			
		||||
                font-weight: 600;
 | 
			
		||||
                color: #2271b1;
 | 
			
		||||
                margin: 0;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-reports-tables {
 | 
			
		||||
                display: grid;
 | 
			
		||||
                grid-template-columns: 1fr 1fr;
 | 
			
		||||
                gap: 30px;
 | 
			
		||||
                margin-top: 30px;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-report-section h2 {
 | 
			
		||||
                margin-bottom: 15px;
 | 
			
		||||
            }
 | 
			
		||||
            @media (max-width: 1200px) {
 | 
			
		||||
                .wpdd-reports-tables {
 | 
			
		||||
                    grid-template-columns: 1fr;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        </style>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_customers_page() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get all users who have made purchases, regardless of their role
 | 
			
		||||
        $customers = $wpdb->get_results(
 | 
			
		||||
            "SELECT 
 | 
			
		||||
                u.ID,
 | 
			
		||||
                u.user_email,
 | 
			
		||||
                u.display_name,
 | 
			
		||||
                u.user_registered,
 | 
			
		||||
                COUNT(o.id) as total_orders,
 | 
			
		||||
                SUM(o.amount) as total_spent,
 | 
			
		||||
                MAX(o.purchase_date) as last_order_date
 | 
			
		||||
             FROM {$wpdb->users} u
 | 
			
		||||
             INNER JOIN {$wpdb->prefix}wpdd_orders o ON u.ID = o.customer_id AND o.status = 'completed'
 | 
			
		||||
             GROUP BY u.ID
 | 
			
		||||
             ORDER BY total_spent DESC"
 | 
			
		||||
        );
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wrap">
 | 
			
		||||
            <h1><?php _e('Customers', 'wp-digital-download'); ?></h1>
 | 
			
		||||
            
 | 
			
		||||
            <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <th><?php _e('Customer', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Email', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Orders', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Total Spent', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Registered', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Last Order', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        <th><?php _e('Actions', 'wp-digital-download'); ?></th>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                    <?php if ($customers) : ?>
 | 
			
		||||
                        <?php foreach ($customers as $customer) : ?>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <strong><?php echo esc_html($customer->display_name); ?></strong>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td><?php echo esc_html($customer->user_email); ?></td>
 | 
			
		||||
                                <td><?php echo intval($customer->total_orders); ?></td>
 | 
			
		||||
                                <td>$<?php echo number_format($customer->total_spent ?: 0, 2); ?></td>
 | 
			
		||||
                                <td><?php echo date_i18n(get_option('date_format'), strtotime($customer->user_registered)); ?></td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <?php 
 | 
			
		||||
                                    echo $customer->last_order_date 
 | 
			
		||||
                                        ? date_i18n(get_option('date_format'), strtotime($customer->last_order_date))
 | 
			
		||||
                                        : '-';
 | 
			
		||||
                                    ?>
 | 
			
		||||
                                </td>
 | 
			
		||||
                                <td>
 | 
			
		||||
                                    <a href="<?php echo get_edit_user_link($customer->ID); ?>" class="button button-small">
 | 
			
		||||
                                        <?php _e('Edit', 'wp-digital-download'); ?>
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                    <a href="<?php echo add_query_arg(array(
 | 
			
		||||
                                        'post_type' => 'wpdd_product',
 | 
			
		||||
                                        'page' => 'wpdd-orders',
 | 
			
		||||
                                        'customer_id' => $customer->ID
 | 
			
		||||
                                    ), admin_url('edit.php')); ?>" class="button button-small">
 | 
			
		||||
                                        <?php _e('View Orders', 'wp-digital-download'); ?>
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </td>
 | 
			
		||||
                            </tr>
 | 
			
		||||
                        <?php endforeach; ?>
 | 
			
		||||
                    <?php else : ?>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td colspan="7"><?php _e('No customers found.', 'wp-digital-download'); ?></td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    <?php endif; ?>
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_admin_actions() {
 | 
			
		||||
        if (isset($_GET['action']) && $_GET['action'] === 'wpdd_view_order') {
 | 
			
		||||
            if (!isset($_GET['order_id']) || !wp_verify_nonce($_GET['_wpnonce'] ?? '', 'wpdd_view_order_' . $_GET['order_id'])) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            self::view_order_details(intval($_GET['order_id']));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function view_order_details($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE o.id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            wp_die(__('Order not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        include WPDD_PLUGIN_PATH . 'admin/views/order-details.php';
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_shortcodes_page() {
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wrap">
 | 
			
		||||
            <h1><?php _e('Available Shortcodes', 'wp-digital-download'); ?></h1>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-shortcodes-intro">
 | 
			
		||||
                <p><?php _e('Use these shortcodes to display digital download content on your pages and posts. Simply copy and paste the shortcode into any page or post editor.', 'wp-digital-download'); ?></p>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-shortcodes-grid">
 | 
			
		||||
                
 | 
			
		||||
                <!-- Shop Shortcode -->
 | 
			
		||||
                <div class="wpdd-shortcode-card">
 | 
			
		||||
                    <h3><?php _e('Shop Page', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <div class="wpdd-shortcode-example">
 | 
			
		||||
                        <code>[wpdd_shop]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p><?php _e('Displays a grid of all available products with filtering and pagination.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    
 | 
			
		||||
                    <h4><?php _e('Available Parameters:', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                    <ul class="wpdd-params-list">
 | 
			
		||||
                        <li><strong>posts_per_page</strong> - Number of products per page (default: 12)</li>
 | 
			
		||||
                        <li><strong>columns</strong> - Grid columns (default: 3, options: 1-6)</li>
 | 
			
		||||
                        <li><strong>orderby</strong> - Sort order (date, title, price, menu_order)</li>
 | 
			
		||||
                        <li><strong>order</strong> - ASC or DESC (default: DESC)</li>
 | 
			
		||||
                        <li><strong>category</strong> - Show only specific categories (comma separated slugs)</li>
 | 
			
		||||
                        <li><strong>show_filters</strong> - Show search/filter form (yes/no, default: yes)</li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                    
 | 
			
		||||
                    <h4><?php _e('Examples:', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                    <div class="wpdd-shortcode-examples">
 | 
			
		||||
                        <code>[wpdd_shop posts_per_page="6" columns="2"]</code><br>
 | 
			
		||||
                        <code>[wpdd_shop category="music,videos" show_filters="no"]</code><br>
 | 
			
		||||
                        <code>[wpdd_shop orderby="price" order="ASC" columns="4"]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Checkout Shortcode -->
 | 
			
		||||
                <div class="wpdd-shortcode-card">
 | 
			
		||||
                    <h3><?php _e('Checkout Page', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <div class="wpdd-shortcode-example">
 | 
			
		||||
                        <code>[wpdd_checkout]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p><?php _e('Displays the checkout form for purchasing products. Typically used on a dedicated checkout page.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    
 | 
			
		||||
                    <p class="wpdd-note"><?php _e('Note: This shortcode automatically detects the product to purchase from the URL parameter.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Customer Purchases Shortcode -->
 | 
			
		||||
                <div class="wpdd-shortcode-card">
 | 
			
		||||
                    <h3><?php _e('Customer Purchases', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <div class="wpdd-shortcode-example">
 | 
			
		||||
                        <code>[wpdd_customer_purchases]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p><?php _e('Shows a table of customer\'s purchase history with download links. Requires user to be logged in.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    
 | 
			
		||||
                    <p class="wpdd-note"><?php _e('Note: This page also supports guest access via email links sent after purchase.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Thank You Shortcode -->
 | 
			
		||||
                <div class="wpdd-shortcode-card">
 | 
			
		||||
                    <h3><?php _e('Thank You Page', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <div class="wpdd-shortcode-example">
 | 
			
		||||
                        <code>[wpdd_thank_you]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p><?php _e('Displays order confirmation and download links after successful purchase. Used on the thank you page.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    
 | 
			
		||||
                    <p class="wpdd-note"><?php _e('Note: This shortcode requires an order_id parameter in the URL.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Single Product Shortcode -->
 | 
			
		||||
                <div class="wpdd-shortcode-card">
 | 
			
		||||
                    <h3><?php _e('Single Product', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <div class="wpdd-shortcode-example">
 | 
			
		||||
                        <code>[wpdd_single_product id="123"]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p><?php _e('Display a single product card anywhere on your site.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    
 | 
			
		||||
                    <h4><?php _e('Parameters:', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                    <ul class="wpdd-params-list">
 | 
			
		||||
                        <li><strong>id</strong> - Product ID (required)</li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                    
 | 
			
		||||
                    <h4><?php _e('Example:', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                    <div class="wpdd-shortcode-examples">
 | 
			
		||||
                        <code>[wpdd_single_product id="456"]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <!-- Buy Button Shortcode -->
 | 
			
		||||
                <div class="wpdd-shortcode-card">
 | 
			
		||||
                    <h3><?php _e('Buy Button', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <div class="wpdd-shortcode-example">
 | 
			
		||||
                        <code>[wpdd_buy_button id="123"]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p><?php _e('Display just a buy button for a specific product.', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    
 | 
			
		||||
                    <h4><?php _e('Parameters:', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                    <ul class="wpdd-params-list">
 | 
			
		||||
                        <li><strong>id</strong> - Product ID (default: current post ID)</li>
 | 
			
		||||
                        <li><strong>text</strong> - Button text (default: "Buy Now")</li>
 | 
			
		||||
                        <li><strong>class</strong> - CSS class for styling</li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                    
 | 
			
		||||
                    <h4><?php _e('Examples:', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                    <div class="wpdd-shortcode-examples">
 | 
			
		||||
                        <code>[wpdd_buy_button id="789" text="Purchase Now"]</code><br>
 | 
			
		||||
                        <code>[wpdd_buy_button text="Get This Product" class="my-custom-button"]</code>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Page Setup Section -->
 | 
			
		||||
            <div class="wpdd-page-setup">
 | 
			
		||||
                <h2><?php _e('Required Pages Setup', 'wp-digital-download'); ?></h2>
 | 
			
		||||
                <p><?php _e('For the plugin to work correctly, you need these pages with their respective shortcodes:', 'wp-digital-download'); ?></p>
 | 
			
		||||
                
 | 
			
		||||
                <table class="wp-list-table widefat fixed striped">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th><?php _e('Page Name', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Shortcode', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Purpose', 'wp-digital-download'); ?></th>
 | 
			
		||||
                            <th><?php _e('Status', 'wp-digital-download'); ?></th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong><?php _e('Shop', 'wp-digital-download'); ?></strong></td>
 | 
			
		||||
                            <td><code>[wpdd_shop]</code></td>
 | 
			
		||||
                            <td><?php _e('Main product listing page', 'wp-digital-download'); ?></td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php 
 | 
			
		||||
                                $shop_page_id = get_option('wpdd_shop_page_id');
 | 
			
		||||
                                if ($shop_page_id && get_post($shop_page_id)) {
 | 
			
		||||
                                    echo '<span style="color: green;">✓ ' . __('Created', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                    echo ' (<a href="' . get_edit_post_link($shop_page_id) . '">' . __('Edit', 'wp-digital-download') . '</a>)';
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    echo '<span style="color: red;">✗ ' . __('Missing', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                }
 | 
			
		||||
                                ?>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong><?php _e('Checkout', 'wp-digital-download'); ?></strong></td>
 | 
			
		||||
                            <td><code>[wpdd_checkout]</code></td>
 | 
			
		||||
                            <td><?php _e('Purchase processing page', 'wp-digital-download'); ?></td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php 
 | 
			
		||||
                                $checkout_page_id = get_option('wpdd_checkout_page_id');
 | 
			
		||||
                                if ($checkout_page_id && get_post($checkout_page_id)) {
 | 
			
		||||
                                    echo '<span style="color: green;">✓ ' . __('Created', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                    echo ' (<a href="' . get_edit_post_link($checkout_page_id) . '">' . __('Edit', 'wp-digital-download') . '</a>)';
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    echo '<span style="color: red;">✗ ' . __('Missing', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                }
 | 
			
		||||
                                ?>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong><?php _e('My Purchases', 'wp-digital-download'); ?></strong></td>
 | 
			
		||||
                            <td><code>[wpdd_customer_purchases]</code></td>
 | 
			
		||||
                            <td><?php _e('Customer purchase history', 'wp-digital-download'); ?></td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php 
 | 
			
		||||
                                $purchases_page_id = get_option('wpdd_purchases_page_id');
 | 
			
		||||
                                if ($purchases_page_id && get_post($purchases_page_id)) {
 | 
			
		||||
                                    echo '<span style="color: green;">✓ ' . __('Created', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                    echo ' (<a href="' . get_edit_post_link($purchases_page_id) . '">' . __('Edit', 'wp-digital-download') . '</a>)';
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    echo '<span style="color: red;">✗ ' . __('Missing', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                }
 | 
			
		||||
                                ?>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <td><strong><?php _e('Thank You', 'wp-digital-download'); ?></strong></td>
 | 
			
		||||
                            <td><code>[wpdd_thank_you]</code></td>
 | 
			
		||||
                            <td><?php _e('Post-purchase confirmation', 'wp-digital-download'); ?></td>
 | 
			
		||||
                            <td>
 | 
			
		||||
                                <?php 
 | 
			
		||||
                                $thank_you_page_id = get_option('wpdd_thank_you_page_id');
 | 
			
		||||
                                if ($thank_you_page_id && get_post($thank_you_page_id)) {
 | 
			
		||||
                                    echo '<span style="color: green;">✓ ' . __('Created', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                    echo ' (<a href="' . get_edit_post_link($thank_you_page_id) . '">' . __('Edit', 'wp-digital-download') . '</a>)';
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    echo '<span style="color: red;">✗ ' . __('Missing', 'wp-digital-download') . '</span>';
 | 
			
		||||
                                }
 | 
			
		||||
                                ?>
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <style>
 | 
			
		||||
            .wpdd-shortcodes-intro {
 | 
			
		||||
                background: #f1f1f1;
 | 
			
		||||
                padding: 15px;
 | 
			
		||||
                border-radius: 5px;
 | 
			
		||||
                margin: 20px 0;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-shortcodes-grid {
 | 
			
		||||
                display: grid;
 | 
			
		||||
                grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
 | 
			
		||||
                gap: 20px;
 | 
			
		||||
                margin: 30px 0;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-shortcode-card {
 | 
			
		||||
                background: #fff;
 | 
			
		||||
                border: 1px solid #ccd0d4;
 | 
			
		||||
                border-radius: 5px;
 | 
			
		||||
                padding: 20px;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-shortcode-card h3 {
 | 
			
		||||
                margin-top: 0;
 | 
			
		||||
                color: #23282d;
 | 
			
		||||
                border-bottom: 1px solid #eee;
 | 
			
		||||
                padding-bottom: 10px;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-shortcode-example {
 | 
			
		||||
                background: #f8f9fa;
 | 
			
		||||
                padding: 10px;
 | 
			
		||||
                border-radius: 3px;
 | 
			
		||||
                margin: 10px 0;
 | 
			
		||||
                font-family: monospace;
 | 
			
		||||
                border-left: 4px solid #2271b1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-shortcode-examples {
 | 
			
		||||
                background: #f8f9fa;
 | 
			
		||||
                padding: 10px;
 | 
			
		||||
                border-radius: 3px;
 | 
			
		||||
                margin: 10px 0;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-shortcode-examples code {
 | 
			
		||||
                display: block;
 | 
			
		||||
                margin: 5px 0;
 | 
			
		||||
                color: #d63384;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-params-list {
 | 
			
		||||
                background: #fafafa;
 | 
			
		||||
                padding: 10px 30px;
 | 
			
		||||
                border-radius: 3px;
 | 
			
		||||
                margin: 10px 0;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-params-list li {
 | 
			
		||||
                margin: 8px 0;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-note {
 | 
			
		||||
                background: #fff3cd;
 | 
			
		||||
                border: 1px solid #ffeaa7;
 | 
			
		||||
                color: #856404;
 | 
			
		||||
                padding: 10px;
 | 
			
		||||
                border-radius: 3px;
 | 
			
		||||
                font-style: italic;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-page-setup {
 | 
			
		||||
                margin-top: 40px;
 | 
			
		||||
                padding-top: 30px;
 | 
			
		||||
                border-top: 2px solid #ddd;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-page-setup h2 {
 | 
			
		||||
                color: #23282d;
 | 
			
		||||
                margin-bottom: 15px;
 | 
			
		||||
            }
 | 
			
		||||
        </style>
 | 
			
		||||
        
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										682
									
								
								admin/class-wpdd-settings.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										682
									
								
								admin/class-wpdd-settings.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,682 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Settings {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('admin_menu', array(__CLASS__, 'add_settings_page'));
 | 
			
		||||
        add_action('admin_init', array(__CLASS__, 'register_settings'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_settings_page() {
 | 
			
		||||
        add_submenu_page(
 | 
			
		||||
            'edit.php?post_type=wpdd_product',
 | 
			
		||||
            __('Settings', 'wp-digital-download'),
 | 
			
		||||
            __('Settings', 'wp-digital-download'),
 | 
			
		||||
            'wpdd_manage_settings',
 | 
			
		||||
            'wpdd-settings',
 | 
			
		||||
            array(__CLASS__, 'render_settings_page')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    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_admin_email');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_from_name');
 | 
			
		||||
        register_setting('wpdd_settings', 'wpdd_from_email');
 | 
			
		||||
        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(
 | 
			
		||||
            'sanitize_callback' => array(__CLASS__, 'sanitize_commission_rate')
 | 
			
		||||
        ));
 | 
			
		||||
        register_setting('wpdd_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');
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            __('General Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'general_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            __('PayPal Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'paypal_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            __('Email Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'email_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            __('Download Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'download_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_section(
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            __('Watermark Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'watermark_section_callback'),
 | 
			
		||||
            'wpdd_settings'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        self::add_general_fields();
 | 
			
		||||
        self::add_paypal_fields();
 | 
			
		||||
        self::add_email_fields();
 | 
			
		||||
        self::add_download_fields();
 | 
			
		||||
        self::add_watermark_fields();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function add_general_fields() {
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_currency',
 | 
			
		||||
            __('Currency', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'currency_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_currency'
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_enable_guest_checkout',
 | 
			
		||||
            __('Guest Checkout', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'checkbox_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_enable_guest_checkout',
 | 
			
		||||
                'label' => __('Allow guest customers to purchase without creating an account', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_commission_rate',
 | 
			
		||||
            __('Platform Commission Rate (%)', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_commission_rate',
 | 
			
		||||
                'description' => __('Platform commission rate from sales (0-100). Creators receive the remainder.', 'wp-digital-download'),
 | 
			
		||||
                'min' => 0,
 | 
			
		||||
                'max' => 100,
 | 
			
		||||
                'step' => 0.01
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_payout_threshold',
 | 
			
		||||
            __('Automatic Payout Threshold ($)', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_payout_threshold',
 | 
			
		||||
                'description' => __('Minimum balance for automatic payouts (0 to disable)', 'wp-digital-download'),
 | 
			
		||||
                'min' => 0,
 | 
			
		||||
                'step' => 0.01
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_terms_page',
 | 
			
		||||
            __('Terms & Conditions Page', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'page_dropdown_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array('name' => 'wpdd_terms_page')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_privacy_page',
 | 
			
		||||
            __('Privacy Policy Page', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'page_dropdown_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_general_settings',
 | 
			
		||||
            array('name' => 'wpdd_privacy_page')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function add_paypal_fields() {
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_paypal_mode',
 | 
			
		||||
            __('PayPal Mode', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'select_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_paypal_mode',
 | 
			
		||||
                'options' => array(
 | 
			
		||||
                    'sandbox' => __('Sandbox (Testing)', 'wp-digital-download'),
 | 
			
		||||
                    'live' => __('Live (Production)', 'wp-digital-download')
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_paypal_client_id',
 | 
			
		||||
            __('PayPal Client ID', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            array('name' => 'wpdd_paypal_client_id')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_paypal_secret',
 | 
			
		||||
            __('PayPal Secret', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'password_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_paypal_settings',
 | 
			
		||||
            array('name' => 'wpdd_paypal_secret')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function add_email_fields() {
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_admin_email',
 | 
			
		||||
            __('Admin Email', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'email_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_admin_email',
 | 
			
		||||
                'description' => __('Email address for admin notifications', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_from_name',
 | 
			
		||||
            __('From Name', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_from_name',
 | 
			
		||||
                'description' => __('Name shown in email headers', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_from_email',
 | 
			
		||||
            __('From Email', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'email_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_email_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_from_email',
 | 
			
		||||
                'description' => __('Email address shown in email headers', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function add_download_fields() {
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_default_download_limit',
 | 
			
		||||
            __('Default Download Limit', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_default_download_limit',
 | 
			
		||||
                'description' => __('Default number of downloads allowed per purchase (0 = unlimited)', 'wp-digital-download'),
 | 
			
		||||
                'min' => 0
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_default_download_expiry',
 | 
			
		||||
            __('Default Download Expiry (days)', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'number_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_default_download_expiry',
 | 
			
		||||
                'description' => __('Default number of days downloads remain available (0 = never expires)', 'wp-digital-download'),
 | 
			
		||||
                'min' => 0
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_file_access_method',
 | 
			
		||||
            __('File Access Method', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'select_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_download_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_file_access_method',
 | 
			
		||||
                'options' => array(
 | 
			
		||||
                    'direct' => __('Direct Download', 'wp-digital-download'),
 | 
			
		||||
                    'redirect' => __('Redirect to File', 'wp-digital-download'),
 | 
			
		||||
                    'force' => __('Force Download', 'wp-digital-download')
 | 
			
		||||
                ),
 | 
			
		||||
                'description' => __('How files are delivered to customers', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function add_watermark_fields() {
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_enable_watermark',
 | 
			
		||||
            __('Enable Watermarking', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'checkbox_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_enable_watermark',
 | 
			
		||||
                'label' => __('Enable watermarking for images and PDFs by default', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_settings_field(
 | 
			
		||||
            'wpdd_watermark_text',
 | 
			
		||||
            __('Default Watermark Text', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'text_field'),
 | 
			
		||||
            'wpdd_settings',
 | 
			
		||||
            'wpdd_watermark_settings',
 | 
			
		||||
            array(
 | 
			
		||||
                'name' => 'wpdd_watermark_text',
 | 
			
		||||
                'description' => __('Available placeholders: {customer_name}, {customer_email}, {order_id}, {date}, {site_name}', 'wp-digital-download')
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_settings_page() {
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wrap">
 | 
			
		||||
            <h1><?php _e('WP Digital Download Settings', 'wp-digital-download'); ?></h1>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-settings-sidebar">
 | 
			
		||||
                <div class="wpdd-settings-box">
 | 
			
		||||
                    <h3><?php _e('Quick Setup', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <p><?php _e('To get started quickly:', 'wp-digital-download'); ?></p>
 | 
			
		||||
                    <ol>
 | 
			
		||||
                        <li><?php _e('Configure PayPal settings above', 'wp-digital-download'); ?></li>
 | 
			
		||||
                        <li><?php _e('Create your first product', 'wp-digital-download'); ?></li>
 | 
			
		||||
                        <li><?php _e('Add the shop shortcode [wpdd_shop] to a page', 'wp-digital-download'); ?></li>
 | 
			
		||||
                        <li><?php _e('Test with a free product first', 'wp-digital-download'); ?></li>
 | 
			
		||||
                    </ol>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="wpdd-settings-box">
 | 
			
		||||
                    <h3><?php _e('Available Shortcodes', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <ul>
 | 
			
		||||
                        <li><code>[wpdd_shop]</code> - <?php _e('Display product storefront', 'wp-digital-download'); ?></li>
 | 
			
		||||
                        <li><code>[wpdd_customer_purchases]</code> - <?php _e('Customer purchase history', 'wp-digital-download'); ?></li>
 | 
			
		||||
                        <li><code>[wpdd_checkout]</code> - <?php _e('Checkout page', 'wp-digital-download'); ?></li>
 | 
			
		||||
                        <li><code>[wpdd_thank_you]</code> - <?php _e('Thank you page', 'wp-digital-download'); ?></li>
 | 
			
		||||
                        <li><code>[wpdd_product id="123"]</code> - <?php _e('Single product display', 'wp-digital-download'); ?></li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="wpdd-settings-box">
 | 
			
		||||
                    <h3><?php _e('System Status', 'wp-digital-download'); ?></h3>
 | 
			
		||||
                    <?php self::system_status(); ?>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <form method="post" action="options.php" class="wpdd-settings-form">
 | 
			
		||||
                <?php
 | 
			
		||||
                settings_fields('wpdd_settings');
 | 
			
		||||
                do_settings_sections('wpdd_settings');
 | 
			
		||||
                submit_button();
 | 
			
		||||
                ?>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <style>
 | 
			
		||||
            .wpdd-settings-sidebar {
 | 
			
		||||
                float: right;
 | 
			
		||||
                width: 300px;
 | 
			
		||||
                margin-left: 20px;
 | 
			
		||||
                position: relative;
 | 
			
		||||
                z-index: 10;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-settings-form {
 | 
			
		||||
                overflow: hidden;
 | 
			
		||||
                margin-right: 340px;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-settings-box {
 | 
			
		||||
                background: white;
 | 
			
		||||
                border: 1px solid #ccd0d4;
 | 
			
		||||
                padding: 20px;
 | 
			
		||||
                margin-bottom: 20px;
 | 
			
		||||
                box-shadow: 0 1px 3px rgba(0,0,0,0.05);
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-settings-box h3 {
 | 
			
		||||
                margin-top: 0;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-settings-box code {
 | 
			
		||||
                background: #f1f1f1;
 | 
			
		||||
                padding: 2px 5px;
 | 
			
		||||
                font-size: 12px;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-status-good { color: #46b450; }
 | 
			
		||||
            .wpdd-status-warning { color: #ffb900; }
 | 
			
		||||
            .wpdd-status-error { color: #dc3232; }
 | 
			
		||||
            @media (max-width: 1200px) {
 | 
			
		||||
                .wpdd-settings-sidebar {
 | 
			
		||||
                    float: none;
 | 
			
		||||
                    width: 100%;
 | 
			
		||||
                    margin-left: 0;
 | 
			
		||||
                    margin-top: 30px;
 | 
			
		||||
                }
 | 
			
		||||
                .wpdd-settings-form {
 | 
			
		||||
                    margin-right: 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        </style>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function general_section_callback() {
 | 
			
		||||
        echo '<p>' . __('Configure basic plugin settings.', 'wp-digital-download') . '</p>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function paypal_section_callback() {
 | 
			
		||||
        echo '<p>' . __('Configure PayPal payment settings. You can get your API credentials from the PayPal Developer Dashboard.', 'wp-digital-download') . '</p>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function email_section_callback() {
 | 
			
		||||
        echo '<p>' . __('Configure email settings for purchase notifications.', 'wp-digital-download') . '</p>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function download_section_callback() {
 | 
			
		||||
        echo '<p>' . __('Configure default download and file protection settings.', 'wp-digital-download') . '</p>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function watermark_section_callback() {
 | 
			
		||||
        echo '<p>' . __('Configure watermarking settings for images and PDF files.', 'wp-digital-download') . '</p>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function text_field($args) {
 | 
			
		||||
        $name = $args['name'] ?? '';
 | 
			
		||||
        $value = get_option($name, '');
 | 
			
		||||
        $description = isset($args['description']) ? $args['description'] : '';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($name)) {
 | 
			
		||||
            echo '<p>Error: Field name not provided</p>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        printf(
 | 
			
		||||
            '<input type="text" id="%s" name="%s" value="%s" class="regular-text" />',
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($value)
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($description) {
 | 
			
		||||
            printf('<p class="description">%s</p>', esc_html($description));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function password_field($args) {
 | 
			
		||||
        $name = $args['name'] ?? '';
 | 
			
		||||
        $value = get_option($name, '');
 | 
			
		||||
        $description = isset($args['description']) ? $args['description'] : '';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($name)) {
 | 
			
		||||
            echo '<p>Error: Field name not provided</p>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        printf(
 | 
			
		||||
            '<input type="password" id="%s" name="%s" value="%s" class="regular-text" />',
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($value)
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($description) {
 | 
			
		||||
            printf('<p class="description">%s</p>', esc_html($description));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function email_field($args) {
 | 
			
		||||
        $name = $args['name'] ?? '';
 | 
			
		||||
        $value = get_option($name, '');
 | 
			
		||||
        $description = isset($args['description']) ? $args['description'] : '';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($name)) {
 | 
			
		||||
            echo '<p>Error: Field name not provided</p>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        printf(
 | 
			
		||||
            '<input type="email" id="%s" name="%s" value="%s" class="regular-text" />',
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($value)
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($description) {
 | 
			
		||||
            printf('<p class="description">%s</p>', esc_html($description));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function number_field($args) {
 | 
			
		||||
        $name = $args['name'] ?? '';
 | 
			
		||||
        $value = get_option($name, '');
 | 
			
		||||
        $description = isset($args['description']) ? $args['description'] : '';
 | 
			
		||||
        $min = isset($args['min']) ? $args['min'] : 0;
 | 
			
		||||
        $max = isset($args['max']) ? $args['max'] : '';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($name)) {
 | 
			
		||||
            echo '<p>Error: Field name not provided</p>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        printf(
 | 
			
		||||
            '<input type="number" id="%s" name="%s" value="%s" min="%s" %s />',
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($value),
 | 
			
		||||
            esc_attr($min),
 | 
			
		||||
            $max ? 'max="' . esc_attr($max) . '"' : ''
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($description) {
 | 
			
		||||
            printf('<p class="description">%s</p>', esc_html($description));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function checkbox_field($args) {
 | 
			
		||||
        $name = $args['name'] ?? '';
 | 
			
		||||
        $value = get_option($name, 0);
 | 
			
		||||
        $label = $args['label'] ?? '';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($name)) {
 | 
			
		||||
            echo '<p>Error: Field name not provided</p>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        printf(
 | 
			
		||||
            '<label><input type="checkbox" id="%s" name="%s" value="1" %s /> %s</label>',
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            esc_attr($name),
 | 
			
		||||
            checked($value, 1, false),
 | 
			
		||||
            esc_html($label)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function select_field($args) {
 | 
			
		||||
        $name = $args['name'] ?? '';
 | 
			
		||||
        $value = get_option($name, '');
 | 
			
		||||
        $options = $args['options'] ?? array();
 | 
			
		||||
        $description = isset($args['description']) ? $args['description'] : '';
 | 
			
		||||
        
 | 
			
		||||
        if (empty($name)) {
 | 
			
		||||
            echo '<p>Error: Field name not provided</p>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        printf('<select id="%s" name="%s">', esc_attr($name), esc_attr($name));
 | 
			
		||||
        
 | 
			
		||||
        foreach ($options as $option_value => $option_label) {
 | 
			
		||||
            printf(
 | 
			
		||||
                '<option value="%s" %s>%s</option>',
 | 
			
		||||
                esc_attr($option_value),
 | 
			
		||||
                selected($value, $option_value, false),
 | 
			
		||||
                esc_html($option_label)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        echo '</select>';
 | 
			
		||||
        
 | 
			
		||||
        if ($description) {
 | 
			
		||||
            printf('<p class="description">%s</p>', esc_html($description));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function currency_field($args) {
 | 
			
		||||
        $currencies = array(
 | 
			
		||||
            'USD' => 'US Dollar ($)',
 | 
			
		||||
            'EUR' => 'Euro (€)',
 | 
			
		||||
            'GBP' => 'British Pound (£)',
 | 
			
		||||
            'CAD' => 'Canadian Dollar (C$)',
 | 
			
		||||
            'AUD' => 'Australian Dollar (A$)',
 | 
			
		||||
            'JPY' => 'Japanese Yen (¥)'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $args['options'] = $currencies;
 | 
			
		||||
        self::select_field($args);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function page_dropdown_field($args) {
 | 
			
		||||
        $name = $args['name'] ?? '';
 | 
			
		||||
        $value = get_option($name, '');
 | 
			
		||||
        
 | 
			
		||||
        if (empty($name)) {
 | 
			
		||||
            echo '<p>Error: Field name not provided</p>';
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_dropdown_pages(array(
 | 
			
		||||
            'name' => $name,
 | 
			
		||||
            'id' => $name,
 | 
			
		||||
            'selected' => $value,
 | 
			
		||||
            'show_option_none' => __('— Select —', 'wp-digital-download'),
 | 
			
		||||
            'option_none_value' => ''
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function system_status() {
 | 
			
		||||
        $status = array();
 | 
			
		||||
        
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        if (is_writable($upload_dir['basedir'])) {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('Upload Directory', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Writable', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-good'
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('Upload Directory', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Not Writable', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-error'
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (file_exists($protected_dir)) {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('Protected Directory', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Exists', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-good'
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('Protected Directory', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Missing', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-warning'
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (function_exists('imagecreatefrompng')) {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('GD Library', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Available', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-good'
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('GD Library', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Not Available', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-warning'
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $paypal_client_id = get_option('wpdd_paypal_client_id');
 | 
			
		||||
        if ($paypal_client_id) {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('PayPal', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Configured', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-good'
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            $status[] = array(
 | 
			
		||||
                'label' => __('PayPal', 'wp-digital-download'),
 | 
			
		||||
                'value' => __('Not Configured', 'wp-digital-download'),
 | 
			
		||||
                'class' => 'wpdd-status-warning'
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        echo '<ul>';
 | 
			
		||||
        foreach ($status as $item) {
 | 
			
		||||
            printf(
 | 
			
		||||
                '<li>%s: <span class="%s">%s</span></li>',
 | 
			
		||||
                esc_html($item['label']),
 | 
			
		||||
                esc_attr($item['class']),
 | 
			
		||||
                esc_html($item['value'])
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        echo '</ul>';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function sanitize_commission_rate($input) {
 | 
			
		||||
        $value = floatval($input);
 | 
			
		||||
        if ($value < 0) {
 | 
			
		||||
            $value = 0;
 | 
			
		||||
            add_settings_error('wpdd_commission_rate', 'invalid_rate', __('Commission rate cannot be less than 0%. Set to 0%.', 'wp-digital-download'));
 | 
			
		||||
        } elseif ($value > 100) {
 | 
			
		||||
            $value = 100;
 | 
			
		||||
            add_settings_error('wpdd_commission_rate', 'invalid_rate', __('Commission rate cannot exceed 100%. Set to 100%.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        return $value;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function sanitize_payout_threshold($input) {
 | 
			
		||||
        $value = floatval($input);
 | 
			
		||||
        if ($value < 0) {
 | 
			
		||||
            $value = 0;
 | 
			
		||||
            add_settings_error('wpdd_payout_threshold', 'invalid_threshold', __('Payout threshold cannot be negative. Set to 0 (disabled).', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        return $value;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										451
									
								
								assets/css/admin.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										451
									
								
								assets/css/admin.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,451 @@
 | 
			
		||||
/* WP Digital Download - Admin Styles */
 | 
			
		||||
 | 
			
		||||
/* Product Metaboxes */
 | 
			
		||||
.wpdd-metabox-content {
 | 
			
		||||
    padding: 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-metabox-content p {
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-metabox-content label {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-metabox-content input[type="text"],
 | 
			
		||||
.wpdd-metabox-content input[type="number"],
 | 
			
		||||
.wpdd-metabox-content input[type="email"] {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-metabox-content input[type="checkbox"] {
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-metabox-content .description {
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
    color: #666;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Price fields toggle */
 | 
			
		||||
#wpdd_is_free:checked ~ .wpdd-price-field {
 | 
			
		||||
    opacity: 0.5;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Files Metabox */
 | 
			
		||||
.wpdd-files-container {
 | 
			
		||||
    padding: 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#wpdd-files-list {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-item {
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    background: #f9f9f9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-header {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding: 10px 15px;
 | 
			
		||||
    background: #f1f1f1;
 | 
			
		||||
    border-bottom: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-handle {
 | 
			
		||||
    cursor: move;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
    color: #666;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-header input[type="text"] {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
    padding: 5px 8px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-content {
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-url {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-url-input {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-upload-file,
 | 
			
		||||
.wpdd-remove-file {
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-remove-file {
 | 
			
		||||
    background: #dc3545;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border: none;
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-remove-file:hover {
 | 
			
		||||
    background: #c82333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#wpdd-add-file {
 | 
			
		||||
    background: #0073aa;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border: none;
 | 
			
		||||
    padding: 10px 20px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#wpdd-add-file:hover {
 | 
			
		||||
    background: #005a87;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Settings Grid */
 | 
			
		||||
.wpdd-settings-grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: 1fr 1fr;
 | 
			
		||||
    gap: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-setting-group {
 | 
			
		||||
    background: #f9f9f9;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    border: 1px solid #e1e5e9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-setting-group h4 {
 | 
			
		||||
    margin: 0 0 15px 0;
 | 
			
		||||
    color: #23282d;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-setting-group p {
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-setting-group label {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-setting-group input[type="text"],
 | 
			
		||||
.wpdd-setting-group input[type="number"] {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 6px 8px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-setting-group .description {
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    color: #666;
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 782px) {
 | 
			
		||||
    .wpdd-settings-grid {
 | 
			
		||||
        grid-template-columns: 1fr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Stats Metabox */
 | 
			
		||||
.wpdd-stats p {
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-stats strong {
 | 
			
		||||
    color: #23282d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Product List Columns */
 | 
			
		||||
.column-wpdd_price,
 | 
			
		||||
.column-wpdd_sales,
 | 
			
		||||
.column-wpdd_revenue,
 | 
			
		||||
.column-wpdd_files {
 | 
			
		||||
    width: 10%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* File Upload Progress */
 | 
			
		||||
.wpdd-upload-progress {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    background: #f1f1f1;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin: 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-upload-progress-bar {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: #0073aa;
 | 
			
		||||
    transition: width 0.3s;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Drag and Drop Sorting */
 | 
			
		||||
.wpdd-file-item.ui-sortable-helper {
 | 
			
		||||
    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
 | 
			
		||||
    transform: rotate(2deg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-file-item.ui-sortable-placeholder {
 | 
			
		||||
    border: 2px dashed #0073aa;
 | 
			
		||||
    background: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Admin Dashboard Widgets */
 | 
			
		||||
.wpdd-sales-summary .wpdd-stats-grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: 1fr 1fr;
 | 
			
		||||
    gap: 15px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-sales-summary .wpdd-stat {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    background: #f0f0f1;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-sales-summary .wpdd-stat-value {
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    color: #2271b1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-sales-summary .wpdd-stat-label {
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    color: #646970;
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Orders Page */
 | 
			
		||||
.wpdd-status {
 | 
			
		||||
    padding: 3px 8px;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-status-completed {
 | 
			
		||||
    background: #d4edda;
 | 
			
		||||
    color: #155724;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-status-pending {
 | 
			
		||||
    background: #fff3cd;
 | 
			
		||||
    color: #856404;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-status-failed {
 | 
			
		||||
    background: #f8d7da;
 | 
			
		||||
    color: #721c24;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Reports Page */
 | 
			
		||||
.wpdd-date-filter {
 | 
			
		||||
    margin: 20px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-stats-grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 | 
			
		||||
    gap: 20px;
 | 
			
		||||
    margin: 30px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-stat-box {
 | 
			
		||||
    background: white;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border: 1px solid #ccd0d4;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-stat-box h3 {
 | 
			
		||||
    margin: 0 0 10px 0;
 | 
			
		||||
    color: #23282d;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-stat-value {
 | 
			
		||||
    font-size: 32px;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    color: #2271b1;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    line-height: 1.2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-reports-tables {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: 1fr 1fr;
 | 
			
		||||
    gap: 30px;
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-report-section h2 {
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 1200px) {
 | 
			
		||||
    .wpdd-reports-tables {
 | 
			
		||||
        grid-template-columns: 1fr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Settings Page */
 | 
			
		||||
.wpdd-settings-sidebar {
 | 
			
		||||
    float: right;
 | 
			
		||||
    width: 300px;
 | 
			
		||||
    margin-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-settings-box {
 | 
			
		||||
    background: white;
 | 
			
		||||
    border: 1px solid #ccd0d4;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-settings-box h3 {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-settings-box code {
 | 
			
		||||
    background: #f1f1f1;
 | 
			
		||||
    padding: 2px 5px;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-settings-box ul {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-settings-box li {
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-status-good {
 | 
			
		||||
    color: #46b450;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-status-warning {
 | 
			
		||||
    color: #ffb900;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-status-error {
 | 
			
		||||
    color: #dc3232;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#wpforms-settings .form-table {
 | 
			
		||||
    max-width: calc(100% - 340px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 1200px) {
 | 
			
		||||
    .wpdd-settings-sidebar {
 | 
			
		||||
        float: none;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        margin-left: 0;
 | 
			
		||||
        margin-top: 30px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    #wpforms-settings .form-table {
 | 
			
		||||
        max-width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Responsive adjustments */
 | 
			
		||||
@media (max-width: 782px) {
 | 
			
		||||
    .wpdd-file-header {
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        align-items: stretch;
 | 
			
		||||
        gap: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .wpdd-file-url {
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        gap: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .wpdd-settings-sidebar {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        margin-left: 0;
 | 
			
		||||
        margin-top: 20px;
 | 
			
		||||
        float: none;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .wpdd-stats-grid {
 | 
			
		||||
        grid-template-columns: 1fr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Loading states */
 | 
			
		||||
.wpdd-loading {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    opacity: 0.6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-loading::after {
 | 
			
		||||
    content: '';
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    margin: -10px 0 0 -10px;
 | 
			
		||||
    border: 2px solid #ccc;
 | 
			
		||||
    border-top: 2px solid #0073aa;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    animation: wpdd-spin 1s linear infinite;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes wpdd-spin {
 | 
			
		||||
    0% { transform: rotate(0deg); }
 | 
			
		||||
    100% { transform: rotate(360deg); }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										469
									
								
								assets/css/frontend.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										469
									
								
								assets/css/frontend.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,469 @@
 | 
			
		||||
/* WP Digital Download - Frontend Styles */
 | 
			
		||||
 | 
			
		||||
/* Shop Grid */
 | 
			
		||||
.wpdd-shop-container {
 | 
			
		||||
    max-width: 1200px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-shop-filters {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    margin-bottom: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-filter-form {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 15px;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-filter-form input[type="text"],
 | 
			
		||||
.wpdd-filter-form select {
 | 
			
		||||
    padding: 8px 12px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    min-width: 150px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-filter-submit {
 | 
			
		||||
    padding: 8px 16px;
 | 
			
		||||
    background: #0073aa;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: background 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-filter-submit:hover {
 | 
			
		||||
    background: #005a87;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-products-grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    gap: 30px;
 | 
			
		||||
    margin-bottom: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-columns-1 { grid-template-columns: 1fr; }
 | 
			
		||||
.wpdd-columns-2 { grid-template-columns: repeat(2, 1fr); }
 | 
			
		||||
.wpdd-columns-3 { grid-template-columns: repeat(3, 1fr); }
 | 
			
		||||
.wpdd-columns-4 { grid-template-columns: repeat(4, 1fr); }
 | 
			
		||||
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .wpdd-columns-2,
 | 
			
		||||
    .wpdd-columns-3,
 | 
			
		||||
    .wpdd-columns-4 {
 | 
			
		||||
        grid-template-columns: 1fr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 769px) and (max-width: 1024px) {
 | 
			
		||||
    .wpdd-columns-3,
 | 
			
		||||
    .wpdd-columns-4 {
 | 
			
		||||
        grid-template-columns: repeat(2, 1fr);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Product Cards */
 | 
			
		||||
.wpdd-product-card {
 | 
			
		||||
    background: white;
 | 
			
		||||
    border: 1px solid #e1e5e9;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    transition: transform 0.3s, box-shadow 0.3s;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-card:hover {
 | 
			
		||||
    transform: translateY(-5px);
 | 
			
		||||
    box-shadow: 0 8px 25px rgba(0,0,0,0.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-image {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    height: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-image img {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    object-fit: cover;
 | 
			
		||||
    transition: transform 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-card:hover .wpdd-product-image img {
 | 
			
		||||
    transform: scale(1.05);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-info {
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-title {
 | 
			
		||||
    margin: 0 0 10px 0;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    line-height: 1.4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-title a {
 | 
			
		||||
    color: #333;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    transition: color 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-title a:hover {
 | 
			
		||||
    color: #0073aa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-meta {
 | 
			
		||||
    color: #666;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-creator {
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-excerpt {
 | 
			
		||||
    color: #555;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    line-height: 1.5;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-price {
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-price-free {
 | 
			
		||||
    color: #28a745;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-price-regular {
 | 
			
		||||
    color: #333;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-price-sale {
 | 
			
		||||
    color: #dc3545;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-price-strike {
 | 
			
		||||
    text-decoration: line-through;
 | 
			
		||||
    color: #999;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-actions {
 | 
			
		||||
    margin-top: auto;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-product-actions .wpdd-btn {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Buttons */
 | 
			
		||||
.wpdd-btn {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    padding: 10px 20px;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    line-height: 1.4;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-primary {
 | 
			
		||||
    background: #0073aa;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-primary:hover {
 | 
			
		||||
    background: #005a87;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-view {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    color: #333;
 | 
			
		||||
    border: 1px solid #dee2e6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-view:hover {
 | 
			
		||||
    background: #e9ecef;
 | 
			
		||||
    color: #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-buy {
 | 
			
		||||
    background: #28a745;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-buy:hover {
 | 
			
		||||
    background: #218838;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-owned-product {
 | 
			
		||||
    background: #17a2b8 !important;
 | 
			
		||||
    color: white !important;
 | 
			
		||||
    padding: 10px 30px !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-owned-product:hover {
 | 
			
		||||
    background: #138496 !important;
 | 
			
		||||
    color: white !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-view:hover {
 | 
			
		||||
    background: #e9ecef;
 | 
			
		||||
    color: #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-download {
 | 
			
		||||
    background: #28a745;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-download:hover {
 | 
			
		||||
    background: #218838;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-btn-large {
 | 
			
		||||
    padding: 15px 30px;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Customer Purchases */
 | 
			
		||||
.wpdd-customer-purchases {
 | 
			
		||||
    max-width: 1000px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-purchases-table {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border-collapse: collapse;
 | 
			
		||||
    margin-bottom: 30px;
 | 
			
		||||
    background: white;
 | 
			
		||||
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-purchases-table th,
 | 
			
		||||
.wpdd-purchases-table td {
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    border-bottom: 1px solid #e1e5e9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-purchases-table th {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    color: #495057;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-purchases-table tr:hover {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-download-expired {
 | 
			
		||||
    color: #dc3545;
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Checkout */
 | 
			
		||||
.wpdd-checkout {
 | 
			
		||||
    max-width: 600px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-product {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    margin-bottom: 30px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-product h3 {
 | 
			
		||||
    margin: 0 0 15px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-product img {
 | 
			
		||||
    max-width: 150px;
 | 
			
		||||
    height: auto;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-price {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    color: #0073aa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-section {
 | 
			
		||||
    background: white;
 | 
			
		||||
    padding: 25px;
 | 
			
		||||
    border: 1px solid #e1e5e9;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-section h4 {
 | 
			
		||||
    margin: 0 0 20px 0;
 | 
			
		||||
    padding-bottom: 10px;
 | 
			
		||||
    border-bottom: 1px solid #e1e5e9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-section p {
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-section label {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-section input[type="text"],
 | 
			
		||||
.wpdd-checkout-section input[type="email"] {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-checkout-section input[type="checkbox"] {
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Thank You Page */
 | 
			
		||||
.wpdd-thank-you {
 | 
			
		||||
    max-width: 600px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-order-details {
 | 
			
		||||
    background: white;
 | 
			
		||||
    padding: 30px;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-order-info {
 | 
			
		||||
    background: #f8f9fa;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    margin: 20px 0;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-download-section {
 | 
			
		||||
    margin: 30px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-download-section h3 {
 | 
			
		||||
    color: #28a745;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Pagination */
 | 
			
		||||
.wpdd-pagination {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin: 40px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-pagination .page-numbers {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    padding: 8px 12px;
 | 
			
		||||
    margin: 0 4px;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    border: 1px solid #dee2e6;
 | 
			
		||||
    color: #495057;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-pagination .page-numbers:hover,
 | 
			
		||||
.wpdd-pagination .page-numbers.current {
 | 
			
		||||
    background: #0073aa;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border-color: #0073aa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Responsive */
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .wpdd-shop-filters .wpdd-filter-form {
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        align-items: stretch;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .wpdd-filter-form input,
 | 
			
		||||
    .wpdd-filter-form select {
 | 
			
		||||
        min-width: auto;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .wpdd-purchases-table {
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .wpdd-purchases-table th,
 | 
			
		||||
    .wpdd-purchases-table td {
 | 
			
		||||
        padding: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .wpdd-checkout-section {
 | 
			
		||||
        padding: 20px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* No products message */
 | 
			
		||||
.wpdd-no-products {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    color: #666;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    margin: 50px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Login required message */
 | 
			
		||||
.wpdd-login-required {
 | 
			
		||||
    background: #fff3cd;
 | 
			
		||||
    color: #856404;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    border: 1px solid #ffeaa7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.wpdd-login-required a {
 | 
			
		||||
    color: #856404;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										411
									
								
								assets/js/admin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										411
									
								
								assets/js/admin.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,411 @@
 | 
			
		||||
jQuery(document).ready(function($) {
 | 
			
		||||
    'use strict';
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Main WPDD Admin Object
 | 
			
		||||
     */
 | 
			
		||||
    window.WPDD_Admin = {
 | 
			
		||||
        
 | 
			
		||||
        init: function() {
 | 
			
		||||
            this.initFileManager();
 | 
			
		||||
            this.initPriceToggle();
 | 
			
		||||
            this.initFormValidation();
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initFileManager: function() {
 | 
			
		||||
            var fileIndex = $('#wpdd-files-list .wpdd-file-item').length;
 | 
			
		||||
            
 | 
			
		||||
            // Add new file
 | 
			
		||||
            $('#wpdd-add-file').on('click', function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                
 | 
			
		||||
                var template = $('#wpdd-file-template').html();
 | 
			
		||||
                template = template.replace(/INDEX/g, fileIndex);
 | 
			
		||||
                
 | 
			
		||||
                var $newFile = $(template);
 | 
			
		||||
                $newFile.attr('data-index', fileIndex);
 | 
			
		||||
                
 | 
			
		||||
                $('#wpdd-files-list').append($newFile);
 | 
			
		||||
                fileIndex++;
 | 
			
		||||
                
 | 
			
		||||
                WPDD_Admin.updateFileIndices();
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Remove file
 | 
			
		||||
            $(document).on('click', '.wpdd-remove-file', function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                
 | 
			
		||||
                if (confirm('Are you sure you want to remove this file?')) {
 | 
			
		||||
                    $(this).closest('.wpdd-file-item').remove();
 | 
			
		||||
                    WPDD_Admin.updateFileIndices();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Upload file
 | 
			
		||||
            $(document).on('click', '.wpdd-upload-file', function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                
 | 
			
		||||
                var $button = $(this);
 | 
			
		||||
                var $container = $button.closest('.wpdd-file-item');
 | 
			
		||||
                var $urlInput = $container.find('.wpdd-file-url-input');
 | 
			
		||||
                var $idInput = $container.find('.wpdd-file-id');
 | 
			
		||||
                var $nameInput = $container.find('input[name*="[name]"]');
 | 
			
		||||
                
 | 
			
		||||
                // Create file input element
 | 
			
		||||
                var $fileInput = $('<input type="file" style="display:none;" />');
 | 
			
		||||
                
 | 
			
		||||
                $fileInput.on('change', function(event) {
 | 
			
		||||
                    var file = event.target.files[0];
 | 
			
		||||
                    if (!file) return;
 | 
			
		||||
                    
 | 
			
		||||
                    // Show loading state
 | 
			
		||||
                    $button.prop('disabled', true).text('Uploading...');
 | 
			
		||||
                    
 | 
			
		||||
                    var formData = new FormData();
 | 
			
		||||
                    formData.append('action', 'wpdd_upload_protected_file');
 | 
			
		||||
                    formData.append('file', file);
 | 
			
		||||
                    formData.append('nonce', wpdd_admin_nonce);
 | 
			
		||||
                    
 | 
			
		||||
                    $.ajax({
 | 
			
		||||
                        url: ajaxurl,
 | 
			
		||||
                        type: 'POST',
 | 
			
		||||
                        data: formData,
 | 
			
		||||
                        processData: false,
 | 
			
		||||
                        contentType: false,
 | 
			
		||||
                        success: function(response) {
 | 
			
		||||
                            if (response.success) {
 | 
			
		||||
                                $urlInput.val(response.data.protected_url);
 | 
			
		||||
                                $idInput.val(response.data.file_id);
 | 
			
		||||
                                
 | 
			
		||||
                                if (!$nameInput.val()) {
 | 
			
		||||
                                    $nameInput.val(file.name);
 | 
			
		||||
                                }
 | 
			
		||||
                                
 | 
			
		||||
                                WPDD_Admin.showAdminNotice('File uploaded to protected directory', 'success');
 | 
			
		||||
                            } else {
 | 
			
		||||
                                WPDD_Admin.showAdminNotice(response.data || 'Upload failed', 'error');
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            $button.prop('disabled', false).text('Upload File');
 | 
			
		||||
                        },
 | 
			
		||||
                        error: function() {
 | 
			
		||||
                            WPDD_Admin.showAdminNotice('Upload failed', 'error');
 | 
			
		||||
                            $button.prop('disabled', false).text('Upload File');
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    
 | 
			
		||||
                    // Clean up
 | 
			
		||||
                    $fileInput.remove();
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                // Trigger file selection
 | 
			
		||||
                $('body').append($fileInput);
 | 
			
		||||
                $fileInput.trigger('click');
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Make files sortable
 | 
			
		||||
            if ($.fn.sortable) {
 | 
			
		||||
                $('#wpdd-files-list').sortable({
 | 
			
		||||
                    handle: '.wpdd-file-handle',
 | 
			
		||||
                    placeholder: 'wpdd-file-placeholder',
 | 
			
		||||
                    update: function() {
 | 
			
		||||
                        WPDD_Admin.updateFileIndices();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        moveToProtectedDirectory: function(attachmentId, $container) {
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                url: ajaxurl,
 | 
			
		||||
                type: 'POST',
 | 
			
		||||
                data: {
 | 
			
		||||
                    action: 'wpdd_move_to_protected',
 | 
			
		||||
                    attachment_id: attachmentId,
 | 
			
		||||
                    nonce: wpdd_admin_nonce
 | 
			
		||||
                },
 | 
			
		||||
                success: function(response) {
 | 
			
		||||
                    if (response.success && response.data.protected_url) {
 | 
			
		||||
                        $container.find('.wpdd-file-url-input').val(response.data.protected_url);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        updateFileIndices: function() {
 | 
			
		||||
            $('#wpdd-files-list .wpdd-file-item').each(function(index) {
 | 
			
		||||
                var $item = $(this);
 | 
			
		||||
                $item.attr('data-index', index);
 | 
			
		||||
                
 | 
			
		||||
                // Update input names
 | 
			
		||||
                $item.find('input').each(function() {
 | 
			
		||||
                    var name = $(this).attr('name');
 | 
			
		||||
                    if (name) {
 | 
			
		||||
                        name = name.replace(/\[\d+\]/, '[' + index + ']');
 | 
			
		||||
                        $(this).attr('name', name);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initPriceToggle: function() {
 | 
			
		||||
            $('#wpdd_is_free').on('change', function() {
 | 
			
		||||
                var $priceFields = $('.wpdd-price-field');
 | 
			
		||||
                if ($(this).is(':checked')) {
 | 
			
		||||
                    $priceFields.slideUp();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $priceFields.slideDown();
 | 
			
		||||
                }
 | 
			
		||||
            }).trigger('change');
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initFormValidation: function() {
 | 
			
		||||
            // Validate PayPal settings
 | 
			
		||||
            $('input[name="wpdd_paypal_client_id"], input[name="wpdd_paypal_secret"]').on('blur', function() {
 | 
			
		||||
                var $field = $(this);
 | 
			
		||||
                var value = $field.val().trim();
 | 
			
		||||
                
 | 
			
		||||
                if (value && value.length < 10) {
 | 
			
		||||
                    $field.addClass('error');
 | 
			
		||||
                    WPDD_Admin.showAdminNotice('Invalid PayPal credential format', 'error');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $field.removeClass('error');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Validate email fields
 | 
			
		||||
            $('input[type="email"]').on('blur', function() {
 | 
			
		||||
                var $field = $(this);
 | 
			
		||||
                var value = $field.val().trim();
 | 
			
		||||
                var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
			
		||||
                
 | 
			
		||||
                if (value && !emailRegex.test(value)) {
 | 
			
		||||
                    $field.addClass('error');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $field.removeClass('error');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Validate number fields
 | 
			
		||||
            $('input[type="number"]').on('input', function() {
 | 
			
		||||
                var $field = $(this);
 | 
			
		||||
                var value = parseFloat($field.val());
 | 
			
		||||
                var min = parseFloat($field.attr('min'));
 | 
			
		||||
                var max = parseFloat($field.attr('max'));
 | 
			
		||||
                
 | 
			
		||||
                if (isNaN(value) || (min !== undefined && value < min) || (max !== undefined && value > max)) {
 | 
			
		||||
                    $field.addClass('error');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $field.removeClass('error');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        showAdminNotice: function(message, type) {
 | 
			
		||||
            type = type || 'info';
 | 
			
		||||
            
 | 
			
		||||
            var notice = $('<div class="notice notice-' + type + ' is-dismissible">' +
 | 
			
		||||
                          '<p>' + message + '</p>' +
 | 
			
		||||
                          '<button type="button" class="notice-dismiss"></button>' +
 | 
			
		||||
                          '</div>');
 | 
			
		||||
            
 | 
			
		||||
            $('.wrap h1').first().after(notice);
 | 
			
		||||
            
 | 
			
		||||
            // Handle dismiss
 | 
			
		||||
            notice.find('.notice-dismiss').on('click', function() {
 | 
			
		||||
                notice.fadeOut(function() {
 | 
			
		||||
                    notice.remove();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Auto-dismiss after 5 seconds for non-error notices
 | 
			
		||||
            if (type !== 'error') {
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    notice.fadeOut(function() {
 | 
			
		||||
                        notice.remove();
 | 
			
		||||
                    });
 | 
			
		||||
                }, 5000);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initReportsCharts: function() {
 | 
			
		||||
            // Placeholder for future chart implementation
 | 
			
		||||
            // Could integrate Chart.js or similar library
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initBulkActions: function() {
 | 
			
		||||
            // Handle bulk actions for orders, customers, etc.
 | 
			
		||||
            $('.bulkactions select').on('change', function() {
 | 
			
		||||
                var action = $(this).val();
 | 
			
		||||
                var $button = $(this).siblings('input[type="submit"]');
 | 
			
		||||
                
 | 
			
		||||
                if (action) {
 | 
			
		||||
                    $button.prop('disabled', false);
 | 
			
		||||
                } else {
 | 
			
		||||
                    $button.prop('disabled', true);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initQuickEdit: function() {
 | 
			
		||||
            // Quick edit functionality for products
 | 
			
		||||
            $('.editinline').on('click', function() {
 | 
			
		||||
                var $row = $(this).closest('tr');
 | 
			
		||||
                var productId = $row.find('.check-column input').val();
 | 
			
		||||
                
 | 
			
		||||
                // Populate quick edit fields with current values
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    var $quickEdit = $('.inline-edit-row');
 | 
			
		||||
                    
 | 
			
		||||
                    // Pre-fill price from the displayed value
 | 
			
		||||
                    var price = $row.find('.column-wpdd_price').text().replace('$', '');
 | 
			
		||||
                    if (price !== 'Free') {
 | 
			
		||||
                        $quickEdit.find('input[name="_wpdd_price"]').val(price);
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                }, 100);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initFilePreview: function() {
 | 
			
		||||
            // Show file preview/details when hovering over file names
 | 
			
		||||
            $(document).on('mouseenter', '.wpdd-file-url-input', function() {
 | 
			
		||||
                var url = $(this).val();
 | 
			
		||||
                if (url) {
 | 
			
		||||
                    var filename = url.split('/').pop();
 | 
			
		||||
                    var extension = filename.split('.').pop().toLowerCase();
 | 
			
		||||
                    var fileType = WPDD_Admin.getFileType(extension);
 | 
			
		||||
                    
 | 
			
		||||
                    $(this).attr('title', 'File: ' + filename + ' (Type: ' + fileType + ')');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        getFileType: function(extension) {
 | 
			
		||||
            var types = {
 | 
			
		||||
                'pdf': 'PDF Document',
 | 
			
		||||
                'doc': 'Word Document',
 | 
			
		||||
                'docx': 'Word Document',
 | 
			
		||||
                'zip': 'Archive',
 | 
			
		||||
                'rar': 'Archive',
 | 
			
		||||
                'jpg': 'Image',
 | 
			
		||||
                'jpeg': 'Image',
 | 
			
		||||
                'png': 'Image',
 | 
			
		||||
                'gif': 'Image',
 | 
			
		||||
                'mp3': 'Audio',
 | 
			
		||||
                'wav': 'Audio',
 | 
			
		||||
                'mp4': 'Video',
 | 
			
		||||
                'avi': 'Video'
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            return types[extension] || 'File';
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initColorPicker: function() {
 | 
			
		||||
            // Initialize color picker for watermark settings
 | 
			
		||||
            if ($.fn.wpColorPicker) {
 | 
			
		||||
                $('.wpdd-color-picker').wpColorPicker();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initTabs: function() {
 | 
			
		||||
            // Settings page tabs
 | 
			
		||||
            $('.wpdd-settings-tabs').on('click', 'a', function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                
 | 
			
		||||
                var $tab = $(this);
 | 
			
		||||
                var target = $tab.attr('href');
 | 
			
		||||
                
 | 
			
		||||
                // Update active tab
 | 
			
		||||
                $tab.siblings().removeClass('nav-tab-active');
 | 
			
		||||
                $tab.addClass('nav-tab-active');
 | 
			
		||||
                
 | 
			
		||||
                // Show/hide content
 | 
			
		||||
                $('.wpdd-settings-content').hide();
 | 
			
		||||
                $(target).show();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Initialize admin functionality
 | 
			
		||||
    WPDD_Admin.init();
 | 
			
		||||
    
 | 
			
		||||
    // Add admin-specific styles
 | 
			
		||||
    $('<style>')
 | 
			
		||||
        .prop('type', 'text/css')
 | 
			
		||||
        .html(`
 | 
			
		||||
            .wpdd-file-item.ui-sortable-helper {
 | 
			
		||||
                box-shadow: 0 4px 8px rgba(0,0,0,0.2);
 | 
			
		||||
                transform: rotate(2deg);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-file-placeholder {
 | 
			
		||||
                border: 2px dashed #0073aa !important;
 | 
			
		||||
                background: transparent !important;
 | 
			
		||||
                height: 80px !important;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            input.error {
 | 
			
		||||
                border-color: #dc3232 !important;
 | 
			
		||||
                box-shadow: 0 0 2px rgba(220, 50, 50, 0.3) !important;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-loading-overlay {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                top: 0;
 | 
			
		||||
                left: 0;
 | 
			
		||||
                width: 100%;
 | 
			
		||||
                height: 100%;
 | 
			
		||||
                background: rgba(255,255,255,0.8);
 | 
			
		||||
                display: flex;
 | 
			
		||||
                align-items: center;
 | 
			
		||||
                justify-content: center;
 | 
			
		||||
                z-index: 9999;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-spinner {
 | 
			
		||||
                width: 40px;
 | 
			
		||||
                height: 40px;
 | 
			
		||||
                border: 4px solid #f3f3f3;
 | 
			
		||||
                border-top: 4px solid #0073aa;
 | 
			
		||||
                border-radius: 50%;
 | 
			
		||||
                animation: spin 1s linear infinite;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @keyframes spin {
 | 
			
		||||
                0% { transform: rotate(0deg); }
 | 
			
		||||
                100% { transform: rotate(360deg); }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-help-tip {
 | 
			
		||||
                color: #666;
 | 
			
		||||
                cursor: help;
 | 
			
		||||
                margin-left: 5px;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-help-tip:hover {
 | 
			
		||||
                color: #0073aa;
 | 
			
		||||
            }
 | 
			
		||||
        `)
 | 
			
		||||
        .appendTo('head');
 | 
			
		||||
    
 | 
			
		||||
    // Initialize additional features based on current page
 | 
			
		||||
    var currentScreen = window.pagenow || '';
 | 
			
		||||
    
 | 
			
		||||
    if (currentScreen === 'wpdd_product') {
 | 
			
		||||
        WPDD_Admin.initQuickEdit();
 | 
			
		||||
        WPDD_Admin.initFilePreview();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (currentScreen.includes('wpdd-settings')) {
 | 
			
		||||
        WPDD_Admin.initColorPicker();
 | 
			
		||||
        WPDD_Admin.initTabs();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (currentScreen.includes('wpdd-reports')) {
 | 
			
		||||
        WPDD_Admin.initReportsCharts();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Global admin features
 | 
			
		||||
    WPDD_Admin.initBulkActions();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										554
									
								
								assets/js/frontend.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										554
									
								
								assets/js/frontend.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,554 @@
 | 
			
		||||
jQuery(document).ready(function($) {
 | 
			
		||||
    'use strict';
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Main WPDD Frontend Object
 | 
			
		||||
     */
 | 
			
		||||
    window.WPDD = {
 | 
			
		||||
        
 | 
			
		||||
        init: function() {
 | 
			
		||||
            this.bindEvents();
 | 
			
		||||
            this.initProductCards();
 | 
			
		||||
            this.initCheckout();
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        bindEvents: function() {
 | 
			
		||||
            // Add to cart buttons
 | 
			
		||||
            $(document).on('click', '.wpdd-add-to-cart', this.addToCart);
 | 
			
		||||
            
 | 
			
		||||
            // Free download buttons
 | 
			
		||||
            $(document).on('click', '.wpdd-free-download', this.processFreeDownload);
 | 
			
		||||
            
 | 
			
		||||
            // Product quickview
 | 
			
		||||
            $(document).on('click', '.wpdd-quickview', this.showQuickview);
 | 
			
		||||
            
 | 
			
		||||
            // Filter form submission
 | 
			
		||||
            $(document).on('submit', '.wpdd-filter-form', this.handleFilters);
 | 
			
		||||
            
 | 
			
		||||
            // Download status check
 | 
			
		||||
            $(document).on('click', '.wpdd-check-download', this.checkDownloadStatus);
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initProductCards: function() {
 | 
			
		||||
            // Add hover effects and animations
 | 
			
		||||
            $('.wpdd-product-card').each(function() {
 | 
			
		||||
                var $card = $(this);
 | 
			
		||||
                var $image = $card.find('.wpdd-product-image img');
 | 
			
		||||
                
 | 
			
		||||
                $card.hover(
 | 
			
		||||
                    function() {
 | 
			
		||||
                        $(this).addClass('hovered');
 | 
			
		||||
                    },
 | 
			
		||||
                    function() {
 | 
			
		||||
                        $(this).removeClass('hovered');
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        initCheckout: function() {
 | 
			
		||||
            // Free product checkout handler
 | 
			
		||||
            $('#wpdd-checkout-form').on('submit', function(e) {
 | 
			
		||||
                var $form = $(this);
 | 
			
		||||
                var isFree = $form.data('product-free') == '1';
 | 
			
		||||
                
 | 
			
		||||
                if (isFree) {
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                    WPDD.processFreeCheckout($form);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Price display toggle based on free checkbox
 | 
			
		||||
            $('#wpdd_is_free').on('change', function() {
 | 
			
		||||
                var $priceFields = $('.wpdd-price-field');
 | 
			
		||||
                if ($(this).is(':checked')) {
 | 
			
		||||
                    $priceFields.hide();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $priceFields.show();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        addToCart: function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            var $button = $(this);
 | 
			
		||||
            var productId = $button.data('product-id');
 | 
			
		||||
            
 | 
			
		||||
            if (!productId) {
 | 
			
		||||
                WPDD.showNotice('Invalid product', 'error');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $button.addClass('loading').prop('disabled', true);
 | 
			
		||||
            
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                url: wpdd_ajax.url,
 | 
			
		||||
                type: 'POST',
 | 
			
		||||
                data: {
 | 
			
		||||
                    action: 'wpdd_add_to_cart',
 | 
			
		||||
                    product_id: productId,
 | 
			
		||||
                    nonce: wpdd_ajax.nonce
 | 
			
		||||
                },
 | 
			
		||||
                success: function(response) {
 | 
			
		||||
                    if (response.success) {
 | 
			
		||||
                        WPDD.showNotice(response.data.message, 'success');
 | 
			
		||||
                        
 | 
			
		||||
                        // Update cart count if element exists
 | 
			
		||||
                        $('.wpdd-cart-count').text(response.data.cart_count);
 | 
			
		||||
                        
 | 
			
		||||
                        // Show checkout button
 | 
			
		||||
                        if (response.data.checkout_url) {
 | 
			
		||||
                            $button.after('<a href="' + response.data.checkout_url + 
 | 
			
		||||
                                         '" class="wpdd-btn wpdd-btn-primary">Checkout</a>');
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        WPDD.showNotice(response.data, 'error');
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                error: function() {
 | 
			
		||||
                    WPDD.showNotice('An error occurred. Please try again.', 'error');
 | 
			
		||||
                },
 | 
			
		||||
                complete: function() {
 | 
			
		||||
                    $button.removeClass('loading').prop('disabled', false);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        processFreeDownload: function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            var $button = $(this);
 | 
			
		||||
            var productId = $button.data('product-id');
 | 
			
		||||
            var $form = $button.closest('form');
 | 
			
		||||
            
 | 
			
		||||
            if (!productId) {
 | 
			
		||||
                WPDD.showNotice('Invalid product', 'error');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            var customerEmail = $form.find('input[name="customer_email"]').val();
 | 
			
		||||
            var customerName = $form.find('input[name="customer_name"]').val();
 | 
			
		||||
            
 | 
			
		||||
            if (!customerEmail && !WPDD.isUserLoggedIn()) {
 | 
			
		||||
                WPDD.showNotice('Please provide your email address', 'error');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $button.addClass('loading').prop('disabled', true);
 | 
			
		||||
            
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                url: wpdd_ajax.url,
 | 
			
		||||
                type: 'POST',
 | 
			
		||||
                data: {
 | 
			
		||||
                    action: 'wpdd_process_free_download',
 | 
			
		||||
                    product_id: productId,
 | 
			
		||||
                    customer_email: customerEmail,
 | 
			
		||||
                    customer_name: customerName,
 | 
			
		||||
                    nonce: wpdd_ajax.nonce
 | 
			
		||||
                },
 | 
			
		||||
                success: function(response) {
 | 
			
		||||
                    if (response.success) {
 | 
			
		||||
                        window.location.href = response.data.redirect_url;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        WPDD.showNotice(response.data, 'error');
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                error: function() {
 | 
			
		||||
                    WPDD.showNotice('An error occurred. Please try again.', 'error');
 | 
			
		||||
                },
 | 
			
		||||
                complete: function() {
 | 
			
		||||
                    $button.removeClass('loading').prop('disabled', false);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        processFreeCheckout: function($form) {
 | 
			
		||||
            var formData = $form.serialize();
 | 
			
		||||
            var $submitBtn = $form.find('button[type="submit"]');
 | 
			
		||||
            var productId = $form.find('input[name="product_id"]').val();
 | 
			
		||||
            
 | 
			
		||||
            $submitBtn.addClass('loading').prop('disabled', true);
 | 
			
		||||
            
 | 
			
		||||
            // Add visual feedback
 | 
			
		||||
            $submitBtn.text('Processing...');
 | 
			
		||||
            
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                url: wpdd_ajax.url,
 | 
			
		||||
                type: 'POST',
 | 
			
		||||
                data: formData + '&action=wpdd_process_free_download&nonce=' + wpdd_ajax.nonce + '&product_id=' + productId,
 | 
			
		||||
                success: function(response) {
 | 
			
		||||
                    if (response.success) {
 | 
			
		||||
                        window.location.href = response.data.redirect_url;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        WPDD.showNotice(response.data || 'Failed to process download', 'error');
 | 
			
		||||
                        $submitBtn.text('Get Free Download');
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                error: function(xhr, status, error) {
 | 
			
		||||
                    console.error('AJAX Error:', status, error);
 | 
			
		||||
                    WPDD.showNotice('An error occurred. Please try again.', 'error');
 | 
			
		||||
                    $submitBtn.text('Get Free Download');
 | 
			
		||||
                },
 | 
			
		||||
                complete: function() {
 | 
			
		||||
                    $submitBtn.removeClass('loading').prop('disabled', false);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        showQuickview: function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            var productId = $(this).data('product-id');
 | 
			
		||||
            
 | 
			
		||||
            if (!productId) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                url: wpdd_ajax.url,
 | 
			
		||||
                type: 'POST',
 | 
			
		||||
                data: {
 | 
			
		||||
                    action: 'wpdd_get_product_details',
 | 
			
		||||
                    product_id: productId,
 | 
			
		||||
                    nonce: wpdd_ajax.nonce
 | 
			
		||||
                },
 | 
			
		||||
                success: function(response) {
 | 
			
		||||
                    if (response.success) {
 | 
			
		||||
                        WPDD.displayQuickview(response.data);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        WPDD.showNotice('Unable to load product details', 'error');
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                error: function() {
 | 
			
		||||
                    WPDD.showNotice('An error occurred. Please try again.', 'error');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        displayQuickview: function(product) {
 | 
			
		||||
            var modal = $('<div class="wpdd-modal"><div class="wpdd-modal-content"></div></div>');
 | 
			
		||||
            var content = '';
 | 
			
		||||
            
 | 
			
		||||
            content += '<div class="wpdd-modal-header">';
 | 
			
		||||
            content += '<h2>' + product.title + '</h2>';
 | 
			
		||||
            content += '<button class="wpdd-modal-close">×</button>';
 | 
			
		||||
            content += '</div>';
 | 
			
		||||
            
 | 
			
		||||
            content += '<div class="wpdd-modal-body">';
 | 
			
		||||
            if (product.thumbnail) {
 | 
			
		||||
                content += '<img src="' + product.thumbnail + '" alt="' + product.title + '" class="wpdd-modal-image">';
 | 
			
		||||
            }
 | 
			
		||||
            content += '<div class="wpdd-modal-details">';
 | 
			
		||||
            content += '<p class="wpdd-modal-price">';
 | 
			
		||||
            if (product.is_free) {
 | 
			
		||||
                content += '<span class="wpdd-price-free">Free</span>';
 | 
			
		||||
            } else {
 | 
			
		||||
                content += '<span class="wpdd-price-regular">$' + product.final_price + '</span>';
 | 
			
		||||
            }
 | 
			
		||||
            content += '</p>';
 | 
			
		||||
            content += '<p class="wpdd-modal-creator">by ' + product.creator.name + '</p>';
 | 
			
		||||
            content += '<div class="wpdd-modal-description">' + product.description + '</div>';
 | 
			
		||||
            content += '<p><strong>Files:</strong> ' + product.files_count + '</p>';
 | 
			
		||||
            content += '<p><strong>Download Limit:</strong> ' + product.download_limit + '</p>';
 | 
			
		||||
            content += '<p><strong>Expires:</strong> ' + product.download_expiry + '</p>';
 | 
			
		||||
            content += '</div>';
 | 
			
		||||
            content += '</div>';
 | 
			
		||||
            
 | 
			
		||||
            content += '<div class="wpdd-modal-footer">';
 | 
			
		||||
            content += '<a href="' + window.location.origin + '?product_id=' + product.id + 
 | 
			
		||||
                       '" class="wpdd-btn wpdd-btn-primary">View Details</a>';
 | 
			
		||||
            content += '</div>';
 | 
			
		||||
            
 | 
			
		||||
            modal.find('.wpdd-modal-content').html(content);
 | 
			
		||||
            $('body').append(modal);
 | 
			
		||||
            
 | 
			
		||||
            modal.addClass('active');
 | 
			
		||||
            
 | 
			
		||||
            // Close modal events
 | 
			
		||||
            modal.on('click', '.wpdd-modal-close, .wpdd-modal', function(e) {
 | 
			
		||||
                if (e.target === this) {
 | 
			
		||||
                    modal.removeClass('active');
 | 
			
		||||
                    setTimeout(function() {
 | 
			
		||||
                        modal.remove();
 | 
			
		||||
                    }, 300);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        handleFilters: function(e) {
 | 
			
		||||
            // Let the form submit naturally for now
 | 
			
		||||
            // Could be enhanced with AJAX filtering
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        checkDownloadStatus: function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            var productId = $(this).data('product-id');
 | 
			
		||||
            var $button = $(this);
 | 
			
		||||
            
 | 
			
		||||
            if (!WPDD.isUserLoggedIn()) {
 | 
			
		||||
                WPDD.showNotice('Please login to check download status', 'error');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $button.addClass('loading');
 | 
			
		||||
            
 | 
			
		||||
            $.ajax({
 | 
			
		||||
                url: wpdd_ajax.url,
 | 
			
		||||
                type: 'POST',
 | 
			
		||||
                data: {
 | 
			
		||||
                    action: 'wpdd_check_download_status',
 | 
			
		||||
                    product_id: productId,
 | 
			
		||||
                    nonce: wpdd_ajax.nonce
 | 
			
		||||
                },
 | 
			
		||||
                success: function(response) {
 | 
			
		||||
                    if (response.success) {
 | 
			
		||||
                        var data = response.data;
 | 
			
		||||
                        if (data.can_download && data.download_url) {
 | 
			
		||||
                            window.location.href = data.download_url;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            WPDD.showNotice(data.message, data.can_download ? 'success' : 'error');
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        WPDD.showNotice(response.data, 'error');
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                error: function() {
 | 
			
		||||
                    WPDD.showNotice('An error occurred. Please try again.', 'error');
 | 
			
		||||
                },
 | 
			
		||||
                complete: function() {
 | 
			
		||||
                    $button.removeClass('loading');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        showNotice: function(message, type) {
 | 
			
		||||
            type = type || 'info';
 | 
			
		||||
            
 | 
			
		||||
            var notice = $('<div class="wpdd-notice wpdd-notice-' + type + '">' + 
 | 
			
		||||
                          '<p>' + message + '</p>' +
 | 
			
		||||
                          '<button class="wpdd-notice-dismiss">×</button>' +
 | 
			
		||||
                          '</div>');
 | 
			
		||||
            
 | 
			
		||||
            $('body').prepend(notice);
 | 
			
		||||
            
 | 
			
		||||
            notice.addClass('active');
 | 
			
		||||
            
 | 
			
		||||
            // Auto-dismiss after 5 seconds
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                notice.removeClass('active');
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    notice.remove();
 | 
			
		||||
                }, 300);
 | 
			
		||||
            }, 5000);
 | 
			
		||||
            
 | 
			
		||||
            // Manual dismiss
 | 
			
		||||
            notice.find('.wpdd-notice-dismiss').on('click', function() {
 | 
			
		||||
                notice.removeClass('active');
 | 
			
		||||
                setTimeout(function() {
 | 
			
		||||
                    notice.remove();
 | 
			
		||||
                }, 300);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        isUserLoggedIn: function() {
 | 
			
		||||
            return $('body').hasClass('logged-in');
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        formatPrice: function(price) {
 | 
			
		||||
            return '$' + parseFloat(price).toFixed(2);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Initialize frontend functionality
 | 
			
		||||
    WPDD.init();
 | 
			
		||||
    
 | 
			
		||||
    // Add modal and notice styles if not already present
 | 
			
		||||
    if (!$('#wpdd-dynamic-styles').length) {
 | 
			
		||||
        var styles = `
 | 
			
		||||
            <style id="wpdd-dynamic-styles">
 | 
			
		||||
                .wpdd-modal {
 | 
			
		||||
                    display: flex;
 | 
			
		||||
                    position: fixed;
 | 
			
		||||
                    top: 0;
 | 
			
		||||
                    left: 0;
 | 
			
		||||
                    width: 100%;
 | 
			
		||||
                    height: 100%;
 | 
			
		||||
                    background: rgba(0,0,0,0.8);
 | 
			
		||||
                    z-index: 999999;
 | 
			
		||||
                    opacity: 0;
 | 
			
		||||
                    visibility: hidden;
 | 
			
		||||
                    transition: all 0.3s;
 | 
			
		||||
                    align-items: center;
 | 
			
		||||
                    justify-content: center;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal.active {
 | 
			
		||||
                    opacity: 1;
 | 
			
		||||
                    visibility: visible;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal-content {
 | 
			
		||||
                    background: white;
 | 
			
		||||
                    max-width: 600px;
 | 
			
		||||
                    width: 90%;
 | 
			
		||||
                    max-height: 80vh;
 | 
			
		||||
                    border-radius: 8px;
 | 
			
		||||
                    overflow: hidden;
 | 
			
		||||
                    transform: scale(0.8);
 | 
			
		||||
                    transition: transform 0.3s;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal.active .wpdd-modal-content {
 | 
			
		||||
                    transform: scale(1);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal-header {
 | 
			
		||||
                    display: flex;
 | 
			
		||||
                    justify-content: space-between;
 | 
			
		||||
                    align-items: center;
 | 
			
		||||
                    padding: 20px;
 | 
			
		||||
                    border-bottom: 1px solid #eee;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal-header h2 {
 | 
			
		||||
                    margin: 0;
 | 
			
		||||
                    font-size: 20px;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal-close {
 | 
			
		||||
                    background: none;
 | 
			
		||||
                    border: none;
 | 
			
		||||
                    font-size: 24px;
 | 
			
		||||
                    cursor: pointer;
 | 
			
		||||
                    color: #666;
 | 
			
		||||
                    padding: 0;
 | 
			
		||||
                    width: 30px;
 | 
			
		||||
                    height: 30px;
 | 
			
		||||
                    display: flex;
 | 
			
		||||
                    align-items: center;
 | 
			
		||||
                    justify-content: center;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal-body {
 | 
			
		||||
                    padding: 20px;
 | 
			
		||||
                    max-height: 50vh;
 | 
			
		||||
                    overflow-y: auto;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal-image {
 | 
			
		||||
                    width: 100%;
 | 
			
		||||
                    max-width: 200px;
 | 
			
		||||
                    height: auto;
 | 
			
		||||
                    float: left;
 | 
			
		||||
                    margin: 0 20px 20px 0;
 | 
			
		||||
                    border-radius: 4px;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-modal-footer {
 | 
			
		||||
                    padding: 20px;
 | 
			
		||||
                    border-top: 1px solid #eee;
 | 
			
		||||
                    text-align: right;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-notice {
 | 
			
		||||
                    position: fixed;
 | 
			
		||||
                    top: 32px;
 | 
			
		||||
                    right: 20px;
 | 
			
		||||
                    max-width: 400px;
 | 
			
		||||
                    z-index: 999999;
 | 
			
		||||
                    opacity: 0;
 | 
			
		||||
                    transform: translateX(100%);
 | 
			
		||||
                    transition: all 0.3s;
 | 
			
		||||
                    border-radius: 4px;
 | 
			
		||||
                    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-notice.active {
 | 
			
		||||
                    opacity: 1;
 | 
			
		||||
                    transform: translateX(0);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-notice p {
 | 
			
		||||
                    margin: 0;
 | 
			
		||||
                    padding: 15px 40px 15px 15px;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-notice-dismiss {
 | 
			
		||||
                    position: absolute;
 | 
			
		||||
                    top: 5px;
 | 
			
		||||
                    right: 10px;
 | 
			
		||||
                    background: none;
 | 
			
		||||
                    border: none;
 | 
			
		||||
                    font-size: 18px;
 | 
			
		||||
                    cursor: pointer;
 | 
			
		||||
                    color: inherit;
 | 
			
		||||
                    opacity: 0.7;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-notice-success {
 | 
			
		||||
                    background: #d4edda;
 | 
			
		||||
                    color: #155724;
 | 
			
		||||
                    border: 1px solid #c3e6cb;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-notice-error {
 | 
			
		||||
                    background: #f8d7da;
 | 
			
		||||
                    color: #721c24;
 | 
			
		||||
                    border: 1px solid #f5c6cb;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .wpdd-notice-info {
 | 
			
		||||
                    background: #d1ecf1;
 | 
			
		||||
                    color: #0c5460;
 | 
			
		||||
                    border: 1px solid #bee5eb;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .loading {
 | 
			
		||||
                    position: relative;
 | 
			
		||||
                    pointer-events: none;
 | 
			
		||||
                    opacity: 0.7;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                .loading::after {
 | 
			
		||||
                    content: '';
 | 
			
		||||
                    position: absolute;
 | 
			
		||||
                    top: 50%;
 | 
			
		||||
                    left: 50%;
 | 
			
		||||
                    width: 16px;
 | 
			
		||||
                    height: 16px;
 | 
			
		||||
                    margin: -8px 0 0 -8px;
 | 
			
		||||
                    border: 2px solid transparent;
 | 
			
		||||
                    border-top: 2px solid currentColor;
 | 
			
		||||
                    border-radius: 50%;
 | 
			
		||||
                    animation: spin 1s linear infinite;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                @keyframes spin {
 | 
			
		||||
                    0% { transform: rotate(0deg); }
 | 
			
		||||
                    100% { transform: rotate(360deg); }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                @media (max-width: 600px) {
 | 
			
		||||
                    .wpdd-modal-content {
 | 
			
		||||
                        width: 95%;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    .wpdd-modal-image {
 | 
			
		||||
                        float: none;
 | 
			
		||||
                        margin: 0 0 20px 0;
 | 
			
		||||
                        max-width: 100%;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    .wpdd-notice {
 | 
			
		||||
                        right: 10px;
 | 
			
		||||
                        left: 10px;
 | 
			
		||||
                        max-width: none;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            </style>
 | 
			
		||||
        `;
 | 
			
		||||
        $('head').append(styles);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										346
									
								
								assets/js/paypal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								assets/js/paypal.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,346 @@
 | 
			
		||||
jQuery(document).ready(function($) {
 | 
			
		||||
    'use strict';
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * PayPal Integration Object
 | 
			
		||||
     */
 | 
			
		||||
    window.WPDD_PayPal = {
 | 
			
		||||
        
 | 
			
		||||
        init: function() {
 | 
			
		||||
            if (typeof paypal !== 'undefined') {
 | 
			
		||||
                this.renderPayPalButton();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        renderPayPalButton: function() {
 | 
			
		||||
            var $container = $('#wpdd-paypal-button');
 | 
			
		||||
            
 | 
			
		||||
            if (!$container.length) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            var $form = $container.closest('form');
 | 
			
		||||
            
 | 
			
		||||
            paypal.Buttons({
 | 
			
		||||
                createOrder: function(data, actions) {
 | 
			
		||||
                    return WPDD_PayPal.createOrder($form);
 | 
			
		||||
                },
 | 
			
		||||
                
 | 
			
		||||
                onApprove: function(data, actions) {
 | 
			
		||||
                    return WPDD_PayPal.captureOrder(data.orderID);
 | 
			
		||||
                },
 | 
			
		||||
                
 | 
			
		||||
                onError: function(err) {
 | 
			
		||||
                    console.error('PayPal Error:', err);
 | 
			
		||||
                    WPDD_PayPal.showError('Payment processing failed. Please try again.');
 | 
			
		||||
                },
 | 
			
		||||
                
 | 
			
		||||
                onCancel: function(data) {
 | 
			
		||||
                    WPDD_PayPal.showNotice('Payment was cancelled.', 'info');
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
            }).render('#wpdd-paypal-button');
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        createOrder: function($form) {
 | 
			
		||||
            return new Promise(function(resolve, reject) {
 | 
			
		||||
                var formData = $form.serialize();
 | 
			
		||||
                
 | 
			
		||||
                $.ajax({
 | 
			
		||||
                    url: wpdd_paypal.ajax_url,
 | 
			
		||||
                    type: 'POST',
 | 
			
		||||
                    data: formData + '&action=wpdd_create_paypal_order&nonce=' + wpdd_paypal.nonce,
 | 
			
		||||
                    success: function(response) {
 | 
			
		||||
                        if (response.success) {
 | 
			
		||||
                            resolve(response.data.orderID);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            reject(new Error(response.data || 'Failed to create PayPal order'));
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    error: function(xhr, status, error) {
 | 
			
		||||
                        reject(new Error('Network error: ' + error));
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        captureOrder: function(orderID) {
 | 
			
		||||
            return new Promise(function(resolve, reject) {
 | 
			
		||||
                $.ajax({
 | 
			
		||||
                    url: wpdd_paypal.ajax_url,
 | 
			
		||||
                    type: 'POST',
 | 
			
		||||
                    data: {
 | 
			
		||||
                        action: 'wpdd_capture_paypal_order',
 | 
			
		||||
                        orderID: orderID,
 | 
			
		||||
                        nonce: wpdd_paypal.nonce
 | 
			
		||||
                    },
 | 
			
		||||
                    success: function(response) {
 | 
			
		||||
                        if (response.success) {
 | 
			
		||||
                            // Redirect to thank you page
 | 
			
		||||
                            window.location.href = response.data.redirect_url;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            reject(new Error(response.data || 'Failed to capture payment'));
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    error: function(xhr, status, error) {
 | 
			
		||||
                        reject(new Error('Network error: ' + error));
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        showError: function(message) {
 | 
			
		||||
            this.showNotice(message, 'error');
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        showNotice: function(message, type) {
 | 
			
		||||
            type = type || 'info';
 | 
			
		||||
            
 | 
			
		||||
            // Remove existing notices
 | 
			
		||||
            $('.wpdd-paypal-notice').remove();
 | 
			
		||||
            
 | 
			
		||||
            var notice = $('<div class="wpdd-paypal-notice wpdd-notice-' + type + '">' +
 | 
			
		||||
                          '<p>' + message + '</p>' +
 | 
			
		||||
                          '<button class="wpdd-notice-dismiss">×</button>' +
 | 
			
		||||
                          '</div>');
 | 
			
		||||
            
 | 
			
		||||
            $('#wpdd-paypal-button').before(notice);
 | 
			
		||||
            
 | 
			
		||||
            // Auto-dismiss after 5 seconds
 | 
			
		||||
            setTimeout(function() {
 | 
			
		||||
                notice.fadeOut(function() {
 | 
			
		||||
                    notice.remove();
 | 
			
		||||
                });
 | 
			
		||||
            }, 5000);
 | 
			
		||||
            
 | 
			
		||||
            // Manual dismiss
 | 
			
		||||
            notice.find('.wpdd-notice-dismiss').on('click', function() {
 | 
			
		||||
                notice.fadeOut(function() {
 | 
			
		||||
                    notice.remove();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        validateForm: function($form) {
 | 
			
		||||
            var isValid = true;
 | 
			
		||||
            var errors = [];
 | 
			
		||||
            
 | 
			
		||||
            // Check required fields
 | 
			
		||||
            $form.find('input[required]').each(function() {
 | 
			
		||||
                var $field = $(this);
 | 
			
		||||
                var value = $field.val().trim();
 | 
			
		||||
                var fieldName = $field.attr('name') || $field.attr('id');
 | 
			
		||||
                
 | 
			
		||||
                if (!value) {
 | 
			
		||||
                    isValid = false;
 | 
			
		||||
                    errors.push(fieldName + ' is required');
 | 
			
		||||
                    $field.addClass('error');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $field.removeClass('error');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            // Validate email format
 | 
			
		||||
            $form.find('input[type="email"]').each(function() {
 | 
			
		||||
                var $field = $(this);
 | 
			
		||||
                var value = $field.val().trim();
 | 
			
		||||
                var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
			
		||||
                
 | 
			
		||||
                if (value && !emailRegex.test(value)) {
 | 
			
		||||
                    isValid = false;
 | 
			
		||||
                    errors.push('Please enter a valid email address');
 | 
			
		||||
                    $field.addClass('error');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            if (!isValid) {
 | 
			
		||||
                this.showError(errors.join('<br>'));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return isValid;
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        showLoadingState: function() {
 | 
			
		||||
            $('#wpdd-paypal-button').addClass('wpdd-loading');
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        hideLoadingState: function() {
 | 
			
		||||
            $('#wpdd-paypal-button').removeClass('wpdd-loading');
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        disableForm: function($form) {
 | 
			
		||||
            $form.find('input, select, button').prop('disabled', true);
 | 
			
		||||
        },
 | 
			
		||||
        
 | 
			
		||||
        enableForm: function($form) {
 | 
			
		||||
            $form.find('input, select, button').prop('disabled', false);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Initialize PayPal integration after object definition
 | 
			
		||||
    WPDD_PayPal.init();
 | 
			
		||||
    
 | 
			
		||||
    // Add PayPal-specific styles
 | 
			
		||||
    $('<style>')
 | 
			
		||||
        .prop('type', 'text/css')
 | 
			
		||||
        .html(`
 | 
			
		||||
            #wpdd-paypal-button {
 | 
			
		||||
                margin: 20px 0;
 | 
			
		||||
                min-height: 45px;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-paypal-notice {
 | 
			
		||||
                padding: 12px 16px;
 | 
			
		||||
                margin-bottom: 15px;
 | 
			
		||||
                border-radius: 4px;
 | 
			
		||||
                position: relative;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-notice-success {
 | 
			
		||||
                background: #d4edda;
 | 
			
		||||
                color: #155724;
 | 
			
		||||
                border: 1px solid #c3e6cb;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-notice-error {
 | 
			
		||||
                background: #f8d7da;
 | 
			
		||||
                color: #721c24;
 | 
			
		||||
                border: 1px solid #f5c6cb;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-notice-info {
 | 
			
		||||
                background: #d1ecf1;
 | 
			
		||||
                color: #0c5460;
 | 
			
		||||
                border: 1px solid #bee5eb;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-notice-dismiss {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                top: 8px;
 | 
			
		||||
                right: 12px;
 | 
			
		||||
                background: none;
 | 
			
		||||
                border: none;
 | 
			
		||||
                font-size: 16px;
 | 
			
		||||
                cursor: pointer;
 | 
			
		||||
                color: inherit;
 | 
			
		||||
                opacity: 0.7;
 | 
			
		||||
                padding: 0;
 | 
			
		||||
                line-height: 1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-notice-dismiss:hover {
 | 
			
		||||
                opacity: 1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-loading {
 | 
			
		||||
                position: relative;
 | 
			
		||||
                opacity: 0.6;
 | 
			
		||||
                pointer-events: none;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-loading::after {
 | 
			
		||||
                content: '';
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                top: 50%;
 | 
			
		||||
                left: 50%;
 | 
			
		||||
                width: 20px;
 | 
			
		||||
                height: 20px;
 | 
			
		||||
                margin: -10px 0 0 -10px;
 | 
			
		||||
                border: 2px solid #ccc;
 | 
			
		||||
                border-top: 2px solid #0073aa;
 | 
			
		||||
                border-radius: 50%;
 | 
			
		||||
                animation: wpdd-paypal-spin 1s linear infinite;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @keyframes wpdd-paypal-spin {
 | 
			
		||||
                0% { transform: rotate(0deg); }
 | 
			
		||||
                100% { transform: rotate(360deg); }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            input.error {
 | 
			
		||||
                border-color: #dc3545 !important;
 | 
			
		||||
                box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-checkout-section {
 | 
			
		||||
                transition: opacity 0.3s;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-checkout-section.disabled {
 | 
			
		||||
                opacity: 0.5;
 | 
			
		||||
                pointer-events: none;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            /* PayPal button container responsive */
 | 
			
		||||
            @media (max-width: 400px) {
 | 
			
		||||
                #wpdd-paypal-button {
 | 
			
		||||
                    min-height: 40px;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            /* Form validation styling */
 | 
			
		||||
            .wpdd-form-row {
 | 
			
		||||
                margin-bottom: 15px;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-form-row.has-error input {
 | 
			
		||||
                border-color: #dc3545;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-field-error {
 | 
			
		||||
                color: #dc3545;
 | 
			
		||||
                font-size: 12px;
 | 
			
		||||
                margin-top: 5px;
 | 
			
		||||
                display: none;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            .wpdd-form-row.has-error .wpdd-field-error {
 | 
			
		||||
                display: block;
 | 
			
		||||
            }
 | 
			
		||||
        `)
 | 
			
		||||
        .appendTo('head');
 | 
			
		||||
    
 | 
			
		||||
    // Form validation enhancement
 | 
			
		||||
    $(document).on('blur', '#wpdd-checkout-form input[required]', function() {
 | 
			
		||||
        var $field = $(this);
 | 
			
		||||
        var $row = $field.closest('.wpdd-form-row');
 | 
			
		||||
        var value = $field.val().trim();
 | 
			
		||||
        
 | 
			
		||||
        if (!value) {
 | 
			
		||||
            $row.addClass('has-error');
 | 
			
		||||
        } else {
 | 
			
		||||
            $row.removeClass('has-error');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Real-time email validation
 | 
			
		||||
    $(document).on('input', '#wpdd-checkout-form input[type="email"]', function() {
 | 
			
		||||
        var $field = $(this);
 | 
			
		||||
        var $row = $field.closest('.wpdd-form-row');
 | 
			
		||||
        var value = $field.val().trim();
 | 
			
		||||
        var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
			
		||||
        
 | 
			
		||||
        if (value && !emailRegex.test(value)) {
 | 
			
		||||
            $row.addClass('has-error');
 | 
			
		||||
        } else {
 | 
			
		||||
            $row.removeClass('has-error');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Prevent double-submission
 | 
			
		||||
    var formSubmitting = false;
 | 
			
		||||
    
 | 
			
		||||
    $(document).on('submit', '#wpdd-checkout-form', function(e) {
 | 
			
		||||
        if (formSubmitting) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        formSubmitting = true;
 | 
			
		||||
        
 | 
			
		||||
        // Re-enable after 5 seconds as failsafe
 | 
			
		||||
        setTimeout(function() {
 | 
			
		||||
            formSubmitting = false;
 | 
			
		||||
        }, 5000);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										491
									
								
								includes/class-wpdd-ajax.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										491
									
								
								includes/class-wpdd-ajax.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,491 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Ajax {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('wp_ajax_wpdd_process_free_download', array(__CLASS__, 'process_free_download'));
 | 
			
		||||
        add_action('wp_ajax_nopriv_wpdd_process_free_download', array(__CLASS__, 'process_free_download'));
 | 
			
		||||
        
 | 
			
		||||
        add_action('wp_ajax_wpdd_add_to_cart', array(__CLASS__, 'add_to_cart'));
 | 
			
		||||
        add_action('wp_ajax_nopriv_wpdd_add_to_cart', array(__CLASS__, 'add_to_cart'));
 | 
			
		||||
        
 | 
			
		||||
        add_action('wp_ajax_wpdd_get_product_details', array(__CLASS__, 'get_product_details'));
 | 
			
		||||
        add_action('wp_ajax_nopriv_wpdd_get_product_details', array(__CLASS__, 'get_product_details'));
 | 
			
		||||
        
 | 
			
		||||
        add_action('wp_ajax_wpdd_check_download_status', array(__CLASS__, 'check_download_status'));
 | 
			
		||||
        add_action('wp_ajax_nopriv_wpdd_check_download_status', array(__CLASS__, 'check_download_status'));
 | 
			
		||||
        
 | 
			
		||||
        add_action('wp_ajax_wpdd_move_to_protected', array(__CLASS__, 'move_to_protected'));
 | 
			
		||||
        add_action('wp_ajax_wpdd_upload_protected_file', array(__CLASS__, 'upload_protected_file'));
 | 
			
		||||
        
 | 
			
		||||
        // Customer management actions
 | 
			
		||||
        add_action('wp_ajax_wpdd_change_password', array(__CLASS__, 'change_password'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function process_free_download() {
 | 
			
		||||
        check_ajax_referer('wpdd-ajax-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        if (!$product_id) {
 | 
			
		||||
            wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($product_id);
 | 
			
		||||
        if (!$product || $product->post_type !== 'wpdd_product') {
 | 
			
		||||
            wp_send_json_error(__('Product not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // IMPORTANT: Always check the actual database values, never trust client input
 | 
			
		||||
        $is_free = get_post_meta($product_id, '_wpdd_is_free', true);
 | 
			
		||||
        $price = get_post_meta($product_id, '_wpdd_price', true);
 | 
			
		||||
        $sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
 | 
			
		||||
        
 | 
			
		||||
        // Calculate actual price
 | 
			
		||||
        $actual_price = ($sale_price && $sale_price < $price) ? $sale_price : $price;
 | 
			
		||||
        
 | 
			
		||||
        // Security check: Verify this is actually a free product
 | 
			
		||||
        if (!$is_free && $actual_price > 0) {
 | 
			
		||||
            // Log potential security breach attempt
 | 
			
		||||
            error_log('WPDD Security: Attempted to download paid product ' . $product_id . ' as free from IP: ' . $_SERVER['REMOTE_ADDR']);
 | 
			
		||||
            wp_send_json_error(__('This product is not available for free download.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $customer_data = array(
 | 
			
		||||
            'email' => sanitize_email($_POST['customer_email'] ?? ''),
 | 
			
		||||
            'name' => sanitize_text_field($_POST['customer_name'] ?? '')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (!is_user_logged_in() && empty($customer_data['email'])) {
 | 
			
		||||
            wp_send_json_error(__('Please provide your email address.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create account if requested
 | 
			
		||||
        if (!is_user_logged_in() && isset($_POST['create_account']) && $_POST['create_account'] == '1') {
 | 
			
		||||
            $user_id = wp_create_user(
 | 
			
		||||
                $customer_data['email'],
 | 
			
		||||
                wp_generate_password(),
 | 
			
		||||
                $customer_data['email']
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            if (!is_wp_error($user_id)) {
 | 
			
		||||
                wp_update_user(array(
 | 
			
		||||
                    'ID' => $user_id,
 | 
			
		||||
                    'display_name' => $customer_data['name'],
 | 
			
		||||
                    'first_name' => $customer_data['name']
 | 
			
		||||
                ));
 | 
			
		||||
                
 | 
			
		||||
                // Set customer role (remove default role first)
 | 
			
		||||
                $user = new WP_User($user_id);
 | 
			
		||||
                $user->remove_role('subscriber'); // Remove default WordPress role
 | 
			
		||||
                $user->add_role('wpdd_customer');
 | 
			
		||||
                
 | 
			
		||||
                // Send new account email
 | 
			
		||||
                wp_new_user_notification($user_id, null, 'user');
 | 
			
		||||
                
 | 
			
		||||
                // Log them in
 | 
			
		||||
                wp_set_current_user($user_id);
 | 
			
		||||
                wp_set_auth_cookie($user_id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $order_id = WPDD_Orders::create_order($product_id, $customer_data, 'free');
 | 
			
		||||
        
 | 
			
		||||
        if ($order_id) {
 | 
			
		||||
            $order = WPDD_Orders::get_order($order_id);
 | 
			
		||||
            
 | 
			
		||||
            wp_send_json_success(array(
 | 
			
		||||
                'redirect_url' => add_query_arg(
 | 
			
		||||
                    'order_id',
 | 
			
		||||
                    $order->order_number,
 | 
			
		||||
                    get_permalink(get_option('wpdd_thank_you_page_id'))
 | 
			
		||||
                )
 | 
			
		||||
            ));
 | 
			
		||||
        } else {
 | 
			
		||||
            wp_send_json_error(__('Failed to process download. Please try again.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_to_cart() {
 | 
			
		||||
        check_ajax_referer('wpdd-ajax-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        if (!$product_id) {
 | 
			
		||||
            wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($product_id);
 | 
			
		||||
        if (!$product || $product->post_type !== 'wpdd_product') {
 | 
			
		||||
            wp_send_json_error(__('Product not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($_SESSION['wpdd_cart'])) {
 | 
			
		||||
            $_SESSION['wpdd_cart'] = array();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (in_array($product_id, $_SESSION['wpdd_cart'])) {
 | 
			
		||||
            wp_send_json_error(__('Product already in cart.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $_SESSION['wpdd_cart'][] = $product_id;
 | 
			
		||||
        
 | 
			
		||||
        wp_send_json_success(array(
 | 
			
		||||
            'message' => __('Product added to cart.', 'wp-digital-download'),
 | 
			
		||||
            'cart_count' => count($_SESSION['wpdd_cart']),
 | 
			
		||||
            'checkout_url' => add_query_arg(
 | 
			
		||||
                'product_id',
 | 
			
		||||
                $product_id,
 | 
			
		||||
                get_permalink(get_option('wpdd_checkout_page_id'))
 | 
			
		||||
            )
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_product_details() {
 | 
			
		||||
        check_ajax_referer('wpdd-ajax-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        if (!$product_id) {
 | 
			
		||||
            wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($product_id);
 | 
			
		||||
        if (!$product || $product->post_type !== 'wpdd_product') {
 | 
			
		||||
            wp_send_json_error(__('Product not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $price = get_post_meta($product_id, '_wpdd_price', true);
 | 
			
		||||
        $sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
 | 
			
		||||
        $is_free = get_post_meta($product_id, '_wpdd_is_free', true);
 | 
			
		||||
        $files = get_post_meta($product_id, '_wpdd_files', true);
 | 
			
		||||
        $download_limit = get_post_meta($product_id, '_wpdd_download_limit', true);
 | 
			
		||||
        $download_expiry = get_post_meta($product_id, '_wpdd_download_expiry', true);
 | 
			
		||||
        
 | 
			
		||||
        $creator = get_userdata($product->post_author);
 | 
			
		||||
        
 | 
			
		||||
        $data = array(
 | 
			
		||||
            'id' => $product_id,
 | 
			
		||||
            'title' => $product->post_title,
 | 
			
		||||
            'description' => $product->post_content,
 | 
			
		||||
            'excerpt' => $product->post_excerpt,
 | 
			
		||||
            'price' => $price,
 | 
			
		||||
            'sale_price' => $sale_price,
 | 
			
		||||
            'is_free' => $is_free,
 | 
			
		||||
            'final_price' => $is_free ? 0 : (($sale_price && $sale_price < $price) ? $sale_price : $price),
 | 
			
		||||
            'creator' => array(
 | 
			
		||||
                'id' => $creator->ID,
 | 
			
		||||
                'name' => $creator->display_name,
 | 
			
		||||
                'avatar' => get_avatar_url($creator->ID)
 | 
			
		||||
            ),
 | 
			
		||||
            'files_count' => is_array($files) ? count($files) : 0,
 | 
			
		||||
            'download_limit' => $download_limit ?: __('Unlimited', 'wp-digital-download'),
 | 
			
		||||
            'download_expiry' => $download_expiry ? sprintf(__('%d days', 'wp-digital-download'), $download_expiry) : __('Never expires', 'wp-digital-download'),
 | 
			
		||||
            'thumbnail' => get_the_post_thumbnail_url($product_id, 'full'),
 | 
			
		||||
            'categories' => wp_get_post_terms($product_id, 'wpdd_product_category', array('fields' => 'names')),
 | 
			
		||||
            'tags' => wp_get_post_terms($product_id, 'wpdd_product_tag', array('fields' => 'names'))
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (is_user_logged_in()) {
 | 
			
		||||
            $current_user = wp_get_current_user();
 | 
			
		||||
            $data['can_download'] = WPDD_Customer::can_download_product($current_user->ID, $product_id);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_send_json_success($data);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function check_download_status() {
 | 
			
		||||
        check_ajax_referer('wpdd-ajax-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        if (!is_user_logged_in()) {
 | 
			
		||||
            wp_send_json_error(__('Please login to check download status.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        if (!$product_id) {
 | 
			
		||||
            wp_send_json_error(__('Invalid product.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $current_user = wp_get_current_user();
 | 
			
		||||
        
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
             WHERE customer_id = %d 
 | 
			
		||||
             AND product_id = %d 
 | 
			
		||||
             AND status = 'completed'
 | 
			
		||||
             ORDER BY purchase_date DESC
 | 
			
		||||
             LIMIT 1",
 | 
			
		||||
            $current_user->ID,
 | 
			
		||||
            $product_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            wp_send_json_success(array(
 | 
			
		||||
                'has_purchased' => false,
 | 
			
		||||
                'message' => __('You have not purchased this product.', 'wp-digital-download')
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $download_limit = get_post_meta($product_id, '_wpdd_download_limit', true);
 | 
			
		||||
        $download_expiry = get_post_meta($product_id, '_wpdd_download_expiry', true);
 | 
			
		||||
        
 | 
			
		||||
        $can_download = true;
 | 
			
		||||
        $message = '';
 | 
			
		||||
        
 | 
			
		||||
        if ($download_expiry > 0) {
 | 
			
		||||
            $expiry_date = date('Y-m-d H:i:s', strtotime($order->purchase_date . ' + ' . $download_expiry . ' days'));
 | 
			
		||||
            if (current_time('mysql') > $expiry_date) {
 | 
			
		||||
                $can_download = false;
 | 
			
		||||
                $message = __('Your download period has expired.', 'wp-digital-download');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($can_download && $download_limit > 0 && $order->download_count >= $download_limit) {
 | 
			
		||||
            $can_download = false;
 | 
			
		||||
            $message = __('You have reached the download limit.', 'wp-digital-download');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_send_json_success(array(
 | 
			
		||||
            'has_purchased' => true,
 | 
			
		||||
            'can_download' => $can_download,
 | 
			
		||||
            'download_count' => $order->download_count,
 | 
			
		||||
            'download_limit' => $download_limit ?: 0,
 | 
			
		||||
            'purchase_date' => $order->purchase_date,
 | 
			
		||||
            'message' => $message ?: __('You can download this product.', 'wp-digital-download'),
 | 
			
		||||
            'download_url' => $can_download ? wp_nonce_url(
 | 
			
		||||
                add_query_arg(array('wpdd_download' => $order->id)),
 | 
			
		||||
                'wpdd_download_' . $order->id
 | 
			
		||||
            ) : ''
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function move_to_protected() {
 | 
			
		||||
        check_ajax_referer('wpdd-admin-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        if (!current_user_can('edit_wpdd_products')) {
 | 
			
		||||
            wp_send_json_error(__('You do not have permission to perform this action.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $attachment_id = isset($_POST['attachment_id']) ? intval($_POST['attachment_id']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        if (!$attachment_id) {
 | 
			
		||||
            wp_send_json_error(__('Invalid attachment ID.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $file_path = get_attached_file($attachment_id);
 | 
			
		||||
        if (!$file_path || !file_exists($file_path)) {
 | 
			
		||||
            wp_send_json_error(__('File not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create protected directory if it doesn't exist
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($protected_dir)) {
 | 
			
		||||
            wp_mkdir_p($protected_dir);
 | 
			
		||||
            
 | 
			
		||||
            // Add .htaccess protection
 | 
			
		||||
            $htaccess_content = "Options -Indexes\ndeny from all\n";
 | 
			
		||||
            file_put_contents($protected_dir . '/.htaccess', $htaccess_content);
 | 
			
		||||
            
 | 
			
		||||
            // Add index.php protection
 | 
			
		||||
            $index_content = "<?php\n// Silence is golden.\n";
 | 
			
		||||
            file_put_contents($protected_dir . '/index.php', $index_content);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create subdirectory based on year/month
 | 
			
		||||
        $time = current_time('mysql');
 | 
			
		||||
        $y = substr($time, 0, 4);
 | 
			
		||||
        $m = substr($time, 5, 2);
 | 
			
		||||
        $subdir = "$y/$m";
 | 
			
		||||
        $protected_subdir = trailingslashit($protected_dir) . $subdir;
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($protected_subdir)) {
 | 
			
		||||
            wp_mkdir_p($protected_subdir);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Generate unique filename
 | 
			
		||||
        $filename = basename($file_path);
 | 
			
		||||
        $unique_filename = wp_unique_filename($protected_subdir, $filename);
 | 
			
		||||
        $new_file_path = trailingslashit($protected_subdir) . $unique_filename;
 | 
			
		||||
        
 | 
			
		||||
        // Move file to protected directory
 | 
			
		||||
        if (rename($file_path, $new_file_path)) {
 | 
			
		||||
            // Update attachment metadata
 | 
			
		||||
            update_attached_file($attachment_id, $new_file_path);
 | 
			
		||||
            
 | 
			
		||||
            // Store protected path reference
 | 
			
		||||
            update_post_meta($attachment_id, '_wpdd_protected_path', $new_file_path);
 | 
			
		||||
            
 | 
			
		||||
            // Generate secure token for download URL
 | 
			
		||||
            $token = wp_generate_password(32, false);
 | 
			
		||||
            update_post_meta($attachment_id, '_wpdd_download_token', $token);
 | 
			
		||||
            
 | 
			
		||||
            wp_send_json_success(array(
 | 
			
		||||
                'protected_url' => home_url('?wpdd_file_download=' . $attachment_id . '&token=' . $token),
 | 
			
		||||
                'protected_path' => $new_file_path,
 | 
			
		||||
                'message' => __('File moved to protected directory.', 'wp-digital-download')
 | 
			
		||||
            ));
 | 
			
		||||
        } else {
 | 
			
		||||
            wp_send_json_error(__('Failed to move file to protected directory.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function upload_protected_file() {
 | 
			
		||||
        check_ajax_referer('wpdd-admin-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        if (!current_user_can('edit_wpdd_products')) {
 | 
			
		||||
            wp_send_json_error(__('You do not have permission to upload files.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($_FILES['file'])) {
 | 
			
		||||
            wp_send_json_error(__('No file uploaded.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $uploaded_file = $_FILES['file'];
 | 
			
		||||
        
 | 
			
		||||
        // Check for upload errors
 | 
			
		||||
        if ($uploaded_file['error'] !== UPLOAD_ERR_OK) {
 | 
			
		||||
            wp_send_json_error(__('Upload failed.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Validate file type
 | 
			
		||||
        $allowed_types = array(
 | 
			
		||||
            'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
 | 
			
		||||
            'zip', 'rar', '7z', 'tar', 'gz',
 | 
			
		||||
            'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp',
 | 
			
		||||
            'mp3', 'wav', 'flac', 'aac', 'ogg',
 | 
			
		||||
            'mp4', 'avi', 'mkv', 'mov', 'wmv',
 | 
			
		||||
            'txt', 'rtf', 'epub', 'mobi'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $file_ext = strtolower(pathinfo($uploaded_file['name'], PATHINFO_EXTENSION));
 | 
			
		||||
        if (!in_array($file_ext, $allowed_types)) {
 | 
			
		||||
            wp_send_json_error(__('File type not allowed.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create protected directory structure
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($protected_dir)) {
 | 
			
		||||
            wp_mkdir_p($protected_dir);
 | 
			
		||||
            
 | 
			
		||||
            // Add .htaccess protection
 | 
			
		||||
            $htaccess_content = "Options -Indexes\ndeny from all\n";
 | 
			
		||||
            file_put_contents($protected_dir . '/.htaccess', $htaccess_content);
 | 
			
		||||
            
 | 
			
		||||
            // Add index.php protection
 | 
			
		||||
            $index_content = "<?php\n// Silence is golden.\n";
 | 
			
		||||
            file_put_contents($protected_dir . '/index.php', $index_content);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create subdirectory based on year/month
 | 
			
		||||
        $time = current_time('mysql');
 | 
			
		||||
        $y = substr($time, 0, 4);
 | 
			
		||||
        $m = substr($time, 5, 2);
 | 
			
		||||
        $subdir = "$y/$m";
 | 
			
		||||
        $protected_subdir = trailingslashit($protected_dir) . $subdir;
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($protected_subdir)) {
 | 
			
		||||
            wp_mkdir_p($protected_subdir);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Generate unique filename
 | 
			
		||||
        $filename = sanitize_file_name($uploaded_file['name']);
 | 
			
		||||
        $unique_filename = wp_unique_filename($protected_subdir, $filename);
 | 
			
		||||
        $file_path = trailingslashit($protected_subdir) . $unique_filename;
 | 
			
		||||
        
 | 
			
		||||
        // Move uploaded file to protected directory
 | 
			
		||||
        if (move_uploaded_file($uploaded_file['tmp_name'], $file_path)) {
 | 
			
		||||
            // Generate secure token for download URL
 | 
			
		||||
            $token = wp_generate_password(32, false);
 | 
			
		||||
            $file_id = 'wpdd_' . uniqid();
 | 
			
		||||
            
 | 
			
		||||
            // Store file metadata
 | 
			
		||||
            $file_meta = array(
 | 
			
		||||
                'file_path' => $file_path,
 | 
			
		||||
                'file_name' => $unique_filename,
 | 
			
		||||
                'original_name' => $uploaded_file['name'],
 | 
			
		||||
                'file_size' => filesize($file_path),
 | 
			
		||||
                'file_type' => $uploaded_file['type'],
 | 
			
		||||
                'upload_date' => current_time('mysql'),
 | 
			
		||||
                'token' => $token
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            // Store in options table (in production, consider custom table)
 | 
			
		||||
            update_option('wpdd_protected_file_' . $file_id, $file_meta);
 | 
			
		||||
            
 | 
			
		||||
            wp_send_json_success(array(
 | 
			
		||||
                'protected_url' => home_url('?wpdd_protected_download=' . $file_id . '&token=' . $token),
 | 
			
		||||
                'file_id' => $file_id,
 | 
			
		||||
                'file_name' => $unique_filename,
 | 
			
		||||
                'file_size' => size_format($file_meta['file_size']),
 | 
			
		||||
                'message' => __('File uploaded successfully to protected directory.', 'wp-digital-download')
 | 
			
		||||
            ));
 | 
			
		||||
        } else {
 | 
			
		||||
            wp_send_json_error(__('Failed to save uploaded file.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle password change for customers
 | 
			
		||||
     */
 | 
			
		||||
    public static function change_password() {
 | 
			
		||||
        check_ajax_referer('wpdd_change_password', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        if (!is_user_logged_in()) {
 | 
			
		||||
            wp_send_json_error(__('You must be logged in to change your password.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $current_user = wp_get_current_user();
 | 
			
		||||
        
 | 
			
		||||
        // Only allow customers to change their own password this way
 | 
			
		||||
        if (!in_array('wpdd_customer', $current_user->roles)) {
 | 
			
		||||
            wp_send_json_error(__('This function is only available for customers.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $current_password = sanitize_text_field($_POST['current_password'] ?? '');
 | 
			
		||||
        $new_password = sanitize_text_field($_POST['new_password'] ?? '');
 | 
			
		||||
        $confirm_password = sanitize_text_field($_POST['confirm_password'] ?? '');
 | 
			
		||||
        
 | 
			
		||||
        if (empty($current_password) || empty($new_password) || empty($confirm_password)) {
 | 
			
		||||
            wp_send_json_error(__('All password fields are required.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($new_password !== $confirm_password) {
 | 
			
		||||
            wp_send_json_error(__('New passwords do not match.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (strlen($new_password) < 8) {
 | 
			
		||||
            wp_send_json_error(__('New password must be at least 8 characters long.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Verify current password
 | 
			
		||||
        if (!wp_check_password($current_password, $current_user->user_pass, $current_user->ID)) {
 | 
			
		||||
            wp_send_json_error(__('Current password is incorrect.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Update password
 | 
			
		||||
        $result = wp_update_user(array(
 | 
			
		||||
            'ID' => $current_user->ID,
 | 
			
		||||
            'user_pass' => $new_password
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (is_wp_error($result)) {
 | 
			
		||||
            wp_send_json_error(__('Failed to update password. Please try again.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Re-authenticate user to prevent logout
 | 
			
		||||
        wp_set_current_user($current_user->ID);
 | 
			
		||||
        wp_set_auth_cookie($current_user->ID);
 | 
			
		||||
        
 | 
			
		||||
        wp_send_json_success(__('Password changed successfully!', 'wp-digital-download'));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										177
									
								
								includes/class-wpdd-creator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								includes/class-wpdd-creator.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Creator {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('show_user_profile', array(__CLASS__, 'add_profile_fields'));
 | 
			
		||||
        add_action('edit_user_profile', array(__CLASS__, 'add_profile_fields'));
 | 
			
		||||
        add_action('personal_options_update', array(__CLASS__, 'save_profile_fields'));
 | 
			
		||||
        add_action('edit_user_profile_update', array(__CLASS__, 'save_profile_fields'));
 | 
			
		||||
        add_action('user_register', array(__CLASS__, 'set_default_fields'));
 | 
			
		||||
        add_action('wpdd_order_completed', array(__CLASS__, 'add_earnings_to_balance'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_profile_fields($user) {
 | 
			
		||||
        // Only show for creators and admins
 | 
			
		||||
        if (!self::is_creator($user->ID) && !current_user_can('manage_options')) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ?>
 | 
			
		||||
        <h3><?php _e('Creator Payout Settings', 'wp-digital-download'); ?></h3>
 | 
			
		||||
        <table class="form-table">
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th><label for="wpdd_paypal_email"><?php _e('PayPal Email', 'wp-digital-download'); ?></label></th>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <input type="email" name="wpdd_paypal_email" id="wpdd_paypal_email" 
 | 
			
		||||
                           value="<?php echo esc_attr(get_user_meta($user->ID, 'wpdd_paypal_email', true)); ?>" 
 | 
			
		||||
                           class="regular-text" />
 | 
			
		||||
                    <p class="description"><?php _e('PayPal email address for receiving payouts', 'wp-digital-download'); ?></p>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <?php if (current_user_can('manage_options')) : ?>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th><label><?php _e('Creator Balance', 'wp-digital-download'); ?></label></th>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <?php 
 | 
			
		||||
                    $balance = self::get_creator_balance($user->ID);
 | 
			
		||||
                    $currency = get_option('wpdd_currency', 'USD');
 | 
			
		||||
                    echo '<strong>' . wpdd_format_price($balance, $currency) . '</strong>';
 | 
			
		||||
                    ?>
 | 
			
		||||
                    <p class="description"><?php _e('Current unpaid earnings balance', 'wp-digital-download'); ?></p>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th><label><?php _e('Total Earnings', 'wp-digital-download'); ?></label></th>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <?php 
 | 
			
		||||
                    $total = self::get_creator_total_earnings($user->ID);
 | 
			
		||||
                    echo '<strong>' . wpdd_format_price($total, $currency) . '</strong>';
 | 
			
		||||
                    ?>
 | 
			
		||||
                    <p class="description"><?php _e('Total lifetime earnings', 'wp-digital-download'); ?></p>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <?php endif; ?>
 | 
			
		||||
        </table>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function save_profile_fields($user_id) {
 | 
			
		||||
        if (!current_user_can('edit_user', $user_id)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_POST['wpdd_paypal_email'])) {
 | 
			
		||||
            $email = sanitize_email($_POST['wpdd_paypal_email']);
 | 
			
		||||
            if (!empty($email) && !is_email($email)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            update_user_meta($user_id, 'wpdd_paypal_email', $email);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function set_default_fields($user_id) {
 | 
			
		||||
        if (self::is_creator($user_id)) {
 | 
			
		||||
            update_user_meta($user_id, 'wpdd_creator_balance', 0);
 | 
			
		||||
            update_user_meta($user_id, 'wpdd_total_earnings', 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function is_creator($user_id) {
 | 
			
		||||
        $user = get_userdata($user_id);
 | 
			
		||||
        return $user && in_array('wpdd_creator', (array) $user->roles);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_creator_balance($user_id) {
 | 
			
		||||
        return floatval(get_user_meta($user_id, 'wpdd_creator_balance', true));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_creator_total_earnings($user_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $total = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT SUM(o.total) 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             INNER JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE p.post_author = %d 
 | 
			
		||||
             AND o.status = 'completed'",
 | 
			
		||||
            $user_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        return floatval($total);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_creator_net_earnings($user_id) {
 | 
			
		||||
        $total = self::get_creator_total_earnings($user_id);
 | 
			
		||||
        $commission_rate = floatval(get_option('wpdd_commission_rate', 0));
 | 
			
		||||
        $net = $total * (1 - ($commission_rate / 100));
 | 
			
		||||
        return $net;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_earnings_to_balance($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order || $order->status !== 'completed') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($order->product_id);
 | 
			
		||||
        if (!$product) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $creator_id = $product->post_author;
 | 
			
		||||
        $commission_rate = floatval(get_option('wpdd_commission_rate', 0));
 | 
			
		||||
        $creator_share = $order->total * (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);
 | 
			
		||||
        
 | 
			
		||||
        // Log the earning
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_creator_earnings',
 | 
			
		||||
            array(
 | 
			
		||||
                'creator_id' => $creator_id,
 | 
			
		||||
                'order_id' => $order_id,
 | 
			
		||||
                'product_id' => $order->product_id,
 | 
			
		||||
                'sale_amount' => $order->total,
 | 
			
		||||
                'commission_rate' => $commission_rate,
 | 
			
		||||
                'creator_earning' => $creator_share,
 | 
			
		||||
                'created_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%d', '%d', '%f', '%f', '%f', '%s')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_creators_with_balance() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $threshold = floatval(get_option('wpdd_payout_threshold', 0));
 | 
			
		||||
        
 | 
			
		||||
        $query = "SELECT u.ID, u.display_name, u.user_email, 
 | 
			
		||||
                         um1.meta_value as paypal_email,
 | 
			
		||||
                         um2.meta_value as balance
 | 
			
		||||
                  FROM {$wpdb->users} u
 | 
			
		||||
                  INNER JOIN {$wpdb->usermeta} um ON u.ID = um.user_id
 | 
			
		||||
                  LEFT JOIN {$wpdb->usermeta} um1 ON u.ID = um1.user_id AND um1.meta_key = 'wpdd_paypal_email'
 | 
			
		||||
                  LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'wpdd_creator_balance'
 | 
			
		||||
                  WHERE um.meta_key = '{$wpdb->prefix}capabilities'
 | 
			
		||||
                  AND um.meta_value LIKE '%wpdd_creator%'
 | 
			
		||||
                  AND CAST(um2.meta_value AS DECIMAL(10,2)) > 0";
 | 
			
		||||
        
 | 
			
		||||
        if ($threshold > 0) {
 | 
			
		||||
            $query .= $wpdb->prepare(" AND CAST(um2.meta_value AS DECIMAL(10,2)) >= %f", $threshold);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $wpdb->get_results($query);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										377
									
								
								includes/class-wpdd-customer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										377
									
								
								includes/class-wpdd-customer.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,377 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Customer {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('wp_dashboard_setup', array(__CLASS__, 'add_dashboard_widgets'));
 | 
			
		||||
        add_filter('login_redirect', array(__CLASS__, 'login_redirect'), 10, 3);
 | 
			
		||||
        add_action('show_user_profile', array(__CLASS__, 'add_customer_fields'));
 | 
			
		||||
        add_action('edit_user_profile', array(__CLASS__, 'add_customer_fields'));
 | 
			
		||||
        
 | 
			
		||||
        // Block wp-admin access for customers
 | 
			
		||||
        add_action('admin_init', array(__CLASS__, 'restrict_admin_access'));
 | 
			
		||||
        
 | 
			
		||||
        // Add frontend logout and account management
 | 
			
		||||
        add_action('wp_footer', array(__CLASS__, 'add_customer_scripts'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_dashboard_widgets() {
 | 
			
		||||
        if (current_user_can('wpdd_view_purchases')) {
 | 
			
		||||
            wp_add_dashboard_widget(
 | 
			
		||||
                'wpdd_customer_recent_purchases',
 | 
			
		||||
                __('Recent Purchases', 'wp-digital-download'),
 | 
			
		||||
                array(__CLASS__, 'recent_purchases_widget')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (current_user_can('wpdd_view_own_sales')) {
 | 
			
		||||
            wp_add_dashboard_widget(
 | 
			
		||||
                'wpdd_creator_sales_summary',
 | 
			
		||||
                __('Sales Summary', 'wp-digital-download'),
 | 
			
		||||
                array(__CLASS__, 'sales_summary_widget')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function recent_purchases_widget() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        $current_user = wp_get_current_user();
 | 
			
		||||
        
 | 
			
		||||
        $recent_orders = $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE o.customer_id = %d 
 | 
			
		||||
             AND o.status = 'completed'
 | 
			
		||||
             ORDER BY o.purchase_date DESC
 | 
			
		||||
             LIMIT 5",
 | 
			
		||||
            $current_user->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if ($recent_orders) {
 | 
			
		||||
            echo '<ul>';
 | 
			
		||||
            foreach ($recent_orders as $order) {
 | 
			
		||||
                printf(
 | 
			
		||||
                    '<li>%s - <a href="%s">%s</a> ($%s)</li>',
 | 
			
		||||
                    date_i18n(get_option('date_format'), strtotime($order->purchase_date)),
 | 
			
		||||
                    get_permalink($order->product_id),
 | 
			
		||||
                    esc_html($order->product_name),
 | 
			
		||||
                    number_format($order->amount, 2)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            echo '</ul>';
 | 
			
		||||
            
 | 
			
		||||
            printf(
 | 
			
		||||
                '<p><a href="%s" class="button">%s</a></p>',
 | 
			
		||||
                get_permalink(get_option('wpdd_purchases_page_id')),
 | 
			
		||||
                __('View All Purchases', 'wp-digital-download')
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            echo '<p>' . __('No purchases yet.', 'wp-digital-download') . '</p>';
 | 
			
		||||
            printf(
 | 
			
		||||
                '<p><a href="%s" class="button button-primary">%s</a></p>',
 | 
			
		||||
                get_permalink(get_option('wpdd_shop_page_id')),
 | 
			
		||||
                __('Browse Products', 'wp-digital-download')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function sales_summary_widget() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        $current_user = wp_get_current_user();
 | 
			
		||||
        
 | 
			
		||||
        $stats = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT 
 | 
			
		||||
                COUNT(*) as total_sales,
 | 
			
		||||
                SUM(amount) as total_revenue,
 | 
			
		||||
                COUNT(DISTINCT product_id) as products_sold
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders
 | 
			
		||||
             WHERE creator_id = %d 
 | 
			
		||||
             AND status = 'completed'
 | 
			
		||||
             AND purchase_date >= DATE_SUB(NOW(), INTERVAL 30 DAY)",
 | 
			
		||||
            $current_user->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $recent_sales = $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE o.creator_id = %d 
 | 
			
		||||
             AND o.status = 'completed'
 | 
			
		||||
             ORDER BY o.purchase_date DESC
 | 
			
		||||
             LIMIT 5",
 | 
			
		||||
            $current_user->ID
 | 
			
		||||
        ));
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wpdd-sales-summary">
 | 
			
		||||
            <div class="wpdd-stats-grid">
 | 
			
		||||
                <div class="wpdd-stat">
 | 
			
		||||
                    <span class="wpdd-stat-value"><?php echo intval($stats->total_sales); ?></span>
 | 
			
		||||
                    <span class="wpdd-stat-label"><?php _e('Sales (30 days)', 'wp-digital-download'); ?></span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="wpdd-stat">
 | 
			
		||||
                    <span class="wpdd-stat-value">$<?php echo number_format($stats->total_revenue ?: 0, 2); ?></span>
 | 
			
		||||
                    <span class="wpdd-stat-label"><?php _e('Revenue (30 days)', 'wp-digital-download'); ?></span>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <?php if ($recent_sales) : ?>
 | 
			
		||||
                <h4><?php _e('Recent Sales', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                <ul>
 | 
			
		||||
                    <?php foreach ($recent_sales as $sale) : ?>
 | 
			
		||||
                        <li>
 | 
			
		||||
                            <?php echo date_i18n(get_option('date_format'), strtotime($sale->purchase_date)); ?> - 
 | 
			
		||||
                            <?php echo esc_html($sale->product_name); ?> 
 | 
			
		||||
                            ($<?php echo number_format($sale->amount, 2); ?>)
 | 
			
		||||
                        </li>
 | 
			
		||||
                    <?php endforeach; ?>
 | 
			
		||||
                </ul>
 | 
			
		||||
            <?php endif; ?>
 | 
			
		||||
            
 | 
			
		||||
            <p>
 | 
			
		||||
                <a href="<?php echo admin_url('edit.php?post_type=wpdd_product'); ?>" class="button">
 | 
			
		||||
                    <?php _e('Manage Products', 'wp-digital-download'); ?>
 | 
			
		||||
                </a>
 | 
			
		||||
            </p>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <style>
 | 
			
		||||
            .wpdd-stats-grid {
 | 
			
		||||
                display: grid;
 | 
			
		||||
                grid-template-columns: 1fr 1fr;
 | 
			
		||||
                gap: 15px;
 | 
			
		||||
                margin-bottom: 20px;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-stat {
 | 
			
		||||
                text-align: center;
 | 
			
		||||
                padding: 10px;
 | 
			
		||||
                background: #f0f0f1;
 | 
			
		||||
                border-radius: 4px;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-stat-value {
 | 
			
		||||
                display: block;
 | 
			
		||||
                font-size: 24px;
 | 
			
		||||
                font-weight: 600;
 | 
			
		||||
                color: #2271b1;
 | 
			
		||||
            }
 | 
			
		||||
            .wpdd-stat-label {
 | 
			
		||||
                display: block;
 | 
			
		||||
                font-size: 12px;
 | 
			
		||||
                color: #646970;
 | 
			
		||||
                margin-top: 5px;
 | 
			
		||||
            }
 | 
			
		||||
        </style>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function login_redirect($redirect_to, $requested_redirect_to, $user) {
 | 
			
		||||
        if (!is_wp_error($user) && in_array('wpdd_customer', $user->roles)) {
 | 
			
		||||
            $purchases_page = get_option('wpdd_purchases_page_id');
 | 
			
		||||
            if ($purchases_page) {
 | 
			
		||||
                return get_permalink($purchases_page);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $redirect_to;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_customer_fields($user) {
 | 
			
		||||
        if (!in_array('wpdd_customer', $user->roles)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $total_purchases = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
             WHERE customer_id = %d AND status = 'completed'",
 | 
			
		||||
            $user->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $total_spent = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT SUM(amount) FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
             WHERE customer_id = %d AND status = 'completed'",
 | 
			
		||||
            $user->ID
 | 
			
		||||
        ));
 | 
			
		||||
        ?>
 | 
			
		||||
        <h3><?php _e('Customer Information', 'wp-digital-download'); ?></h3>
 | 
			
		||||
        <table class="form-table">
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th><?php _e('Total Purchases', 'wp-digital-download'); ?></th>
 | 
			
		||||
                <td><?php echo intval($total_purchases); ?></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th><?php _e('Total Spent', 'wp-digital-download'); ?></th>
 | 
			
		||||
                <td>$<?php echo number_format($total_spent ?: 0, 2); ?></td>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </table>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_customer_purchases($customer_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        return $wpdb->get_results($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE o.customer_id = %d 
 | 
			
		||||
             AND o.status = 'completed'
 | 
			
		||||
             ORDER BY o.purchase_date DESC",
 | 
			
		||||
            $customer_id
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function can_download_product($customer_id, $product_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
             WHERE customer_id = %d 
 | 
			
		||||
             AND product_id = %d 
 | 
			
		||||
             AND status = 'completed'
 | 
			
		||||
             ORDER BY purchase_date DESC
 | 
			
		||||
             LIMIT 1",
 | 
			
		||||
            $customer_id,
 | 
			
		||||
            $product_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $download_limit = get_post_meta($product_id, '_wpdd_download_limit', true);
 | 
			
		||||
        $download_expiry = get_post_meta($product_id, '_wpdd_download_expiry', true);
 | 
			
		||||
        
 | 
			
		||||
        if ($download_expiry > 0) {
 | 
			
		||||
            $expiry_date = date('Y-m-d H:i:s', strtotime($order->purchase_date . ' + ' . $download_expiry . ' days'));
 | 
			
		||||
            if (current_time('mysql') > $expiry_date) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($download_limit > 0 && $order->download_count >= $download_limit) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Block wp-admin access for customers
 | 
			
		||||
     */
 | 
			
		||||
    public static function restrict_admin_access() {
 | 
			
		||||
        $current_user = wp_get_current_user();
 | 
			
		||||
        
 | 
			
		||||
        // Only block for wpdd_customer role, allow creators and admins
 | 
			
		||||
        if (in_array('wpdd_customer', $current_user->roles) && !current_user_can('manage_options')) {
 | 
			
		||||
            // Allow AJAX requests
 | 
			
		||||
            if (defined('DOING_AJAX') && DOING_AJAX) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Redirect to purchases page
 | 
			
		||||
            $purchases_page = get_option('wpdd_purchases_page_id');
 | 
			
		||||
            $redirect_url = $purchases_page ? get_permalink($purchases_page) : home_url();
 | 
			
		||||
            wp_redirect($redirect_url);
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Add frontend customer scripts and functionality
 | 
			
		||||
     */
 | 
			
		||||
    public static function add_customer_scripts() {
 | 
			
		||||
        if (is_user_logged_in()) {
 | 
			
		||||
            $current_user = wp_get_current_user();
 | 
			
		||||
            
 | 
			
		||||
            // Only for customers
 | 
			
		||||
            if (in_array('wpdd_customer', $current_user->roles)) {
 | 
			
		||||
                ?>
 | 
			
		||||
                <script>
 | 
			
		||||
                // Add logout functionality to customer pages
 | 
			
		||||
                document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
                    // Add logout link to customer navigation if it exists
 | 
			
		||||
                    var customerNav = document.querySelector('.wpdd-customer-nav, .wpdd-shop-filters, .wpdd-customer-purchases');
 | 
			
		||||
                    if (customerNav && !document.querySelector('.wpdd-customer-logout')) {
 | 
			
		||||
                        var logoutLink = document.createElement('div');
 | 
			
		||||
                        logoutLink.className = 'wpdd-customer-logout';
 | 
			
		||||
                        logoutLink.style.cssText = 'margin-top: 10px; padding: 10px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px;';
 | 
			
		||||
                        logoutLink.innerHTML = '<strong>Welcome, <?php echo esc_js($current_user->display_name); ?>!</strong> | ' +
 | 
			
		||||
                            '<a href="<?php echo wp_logout_url(get_permalink()); ?>" style="color: #dc3545;">Logout</a> | ' +
 | 
			
		||||
                            '<a href="#" onclick="wpdd_show_password_form()" style="color: #007cba;">Change Password</a>';
 | 
			
		||||
                        customerNav.appendChild(logoutLink);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                // Password change functionality
 | 
			
		||||
                function wpdd_show_password_form() {
 | 
			
		||||
                    var passwordForm = document.getElementById('wpdd-password-form');
 | 
			
		||||
                    if (passwordForm) {
 | 
			
		||||
                        passwordForm.style.display = passwordForm.style.display === 'none' ? 'block' : 'none';
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    var formHtml = '<div id="wpdd-password-form" style="margin-top: 15px; padding: 15px; background: white; border: 2px solid #007cba; border-radius: 4px;">' +
 | 
			
		||||
                        '<h4>Change Password</h4>' +
 | 
			
		||||
                        '<form id="wpdd-change-password" onsubmit="wpdd_change_password(event)">' +
 | 
			
		||||
                        '<p><input type="password" name="current_password" placeholder="Current Password" required style="width: 100%; margin-bottom: 10px; padding: 8px;"></p>' +
 | 
			
		||||
                        '<p><input type="password" name="new_password" placeholder="New Password" required style="width: 100%; margin-bottom: 10px; padding: 8px;"></p>' +
 | 
			
		||||
                        '<p><input type="password" name="confirm_password" placeholder="Confirm New Password" required style="width: 100%; margin-bottom: 10px; padding: 8px;"></p>' +
 | 
			
		||||
                        '<p><button type="submit" style="background: #007cba; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Update Password</button> ' +
 | 
			
		||||
                        '<button type="button" onclick="wpdd_hide_password_form()" style="background: #6c757d; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">Cancel</button></p>' +
 | 
			
		||||
                        '</form></div>';
 | 
			
		||||
                    
 | 
			
		||||
                    var logoutDiv = document.querySelector('.wpdd-customer-logout');
 | 
			
		||||
                    if (logoutDiv) {
 | 
			
		||||
                        logoutDiv.insertAdjacentHTML('afterend', formHtml);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                function wpdd_hide_password_form() {
 | 
			
		||||
                    var passwordForm = document.getElementById('wpdd-password-form');
 | 
			
		||||
                    if (passwordForm) {
 | 
			
		||||
                        passwordForm.remove();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                function wpdd_change_password(event) {
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                    var form = event.target;
 | 
			
		||||
                    var formData = new FormData(form);
 | 
			
		||||
                    
 | 
			
		||||
                    if (formData.get('new_password') !== formData.get('confirm_password')) {
 | 
			
		||||
                        alert('New passwords do not match!');
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    formData.append('action', 'wpdd_change_password');
 | 
			
		||||
                    formData.append('nonce', '<?php echo wp_create_nonce('wpdd_change_password'); ?>');
 | 
			
		||||
                    
 | 
			
		||||
                    fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
 | 
			
		||||
                        method: 'POST',
 | 
			
		||||
                        body: formData
 | 
			
		||||
                    })
 | 
			
		||||
                    .then(response => response.json())
 | 
			
		||||
                    .then(data => {
 | 
			
		||||
                        if (data.success) {
 | 
			
		||||
                            alert('Password changed successfully!');
 | 
			
		||||
                            wpdd_hide_password_form();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            alert('Error: ' + (data.data || 'Failed to change password'));
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                    .catch(error => {
 | 
			
		||||
                        alert('Error: ' + error.message);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                </script>
 | 
			
		||||
                <?php
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										752
									
								
								includes/class-wpdd-download-handler.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										752
									
								
								includes/class-wpdd-download-handler.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,752 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Download_Handler {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        // Use priority 1 to ensure our handler runs early
 | 
			
		||||
        add_action('init', array(__CLASS__, 'handle_download_request'), 1);
 | 
			
		||||
        add_action('init', array(__CLASS__, 'handle_secure_file_download'), 1);
 | 
			
		||||
        add_action('init', array(__CLASS__, 'handle_protected_download'), 1);
 | 
			
		||||
        
 | 
			
		||||
        // Also hook to template_redirect as a fallback
 | 
			
		||||
        add_action('template_redirect', array(__CLASS__, 'handle_download_request'), 1);
 | 
			
		||||
        
 | 
			
		||||
        // Debug: Add a test endpoint for admins
 | 
			
		||||
        if (current_user_can('manage_options') && isset($_GET['wpdd_test_download'])) {
 | 
			
		||||
            add_action('init', function() {
 | 
			
		||||
                wp_die('WPDD Download Handler is loaded and working! Current time: ' . current_time('mysql'));
 | 
			
		||||
            }, 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_download_request() {
 | 
			
		||||
        // TESTING: Add a test parameter to verify changes are taking effect
 | 
			
		||||
        if (isset($_GET['test_update_working'])) {
 | 
			
		||||
            wp_die('TEST: Changes are working! File updated successfully.');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Early exit if not a download request
 | 
			
		||||
        if (!isset($_GET['wpdd_download']) && !isset($_GET['wpdd_download_token'])) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Debug logging for admin users
 | 
			
		||||
        if (current_user_can('manage_options')) {
 | 
			
		||||
            error_log('WPDD Debug: Download request detected!');
 | 
			
		||||
            error_log('WPDD Debug: GET params: ' . print_r($_GET, true));
 | 
			
		||||
            error_log('WPDD Debug: Current action: ' . current_action());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_GET['wpdd_download'])) {
 | 
			
		||||
            // Add debug output before processing
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                error_log('WPDD Debug: Processing download by order ID: ' . $_GET['wpdd_download']);
 | 
			
		||||
            }
 | 
			
		||||
            self::process_download_by_order();
 | 
			
		||||
            exit; // Make sure we exit after processing
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_GET['wpdd_download_token'])) {
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                error_log('WPDD Debug: Processing download by token: ' . $_GET['wpdd_download_token']);
 | 
			
		||||
            }
 | 
			
		||||
            self::process_download_by_token();
 | 
			
		||||
            exit; // Make sure we exit after processing
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_protected_download() {
 | 
			
		||||
        if (!isset($_GET['wpdd_protected_download'])) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $file_id = sanitize_text_field($_GET['wpdd_protected_download']);
 | 
			
		||||
        $token = sanitize_text_field($_GET['token'] ?? '');
 | 
			
		||||
        
 | 
			
		||||
        if (!$file_id || !$token) {
 | 
			
		||||
            wp_die(__('Invalid download link.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get file metadata
 | 
			
		||||
        $file_meta = get_option('wpdd_protected_file_' . $file_id);
 | 
			
		||||
        
 | 
			
		||||
        if (!$file_meta || $file_meta['token'] !== $token) {
 | 
			
		||||
            wp_die(__('Invalid download token.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($file_meta['file_path'])) {
 | 
			
		||||
            wp_die(__('File not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Deliver the file
 | 
			
		||||
        self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function process_download_by_order() {
 | 
			
		||||
        $download_link_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: Nonce received: ' . ($_GET['_wpnonce'] ?? 'none'));
 | 
			
		||||
            error_log('WPDD Debug: Expected nonce action: wpdd_download_' . $download_link_id);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'wpdd_download_' . $download_link_id)) {
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                error_log('WPDD Debug: Nonce verification failed!');
 | 
			
		||||
            }
 | 
			
		||||
            wp_die(__('Invalid download link.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        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']);
 | 
			
		||||
            $key = sanitize_text_field($_GET['key']);
 | 
			
		||||
            
 | 
			
		||||
            // Verify the key
 | 
			
		||||
            $expected_key = substr(md5($email . AUTH_KEY), 0, 10);
 | 
			
		||||
            if ($key !== $expected_key) {
 | 
			
		||||
                wp_die(__('Invalid access key.', 'wp-digital-download'));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
                "SELECT * FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
                 WHERE id = %d AND customer_email = %s AND status = 'completed'",
 | 
			
		||||
                $order_id,
 | 
			
		||||
                $email
 | 
			
		||||
            ));
 | 
			
		||||
        } elseif (is_user_logged_in()) {
 | 
			
		||||
            $current_user = wp_get_current_user();
 | 
			
		||||
            
 | 
			
		||||
            $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
                "SELECT * FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
                 WHERE id = %d AND (customer_id = %d OR customer_email = %s) AND status = 'completed'",
 | 
			
		||||
                $order_id,
 | 
			
		||||
                $current_user->ID,
 | 
			
		||||
                $current_user->user_email
 | 
			
		||||
            ));
 | 
			
		||||
        } else {
 | 
			
		||||
            // For unregistered users, try to look up order by order number from URL if available
 | 
			
		||||
            if (isset($_GET['order_id'])) {
 | 
			
		||||
                $order_number = sanitize_text_field($_GET['order_id']);
 | 
			
		||||
                
 | 
			
		||||
                // Look up order by order ID and verify it matches the order number
 | 
			
		||||
                $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
                    "SELECT * FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
                     WHERE id = %d AND order_number = %s AND status = 'completed'",
 | 
			
		||||
                    $order_id,
 | 
			
		||||
                    $order_number
 | 
			
		||||
                ));
 | 
			
		||||
                
 | 
			
		||||
                if (current_user_can('manage_options')) {
 | 
			
		||||
                    error_log('WPDD Debug: Guest order lookup - Order ID: ' . $order_id . ', Order Number: ' . $order_number . ', Found: ' . ($order ? 'Yes' : 'No'));
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if (!$order) {
 | 
			
		||||
                    wp_die(__('Invalid order or order not found.', 'wp-digital-download'));
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Debug: Show what parameters we have
 | 
			
		||||
                $debug_info = '';
 | 
			
		||||
                if (current_user_can('manage_options')) {
 | 
			
		||||
                    $debug_info = '<br><br>Debug info:<br>';
 | 
			
		||||
                    $debug_info .= 'GET params: ' . print_r($_GET, true);
 | 
			
		||||
                    $debug_info .= '<br>User logged in: ' . (is_user_logged_in() ? 'Yes' : 'No');
 | 
			
		||||
                }
 | 
			
		||||
                wp_die(__('You must be logged in to download this product or provide a valid order reference.', 'wp-digital-download') . $debug_info);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            wp_die(__('Invalid order or you do not have permission to download this product.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        self::process_download($order);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function process_download_by_token() {
 | 
			
		||||
        $token = sanitize_text_field($_GET['wpdd_download_token']);
 | 
			
		||||
        
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $download_link = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT dl.*, o.* 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_download_links dl
 | 
			
		||||
             INNER JOIN {$wpdb->prefix}wpdd_orders o ON dl.order_id = o.id
 | 
			
		||||
             WHERE dl.token = %s",
 | 
			
		||||
            $token
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$download_link) {
 | 
			
		||||
            wp_die(__('Invalid download link.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($download_link->expires_at < current_time('mysql')) {
 | 
			
		||||
            // Check if user still has downloads remaining
 | 
			
		||||
            if ($download_link->max_downloads > 0 && $download_link->download_count >= $download_link->max_downloads) {
 | 
			
		||||
                wp_die(__('This download link has expired and you have no downloads remaining.', 'wp-digital-download'));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Only send refresh email if this appears to be a real user attempt (not automated)
 | 
			
		||||
            // Check if the customer is unregistered (has no user account)
 | 
			
		||||
            $is_unregistered_customer = ($download_link->customer_id == 0);
 | 
			
		||||
            
 | 
			
		||||
            if ($is_unregistered_customer) {
 | 
			
		||||
                // Generate new token and send refresh email only for unregistered customers
 | 
			
		||||
                $new_token = self::refresh_download_token($download_link->order_id, $download_link->customer_email);
 | 
			
		||||
                
 | 
			
		||||
                if ($new_token) {
 | 
			
		||||
                    wp_die(sprintf(
 | 
			
		||||
                        __('Your download link has expired. A new download link has been sent to %s. Please check your email and try again.', 'wp-digital-download'),
 | 
			
		||||
                        esc_html($download_link->customer_email)
 | 
			
		||||
                    ));
 | 
			
		||||
                } else {
 | 
			
		||||
                    wp_die(__('This download link has expired and could not be refreshed. Please contact support.', 'wp-digital-download'));
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // For registered users, just show expired message (they can log in to get new links)
 | 
			
		||||
                wp_die(__('This download link has expired. Please log in to your account to get a new download link.', 'wp-digital-download'));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($download_link->max_downloads > 0 && $download_link->download_count >= $download_link->max_downloads) {
 | 
			
		||||
            wp_die(__('Download limit exceeded.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
            array('download_count' => $download_link->download_count + 1),
 | 
			
		||||
            array('id' => $download_link->id),
 | 
			
		||||
            array('%d'),
 | 
			
		||||
            array('%d')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        self::process_download($download_link);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh an expired download token and send new link via email
 | 
			
		||||
     */
 | 
			
		||||
    private static function refresh_download_token($order_id, $customer_email) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Generate new token with extended expiry (72 hours as suggested)
 | 
			
		||||
        $new_token = wp_hash(uniqid() . $order_id . time());
 | 
			
		||||
        $new_expires_at = date('Y-m-d H:i:s', strtotime('+72 hours'));
 | 
			
		||||
        
 | 
			
		||||
        // Update the existing download link with new token and expiry
 | 
			
		||||
        $updated = $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
            array(
 | 
			
		||||
                'token' => $new_token,
 | 
			
		||||
                'expires_at' => $new_expires_at,
 | 
			
		||||
                'refreshed_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('order_id' => $order_id),
 | 
			
		||||
            array('%s', '%s', '%s'),
 | 
			
		||||
            array('%d')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (!$updated) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Send refresh email
 | 
			
		||||
        self::send_refresh_email($order_id, $new_token, $customer_email);
 | 
			
		||||
        
 | 
			
		||||
        return $new_token;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Send refresh email with new download link
 | 
			
		||||
     */
 | 
			
		||||
    private static function send_refresh_email($order_id, $token, $customer_email) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get order details
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             WHERE o.id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $download_url = add_query_arg(array(
 | 
			
		||||
            'wpdd_download_token' => $token
 | 
			
		||||
        ), home_url());
 | 
			
		||||
        
 | 
			
		||||
        $subject = sprintf(__('New Download Link for %s', 'wp-digital-download'), $order->product_name);
 | 
			
		||||
        
 | 
			
		||||
        $message = sprintf(
 | 
			
		||||
            __("Hello %s,\n\nYour download link for \"%s\" has expired, so we've generated a new one for you.\n\nThis new link is valid for 72 hours and can be used for your remaining downloads.\n\nDownload Link: %s\n\nOrder Number: %s\nPurchase Date: %s\n\nIf you have any issues, please contact our support team.\n\nBest regards,\n%s", 'wp-digital-download'),
 | 
			
		||||
            $order->customer_name,
 | 
			
		||||
            $order->product_name,
 | 
			
		||||
            $download_url,
 | 
			
		||||
            $order->order_number,
 | 
			
		||||
            date_i18n(get_option('date_format'), strtotime($order->purchase_date)),
 | 
			
		||||
            get_bloginfo('name')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $headers = array('Content-Type: text/plain; charset=UTF-8');
 | 
			
		||||
        
 | 
			
		||||
        return wp_mail($customer_email, $subject, $message, $headers);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Create download token for orders that don't have one (legacy orders)
 | 
			
		||||
     */
 | 
			
		||||
    public static function ensure_download_token($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Check if token already exists
 | 
			
		||||
        $existing_token = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT token FROM {$wpdb->prefix}wpdd_download_links WHERE order_id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if ($existing_token) {
 | 
			
		||||
            return $existing_token;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Create new token for legacy order
 | 
			
		||||
        $token = wp_hash(uniqid() . $order_id . time());
 | 
			
		||||
        $expires_at = date('Y-m-d H:i:s', strtotime('+7 days'));
 | 
			
		||||
        
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
            array(
 | 
			
		||||
                'order_id' => $order_id,
 | 
			
		||||
                'token' => $token,
 | 
			
		||||
                'expires_at' => $expires_at,
 | 
			
		||||
                'max_downloads' => 5,
 | 
			
		||||
                'created_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%s', '%s', '%d', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $token;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function process_download($order) {
 | 
			
		||||
        $product_id = $order->product_id;
 | 
			
		||||
        $files = get_post_meta($product_id, '_wpdd_files', true);
 | 
			
		||||
        
 | 
			
		||||
        // Debug output for admins
 | 
			
		||||
        if (current_user_can('manage_options') && empty($files)) {
 | 
			
		||||
            wp_die(sprintf(__('Debug: No files found for product ID %d. Files data: %s', 'wp-digital-download'), 
 | 
			
		||||
                $product_id, '<pre>' . print_r($files, true) . '</pre>'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (empty($files)) {
 | 
			
		||||
            wp_die(__('No files available for download.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Debug output for admins
 | 
			
		||||
        if (current_user_can('manage_options')) {
 | 
			
		||||
            error_log('WPDD Debug: Files for product ' . $product_id . ': ' . print_r($files, true));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $download_limit = get_post_meta($product_id, '_wpdd_download_limit', true);
 | 
			
		||||
        $download_expiry = get_post_meta($product_id, '_wpdd_download_expiry', true);
 | 
			
		||||
        
 | 
			
		||||
        if ($download_expiry > 0) {
 | 
			
		||||
            $expiry_date = date('Y-m-d H:i:s', strtotime($order->purchase_date . ' + ' . $download_expiry . ' days'));
 | 
			
		||||
            if (current_time('mysql') > $expiry_date) {
 | 
			
		||||
                wp_die(__('Your download period has expired.', 'wp-digital-download'));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($download_limit > 0 && $order->download_count >= $download_limit) {
 | 
			
		||||
            wp_die(__('You have reached the download limit for this product.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_orders',
 | 
			
		||||
            array('download_count' => $order->download_count + 1),
 | 
			
		||||
            array('id' => $order->id),
 | 
			
		||||
            array('%d'),
 | 
			
		||||
            array('%d')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $file_index = isset($_GET['file']) ? intval($_GET['file']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        // Handle array structure - files might be indexed or not
 | 
			
		||||
        $file_list = array_values($files); // Reindex to ensure numeric keys
 | 
			
		||||
        
 | 
			
		||||
        if (count($file_list) > 1 && !isset($_GET['file'])) {
 | 
			
		||||
            self::show_file_selection($file_list, $order);
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($file_list[$file_index])) {
 | 
			
		||||
            // Debug output for admins
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                wp_die(sprintf(__('Debug: File index %d not found. Available files: %s', 'wp-digital-download'), 
 | 
			
		||||
                    $file_index, '<pre>' . print_r($file_list, true) . '</pre>'));
 | 
			
		||||
            }
 | 
			
		||||
            wp_die(__('File not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $file = $file_list[$file_index];
 | 
			
		||||
        
 | 
			
		||||
        self::log_download($order, $product_id, $file['id'] ?? $file_index);
 | 
			
		||||
        
 | 
			
		||||
        // Debug for admins
 | 
			
		||||
        if (current_user_can('manage_options')) {
 | 
			
		||||
            error_log('WPDD Debug: Processing file - ID: ' . ($file['id'] ?? 'none') . ', URL: ' . ($file['url'] ?? 'none'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Check if this is a protected file
 | 
			
		||||
        if (isset($file['id']) && strpos($file['id'], 'wpdd_') === 0) {
 | 
			
		||||
            // This is a protected file, get its metadata
 | 
			
		||||
            $file_meta = get_option('wpdd_protected_file_' . $file['id']);
 | 
			
		||||
            
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                error_log('WPDD Debug: Protected file detected - ' . $file['id']);
 | 
			
		||||
                error_log('WPDD Debug: File meta exists: ' . ($file_meta ? 'Yes' : 'No'));
 | 
			
		||||
                if ($file_meta) {
 | 
			
		||||
                    error_log('WPDD Debug: File path exists: ' . (file_exists($file_meta['file_path']) ? 'Yes' : 'No'));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if ($file_meta && file_exists($file_meta['file_path'])) {
 | 
			
		||||
                $enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
 | 
			
		||||
                
 | 
			
		||||
                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);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Check if URL contains protected download parameter (alternative check)
 | 
			
		||||
        if (isset($file['url']) && strpos($file['url'], 'wpdd_protected_download=') !== false) {
 | 
			
		||||
            // Extract file_id from URL
 | 
			
		||||
            if (preg_match('/wpdd_protected_download=([^&]+)/', $file['url'], $matches)) {
 | 
			
		||||
                $file_id = $matches[1];
 | 
			
		||||
                $file_meta = get_option('wpdd_protected_file_' . $file_id);
 | 
			
		||||
                
 | 
			
		||||
                if (current_user_can('manage_options')) {
 | 
			
		||||
                    error_log('WPDD Debug: Protected URL detected - ' . $file_id);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if ($file_meta && file_exists($file_meta['file_path'])) {
 | 
			
		||||
                    $enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
 | 
			
		||||
                    
 | 
			
		||||
                    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);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        self::deliver_protected_file($file_meta['file_path']);
 | 
			
		||||
                    }
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Regular file handling (backward compatibility)
 | 
			
		||||
        $enable_watermark = get_post_meta($product_id, '_wpdd_enable_watermark', true);
 | 
			
		||||
        
 | 
			
		||||
        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);
 | 
			
		||||
            } else {
 | 
			
		||||
                self::deliver_file($file['url'], $file['name']);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            self::deliver_file($file['url'], $file['name']);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function show_file_selection($files, $order) {
 | 
			
		||||
        ?>
 | 
			
		||||
        <!DOCTYPE html>
 | 
			
		||||
        <html <?php language_attributes(); ?>>
 | 
			
		||||
        <head>
 | 
			
		||||
            <meta charset="<?php bloginfo('charset'); ?>">
 | 
			
		||||
            <title><?php _e('Select File to Download', 'wp-digital-download'); ?></title>
 | 
			
		||||
            <style>
 | 
			
		||||
                body {
 | 
			
		||||
                    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
 | 
			
		||||
                    max-width: 600px;
 | 
			
		||||
                    margin: 50px auto;
 | 
			
		||||
                    padding: 20px;
 | 
			
		||||
                    background: #f5f5f5;
 | 
			
		||||
                }
 | 
			
		||||
                .container {
 | 
			
		||||
                    background: white;
 | 
			
		||||
                    padding: 30px;
 | 
			
		||||
                    border-radius: 8px;
 | 
			
		||||
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
 | 
			
		||||
                }
 | 
			
		||||
                h1 {
 | 
			
		||||
                    color: #333;
 | 
			
		||||
                    margin-bottom: 20px;
 | 
			
		||||
                }
 | 
			
		||||
                .file-list {
 | 
			
		||||
                    list-style: none;
 | 
			
		||||
                    padding: 0;
 | 
			
		||||
                }
 | 
			
		||||
                .file-item {
 | 
			
		||||
                    margin-bottom: 15px;
 | 
			
		||||
                    padding: 15px;
 | 
			
		||||
                    background: #f9f9f9;
 | 
			
		||||
                    border-radius: 4px;
 | 
			
		||||
                    display: flex;
 | 
			
		||||
                    justify-content: space-between;
 | 
			
		||||
                    align-items: center;
 | 
			
		||||
                }
 | 
			
		||||
                .file-name {
 | 
			
		||||
                    font-weight: 500;
 | 
			
		||||
                }
 | 
			
		||||
                .download-btn {
 | 
			
		||||
                    padding: 8px 16px;
 | 
			
		||||
                    background: #2271b1;
 | 
			
		||||
                    color: white;
 | 
			
		||||
                    text-decoration: none;
 | 
			
		||||
                    border-radius: 4px;
 | 
			
		||||
                    transition: background 0.2s;
 | 
			
		||||
                }
 | 
			
		||||
                .download-btn:hover {
 | 
			
		||||
                    background: #135e96;
 | 
			
		||||
                }
 | 
			
		||||
            </style>
 | 
			
		||||
        </head>
 | 
			
		||||
        <body>
 | 
			
		||||
            <div class="container">
 | 
			
		||||
                <h1><?php _e('Select File to Download', 'wp-digital-download'); ?></h1>
 | 
			
		||||
                <ul class="file-list">
 | 
			
		||||
                    <?php foreach ($files as $index => $file) : ?>
 | 
			
		||||
                        <li class="file-item">
 | 
			
		||||
                            <span class="file-name"><?php echo esc_html($file['name'] ?? 'File ' . ($index + 1)); ?></span>
 | 
			
		||||
                            <a href="<?php echo add_query_arg('file', $index); ?>" class="download-btn">
 | 
			
		||||
                                <?php _e('Download', 'wp-digital-download'); ?>
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                    <?php endforeach; ?>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
        </body>
 | 
			
		||||
        </html>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_secure_file_download() {
 | 
			
		||||
        if (!isset($_GET['wpdd_file_download'])) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $attachment_id = intval($_GET['wpdd_file_download']);
 | 
			
		||||
        $token = sanitize_text_field($_GET['token'] ?? '');
 | 
			
		||||
        
 | 
			
		||||
        if (!$attachment_id || !$token) {
 | 
			
		||||
            wp_die(__('Invalid download link.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Verify token
 | 
			
		||||
        $stored_token = get_post_meta($attachment_id, '_wpdd_download_token', true);
 | 
			
		||||
        if ($token !== $stored_token) {
 | 
			
		||||
            wp_die(__('Invalid download token.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Check if user has permission to download
 | 
			
		||||
        if (!is_user_logged_in()) {
 | 
			
		||||
            wp_die(__('You must be logged in to download this file.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get protected file path
 | 
			
		||||
        $protected_path = get_post_meta($attachment_id, '_wpdd_protected_path', true);
 | 
			
		||||
        if (!$protected_path || !file_exists($protected_path)) {
 | 
			
		||||
            // Fallback to regular attachment path
 | 
			
		||||
            $protected_path = get_attached_file($attachment_id);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$protected_path || !file_exists($protected_path)) {
 | 
			
		||||
            wp_die(__('File not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Deliver the file
 | 
			
		||||
        self::deliver_protected_file($protected_path);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function deliver_protected_file($file_path, $is_temp = false) {
 | 
			
		||||
        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);
 | 
			
		||||
        
 | 
			
		||||
        nocache_headers();
 | 
			
		||||
        
 | 
			
		||||
        header('Content-Type: ' . ($file_type['type'] ?: 'application/octet-stream'));
 | 
			
		||||
        header('Content-Disposition: attachment; filename="' . $file_name . '"');
 | 
			
		||||
        header('Content-Length: ' . $file_size);
 | 
			
		||||
        header('Content-Transfer-Encoding: binary');
 | 
			
		||||
        
 | 
			
		||||
        if (ob_get_level()) {
 | 
			
		||||
            ob_end_clean();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        readfile($file_path);
 | 
			
		||||
        
 | 
			
		||||
        if ($is_temp && file_exists($file_path)) {
 | 
			
		||||
            unlink($file_path);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function deliver_file($file_path, $file_name, $is_temp = false) {
 | 
			
		||||
        $original_path = $file_path;
 | 
			
		||||
        
 | 
			
		||||
        // Check if this is a protected file URL
 | 
			
		||||
        if (strpos($file_path, 'wpdd_file_download=') !== false) {
 | 
			
		||||
            // This is already a protected URL, just redirect to it
 | 
			
		||||
            wp_redirect($file_path);
 | 
			
		||||
            exit;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Check if file is in protected directory
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        if (strpos($file_path, $protected_dir) === 0) {
 | 
			
		||||
            // File is in protected directory, deliver directly
 | 
			
		||||
            if (!file_exists($file_path)) {
 | 
			
		||||
                wp_die(__('Protected file not found.', 'wp-digital-download'));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            self::deliver_protected_file($file_path);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Convert URL to file path if needed
 | 
			
		||||
        if (filter_var($file_path, FILTER_VALIDATE_URL)) {
 | 
			
		||||
            $upload_dir = wp_upload_dir();
 | 
			
		||||
            $site_url = get_site_url();
 | 
			
		||||
            
 | 
			
		||||
            // Debug logging
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                error_log('WPDD Debug: Original URL: ' . $file_path);
 | 
			
		||||
                error_log('WPDD Debug: Upload baseurl: ' . $upload_dir['baseurl']);
 | 
			
		||||
                error_log('WPDD Debug: Upload basedir: ' . $upload_dir['basedir']);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Handle various URL formats
 | 
			
		||||
            if (strpos($file_path, $upload_dir['baseurl']) === 0) {
 | 
			
		||||
                $file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file_path);
 | 
			
		||||
            } elseif (strpos($file_path, $site_url) === 0) {
 | 
			
		||||
                // Handle site URL paths
 | 
			
		||||
                $file_path = str_replace($site_url . '/wp-content/uploads', $upload_dir['basedir'], $file_path);
 | 
			
		||||
            } else {
 | 
			
		||||
                // External URL - just redirect
 | 
			
		||||
                wp_redirect($file_path);
 | 
			
		||||
                exit;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Debug logging
 | 
			
		||||
        if (current_user_can('manage_options')) {
 | 
			
		||||
            error_log('WPDD Debug: Final file path: ' . $file_path);
 | 
			
		||||
            error_log('WPDD Debug: File exists: ' . (file_exists($file_path) ? 'Yes' : 'No'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($file_path)) {
 | 
			
		||||
            // Debug output for admin users
 | 
			
		||||
            if (current_user_can('manage_options')) {
 | 
			
		||||
                wp_die(sprintf(__('File not found at path: %s<br>Original: %s', 'wp-digital-download'), 
 | 
			
		||||
                    $file_path, $original_path));
 | 
			
		||||
            }
 | 
			
		||||
            wp_die(__('File not found. Please contact the administrator.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $file_size = filesize($file_path);
 | 
			
		||||
        $file_type = wp_check_filetype($file_path);
 | 
			
		||||
        
 | 
			
		||||
        if (empty($file_name)) {
 | 
			
		||||
            $file_name = basename($file_path);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        nocache_headers();
 | 
			
		||||
        
 | 
			
		||||
        header('Content-Type: ' . ($file_type['type'] ?: 'application/octet-stream'));
 | 
			
		||||
        header('Content-Disposition: attachment; filename="' . $file_name . '"');
 | 
			
		||||
        header('Content-Length: ' . $file_size);
 | 
			
		||||
        header('Content-Transfer-Encoding: binary');
 | 
			
		||||
        
 | 
			
		||||
        if (ob_get_level()) {
 | 
			
		||||
            ob_end_clean();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        readfile($file_path);
 | 
			
		||||
        
 | 
			
		||||
        if ($is_temp && file_exists($file_path)) {
 | 
			
		||||
            unlink($file_path);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function log_download($order, $product_id, $file_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_downloads',
 | 
			
		||||
            array(
 | 
			
		||||
                'order_id' => $order->id,
 | 
			
		||||
                'product_id' => $product_id,
 | 
			
		||||
                'customer_id' => $order->customer_id,
 | 
			
		||||
                'file_id' => $file_id,
 | 
			
		||||
                'download_date' => current_time('mysql'),
 | 
			
		||||
                'ip_address' => $_SERVER['REMOTE_ADDR'],
 | 
			
		||||
                'user_agent' => $_SERVER['HTTP_USER_AGENT']
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%d', '%d', '%s', '%s', '%s', '%s')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function is_watermarkable($file_url) {
 | 
			
		||||
        $supported_types = array('jpg', 'jpeg', 'png', 'gif', 'pdf');
 | 
			
		||||
        $file_extension = strtolower(pathinfo($file_url, PATHINFO_EXTENSION));
 | 
			
		||||
        return in_array($file_extension, $supported_types);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										180
									
								
								includes/class-wpdd-file-protection.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								includes/class-wpdd-file-protection.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_File_Protection {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('template_redirect', array(__CLASS__, 'protect_direct_access'));
 | 
			
		||||
        add_filter('mod_rewrite_rules', array(__CLASS__, 'add_rewrite_rules'));
 | 
			
		||||
        add_action('init', array(__CLASS__, 'add_download_endpoint'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function protect_direct_access() {
 | 
			
		||||
        $request_uri = $_SERVER['REQUEST_URI'];
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = '/' . WPDD_UPLOADS_DIR . '/';
 | 
			
		||||
        
 | 
			
		||||
        if (strpos($request_uri, $protected_dir) !== false) {
 | 
			
		||||
            wp_die(__('Direct access to this file is not allowed.', 'wp-digital-download'), 403);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_rewrite_rules($rules) {
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_path = str_replace(ABSPATH, '', $upload_dir['basedir']) . '/' . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        $new_rules = "# WP Digital Download Protection\n";
 | 
			
		||||
        $new_rules .= "<IfModule mod_rewrite.c>\n";
 | 
			
		||||
        $new_rules .= "RewriteRule ^" . $protected_path . "/.*$ - [F,L]\n";
 | 
			
		||||
        $new_rules .= "</IfModule>\n\n";
 | 
			
		||||
        
 | 
			
		||||
        return $new_rules . $rules;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_download_endpoint() {
 | 
			
		||||
        add_rewrite_endpoint('wpdd-download', EP_ROOT);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function move_to_protected_directory($attachment_id) {
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($protected_dir)) {
 | 
			
		||||
            wp_mkdir_p($protected_dir);
 | 
			
		||||
            self::create_protection_files($protected_dir);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $file_path = get_attached_file($attachment_id);
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($file_path)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $filename = basename($file_path);
 | 
			
		||||
        $unique_filename = wp_unique_filename($protected_dir, $filename);
 | 
			
		||||
        $new_path = trailingslashit($protected_dir) . $unique_filename;
 | 
			
		||||
        
 | 
			
		||||
        if (copy($file_path, $new_path)) {
 | 
			
		||||
            $protected_url = trailingslashit($upload_dir['baseurl']) . WPDD_UPLOADS_DIR . '/' . $unique_filename;
 | 
			
		||||
            
 | 
			
		||||
            update_post_meta($attachment_id, '_wpdd_protected_file', $new_path);
 | 
			
		||||
            update_post_meta($attachment_id, '_wpdd_protected_url', $protected_url);
 | 
			
		||||
            
 | 
			
		||||
            return array(
 | 
			
		||||
                'path' => $new_path,
 | 
			
		||||
                'url' => $protected_url
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function create_protection_files($directory) {
 | 
			
		||||
        $htaccess_content = "Options -Indexes\n";
 | 
			
		||||
        $htaccess_content .= "deny from all\n";
 | 
			
		||||
        
 | 
			
		||||
        $htaccess_file = trailingslashit($directory) . '.htaccess';
 | 
			
		||||
        if (!file_exists($htaccess_file)) {
 | 
			
		||||
            file_put_contents($htaccess_file, $htaccess_content);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $index_content = "<?php\n// Silence is golden.\n";
 | 
			
		||||
        $index_file = trailingslashit($directory) . 'index.php';
 | 
			
		||||
        if (!file_exists($index_file)) {
 | 
			
		||||
            file_put_contents($index_file, $index_content);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $nginx_content = "location ~* ^/wp-content/uploads/" . WPDD_UPLOADS_DIR . " {\n";
 | 
			
		||||
        $nginx_content .= "    deny all;\n";
 | 
			
		||||
        $nginx_content .= "    return 403;\n";
 | 
			
		||||
        $nginx_content .= "}\n";
 | 
			
		||||
        
 | 
			
		||||
        $nginx_file = trailingslashit($directory) . 'nginx.conf';
 | 
			
		||||
        if (!file_exists($nginx_file)) {
 | 
			
		||||
            file_put_contents($nginx_file, $nginx_content);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_protected_file_url($file_path) {
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        if (strpos($file_path, $protected_dir) === 0) {
 | 
			
		||||
            $relative_path = str_replace($protected_dir, '', $file_path);
 | 
			
		||||
            return trailingslashit($upload_dir['baseurl']) . WPDD_UPLOADS_DIR . $relative_path;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $file_path;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function is_protected_file($file_path) {
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protected_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        return strpos($file_path, $protected_dir) === 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function generate_secure_download_url($file_path, $order_id, $expiry_hours = 24) {
 | 
			
		||||
        $token = wp_hash($file_path . $order_id . time());
 | 
			
		||||
        $expiry = time() + ($expiry_hours * 3600);
 | 
			
		||||
        
 | 
			
		||||
        set_transient('wpdd_download_' . $token, array(
 | 
			
		||||
            'file_path' => $file_path,
 | 
			
		||||
            'order_id' => $order_id,
 | 
			
		||||
            'expiry' => $expiry
 | 
			
		||||
        ), $expiry_hours * 3600);
 | 
			
		||||
        
 | 
			
		||||
        return add_query_arg(array(
 | 
			
		||||
            'wpdd_secure_download' => $token
 | 
			
		||||
        ), home_url());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function handle_secure_download() {
 | 
			
		||||
        if (!isset($_GET['wpdd_secure_download'])) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $token = sanitize_text_field($_GET['wpdd_secure_download']);
 | 
			
		||||
        $data = get_transient('wpdd_download_' . $token);
 | 
			
		||||
        
 | 
			
		||||
        if (!$data) {
 | 
			
		||||
            wp_die(__('Invalid or expired download link.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($data['expiry'] < time()) {
 | 
			
		||||
            delete_transient('wpdd_download_' . $token);
 | 
			
		||||
            wp_die(__('This download link has expired.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($data['file_path'])) {
 | 
			
		||||
            wp_die(__('File not found.', 'wp-digital-download'));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        delete_transient('wpdd_download_' . $token);
 | 
			
		||||
        
 | 
			
		||||
        self::serve_download($data['file_path']);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function serve_download($file_path) {
 | 
			
		||||
        $file_name = basename($file_path);
 | 
			
		||||
        $file_size = filesize($file_path);
 | 
			
		||||
        $file_type = wp_check_filetype($file_path);
 | 
			
		||||
        
 | 
			
		||||
        nocache_headers();
 | 
			
		||||
        
 | 
			
		||||
        header('Content-Type: ' . ($file_type['type'] ?: 'application/octet-stream'));
 | 
			
		||||
        header('Content-Disposition: attachment; filename="' . $file_name . '"');
 | 
			
		||||
        header('Content-Length: ' . $file_size);
 | 
			
		||||
        header('Content-Transfer-Encoding: binary');
 | 
			
		||||
        
 | 
			
		||||
        if (ob_get_level()) {
 | 
			
		||||
            ob_end_clean();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        readfile($file_path);
 | 
			
		||||
        exit;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										215
									
								
								includes/class-wpdd-install.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								includes/class-wpdd-install.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Install {
 | 
			
		||||
    
 | 
			
		||||
    public static function activate() {
 | 
			
		||||
        // Load required files if not already loaded
 | 
			
		||||
        if (!class_exists('WPDD_Post_Types')) {
 | 
			
		||||
            require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-post-types.php';
 | 
			
		||||
        }
 | 
			
		||||
        if (!class_exists('WPDD_Roles')) {
 | 
			
		||||
            require_once WPDD_PLUGIN_PATH . 'includes/class-wpdd-roles.php';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Register post types and taxonomies before flushing rules
 | 
			
		||||
        WPDD_Post_Types::register_post_types();
 | 
			
		||||
        WPDD_Post_Types::register_taxonomies();
 | 
			
		||||
        
 | 
			
		||||
        self::create_tables();
 | 
			
		||||
        self::create_pages();
 | 
			
		||||
        self::create_upload_protection();
 | 
			
		||||
        
 | 
			
		||||
        WPDD_Roles::create_roles();
 | 
			
		||||
        
 | 
			
		||||
        // Flush rewrite rules after post types are registered
 | 
			
		||||
        flush_rewrite_rules();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function deactivate() {
 | 
			
		||||
        flush_rewrite_rules();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function create_tables() {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $charset_collate = $wpdb->get_charset_collate();
 | 
			
		||||
        
 | 
			
		||||
        $sql = array();
 | 
			
		||||
        
 | 
			
		||||
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_orders (
 | 
			
		||||
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
            order_number varchar(50) NOT NULL,
 | 
			
		||||
            product_id bigint(20) NOT NULL,
 | 
			
		||||
            customer_id bigint(20) NOT NULL,
 | 
			
		||||
            creator_id bigint(20) NOT NULL,
 | 
			
		||||
            status varchar(20) NOT NULL DEFAULT 'pending',
 | 
			
		||||
            payment_method varchar(50) DEFAULT NULL,
 | 
			
		||||
            transaction_id varchar(100) DEFAULT NULL,
 | 
			
		||||
            amount decimal(10,2) NOT NULL,
 | 
			
		||||
            currency varchar(10) NOT NULL DEFAULT 'USD',
 | 
			
		||||
            customer_email varchar(100) NOT NULL,
 | 
			
		||||
            customer_name varchar(100) DEFAULT NULL,
 | 
			
		||||
            purchase_date datetime DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
            download_count int(11) DEFAULT 0,
 | 
			
		||||
            notes text DEFAULT NULL,
 | 
			
		||||
            PRIMARY KEY (id),
 | 
			
		||||
            KEY order_number (order_number),
 | 
			
		||||
            KEY product_id (product_id),
 | 
			
		||||
            KEY customer_id (customer_id),
 | 
			
		||||
            KEY creator_id (creator_id),
 | 
			
		||||
            KEY status (status)
 | 
			
		||||
        ) $charset_collate;";
 | 
			
		||||
        
 | 
			
		||||
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_downloads (
 | 
			
		||||
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
            order_id bigint(20) NOT NULL,
 | 
			
		||||
            product_id bigint(20) NOT NULL,
 | 
			
		||||
            customer_id bigint(20) NOT NULL,
 | 
			
		||||
            file_id varchar(100) NOT NULL,
 | 
			
		||||
            download_date datetime DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
            ip_address varchar(45) DEFAULT NULL,
 | 
			
		||||
            user_agent text DEFAULT NULL,
 | 
			
		||||
            PRIMARY KEY (id),
 | 
			
		||||
            KEY order_id (order_id),
 | 
			
		||||
            KEY product_id (product_id),
 | 
			
		||||
            KEY customer_id (customer_id)
 | 
			
		||||
        ) $charset_collate;";
 | 
			
		||||
        
 | 
			
		||||
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_download_links (
 | 
			
		||||
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
            order_id bigint(20) NOT NULL,
 | 
			
		||||
            token varchar(64) NOT NULL,
 | 
			
		||||
            expires_at datetime NOT NULL,
 | 
			
		||||
            download_count int(11) DEFAULT 0,
 | 
			
		||||
            max_downloads int(11) DEFAULT 5,
 | 
			
		||||
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
            PRIMARY KEY (id),
 | 
			
		||||
            UNIQUE KEY token (token),
 | 
			
		||||
            KEY order_id (order_id)
 | 
			
		||||
        ) $charset_collate;";
 | 
			
		||||
        
 | 
			
		||||
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_creator_earnings (
 | 
			
		||||
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
            creator_id bigint(20) NOT NULL,
 | 
			
		||||
            order_id bigint(20) NOT NULL,
 | 
			
		||||
            product_id bigint(20) NOT NULL,
 | 
			
		||||
            sale_amount decimal(10,2) NOT NULL,
 | 
			
		||||
            commission_rate decimal(5,2) NOT NULL,
 | 
			
		||||
            creator_earning decimal(10,2) NOT NULL,
 | 
			
		||||
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
            PRIMARY KEY (id),
 | 
			
		||||
            KEY creator_id (creator_id),
 | 
			
		||||
            KEY order_id (order_id),
 | 
			
		||||
            KEY product_id (product_id)
 | 
			
		||||
        ) $charset_collate;";
 | 
			
		||||
        
 | 
			
		||||
        $sql[] = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpdd_payouts (
 | 
			
		||||
            id bigint(20) NOT NULL AUTO_INCREMENT,
 | 
			
		||||
            creator_id bigint(20) NOT NULL,
 | 
			
		||||
            amount decimal(10,2) NOT NULL,
 | 
			
		||||
            currency varchar(10) NOT NULL,
 | 
			
		||||
            paypal_email varchar(100) NOT NULL,
 | 
			
		||||
            transaction_id varchar(100) DEFAULT NULL,
 | 
			
		||||
            status varchar(20) NOT NULL DEFAULT 'pending',
 | 
			
		||||
            payout_method varchar(20) NOT NULL DEFAULT 'manual',
 | 
			
		||||
            notes text DEFAULT NULL,
 | 
			
		||||
            processed_by bigint(20) DEFAULT NULL,
 | 
			
		||||
            processed_at datetime DEFAULT NULL,
 | 
			
		||||
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
            PRIMARY KEY (id),
 | 
			
		||||
            KEY creator_id (creator_id),
 | 
			
		||||
            KEY status (status),
 | 
			
		||||
            KEY transaction_id (transaction_id)
 | 
			
		||||
        ) $charset_collate;";
 | 
			
		||||
        
 | 
			
		||||
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
 | 
			
		||||
        
 | 
			
		||||
        foreach ($sql as $query) {
 | 
			
		||||
            dbDelta($query);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        update_option('wpdd_db_version', WPDD_VERSION);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function create_pages() {
 | 
			
		||||
        $pages = array(
 | 
			
		||||
            'shop' => array(
 | 
			
		||||
                'title' => __('Shop', 'wp-digital-download'),
 | 
			
		||||
                'content' => '[wpdd_shop]',
 | 
			
		||||
                'option' => 'wpdd_shop_page_id'
 | 
			
		||||
            ),
 | 
			
		||||
            'my-purchases' => array(
 | 
			
		||||
                'title' => __('My Purchases', 'wp-digital-download'),
 | 
			
		||||
                'content' => '[wpdd_customer_purchases]',
 | 
			
		||||
                'option' => 'wpdd_purchases_page_id'
 | 
			
		||||
            ),
 | 
			
		||||
            'checkout' => array(
 | 
			
		||||
                'title' => __('Checkout', 'wp-digital-download'),
 | 
			
		||||
                'content' => '[wpdd_checkout]',
 | 
			
		||||
                'option' => 'wpdd_checkout_page_id'
 | 
			
		||||
            ),
 | 
			
		||||
            'thank-you' => array(
 | 
			
		||||
                'title' => __('Thank You', 'wp-digital-download'),
 | 
			
		||||
                'content' => '[wpdd_thank_you]',
 | 
			
		||||
                'option' => 'wpdd_thank_you_page_id'
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        foreach ($pages as $slug => $page) {
 | 
			
		||||
            // Check if page already exists
 | 
			
		||||
            $existing_page_id = get_option($page['option']);
 | 
			
		||||
            if ($existing_page_id && get_post($existing_page_id)) {
 | 
			
		||||
                continue; // Page already exists, skip creation
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Check if a page with this slug already exists
 | 
			
		||||
            $existing_page = get_page_by_path($slug);
 | 
			
		||||
            if ($existing_page) {
 | 
			
		||||
                update_option($page['option'], $existing_page->ID);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Create the page
 | 
			
		||||
            $page_id = wp_insert_post(array(
 | 
			
		||||
                'post_title' => $page['title'],
 | 
			
		||||
                'post_content' => $page['content'],
 | 
			
		||||
                'post_status' => 'publish',
 | 
			
		||||
                'post_type' => 'page',
 | 
			
		||||
                'post_name' => $slug
 | 
			
		||||
            ));
 | 
			
		||||
            
 | 
			
		||||
            if ($page_id && !is_wp_error($page_id)) {
 | 
			
		||||
                update_option($page['option'], $page_id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function create_upload_protection() {
 | 
			
		||||
        $upload_dir = wp_upload_dir();
 | 
			
		||||
        $protection_dir = trailingslashit($upload_dir['basedir']) . WPDD_UPLOADS_DIR;
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($protection_dir)) {
 | 
			
		||||
            wp_mkdir_p($protection_dir);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $htaccess_content = "Options -Indexes\n";
 | 
			
		||||
        $htaccess_content .= "deny from all\n";
 | 
			
		||||
        
 | 
			
		||||
        $htaccess_file = trailingslashit($protection_dir) . '.htaccess';
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($htaccess_file)) {
 | 
			
		||||
            file_put_contents($htaccess_file, $htaccess_content);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $index_content = "<?php\n// Silence is golden.\n";
 | 
			
		||||
        $index_file = trailingslashit($protection_dir) . 'index.php';
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($index_file)) {
 | 
			
		||||
            file_put_contents($index_file, $index_content);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										308
									
								
								includes/class-wpdd-metaboxes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								includes/class-wpdd-metaboxes.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,308 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Metaboxes {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('add_meta_boxes', array(__CLASS__, 'add_meta_boxes'));
 | 
			
		||||
        add_action('save_post_wpdd_product', array(__CLASS__, 'save_product_meta'), 10, 2);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_meta_boxes() {
 | 
			
		||||
        add_meta_box(
 | 
			
		||||
            'wpdd_product_pricing',
 | 
			
		||||
            __('Product Pricing', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'render_pricing_metabox'),
 | 
			
		||||
            'wpdd_product',
 | 
			
		||||
            'side',
 | 
			
		||||
            'high'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_meta_box(
 | 
			
		||||
            'wpdd_product_files',
 | 
			
		||||
            __('Downloadable Files', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'render_files_metabox'),
 | 
			
		||||
            'wpdd_product',
 | 
			
		||||
            'normal',
 | 
			
		||||
            'high'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_meta_box(
 | 
			
		||||
            'wpdd_product_settings',
 | 
			
		||||
            __('Product Settings', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'render_settings_metabox'),
 | 
			
		||||
            'wpdd_product',
 | 
			
		||||
            'normal',
 | 
			
		||||
            'default'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        add_meta_box(
 | 
			
		||||
            'wpdd_product_stats',
 | 
			
		||||
            __('Product Statistics', 'wp-digital-download'),
 | 
			
		||||
            array(__CLASS__, 'render_stats_metabox'),
 | 
			
		||||
            'wpdd_product',
 | 
			
		||||
            'side',
 | 
			
		||||
            'low'
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_pricing_metabox($post) {
 | 
			
		||||
        wp_nonce_field('wpdd_save_product_meta', 'wpdd_product_meta_nonce');
 | 
			
		||||
        
 | 
			
		||||
        $price = get_post_meta($post->ID, '_wpdd_price', true);
 | 
			
		||||
        $is_free = get_post_meta($post->ID, '_wpdd_is_free', true);
 | 
			
		||||
        $sale_price = get_post_meta($post->ID, '_wpdd_sale_price', true);
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wpdd-metabox-content">
 | 
			
		||||
            <p>
 | 
			
		||||
                <label>
 | 
			
		||||
                    <input type="checkbox" name="wpdd_is_free" id="wpdd_is_free" value="1" <?php checked($is_free, '1'); ?> />
 | 
			
		||||
                    <?php _e('This is a free product', 'wp-digital-download'); ?>
 | 
			
		||||
                </label>
 | 
			
		||||
            </p>
 | 
			
		||||
            
 | 
			
		||||
            <p class="wpdd-price-field">
 | 
			
		||||
                <label for="wpdd_price"><?php _e('Regular Price', 'wp-digital-download'); ?> ($)</label>
 | 
			
		||||
                <input type="number" id="wpdd_price" name="wpdd_price" value="<?php echo esc_attr($price); ?>" step="0.01" min="0" />
 | 
			
		||||
            </p>
 | 
			
		||||
            
 | 
			
		||||
            <p class="wpdd-price-field">
 | 
			
		||||
                <label for="wpdd_sale_price"><?php _e('Sale Price', 'wp-digital-download'); ?> ($)</label>
 | 
			
		||||
                <input type="number" id="wpdd_sale_price" name="wpdd_sale_price" value="<?php echo esc_attr($sale_price); ?>" step="0.01" min="0" />
 | 
			
		||||
                <span class="description"><?php _e('Leave blank for no sale', 'wp-digital-download'); ?></span>
 | 
			
		||||
            </p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_files_metabox($post) {
 | 
			
		||||
        $files = get_post_meta($post->ID, '_wpdd_files', true);
 | 
			
		||||
        if (!is_array($files)) {
 | 
			
		||||
            $files = array();
 | 
			
		||||
        }
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wpdd-files-container">
 | 
			
		||||
            <div id="wpdd-files-list">
 | 
			
		||||
                <?php if (!empty($files)) : ?>
 | 
			
		||||
                    <?php foreach ($files as $index => $file) : ?>
 | 
			
		||||
                        <div class="wpdd-file-item" data-index="<?php echo $index; ?>">
 | 
			
		||||
                            <div class="wpdd-file-header">
 | 
			
		||||
                                <span class="wpdd-file-handle dashicons dashicons-menu"></span>
 | 
			
		||||
                                <input type="text" name="wpdd_files[<?php echo $index; ?>][name]" 
 | 
			
		||||
                                       value="<?php echo esc_attr($file['name']); ?>" 
 | 
			
		||||
                                       placeholder="<?php _e('File Name', 'wp-digital-download'); ?>" />
 | 
			
		||||
                                <button type="button" class="button wpdd-remove-file">
 | 
			
		||||
                                    <?php _e('Remove', 'wp-digital-download'); ?>
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="wpdd-file-content">
 | 
			
		||||
                                <div class="wpdd-file-url">
 | 
			
		||||
                                    <input type="text" name="wpdd_files[<?php echo $index; ?>][url]" 
 | 
			
		||||
                                           value="<?php echo esc_url($file['url']); ?>" 
 | 
			
		||||
                                           placeholder="<?php _e('File URL', 'wp-digital-download'); ?>" 
 | 
			
		||||
                                           class="wpdd-file-url-input" />
 | 
			
		||||
                                    <button type="button" class="button wpdd-upload-file">
 | 
			
		||||
                                        <?php _e('Upload File', 'wp-digital-download'); ?>
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <input type="hidden" name="wpdd_files[<?php echo $index; ?>][id]" 
 | 
			
		||||
                                       value="<?php echo esc_attr($file['id']); ?>" 
 | 
			
		||||
                                       class="wpdd-file-id" />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    <?php endforeach; ?>
 | 
			
		||||
                <?php endif; ?>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <button type="button" id="wpdd-add-file" class="button button-primary">
 | 
			
		||||
                <?php _e('Add File', 'wp-digital-download'); ?>
 | 
			
		||||
            </button>
 | 
			
		||||
            
 | 
			
		||||
            <template id="wpdd-file-template">
 | 
			
		||||
                <div class="wpdd-file-item" data-index="">
 | 
			
		||||
                    <div class="wpdd-file-header">
 | 
			
		||||
                        <span class="wpdd-file-handle dashicons dashicons-menu"></span>
 | 
			
		||||
                        <input type="text" name="wpdd_files[INDEX][name]" 
 | 
			
		||||
                               placeholder="<?php _e('File Name', 'wp-digital-download'); ?>" />
 | 
			
		||||
                        <button type="button" class="button wpdd-remove-file">
 | 
			
		||||
                            <?php _e('Remove', 'wp-digital-download'); ?>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="wpdd-file-content">
 | 
			
		||||
                        <div class="wpdd-file-url">
 | 
			
		||||
                            <input type="text" name="wpdd_files[INDEX][url]" 
 | 
			
		||||
                                   placeholder="<?php _e('File URL', 'wp-digital-download'); ?>" 
 | 
			
		||||
                                   class="wpdd-file-url-input" />
 | 
			
		||||
                            <button type="button" class="button wpdd-upload-file">
 | 
			
		||||
                                <?php _e('Upload File', 'wp-digital-download'); ?>
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <input type="hidden" name="wpdd_files[INDEX][id]" class="wpdd-file-id" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </div>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_settings_metabox($post) {
 | 
			
		||||
        $download_limit = get_post_meta($post->ID, '_wpdd_download_limit', true);
 | 
			
		||||
        $download_expiry = get_post_meta($post->ID, '_wpdd_download_expiry', true);
 | 
			
		||||
        $enable_watermark = get_post_meta($post->ID, '_wpdd_enable_watermark', true);
 | 
			
		||||
        $watermark_text = get_post_meta($post->ID, '_wpdd_watermark_text', true);
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wpdd-settings-grid">
 | 
			
		||||
            <div class="wpdd-setting-group">
 | 
			
		||||
                <h4><?php _e('Download Settings', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                
 | 
			
		||||
                <p>
 | 
			
		||||
                    <label for="wpdd_download_limit">
 | 
			
		||||
                        <?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" />
 | 
			
		||||
                    <span class="description">
 | 
			
		||||
                        <?php _e('Number of times a customer can download after purchase. 0 = unlimited', 'wp-digital-download'); ?>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </p>
 | 
			
		||||
                
 | 
			
		||||
                <p>
 | 
			
		||||
                    <label for="wpdd_download_expiry">
 | 
			
		||||
                        <?php _e('Download Expiry (days)', 'wp-digital-download'); ?>
 | 
			
		||||
                    </label>
 | 
			
		||||
                    <input type="number" id="wpdd_download_expiry" name="wpdd_download_expiry" 
 | 
			
		||||
                           value="<?php echo esc_attr($download_expiry ?: 30); ?>" min="0" />
 | 
			
		||||
                    <span class="description">
 | 
			
		||||
                        <?php _e('Number of days download link remains valid. 0 = never expires', 'wp-digital-download'); ?>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div class="wpdd-setting-group">
 | 
			
		||||
                <h4><?php _e('Watermark Settings', 'wp-digital-download'); ?></h4>
 | 
			
		||||
                
 | 
			
		||||
                <p>
 | 
			
		||||
                    <label>
 | 
			
		||||
                        <input type="checkbox" name="wpdd_enable_watermark" value="1" 
 | 
			
		||||
                               <?php checked($enable_watermark, '1'); ?> />
 | 
			
		||||
                        <?php _e('Enable watermarking for images and PDFs', 'wp-digital-download'); ?>
 | 
			
		||||
                    </label>
 | 
			
		||||
                </p>
 | 
			
		||||
                
 | 
			
		||||
                <p>
 | 
			
		||||
                    <label for="wpdd_watermark_text">
 | 
			
		||||
                        <?php _e('Watermark Text', 'wp-digital-download'); ?>
 | 
			
		||||
                    </label>
 | 
			
		||||
                    <input type="text" id="wpdd_watermark_text" name="wpdd_watermark_text" 
 | 
			
		||||
                           value="<?php echo esc_attr($watermark_text); ?>" 
 | 
			
		||||
                           placeholder="<?php _e('e.g., {customer_email} - {order_id}', 'wp-digital-download'); ?>" />
 | 
			
		||||
                    <span class="description">
 | 
			
		||||
                        <?php _e('Available placeholders: {customer_name}, {customer_email}, {order_id}, {date}', 'wp-digital-download'); ?>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function render_stats_metabox($post) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $total_sales = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
             WHERE product_id = %d AND status = 'completed'",
 | 
			
		||||
            $post->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $total_revenue = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT SUM(amount) FROM {$wpdb->prefix}wpdd_orders 
 | 
			
		||||
             WHERE product_id = %d AND status = 'completed'",
 | 
			
		||||
            $post->ID
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $total_downloads = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT COUNT(*) FROM {$wpdb->prefix}wpdd_downloads 
 | 
			
		||||
             WHERE product_id = %d",
 | 
			
		||||
            $post->ID
 | 
			
		||||
        ));
 | 
			
		||||
        ?>
 | 
			
		||||
        <div class="wpdd-stats">
 | 
			
		||||
            <p>
 | 
			
		||||
                <strong><?php _e('Total Sales:', 'wp-digital-download'); ?></strong> 
 | 
			
		||||
                <?php echo intval($total_sales); ?>
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
                <strong><?php _e('Total Revenue:', 'wp-digital-download'); ?></strong> 
 | 
			
		||||
                $<?php echo number_format(floatval($total_revenue), 2); ?>
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
                <strong><?php _e('Total Downloads:', 'wp-digital-download'); ?></strong> 
 | 
			
		||||
                <?php echo intval($total_downloads); ?>
 | 
			
		||||
            </p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <?php
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function save_product_meta($post_id, $post) {
 | 
			
		||||
        if (!isset($_POST['wpdd_product_meta_nonce']) || 
 | 
			
		||||
            !wp_verify_nonce($_POST['wpdd_product_meta_nonce'], 'wpdd_save_product_meta')) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!current_user_can('edit_post', $post_id)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        update_post_meta($post_id, '_wpdd_is_free', 
 | 
			
		||||
            isset($_POST['wpdd_is_free']) ? '1' : '0');
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_POST['wpdd_price'])) {
 | 
			
		||||
            update_post_meta($post_id, '_wpdd_price', 
 | 
			
		||||
                floatval($_POST['wpdd_price']));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_POST['wpdd_sale_price'])) {
 | 
			
		||||
            update_post_meta($post_id, '_wpdd_sale_price', 
 | 
			
		||||
                floatval($_POST['wpdd_sale_price']));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_POST['wpdd_files']) && is_array($_POST['wpdd_files'])) {
 | 
			
		||||
            $files = array();
 | 
			
		||||
            foreach ($_POST['wpdd_files'] as $file) {
 | 
			
		||||
                if (!empty($file['url'])) {
 | 
			
		||||
                    $files[] = array(
 | 
			
		||||
                        'id' => sanitize_text_field($file['id']),
 | 
			
		||||
                        'name' => sanitize_text_field($file['name']),
 | 
			
		||||
                        'url' => esc_url_raw($file['url'])
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            update_post_meta($post_id, '_wpdd_files', $files);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_POST['wpdd_download_limit'])) {
 | 
			
		||||
            update_post_meta($post_id, '_wpdd_download_limit', 
 | 
			
		||||
                intval($_POST['wpdd_download_limit']));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_POST['wpdd_download_expiry'])) {
 | 
			
		||||
            update_post_meta($post_id, '_wpdd_download_expiry', 
 | 
			
		||||
                intval($_POST['wpdd_download_expiry']));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        update_post_meta($post_id, '_wpdd_enable_watermark', 
 | 
			
		||||
            isset($_POST['wpdd_enable_watermark']) ? '1' : '0');
 | 
			
		||||
        
 | 
			
		||||
        if (isset($_POST['wpdd_watermark_text'])) {
 | 
			
		||||
            update_post_meta($post_id, '_wpdd_watermark_text', 
 | 
			
		||||
                sanitize_text_field($_POST['wpdd_watermark_text']));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										318
									
								
								includes/class-wpdd-orders.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										318
									
								
								includes/class-wpdd-orders.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,318 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Orders {
 | 
			
		||||
    
 | 
			
		||||
    public static function create_order($product_id, $customer_data, $payment_method = 'free') {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($product_id);
 | 
			
		||||
        if (!$product || $product->post_type !== 'wpdd_product') {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $price = get_post_meta($product_id, '_wpdd_price', true);
 | 
			
		||||
        $sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
 | 
			
		||||
        $is_free = get_post_meta($product_id, '_wpdd_is_free', true);
 | 
			
		||||
        
 | 
			
		||||
        $amount = $is_free ? 0 : (($sale_price && $sale_price < $price) ? $sale_price : $price);
 | 
			
		||||
        
 | 
			
		||||
        $order_number = 'WPDD-' . strtoupper(uniqid());
 | 
			
		||||
        
 | 
			
		||||
        $customer_id = 0;
 | 
			
		||||
        if (is_user_logged_in()) {
 | 
			
		||||
            $current_user = wp_get_current_user();
 | 
			
		||||
            $customer_id = $current_user->ID;
 | 
			
		||||
            $customer_email = $current_user->user_email;
 | 
			
		||||
            $customer_name = $current_user->display_name;
 | 
			
		||||
        } else {
 | 
			
		||||
            $customer_email = $customer_data['email'];
 | 
			
		||||
            $customer_name = $customer_data['name'];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_orders',
 | 
			
		||||
            array(
 | 
			
		||||
                'order_number' => $order_number,
 | 
			
		||||
                'product_id' => $product_id,
 | 
			
		||||
                'customer_id' => $customer_id,
 | 
			
		||||
                'creator_id' => $product->post_author,
 | 
			
		||||
                'status' => ($payment_method === 'free' || $amount == 0) ? 'completed' : 'pending',
 | 
			
		||||
                'payment_method' => $payment_method,
 | 
			
		||||
                'amount' => $amount,
 | 
			
		||||
                'currency' => 'USD',
 | 
			
		||||
                'customer_email' => $customer_email,
 | 
			
		||||
                'customer_name' => $customer_name,
 | 
			
		||||
                'purchase_date' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%s', '%d', '%d', '%d', '%s', '%s', '%f', '%s', '%s', '%s', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            $order_id = $wpdb->insert_id;
 | 
			
		||||
            
 | 
			
		||||
            if ($payment_method === 'free' || $amount == 0) {
 | 
			
		||||
                self::complete_order($order_id);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return $order_id;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function complete_order($order_id, $transaction_id = null) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $order = self::get_order($order_id);
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $update_data = array(
 | 
			
		||||
            'status' => 'completed'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($transaction_id) {
 | 
			
		||||
            $update_data['transaction_id'] = $transaction_id;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $result = $wpdb->update(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_orders',
 | 
			
		||||
            $update_data,
 | 
			
		||||
            array('id' => $order_id),
 | 
			
		||||
            array('%s', '%s'),
 | 
			
		||||
            array('%d')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($result) {
 | 
			
		||||
            self::generate_download_link($order_id);
 | 
			
		||||
            
 | 
			
		||||
            self::send_order_emails($order_id);
 | 
			
		||||
            
 | 
			
		||||
            update_post_meta(
 | 
			
		||||
                $order->product_id, 
 | 
			
		||||
                '_wpdd_sales_count',
 | 
			
		||||
                intval(get_post_meta($order->product_id, '_wpdd_sales_count', true)) + 1
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            do_action('wpdd_order_completed', $order_id);
 | 
			
		||||
            
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_order($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        if (is_numeric($order_id)) {
 | 
			
		||||
            return $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
                "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE id = %d",
 | 
			
		||||
                $order_id
 | 
			
		||||
            ));
 | 
			
		||||
        } else {
 | 
			
		||||
            return $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
                "SELECT * FROM {$wpdb->prefix}wpdd_orders WHERE order_number = %s",
 | 
			
		||||
                $order_id
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_orders($args = array()) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $defaults = array(
 | 
			
		||||
            'status' => '',
 | 
			
		||||
            'customer_id' => 0,
 | 
			
		||||
            'creator_id' => 0,
 | 
			
		||||
            'product_id' => 0,
 | 
			
		||||
            'limit' => 20,
 | 
			
		||||
            'offset' => 0,
 | 
			
		||||
            'orderby' => 'purchase_date',
 | 
			
		||||
            'order' => 'DESC'
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $args = wp_parse_args($args, $defaults);
 | 
			
		||||
        
 | 
			
		||||
        $where = array('1=1');
 | 
			
		||||
        
 | 
			
		||||
        if ($args['status']) {
 | 
			
		||||
            $where[] = $wpdb->prepare("status = %s", $args['status']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($args['customer_id']) {
 | 
			
		||||
            $where[] = $wpdb->prepare("customer_id = %d", $args['customer_id']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($args['creator_id']) {
 | 
			
		||||
            $where[] = $wpdb->prepare("creator_id = %d", $args['creator_id']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if ($args['product_id']) {
 | 
			
		||||
            $where[] = $wpdb->prepare("product_id = %d", $args['product_id']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $where_clause = implode(' AND ', $where);
 | 
			
		||||
        
 | 
			
		||||
        $query = $wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name, 
 | 
			
		||||
                    u.display_name as customer_display_name,
 | 
			
		||||
                    c.display_name as creator_display_name
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             LEFT JOIN {$wpdb->users} u ON o.customer_id = u.ID
 | 
			
		||||
             LEFT JOIN {$wpdb->users} c ON o.creator_id = c.ID
 | 
			
		||||
             WHERE {$where_clause}
 | 
			
		||||
             ORDER BY {$args['orderby']} {$args['order']}
 | 
			
		||||
             LIMIT %d OFFSET %d",
 | 
			
		||||
            $args['limit'],
 | 
			
		||||
            $args['offset']
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $wpdb->get_results($query);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function generate_download_link($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $token = wp_hash(uniqid() . $order_id . time());
 | 
			
		||||
        $expires_at = date('Y-m-d H:i:s', strtotime('+7 days'));
 | 
			
		||||
        
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
            array(
 | 
			
		||||
                'order_id' => $order_id,
 | 
			
		||||
                'token' => $token,
 | 
			
		||||
                'expires_at' => $expires_at,
 | 
			
		||||
                'max_downloads' => 5,
 | 
			
		||||
                'created_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%s', '%s', '%d', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $token;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function send_order_emails($order_id) {
 | 
			
		||||
        $order = self::get_order($order_id);
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        self::send_customer_email($order);
 | 
			
		||||
        
 | 
			
		||||
        self::send_creator_email($order);
 | 
			
		||||
        
 | 
			
		||||
        self::send_admin_email($order);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function send_customer_email($order) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($order->product_id);
 | 
			
		||||
        $download_link = $wpdb->get_var($wpdb->prepare(
 | 
			
		||||
            "SELECT token FROM {$wpdb->prefix}wpdd_download_links 
 | 
			
		||||
             WHERE order_id = %d ORDER BY id DESC LIMIT 1",
 | 
			
		||||
            $order->id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        $download_url = add_query_arg(array(
 | 
			
		||||
            'wpdd_download_token' => $download_link
 | 
			
		||||
        ), home_url());
 | 
			
		||||
        
 | 
			
		||||
        $subject = sprintf(
 | 
			
		||||
            __('Your purchase of %s from %s', 'wp-digital-download'),
 | 
			
		||||
            $product->post_title,
 | 
			
		||||
            get_bloginfo('name')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $message = sprintf(
 | 
			
		||||
            __("Hi %s,\n\nThank you for your purchase!\n\n", 'wp-digital-download'),
 | 
			
		||||
            $order->customer_name
 | 
			
		||||
        );
 | 
			
		||||
        $message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
 | 
			
		||||
        $message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $product->post_title);
 | 
			
		||||
        
 | 
			
		||||
        if ($order->amount > 0) {
 | 
			
		||||
            $message .= sprintf(__("Amount: $%s\n", 'wp-digital-download'), number_format($order->amount, 2));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $message .= "\n" . __("Download your product here:\n", 'wp-digital-download');
 | 
			
		||||
        $message .= $download_url . "\n\n";
 | 
			
		||||
        $message .= __("This download link will expire in 7 days.\n\n", 'wp-digital-download');
 | 
			
		||||
        
 | 
			
		||||
        if ($order->customer_id) {
 | 
			
		||||
            $purchases_url = get_permalink(get_option('wpdd_purchases_page_id'));
 | 
			
		||||
            $message .= sprintf(
 | 
			
		||||
                __("You can also access your downloads anytime from your account:\n%s\n\n", 'wp-digital-download'),
 | 
			
		||||
                $purchases_url
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $message .= sprintf(__("Best regards,\n%s", 'wp-digital-download'), get_bloginfo('name'));
 | 
			
		||||
        
 | 
			
		||||
        wp_mail($order->customer_email, $subject, $message);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function send_creator_email($order) {
 | 
			
		||||
        $creator = get_userdata($order->creator_id);
 | 
			
		||||
        if (!$creator) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($order->product_id);
 | 
			
		||||
        
 | 
			
		||||
        $subject = sprintf(
 | 
			
		||||
            __('New sale: %s', 'wp-digital-download'),
 | 
			
		||||
            $product->post_title
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $message = sprintf(
 | 
			
		||||
            __("Hi %s,\n\nYou have a new sale!\n\n", 'wp-digital-download'),
 | 
			
		||||
            $creator->display_name
 | 
			
		||||
        );
 | 
			
		||||
        $message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $product->post_title);
 | 
			
		||||
        $message .= sprintf(__("Customer: %s\n", 'wp-digital-download'), $order->customer_name);
 | 
			
		||||
        $message .= sprintf(__("Amount: $%s\n", 'wp-digital-download'), number_format($order->amount, 2));
 | 
			
		||||
        $message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
 | 
			
		||||
        $message .= "\n" . sprintf(
 | 
			
		||||
            __("View your sales dashboard:\n%s\n", 'wp-digital-download'),
 | 
			
		||||
            admin_url()
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        wp_mail($creator->user_email, $subject, $message);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function send_admin_email($order) {
 | 
			
		||||
        $admin_email = get_option('wpdd_admin_email', get_option('admin_email'));
 | 
			
		||||
        
 | 
			
		||||
        if (!$admin_email) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($order->product_id);
 | 
			
		||||
        
 | 
			
		||||
        $subject = sprintf(
 | 
			
		||||
            __('[%s] New Digital Download Sale', 'wp-digital-download'),
 | 
			
		||||
            get_bloginfo('name')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $message = __("A new digital download sale has been completed.\n\n", 'wp-digital-download');
 | 
			
		||||
        $message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
 | 
			
		||||
        $message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $product->post_title);
 | 
			
		||||
        $message .= sprintf(__("Customer: %s (%s)\n", 'wp-digital-download'), $order->customer_name, $order->customer_email);
 | 
			
		||||
        $message .= sprintf(__("Amount: $%s\n", 'wp-digital-download'), number_format($order->amount, 2));
 | 
			
		||||
        $message .= sprintf(__("Payment Method: %s\n", 'wp-digital-download'), $order->payment_method);
 | 
			
		||||
        
 | 
			
		||||
        if ($order->transaction_id) {
 | 
			
		||||
            $message .= sprintf(__("Transaction ID: %s\n", 'wp-digital-download'), $order->transaction_id);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_mail($admin_email, $subject, $message);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										231
									
								
								includes/class-wpdd-paypal-payouts.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								includes/class-wpdd-paypal-payouts.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,231 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_PayPal_Payouts {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        // Schedule cron event for automatic payouts
 | 
			
		||||
        if (!wp_next_scheduled('wpdd_process_automatic_payouts')) {
 | 
			
		||||
            wp_schedule_event(time(), 'daily', 'wpdd_process_automatic_payouts');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        add_action('wpdd_process_automatic_payouts', array(__CLASS__, 'process_automatic_payouts'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function process_automatic_payouts() {
 | 
			
		||||
        $threshold = floatval(get_option('wpdd_payout_threshold', 0));
 | 
			
		||||
        
 | 
			
		||||
        if ($threshold <= 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $creators = WPDD_Creator::get_creators_with_balance();
 | 
			
		||||
        
 | 
			
		||||
        foreach ($creators as $creator) {
 | 
			
		||||
            if (floatval($creator->balance) >= $threshold && !empty($creator->paypal_email)) {
 | 
			
		||||
                WPDD_Admin_Payouts::create_payout($creator->ID, 'automatic');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function process_payout($payout_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        // Get payout details
 | 
			
		||||
        $payout = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT * FROM {$wpdb->prefix}wpdd_payouts WHERE id = %d",
 | 
			
		||||
            $payout_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$payout) {
 | 
			
		||||
            return array('success' => false, 'error' => 'Payout not found');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get PayPal credentials
 | 
			
		||||
        $mode = get_option('wpdd_paypal_mode', 'sandbox');
 | 
			
		||||
        $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');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get access token
 | 
			
		||||
        $token_result = self::get_access_token($client_id, $secret, $mode);
 | 
			
		||||
        
 | 
			
		||||
        if (!$token_result['success']) {
 | 
			
		||||
            return array('success' => false, 'error' => $token_result['error']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $access_token = $token_result['token'];
 | 
			
		||||
        
 | 
			
		||||
        // Create payout batch
 | 
			
		||||
        $batch_result = self::create_payout_batch($payout, $access_token, $mode);
 | 
			
		||||
        
 | 
			
		||||
        if ($batch_result['success']) {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'transaction_id' => $batch_result['batch_id']
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => false,
 | 
			
		||||
                'error' => $batch_result['error']
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function get_access_token($client_id, $secret, $mode) {
 | 
			
		||||
        $base_url = $mode === 'sandbox' 
 | 
			
		||||
            ? 'https://api-m.sandbox.paypal.com' 
 | 
			
		||||
            : 'https://api-m.paypal.com';
 | 
			
		||||
        
 | 
			
		||||
        $response = wp_remote_post(
 | 
			
		||||
            $base_url . '/v1/oauth2/token',
 | 
			
		||||
            array(
 | 
			
		||||
                'headers' => array(
 | 
			
		||||
                    'Authorization' => 'Basic ' . base64_encode($client_id . ':' . $secret),
 | 
			
		||||
                    'Content-Type' => 'application/x-www-form-urlencoded'
 | 
			
		||||
                ),
 | 
			
		||||
                'body' => 'grant_type=client_credentials',
 | 
			
		||||
                'timeout' => 30
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (is_wp_error($response)) {
 | 
			
		||||
            return array('success' => false, 'error' => $response->get_error_message());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $body = json_decode(wp_remote_retrieve_body($response), true);
 | 
			
		||||
        
 | 
			
		||||
        if (isset($body['access_token'])) {
 | 
			
		||||
            return array('success' => true, 'token' => $body['access_token']);
 | 
			
		||||
        } else {
 | 
			
		||||
            $error = isset($body['error_description']) ? $body['error_description'] : 'Failed to get access token';
 | 
			
		||||
            return array('success' => false, 'error' => $error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function create_payout_batch($payout, $access_token, $mode) {
 | 
			
		||||
        $base_url = $mode === 'sandbox' 
 | 
			
		||||
            ? 'https://api-m.sandbox.paypal.com' 
 | 
			
		||||
            : 'https://api-m.paypal.com';
 | 
			
		||||
        
 | 
			
		||||
        $batch_id = 'WPDD_' . $payout->id . '_' . time();
 | 
			
		||||
        
 | 
			
		||||
        $payout_data = array(
 | 
			
		||||
            'sender_batch_header' => array(
 | 
			
		||||
                'sender_batch_id' => $batch_id,
 | 
			
		||||
                'email_subject' => 'You have received a payout!',
 | 
			
		||||
                'email_message' => 'You have received a payout from ' . get_bloginfo('name')
 | 
			
		||||
            ),
 | 
			
		||||
            'items' => array(
 | 
			
		||||
                array(
 | 
			
		||||
                    'recipient_type' => 'EMAIL',
 | 
			
		||||
                    'amount' => array(
 | 
			
		||||
                        'value' => number_format($payout->amount, 2, '.', ''),
 | 
			
		||||
                        'currency' => $payout->currency
 | 
			
		||||
                    ),
 | 
			
		||||
                    'receiver' => $payout->paypal_email,
 | 
			
		||||
                    'note' => 'Payout for your sales on ' . get_bloginfo('name'),
 | 
			
		||||
                    'sender_item_id' => 'payout_' . $payout->id
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $response = wp_remote_post(
 | 
			
		||||
            $base_url . '/v1/payments/payouts',
 | 
			
		||||
            array(
 | 
			
		||||
                'headers' => array(
 | 
			
		||||
                    'Authorization' => 'Bearer ' . $access_token,
 | 
			
		||||
                    'Content-Type' => 'application/json'
 | 
			
		||||
                ),
 | 
			
		||||
                'body' => json_encode($payout_data),
 | 
			
		||||
                'timeout' => 30
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (is_wp_error($response)) {
 | 
			
		||||
            return array('success' => false, 'error' => $response->get_error_message());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response_code = wp_remote_retrieve_response_code($response);
 | 
			
		||||
        $body = json_decode(wp_remote_retrieve_body($response), true);
 | 
			
		||||
        
 | 
			
		||||
        if ($response_code === 201 && isset($body['batch_header']['payout_batch_id'])) {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'batch_id' => $body['batch_header']['payout_batch_id']
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            $error = 'Failed to create payout batch';
 | 
			
		||||
            if (isset($body['message'])) {
 | 
			
		||||
                $error = $body['message'];
 | 
			
		||||
            } elseif (isset($body['error_description'])) {
 | 
			
		||||
                $error = $body['error_description'];
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return array('success' => false, 'error' => $error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function check_batch_status($batch_id, $mode = null) {
 | 
			
		||||
        if (!$mode) {
 | 
			
		||||
            $mode = get_option('wpdd_paypal_mode', 'sandbox');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $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');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get access token
 | 
			
		||||
        $token_result = self::get_access_token($client_id, $secret, $mode);
 | 
			
		||||
        
 | 
			
		||||
        if (!$token_result['success']) {
 | 
			
		||||
            return array('success' => false, 'error' => $token_result['error']);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $access_token = $token_result['token'];
 | 
			
		||||
        
 | 
			
		||||
        $base_url = $mode === 'sandbox' 
 | 
			
		||||
            ? 'https://api-m.sandbox.paypal.com' 
 | 
			
		||||
            : 'https://api-m.paypal.com';
 | 
			
		||||
        
 | 
			
		||||
        $response = wp_remote_get(
 | 
			
		||||
            $base_url . '/v1/payments/payouts/' . $batch_id,
 | 
			
		||||
            array(
 | 
			
		||||
                'headers' => array(
 | 
			
		||||
                    'Authorization' => 'Bearer ' . $access_token,
 | 
			
		||||
                    'Content-Type' => 'application/json'
 | 
			
		||||
                ),
 | 
			
		||||
                'timeout' => 30
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (is_wp_error($response)) {
 | 
			
		||||
            return array('success' => false, 'error' => $response->get_error_message());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $body = json_decode(wp_remote_retrieve_body($response), true);
 | 
			
		||||
        
 | 
			
		||||
        if (isset($body['batch_header'])) {
 | 
			
		||||
            return array(
 | 
			
		||||
                'success' => true,
 | 
			
		||||
                'status' => $body['batch_header']['batch_status'],
 | 
			
		||||
                'data' => $body
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            return array('success' => false, 'error' => 'Failed to get batch status');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function deactivate() {
 | 
			
		||||
        wp_clear_scheduled_hook('wpdd_process_automatic_payouts');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										302
									
								
								includes/class-wpdd-paypal.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								includes/class-wpdd-paypal.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,302 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_PayPal {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        add_action('wp_enqueue_scripts', array(__CLASS__, 'enqueue_paypal_sdk'));
 | 
			
		||||
        add_action('wp_ajax_wpdd_create_paypal_order', array(__CLASS__, 'create_order'));
 | 
			
		||||
        add_action('wp_ajax_nopriv_wpdd_create_paypal_order', array(__CLASS__, 'create_order'));
 | 
			
		||||
        add_action('wp_ajax_wpdd_capture_paypal_order', array(__CLASS__, 'capture_order'));
 | 
			
		||||
        add_action('wp_ajax_nopriv_wpdd_capture_paypal_order', array(__CLASS__, 'capture_order'));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function enqueue_paypal_sdk() {
 | 
			
		||||
        if (!is_page(get_option('wpdd_checkout_page_id'))) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $client_id = get_option('wpdd_paypal_client_id');
 | 
			
		||||
        $mode = get_option('wpdd_paypal_mode', 'sandbox');
 | 
			
		||||
        
 | 
			
		||||
        if (!$client_id) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        wp_enqueue_script(
 | 
			
		||||
            'paypal-sdk',
 | 
			
		||||
            'https://www.paypal.com/sdk/js?client-id=' . $client_id . '¤cy=USD',
 | 
			
		||||
            array(),
 | 
			
		||||
            null,
 | 
			
		||||
            true
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        wp_enqueue_script(
 | 
			
		||||
            'wpdd-paypal',
 | 
			
		||||
            WPDD_PLUGIN_URL . 'assets/js/paypal.js',
 | 
			
		||||
            array('jquery', 'paypal-sdk'),
 | 
			
		||||
            WPDD_VERSION,
 | 
			
		||||
            true
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        wp_localize_script('wpdd-paypal', 'wpdd_paypal', array(
 | 
			
		||||
            'ajax_url' => admin_url('admin-ajax.php'),
 | 
			
		||||
            'nonce' => wp_create_nonce('wpdd-paypal-nonce'),
 | 
			
		||||
            'mode' => $mode
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function create_order() {
 | 
			
		||||
        check_ajax_referer('wpdd-paypal-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
 | 
			
		||||
        
 | 
			
		||||
        if (!$product_id) {
 | 
			
		||||
            wp_send_json_error('Invalid product');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product = get_post($product_id);
 | 
			
		||||
        
 | 
			
		||||
        if (!$product || $product->post_type !== 'wpdd_product') {
 | 
			
		||||
            wp_send_json_error('Product not found');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $price = get_post_meta($product_id, '_wpdd_price', true);
 | 
			
		||||
        $sale_price = get_post_meta($product_id, '_wpdd_sale_price', true);
 | 
			
		||||
        $final_price = ($sale_price && $sale_price < $price) ? $sale_price : $price;
 | 
			
		||||
        
 | 
			
		||||
        $order_data = array(
 | 
			
		||||
            'intent' => 'CAPTURE',
 | 
			
		||||
            'purchase_units' => array(
 | 
			
		||||
                array(
 | 
			
		||||
                    'reference_id' => 'wpdd_' . $product_id . '_' . time(),
 | 
			
		||||
                    'description' => substr($product->post_title, 0, 127),
 | 
			
		||||
                    'amount' => array(
 | 
			
		||||
                        'currency_code' => 'USD',
 | 
			
		||||
                        'value' => number_format($final_price, 2, '.', '')
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
            'application_context' => array(
 | 
			
		||||
                'brand_name' => get_bloginfo('name'),
 | 
			
		||||
                'return_url' => add_query_arg('wpdd_paypal_return', '1', get_permalink(get_option('wpdd_thank_you_page_id'))),
 | 
			
		||||
                'cancel_url' => add_query_arg('wpdd_paypal_cancel', '1', get_permalink(get_option('wpdd_checkout_page_id')))
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $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'] ?? '')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            wp_send_json_success(array('orderID' => $paypal_order['id']));
 | 
			
		||||
        } else {
 | 
			
		||||
            wp_send_json_error('Failed to create PayPal order');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function capture_order() {
 | 
			
		||||
        check_ajax_referer('wpdd-paypal-nonce', 'nonce');
 | 
			
		||||
        
 | 
			
		||||
        $paypal_order_id = isset($_POST['orderID']) ? sanitize_text_field($_POST['orderID']) : '';
 | 
			
		||||
        
 | 
			
		||||
        if (!$paypal_order_id) {
 | 
			
		||||
            wp_send_json_error('Invalid order ID');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $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();
 | 
			
		||||
            
 | 
			
		||||
            // Add error logging for debugging session issues
 | 
			
		||||
            if (empty($session_data)) {
 | 
			
		||||
                error_log('WPDD PayPal Error: No session data found for PayPal order ' . $paypal_order_id);
 | 
			
		||||
                error_log('WPDD PayPal Debug: Session ID: ' . session_id());
 | 
			
		||||
                error_log('WPDD PayPal Debug: Available sessions: ' . print_r($_SESSION ?? array(), true));
 | 
			
		||||
                wp_send_json_error('Session data not found - order cannot be processed');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $order_number = 'WPDD-' . strtoupper(uniqid());
 | 
			
		||||
            
 | 
			
		||||
            global $wpdb;
 | 
			
		||||
            
 | 
			
		||||
            $customer_id = 0;
 | 
			
		||||
            $customer_email = $session_data['customer_email'];
 | 
			
		||||
            $customer_name = $session_data['customer_name'];
 | 
			
		||||
            
 | 
			
		||||
            if (is_user_logged_in()) {
 | 
			
		||||
                $current_user = wp_get_current_user();
 | 
			
		||||
                $customer_id = $current_user->ID;
 | 
			
		||||
                $customer_email = $current_user->user_email;
 | 
			
		||||
                $customer_name = $current_user->display_name;
 | 
			
		||||
            } elseif (!empty($_POST['create_account']) && !empty($customer_email)) {
 | 
			
		||||
                $username = strstr($customer_email, '@', true) . '_' . wp_rand(1000, 9999);
 | 
			
		||||
                $password = wp_generate_password();
 | 
			
		||||
                
 | 
			
		||||
                $user_id = wp_create_user($username, $password, $customer_email);
 | 
			
		||||
                
 | 
			
		||||
                if (!is_wp_error($user_id)) {
 | 
			
		||||
                    $customer_id = $user_id;
 | 
			
		||||
                    wp_update_user(array(
 | 
			
		||||
                        'ID' => $user_id,
 | 
			
		||||
                        'display_name' => $customer_name,
 | 
			
		||||
                        'first_name' => $customer_name
 | 
			
		||||
                    ));
 | 
			
		||||
                    
 | 
			
		||||
                    $user = new WP_User($user_id);
 | 
			
		||||
                    $user->set_role('wpdd_customer');
 | 
			
		||||
                    
 | 
			
		||||
                    wp_new_user_notification($user_id, null, 'both');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $product = get_post($session_data['product_id']);
 | 
			
		||||
            
 | 
			
		||||
            $wpdb->insert(
 | 
			
		||||
                $wpdb->prefix . 'wpdd_orders',
 | 
			
		||||
                array(
 | 
			
		||||
                    'order_number' => $order_number,
 | 
			
		||||
                    'product_id' => $session_data['product_id'],
 | 
			
		||||
                    'customer_id' => $customer_id,
 | 
			
		||||
                    'creator_id' => $product->post_author,
 | 
			
		||||
                    'status' => 'completed',
 | 
			
		||||
                    'payment_method' => 'paypal',
 | 
			
		||||
                    'transaction_id' => $capture_response['id'],
 | 
			
		||||
                    'amount' => $session_data['amount'],
 | 
			
		||||
                    'currency' => 'USD',
 | 
			
		||||
                    'customer_email' => $customer_email,
 | 
			
		||||
                    'customer_name' => $customer_name,
 | 
			
		||||
                    'purchase_date' => current_time('mysql')
 | 
			
		||||
                ),
 | 
			
		||||
                array('%s', '%d', '%d', '%d', '%s', '%s', '%s', '%f', '%s', '%s', '%s', '%s')
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            $order_id = $wpdb->insert_id;
 | 
			
		||||
            
 | 
			
		||||
            self::generate_download_link($order_id);
 | 
			
		||||
            
 | 
			
		||||
            self::send_purchase_email($order_id);
 | 
			
		||||
            
 | 
			
		||||
            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]);
 | 
			
		||||
            
 | 
			
		||||
            wp_send_json_success(array(
 | 
			
		||||
                'redirect_url' => add_query_arg(
 | 
			
		||||
                    'order_id', 
 | 
			
		||||
                    $order_number, 
 | 
			
		||||
                    get_permalink(get_option('wpdd_thank_you_page_id'))
 | 
			
		||||
                )
 | 
			
		||||
            ));
 | 
			
		||||
        } else {
 | 
			
		||||
            wp_send_json_error('Payment capture failed');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function api_request($endpoint, $data = array(), $method = 'GET') {
 | 
			
		||||
        $mode = get_option('wpdd_paypal_mode', 'sandbox');
 | 
			
		||||
        $client_id = get_option('wpdd_paypal_client_id');
 | 
			
		||||
        $secret = get_option('wpdd_paypal_secret');
 | 
			
		||||
        
 | 
			
		||||
        if (!$client_id || !$secret) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $base_url = $mode === 'live' 
 | 
			
		||||
            ? 'https://api.paypal.com' 
 | 
			
		||||
            : 'https://api.sandbox.paypal.com';
 | 
			
		||||
        
 | 
			
		||||
        $auth = base64_encode($client_id . ':' . $secret);
 | 
			
		||||
        
 | 
			
		||||
        $args = array(
 | 
			
		||||
            'method' => $method,
 | 
			
		||||
            'headers' => array(
 | 
			
		||||
                'Authorization' => 'Basic ' . $auth,
 | 
			
		||||
                'Content-Type' => 'application/json'
 | 
			
		||||
            ),
 | 
			
		||||
            'timeout' => 30
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if (!empty($data)) {
 | 
			
		||||
            $args['body'] = json_encode($data);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = wp_remote_request($base_url . $endpoint, $args);
 | 
			
		||||
        
 | 
			
		||||
        if (is_wp_error($response)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $body = wp_remote_retrieve_body($response);
 | 
			
		||||
        return json_decode($body, true);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function generate_download_link($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $token = wp_hash(uniqid() . $order_id . time());
 | 
			
		||||
        $expires_at = date('Y-m-d H:i:s', strtotime('+7 days'));
 | 
			
		||||
        
 | 
			
		||||
        $wpdb->insert(
 | 
			
		||||
            $wpdb->prefix . 'wpdd_download_links',
 | 
			
		||||
            array(
 | 
			
		||||
                'order_id' => $order_id,
 | 
			
		||||
                'token' => $token,
 | 
			
		||||
                'expires_at' => $expires_at,
 | 
			
		||||
                'max_downloads' => 5,
 | 
			
		||||
                'created_at' => current_time('mysql')
 | 
			
		||||
            ),
 | 
			
		||||
            array('%d', '%s', '%s', '%d', '%s')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $token;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function send_purchase_email($order_id) {
 | 
			
		||||
        global $wpdb;
 | 
			
		||||
        
 | 
			
		||||
        $order = $wpdb->get_row($wpdb->prepare(
 | 
			
		||||
            "SELECT o.*, p.post_title as product_name, dl.token 
 | 
			
		||||
             FROM {$wpdb->prefix}wpdd_orders o
 | 
			
		||||
             LEFT JOIN {$wpdb->posts} p ON o.product_id = p.ID
 | 
			
		||||
             LEFT JOIN {$wpdb->prefix}wpdd_download_links dl ON o.id = dl.order_id
 | 
			
		||||
             WHERE o.id = %d",
 | 
			
		||||
            $order_id
 | 
			
		||||
        ));
 | 
			
		||||
        
 | 
			
		||||
        if (!$order) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $download_url = add_query_arg(array(
 | 
			
		||||
            'wpdd_download_token' => $order->token
 | 
			
		||||
        ), home_url());
 | 
			
		||||
        
 | 
			
		||||
        $subject = sprintf(__('Your Purchase from %s', 'wp-digital-download'), get_bloginfo('name'));
 | 
			
		||||
        
 | 
			
		||||
        $message = sprintf(
 | 
			
		||||
            __("Hi %s,\n\nThank you for your purchase!\n\n", 'wp-digital-download'),
 | 
			
		||||
            $order->customer_name
 | 
			
		||||
        );
 | 
			
		||||
        $message .= sprintf(__("Order Number: %s\n", 'wp-digital-download'), $order->order_number);
 | 
			
		||||
        $message .= sprintf(__("Product: %s\n", 'wp-digital-download'), $order->product_name);
 | 
			
		||||
        $message .= sprintf(__("Amount: $%s\n\n", 'wp-digital-download'), number_format($order->amount, 2));
 | 
			
		||||
        $message .= __("Download your product here:\n", 'wp-digital-download');
 | 
			
		||||
        $message .= $download_url . "\n\n";
 | 
			
		||||
        $message .= __("This download link will expire in 7 days.\n\n", 'wp-digital-download');
 | 
			
		||||
        $message .= sprintf(__("Best regards,\n%s", 'wp-digital-download'), get_bloginfo('name'));
 | 
			
		||||
        
 | 
			
		||||
        wp_mail($order->customer_email, $subject, $message);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								includes/class-wpdd-post-types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								includes/class-wpdd-post-types.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Post_Types {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        // Register immediately since we're already in the init hook
 | 
			
		||||
        self::register_post_types();
 | 
			
		||||
        self::register_taxonomies();
 | 
			
		||||
        add_filter('post_type_link', array(__CLASS__, 'product_permalink'), 10, 2);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function register_post_types() {
 | 
			
		||||
        $labels = array(
 | 
			
		||||
            'name' => __('Products', 'wp-digital-download'),
 | 
			
		||||
            'singular_name' => __('Product', 'wp-digital-download'),
 | 
			
		||||
            'menu_name' => __('Digital Products', 'wp-digital-download'),
 | 
			
		||||
            'add_new' => __('Add New', 'wp-digital-download'),
 | 
			
		||||
            'add_new_item' => __('Add New Product', 'wp-digital-download'),
 | 
			
		||||
            'edit_item' => __('Edit Product', 'wp-digital-download'),
 | 
			
		||||
            'new_item' => __('New Product', 'wp-digital-download'),
 | 
			
		||||
            'view_item' => __('View Product', 'wp-digital-download'),
 | 
			
		||||
            'view_items' => __('View Products', 'wp-digital-download'),
 | 
			
		||||
            'search_items' => __('Search Products', 'wp-digital-download'),
 | 
			
		||||
            'not_found' => __('No products found', 'wp-digital-download'),
 | 
			
		||||
            'not_found_in_trash' => __('No products found in Trash', 'wp-digital-download'),
 | 
			
		||||
            'all_items' => __('All Products', 'wp-digital-download'),
 | 
			
		||||
            'archives' => __('Product Archives', 'wp-digital-download'),
 | 
			
		||||
            'attributes' => __('Product Attributes', 'wp-digital-download'),
 | 
			
		||||
            'insert_into_item' => __('Insert into product', 'wp-digital-download'),
 | 
			
		||||
            'uploaded_to_this_item' => __('Uploaded to this product', 'wp-digital-download'),
 | 
			
		||||
            'featured_image' => __('Product Image', 'wp-digital-download'),
 | 
			
		||||
            'set_featured_image' => __('Set product image', 'wp-digital-download'),
 | 
			
		||||
            'remove_featured_image' => __('Remove product image', 'wp-digital-download'),
 | 
			
		||||
            'use_featured_image' => __('Use as product image', 'wp-digital-download'),
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $args = array(
 | 
			
		||||
            'labels' => $labels,
 | 
			
		||||
            'public' => true,
 | 
			
		||||
            'publicly_queryable' => true,
 | 
			
		||||
            'show_ui' => true,
 | 
			
		||||
            'show_in_menu' => true,
 | 
			
		||||
            'query_var' => true,
 | 
			
		||||
            'rewrite' => array('slug' => 'product'),
 | 
			
		||||
            'capability_type' => 'post',
 | 
			
		||||
            'capabilities' => array(
 | 
			
		||||
                'edit_post' => 'edit_wpdd_product',
 | 
			
		||||
                'read_post' => 'read_wpdd_product',
 | 
			
		||||
                'delete_post' => 'delete_wpdd_product',
 | 
			
		||||
                'edit_posts' => 'edit_wpdd_products',
 | 
			
		||||
                'edit_others_posts' => 'edit_others_wpdd_products',
 | 
			
		||||
                'publish_posts' => 'publish_wpdd_products',
 | 
			
		||||
                'read_private_posts' => 'read_private_wpdd_products',
 | 
			
		||||
                'delete_posts' => 'delete_wpdd_products',
 | 
			
		||||
                'delete_private_posts' => 'delete_private_wpdd_products',
 | 
			
		||||
                'delete_published_posts' => 'delete_published_wpdd_products',
 | 
			
		||||
                'delete_others_posts' => 'delete_others_wpdd_products',
 | 
			
		||||
                'edit_private_posts' => 'edit_private_wpdd_products',
 | 
			
		||||
                'edit_published_posts' => 'edit_published_wpdd_products',
 | 
			
		||||
            ),
 | 
			
		||||
            'map_meta_cap' => true,
 | 
			
		||||
            'has_archive' => true,
 | 
			
		||||
            'hierarchical' => false,
 | 
			
		||||
            'menu_position' => 25,
 | 
			
		||||
            'menu_icon' => 'dashicons-download',
 | 
			
		||||
            'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'author'),
 | 
			
		||||
            'show_in_rest' => true,
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        register_post_type('wpdd_product', $args);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function register_taxonomies() {
 | 
			
		||||
        $labels = array(
 | 
			
		||||
            'name' => __('Product Categories', 'wp-digital-download'),
 | 
			
		||||
            'singular_name' => __('Product Category', 'wp-digital-download'),
 | 
			
		||||
            'search_items' => __('Search Categories', 'wp-digital-download'),
 | 
			
		||||
            'all_items' => __('All Categories', 'wp-digital-download'),
 | 
			
		||||
            'parent_item' => __('Parent Category', 'wp-digital-download'),
 | 
			
		||||
            'parent_item_colon' => __('Parent Category:', 'wp-digital-download'),
 | 
			
		||||
            'edit_item' => __('Edit Category', 'wp-digital-download'),
 | 
			
		||||
            'update_item' => __('Update Category', 'wp-digital-download'),
 | 
			
		||||
            'add_new_item' => __('Add New Category', 'wp-digital-download'),
 | 
			
		||||
            'new_item_name' => __('New Category Name', 'wp-digital-download'),
 | 
			
		||||
            'menu_name' => __('Categories', 'wp-digital-download'),
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $args = array(
 | 
			
		||||
            'labels' => $labels,
 | 
			
		||||
            'hierarchical' => true,
 | 
			
		||||
            'public' => true,
 | 
			
		||||
            'show_ui' => true,
 | 
			
		||||
            'show_admin_column' => true,
 | 
			
		||||
            'query_var' => true,
 | 
			
		||||
            'rewrite' => array('slug' => 'product-category'),
 | 
			
		||||
            'show_in_rest' => true,
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        register_taxonomy('wpdd_product_category', 'wpdd_product', $args);
 | 
			
		||||
        
 | 
			
		||||
        $tag_labels = array(
 | 
			
		||||
            'name' => __('Product Tags', 'wp-digital-download'),
 | 
			
		||||
            'singular_name' => __('Product Tag', 'wp-digital-download'),
 | 
			
		||||
            'search_items' => __('Search Tags', 'wp-digital-download'),
 | 
			
		||||
            'popular_items' => __('Popular Tags', 'wp-digital-download'),
 | 
			
		||||
            'all_items' => __('All Tags', 'wp-digital-download'),
 | 
			
		||||
            'edit_item' => __('Edit Tag', 'wp-digital-download'),
 | 
			
		||||
            'update_item' => __('Update Tag', 'wp-digital-download'),
 | 
			
		||||
            'add_new_item' => __('Add New Tag', 'wp-digital-download'),
 | 
			
		||||
            'new_item_name' => __('New Tag Name', 'wp-digital-download'),
 | 
			
		||||
            'separate_items_with_commas' => __('Separate tags with commas', 'wp-digital-download'),
 | 
			
		||||
            'add_or_remove_items' => __('Add or remove tags', 'wp-digital-download'),
 | 
			
		||||
            'choose_from_most_used' => __('Choose from the most used tags', 'wp-digital-download'),
 | 
			
		||||
            'menu_name' => __('Tags', 'wp-digital-download'),
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        $tag_args = array(
 | 
			
		||||
            'labels' => $tag_labels,
 | 
			
		||||
            'hierarchical' => false,
 | 
			
		||||
            'public' => true,
 | 
			
		||||
            'show_ui' => true,
 | 
			
		||||
            'show_admin_column' => true,
 | 
			
		||||
            'query_var' => true,
 | 
			
		||||
            'rewrite' => array('slug' => 'product-tag'),
 | 
			
		||||
            'show_in_rest' => true,
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        register_taxonomy('wpdd_product_tag', 'wpdd_product', $tag_args);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function product_permalink($permalink, $post) {
 | 
			
		||||
        if ($post->post_type !== 'wpdd_product') {
 | 
			
		||||
            return $permalink;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $permalink;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										116
									
								
								includes/class-wpdd-roles.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								includes/class-wpdd-roles.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Roles {
 | 
			
		||||
    
 | 
			
		||||
    public static function init() {
 | 
			
		||||
        // Call immediately since we're already in init hook
 | 
			
		||||
        self::maybe_create_roles();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function maybe_create_roles() {
 | 
			
		||||
        if (get_option('wpdd_roles_created') !== WPDD_VERSION) {
 | 
			
		||||
            self::create_roles();
 | 
			
		||||
            update_option('wpdd_roles_created', WPDD_VERSION);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function create_roles() {
 | 
			
		||||
        self::create_customer_role();
 | 
			
		||||
        self::create_creator_role();
 | 
			
		||||
        self::add_admin_capabilities();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function create_customer_role() {
 | 
			
		||||
        add_role(
 | 
			
		||||
            'wpdd_customer',
 | 
			
		||||
            __('Digital Customer', 'wp-digital-download'),
 | 
			
		||||
            array(
 | 
			
		||||
                'read' => true,
 | 
			
		||||
                'wpdd_view_purchases' => true,
 | 
			
		||||
                'wpdd_download_products' => true,
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function create_creator_role() {
 | 
			
		||||
        add_role(
 | 
			
		||||
            'wpdd_creator',
 | 
			
		||||
            __('Digital Creator', 'wp-digital-download'),
 | 
			
		||||
            array(
 | 
			
		||||
                'read' => true,
 | 
			
		||||
                'upload_files' => true,
 | 
			
		||||
                'edit_posts' => false,
 | 
			
		||||
                'delete_posts' => false,
 | 
			
		||||
                'publish_posts' => false,
 | 
			
		||||
                
 | 
			
		||||
                'edit_wpdd_products' => true,
 | 
			
		||||
                'edit_published_wpdd_products' => true,
 | 
			
		||||
                'publish_wpdd_products' => true,
 | 
			
		||||
                'delete_wpdd_products' => true,
 | 
			
		||||
                'delete_published_wpdd_products' => true,
 | 
			
		||||
                'edit_private_wpdd_products' => true,
 | 
			
		||||
                'delete_private_wpdd_products' => true,
 | 
			
		||||
                
 | 
			
		||||
                'wpdd_view_own_sales' => true,
 | 
			
		||||
                'wpdd_manage_own_products' => true,
 | 
			
		||||
                'wpdd_upload_product_files' => true,
 | 
			
		||||
                'wpdd_view_reports' => true,
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function add_admin_capabilities() {
 | 
			
		||||
        $role = get_role('administrator');
 | 
			
		||||
        
 | 
			
		||||
        if ($role) {
 | 
			
		||||
            $role->add_cap('edit_wpdd_products');
 | 
			
		||||
            $role->add_cap('edit_others_wpdd_products');
 | 
			
		||||
            $role->add_cap('edit_published_wpdd_products');
 | 
			
		||||
            $role->add_cap('publish_wpdd_products');
 | 
			
		||||
            $role->add_cap('delete_wpdd_products');
 | 
			
		||||
            $role->add_cap('delete_others_wpdd_products');
 | 
			
		||||
            $role->add_cap('delete_published_wpdd_products');
 | 
			
		||||
            $role->add_cap('edit_private_wpdd_products');
 | 
			
		||||
            $role->add_cap('delete_private_wpdd_products');
 | 
			
		||||
            
 | 
			
		||||
            $role->add_cap('wpdd_manage_settings');
 | 
			
		||||
            $role->add_cap('wpdd_view_all_sales');
 | 
			
		||||
            $role->add_cap('wpdd_manage_all_products');
 | 
			
		||||
            $role->add_cap('wpdd_view_reports');
 | 
			
		||||
            $role->add_cap('wpdd_manage_orders');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function remove_roles() {
 | 
			
		||||
        remove_role('wpdd_customer');
 | 
			
		||||
        remove_role('wpdd_creator');
 | 
			
		||||
        
 | 
			
		||||
        $role = get_role('administrator');
 | 
			
		||||
        if ($role) {
 | 
			
		||||
            $caps = array(
 | 
			
		||||
                'edit_wpdd_products',
 | 
			
		||||
                'edit_others_wpdd_products',
 | 
			
		||||
                'edit_published_wpdd_products',
 | 
			
		||||
                'publish_wpdd_products',
 | 
			
		||||
                'delete_wpdd_products',
 | 
			
		||||
                'delete_others_wpdd_products',
 | 
			
		||||
                'delete_published_wpdd_products',
 | 
			
		||||
                'edit_private_wpdd_products',
 | 
			
		||||
                'delete_private_wpdd_products',
 | 
			
		||||
                'wpdd_manage_settings',
 | 
			
		||||
                'wpdd_view_all_sales',
 | 
			
		||||
                'wpdd_manage_all_products',
 | 
			
		||||
                'wpdd_view_reports',
 | 
			
		||||
                'wpdd_manage_orders'
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            foreach ($caps as $cap) {
 | 
			
		||||
                $role->remove_cap($cap);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1167
									
								
								includes/class-wpdd-shortcodes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1167
									
								
								includes/class-wpdd-shortcodes.php
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										249
									
								
								includes/class-wpdd-watermark.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								includes/class-wpdd-watermark.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,249 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
if (!defined('ABSPATH')) {
 | 
			
		||||
    exit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WPDD_Watermark {
 | 
			
		||||
    
 | 
			
		||||
    public static function apply_watermark($file_path, $order) {
 | 
			
		||||
        $file_extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
 | 
			
		||||
        
 | 
			
		||||
        if (filter_var($file_path, FILTER_VALIDATE_URL)) {
 | 
			
		||||
            $upload_dir = wp_upload_dir();
 | 
			
		||||
            if (strpos($file_path, $upload_dir['baseurl']) === 0) {
 | 
			
		||||
                $file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file_path);
 | 
			
		||||
            } else {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($file_path)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $product_id = $order->product_id;
 | 
			
		||||
        $watermark_text = get_post_meta($product_id, '_wpdd_watermark_text', true);
 | 
			
		||||
        
 | 
			
		||||
        if (empty($watermark_text)) {
 | 
			
		||||
            $watermark_text = '{customer_email}';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $watermark_text = self::parse_watermark_placeholders($watermark_text, $order);
 | 
			
		||||
        
 | 
			
		||||
        switch ($file_extension) {
 | 
			
		||||
            case 'jpg':
 | 
			
		||||
            case 'jpeg':
 | 
			
		||||
            case 'png':
 | 
			
		||||
            case 'gif':
 | 
			
		||||
                return self::watermark_image($file_path, $watermark_text);
 | 
			
		||||
            case 'pdf':
 | 
			
		||||
                return self::watermark_pdf($file_path, $watermark_text);
 | 
			
		||||
            default:
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function parse_watermark_placeholders($text, $order) {
 | 
			
		||||
        $customer = get_userdata($order->customer_id);
 | 
			
		||||
        
 | 
			
		||||
        $replacements = array(
 | 
			
		||||
            '{customer_name}' => $order->customer_name,
 | 
			
		||||
            '{customer_email}' => $order->customer_email,
 | 
			
		||||
            '{order_id}' => $order->order_number,
 | 
			
		||||
            '{date}' => date_i18n(get_option('date_format')),
 | 
			
		||||
            '{site_name}' => get_bloginfo('name')
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return str_replace(array_keys($replacements), array_values($replacements), $text);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function watermark_image($file_path, $watermark_text) {
 | 
			
		||||
        if (!function_exists('imagecreatefrompng')) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $file_info = pathinfo($file_path);
 | 
			
		||||
        $extension = strtolower($file_info['extension']);
 | 
			
		||||
        
 | 
			
		||||
        switch ($extension) {
 | 
			
		||||
            case 'jpg':
 | 
			
		||||
            case 'jpeg':
 | 
			
		||||
                $image = imagecreatefromjpeg($file_path);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'png':
 | 
			
		||||
                $image = imagecreatefrompng($file_path);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'gif':
 | 
			
		||||
                $image = imagecreatefromgif($file_path);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!$image) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $width = imagesx($image);
 | 
			
		||||
        $height = imagesy($image);
 | 
			
		||||
        
 | 
			
		||||
        $font_size = max(10, min(30, $width / 40));
 | 
			
		||||
        $font_file = WPDD_PLUGIN_PATH . 'assets/fonts/arial.ttf';
 | 
			
		||||
        
 | 
			
		||||
        if (!file_exists($font_file)) {
 | 
			
		||||
            $font = 5;
 | 
			
		||||
            $text_width = imagefontwidth($font) * strlen($watermark_text);
 | 
			
		||||
            $text_height = imagefontheight($font);
 | 
			
		||||
        } else {
 | 
			
		||||
            $bbox = imagettfbbox($font_size, 0, $font_file, $watermark_text);
 | 
			
		||||
            $text_width = $bbox[2] - $bbox[0];
 | 
			
		||||
            $text_height = $bbox[1] - $bbox[7];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $x = ($width - $text_width) / 2;
 | 
			
		||||
        $y = $height - 50;
 | 
			
		||||
        
 | 
			
		||||
        $text_color = imagecolorallocatealpha($image, 255, 255, 255, 30);
 | 
			
		||||
        $shadow_color = imagecolorallocatealpha($image, 0, 0, 0, 50);
 | 
			
		||||
        
 | 
			
		||||
        if (file_exists($font_file)) {
 | 
			
		||||
            imagettftext($image, $font_size, 0, $x + 2, $y + 2, $shadow_color, $font_file, $watermark_text);
 | 
			
		||||
            imagettftext($image, $font_size, 0, $x, $y, $text_color, $font_file, $watermark_text);
 | 
			
		||||
        } else {
 | 
			
		||||
            imagestring($image, $font, $x + 2, $y + 2, $watermark_text, $shadow_color);
 | 
			
		||||
            imagestring($image, $font, $x, $y, $watermark_text, $text_color);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $temp_dir = get_temp_dir();
 | 
			
		||||
        $temp_file = $temp_dir . 'wpdd_watermark_' . uniqid() . '.' . $extension;
 | 
			
		||||
        
 | 
			
		||||
        switch ($extension) {
 | 
			
		||||
            case 'jpg':
 | 
			
		||||
            case 'jpeg':
 | 
			
		||||
                imagejpeg($image, $temp_file, 90);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'png':
 | 
			
		||||
                imagepng($image, $temp_file, 9);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'gif':
 | 
			
		||||
                imagegif($image, $temp_file);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        imagedestroy($image);
 | 
			
		||||
        
 | 
			
		||||
        return $temp_file;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function watermark_pdf($file_path, $watermark_text) {
 | 
			
		||||
        if (!class_exists('TCPDF') && !class_exists('FPDF')) {
 | 
			
		||||
            return self::watermark_pdf_basic($file_path, $watermark_text);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (class_exists('TCPDF')) {
 | 
			
		||||
            return self::watermark_pdf_tcpdf($file_path, $watermark_text);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function watermark_pdf_basic($file_path, $watermark_text) {
 | 
			
		||||
        $temp_dir = get_temp_dir();
 | 
			
		||||
        $temp_file = $temp_dir . 'wpdd_watermark_' . uniqid() . '.pdf';
 | 
			
		||||
        
 | 
			
		||||
        if (copy($file_path, $temp_file)) {
 | 
			
		||||
            return $temp_file;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private static function watermark_pdf_tcpdf($file_path, $watermark_text) {
 | 
			
		||||
        require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
 | 
			
		||||
        
 | 
			
		||||
        $pdf = new TCPDF();
 | 
			
		||||
        $pdf->SetProtection(array('print'), '', null, 0, null);
 | 
			
		||||
        
 | 
			
		||||
        $pagecount = $pdf->setSourceFile($file_path);
 | 
			
		||||
        
 | 
			
		||||
        for ($i = 1; $i <= $pagecount; $i++) {
 | 
			
		||||
            $tplidx = $pdf->importPage($i);
 | 
			
		||||
            $pdf->AddPage();
 | 
			
		||||
            $pdf->useTemplate($tplidx);
 | 
			
		||||
            
 | 
			
		||||
            $pdf->SetFont('helvetica', '', 12);
 | 
			
		||||
            $pdf->SetTextColor(200, 200, 200);
 | 
			
		||||
            $pdf->SetAlpha(0.5);
 | 
			
		||||
            
 | 
			
		||||
            $pdf->StartTransform();
 | 
			
		||||
            $pdf->Rotate(45, $pdf->getPageWidth() / 2, $pdf->getPageHeight() / 2);
 | 
			
		||||
            $pdf->Text(
 | 
			
		||||
                $pdf->getPageWidth() / 2 - 50,
 | 
			
		||||
                $pdf->getPageHeight() / 2,
 | 
			
		||||
                $watermark_text
 | 
			
		||||
            );
 | 
			
		||||
            $pdf->StopTransform();
 | 
			
		||||
            
 | 
			
		||||
            $pdf->SetAlpha(1);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $temp_dir = get_temp_dir();
 | 
			
		||||
        $temp_file = $temp_dir . 'wpdd_watermark_' . uniqid() . '.pdf';
 | 
			
		||||
        
 | 
			
		||||
        $pdf->Output($temp_file, 'F');
 | 
			
		||||
        
 | 
			
		||||
        return $temp_file;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function add_text_watermark($content, $watermark_text) {
 | 
			
		||||
        $watermark_html = sprintf(
 | 
			
		||||
            '<div style="position: fixed; top: 50%%; left: 50%%; transform: translate(-50%%, -50%%) rotate(-45deg); 
 | 
			
		||||
                        opacity: 0.1; font-size: 48px; color: #000; z-index: -1; user-select: none;">%s</div>',
 | 
			
		||||
            esc_html($watermark_text)
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $watermark_html . $content;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function get_watermark_settings() {
 | 
			
		||||
        return array(
 | 
			
		||||
            'enabled' => get_option('wpdd_watermark_enabled', false),
 | 
			
		||||
            'text' => get_option('wpdd_watermark_text', '{customer_email}'),
 | 
			
		||||
            'position' => get_option('wpdd_watermark_position', 'center'),
 | 
			
		||||
            'opacity' => get_option('wpdd_watermark_opacity', 30),
 | 
			
		||||
            'font_size' => get_option('wpdd_watermark_font_size', 'auto'),
 | 
			
		||||
            'color' => get_option('wpdd_watermark_color', '#ffffff')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static function preview_watermark($file_type = 'image') {
 | 
			
		||||
        $settings = self::get_watermark_settings();
 | 
			
		||||
        $preview_text = str_replace(
 | 
			
		||||
            array('{customer_name}', '{customer_email}', '{order_id}', '{date}', '{site_name}'),
 | 
			
		||||
            array('John Doe', 'john@example.com', 'WPDD-123456', date_i18n(get_option('date_format')), get_bloginfo('name')),
 | 
			
		||||
            $settings['text']
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($file_type === 'image') {
 | 
			
		||||
            $width = 600;
 | 
			
		||||
            $height = 400;
 | 
			
		||||
            
 | 
			
		||||
            $image = imagecreatetruecolor($width, $height);
 | 
			
		||||
            $bg_color = imagecolorallocate($image, 240, 240, 240);
 | 
			
		||||
            imagefill($image, 0, 0, $bg_color);
 | 
			
		||||
            
 | 
			
		||||
            $text_color = imagecolorallocatealpha($image, 100, 100, 100, 50);
 | 
			
		||||
            
 | 
			
		||||
            $font_size = 20;
 | 
			
		||||
            $x = ($width - (strlen($preview_text) * 10)) / 2;
 | 
			
		||||
            $y = $height / 2;
 | 
			
		||||
            
 | 
			
		||||
            imagestring($image, 5, $x, $y, $preview_text, $text_color);
 | 
			
		||||
            
 | 
			
		||||
            header('Content-Type: image/png');
 | 
			
		||||
            imagepng($image);
 | 
			
		||||
            imagedestroy($image);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								node_modules/.bin/playwright
									
									
									
										generated
									
									
										vendored
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								node_modules/.bin/playwright
									
									
									
										generated
									
									
										vendored
									
									
										Symbolic link
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
../@playwright/test/cli.js
 | 
			
		||||
							
								
								
									
										1
									
								
								node_modules/.bin/playwright-core
									
									
									
										generated
									
									
										vendored
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								node_modules/.bin/playwright-core
									
									
									
										generated
									
									
										vendored
									
									
										Symbolic link
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
../playwright-core/cli.js
 | 
			
		||||
							
								
								
									
										202
									
								
								node_modules/@playwright/test/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								node_modules/@playwright/test/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,202 @@
 | 
			
		||||
                                 Apache License
 | 
			
		||||
                           Version 2.0, January 2004
 | 
			
		||||
                        http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
   1. Definitions.
 | 
			
		||||
 | 
			
		||||
      "License" shall mean the terms and conditions for use, reproduction,
 | 
			
		||||
      and distribution as defined by Sections 1 through 9 of this document.
 | 
			
		||||
 | 
			
		||||
      "Licensor" shall mean the copyright owner or entity authorized by
 | 
			
		||||
      the copyright owner that is granting the License.
 | 
			
		||||
 | 
			
		||||
      "Legal Entity" shall mean the union of the acting entity and all
 | 
			
		||||
      other entities that control, are controlled by, or are under common
 | 
			
		||||
      control with that entity. For the purposes of this definition,
 | 
			
		||||
      "control" means (i) the power, direct or indirect, to cause the
 | 
			
		||||
      direction or management of such entity, whether by contract or
 | 
			
		||||
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
			
		||||
      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
			
		||||
 | 
			
		||||
      "You" (or "Your") shall mean an individual or Legal Entity
 | 
			
		||||
      exercising permissions granted by this License.
 | 
			
		||||
 | 
			
		||||
      "Source" form shall mean the preferred form for making modifications,
 | 
			
		||||
      including but not limited to software source code, documentation
 | 
			
		||||
      source, and configuration files.
 | 
			
		||||
 | 
			
		||||
      "Object" form shall mean any form resulting from mechanical
 | 
			
		||||
      transformation or translation of a Source form, including but
 | 
			
		||||
      not limited to compiled object code, generated documentation,
 | 
			
		||||
      and conversions to other media types.
 | 
			
		||||
 | 
			
		||||
      "Work" shall mean the work of authorship, whether in Source or
 | 
			
		||||
      Object form, made available under the License, as indicated by a
 | 
			
		||||
      copyright notice that is included in or attached to the work
 | 
			
		||||
      (an example is provided in the Appendix below).
 | 
			
		||||
 | 
			
		||||
      "Derivative Works" shall mean any work, whether in Source or Object
 | 
			
		||||
      form, that is based on (or derived from) the Work and for which the
 | 
			
		||||
      editorial revisions, annotations, elaborations, or other modifications
 | 
			
		||||
      represent, as a whole, an original work of authorship. For the purposes
 | 
			
		||||
      of this License, Derivative Works shall not include works that remain
 | 
			
		||||
      separable from, or merely link (or bind by name) to the interfaces of,
 | 
			
		||||
      the Work and Derivative Works thereof.
 | 
			
		||||
 | 
			
		||||
      "Contribution" shall mean any work of authorship, including
 | 
			
		||||
      the original version of the Work and any modifications or additions
 | 
			
		||||
      to that Work or Derivative Works thereof, that is intentionally
 | 
			
		||||
      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
			
		||||
      or by an individual or Legal Entity authorized to submit on behalf of
 | 
			
		||||
      the copyright owner. For the purposes of this definition, "submitted"
 | 
			
		||||
      means any form of electronic, verbal, or written communication sent
 | 
			
		||||
      to the Licensor or its representatives, including but not limited to
 | 
			
		||||
      communication on electronic mailing lists, source code control systems,
 | 
			
		||||
      and issue tracking systems that are managed by, or on behalf of, the
 | 
			
		||||
      Licensor for the purpose of discussing and improving the Work, but
 | 
			
		||||
      excluding communication that is conspicuously marked or otherwise
 | 
			
		||||
      designated in writing by the copyright owner as "Not a Contribution."
 | 
			
		||||
 | 
			
		||||
      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
			
		||||
      on behalf of whom a Contribution has been received by Licensor and
 | 
			
		||||
      subsequently incorporated within the Work.
 | 
			
		||||
 | 
			
		||||
   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      copyright license to reproduce, prepare Derivative Works of,
 | 
			
		||||
      publicly display, publicly perform, sublicense, and distribute the
 | 
			
		||||
      Work and such Derivative Works in Source or Object form.
 | 
			
		||||
 | 
			
		||||
   3. Grant of Patent License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      (except as stated in this section) patent license to make, have made,
 | 
			
		||||
      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
			
		||||
      where such license applies only to those patent claims licensable
 | 
			
		||||
      by such Contributor that are necessarily infringed by their
 | 
			
		||||
      Contribution(s) alone or by combination of their Contribution(s)
 | 
			
		||||
      with the Work to which such Contribution(s) was submitted. If You
 | 
			
		||||
      institute patent litigation against any entity (including a
 | 
			
		||||
      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
			
		||||
      or a Contribution incorporated within the Work constitutes direct
 | 
			
		||||
      or contributory patent infringement, then any patent licenses
 | 
			
		||||
      granted to You under this License for that Work shall terminate
 | 
			
		||||
      as of the date such litigation is filed.
 | 
			
		||||
 | 
			
		||||
   4. Redistribution. You may reproduce and distribute copies of the
 | 
			
		||||
      Work or Derivative Works thereof in any medium, with or without
 | 
			
		||||
      modifications, and in Source or Object form, provided that You
 | 
			
		||||
      meet the following conditions:
 | 
			
		||||
 | 
			
		||||
      (a) You must give any other recipients of the Work or
 | 
			
		||||
          Derivative Works a copy of this License; and
 | 
			
		||||
 | 
			
		||||
      (b) You must cause any modified files to carry prominent notices
 | 
			
		||||
          stating that You changed the files; and
 | 
			
		||||
 | 
			
		||||
      (c) You must retain, in the Source form of any Derivative Works
 | 
			
		||||
          that You distribute, all copyright, patent, trademark, and
 | 
			
		||||
          attribution notices from the Source form of the Work,
 | 
			
		||||
          excluding those notices that do not pertain to any part of
 | 
			
		||||
          the Derivative Works; and
 | 
			
		||||
 | 
			
		||||
      (d) If the Work includes a "NOTICE" text file as part of its
 | 
			
		||||
          distribution, then any Derivative Works that You distribute must
 | 
			
		||||
          include a readable copy of the attribution notices contained
 | 
			
		||||
          within such NOTICE file, excluding those notices that do not
 | 
			
		||||
          pertain to any part of the Derivative Works, in at least one
 | 
			
		||||
          of the following places: within a NOTICE text file distributed
 | 
			
		||||
          as part of the Derivative Works; within the Source form or
 | 
			
		||||
          documentation, if provided along with the Derivative Works; or,
 | 
			
		||||
          within a display generated by the Derivative Works, if and
 | 
			
		||||
          wherever such third-party notices normally appear. The contents
 | 
			
		||||
          of the NOTICE file are for informational purposes only and
 | 
			
		||||
          do not modify the License. You may add Your own attribution
 | 
			
		||||
          notices within Derivative Works that You distribute, alongside
 | 
			
		||||
          or as an addendum to the NOTICE text from the Work, provided
 | 
			
		||||
          that such additional attribution notices cannot be construed
 | 
			
		||||
          as modifying the License.
 | 
			
		||||
 | 
			
		||||
      You may add Your own copyright statement to Your modifications and
 | 
			
		||||
      may provide additional or different license terms and conditions
 | 
			
		||||
      for use, reproduction, or distribution of Your modifications, or
 | 
			
		||||
      for any such Derivative Works as a whole, provided Your use,
 | 
			
		||||
      reproduction, and distribution of the Work otherwise complies with
 | 
			
		||||
      the conditions stated in this License.
 | 
			
		||||
 | 
			
		||||
   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
			
		||||
      any Contribution intentionally submitted for inclusion in the Work
 | 
			
		||||
      by You to the Licensor shall be under the terms and conditions of
 | 
			
		||||
      this License, without any additional terms or conditions.
 | 
			
		||||
      Notwithstanding the above, nothing herein shall supersede or modify
 | 
			
		||||
      the terms of any separate license agreement you may have executed
 | 
			
		||||
      with Licensor regarding such Contributions.
 | 
			
		||||
 | 
			
		||||
   6. Trademarks. This License does not grant permission to use the trade
 | 
			
		||||
      names, trademarks, service marks, or product names of the Licensor,
 | 
			
		||||
      except as required for reasonable and customary use in describing the
 | 
			
		||||
      origin of the Work and reproducing the content of the NOTICE file.
 | 
			
		||||
 | 
			
		||||
   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
			
		||||
      agreed to in writing, Licensor provides the Work (and each
 | 
			
		||||
      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
			
		||||
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
      implied, including, without limitation, any warranties or conditions
 | 
			
		||||
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
			
		||||
      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
			
		||||
      appropriateness of using or redistributing the Work and assume any
 | 
			
		||||
      risks associated with Your exercise of permissions under this License.
 | 
			
		||||
 | 
			
		||||
   8. Limitation of Liability. In no event and under no legal theory,
 | 
			
		||||
      whether in tort (including negligence), contract, or otherwise,
 | 
			
		||||
      unless required by applicable law (such as deliberate and grossly
 | 
			
		||||
      negligent acts) or agreed to in writing, shall any Contributor be
 | 
			
		||||
      liable to You for damages, including any direct, indirect, special,
 | 
			
		||||
      incidental, or consequential damages of any character arising as a
 | 
			
		||||
      result of this License or out of the use or inability to use the
 | 
			
		||||
      Work (including but not limited to damages for loss of goodwill,
 | 
			
		||||
      work stoppage, computer failure or malfunction, or any and all
 | 
			
		||||
      other commercial damages or losses), even if such Contributor
 | 
			
		||||
      has been advised of the possibility of such damages.
 | 
			
		||||
 | 
			
		||||
   9. Accepting Warranty or Additional Liability. While redistributing
 | 
			
		||||
      the Work or Derivative Works thereof, You may choose to offer,
 | 
			
		||||
      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
			
		||||
      or other liability obligations and/or rights consistent with this
 | 
			
		||||
      License. However, in accepting such obligations, You may act only
 | 
			
		||||
      on Your own behalf and on Your sole responsibility, not on behalf
 | 
			
		||||
      of any other Contributor, and only if You agree to indemnify,
 | 
			
		||||
      defend, and hold each Contributor harmless for any liability
 | 
			
		||||
      incurred by, or claims asserted against, such Contributor by reason
 | 
			
		||||
      of your accepting any such warranty or additional liability.
 | 
			
		||||
 | 
			
		||||
   END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
   APPENDIX: How to apply the Apache License to your work.
 | 
			
		||||
 | 
			
		||||
      To apply the Apache License to your work, attach the following
 | 
			
		||||
      boilerplate notice, with the fields enclosed by brackets "[]"
 | 
			
		||||
      replaced with your own identifying information. (Don't include
 | 
			
		||||
      the brackets!)  The text should be enclosed in the appropriate
 | 
			
		||||
      comment syntax for the file format. We also recommend that a
 | 
			
		||||
      file or class name and description of purpose be included on the
 | 
			
		||||
      same "printed page" as the copyright notice for easier
 | 
			
		||||
      identification within third-party archives.
 | 
			
		||||
 | 
			
		||||
   Portions Copyright (c) Microsoft Corporation.
 | 
			
		||||
   Portions Copyright 2017 Google Inc.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
							
								
								
									
										5
									
								
								node_modules/@playwright/test/NOTICE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								node_modules/@playwright/test/NOTICE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
Playwright
 | 
			
		||||
Copyright (c) Microsoft Corporation
 | 
			
		||||
 | 
			
		||||
This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer),
 | 
			
		||||
available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE).
 | 
			
		||||
							
								
								
									
										168
									
								
								node_modules/@playwright/test/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								node_modules/@playwright/test/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
			
		||||
# 🎭 Playwright
 | 
			
		||||
 | 
			
		||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop --> [](https://aka.ms/playwright/discord)
 | 
			
		||||
 | 
			
		||||
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
 | 
			
		||||
 | 
			
		||||
Playwright is a framework for Web Testing and Automation. It allows testing [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.
 | 
			
		||||
 | 
			
		||||
|          | Linux | macOS | Windows |
 | 
			
		||||
|   :---   | :---: | :---: | :---:   |
 | 
			
		||||
| Chromium <!-- GEN:chromium-version -->140.0.7339.16<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
 | 
			
		||||
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
 | 
			
		||||
| Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
 | 
			
		||||
 | 
			
		||||
Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.
 | 
			
		||||
 | 
			
		||||
Looking for Playwright for [Python](https://playwright.dev/python/docs/intro), [.NET](https://playwright.dev/dotnet/docs/intro), or [Java](https://playwright.dev/java/docs/intro)?
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
Playwright has its own test runner for end-to-end tests, we call it Playwright Test.
 | 
			
		||||
 | 
			
		||||
### Using init command
 | 
			
		||||
 | 
			
		||||
The easiest way to get started with Playwright Test is to run the init command.
 | 
			
		||||
 | 
			
		||||
```Shell
 | 
			
		||||
# Run from your project's root directory
 | 
			
		||||
npm init playwright@latest
 | 
			
		||||
# Or create a new project
 | 
			
		||||
npm init playwright@latest new-project
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This will create a configuration file, optionally add examples, a GitHub Action workflow and a first test example.spec.ts. You can now jump directly to writing assertions section.
 | 
			
		||||
 | 
			
		||||
### Manually
 | 
			
		||||
 | 
			
		||||
Add dependency and install browsers.
 | 
			
		||||
 | 
			
		||||
```Shell
 | 
			
		||||
npm i -D @playwright/test
 | 
			
		||||
# install supported browsers
 | 
			
		||||
npx playwright install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You can optionally install only selected browsers, see [install browsers](https://playwright.dev/docs/cli#install-browsers) for more details. Or you can install no browsers at all and use existing [browser channels](https://playwright.dev/docs/browsers).
 | 
			
		||||
 | 
			
		||||
* [Getting started](https://playwright.dev/docs/intro)
 | 
			
		||||
* [API reference](https://playwright.dev/docs/api/class-playwright)
 | 
			
		||||
 | 
			
		||||
## Capabilities
 | 
			
		||||
 | 
			
		||||
### Resilient • No flaky tests
 | 
			
		||||
 | 
			
		||||
**Auto-wait**. Playwright waits for elements to be actionable prior to performing actions. It also has a rich set of introspection events. The combination of the two eliminates the need for artificial timeouts - a primary cause of flaky tests.
 | 
			
		||||
 | 
			
		||||
**Web-first assertions**. Playwright assertions are created specifically for the dynamic web. Checks are automatically retried until the necessary conditions are met.
 | 
			
		||||
 | 
			
		||||
**Tracing**. Configure test retry strategy, capture execution trace, videos and screenshots to eliminate flakes.
 | 
			
		||||
 | 
			
		||||
### No trade-offs • No limits
 | 
			
		||||
 | 
			
		||||
Browsers run web content belonging to different origins in different processes. Playwright is aligned with the architecture of the modern browsers and runs tests out-of-process. This makes Playwright free of the typical in-process test runner limitations.
 | 
			
		||||
 | 
			
		||||
**Multiple everything**. Test scenarios that span multiple tabs, multiple origins and multiple users. Create scenarios with different contexts for different users and run them against your server, all in one test.
 | 
			
		||||
 | 
			
		||||
**Trusted events**. Hover elements, interact with dynamic controls and produce trusted events. Playwright uses real browser input pipeline indistinguishable from the real user.
 | 
			
		||||
 | 
			
		||||
Test frames, pierce Shadow DOM. Playwright selectors pierce shadow DOM and allow entering frames seamlessly.
 | 
			
		||||
 | 
			
		||||
### Full isolation • Fast execution
 | 
			
		||||
 | 
			
		||||
**Browser contexts**. Playwright creates a browser context for each test. Browser context is equivalent to a brand new browser profile. This delivers full test isolation with zero overhead. Creating a new browser context only takes a handful of milliseconds.
 | 
			
		||||
 | 
			
		||||
**Log in once**. Save the authentication state of the context and reuse it in all the tests. This bypasses repetitive log-in operations in each test, yet delivers full isolation of independent tests.
 | 
			
		||||
 | 
			
		||||
### Powerful Tooling
 | 
			
		||||
 | 
			
		||||
**[Codegen](https://playwright.dev/docs/codegen)**. Generate tests by recording your actions. Save them into any language.
 | 
			
		||||
 | 
			
		||||
**[Playwright inspector](https://playwright.dev/docs/inspector)**. Inspect page, generate selectors, step through the test execution, see click points and explore execution logs.
 | 
			
		||||
 | 
			
		||||
**[Trace Viewer](https://playwright.dev/docs/trace-viewer)**. Capture all the information to investigate the test failure. Playwright trace contains test execution screencast, live DOM snapshots, action explorer, test source and many more.
 | 
			
		||||
 | 
			
		||||
Looking for Playwright for [TypeScript](https://playwright.dev/docs/intro), [JavaScript](https://playwright.dev/docs/intro), [Python](https://playwright.dev/python/docs/intro), [.NET](https://playwright.dev/dotnet/docs/intro), or [Java](https://playwright.dev/java/docs/intro)?
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
To learn how to run these Playwright Test examples, check out our [getting started docs](https://playwright.dev/docs/intro).
 | 
			
		||||
 | 
			
		||||
#### Page screenshot
 | 
			
		||||
 | 
			
		||||
This code snippet navigates to Playwright homepage and saves a screenshot.
 | 
			
		||||
 | 
			
		||||
```TypeScript
 | 
			
		||||
import { test } from '@playwright/test';
 | 
			
		||||
 | 
			
		||||
test('Page Screenshot', async ({ page }) => {
 | 
			
		||||
  await page.goto('https://playwright.dev/');
 | 
			
		||||
  await page.screenshot({ path: `example.png` });
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Mobile and geolocation
 | 
			
		||||
 | 
			
		||||
This snippet emulates Mobile Safari on a device at given geolocation, navigates to maps.google.com, performs the action and takes a screenshot.
 | 
			
		||||
 | 
			
		||||
```TypeScript
 | 
			
		||||
import { test, devices } from '@playwright/test';
 | 
			
		||||
 | 
			
		||||
test.use({
 | 
			
		||||
  ...devices['iPhone 13 Pro'],
 | 
			
		||||
  locale: 'en-US',
 | 
			
		||||
  geolocation: { longitude: 12.492507, latitude: 41.889938 },
 | 
			
		||||
  permissions: ['geolocation'],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Mobile and geolocation', async ({ page }) => {
 | 
			
		||||
  await page.goto('https://maps.google.com');
 | 
			
		||||
  await page.getByText('Your location').click();
 | 
			
		||||
  await page.waitForRequest(/.*preview\/pwa/);
 | 
			
		||||
  await page.screenshot({ path: 'colosseum-iphone.png' });
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Evaluate in browser context
 | 
			
		||||
 | 
			
		||||
This code snippet navigates to example.com, and executes a script in the page context.
 | 
			
		||||
 | 
			
		||||
```TypeScript
 | 
			
		||||
import { test } from '@playwright/test';
 | 
			
		||||
 | 
			
		||||
test('Evaluate in browser context', async ({ page }) => {
 | 
			
		||||
  await page.goto('https://www.example.com/');
 | 
			
		||||
  const dimensions = await page.evaluate(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      width: document.documentElement.clientWidth,
 | 
			
		||||
      height: document.documentElement.clientHeight,
 | 
			
		||||
      deviceScaleFactor: window.devicePixelRatio
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  console.log(dimensions);
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Intercept network requests
 | 
			
		||||
 | 
			
		||||
This code snippet sets up request routing for a page to log all network requests.
 | 
			
		||||
 | 
			
		||||
```TypeScript
 | 
			
		||||
import { test } from '@playwright/test';
 | 
			
		||||
 | 
			
		||||
test('Intercept network requests', async ({ page }) => {
 | 
			
		||||
  // Log and continue all network requests
 | 
			
		||||
  await page.route('**', route => {
 | 
			
		||||
    console.log(route.request().url());
 | 
			
		||||
    route.continue();
 | 
			
		||||
  });
 | 
			
		||||
  await page.goto('http://todomvc.com');
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Resources
 | 
			
		||||
 | 
			
		||||
* [Documentation](https://playwright.dev)
 | 
			
		||||
* [API reference](https://playwright.dev/docs/api/class-playwright/)
 | 
			
		||||
* [Contribution guide](CONTRIBUTING.md)
 | 
			
		||||
* [Changelog](https://github.com/microsoft/playwright/releases)
 | 
			
		||||
							
								
								
									
										19
									
								
								node_modules/@playwright/test/cli.js
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								node_modules/@playwright/test/cli.js
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
#!/usr/bin/env node
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const { program } = require('playwright/lib/program');
 | 
			
		||||
program.parse(process.argv);
 | 
			
		||||
							
								
								
									
										18
									
								
								node_modules/@playwright/test/index.d.ts
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								node_modules/@playwright/test/index.d.ts
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export * from 'playwright/test';
 | 
			
		||||
export { default } from 'playwright/test';
 | 
			
		||||
							
								
								
									
										17
									
								
								node_modules/@playwright/test/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								node_modules/@playwright/test/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
module.exports = require('playwright/test');
 | 
			
		||||
							
								
								
									
										18
									
								
								node_modules/@playwright/test/index.mjs
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								node_modules/@playwright/test/index.mjs
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export * from 'playwright/test';
 | 
			
		||||
export { default } from 'playwright/test';
 | 
			
		||||
							
								
								
									
										17
									
								
								node_modules/@playwright/test/reporter.d.ts
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								node_modules/@playwright/test/reporter.d.ts
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export * from 'playwright/types/testReporter';
 | 
			
		||||
							
								
								
									
										17
									
								
								node_modules/@playwright/test/reporter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								node_modules/@playwright/test/reporter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// We only export types in reporter.d.ts.
 | 
			
		||||
							
								
								
									
										17
									
								
								node_modules/@playwright/test/reporter.mjs
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								node_modules/@playwright/test/reporter.mjs
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// We only export types in reporter.d.ts.
 | 
			
		||||
							
								
								
									
										202
									
								
								node_modules/playwright-core/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								node_modules/playwright-core/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,202 @@
 | 
			
		||||
                                 Apache License
 | 
			
		||||
                           Version 2.0, January 2004
 | 
			
		||||
                        http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
   1. Definitions.
 | 
			
		||||
 | 
			
		||||
      "License" shall mean the terms and conditions for use, reproduction,
 | 
			
		||||
      and distribution as defined by Sections 1 through 9 of this document.
 | 
			
		||||
 | 
			
		||||
      "Licensor" shall mean the copyright owner or entity authorized by
 | 
			
		||||
      the copyright owner that is granting the License.
 | 
			
		||||
 | 
			
		||||
      "Legal Entity" shall mean the union of the acting entity and all
 | 
			
		||||
      other entities that control, are controlled by, or are under common
 | 
			
		||||
      control with that entity. For the purposes of this definition,
 | 
			
		||||
      "control" means (i) the power, direct or indirect, to cause the
 | 
			
		||||
      direction or management of such entity, whether by contract or
 | 
			
		||||
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
			
		||||
      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
			
		||||
 | 
			
		||||
      "You" (or "Your") shall mean an individual or Legal Entity
 | 
			
		||||
      exercising permissions granted by this License.
 | 
			
		||||
 | 
			
		||||
      "Source" form shall mean the preferred form for making modifications,
 | 
			
		||||
      including but not limited to software source code, documentation
 | 
			
		||||
      source, and configuration files.
 | 
			
		||||
 | 
			
		||||
      "Object" form shall mean any form resulting from mechanical
 | 
			
		||||
      transformation or translation of a Source form, including but
 | 
			
		||||
      not limited to compiled object code, generated documentation,
 | 
			
		||||
      and conversions to other media types.
 | 
			
		||||
 | 
			
		||||
      "Work" shall mean the work of authorship, whether in Source or
 | 
			
		||||
      Object form, made available under the License, as indicated by a
 | 
			
		||||
      copyright notice that is included in or attached to the work
 | 
			
		||||
      (an example is provided in the Appendix below).
 | 
			
		||||
 | 
			
		||||
      "Derivative Works" shall mean any work, whether in Source or Object
 | 
			
		||||
      form, that is based on (or derived from) the Work and for which the
 | 
			
		||||
      editorial revisions, annotations, elaborations, or other modifications
 | 
			
		||||
      represent, as a whole, an original work of authorship. For the purposes
 | 
			
		||||
      of this License, Derivative Works shall not include works that remain
 | 
			
		||||
      separable from, or merely link (or bind by name) to the interfaces of,
 | 
			
		||||
      the Work and Derivative Works thereof.
 | 
			
		||||
 | 
			
		||||
      "Contribution" shall mean any work of authorship, including
 | 
			
		||||
      the original version of the Work and any modifications or additions
 | 
			
		||||
      to that Work or Derivative Works thereof, that is intentionally
 | 
			
		||||
      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
			
		||||
      or by an individual or Legal Entity authorized to submit on behalf of
 | 
			
		||||
      the copyright owner. For the purposes of this definition, "submitted"
 | 
			
		||||
      means any form of electronic, verbal, or written communication sent
 | 
			
		||||
      to the Licensor or its representatives, including but not limited to
 | 
			
		||||
      communication on electronic mailing lists, source code control systems,
 | 
			
		||||
      and issue tracking systems that are managed by, or on behalf of, the
 | 
			
		||||
      Licensor for the purpose of discussing and improving the Work, but
 | 
			
		||||
      excluding communication that is conspicuously marked or otherwise
 | 
			
		||||
      designated in writing by the copyright owner as "Not a Contribution."
 | 
			
		||||
 | 
			
		||||
      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
			
		||||
      on behalf of whom a Contribution has been received by Licensor and
 | 
			
		||||
      subsequently incorporated within the Work.
 | 
			
		||||
 | 
			
		||||
   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      copyright license to reproduce, prepare Derivative Works of,
 | 
			
		||||
      publicly display, publicly perform, sublicense, and distribute the
 | 
			
		||||
      Work and such Derivative Works in Source or Object form.
 | 
			
		||||
 | 
			
		||||
   3. Grant of Patent License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      (except as stated in this section) patent license to make, have made,
 | 
			
		||||
      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
			
		||||
      where such license applies only to those patent claims licensable
 | 
			
		||||
      by such Contributor that are necessarily infringed by their
 | 
			
		||||
      Contribution(s) alone or by combination of their Contribution(s)
 | 
			
		||||
      with the Work to which such Contribution(s) was submitted. If You
 | 
			
		||||
      institute patent litigation against any entity (including a
 | 
			
		||||
      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
			
		||||
      or a Contribution incorporated within the Work constitutes direct
 | 
			
		||||
      or contributory patent infringement, then any patent licenses
 | 
			
		||||
      granted to You under this License for that Work shall terminate
 | 
			
		||||
      as of the date such litigation is filed.
 | 
			
		||||
 | 
			
		||||
   4. Redistribution. You may reproduce and distribute copies of the
 | 
			
		||||
      Work or Derivative Works thereof in any medium, with or without
 | 
			
		||||
      modifications, and in Source or Object form, provided that You
 | 
			
		||||
      meet the following conditions:
 | 
			
		||||
 | 
			
		||||
      (a) You must give any other recipients of the Work or
 | 
			
		||||
          Derivative Works a copy of this License; and
 | 
			
		||||
 | 
			
		||||
      (b) You must cause any modified files to carry prominent notices
 | 
			
		||||
          stating that You changed the files; and
 | 
			
		||||
 | 
			
		||||
      (c) You must retain, in the Source form of any Derivative Works
 | 
			
		||||
          that You distribute, all copyright, patent, trademark, and
 | 
			
		||||
          attribution notices from the Source form of the Work,
 | 
			
		||||
          excluding those notices that do not pertain to any part of
 | 
			
		||||
          the Derivative Works; and
 | 
			
		||||
 | 
			
		||||
      (d) If the Work includes a "NOTICE" text file as part of its
 | 
			
		||||
          distribution, then any Derivative Works that You distribute must
 | 
			
		||||
          include a readable copy of the attribution notices contained
 | 
			
		||||
          within such NOTICE file, excluding those notices that do not
 | 
			
		||||
          pertain to any part of the Derivative Works, in at least one
 | 
			
		||||
          of the following places: within a NOTICE text file distributed
 | 
			
		||||
          as part of the Derivative Works; within the Source form or
 | 
			
		||||
          documentation, if provided along with the Derivative Works; or,
 | 
			
		||||
          within a display generated by the Derivative Works, if and
 | 
			
		||||
          wherever such third-party notices normally appear. The contents
 | 
			
		||||
          of the NOTICE file are for informational purposes only and
 | 
			
		||||
          do not modify the License. You may add Your own attribution
 | 
			
		||||
          notices within Derivative Works that You distribute, alongside
 | 
			
		||||
          or as an addendum to the NOTICE text from the Work, provided
 | 
			
		||||
          that such additional attribution notices cannot be construed
 | 
			
		||||
          as modifying the License.
 | 
			
		||||
 | 
			
		||||
      You may add Your own copyright statement to Your modifications and
 | 
			
		||||
      may provide additional or different license terms and conditions
 | 
			
		||||
      for use, reproduction, or distribution of Your modifications, or
 | 
			
		||||
      for any such Derivative Works as a whole, provided Your use,
 | 
			
		||||
      reproduction, and distribution of the Work otherwise complies with
 | 
			
		||||
      the conditions stated in this License.
 | 
			
		||||
 | 
			
		||||
   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
			
		||||
      any Contribution intentionally submitted for inclusion in the Work
 | 
			
		||||
      by You to the Licensor shall be under the terms and conditions of
 | 
			
		||||
      this License, without any additional terms or conditions.
 | 
			
		||||
      Notwithstanding the above, nothing herein shall supersede or modify
 | 
			
		||||
      the terms of any separate license agreement you may have executed
 | 
			
		||||
      with Licensor regarding such Contributions.
 | 
			
		||||
 | 
			
		||||
   6. Trademarks. This License does not grant permission to use the trade
 | 
			
		||||
      names, trademarks, service marks, or product names of the Licensor,
 | 
			
		||||
      except as required for reasonable and customary use in describing the
 | 
			
		||||
      origin of the Work and reproducing the content of the NOTICE file.
 | 
			
		||||
 | 
			
		||||
   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
			
		||||
      agreed to in writing, Licensor provides the Work (and each
 | 
			
		||||
      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
			
		||||
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
      implied, including, without limitation, any warranties or conditions
 | 
			
		||||
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
			
		||||
      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
			
		||||
      appropriateness of using or redistributing the Work and assume any
 | 
			
		||||
      risks associated with Your exercise of permissions under this License.
 | 
			
		||||
 | 
			
		||||
   8. Limitation of Liability. In no event and under no legal theory,
 | 
			
		||||
      whether in tort (including negligence), contract, or otherwise,
 | 
			
		||||
      unless required by applicable law (such as deliberate and grossly
 | 
			
		||||
      negligent acts) or agreed to in writing, shall any Contributor be
 | 
			
		||||
      liable to You for damages, including any direct, indirect, special,
 | 
			
		||||
      incidental, or consequential damages of any character arising as a
 | 
			
		||||
      result of this License or out of the use or inability to use the
 | 
			
		||||
      Work (including but not limited to damages for loss of goodwill,
 | 
			
		||||
      work stoppage, computer failure or malfunction, or any and all
 | 
			
		||||
      other commercial damages or losses), even if such Contributor
 | 
			
		||||
      has been advised of the possibility of such damages.
 | 
			
		||||
 | 
			
		||||
   9. Accepting Warranty or Additional Liability. While redistributing
 | 
			
		||||
      the Work or Derivative Works thereof, You may choose to offer,
 | 
			
		||||
      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
			
		||||
      or other liability obligations and/or rights consistent with this
 | 
			
		||||
      License. However, in accepting such obligations, You may act only
 | 
			
		||||
      on Your own behalf and on Your sole responsibility, not on behalf
 | 
			
		||||
      of any other Contributor, and only if You agree to indemnify,
 | 
			
		||||
      defend, and hold each Contributor harmless for any liability
 | 
			
		||||
      incurred by, or claims asserted against, such Contributor by reason
 | 
			
		||||
      of your accepting any such warranty or additional liability.
 | 
			
		||||
 | 
			
		||||
   END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
   APPENDIX: How to apply the Apache License to your work.
 | 
			
		||||
 | 
			
		||||
      To apply the Apache License to your work, attach the following
 | 
			
		||||
      boilerplate notice, with the fields enclosed by brackets "[]"
 | 
			
		||||
      replaced with your own identifying information. (Don't include
 | 
			
		||||
      the brackets!)  The text should be enclosed in the appropriate
 | 
			
		||||
      comment syntax for the file format. We also recommend that a
 | 
			
		||||
      file or class name and description of purpose be included on the
 | 
			
		||||
      same "printed page" as the copyright notice for easier
 | 
			
		||||
      identification within third-party archives.
 | 
			
		||||
 | 
			
		||||
   Portions Copyright (c) Microsoft Corporation.
 | 
			
		||||
   Portions Copyright 2017 Google Inc.
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
							
								
								
									
										5
									
								
								node_modules/playwright-core/NOTICE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								node_modules/playwright-core/NOTICE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
Playwright
 | 
			
		||||
Copyright (c) Microsoft Corporation
 | 
			
		||||
 | 
			
		||||
This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer),
 | 
			
		||||
available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE).
 | 
			
		||||
							
								
								
									
										3
									
								
								node_modules/playwright-core/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								node_modules/playwright-core/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
# playwright-core
 | 
			
		||||
 | 
			
		||||
This package contains the no-browser flavor of [Playwright](http://github.com/microsoft/playwright).
 | 
			
		||||
							
								
								
									
										1502
									
								
								node_modules/playwright-core/ThirdPartyNotices.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1502
									
								
								node_modules/playwright-core/ThirdPartyNotices.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5
									
								
								node_modules/playwright-core/bin/install_media_pack.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								node_modules/playwright-core/bin/install_media_pack.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
$osInfo = Get-WmiObject -Class Win32_OperatingSystem
 | 
			
		||||
# check if running on Windows Server
 | 
			
		||||
if ($osInfo.ProductType -eq 3) {
 | 
			
		||||
  Install-WindowsFeature Server-Media-Foundation
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
if [[ $(arch) == "aarch64" ]]; then
 | 
			
		||||
  echo "ERROR: not supported on Linux Arm64"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
 | 
			
		||||
  if [[ ! -f "/etc/os-release" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  ID=$(bash -c 'source /etc/os-release && echo $ID')
 | 
			
		||||
  if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 1. make sure to remove old beta if any.
 | 
			
		||||
if dpkg --get-selections | grep -q "^google-chrome-beta[[:space:]]*install$" >/dev/null; then
 | 
			
		||||
  apt-get remove -y google-chrome-beta
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 2. Update apt lists (needed to install curl and chrome dependencies)
 | 
			
		||||
apt-get update
 | 
			
		||||
 | 
			
		||||
# 3. Install curl to download chrome
 | 
			
		||||
if ! command -v curl >/dev/null; then
 | 
			
		||||
  apt-get install -y curl
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 4. download chrome beta from dl.google.com and install it.
 | 
			
		||||
cd /tmp
 | 
			
		||||
curl -O https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb
 | 
			
		||||
apt-get install -y ./google-chrome-beta_current_amd64.deb
 | 
			
		||||
rm -rf ./google-chrome-beta_current_amd64.deb
 | 
			
		||||
cd -
 | 
			
		||||
google-chrome-beta --version
 | 
			
		||||
							
								
								
									
										13
									
								
								node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										13
									
								
								node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
rm -rf "/Applications/Google Chrome Beta.app"
 | 
			
		||||
cd /tmp
 | 
			
		||||
curl --retry 3 -o ./googlechromebeta.dmg -k https://dl.google.com/chrome/mac/universal/beta/googlechromebeta.dmg
 | 
			
		||||
hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechromebeta.dmg ./googlechromebeta.dmg
 | 
			
		||||
cp -pR "/Volumes/googlechromebeta.dmg/Google Chrome Beta.app" /Applications
 | 
			
		||||
hdiutil detach /Volumes/googlechromebeta.dmg
 | 
			
		||||
rm -rf /tmp/googlechromebeta.dmg
 | 
			
		||||
 | 
			
		||||
/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta --version
 | 
			
		||||
							
								
								
									
										24
									
								
								node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
$ErrorActionPreference = 'Stop'
 | 
			
		||||
 | 
			
		||||
$url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromebetastandaloneenterprise64.msi'
 | 
			
		||||
 | 
			
		||||
Write-Host "Downloading Google Chrome Beta"
 | 
			
		||||
$wc = New-Object net.webclient
 | 
			
		||||
$msiInstaller = "$env:temp\google-chrome-beta.msi"
 | 
			
		||||
$wc.Downloadfile($url, $msiInstaller)
 | 
			
		||||
 | 
			
		||||
Write-Host "Installing Google Chrome Beta"
 | 
			
		||||
$arguments = "/i `"$msiInstaller`" /quiet"
 | 
			
		||||
Start-Process msiexec.exe -ArgumentList $arguments -Wait
 | 
			
		||||
Remove-Item $msiInstaller
 | 
			
		||||
 | 
			
		||||
$suffix = "\\Google\\Chrome Beta\\Application\\chrome.exe"
 | 
			
		||||
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
 | 
			
		||||
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles}$suffix").VersionInfo
 | 
			
		||||
} else {
 | 
			
		||||
    Write-Host "ERROR: Failed to install Google Chrome Beta."
 | 
			
		||||
    Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
 | 
			
		||||
    exit 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
if [[ $(arch) == "aarch64" ]]; then
 | 
			
		||||
  echo "ERROR: not supported on Linux Arm64"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
 | 
			
		||||
  if [[ ! -f "/etc/os-release" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  ID=$(bash -c 'source /etc/os-release && echo $ID')
 | 
			
		||||
  if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 1. make sure to remove old stable if any.
 | 
			
		||||
if dpkg --get-selections | grep -q "^google-chrome[[:space:]]*install$" >/dev/null; then
 | 
			
		||||
  apt-get remove -y google-chrome
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 2. Update apt lists (needed to install curl and chrome dependencies)
 | 
			
		||||
apt-get update
 | 
			
		||||
 | 
			
		||||
# 3. Install curl to download chrome
 | 
			
		||||
if ! command -v curl >/dev/null; then
 | 
			
		||||
  apt-get install -y curl
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 4. download chrome stable from dl.google.com and install it.
 | 
			
		||||
cd /tmp
 | 
			
		||||
curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
 | 
			
		||||
apt-get install -y ./google-chrome-stable_current_amd64.deb
 | 
			
		||||
rm -rf ./google-chrome-stable_current_amd64.deb
 | 
			
		||||
cd -
 | 
			
		||||
google-chrome --version
 | 
			
		||||
							
								
								
									
										12
									
								
								node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
rm -rf "/Applications/Google Chrome.app"
 | 
			
		||||
cd /tmp
 | 
			
		||||
curl --retry 3 -o ./googlechrome.dmg -k https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg
 | 
			
		||||
hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechrome.dmg ./googlechrome.dmg
 | 
			
		||||
cp -pR "/Volumes/googlechrome.dmg/Google Chrome.app" /Applications
 | 
			
		||||
hdiutil detach /Volumes/googlechrome.dmg
 | 
			
		||||
rm -rf /tmp/googlechrome.dmg
 | 
			
		||||
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version
 | 
			
		||||
							
								
								
									
										24
									
								
								node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
$ErrorActionPreference = 'Stop'
 | 
			
		||||
$url = 'https://dl.google.com/tag/s/dl/chrome/install/googlechromestandaloneenterprise64.msi'
 | 
			
		||||
 | 
			
		||||
$wc = New-Object net.webclient
 | 
			
		||||
$msiInstaller = "$env:temp\google-chrome.msi"
 | 
			
		||||
Write-Host "Downloading Google Chrome"
 | 
			
		||||
$wc.Downloadfile($url, $msiInstaller)
 | 
			
		||||
 | 
			
		||||
Write-Host "Installing Google Chrome"
 | 
			
		||||
$arguments = "/i `"$msiInstaller`" /quiet"
 | 
			
		||||
Start-Process msiexec.exe -ArgumentList $arguments -Wait
 | 
			
		||||
Remove-Item $msiInstaller
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
$suffix = "\\Google\\Chrome\\Application\\chrome.exe"
 | 
			
		||||
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
 | 
			
		||||
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles}$suffix").VersionInfo
 | 
			
		||||
} else {
 | 
			
		||||
    Write-Host "ERROR: Failed to install Google Chrome."
 | 
			
		||||
    Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
 | 
			
		||||
    exit 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										48
									
								
								node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
if [[ $(arch) == "aarch64" ]]; then
 | 
			
		||||
  echo "ERROR: not supported on Linux Arm64"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
 | 
			
		||||
  if [[ ! -f "/etc/os-release" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  ID=$(bash -c 'source /etc/os-release && echo $ID')
 | 
			
		||||
  if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 1. make sure to remove old beta if any.
 | 
			
		||||
if dpkg --get-selections | grep -q "^microsoft-edge-beta[[:space:]]*install$" >/dev/null; then
 | 
			
		||||
  apt-get remove -y microsoft-edge-beta
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 2. Install curl to download Microsoft gpg key
 | 
			
		||||
if ! command -v curl >/dev/null; then
 | 
			
		||||
  apt-get update
 | 
			
		||||
  apt-get install -y curl
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# GnuPG is not preinstalled in slim images
 | 
			
		||||
if ! command -v gpg >/dev/null; then
 | 
			
		||||
  apt-get update
 | 
			
		||||
  apt-get install -y gpg
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
 | 
			
		||||
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
 | 
			
		||||
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
 | 
			
		||||
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list'
 | 
			
		||||
rm /tmp/microsoft.gpg
 | 
			
		||||
apt-get update && apt-get install -y microsoft-edge-beta
 | 
			
		||||
 | 
			
		||||
microsoft-edge-beta --version
 | 
			
		||||
							
								
								
									
										11
									
								
								node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
cd /tmp
 | 
			
		||||
curl --retry 3 -o ./msedge_beta.pkg -k "$1"
 | 
			
		||||
# Note: there's no way to uninstall previously installed MSEdge.
 | 
			
		||||
# However, running PKG again seems to update installation.
 | 
			
		||||
sudo installer -pkg /tmp/msedge_beta.pkg -target /
 | 
			
		||||
rm -rf /tmp/msedge_beta.pkg
 | 
			
		||||
/Applications/Microsoft\ Edge\ Beta.app/Contents/MacOS/Microsoft\ Edge\ Beta --version
 | 
			
		||||
							
								
								
									
										23
									
								
								node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
$ErrorActionPreference = 'Stop'
 | 
			
		||||
$url = $args[0]
 | 
			
		||||
 | 
			
		||||
Write-Host "Downloading Microsoft Edge Beta"
 | 
			
		||||
$wc = New-Object net.webclient
 | 
			
		||||
$msiInstaller = "$env:temp\microsoft-edge-beta.msi"
 | 
			
		||||
$wc.Downloadfile($url, $msiInstaller)
 | 
			
		||||
 | 
			
		||||
Write-Host "Installing Microsoft Edge Beta"
 | 
			
		||||
$arguments = "/i `"$msiInstaller`" /quiet"
 | 
			
		||||
Start-Process msiexec.exe -ArgumentList $arguments -Wait
 | 
			
		||||
Remove-Item $msiInstaller
 | 
			
		||||
 | 
			
		||||
$suffix = "\\Microsoft\\Edge Beta\\Application\\msedge.exe"
 | 
			
		||||
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
 | 
			
		||||
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles}$suffix").VersionInfo
 | 
			
		||||
} else {
 | 
			
		||||
    Write-Host "ERROR: Failed to install Microsoft Edge Beta."
 | 
			
		||||
    Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
 | 
			
		||||
    exit 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										48
									
								
								node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
if [[ $(arch) == "aarch64" ]]; then
 | 
			
		||||
  echo "ERROR: not supported on Linux Arm64"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
 | 
			
		||||
  if [[ ! -f "/etc/os-release" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  ID=$(bash -c 'source /etc/os-release && echo $ID')
 | 
			
		||||
  if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 1. make sure to remove old dev if any.
 | 
			
		||||
if dpkg --get-selections | grep -q "^microsoft-edge-dev[[:space:]]*install$" >/dev/null; then
 | 
			
		||||
  apt-get remove -y microsoft-edge-dev
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 2. Install curl to download Microsoft gpg key
 | 
			
		||||
if ! command -v curl >/dev/null; then
 | 
			
		||||
  apt-get update
 | 
			
		||||
  apt-get install -y curl
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# GnuPG is not preinstalled in slim images
 | 
			
		||||
if ! command -v gpg >/dev/null; then
 | 
			
		||||
  apt-get update
 | 
			
		||||
  apt-get install -y gpg
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
 | 
			
		||||
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
 | 
			
		||||
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
 | 
			
		||||
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list'
 | 
			
		||||
rm /tmp/microsoft.gpg
 | 
			
		||||
apt-get update && apt-get install -y microsoft-edge-dev
 | 
			
		||||
 | 
			
		||||
microsoft-edge-dev --version
 | 
			
		||||
							
								
								
									
										11
									
								
								node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
cd /tmp
 | 
			
		||||
curl --retry 3 -o ./msedge_dev.pkg -k "$1"
 | 
			
		||||
# Note: there's no way to uninstall previously installed MSEdge.
 | 
			
		||||
# However, running PKG again seems to update installation.
 | 
			
		||||
sudo installer -pkg /tmp/msedge_dev.pkg -target /
 | 
			
		||||
rm -rf /tmp/msedge_dev.pkg
 | 
			
		||||
/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev --version
 | 
			
		||||
							
								
								
									
										23
									
								
								node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
$ErrorActionPreference = 'Stop'
 | 
			
		||||
$url = $args[0]
 | 
			
		||||
 | 
			
		||||
Write-Host "Downloading Microsoft Edge Dev"
 | 
			
		||||
$wc = New-Object net.webclient
 | 
			
		||||
$msiInstaller = "$env:temp\microsoft-edge-dev.msi"
 | 
			
		||||
$wc.Downloadfile($url, $msiInstaller)
 | 
			
		||||
 | 
			
		||||
Write-Host "Installing Microsoft Edge Dev"
 | 
			
		||||
$arguments = "/i `"$msiInstaller`" /quiet"
 | 
			
		||||
Start-Process msiexec.exe -ArgumentList $arguments -Wait
 | 
			
		||||
Remove-Item $msiInstaller
 | 
			
		||||
 | 
			
		||||
$suffix = "\\Microsoft\\Edge Dev\\Application\\msedge.exe"
 | 
			
		||||
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
 | 
			
		||||
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles}$suffix").VersionInfo
 | 
			
		||||
} else {
 | 
			
		||||
    Write-Host "ERROR: Failed to install Microsoft Edge Dev."
 | 
			
		||||
    Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
 | 
			
		||||
    exit 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										48
									
								
								node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
if [[ $(arch) == "aarch64" ]]; then
 | 
			
		||||
  echo "ERROR: not supported on Linux Arm64"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
 | 
			
		||||
  if [[ ! -f "/etc/os-release" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  ID=$(bash -c 'source /etc/os-release && echo $ID')
 | 
			
		||||
  if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
 | 
			
		||||
    echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 1. make sure to remove old stable if any.
 | 
			
		||||
if dpkg --get-selections | grep -q "^microsoft-edge-stable[[:space:]]*install$" >/dev/null; then
 | 
			
		||||
  apt-get remove -y microsoft-edge-stable
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 2. Install curl to download Microsoft gpg key
 | 
			
		||||
if ! command -v curl >/dev/null; then
 | 
			
		||||
  apt-get update
 | 
			
		||||
  apt-get install -y curl
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# GnuPG is not preinstalled in slim images
 | 
			
		||||
if ! command -v gpg >/dev/null; then
 | 
			
		||||
  apt-get update
 | 
			
		||||
  apt-get install -y gpg
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
 | 
			
		||||
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
 | 
			
		||||
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
 | 
			
		||||
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-stable.list'
 | 
			
		||||
rm /tmp/microsoft.gpg
 | 
			
		||||
apt-get update && apt-get install -y microsoft-edge-stable
 | 
			
		||||
 | 
			
		||||
microsoft-edge-stable --version
 | 
			
		||||
							
								
								
									
										11
									
								
								node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
cd /tmp
 | 
			
		||||
curl --retry 3 -o ./msedge_stable.pkg -k "$1"
 | 
			
		||||
# Note: there's no way to uninstall previously installed MSEdge.
 | 
			
		||||
# However, running PKG again seems to update installation.
 | 
			
		||||
sudo installer -pkg /tmp/msedge_stable.pkg -target /
 | 
			
		||||
rm -rf /tmp/msedge_stable.pkg
 | 
			
		||||
/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge --version
 | 
			
		||||
							
								
								
									
										24
									
								
								node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
$ErrorActionPreference = 'Stop'
 | 
			
		||||
 | 
			
		||||
$url = $args[0]
 | 
			
		||||
 | 
			
		||||
Write-Host "Downloading Microsoft Edge"
 | 
			
		||||
$wc = New-Object net.webclient
 | 
			
		||||
$msiInstaller = "$env:temp\microsoft-edge-stable.msi"
 | 
			
		||||
$wc.Downloadfile($url, $msiInstaller)
 | 
			
		||||
 | 
			
		||||
Write-Host "Installing Microsoft Edge"
 | 
			
		||||
$arguments = "/i `"$msiInstaller`" /quiet"
 | 
			
		||||
Start-Process msiexec.exe -ArgumentList $arguments -Wait
 | 
			
		||||
Remove-Item $msiInstaller
 | 
			
		||||
 | 
			
		||||
$suffix = "\\Microsoft\\Edge\\Application\\msedge.exe"
 | 
			
		||||
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
 | 
			
		||||
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
 | 
			
		||||
    (Get-Item "${env:ProgramFiles}$suffix").VersionInfo
 | 
			
		||||
} else {
 | 
			
		||||
    Write-Host "ERROR: Failed to install Microsoft Edge."
 | 
			
		||||
    Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
 | 
			
		||||
    exit 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								node_modules/playwright-core/cli.js
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								node_modules/playwright-core/cli.js
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
#!/usr/bin/env node
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
const { program } = require('./lib/cli/programWithTestStub');
 | 
			
		||||
program.parse(process.argv);
 | 
			
		||||
							
								
								
									
										17
									
								
								node_modules/playwright-core/index.d.ts
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								node_modules/playwright-core/index.d.ts
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export * from './types/types';
 | 
			
		||||
							
								
								
									
										32
									
								
								node_modules/playwright-core/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								node_modules/playwright-core/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
const minimumMajorNodeVersion = 18;
 | 
			
		||||
const currentNodeVersion = process.versions.node;
 | 
			
		||||
const semver = currentNodeVersion.split('.');
 | 
			
		||||
const [major] = [+semver[0]];
 | 
			
		||||
 | 
			
		||||
if (major < minimumMajorNodeVersion) {
 | 
			
		||||
  console.error(
 | 
			
		||||
      'You are running Node.js ' +
 | 
			
		||||
      currentNodeVersion +
 | 
			
		||||
      '.\n' +
 | 
			
		||||
      `Playwright requires Node.js ${minimumMajorNodeVersion} or higher. \n` +
 | 
			
		||||
      'Please update your version of Node.js.'
 | 
			
		||||
  );
 | 
			
		||||
  process.exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = require('./lib/inprocess');
 | 
			
		||||
							
								
								
									
										28
									
								
								node_modules/playwright-core/index.mjs
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								node_modules/playwright-core/index.mjs
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import playwright from './index.js';
 | 
			
		||||
 | 
			
		||||
export const chromium = playwright.chromium;
 | 
			
		||||
export const firefox = playwright.firefox;
 | 
			
		||||
export const webkit = playwright.webkit;
 | 
			
		||||
export const selectors = playwright.selectors;
 | 
			
		||||
export const devices = playwright.devices;
 | 
			
		||||
export const errors = playwright.errors;
 | 
			
		||||
export const request = playwright.request;
 | 
			
		||||
export const _electron = playwright._electron;
 | 
			
		||||
export const _android = playwright._android;
 | 
			
		||||
export default playwright;
 | 
			
		||||
							
								
								
									
										65
									
								
								node_modules/playwright-core/lib/androidServerImpl.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								node_modules/playwright-core/lib/androidServerImpl.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var androidServerImpl_exports = {};
 | 
			
		||||
__export(androidServerImpl_exports, {
 | 
			
		||||
  AndroidServerLauncherImpl: () => AndroidServerLauncherImpl
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(androidServerImpl_exports);
 | 
			
		||||
var import_playwrightServer = require("./remote/playwrightServer");
 | 
			
		||||
var import_playwright = require("./server/playwright");
 | 
			
		||||
var import_crypto = require("./server/utils/crypto");
 | 
			
		||||
var import_utilsBundle = require("./utilsBundle");
 | 
			
		||||
var import_progress = require("./server/progress");
 | 
			
		||||
class AndroidServerLauncherImpl {
 | 
			
		||||
  async launchServer(options = {}) {
 | 
			
		||||
    const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
 | 
			
		||||
    const controller = new import_progress.ProgressController();
 | 
			
		||||
    let devices = await controller.run((progress) => playwright.android.devices(progress, {
 | 
			
		||||
      host: options.adbHost,
 | 
			
		||||
      port: options.adbPort,
 | 
			
		||||
      omitDriverInstall: options.omitDriverInstall
 | 
			
		||||
    }));
 | 
			
		||||
    if (devices.length === 0)
 | 
			
		||||
      throw new Error("No devices found");
 | 
			
		||||
    if (options.deviceSerialNumber) {
 | 
			
		||||
      devices = devices.filter((d) => d.serial === options.deviceSerialNumber);
 | 
			
		||||
      if (devices.length === 0)
 | 
			
		||||
        throw new Error(`No device with serial number '${options.deviceSerialNumber}' was found`);
 | 
			
		||||
    }
 | 
			
		||||
    if (devices.length > 1)
 | 
			
		||||
      throw new Error(`More than one device found. Please specify deviceSerialNumber`);
 | 
			
		||||
    const device = devices[0];
 | 
			
		||||
    const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
 | 
			
		||||
    const server = new import_playwrightServer.PlaywrightServer({ mode: "launchServer", path, maxConnections: 1, preLaunchedAndroidDevice: device });
 | 
			
		||||
    const wsEndpoint = await server.listen(options.port, options.host);
 | 
			
		||||
    const browserServer = new import_utilsBundle.ws.EventEmitter();
 | 
			
		||||
    browserServer.wsEndpoint = () => wsEndpoint;
 | 
			
		||||
    browserServer.close = () => device.close();
 | 
			
		||||
    browserServer.kill = () => device.close();
 | 
			
		||||
    device.on("close", () => {
 | 
			
		||||
      server.close();
 | 
			
		||||
      browserServer.emit("close");
 | 
			
		||||
    });
 | 
			
		||||
    return browserServer;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  AndroidServerLauncherImpl
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										123
									
								
								node_modules/playwright-core/lib/browserServerImpl.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								node_modules/playwright-core/lib/browserServerImpl.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __create = Object.create;
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __getProtoOf = Object.getPrototypeOf;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
 | 
			
		||||
  // If the importer is in node compatibility mode or this is not an ESM
 | 
			
		||||
  // file that has been converted to a CommonJS file using a Babel-
 | 
			
		||||
  // compatible transform (i.e. "__esModule" has not been set), then set
 | 
			
		||||
  // "default" to the CommonJS "module.exports" for node compatibility.
 | 
			
		||||
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
 | 
			
		||||
  mod
 | 
			
		||||
));
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var browserServerImpl_exports = {};
 | 
			
		||||
__export(browserServerImpl_exports, {
 | 
			
		||||
  BrowserServerLauncherImpl: () => BrowserServerLauncherImpl
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(browserServerImpl_exports);
 | 
			
		||||
var import_playwrightServer = require("./remote/playwrightServer");
 | 
			
		||||
var import_helper = require("./server/helper");
 | 
			
		||||
var import_playwright = require("./server/playwright");
 | 
			
		||||
var import_crypto = require("./server/utils/crypto");
 | 
			
		||||
var import_debug = require("./server/utils/debug");
 | 
			
		||||
var import_stackTrace = require("./utils/isomorphic/stackTrace");
 | 
			
		||||
var import_time = require("./utils/isomorphic/time");
 | 
			
		||||
var import_utilsBundle = require("./utilsBundle");
 | 
			
		||||
var validatorPrimitives = __toESM(require("./protocol/validatorPrimitives"));
 | 
			
		||||
var import_progress = require("./server/progress");
 | 
			
		||||
class BrowserServerLauncherImpl {
 | 
			
		||||
  constructor(browserName) {
 | 
			
		||||
    this._browserName = browserName;
 | 
			
		||||
  }
 | 
			
		||||
  async launchServer(options = {}) {
 | 
			
		||||
    const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
 | 
			
		||||
    const metadata = { id: "", startTime: 0, endTime: 0, type: "Internal", method: "", params: {}, log: [], internal: true };
 | 
			
		||||
    const validatorContext = {
 | 
			
		||||
      tChannelImpl: (names, arg, path) => {
 | 
			
		||||
        throw new validatorPrimitives.ValidationError(`${path}: channels are not expected in launchServer`);
 | 
			
		||||
      },
 | 
			
		||||
      binary: "buffer",
 | 
			
		||||
      isUnderTest: import_debug.isUnderTest
 | 
			
		||||
    };
 | 
			
		||||
    let launchOptions = {
 | 
			
		||||
      ...options,
 | 
			
		||||
      ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
 | 
			
		||||
      ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
 | 
			
		||||
      env: options.env ? envObjectToArray(options.env) : void 0,
 | 
			
		||||
      timeout: options.timeout ?? import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT
 | 
			
		||||
    };
 | 
			
		||||
    let browser;
 | 
			
		||||
    try {
 | 
			
		||||
      const controller = new import_progress.ProgressController(metadata);
 | 
			
		||||
      browser = await controller.run(async (progress) => {
 | 
			
		||||
        if (options._userDataDir !== void 0) {
 | 
			
		||||
          const validator = validatorPrimitives.scheme["BrowserTypeLaunchPersistentContextParams"];
 | 
			
		||||
          launchOptions = validator({ ...launchOptions, userDataDir: options._userDataDir }, "", validatorContext);
 | 
			
		||||
          const context = await playwright[this._browserName].launchPersistentContext(progress, options._userDataDir, launchOptions);
 | 
			
		||||
          return context._browser;
 | 
			
		||||
        } else {
 | 
			
		||||
          const validator = validatorPrimitives.scheme["BrowserTypeLaunchParams"];
 | 
			
		||||
          launchOptions = validator(launchOptions, "", validatorContext);
 | 
			
		||||
          return await playwright[this._browserName].launch(progress, launchOptions, toProtocolLogger(options.logger));
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      const log = import_helper.helper.formatBrowserLogs(metadata.log);
 | 
			
		||||
      (0, import_stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
    return this.launchServerOnExistingBrowser(browser, options);
 | 
			
		||||
  }
 | 
			
		||||
  async launchServerOnExistingBrowser(browser, options) {
 | 
			
		||||
    const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
 | 
			
		||||
    const server = new import_playwrightServer.PlaywrightServer({ mode: options._sharedBrowser ? "launchServerShared" : "launchServer", path, maxConnections: Infinity, preLaunchedBrowser: browser, debugController: options._debugController });
 | 
			
		||||
    const wsEndpoint = await server.listen(options.port, options.host);
 | 
			
		||||
    const browserServer = new import_utilsBundle.ws.EventEmitter();
 | 
			
		||||
    browserServer.process = () => browser.options.browserProcess.process;
 | 
			
		||||
    browserServer.wsEndpoint = () => wsEndpoint;
 | 
			
		||||
    browserServer.close = () => browser.options.browserProcess.close();
 | 
			
		||||
    browserServer[Symbol.asyncDispose] = browserServer.close;
 | 
			
		||||
    browserServer.kill = () => browser.options.browserProcess.kill();
 | 
			
		||||
    browserServer._disconnectForTest = () => server.close();
 | 
			
		||||
    browserServer._userDataDirForTest = browser._userDataDirForTest;
 | 
			
		||||
    browser.options.browserProcess.onclose = (exitCode, signal) => {
 | 
			
		||||
      server.close();
 | 
			
		||||
      browserServer.emit("close", exitCode, signal);
 | 
			
		||||
    };
 | 
			
		||||
    return browserServer;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function toProtocolLogger(logger) {
 | 
			
		||||
  return logger ? (direction, message) => {
 | 
			
		||||
    if (logger.isEnabled("protocol", "verbose"))
 | 
			
		||||
      logger.log("protocol", "verbose", (direction === "send" ? "SEND \u25BA " : "\u25C0 RECV ") + JSON.stringify(message), [], {});
 | 
			
		||||
  } : void 0;
 | 
			
		||||
}
 | 
			
		||||
function envObjectToArray(env) {
 | 
			
		||||
  const result = [];
 | 
			
		||||
  for (const name in env) {
 | 
			
		||||
    if (!Object.is(env[name], void 0))
 | 
			
		||||
      result.push({ name, value: String(env[name]) });
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  BrowserServerLauncherImpl
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										97
									
								
								node_modules/playwright-core/lib/cli/driver.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								node_modules/playwright-core/lib/cli/driver.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __create = Object.create;
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __getProtoOf = Object.getPrototypeOf;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
 | 
			
		||||
  // If the importer is in node compatibility mode or this is not an ESM
 | 
			
		||||
  // file that has been converted to a CommonJS file using a Babel-
 | 
			
		||||
  // compatible transform (i.e. "__esModule" has not been set), then set
 | 
			
		||||
  // "default" to the CommonJS "module.exports" for node compatibility.
 | 
			
		||||
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
 | 
			
		||||
  mod
 | 
			
		||||
));
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var driver_exports = {};
 | 
			
		||||
__export(driver_exports, {
 | 
			
		||||
  launchBrowserServer: () => launchBrowserServer,
 | 
			
		||||
  printApiJson: () => printApiJson,
 | 
			
		||||
  runDriver: () => runDriver,
 | 
			
		||||
  runServer: () => runServer
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(driver_exports);
 | 
			
		||||
var import_fs = __toESM(require("fs"));
 | 
			
		||||
var playwright = __toESM(require("../.."));
 | 
			
		||||
var import_pipeTransport = require("../server/utils/pipeTransport");
 | 
			
		||||
var import_playwrightServer = require("../remote/playwrightServer");
 | 
			
		||||
var import_server = require("../server");
 | 
			
		||||
var import_processLauncher = require("../server/utils/processLauncher");
 | 
			
		||||
function printApiJson() {
 | 
			
		||||
  console.log(JSON.stringify(require("../../api.json")));
 | 
			
		||||
}
 | 
			
		||||
function runDriver() {
 | 
			
		||||
  const dispatcherConnection = new import_server.DispatcherConnection();
 | 
			
		||||
  new import_server.RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => {
 | 
			
		||||
    const playwright2 = (0, import_server.createPlaywright)({ sdkLanguage });
 | 
			
		||||
    return new import_server.PlaywrightDispatcher(rootScope, playwright2);
 | 
			
		||||
  });
 | 
			
		||||
  const transport = new import_pipeTransport.PipeTransport(process.stdout, process.stdin);
 | 
			
		||||
  transport.onmessage = (message) => dispatcherConnection.dispatch(JSON.parse(message));
 | 
			
		||||
  const isJavaScriptLanguageBinding = !process.env.PW_LANG_NAME || process.env.PW_LANG_NAME === "javascript";
 | 
			
		||||
  const replacer = !isJavaScriptLanguageBinding && String.prototype.toWellFormed ? (key, value) => {
 | 
			
		||||
    if (typeof value === "string")
 | 
			
		||||
      return value.toWellFormed();
 | 
			
		||||
    return value;
 | 
			
		||||
  } : void 0;
 | 
			
		||||
  dispatcherConnection.onmessage = (message) => transport.send(JSON.stringify(message, replacer));
 | 
			
		||||
  transport.onclose = () => {
 | 
			
		||||
    dispatcherConnection.onmessage = () => {
 | 
			
		||||
    };
 | 
			
		||||
    (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0);
 | 
			
		||||
  };
 | 
			
		||||
  process.on("SIGINT", () => {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
async function runServer(options) {
 | 
			
		||||
  const {
 | 
			
		||||
    port,
 | 
			
		||||
    host,
 | 
			
		||||
    path = "/",
 | 
			
		||||
    maxConnections = Infinity,
 | 
			
		||||
    extension
 | 
			
		||||
  } = options;
 | 
			
		||||
  const server = new import_playwrightServer.PlaywrightServer({ mode: extension ? "extension" : "default", path, maxConnections });
 | 
			
		||||
  const wsEndpoint = await server.listen(port, host);
 | 
			
		||||
  process.on("exit", () => server.close().catch(console.error));
 | 
			
		||||
  console.log("Listening on " + wsEndpoint);
 | 
			
		||||
  process.stdin.on("close", () => (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0));
 | 
			
		||||
}
 | 
			
		||||
async function launchBrowserServer(browserName, configFile) {
 | 
			
		||||
  let options = {};
 | 
			
		||||
  if (configFile)
 | 
			
		||||
    options = JSON.parse(import_fs.default.readFileSync(configFile).toString());
 | 
			
		||||
  const browserType = playwright[browserName];
 | 
			
		||||
  const server = await browserType.launchServer(options);
 | 
			
		||||
  console.log(server.wsEndpoint());
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  launchBrowserServer,
 | 
			
		||||
  printApiJson,
 | 
			
		||||
  runDriver,
 | 
			
		||||
  runServer
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										633
									
								
								node_modules/playwright-core/lib/cli/program.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										633
									
								
								node_modules/playwright-core/lib/cli/program.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,633 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __create = Object.create;
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __getProtoOf = Object.getPrototypeOf;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
 | 
			
		||||
  // If the importer is in node compatibility mode or this is not an ESM
 | 
			
		||||
  // file that has been converted to a CommonJS file using a Babel-
 | 
			
		||||
  // compatible transform (i.e. "__esModule" has not been set), then set
 | 
			
		||||
  // "default" to the CommonJS "module.exports" for node compatibility.
 | 
			
		||||
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
 | 
			
		||||
  mod
 | 
			
		||||
));
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var program_exports = {};
 | 
			
		||||
__export(program_exports, {
 | 
			
		||||
  program: () => import_utilsBundle2.program
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(program_exports);
 | 
			
		||||
var import_fs = __toESM(require("fs"));
 | 
			
		||||
var import_os = __toESM(require("os"));
 | 
			
		||||
var import_path = __toESM(require("path"));
 | 
			
		||||
var playwright = __toESM(require("../.."));
 | 
			
		||||
var import_driver = require("./driver");
 | 
			
		||||
var import_server = require("../server");
 | 
			
		||||
var import_utils = require("../utils");
 | 
			
		||||
var import_traceViewer = require("../server/trace/viewer/traceViewer");
 | 
			
		||||
var import_utils2 = require("../utils");
 | 
			
		||||
var import_ascii = require("../server/utils/ascii");
 | 
			
		||||
var import_utilsBundle = require("../utilsBundle");
 | 
			
		||||
var import_utilsBundle2 = require("../utilsBundle");
 | 
			
		||||
const packageJSON = require("../../package.json");
 | 
			
		||||
import_utilsBundle.program.version("Version " + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
 | 
			
		||||
import_utilsBundle.program.command("mark-docker-image [dockerImageNameTemplate]", { hidden: true }).description("mark docker image").allowUnknownOption(true).action(function(dockerImageNameTemplate) {
 | 
			
		||||
  (0, import_utils2.assert)(dockerImageNameTemplate, "dockerImageNameTemplate is required");
 | 
			
		||||
  (0, import_server.writeDockerVersion)(dockerImageNameTemplate).catch(logErrorAndExit);
 | 
			
		||||
});
 | 
			
		||||
commandWithOpenOptions("open [url]", "open page in browser specified via -b, --browser", []).action(function(url, options) {
 | 
			
		||||
  open(options, url).catch(logErrorAndExit);
 | 
			
		||||
}).addHelpText("afterAll", `
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
  $ open
 | 
			
		||||
  $ open -b webkit https://example.com`);
 | 
			
		||||
commandWithOpenOptions(
 | 
			
		||||
  "codegen [url]",
 | 
			
		||||
  "open page and generate code for user actions",
 | 
			
		||||
  [
 | 
			
		||||
    ["-o, --output <file name>", "saves the generated script to a file"],
 | 
			
		||||
    ["--target <language>", `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()],
 | 
			
		||||
    ["--test-id-attribute <attributeName>", "use the specified attribute to generate data test ID selectors"]
 | 
			
		||||
  ]
 | 
			
		||||
).action(async function(url, options) {
 | 
			
		||||
  await codegen(options, url);
 | 
			
		||||
}).addHelpText("afterAll", `
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
  $ codegen
 | 
			
		||||
  $ codegen --target=python
 | 
			
		||||
  $ codegen -b webkit https://example.com`);
 | 
			
		||||
function suggestedBrowsersToInstall() {
 | 
			
		||||
  return import_server.registry.executables().filter((e) => e.installType !== "none" && e.type !== "tool").map((e) => e.name).join(", ");
 | 
			
		||||
}
 | 
			
		||||
function defaultBrowsersToInstall(options) {
 | 
			
		||||
  let executables = import_server.registry.defaultExecutables();
 | 
			
		||||
  if (options.noShell)
 | 
			
		||||
    executables = executables.filter((e) => e.name !== "chromium-headless-shell");
 | 
			
		||||
  if (options.onlyShell)
 | 
			
		||||
    executables = executables.filter((e) => e.name !== "chromium");
 | 
			
		||||
  return executables;
 | 
			
		||||
}
 | 
			
		||||
function checkBrowsersToInstall(args, options) {
 | 
			
		||||
  if (options.noShell && options.onlyShell)
 | 
			
		||||
    throw new Error(`Only one of --no-shell and --only-shell can be specified`);
 | 
			
		||||
  const faultyArguments = [];
 | 
			
		||||
  const executables = [];
 | 
			
		||||
  const handleArgument = (arg) => {
 | 
			
		||||
    const executable = import_server.registry.findExecutable(arg);
 | 
			
		||||
    if (!executable || executable.installType === "none")
 | 
			
		||||
      faultyArguments.push(arg);
 | 
			
		||||
    else
 | 
			
		||||
      executables.push(executable);
 | 
			
		||||
    if (executable?.browserName === "chromium")
 | 
			
		||||
      executables.push(import_server.registry.findExecutable("ffmpeg"));
 | 
			
		||||
  };
 | 
			
		||||
  for (const arg of args) {
 | 
			
		||||
    if (arg === "chromium") {
 | 
			
		||||
      if (!options.onlyShell)
 | 
			
		||||
        handleArgument("chromium");
 | 
			
		||||
      if (!options.noShell)
 | 
			
		||||
        handleArgument("chromium-headless-shell");
 | 
			
		||||
    } else {
 | 
			
		||||
      handleArgument(arg);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (process.platform === "win32")
 | 
			
		||||
    executables.push(import_server.registry.findExecutable("winldd"));
 | 
			
		||||
  if (faultyArguments.length)
 | 
			
		||||
    throw new Error(`Invalid installation targets: ${faultyArguments.map((name) => `'${name}'`).join(", ")}. Expecting one of: ${suggestedBrowsersToInstall()}`);
 | 
			
		||||
  return executables;
 | 
			
		||||
}
 | 
			
		||||
function printInstalledBrowsers(browsers2) {
 | 
			
		||||
  const browserPaths = /* @__PURE__ */ new Set();
 | 
			
		||||
  for (const browser of browsers2)
 | 
			
		||||
    browserPaths.add(browser.browserPath);
 | 
			
		||||
  console.log(`  Browsers:`);
 | 
			
		||||
  for (const browserPath of [...browserPaths].sort())
 | 
			
		||||
    console.log(`    ${browserPath}`);
 | 
			
		||||
  console.log(`  References:`);
 | 
			
		||||
  const references = /* @__PURE__ */ new Set();
 | 
			
		||||
  for (const browser of browsers2)
 | 
			
		||||
    references.add(browser.referenceDir);
 | 
			
		||||
  for (const reference of [...references].sort())
 | 
			
		||||
    console.log(`    ${reference}`);
 | 
			
		||||
}
 | 
			
		||||
function printGroupedByPlaywrightVersion(browsers2) {
 | 
			
		||||
  const dirToVersion = /* @__PURE__ */ new Map();
 | 
			
		||||
  for (const browser of browsers2) {
 | 
			
		||||
    if (dirToVersion.has(browser.referenceDir))
 | 
			
		||||
      continue;
 | 
			
		||||
    const packageJSON2 = require(import_path.default.join(browser.referenceDir, "package.json"));
 | 
			
		||||
    const version = packageJSON2.version;
 | 
			
		||||
    dirToVersion.set(browser.referenceDir, version);
 | 
			
		||||
  }
 | 
			
		||||
  const groupedByPlaywrightMinorVersion = /* @__PURE__ */ new Map();
 | 
			
		||||
  for (const browser of browsers2) {
 | 
			
		||||
    const version = dirToVersion.get(browser.referenceDir);
 | 
			
		||||
    let entries = groupedByPlaywrightMinorVersion.get(version);
 | 
			
		||||
    if (!entries) {
 | 
			
		||||
      entries = [];
 | 
			
		||||
      groupedByPlaywrightMinorVersion.set(version, entries);
 | 
			
		||||
    }
 | 
			
		||||
    entries.push(browser);
 | 
			
		||||
  }
 | 
			
		||||
  const sortedVersions = [...groupedByPlaywrightMinorVersion.keys()].sort((a, b) => {
 | 
			
		||||
    const aComponents = a.split(".");
 | 
			
		||||
    const bComponents = b.split(".");
 | 
			
		||||
    const aMajor = parseInt(aComponents[0], 10);
 | 
			
		||||
    const bMajor = parseInt(bComponents[0], 10);
 | 
			
		||||
    if (aMajor !== bMajor)
 | 
			
		||||
      return aMajor - bMajor;
 | 
			
		||||
    const aMinor = parseInt(aComponents[1], 10);
 | 
			
		||||
    const bMinor = parseInt(bComponents[1], 10);
 | 
			
		||||
    if (aMinor !== bMinor)
 | 
			
		||||
      return aMinor - bMinor;
 | 
			
		||||
    return aComponents.slice(2).join(".").localeCompare(bComponents.slice(2).join("."));
 | 
			
		||||
  });
 | 
			
		||||
  for (const version of sortedVersions) {
 | 
			
		||||
    console.log(`
 | 
			
		||||
Playwright version: ${version}`);
 | 
			
		||||
    printInstalledBrowsers(groupedByPlaywrightMinorVersion.get(version));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
import_utilsBundle.program.command("install [browser...]").description("ensure browsers necessary for this version of Playwright are installed").option("--with-deps", "install system dependencies for browsers").option("--dry-run", "do not execute installation, only print information").option("--list", "prints list of browsers from all playwright installations").option("--force", "force reinstall of stable browser channels").option("--only-shell", "only install headless shell when installing chromium").option("--no-shell", "do not install chromium headless shell").action(async function(args, options) {
 | 
			
		||||
  if (options.shell === false)
 | 
			
		||||
    options.noShell = true;
 | 
			
		||||
  if ((0, import_utils.isLikelyNpxGlobal)()) {
 | 
			
		||||
    console.error((0, import_ascii.wrapInASCIIBox)([
 | 
			
		||||
      `WARNING: It looks like you are running 'npx playwright install' without first`,
 | 
			
		||||
      `installing your project's dependencies.`,
 | 
			
		||||
      ``,
 | 
			
		||||
      `To avoid unexpected behavior, please install your dependencies first, and`,
 | 
			
		||||
      `then run Playwright's install command:`,
 | 
			
		||||
      ``,
 | 
			
		||||
      `    npm install`,
 | 
			
		||||
      `    npx playwright install`,
 | 
			
		||||
      ``,
 | 
			
		||||
      `If your project does not yet depend on Playwright, first install the`,
 | 
			
		||||
      `applicable npm package (most commonly @playwright/test), and`,
 | 
			
		||||
      `then run Playwright's install command to download the browsers:`,
 | 
			
		||||
      ``,
 | 
			
		||||
      `    npm install @playwright/test`,
 | 
			
		||||
      `    npx playwright install`,
 | 
			
		||||
      ``
 | 
			
		||||
    ].join("\n"), 1));
 | 
			
		||||
  }
 | 
			
		||||
  try {
 | 
			
		||||
    const hasNoArguments = !args.length;
 | 
			
		||||
    const executables = hasNoArguments ? defaultBrowsersToInstall(options) : checkBrowsersToInstall(args, options);
 | 
			
		||||
    if (options.withDeps)
 | 
			
		||||
      await import_server.registry.installDeps(executables, !!options.dryRun);
 | 
			
		||||
    if (options.dryRun && options.list)
 | 
			
		||||
      throw new Error(`Only one of --dry-run and --list can be specified`);
 | 
			
		||||
    if (options.dryRun) {
 | 
			
		||||
      for (const executable of executables) {
 | 
			
		||||
        const version = executable.browserVersion ? `version ` + executable.browserVersion : "";
 | 
			
		||||
        console.log(`browser: ${executable.name}${version ? " " + version : ""}`);
 | 
			
		||||
        console.log(`  Install location:    ${executable.directory ?? "<system>"}`);
 | 
			
		||||
        if (executable.downloadURLs?.length) {
 | 
			
		||||
          const [url, ...fallbacks] = executable.downloadURLs;
 | 
			
		||||
          console.log(`  Download url:        ${url}`);
 | 
			
		||||
          for (let i = 0; i < fallbacks.length; ++i)
 | 
			
		||||
            console.log(`  Download fallback ${i + 1}: ${fallbacks[i]}`);
 | 
			
		||||
        }
 | 
			
		||||
        console.log(``);
 | 
			
		||||
      }
 | 
			
		||||
    } else if (options.list) {
 | 
			
		||||
      const browsers2 = await import_server.registry.listInstalledBrowsers();
 | 
			
		||||
      printGroupedByPlaywrightVersion(browsers2);
 | 
			
		||||
    } else {
 | 
			
		||||
      const forceReinstall = hasNoArguments ? false : !!options.force;
 | 
			
		||||
      await import_server.registry.install(executables, forceReinstall);
 | 
			
		||||
      await import_server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || "javascript").catch((e) => {
 | 
			
		||||
        e.name = "Playwright Host validation warning";
 | 
			
		||||
        console.error(e);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.log(`Failed to install browsers
 | 
			
		||||
${e}`);
 | 
			
		||||
    (0, import_utils.gracefullyProcessExitDoNotHang)(1);
 | 
			
		||||
  }
 | 
			
		||||
}).addHelpText("afterAll", `
 | 
			
		||||
 | 
			
		||||
Examples:
 | 
			
		||||
  - $ install
 | 
			
		||||
    Install default browsers.
 | 
			
		||||
 | 
			
		||||
  - $ install chrome firefox
 | 
			
		||||
    Install custom browsers, supports ${suggestedBrowsersToInstall()}.`);
 | 
			
		||||
import_utilsBundle.program.command("uninstall").description("Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.").option("--all", "Removes all browsers used by any Playwright installation from the system.").action(async (options) => {
 | 
			
		||||
  delete process.env.PLAYWRIGHT_SKIP_BROWSER_GC;
 | 
			
		||||
  await import_server.registry.uninstall(!!options.all).then(({ numberOfBrowsersLeft }) => {
 | 
			
		||||
    if (!options.all && numberOfBrowsersLeft > 0) {
 | 
			
		||||
      console.log("Successfully uninstalled Playwright browsers for the current Playwright installation.");
 | 
			
		||||
      console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations.
 | 
			
		||||
To uninstall Playwright browsers for all installations, re-run with --all flag.`);
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(logErrorAndExit);
 | 
			
		||||
});
 | 
			
		||||
import_utilsBundle.program.command("install-deps [browser...]").description("install dependencies necessary to run browsers (will ask for sudo permissions)").option("--dry-run", "Do not execute installation commands, only print them").action(async function(args, options) {
 | 
			
		||||
  try {
 | 
			
		||||
    if (!args.length)
 | 
			
		||||
      await import_server.registry.installDeps(defaultBrowsersToInstall({}), !!options.dryRun);
 | 
			
		||||
    else
 | 
			
		||||
      await import_server.registry.installDeps(checkBrowsersToInstall(args, {}), !!options.dryRun);
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.log(`Failed to install browser dependencies
 | 
			
		||||
${e}`);
 | 
			
		||||
    (0, import_utils.gracefullyProcessExitDoNotHang)(1);
 | 
			
		||||
  }
 | 
			
		||||
}).addHelpText("afterAll", `
 | 
			
		||||
Examples:
 | 
			
		||||
  - $ install-deps
 | 
			
		||||
    Install dependencies for default browsers.
 | 
			
		||||
 | 
			
		||||
  - $ install-deps chrome firefox
 | 
			
		||||
    Install dependencies for specific browsers, supports ${suggestedBrowsersToInstall()}.`);
 | 
			
		||||
const browsers = [
 | 
			
		||||
  { alias: "cr", name: "Chromium", type: "chromium" },
 | 
			
		||||
  { alias: "ff", name: "Firefox", type: "firefox" },
 | 
			
		||||
  { alias: "wk", name: "WebKit", type: "webkit" }
 | 
			
		||||
];
 | 
			
		||||
for (const { alias, name, type } of browsers) {
 | 
			
		||||
  commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(function(url, options) {
 | 
			
		||||
    open({ ...options, browser: type }, url).catch(logErrorAndExit);
 | 
			
		||||
  }).addHelpText("afterAll", `
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
  $ ${alias} https://example.com`);
 | 
			
		||||
}
 | 
			
		||||
commandWithOpenOptions(
 | 
			
		||||
  "screenshot <url> <filename>",
 | 
			
		||||
  "capture a page screenshot",
 | 
			
		||||
  [
 | 
			
		||||
    ["--wait-for-selector <selector>", "wait for selector before taking a screenshot"],
 | 
			
		||||
    ["--wait-for-timeout <timeout>", "wait for timeout in milliseconds before taking a screenshot"],
 | 
			
		||||
    ["--full-page", "whether to take a full page screenshot (entire scrollable area)"]
 | 
			
		||||
  ]
 | 
			
		||||
).action(function(url, filename, command) {
 | 
			
		||||
  screenshot(command, command, url, filename).catch(logErrorAndExit);
 | 
			
		||||
}).addHelpText("afterAll", `
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
  $ screenshot -b webkit https://example.com example.png`);
 | 
			
		||||
commandWithOpenOptions(
 | 
			
		||||
  "pdf <url> <filename>",
 | 
			
		||||
  "save page as pdf",
 | 
			
		||||
  [
 | 
			
		||||
    ["--paper-format <format>", "paper format: Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6"],
 | 
			
		||||
    ["--wait-for-selector <selector>", "wait for given selector before saving as pdf"],
 | 
			
		||||
    ["--wait-for-timeout <timeout>", "wait for given timeout in milliseconds before saving as pdf"]
 | 
			
		||||
  ]
 | 
			
		||||
).action(function(url, filename, options) {
 | 
			
		||||
  pdf(options, options, url, filename).catch(logErrorAndExit);
 | 
			
		||||
}).addHelpText("afterAll", `
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
  $ pdf https://example.com example.pdf`);
 | 
			
		||||
import_utilsBundle.program.command("run-driver", { hidden: true }).action(function(options) {
 | 
			
		||||
  (0, import_driver.runDriver)();
 | 
			
		||||
});
 | 
			
		||||
import_utilsBundle.program.command("run-server").option("--port <port>", "Server port").option("--host <host>", "Server host").option("--path <path>", "Endpoint Path", "/").option("--max-clients <maxClients>", "Maximum clients").option("--mode <mode>", 'Server mode, either "default" or "extension"').action(function(options) {
 | 
			
		||||
  (0, import_driver.runServer)({
 | 
			
		||||
    port: options.port ? +options.port : void 0,
 | 
			
		||||
    host: options.host,
 | 
			
		||||
    path: options.path,
 | 
			
		||||
    maxConnections: options.maxClients ? +options.maxClients : Infinity,
 | 
			
		||||
    extension: options.mode === "extension" || !!process.env.PW_EXTENSION_MODE
 | 
			
		||||
  }).catch(logErrorAndExit);
 | 
			
		||||
});
 | 
			
		||||
import_utilsBundle.program.command("print-api-json", { hidden: true }).action(function(options) {
 | 
			
		||||
  (0, import_driver.printApiJson)();
 | 
			
		||||
});
 | 
			
		||||
import_utilsBundle.program.command("launch-server", { hidden: true }).requiredOption("--browser <browserName>", 'Browser name, one of "chromium", "firefox" or "webkit"').option("--config <path-to-config-file>", "JSON file with launchServer options").action(function(options) {
 | 
			
		||||
  (0, import_driver.launchBrowserServer)(options.browser, options.config);
 | 
			
		||||
});
 | 
			
		||||
import_utilsBundle.program.command("show-trace [trace...]").option("-b, --browser <browserType>", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("-h, --host <host>", "Host to serve trace on; specifying this option opens trace in a browser tab").option("-p, --port <port>", "Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab").option("--stdin", "Accept trace URLs over stdin to update the viewer").description("show trace viewer").action(function(traces, options) {
 | 
			
		||||
  if (options.browser === "cr")
 | 
			
		||||
    options.browser = "chromium";
 | 
			
		||||
  if (options.browser === "ff")
 | 
			
		||||
    options.browser = "firefox";
 | 
			
		||||
  if (options.browser === "wk")
 | 
			
		||||
    options.browser = "webkit";
 | 
			
		||||
  const openOptions = {
 | 
			
		||||
    host: options.host,
 | 
			
		||||
    port: +options.port,
 | 
			
		||||
    isServer: !!options.stdin
 | 
			
		||||
  };
 | 
			
		||||
  if (options.port !== void 0 || options.host !== void 0)
 | 
			
		||||
    (0, import_traceViewer.runTraceInBrowser)(traces, openOptions).catch(logErrorAndExit);
 | 
			
		||||
  else
 | 
			
		||||
    (0, import_traceViewer.runTraceViewerApp)(traces, options.browser, openOptions, true).catch(logErrorAndExit);
 | 
			
		||||
}).addHelpText("afterAll", `
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
  $ show-trace https://example.com/trace.zip`);
 | 
			
		||||
async function launchContext(options, extraOptions) {
 | 
			
		||||
  validateOptions(options);
 | 
			
		||||
  const browserType = lookupBrowserType(options);
 | 
			
		||||
  const launchOptions = extraOptions;
 | 
			
		||||
  if (options.channel)
 | 
			
		||||
    launchOptions.channel = options.channel;
 | 
			
		||||
  launchOptions.handleSIGINT = false;
 | 
			
		||||
  const contextOptions = (
 | 
			
		||||
    // Copy the device descriptor since we have to compare and modify the options.
 | 
			
		||||
    options.device ? { ...playwright.devices[options.device] } : {}
 | 
			
		||||
  );
 | 
			
		||||
  if (!extraOptions.headless)
 | 
			
		||||
    contextOptions.deviceScaleFactor = import_os.default.platform() === "darwin" ? 2 : 1;
 | 
			
		||||
  if (browserType.name() === "webkit" && process.platform === "linux") {
 | 
			
		||||
    delete contextOptions.hasTouch;
 | 
			
		||||
    delete contextOptions.isMobile;
 | 
			
		||||
  }
 | 
			
		||||
  if (contextOptions.isMobile && browserType.name() === "firefox")
 | 
			
		||||
    contextOptions.isMobile = void 0;
 | 
			
		||||
  if (options.blockServiceWorkers)
 | 
			
		||||
    contextOptions.serviceWorkers = "block";
 | 
			
		||||
  if (options.proxyServer) {
 | 
			
		||||
    launchOptions.proxy = {
 | 
			
		||||
      server: options.proxyServer
 | 
			
		||||
    };
 | 
			
		||||
    if (options.proxyBypass)
 | 
			
		||||
      launchOptions.proxy.bypass = options.proxyBypass;
 | 
			
		||||
  }
 | 
			
		||||
  if (options.viewportSize) {
 | 
			
		||||
    try {
 | 
			
		||||
      const [width, height] = options.viewportSize.split(",").map((n) => +n);
 | 
			
		||||
      if (isNaN(width) || isNaN(height))
 | 
			
		||||
        throw new Error("bad values");
 | 
			
		||||
      contextOptions.viewport = { width, height };
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new Error('Invalid viewport size format: use "width,height", for example --viewport-size="800,600"');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (options.geolocation) {
 | 
			
		||||
    try {
 | 
			
		||||
      const [latitude, longitude] = options.geolocation.split(",").map((n) => parseFloat(n.trim()));
 | 
			
		||||
      contextOptions.geolocation = {
 | 
			
		||||
        latitude,
 | 
			
		||||
        longitude
 | 
			
		||||
      };
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
 | 
			
		||||
    }
 | 
			
		||||
    contextOptions.permissions = ["geolocation"];
 | 
			
		||||
  }
 | 
			
		||||
  if (options.userAgent)
 | 
			
		||||
    contextOptions.userAgent = options.userAgent;
 | 
			
		||||
  if (options.lang)
 | 
			
		||||
    contextOptions.locale = options.lang;
 | 
			
		||||
  if (options.colorScheme)
 | 
			
		||||
    contextOptions.colorScheme = options.colorScheme;
 | 
			
		||||
  if (options.timezone)
 | 
			
		||||
    contextOptions.timezoneId = options.timezone;
 | 
			
		||||
  if (options.loadStorage)
 | 
			
		||||
    contextOptions.storageState = options.loadStorage;
 | 
			
		||||
  if (options.ignoreHttpsErrors)
 | 
			
		||||
    contextOptions.ignoreHTTPSErrors = true;
 | 
			
		||||
  if (options.saveHar) {
 | 
			
		||||
    contextOptions.recordHar = { path: import_path.default.resolve(process.cwd(), options.saveHar), mode: "minimal" };
 | 
			
		||||
    if (options.saveHarGlob)
 | 
			
		||||
      contextOptions.recordHar.urlFilter = options.saveHarGlob;
 | 
			
		||||
    contextOptions.serviceWorkers = "block";
 | 
			
		||||
  }
 | 
			
		||||
  let browser;
 | 
			
		||||
  let context;
 | 
			
		||||
  if (options.userDataDir) {
 | 
			
		||||
    context = await browserType.launchPersistentContext(options.userDataDir, { ...launchOptions, ...contextOptions });
 | 
			
		||||
    browser = context.browser();
 | 
			
		||||
  } else {
 | 
			
		||||
    browser = await browserType.launch(launchOptions);
 | 
			
		||||
    context = await browser.newContext(contextOptions);
 | 
			
		||||
  }
 | 
			
		||||
  let closingBrowser = false;
 | 
			
		||||
  async function closeBrowser() {
 | 
			
		||||
    if (closingBrowser)
 | 
			
		||||
      return;
 | 
			
		||||
    closingBrowser = true;
 | 
			
		||||
    if (options.saveStorage)
 | 
			
		||||
      await context.storageState({ path: options.saveStorage }).catch((e) => null);
 | 
			
		||||
    if (options.saveHar)
 | 
			
		||||
      await context.close();
 | 
			
		||||
    await browser.close();
 | 
			
		||||
  }
 | 
			
		||||
  context.on("page", (page) => {
 | 
			
		||||
    page.on("dialog", () => {
 | 
			
		||||
    });
 | 
			
		||||
    page.on("close", () => {
 | 
			
		||||
      const hasPage = browser.contexts().some((context2) => context2.pages().length > 0);
 | 
			
		||||
      if (hasPage)
 | 
			
		||||
        return;
 | 
			
		||||
      closeBrowser().catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  process.on("SIGINT", async () => {
 | 
			
		||||
    await closeBrowser();
 | 
			
		||||
    (0, import_utils.gracefullyProcessExitDoNotHang)(130);
 | 
			
		||||
  });
 | 
			
		||||
  const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
 | 
			
		||||
  context.setDefaultTimeout(timeout);
 | 
			
		||||
  context.setDefaultNavigationTimeout(timeout);
 | 
			
		||||
  delete launchOptions.headless;
 | 
			
		||||
  delete launchOptions.executablePath;
 | 
			
		||||
  delete launchOptions.handleSIGINT;
 | 
			
		||||
  delete contextOptions.deviceScaleFactor;
 | 
			
		||||
  return { browser, browserName: browserType.name(), context, contextOptions, launchOptions, closeBrowser };
 | 
			
		||||
}
 | 
			
		||||
async function openPage(context, url) {
 | 
			
		||||
  let page = context.pages()[0];
 | 
			
		||||
  if (!page)
 | 
			
		||||
    page = await context.newPage();
 | 
			
		||||
  if (url) {
 | 
			
		||||
    if (import_fs.default.existsSync(url))
 | 
			
		||||
      url = "file://" + import_path.default.resolve(url);
 | 
			
		||||
    else if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:") && !url.startsWith("data:"))
 | 
			
		||||
      url = "http://" + url;
 | 
			
		||||
    await page.goto(url);
 | 
			
		||||
  }
 | 
			
		||||
  return page;
 | 
			
		||||
}
 | 
			
		||||
async function open(options, url) {
 | 
			
		||||
  const { context } = await launchContext(options, { headless: !!process.env.PWTEST_CLI_HEADLESS, executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH });
 | 
			
		||||
  await openPage(context, url);
 | 
			
		||||
}
 | 
			
		||||
async function codegen(options, url) {
 | 
			
		||||
  const { target: language, output: outputFile, testIdAttribute: testIdAttributeName } = options;
 | 
			
		||||
  const tracesDir = import_path.default.join(import_os.default.tmpdir(), `playwright-recorder-trace-${Date.now()}`);
 | 
			
		||||
  const { context, browser, launchOptions, contextOptions, closeBrowser } = await launchContext(options, {
 | 
			
		||||
    headless: !!process.env.PWTEST_CLI_HEADLESS,
 | 
			
		||||
    executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH,
 | 
			
		||||
    tracesDir
 | 
			
		||||
  });
 | 
			
		||||
  const donePromise = new import_utils.ManualPromise();
 | 
			
		||||
  maybeSetupTestHooks(browser, closeBrowser, donePromise);
 | 
			
		||||
  import_utilsBundle.dotenv.config({ path: "playwright.env" });
 | 
			
		||||
  await context._enableRecorder({
 | 
			
		||||
    language,
 | 
			
		||||
    launchOptions,
 | 
			
		||||
    contextOptions,
 | 
			
		||||
    device: options.device,
 | 
			
		||||
    saveStorage: options.saveStorage,
 | 
			
		||||
    mode: "recording",
 | 
			
		||||
    testIdAttributeName,
 | 
			
		||||
    outputFile: outputFile ? import_path.default.resolve(outputFile) : void 0,
 | 
			
		||||
    handleSIGINT: false
 | 
			
		||||
  });
 | 
			
		||||
  await openPage(context, url);
 | 
			
		||||
  donePromise.resolve();
 | 
			
		||||
}
 | 
			
		||||
async function maybeSetupTestHooks(browser, closeBrowser, donePromise) {
 | 
			
		||||
  if (!process.env.PWTEST_CLI_IS_UNDER_TEST)
 | 
			
		||||
    return;
 | 
			
		||||
  const logs = [];
 | 
			
		||||
  require("playwright-core/lib/utilsBundle").debug.log = (...args) => {
 | 
			
		||||
    const line = require("util").format(...args) + "\n";
 | 
			
		||||
    logs.push(line);
 | 
			
		||||
    process.stderr.write(line);
 | 
			
		||||
  };
 | 
			
		||||
  browser.on("disconnected", () => {
 | 
			
		||||
    const hasCrashLine = logs.some((line) => line.includes("process did exit:") && !line.includes("process did exit: exitCode=0, signal=null"));
 | 
			
		||||
    if (hasCrashLine) {
 | 
			
		||||
      process.stderr.write("Detected browser crash.\n");
 | 
			
		||||
      (0, import_utils.gracefullyProcessExitDoNotHang)(1);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  const close = async () => {
 | 
			
		||||
    await donePromise;
 | 
			
		||||
    await closeBrowser();
 | 
			
		||||
  };
 | 
			
		||||
  if (process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT) {
 | 
			
		||||
    setTimeout(close, +process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  let stdin = "";
 | 
			
		||||
  process.stdin.on("data", (data) => {
 | 
			
		||||
    stdin += data.toString();
 | 
			
		||||
    if (stdin.startsWith("exit")) {
 | 
			
		||||
      process.stdin.destroy();
 | 
			
		||||
      close();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
async function waitForPage(page, captureOptions) {
 | 
			
		||||
  if (captureOptions.waitForSelector) {
 | 
			
		||||
    console.log(`Waiting for selector ${captureOptions.waitForSelector}...`);
 | 
			
		||||
    await page.waitForSelector(captureOptions.waitForSelector);
 | 
			
		||||
  }
 | 
			
		||||
  if (captureOptions.waitForTimeout) {
 | 
			
		||||
    console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`);
 | 
			
		||||
    await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
async function screenshot(options, captureOptions, url, path2) {
 | 
			
		||||
  const { context } = await launchContext(options, { headless: true });
 | 
			
		||||
  console.log("Navigating to " + url);
 | 
			
		||||
  const page = await openPage(context, url);
 | 
			
		||||
  await waitForPage(page, captureOptions);
 | 
			
		||||
  console.log("Capturing screenshot into " + path2);
 | 
			
		||||
  await page.screenshot({ path: path2, fullPage: !!captureOptions.fullPage });
 | 
			
		||||
  await page.close();
 | 
			
		||||
}
 | 
			
		||||
async function pdf(options, captureOptions, url, path2) {
 | 
			
		||||
  if (options.browser !== "chromium")
 | 
			
		||||
    throw new Error("PDF creation is only working with Chromium");
 | 
			
		||||
  const { context } = await launchContext({ ...options, browser: "chromium" }, { headless: true });
 | 
			
		||||
  console.log("Navigating to " + url);
 | 
			
		||||
  const page = await openPage(context, url);
 | 
			
		||||
  await waitForPage(page, captureOptions);
 | 
			
		||||
  console.log("Saving as pdf into " + path2);
 | 
			
		||||
  await page.pdf({ path: path2, format: captureOptions.paperFormat });
 | 
			
		||||
  await page.close();
 | 
			
		||||
}
 | 
			
		||||
function lookupBrowserType(options) {
 | 
			
		||||
  let name = options.browser;
 | 
			
		||||
  if (options.device) {
 | 
			
		||||
    const device = playwright.devices[options.device];
 | 
			
		||||
    name = device.defaultBrowserType;
 | 
			
		||||
  }
 | 
			
		||||
  let browserType;
 | 
			
		||||
  switch (name) {
 | 
			
		||||
    case "chromium":
 | 
			
		||||
      browserType = playwright.chromium;
 | 
			
		||||
      break;
 | 
			
		||||
    case "webkit":
 | 
			
		||||
      browserType = playwright.webkit;
 | 
			
		||||
      break;
 | 
			
		||||
    case "firefox":
 | 
			
		||||
      browserType = playwright.firefox;
 | 
			
		||||
      break;
 | 
			
		||||
    case "cr":
 | 
			
		||||
      browserType = playwright.chromium;
 | 
			
		||||
      break;
 | 
			
		||||
    case "wk":
 | 
			
		||||
      browserType = playwright.webkit;
 | 
			
		||||
      break;
 | 
			
		||||
    case "ff":
 | 
			
		||||
      browserType = playwright.firefox;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  if (browserType)
 | 
			
		||||
    return browserType;
 | 
			
		||||
  import_utilsBundle.program.help();
 | 
			
		||||
}
 | 
			
		||||
function validateOptions(options) {
 | 
			
		||||
  if (options.device && !(options.device in playwright.devices)) {
 | 
			
		||||
    const lines = [`Device descriptor not found: '${options.device}', available devices are:`];
 | 
			
		||||
    for (const name in playwright.devices)
 | 
			
		||||
      lines.push(`  "${name}"`);
 | 
			
		||||
    throw new Error(lines.join("\n"));
 | 
			
		||||
  }
 | 
			
		||||
  if (options.colorScheme && !["light", "dark"].includes(options.colorScheme))
 | 
			
		||||
    throw new Error('Invalid color scheme, should be one of "light", "dark"');
 | 
			
		||||
}
 | 
			
		||||
function logErrorAndExit(e) {
 | 
			
		||||
  if (process.env.PWDEBUGIMPL)
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  else
 | 
			
		||||
    console.error(e.name + ": " + e.message);
 | 
			
		||||
  (0, import_utils.gracefullyProcessExitDoNotHang)(1);
 | 
			
		||||
}
 | 
			
		||||
function codegenId() {
 | 
			
		||||
  return process.env.PW_LANG_NAME || "playwright-test";
 | 
			
		||||
}
 | 
			
		||||
function commandWithOpenOptions(command, description, options) {
 | 
			
		||||
  let result = import_utilsBundle.program.command(command).description(description);
 | 
			
		||||
  for (const option of options)
 | 
			
		||||
    result = result.option(option[0], ...option.slice(1));
 | 
			
		||||
  return result.option("-b, --browser <browserType>", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("--block-service-workers", "block service workers").option("--channel <channel>", 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc').option("--color-scheme <scheme>", 'emulate preferred color scheme, "light" or "dark"').option("--device <deviceName>", 'emulate device, for example  "iPhone 11"').option("--geolocation <coordinates>", 'specify geolocation coordinates, for example "37.819722,-122.478611"').option("--ignore-https-errors", "ignore https errors").option("--load-storage <filename>", "load context storage state from the file, previously saved with --save-storage").option("--lang <language>", 'specify language / locale, for example "en-GB"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--save-har <filename>", "save HAR file with all network activity at the end").option("--save-har-glob <glob pattern>", "filter entries in the HAR by matching url against this glob pattern").option("--save-storage <filename>", "save context storage state at the end, for later use with --load-storage").option("--timezone <time zone>", 'time zone to emulate, for example "Europe/Rome"').option("--timeout <timeout>", "timeout for Playwright actions in milliseconds, no timeout by default").option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <directory>", "use the specified user data directory instead of a new context").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"');
 | 
			
		||||
}
 | 
			
		||||
function buildBasePlaywrightCLICommand(cliTargetLang) {
 | 
			
		||||
  switch (cliTargetLang) {
 | 
			
		||||
    case "python":
 | 
			
		||||
      return `playwright`;
 | 
			
		||||
    case "java":
 | 
			
		||||
      return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.."`;
 | 
			
		||||
    case "csharp":
 | 
			
		||||
      return `pwsh bin/Debug/netX/playwright.ps1`;
 | 
			
		||||
    default: {
 | 
			
		||||
      const packageManagerCommand = (0, import_utils2.getPackageManagerExecCommand)();
 | 
			
		||||
      return `${packageManagerCommand} playwright`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  program
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										74
									
								
								node_modules/playwright-core/lib/cli/programWithTestStub.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								node_modules/playwright-core/lib/cli/programWithTestStub.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var programWithTestStub_exports = {};
 | 
			
		||||
__export(programWithTestStub_exports, {
 | 
			
		||||
  program: () => import_program2.program
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(programWithTestStub_exports);
 | 
			
		||||
var import_processLauncher = require("../server/utils/processLauncher");
 | 
			
		||||
var import_utils = require("../utils");
 | 
			
		||||
var import_program = require("./program");
 | 
			
		||||
var import_program2 = require("./program");
 | 
			
		||||
function printPlaywrightTestError(command) {
 | 
			
		||||
  const packages = [];
 | 
			
		||||
  for (const pkg of ["playwright", "playwright-chromium", "playwright-firefox", "playwright-webkit"]) {
 | 
			
		||||
    try {
 | 
			
		||||
      require.resolve(pkg);
 | 
			
		||||
      packages.push(pkg);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!packages.length)
 | 
			
		||||
    packages.push("playwright");
 | 
			
		||||
  const packageManager = (0, import_utils.getPackageManager)();
 | 
			
		||||
  if (packageManager === "yarn") {
 | 
			
		||||
    console.error(`Please install @playwright/test package before running "yarn playwright ${command}"`);
 | 
			
		||||
    console.error(`  yarn remove ${packages.join(" ")}`);
 | 
			
		||||
    console.error("  yarn add -D @playwright/test");
 | 
			
		||||
  } else if (packageManager === "pnpm") {
 | 
			
		||||
    console.error(`Please install @playwright/test package before running "pnpm exec playwright ${command}"`);
 | 
			
		||||
    console.error(`  pnpm remove ${packages.join(" ")}`);
 | 
			
		||||
    console.error("  pnpm add -D @playwright/test");
 | 
			
		||||
  } else {
 | 
			
		||||
    console.error(`Please install @playwright/test package before running "npx playwright ${command}"`);
 | 
			
		||||
    console.error(`  npm uninstall ${packages.join(" ")}`);
 | 
			
		||||
    console.error("  npm install -D @playwright/test");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
const kExternalPlaywrightTestCommands = [
 | 
			
		||||
  ["test", "Run tests with Playwright Test."],
 | 
			
		||||
  ["show-report", "Show Playwright Test HTML report."],
 | 
			
		||||
  ["merge-reports", "Merge Playwright Test Blob reports"]
 | 
			
		||||
];
 | 
			
		||||
function addExternalPlaywrightTestCommands() {
 | 
			
		||||
  for (const [command, description] of kExternalPlaywrightTestCommands) {
 | 
			
		||||
    const playwrightTest = import_program.program.command(command).allowUnknownOption(true).allowExcessArguments(true);
 | 
			
		||||
    playwrightTest.description(`${description} Available in @playwright/test package.`);
 | 
			
		||||
    playwrightTest.action(async () => {
 | 
			
		||||
      printPlaywrightTestError(command);
 | 
			
		||||
      (0, import_processLauncher.gracefullyProcessExitDoNotHang)(1);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
if (!process.env.PW_LANG_NAME)
 | 
			
		||||
  addExternalPlaywrightTestCommands();
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  program
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										49
									
								
								node_modules/playwright-core/lib/client/accessibility.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								node_modules/playwright-core/lib/client/accessibility.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var accessibility_exports = {};
 | 
			
		||||
__export(accessibility_exports, {
 | 
			
		||||
  Accessibility: () => Accessibility
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(accessibility_exports);
 | 
			
		||||
function axNodeFromProtocol(axNode) {
 | 
			
		||||
  const result = {
 | 
			
		||||
    ...axNode,
 | 
			
		||||
    value: axNode.valueNumber !== void 0 ? axNode.valueNumber : axNode.valueString,
 | 
			
		||||
    checked: axNode.checked === "checked" ? true : axNode.checked === "unchecked" ? false : axNode.checked,
 | 
			
		||||
    pressed: axNode.pressed === "pressed" ? true : axNode.pressed === "released" ? false : axNode.pressed,
 | 
			
		||||
    children: axNode.children ? axNode.children.map(axNodeFromProtocol) : void 0
 | 
			
		||||
  };
 | 
			
		||||
  delete result.valueNumber;
 | 
			
		||||
  delete result.valueString;
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
class Accessibility {
 | 
			
		||||
  constructor(channel) {
 | 
			
		||||
    this._channel = channel;
 | 
			
		||||
  }
 | 
			
		||||
  async snapshot(options = {}) {
 | 
			
		||||
    const root = options.root ? options.root._elementChannel : void 0;
 | 
			
		||||
    const result = await this._channel.accessibilitySnapshot({ interestingOnly: options.interestingOnly, root });
 | 
			
		||||
    return result.rootAXNode ? axNodeFromProtocol(result.rootAXNode) : null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Accessibility
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										361
									
								
								node_modules/playwright-core/lib/client/android.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								node_modules/playwright-core/lib/client/android.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,361 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var android_exports = {};
 | 
			
		||||
__export(android_exports, {
 | 
			
		||||
  Android: () => Android,
 | 
			
		||||
  AndroidDevice: () => AndroidDevice,
 | 
			
		||||
  AndroidInput: () => AndroidInput,
 | 
			
		||||
  AndroidSocket: () => AndroidSocket,
 | 
			
		||||
  AndroidWebView: () => AndroidWebView
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(android_exports);
 | 
			
		||||
var import_eventEmitter = require("./eventEmitter");
 | 
			
		||||
var import_browserContext = require("./browserContext");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_waiter = require("./waiter");
 | 
			
		||||
var import_timeoutSettings = require("./timeoutSettings");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
var import_time = require("../utils/isomorphic/time");
 | 
			
		||||
var import_timeoutRunner = require("../utils/isomorphic/timeoutRunner");
 | 
			
		||||
var import_webSocket = require("./webSocket");
 | 
			
		||||
class Android extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(android) {
 | 
			
		||||
    return android._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
 | 
			
		||||
  }
 | 
			
		||||
  setDefaultTimeout(timeout) {
 | 
			
		||||
    this._timeoutSettings.setDefaultTimeout(timeout);
 | 
			
		||||
  }
 | 
			
		||||
  async devices(options = {}) {
 | 
			
		||||
    const { devices } = await this._channel.devices(options);
 | 
			
		||||
    return devices.map((d) => AndroidDevice.from(d));
 | 
			
		||||
  }
 | 
			
		||||
  async launchServer(options = {}) {
 | 
			
		||||
    if (!this._serverLauncher)
 | 
			
		||||
      throw new Error("Launching server is not supported");
 | 
			
		||||
    return await this._serverLauncher.launchServer(options);
 | 
			
		||||
  }
 | 
			
		||||
  async connect(wsEndpoint, options = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const deadline = options.timeout ? (0, import_time.monotonicTime)() + options.timeout : 0;
 | 
			
		||||
      const headers = { "x-playwright-browser": "android", ...options.headers };
 | 
			
		||||
      const connectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout || 0 };
 | 
			
		||||
      const connection = await (0, import_webSocket.connectOverWebSocket)(this._connection, connectParams);
 | 
			
		||||
      let device;
 | 
			
		||||
      connection.on("close", () => {
 | 
			
		||||
        device?._didClose();
 | 
			
		||||
      });
 | 
			
		||||
      const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
 | 
			
		||||
        const playwright = await connection.initializePlaywright();
 | 
			
		||||
        if (!playwright._initializer.preConnectedAndroidDevice) {
 | 
			
		||||
          connection.close();
 | 
			
		||||
          throw new Error("Malformed endpoint. Did you use Android.launchServer method?");
 | 
			
		||||
        }
 | 
			
		||||
        device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
 | 
			
		||||
        device._shouldCloseConnectionOnClose = true;
 | 
			
		||||
        device.on(import_events.Events.AndroidDevice.Close, () => connection.close());
 | 
			
		||||
        return device;
 | 
			
		||||
      }, deadline);
 | 
			
		||||
      if (!result.timedOut) {
 | 
			
		||||
        return result.result;
 | 
			
		||||
      } else {
 | 
			
		||||
        connection.close();
 | 
			
		||||
        throw new Error(`Timeout ${options.timeout}ms exceeded`);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class AndroidDevice extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._webViews = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._shouldCloseConnectionOnClose = false;
 | 
			
		||||
    this._android = parent;
 | 
			
		||||
    this.input = new AndroidInput(this);
 | 
			
		||||
    this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform, parent._timeoutSettings);
 | 
			
		||||
    this._channel.on("webViewAdded", ({ webView }) => this._onWebViewAdded(webView));
 | 
			
		||||
    this._channel.on("webViewRemoved", ({ socketName }) => this._onWebViewRemoved(socketName));
 | 
			
		||||
    this._channel.on("close", () => this._didClose());
 | 
			
		||||
  }
 | 
			
		||||
  static from(androidDevice) {
 | 
			
		||||
    return androidDevice._object;
 | 
			
		||||
  }
 | 
			
		||||
  _onWebViewAdded(webView) {
 | 
			
		||||
    const view = new AndroidWebView(this, webView);
 | 
			
		||||
    this._webViews.set(webView.socketName, view);
 | 
			
		||||
    this.emit(import_events.Events.AndroidDevice.WebView, view);
 | 
			
		||||
  }
 | 
			
		||||
  _onWebViewRemoved(socketName) {
 | 
			
		||||
    const view = this._webViews.get(socketName);
 | 
			
		||||
    this._webViews.delete(socketName);
 | 
			
		||||
    if (view)
 | 
			
		||||
      view.emit(import_events.Events.AndroidWebView.Close);
 | 
			
		||||
  }
 | 
			
		||||
  setDefaultTimeout(timeout) {
 | 
			
		||||
    this._timeoutSettings.setDefaultTimeout(timeout);
 | 
			
		||||
  }
 | 
			
		||||
  serial() {
 | 
			
		||||
    return this._initializer.serial;
 | 
			
		||||
  }
 | 
			
		||||
  model() {
 | 
			
		||||
    return this._initializer.model;
 | 
			
		||||
  }
 | 
			
		||||
  webViews() {
 | 
			
		||||
    return [...this._webViews.values()];
 | 
			
		||||
  }
 | 
			
		||||
  async webView(selector, options) {
 | 
			
		||||
    const predicate = (v) => {
 | 
			
		||||
      if (selector.pkg)
 | 
			
		||||
        return v.pkg() === selector.pkg;
 | 
			
		||||
      if (selector.socketName)
 | 
			
		||||
        return v._socketName() === selector.socketName;
 | 
			
		||||
      return false;
 | 
			
		||||
    };
 | 
			
		||||
    const webView = [...this._webViews.values()].find(predicate);
 | 
			
		||||
    if (webView)
 | 
			
		||||
      return webView;
 | 
			
		||||
    return await this.waitForEvent("webview", { ...options, predicate });
 | 
			
		||||
  }
 | 
			
		||||
  async wait(selector, options = {}) {
 | 
			
		||||
    await this._channel.wait({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async fill(selector, text, options = {}) {
 | 
			
		||||
    await this._channel.fill({ androidSelector: toSelectorChannel(selector), text, ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async press(selector, key, options = {}) {
 | 
			
		||||
    await this.tap(selector, options);
 | 
			
		||||
    await this.input.press(key);
 | 
			
		||||
  }
 | 
			
		||||
  async tap(selector, options = {}) {
 | 
			
		||||
    await this._channel.tap({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async drag(selector, dest, options = {}) {
 | 
			
		||||
    await this._channel.drag({ androidSelector: toSelectorChannel(selector), dest, ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async fling(selector, direction, options = {}) {
 | 
			
		||||
    await this._channel.fling({ androidSelector: toSelectorChannel(selector), direction, ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async longTap(selector, options = {}) {
 | 
			
		||||
    await this._channel.longTap({ androidSelector: toSelectorChannel(selector), ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async pinchClose(selector, percent, options = {}) {
 | 
			
		||||
    await this._channel.pinchClose({ androidSelector: toSelectorChannel(selector), percent, ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async pinchOpen(selector, percent, options = {}) {
 | 
			
		||||
    await this._channel.pinchOpen({ androidSelector: toSelectorChannel(selector), percent, ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async scroll(selector, direction, percent, options = {}) {
 | 
			
		||||
    await this._channel.scroll({ androidSelector: toSelectorChannel(selector), direction, percent, ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async swipe(selector, direction, percent, options = {}) {
 | 
			
		||||
    await this._channel.swipe({ androidSelector: toSelectorChannel(selector), direction, percent, ...options, timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async info(selector) {
 | 
			
		||||
    return (await this._channel.info({ androidSelector: toSelectorChannel(selector) })).info;
 | 
			
		||||
  }
 | 
			
		||||
  async screenshot(options = {}) {
 | 
			
		||||
    const { binary } = await this._channel.screenshot();
 | 
			
		||||
    if (options.path)
 | 
			
		||||
      await this._platform.fs().promises.writeFile(options.path, binary);
 | 
			
		||||
    return binary;
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.close();
 | 
			
		||||
  }
 | 
			
		||||
  async close() {
 | 
			
		||||
    try {
 | 
			
		||||
      if (this._shouldCloseConnectionOnClose)
 | 
			
		||||
        this._connection.close();
 | 
			
		||||
      else
 | 
			
		||||
        await this._channel.close();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if ((0, import_errors.isTargetClosedError)(e))
 | 
			
		||||
        return;
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  _didClose() {
 | 
			
		||||
    this.emit(import_events.Events.AndroidDevice.Close, this);
 | 
			
		||||
  }
 | 
			
		||||
  async shell(command) {
 | 
			
		||||
    const { result } = await this._channel.shell({ command });
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
  async open(command) {
 | 
			
		||||
    return AndroidSocket.from((await this._channel.open({ command })).socket);
 | 
			
		||||
  }
 | 
			
		||||
  async installApk(file, options) {
 | 
			
		||||
    await this._channel.installApk({ file: await loadFile(this._platform, file), args: options && options.args });
 | 
			
		||||
  }
 | 
			
		||||
  async push(file, path, options) {
 | 
			
		||||
    await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : void 0 });
 | 
			
		||||
  }
 | 
			
		||||
  async launchBrowser(options = {}) {
 | 
			
		||||
    const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
 | 
			
		||||
    const result = await this._channel.launchBrowser(contextOptions);
 | 
			
		||||
    const context = import_browserContext.BrowserContext.from(result.context);
 | 
			
		||||
    const selectors = this._android._playwright.selectors;
 | 
			
		||||
    selectors._contextsForSelectors.add(context);
 | 
			
		||||
    context.once(import_events.Events.BrowserContext.Close, () => selectors._contextsForSelectors.delete(context));
 | 
			
		||||
    await context._initializeHarFromOptions(options.recordHar);
 | 
			
		||||
    return context;
 | 
			
		||||
  }
 | 
			
		||||
  async waitForEvent(event, optionsOrPredicate = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
 | 
			
		||||
      const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
 | 
			
		||||
      const waiter = import_waiter.Waiter.createForEvent(this, event);
 | 
			
		||||
      waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
 | 
			
		||||
      if (event !== import_events.Events.AndroidDevice.Close)
 | 
			
		||||
        waiter.rejectOnEvent(this, import_events.Events.AndroidDevice.Close, () => new import_errors.TargetClosedError());
 | 
			
		||||
      const result = await waiter.waitForEvent(this, event, predicate);
 | 
			
		||||
      waiter.dispose();
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class AndroidSocket extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(androidDevice) {
 | 
			
		||||
    return androidDevice._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._channel.on("data", ({ data }) => this.emit(import_events.Events.AndroidSocket.Data, data));
 | 
			
		||||
    this._channel.on("close", () => this.emit(import_events.Events.AndroidSocket.Close));
 | 
			
		||||
  }
 | 
			
		||||
  async write(data) {
 | 
			
		||||
    await this._channel.write({ data });
 | 
			
		||||
  }
 | 
			
		||||
  async close() {
 | 
			
		||||
    await this._channel.close();
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.close();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
async function loadFile(platform, file) {
 | 
			
		||||
  if ((0, import_rtti.isString)(file))
 | 
			
		||||
    return await platform.fs().promises.readFile(file);
 | 
			
		||||
  return file;
 | 
			
		||||
}
 | 
			
		||||
class AndroidInput {
 | 
			
		||||
  constructor(device) {
 | 
			
		||||
    this._device = device;
 | 
			
		||||
  }
 | 
			
		||||
  async type(text) {
 | 
			
		||||
    await this._device._channel.inputType({ text });
 | 
			
		||||
  }
 | 
			
		||||
  async press(key) {
 | 
			
		||||
    await this._device._channel.inputPress({ key });
 | 
			
		||||
  }
 | 
			
		||||
  async tap(point) {
 | 
			
		||||
    await this._device._channel.inputTap({ point });
 | 
			
		||||
  }
 | 
			
		||||
  async swipe(from, segments, steps) {
 | 
			
		||||
    await this._device._channel.inputSwipe({ segments, steps });
 | 
			
		||||
  }
 | 
			
		||||
  async drag(from, to, steps) {
 | 
			
		||||
    await this._device._channel.inputDrag({ from, to, steps });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function toSelectorChannel(selector) {
 | 
			
		||||
  const {
 | 
			
		||||
    checkable,
 | 
			
		||||
    checked,
 | 
			
		||||
    clazz,
 | 
			
		||||
    clickable,
 | 
			
		||||
    depth,
 | 
			
		||||
    desc,
 | 
			
		||||
    enabled,
 | 
			
		||||
    focusable,
 | 
			
		||||
    focused,
 | 
			
		||||
    hasChild,
 | 
			
		||||
    hasDescendant,
 | 
			
		||||
    longClickable,
 | 
			
		||||
    pkg,
 | 
			
		||||
    res,
 | 
			
		||||
    scrollable,
 | 
			
		||||
    selected,
 | 
			
		||||
    text
 | 
			
		||||
  } = selector;
 | 
			
		||||
  const toRegex = (value) => {
 | 
			
		||||
    if (value === void 0)
 | 
			
		||||
      return void 0;
 | 
			
		||||
    if ((0, import_rtti.isRegExp)(value))
 | 
			
		||||
      return value.source;
 | 
			
		||||
    return "^" + value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d") + "$";
 | 
			
		||||
  };
 | 
			
		||||
  return {
 | 
			
		||||
    checkable,
 | 
			
		||||
    checked,
 | 
			
		||||
    clazz: toRegex(clazz),
 | 
			
		||||
    pkg: toRegex(pkg),
 | 
			
		||||
    desc: toRegex(desc),
 | 
			
		||||
    res: toRegex(res),
 | 
			
		||||
    text: toRegex(text),
 | 
			
		||||
    clickable,
 | 
			
		||||
    depth,
 | 
			
		||||
    enabled,
 | 
			
		||||
    focusable,
 | 
			
		||||
    focused,
 | 
			
		||||
    hasChild: hasChild ? { androidSelector: toSelectorChannel(hasChild.selector) } : void 0,
 | 
			
		||||
    hasDescendant: hasDescendant ? { androidSelector: toSelectorChannel(hasDescendant.selector), maxDepth: hasDescendant.maxDepth } : void 0,
 | 
			
		||||
    longClickable,
 | 
			
		||||
    scrollable,
 | 
			
		||||
    selected
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
class AndroidWebView extends import_eventEmitter.EventEmitter {
 | 
			
		||||
  constructor(device, data) {
 | 
			
		||||
    super(device._platform);
 | 
			
		||||
    this._device = device;
 | 
			
		||||
    this._data = data;
 | 
			
		||||
  }
 | 
			
		||||
  pid() {
 | 
			
		||||
    return this._data.pid;
 | 
			
		||||
  }
 | 
			
		||||
  pkg() {
 | 
			
		||||
    return this._data.pkg;
 | 
			
		||||
  }
 | 
			
		||||
  _socketName() {
 | 
			
		||||
    return this._data.socketName;
 | 
			
		||||
  }
 | 
			
		||||
  async page() {
 | 
			
		||||
    if (!this._pagePromise)
 | 
			
		||||
      this._pagePromise = this._fetchPage();
 | 
			
		||||
    return await this._pagePromise;
 | 
			
		||||
  }
 | 
			
		||||
  async _fetchPage() {
 | 
			
		||||
    const { context } = await this._device._channel.connectToWebView({ socketName: this._data.socketName });
 | 
			
		||||
    return import_browserContext.BrowserContext.from(context).pages()[0];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Android,
 | 
			
		||||
  AndroidDevice,
 | 
			
		||||
  AndroidInput,
 | 
			
		||||
  AndroidSocket,
 | 
			
		||||
  AndroidWebView
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										137
									
								
								node_modules/playwright-core/lib/client/api.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								node_modules/playwright-core/lib/client/api.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var api_exports = {};
 | 
			
		||||
__export(api_exports, {
 | 
			
		||||
  APIRequest: () => import_fetch.APIRequest,
 | 
			
		||||
  APIRequestContext: () => import_fetch.APIRequestContext,
 | 
			
		||||
  APIResponse: () => import_fetch.APIResponse,
 | 
			
		||||
  Accessibility: () => import_accessibility.Accessibility,
 | 
			
		||||
  Android: () => import_android.Android,
 | 
			
		||||
  AndroidDevice: () => import_android.AndroidDevice,
 | 
			
		||||
  AndroidInput: () => import_android.AndroidInput,
 | 
			
		||||
  AndroidSocket: () => import_android.AndroidSocket,
 | 
			
		||||
  AndroidWebView: () => import_android.AndroidWebView,
 | 
			
		||||
  Browser: () => import_browser.Browser,
 | 
			
		||||
  BrowserContext: () => import_browserContext.BrowserContext,
 | 
			
		||||
  BrowserType: () => import_browserType.BrowserType,
 | 
			
		||||
  CDPSession: () => import_cdpSession.CDPSession,
 | 
			
		||||
  Clock: () => import_clock.Clock,
 | 
			
		||||
  ConsoleMessage: () => import_consoleMessage.ConsoleMessage,
 | 
			
		||||
  Coverage: () => import_coverage.Coverage,
 | 
			
		||||
  Dialog: () => import_dialog.Dialog,
 | 
			
		||||
  Download: () => import_download.Download,
 | 
			
		||||
  Electron: () => import_electron.Electron,
 | 
			
		||||
  ElectronApplication: () => import_electron.ElectronApplication,
 | 
			
		||||
  ElementHandle: () => import_elementHandle.ElementHandle,
 | 
			
		||||
  FileChooser: () => import_fileChooser.FileChooser,
 | 
			
		||||
  Frame: () => import_frame.Frame,
 | 
			
		||||
  FrameLocator: () => import_locator.FrameLocator,
 | 
			
		||||
  JSHandle: () => import_jsHandle.JSHandle,
 | 
			
		||||
  Keyboard: () => import_input.Keyboard,
 | 
			
		||||
  Locator: () => import_locator.Locator,
 | 
			
		||||
  Mouse: () => import_input.Mouse,
 | 
			
		||||
  Page: () => import_page.Page,
 | 
			
		||||
  Playwright: () => import_playwright.Playwright,
 | 
			
		||||
  Request: () => import_network.Request,
 | 
			
		||||
  Response: () => import_network.Response,
 | 
			
		||||
  Route: () => import_network.Route,
 | 
			
		||||
  Selectors: () => import_selectors.Selectors,
 | 
			
		||||
  TimeoutError: () => import_errors.TimeoutError,
 | 
			
		||||
  Touchscreen: () => import_input.Touchscreen,
 | 
			
		||||
  Tracing: () => import_tracing.Tracing,
 | 
			
		||||
  Video: () => import_video.Video,
 | 
			
		||||
  WebError: () => import_webError.WebError,
 | 
			
		||||
  WebSocket: () => import_network.WebSocket,
 | 
			
		||||
  WebSocketRoute: () => import_network.WebSocketRoute,
 | 
			
		||||
  Worker: () => import_worker.Worker
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(api_exports);
 | 
			
		||||
var import_accessibility = require("./accessibility");
 | 
			
		||||
var import_android = require("./android");
 | 
			
		||||
var import_browser = require("./browser");
 | 
			
		||||
var import_browserContext = require("./browserContext");
 | 
			
		||||
var import_browserType = require("./browserType");
 | 
			
		||||
var import_clock = require("./clock");
 | 
			
		||||
var import_consoleMessage = require("./consoleMessage");
 | 
			
		||||
var import_coverage = require("./coverage");
 | 
			
		||||
var import_dialog = require("./dialog");
 | 
			
		||||
var import_download = require("./download");
 | 
			
		||||
var import_electron = require("./electron");
 | 
			
		||||
var import_locator = require("./locator");
 | 
			
		||||
var import_elementHandle = require("./elementHandle");
 | 
			
		||||
var import_fileChooser = require("./fileChooser");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_frame = require("./frame");
 | 
			
		||||
var import_input = require("./input");
 | 
			
		||||
var import_jsHandle = require("./jsHandle");
 | 
			
		||||
var import_network = require("./network");
 | 
			
		||||
var import_fetch = require("./fetch");
 | 
			
		||||
var import_page = require("./page");
 | 
			
		||||
var import_selectors = require("./selectors");
 | 
			
		||||
var import_tracing = require("./tracing");
 | 
			
		||||
var import_video = require("./video");
 | 
			
		||||
var import_worker = require("./worker");
 | 
			
		||||
var import_cdpSession = require("./cdpSession");
 | 
			
		||||
var import_playwright = require("./playwright");
 | 
			
		||||
var import_webError = require("./webError");
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  APIRequest,
 | 
			
		||||
  APIRequestContext,
 | 
			
		||||
  APIResponse,
 | 
			
		||||
  Accessibility,
 | 
			
		||||
  Android,
 | 
			
		||||
  AndroidDevice,
 | 
			
		||||
  AndroidInput,
 | 
			
		||||
  AndroidSocket,
 | 
			
		||||
  AndroidWebView,
 | 
			
		||||
  Browser,
 | 
			
		||||
  BrowserContext,
 | 
			
		||||
  BrowserType,
 | 
			
		||||
  CDPSession,
 | 
			
		||||
  Clock,
 | 
			
		||||
  ConsoleMessage,
 | 
			
		||||
  Coverage,
 | 
			
		||||
  Dialog,
 | 
			
		||||
  Download,
 | 
			
		||||
  Electron,
 | 
			
		||||
  ElectronApplication,
 | 
			
		||||
  ElementHandle,
 | 
			
		||||
  FileChooser,
 | 
			
		||||
  Frame,
 | 
			
		||||
  FrameLocator,
 | 
			
		||||
  JSHandle,
 | 
			
		||||
  Keyboard,
 | 
			
		||||
  Locator,
 | 
			
		||||
  Mouse,
 | 
			
		||||
  Page,
 | 
			
		||||
  Playwright,
 | 
			
		||||
  Request,
 | 
			
		||||
  Response,
 | 
			
		||||
  Route,
 | 
			
		||||
  Selectors,
 | 
			
		||||
  TimeoutError,
 | 
			
		||||
  Touchscreen,
 | 
			
		||||
  Tracing,
 | 
			
		||||
  Video,
 | 
			
		||||
  WebError,
 | 
			
		||||
  WebSocket,
 | 
			
		||||
  WebSocketRoute,
 | 
			
		||||
  Worker
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										79
									
								
								node_modules/playwright-core/lib/client/artifact.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								node_modules/playwright-core/lib/client/artifact.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var artifact_exports = {};
 | 
			
		||||
__export(artifact_exports, {
 | 
			
		||||
  Artifact: () => Artifact
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(artifact_exports);
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_stream = require("./stream");
 | 
			
		||||
var import_fileUtils = require("./fileUtils");
 | 
			
		||||
class Artifact extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(channel) {
 | 
			
		||||
    return channel._object;
 | 
			
		||||
  }
 | 
			
		||||
  async pathAfterFinished() {
 | 
			
		||||
    if (this._connection.isRemote())
 | 
			
		||||
      throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
 | 
			
		||||
    return (await this._channel.pathAfterFinished()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async saveAs(path) {
 | 
			
		||||
    if (!this._connection.isRemote()) {
 | 
			
		||||
      await this._channel.saveAs({ path });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const result = await this._channel.saveAsStream();
 | 
			
		||||
    const stream = import_stream.Stream.from(result.stream);
 | 
			
		||||
    await (0, import_fileUtils.mkdirIfNeeded)(this._platform, path);
 | 
			
		||||
    await new Promise((resolve, reject) => {
 | 
			
		||||
      stream.stream().pipe(this._platform.fs().createWriteStream(path)).on("finish", resolve).on("error", reject);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async failure() {
 | 
			
		||||
    return (await this._channel.failure()).error || null;
 | 
			
		||||
  }
 | 
			
		||||
  async createReadStream() {
 | 
			
		||||
    const result = await this._channel.stream();
 | 
			
		||||
    const stream = import_stream.Stream.from(result.stream);
 | 
			
		||||
    return stream.stream();
 | 
			
		||||
  }
 | 
			
		||||
  async readIntoBuffer() {
 | 
			
		||||
    const stream = await this.createReadStream();
 | 
			
		||||
    return await new Promise((resolve, reject) => {
 | 
			
		||||
      const chunks = [];
 | 
			
		||||
      stream.on("data", (chunk) => {
 | 
			
		||||
        chunks.push(chunk);
 | 
			
		||||
      });
 | 
			
		||||
      stream.on("end", () => {
 | 
			
		||||
        resolve(Buffer.concat(chunks));
 | 
			
		||||
      });
 | 
			
		||||
      stream.on("error", reject);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async cancel() {
 | 
			
		||||
    return await this._channel.cancel();
 | 
			
		||||
  }
 | 
			
		||||
  async delete() {
 | 
			
		||||
    return await this._channel.delete();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Artifact
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										173
									
								
								node_modules/playwright-core/lib/client/browser.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								node_modules/playwright-core/lib/client/browser.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var browser_exports = {};
 | 
			
		||||
__export(browser_exports, {
 | 
			
		||||
  Browser: () => Browser
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(browser_exports);
 | 
			
		||||
var import_artifact = require("./artifact");
 | 
			
		||||
var import_browserContext = require("./browserContext");
 | 
			
		||||
var import_cdpSession = require("./cdpSession");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_fileUtils = require("./fileUtils");
 | 
			
		||||
class Browser extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._contexts = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._isConnected = true;
 | 
			
		||||
    this._shouldCloseConnectionOnClose = false;
 | 
			
		||||
    this._options = {};
 | 
			
		||||
    this._name = initializer.name;
 | 
			
		||||
    this._channel.on("context", ({ context }) => this._didCreateContext(import_browserContext.BrowserContext.from(context)));
 | 
			
		||||
    this._channel.on("close", () => this._didClose());
 | 
			
		||||
    this._closedPromise = new Promise((f) => this.once(import_events.Events.Browser.Disconnected, f));
 | 
			
		||||
  }
 | 
			
		||||
  static from(browser) {
 | 
			
		||||
    return browser._object;
 | 
			
		||||
  }
 | 
			
		||||
  browserType() {
 | 
			
		||||
    return this._browserType;
 | 
			
		||||
  }
 | 
			
		||||
  async newContext(options = {}) {
 | 
			
		||||
    return await this._innerNewContext(options, false);
 | 
			
		||||
  }
 | 
			
		||||
  async _newContextForReuse(options = {}) {
 | 
			
		||||
    return await this._innerNewContext(options, true);
 | 
			
		||||
  }
 | 
			
		||||
  async _disconnectFromReusedContext(reason) {
 | 
			
		||||
    const context = [...this._contexts].find((context2) => context2._forReuse);
 | 
			
		||||
    if (!context)
 | 
			
		||||
      return;
 | 
			
		||||
    await this._instrumentation.runBeforeCloseBrowserContext(context);
 | 
			
		||||
    for (const page of context.pages())
 | 
			
		||||
      page._onClose();
 | 
			
		||||
    context._onClose();
 | 
			
		||||
    await this._channel.disconnectFromReusedContext({ reason });
 | 
			
		||||
  }
 | 
			
		||||
  async _innerNewContext(options = {}, forReuse) {
 | 
			
		||||
    options = this._browserType._playwright.selectors._withSelectorOptions({
 | 
			
		||||
      ...this._browserType._playwright._defaultContextOptions,
 | 
			
		||||
      ...options
 | 
			
		||||
    });
 | 
			
		||||
    const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
 | 
			
		||||
    const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
 | 
			
		||||
    const context = import_browserContext.BrowserContext.from(response.context);
 | 
			
		||||
    if (forReuse)
 | 
			
		||||
      context._forReuse = true;
 | 
			
		||||
    if (options.logger)
 | 
			
		||||
      context._logger = options.logger;
 | 
			
		||||
    await context._initializeHarFromOptions(options.recordHar);
 | 
			
		||||
    await this._instrumentation.runAfterCreateBrowserContext(context);
 | 
			
		||||
    return context;
 | 
			
		||||
  }
 | 
			
		||||
  _connectToBrowserType(browserType, browserOptions, logger) {
 | 
			
		||||
    this._browserType = browserType;
 | 
			
		||||
    this._options = browserOptions;
 | 
			
		||||
    this._logger = logger;
 | 
			
		||||
    for (const context of this._contexts)
 | 
			
		||||
      this._setupBrowserContext(context);
 | 
			
		||||
  }
 | 
			
		||||
  _didCreateContext(context) {
 | 
			
		||||
    context._browser = this;
 | 
			
		||||
    this._contexts.add(context);
 | 
			
		||||
    if (this._browserType)
 | 
			
		||||
      this._setupBrowserContext(context);
 | 
			
		||||
  }
 | 
			
		||||
  _setupBrowserContext(context) {
 | 
			
		||||
    context._logger = this._logger;
 | 
			
		||||
    context.tracing._tracesDir = this._options.tracesDir;
 | 
			
		||||
    this._browserType._contexts.add(context);
 | 
			
		||||
    this._browserType._playwright.selectors._contextsForSelectors.add(context);
 | 
			
		||||
    context.setDefaultTimeout(this._browserType._playwright._defaultContextTimeout);
 | 
			
		||||
    context.setDefaultNavigationTimeout(this._browserType._playwright._defaultContextNavigationTimeout);
 | 
			
		||||
  }
 | 
			
		||||
  contexts() {
 | 
			
		||||
    return [...this._contexts];
 | 
			
		||||
  }
 | 
			
		||||
  version() {
 | 
			
		||||
    return this._initializer.version;
 | 
			
		||||
  }
 | 
			
		||||
  async newPage(options = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const context = await this.newContext(options);
 | 
			
		||||
      const page = await context.newPage();
 | 
			
		||||
      page._ownedContext = context;
 | 
			
		||||
      context._ownerPage = page;
 | 
			
		||||
      return page;
 | 
			
		||||
    }, { title: "Create page" });
 | 
			
		||||
  }
 | 
			
		||||
  isConnected() {
 | 
			
		||||
    return this._isConnected;
 | 
			
		||||
  }
 | 
			
		||||
  async newBrowserCDPSession() {
 | 
			
		||||
    return import_cdpSession.CDPSession.from((await this._channel.newBrowserCDPSession()).session);
 | 
			
		||||
  }
 | 
			
		||||
  async _launchServer(options = {}) {
 | 
			
		||||
    const serverLauncher = this._browserType._serverLauncher;
 | 
			
		||||
    const browserImpl = this._connection.toImpl?.(this);
 | 
			
		||||
    if (!serverLauncher || !browserImpl)
 | 
			
		||||
      throw new Error("Launching server is not supported");
 | 
			
		||||
    return await serverLauncher.launchServerOnExistingBrowser(browserImpl, {
 | 
			
		||||
      _sharedBrowser: true,
 | 
			
		||||
      ...options
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async startTracing(page, options = {}) {
 | 
			
		||||
    this._path = options.path;
 | 
			
		||||
    await this._channel.startTracing({ ...options, page: page ? page._channel : void 0 });
 | 
			
		||||
  }
 | 
			
		||||
  async stopTracing() {
 | 
			
		||||
    const artifact = import_artifact.Artifact.from((await this._channel.stopTracing()).artifact);
 | 
			
		||||
    const buffer = await artifact.readIntoBuffer();
 | 
			
		||||
    await artifact.delete();
 | 
			
		||||
    if (this._path) {
 | 
			
		||||
      await (0, import_fileUtils.mkdirIfNeeded)(this._platform, this._path);
 | 
			
		||||
      await this._platform.fs().promises.writeFile(this._path, buffer);
 | 
			
		||||
      this._path = void 0;
 | 
			
		||||
    }
 | 
			
		||||
    return buffer;
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.close();
 | 
			
		||||
  }
 | 
			
		||||
  async close(options = {}) {
 | 
			
		||||
    this._closeReason = options.reason;
 | 
			
		||||
    try {
 | 
			
		||||
      if (this._shouldCloseConnectionOnClose)
 | 
			
		||||
        this._connection.close();
 | 
			
		||||
      else
 | 
			
		||||
        await this._channel.close(options);
 | 
			
		||||
      await this._closedPromise;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if ((0, import_errors.isTargetClosedError)(e))
 | 
			
		||||
        return;
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  _didClose() {
 | 
			
		||||
    this._isConnected = false;
 | 
			
		||||
    this.emit(import_events.Events.Browser.Disconnected, this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Browser
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										535
									
								
								node_modules/playwright-core/lib/client/browserContext.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										535
									
								
								node_modules/playwright-core/lib/client/browserContext.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,535 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __create = Object.create;
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __getProtoOf = Object.getPrototypeOf;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
 | 
			
		||||
  // If the importer is in node compatibility mode or this is not an ESM
 | 
			
		||||
  // file that has been converted to a CommonJS file using a Babel-
 | 
			
		||||
  // compatible transform (i.e. "__esModule" has not been set), then set
 | 
			
		||||
  // "default" to the CommonJS "module.exports" for node compatibility.
 | 
			
		||||
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
 | 
			
		||||
  mod
 | 
			
		||||
));
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var browserContext_exports = {};
 | 
			
		||||
__export(browserContext_exports, {
 | 
			
		||||
  BrowserContext: () => BrowserContext,
 | 
			
		||||
  prepareBrowserContextParams: () => prepareBrowserContextParams,
 | 
			
		||||
  toClientCertificatesProtocol: () => toClientCertificatesProtocol
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(browserContext_exports);
 | 
			
		||||
var import_artifact = require("./artifact");
 | 
			
		||||
var import_cdpSession = require("./cdpSession");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_clientHelper = require("./clientHelper");
 | 
			
		||||
var import_clock = require("./clock");
 | 
			
		||||
var import_consoleMessage = require("./consoleMessage");
 | 
			
		||||
var import_dialog = require("./dialog");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_fetch = require("./fetch");
 | 
			
		||||
var import_frame = require("./frame");
 | 
			
		||||
var import_harRouter = require("./harRouter");
 | 
			
		||||
var network = __toESM(require("./network"));
 | 
			
		||||
var import_page = require("./page");
 | 
			
		||||
var import_tracing = require("./tracing");
 | 
			
		||||
var import_waiter = require("./waiter");
 | 
			
		||||
var import_webError = require("./webError");
 | 
			
		||||
var import_worker = require("./worker");
 | 
			
		||||
var import_timeoutSettings = require("./timeoutSettings");
 | 
			
		||||
var import_fileUtils = require("./fileUtils");
 | 
			
		||||
var import_headers = require("../utils/isomorphic/headers");
 | 
			
		||||
var import_urlMatch = require("../utils/isomorphic/urlMatch");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
var import_stackTrace = require("../utils/isomorphic/stackTrace");
 | 
			
		||||
class BrowserContext extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._pages = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._routes = [];
 | 
			
		||||
    this._webSocketRoutes = [];
 | 
			
		||||
    // Browser is null for browser contexts created outside of normal browser, e.g. android or electron.
 | 
			
		||||
    this._browser = null;
 | 
			
		||||
    this._bindings = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._forReuse = false;
 | 
			
		||||
    this._backgroundPages = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._serviceWorkers = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._harRecorders = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._closingStatus = "none";
 | 
			
		||||
    this._harRouters = [];
 | 
			
		||||
    this._options = initializer.options;
 | 
			
		||||
    this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
 | 
			
		||||
    this.tracing = import_tracing.Tracing.from(initializer.tracing);
 | 
			
		||||
    this.request = import_fetch.APIRequestContext.from(initializer.requestContext);
 | 
			
		||||
    this.request._timeoutSettings = this._timeoutSettings;
 | 
			
		||||
    this.clock = new import_clock.Clock(this);
 | 
			
		||||
    this._channel.on("bindingCall", ({ binding }) => this._onBinding(import_page.BindingCall.from(binding)));
 | 
			
		||||
    this._channel.on("close", () => this._onClose());
 | 
			
		||||
    this._channel.on("page", ({ page }) => this._onPage(import_page.Page.from(page)));
 | 
			
		||||
    this._channel.on("route", ({ route }) => this._onRoute(network.Route.from(route)));
 | 
			
		||||
    this._channel.on("webSocketRoute", ({ webSocketRoute }) => this._onWebSocketRoute(network.WebSocketRoute.from(webSocketRoute)));
 | 
			
		||||
    this._channel.on("backgroundPage", ({ page }) => {
 | 
			
		||||
      const backgroundPage = import_page.Page.from(page);
 | 
			
		||||
      this._backgroundPages.add(backgroundPage);
 | 
			
		||||
      this.emit(import_events.Events.BrowserContext.BackgroundPage, backgroundPage);
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("serviceWorker", ({ worker }) => {
 | 
			
		||||
      const serviceWorker = import_worker.Worker.from(worker);
 | 
			
		||||
      serviceWorker._context = this;
 | 
			
		||||
      this._serviceWorkers.add(serviceWorker);
 | 
			
		||||
      this.emit(import_events.Events.BrowserContext.ServiceWorker, serviceWorker);
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("console", (event) => {
 | 
			
		||||
      const consoleMessage = new import_consoleMessage.ConsoleMessage(this._platform, event);
 | 
			
		||||
      this.emit(import_events.Events.BrowserContext.Console, consoleMessage);
 | 
			
		||||
      const page = consoleMessage.page();
 | 
			
		||||
      if (page)
 | 
			
		||||
        page.emit(import_events.Events.Page.Console, consoleMessage);
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("pageError", ({ error, page }) => {
 | 
			
		||||
      const pageObject = import_page.Page.from(page);
 | 
			
		||||
      const parsedError = (0, import_errors.parseError)(error);
 | 
			
		||||
      this.emit(import_events.Events.BrowserContext.WebError, new import_webError.WebError(pageObject, parsedError));
 | 
			
		||||
      if (pageObject)
 | 
			
		||||
        pageObject.emit(import_events.Events.Page.PageError, parsedError);
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("dialog", ({ dialog }) => {
 | 
			
		||||
      const dialogObject = import_dialog.Dialog.from(dialog);
 | 
			
		||||
      let hasListeners = this.emit(import_events.Events.BrowserContext.Dialog, dialogObject);
 | 
			
		||||
      const page = dialogObject.page();
 | 
			
		||||
      if (page)
 | 
			
		||||
        hasListeners = page.emit(import_events.Events.Page.Dialog, dialogObject) || hasListeners;
 | 
			
		||||
      if (!hasListeners) {
 | 
			
		||||
        if (dialogObject.type() === "beforeunload")
 | 
			
		||||
          dialog.accept({}).catch(() => {
 | 
			
		||||
          });
 | 
			
		||||
        else
 | 
			
		||||
          dialog.dismiss().catch(() => {
 | 
			
		||||
          });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("request", ({ request, page }) => this._onRequest(network.Request.from(request), import_page.Page.fromNullable(page)));
 | 
			
		||||
    this._channel.on("requestFailed", ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, import_page.Page.fromNullable(page)));
 | 
			
		||||
    this._channel.on("requestFinished", (params) => this._onRequestFinished(params));
 | 
			
		||||
    this._channel.on("response", ({ response, page }) => this._onResponse(network.Response.from(response), import_page.Page.fromNullable(page)));
 | 
			
		||||
    this._channel.on("recorderEvent", ({ event, data, page, code }) => {
 | 
			
		||||
      if (event === "actionAdded")
 | 
			
		||||
        this._onRecorderEventSink?.actionAdded?.(import_page.Page.from(page), data, code);
 | 
			
		||||
      else if (event === "actionUpdated")
 | 
			
		||||
        this._onRecorderEventSink?.actionUpdated?.(import_page.Page.from(page), data, code);
 | 
			
		||||
      else if (event === "signalAdded")
 | 
			
		||||
        this._onRecorderEventSink?.signalAdded?.(import_page.Page.from(page), data);
 | 
			
		||||
    });
 | 
			
		||||
    this._closedPromise = new Promise((f) => this.once(import_events.Events.BrowserContext.Close, f));
 | 
			
		||||
    this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
 | 
			
		||||
      [import_events.Events.BrowserContext.Console, "console"],
 | 
			
		||||
      [import_events.Events.BrowserContext.Dialog, "dialog"],
 | 
			
		||||
      [import_events.Events.BrowserContext.Request, "request"],
 | 
			
		||||
      [import_events.Events.BrowserContext.Response, "response"],
 | 
			
		||||
      [import_events.Events.BrowserContext.RequestFinished, "requestFinished"],
 | 
			
		||||
      [import_events.Events.BrowserContext.RequestFailed, "requestFailed"]
 | 
			
		||||
    ]));
 | 
			
		||||
  }
 | 
			
		||||
  static from(context) {
 | 
			
		||||
    return context._object;
 | 
			
		||||
  }
 | 
			
		||||
  static fromNullable(context) {
 | 
			
		||||
    return context ? BrowserContext.from(context) : null;
 | 
			
		||||
  }
 | 
			
		||||
  async _initializeHarFromOptions(recordHar) {
 | 
			
		||||
    if (!recordHar)
 | 
			
		||||
      return;
 | 
			
		||||
    const defaultContent = recordHar.path.endsWith(".zip") ? "attach" : "embed";
 | 
			
		||||
    await this._recordIntoHAR(recordHar.path, null, {
 | 
			
		||||
      url: recordHar.urlFilter,
 | 
			
		||||
      updateContent: recordHar.content ?? (recordHar.omitContent ? "omit" : defaultContent),
 | 
			
		||||
      updateMode: recordHar.mode ?? "full"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  _onPage(page) {
 | 
			
		||||
    this._pages.add(page);
 | 
			
		||||
    this.emit(import_events.Events.BrowserContext.Page, page);
 | 
			
		||||
    if (page._opener && !page._opener.isClosed())
 | 
			
		||||
      page._opener.emit(import_events.Events.Page.Popup, page);
 | 
			
		||||
  }
 | 
			
		||||
  _onRequest(request, page) {
 | 
			
		||||
    this.emit(import_events.Events.BrowserContext.Request, request);
 | 
			
		||||
    if (page)
 | 
			
		||||
      page.emit(import_events.Events.Page.Request, request);
 | 
			
		||||
  }
 | 
			
		||||
  _onResponse(response, page) {
 | 
			
		||||
    this.emit(import_events.Events.BrowserContext.Response, response);
 | 
			
		||||
    if (page)
 | 
			
		||||
      page.emit(import_events.Events.Page.Response, response);
 | 
			
		||||
  }
 | 
			
		||||
  _onRequestFailed(request, responseEndTiming, failureText, page) {
 | 
			
		||||
    request._failureText = failureText || null;
 | 
			
		||||
    request._setResponseEndTiming(responseEndTiming);
 | 
			
		||||
    this.emit(import_events.Events.BrowserContext.RequestFailed, request);
 | 
			
		||||
    if (page)
 | 
			
		||||
      page.emit(import_events.Events.Page.RequestFailed, request);
 | 
			
		||||
  }
 | 
			
		||||
  _onRequestFinished(params) {
 | 
			
		||||
    const { responseEndTiming } = params;
 | 
			
		||||
    const request = network.Request.from(params.request);
 | 
			
		||||
    const response = network.Response.fromNullable(params.response);
 | 
			
		||||
    const page = import_page.Page.fromNullable(params.page);
 | 
			
		||||
    request._setResponseEndTiming(responseEndTiming);
 | 
			
		||||
    this.emit(import_events.Events.BrowserContext.RequestFinished, request);
 | 
			
		||||
    if (page)
 | 
			
		||||
      page.emit(import_events.Events.Page.RequestFinished, request);
 | 
			
		||||
    if (response)
 | 
			
		||||
      response._finishedPromise.resolve(null);
 | 
			
		||||
  }
 | 
			
		||||
  async _onRoute(route) {
 | 
			
		||||
    route._context = this;
 | 
			
		||||
    const page = route.request()._safePage();
 | 
			
		||||
    const routeHandlers = this._routes.slice();
 | 
			
		||||
    for (const routeHandler of routeHandlers) {
 | 
			
		||||
      if (page?._closeWasCalled || this._closingStatus !== "none")
 | 
			
		||||
        return;
 | 
			
		||||
      if (!routeHandler.matches(route.request().url()))
 | 
			
		||||
        continue;
 | 
			
		||||
      const index = this._routes.indexOf(routeHandler);
 | 
			
		||||
      if (index === -1)
 | 
			
		||||
        continue;
 | 
			
		||||
      if (routeHandler.willExpire())
 | 
			
		||||
        this._routes.splice(index, 1);
 | 
			
		||||
      const handled = await routeHandler.handle(route);
 | 
			
		||||
      if (!this._routes.length)
 | 
			
		||||
        this._updateInterceptionPatterns({ internal: true }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
      if (handled)
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    await route._innerContinue(
 | 
			
		||||
      true
 | 
			
		||||
      /* isFallback */
 | 
			
		||||
    ).catch(() => {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async _onWebSocketRoute(webSocketRoute) {
 | 
			
		||||
    const routeHandler = this._webSocketRoutes.find((route) => route.matches(webSocketRoute.url()));
 | 
			
		||||
    if (routeHandler)
 | 
			
		||||
      await routeHandler.handle(webSocketRoute);
 | 
			
		||||
    else
 | 
			
		||||
      webSocketRoute.connectToServer();
 | 
			
		||||
  }
 | 
			
		||||
  async _onBinding(bindingCall) {
 | 
			
		||||
    const func = this._bindings.get(bindingCall._initializer.name);
 | 
			
		||||
    if (!func)
 | 
			
		||||
      return;
 | 
			
		||||
    await bindingCall.call(func);
 | 
			
		||||
  }
 | 
			
		||||
  setDefaultNavigationTimeout(timeout) {
 | 
			
		||||
    this._timeoutSettings.setDefaultNavigationTimeout(timeout);
 | 
			
		||||
  }
 | 
			
		||||
  setDefaultTimeout(timeout) {
 | 
			
		||||
    this._timeoutSettings.setDefaultTimeout(timeout);
 | 
			
		||||
  }
 | 
			
		||||
  browser() {
 | 
			
		||||
    return this._browser;
 | 
			
		||||
  }
 | 
			
		||||
  pages() {
 | 
			
		||||
    return [...this._pages];
 | 
			
		||||
  }
 | 
			
		||||
  async newPage() {
 | 
			
		||||
    if (this._ownerPage)
 | 
			
		||||
      throw new Error("Please use browser.newContext()");
 | 
			
		||||
    return import_page.Page.from((await this._channel.newPage()).page);
 | 
			
		||||
  }
 | 
			
		||||
  async cookies(urls) {
 | 
			
		||||
    if (!urls)
 | 
			
		||||
      urls = [];
 | 
			
		||||
    if (urls && typeof urls === "string")
 | 
			
		||||
      urls = [urls];
 | 
			
		||||
    return (await this._channel.cookies({ urls })).cookies;
 | 
			
		||||
  }
 | 
			
		||||
  async addCookies(cookies) {
 | 
			
		||||
    await this._channel.addCookies({ cookies });
 | 
			
		||||
  }
 | 
			
		||||
  async clearCookies(options = {}) {
 | 
			
		||||
    await this._channel.clearCookies({
 | 
			
		||||
      name: (0, import_rtti.isString)(options.name) ? options.name : void 0,
 | 
			
		||||
      nameRegexSource: (0, import_rtti.isRegExp)(options.name) ? options.name.source : void 0,
 | 
			
		||||
      nameRegexFlags: (0, import_rtti.isRegExp)(options.name) ? options.name.flags : void 0,
 | 
			
		||||
      domain: (0, import_rtti.isString)(options.domain) ? options.domain : void 0,
 | 
			
		||||
      domainRegexSource: (0, import_rtti.isRegExp)(options.domain) ? options.domain.source : void 0,
 | 
			
		||||
      domainRegexFlags: (0, import_rtti.isRegExp)(options.domain) ? options.domain.flags : void 0,
 | 
			
		||||
      path: (0, import_rtti.isString)(options.path) ? options.path : void 0,
 | 
			
		||||
      pathRegexSource: (0, import_rtti.isRegExp)(options.path) ? options.path.source : void 0,
 | 
			
		||||
      pathRegexFlags: (0, import_rtti.isRegExp)(options.path) ? options.path.flags : void 0
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async grantPermissions(permissions, options) {
 | 
			
		||||
    await this._channel.grantPermissions({ permissions, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async clearPermissions() {
 | 
			
		||||
    await this._channel.clearPermissions();
 | 
			
		||||
  }
 | 
			
		||||
  async setGeolocation(geolocation) {
 | 
			
		||||
    await this._channel.setGeolocation({ geolocation: geolocation || void 0 });
 | 
			
		||||
  }
 | 
			
		||||
  async setExtraHTTPHeaders(headers) {
 | 
			
		||||
    network.validateHeaders(headers);
 | 
			
		||||
    await this._channel.setExtraHTTPHeaders({ headers: (0, import_headers.headersObjectToArray)(headers) });
 | 
			
		||||
  }
 | 
			
		||||
  async setOffline(offline) {
 | 
			
		||||
    await this._channel.setOffline({ offline });
 | 
			
		||||
  }
 | 
			
		||||
  async setHTTPCredentials(httpCredentials) {
 | 
			
		||||
    await this._channel.setHTTPCredentials({ httpCredentials: httpCredentials || void 0 });
 | 
			
		||||
  }
 | 
			
		||||
  async addInitScript(script, arg) {
 | 
			
		||||
    const source = await (0, import_clientHelper.evaluationScript)(this._platform, script, arg);
 | 
			
		||||
    await this._channel.addInitScript({ source });
 | 
			
		||||
  }
 | 
			
		||||
  async exposeBinding(name, callback, options = {}) {
 | 
			
		||||
    await this._channel.exposeBinding({ name, needsHandle: options.handle });
 | 
			
		||||
    this._bindings.set(name, callback);
 | 
			
		||||
  }
 | 
			
		||||
  async exposeFunction(name, callback) {
 | 
			
		||||
    await this._channel.exposeBinding({ name });
 | 
			
		||||
    const binding = (source, ...args) => callback(...args);
 | 
			
		||||
    this._bindings.set(name, binding);
 | 
			
		||||
  }
 | 
			
		||||
  async route(url, handler, options = {}) {
 | 
			
		||||
    this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
 | 
			
		||||
    await this._updateInterceptionPatterns({ title: "Route requests" });
 | 
			
		||||
  }
 | 
			
		||||
  async routeWebSocket(url, handler) {
 | 
			
		||||
    this._webSocketRoutes.unshift(new network.WebSocketRouteHandler(this._options.baseURL, url, handler));
 | 
			
		||||
    await this._updateWebSocketInterceptionPatterns({ title: "Route WebSockets" });
 | 
			
		||||
  }
 | 
			
		||||
  async _recordIntoHAR(har, page, options = {}) {
 | 
			
		||||
    const { harId } = await this._channel.harStart({
 | 
			
		||||
      page: page?._channel,
 | 
			
		||||
      options: {
 | 
			
		||||
        zip: har.endsWith(".zip"),
 | 
			
		||||
        content: options.updateContent ?? "attach",
 | 
			
		||||
        urlGlob: (0, import_rtti.isString)(options.url) ? options.url : void 0,
 | 
			
		||||
        urlRegexSource: (0, import_rtti.isRegExp)(options.url) ? options.url.source : void 0,
 | 
			
		||||
        urlRegexFlags: (0, import_rtti.isRegExp)(options.url) ? options.url.flags : void 0,
 | 
			
		||||
        mode: options.updateMode ?? "minimal"
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    this._harRecorders.set(harId, { path: har, content: options.updateContent ?? "attach" });
 | 
			
		||||
  }
 | 
			
		||||
  async routeFromHAR(har, options = {}) {
 | 
			
		||||
    const localUtils = this._connection.localUtils();
 | 
			
		||||
    if (!localUtils)
 | 
			
		||||
      throw new Error("Route from har is not supported in thin clients");
 | 
			
		||||
    if (options.update) {
 | 
			
		||||
      await this._recordIntoHAR(har, null, options);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const harRouter = await import_harRouter.HarRouter.create(localUtils, har, options.notFound || "abort", { urlMatch: options.url });
 | 
			
		||||
    this._harRouters.push(harRouter);
 | 
			
		||||
    await harRouter.addContextRoute(this);
 | 
			
		||||
  }
 | 
			
		||||
  _disposeHarRouters() {
 | 
			
		||||
    this._harRouters.forEach((router) => router.dispose());
 | 
			
		||||
    this._harRouters = [];
 | 
			
		||||
  }
 | 
			
		||||
  async unrouteAll(options) {
 | 
			
		||||
    await this._unrouteInternal(this._routes, [], options?.behavior);
 | 
			
		||||
    this._disposeHarRouters();
 | 
			
		||||
  }
 | 
			
		||||
  async unroute(url, handler) {
 | 
			
		||||
    const removed = [];
 | 
			
		||||
    const remaining = [];
 | 
			
		||||
    for (const route of this._routes) {
 | 
			
		||||
      if ((0, import_urlMatch.urlMatchesEqual)(route.url, url) && (!handler || route.handler === handler))
 | 
			
		||||
        removed.push(route);
 | 
			
		||||
      else
 | 
			
		||||
        remaining.push(route);
 | 
			
		||||
    }
 | 
			
		||||
    await this._unrouteInternal(removed, remaining, "default");
 | 
			
		||||
  }
 | 
			
		||||
  async _unrouteInternal(removed, remaining, behavior) {
 | 
			
		||||
    this._routes = remaining;
 | 
			
		||||
    if (behavior && behavior !== "default") {
 | 
			
		||||
      const promises = removed.map((routeHandler) => routeHandler.stop(behavior));
 | 
			
		||||
      await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
    await this._updateInterceptionPatterns({ title: "Unroute requests" });
 | 
			
		||||
  }
 | 
			
		||||
  async _updateInterceptionPatterns(options) {
 | 
			
		||||
    const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes);
 | 
			
		||||
    await this._wrapApiCall(() => this._channel.setNetworkInterceptionPatterns({ patterns }), options);
 | 
			
		||||
  }
 | 
			
		||||
  async _updateWebSocketInterceptionPatterns(options) {
 | 
			
		||||
    const patterns = network.WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes);
 | 
			
		||||
    await this._wrapApiCall(() => this._channel.setWebSocketInterceptionPatterns({ patterns }), options);
 | 
			
		||||
  }
 | 
			
		||||
  _effectiveCloseReason() {
 | 
			
		||||
    return this._closeReason || this._browser?._closeReason;
 | 
			
		||||
  }
 | 
			
		||||
  async waitForEvent(event, optionsOrPredicate = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
 | 
			
		||||
      const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
 | 
			
		||||
      const waiter = import_waiter.Waiter.createForEvent(this, event);
 | 
			
		||||
      waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
 | 
			
		||||
      if (event !== import_events.Events.BrowserContext.Close)
 | 
			
		||||
        waiter.rejectOnEvent(this, import_events.Events.BrowserContext.Close, () => new import_errors.TargetClosedError(this._effectiveCloseReason()));
 | 
			
		||||
      const result = await waiter.waitForEvent(this, event, predicate);
 | 
			
		||||
      waiter.dispose();
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async storageState(options = {}) {
 | 
			
		||||
    const state = await this._channel.storageState({ indexedDB: options.indexedDB });
 | 
			
		||||
    if (options.path) {
 | 
			
		||||
      await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
 | 
			
		||||
      await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
 | 
			
		||||
    }
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
  backgroundPages() {
 | 
			
		||||
    return [...this._backgroundPages];
 | 
			
		||||
  }
 | 
			
		||||
  serviceWorkers() {
 | 
			
		||||
    return [...this._serviceWorkers];
 | 
			
		||||
  }
 | 
			
		||||
  async newCDPSession(page) {
 | 
			
		||||
    if (!(page instanceof import_page.Page) && !(page instanceof import_frame.Frame))
 | 
			
		||||
      throw new Error("page: expected Page or Frame");
 | 
			
		||||
    const result = await this._channel.newCDPSession(page instanceof import_page.Page ? { page: page._channel } : { frame: page._channel });
 | 
			
		||||
    return import_cdpSession.CDPSession.from(result.session);
 | 
			
		||||
  }
 | 
			
		||||
  _onClose() {
 | 
			
		||||
    this._closingStatus = "closed";
 | 
			
		||||
    this._browser?._contexts.delete(this);
 | 
			
		||||
    this._browser?._browserType._contexts.delete(this);
 | 
			
		||||
    this._browser?._browserType._playwright.selectors._contextsForSelectors.delete(this);
 | 
			
		||||
    this._disposeHarRouters();
 | 
			
		||||
    this.tracing._resetStackCounter();
 | 
			
		||||
    this.emit(import_events.Events.BrowserContext.Close, this);
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.close();
 | 
			
		||||
  }
 | 
			
		||||
  async close(options = {}) {
 | 
			
		||||
    if (this._closingStatus !== "none")
 | 
			
		||||
      return;
 | 
			
		||||
    this._closeReason = options.reason;
 | 
			
		||||
    this._closingStatus = "closing";
 | 
			
		||||
    await this.request.dispose(options);
 | 
			
		||||
    await this._instrumentation.runBeforeCloseBrowserContext(this);
 | 
			
		||||
    await this._wrapApiCall(async () => {
 | 
			
		||||
      for (const [harId, harParams] of this._harRecorders) {
 | 
			
		||||
        const har = await this._channel.harExport({ harId });
 | 
			
		||||
        const artifact = import_artifact.Artifact.from(har.artifact);
 | 
			
		||||
        const isCompressed = harParams.content === "attach" || harParams.path.endsWith(".zip");
 | 
			
		||||
        const needCompressed = harParams.path.endsWith(".zip");
 | 
			
		||||
        if (isCompressed && !needCompressed) {
 | 
			
		||||
          const localUtils = this._connection.localUtils();
 | 
			
		||||
          if (!localUtils)
 | 
			
		||||
            throw new Error("Uncompressed har is not supported in thin clients");
 | 
			
		||||
          await artifact.saveAs(harParams.path + ".tmp");
 | 
			
		||||
          await localUtils.harUnzip({ zipFile: harParams.path + ".tmp", harFile: harParams.path });
 | 
			
		||||
        } else {
 | 
			
		||||
          await artifact.saveAs(harParams.path);
 | 
			
		||||
        }
 | 
			
		||||
        await artifact.delete();
 | 
			
		||||
      }
 | 
			
		||||
    }, { internal: true });
 | 
			
		||||
    await this._channel.close(options);
 | 
			
		||||
    await this._closedPromise;
 | 
			
		||||
  }
 | 
			
		||||
  async _enableRecorder(params, eventSink) {
 | 
			
		||||
    if (eventSink)
 | 
			
		||||
      this._onRecorderEventSink = eventSink;
 | 
			
		||||
    await this._channel.enableRecorder(params);
 | 
			
		||||
  }
 | 
			
		||||
  async _disableRecorder() {
 | 
			
		||||
    this._onRecorderEventSink = void 0;
 | 
			
		||||
    await this._channel.disableRecorder();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
async function prepareStorageState(platform, storageState) {
 | 
			
		||||
  if (typeof storageState !== "string")
 | 
			
		||||
    return storageState;
 | 
			
		||||
  try {
 | 
			
		||||
    return JSON.parse(await platform.fs().promises.readFile(storageState, "utf8"));
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    (0, import_stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${storageState}:
 | 
			
		||||
` + e.message);
 | 
			
		||||
    throw e;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
async function prepareBrowserContextParams(platform, options) {
 | 
			
		||||
  if (options.videoSize && !options.videosPath)
 | 
			
		||||
    throw new Error(`"videoSize" option requires "videosPath" to be specified`);
 | 
			
		||||
  if (options.extraHTTPHeaders)
 | 
			
		||||
    network.validateHeaders(options.extraHTTPHeaders);
 | 
			
		||||
  const contextParams = {
 | 
			
		||||
    ...options,
 | 
			
		||||
    viewport: options.viewport === null ? void 0 : options.viewport,
 | 
			
		||||
    noDefaultViewport: options.viewport === null,
 | 
			
		||||
    extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
 | 
			
		||||
    storageState: options.storageState ? await prepareStorageState(platform, options.storageState) : void 0,
 | 
			
		||||
    serviceWorkers: options.serviceWorkers,
 | 
			
		||||
    colorScheme: options.colorScheme === null ? "no-override" : options.colorScheme,
 | 
			
		||||
    reducedMotion: options.reducedMotion === null ? "no-override" : options.reducedMotion,
 | 
			
		||||
    forcedColors: options.forcedColors === null ? "no-override" : options.forcedColors,
 | 
			
		||||
    contrast: options.contrast === null ? "no-override" : options.contrast,
 | 
			
		||||
    acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
 | 
			
		||||
    clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates)
 | 
			
		||||
  };
 | 
			
		||||
  if (!contextParams.recordVideo && options.videosPath) {
 | 
			
		||||
    contextParams.recordVideo = {
 | 
			
		||||
      dir: options.videosPath,
 | 
			
		||||
      size: options.videoSize
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  if (contextParams.recordVideo && contextParams.recordVideo.dir)
 | 
			
		||||
    contextParams.recordVideo.dir = platform.path().resolve(contextParams.recordVideo.dir);
 | 
			
		||||
  return contextParams;
 | 
			
		||||
}
 | 
			
		||||
function toAcceptDownloadsProtocol(acceptDownloads) {
 | 
			
		||||
  if (acceptDownloads === void 0)
 | 
			
		||||
    return void 0;
 | 
			
		||||
  if (acceptDownloads)
 | 
			
		||||
    return "accept";
 | 
			
		||||
  return "deny";
 | 
			
		||||
}
 | 
			
		||||
async function toClientCertificatesProtocol(platform, certs) {
 | 
			
		||||
  if (!certs)
 | 
			
		||||
    return void 0;
 | 
			
		||||
  const bufferizeContent = async (value, path) => {
 | 
			
		||||
    if (value)
 | 
			
		||||
      return value;
 | 
			
		||||
    if (path)
 | 
			
		||||
      return await platform.fs().promises.readFile(path);
 | 
			
		||||
  };
 | 
			
		||||
  return await Promise.all(certs.map(async (cert) => ({
 | 
			
		||||
    origin: cert.origin,
 | 
			
		||||
    cert: await bufferizeContent(cert.cert, cert.certPath),
 | 
			
		||||
    key: await bufferizeContent(cert.key, cert.keyPath),
 | 
			
		||||
    pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
 | 
			
		||||
    passphrase: cert.passphrase
 | 
			
		||||
  })));
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  BrowserContext,
 | 
			
		||||
  prepareBrowserContextParams,
 | 
			
		||||
  toClientCertificatesProtocol
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										184
									
								
								node_modules/playwright-core/lib/client/browserType.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								node_modules/playwright-core/lib/client/browserType.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,184 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var browserType_exports = {};
 | 
			
		||||
__export(browserType_exports, {
 | 
			
		||||
  BrowserType: () => BrowserType
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(browserType_exports);
 | 
			
		||||
var import_browser = require("./browser");
 | 
			
		||||
var import_browserContext = require("./browserContext");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_clientHelper = require("./clientHelper");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_assert = require("../utils/isomorphic/assert");
 | 
			
		||||
var import_headers = require("../utils/isomorphic/headers");
 | 
			
		||||
var import_time = require("../utils/isomorphic/time");
 | 
			
		||||
var import_timeoutRunner = require("../utils/isomorphic/timeoutRunner");
 | 
			
		||||
var import_webSocket = require("./webSocket");
 | 
			
		||||
var import_timeoutSettings = require("./timeoutSettings");
 | 
			
		||||
class BrowserType extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super(...arguments);
 | 
			
		||||
    this._contexts = /* @__PURE__ */ new Set();
 | 
			
		||||
  }
 | 
			
		||||
  static from(browserType) {
 | 
			
		||||
    return browserType._object;
 | 
			
		||||
  }
 | 
			
		||||
  executablePath() {
 | 
			
		||||
    if (!this._initializer.executablePath)
 | 
			
		||||
      throw new Error("Browser is not supported on current platform");
 | 
			
		||||
    return this._initializer.executablePath;
 | 
			
		||||
  }
 | 
			
		||||
  name() {
 | 
			
		||||
    return this._initializer.name;
 | 
			
		||||
  }
 | 
			
		||||
  async launch(options = {}) {
 | 
			
		||||
    (0, import_assert.assert)(!options.userDataDir, "userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead");
 | 
			
		||||
    (0, import_assert.assert)(!options.port, "Cannot specify a port without launching as a server.");
 | 
			
		||||
    const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
 | 
			
		||||
    options = { ...this._playwright._defaultLaunchOptions, ...options };
 | 
			
		||||
    const launchOptions = {
 | 
			
		||||
      ...options,
 | 
			
		||||
      ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
 | 
			
		||||
      ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
 | 
			
		||||
      env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0,
 | 
			
		||||
      timeout: new import_timeoutSettings.TimeoutSettings(this._platform).launchTimeout(options)
 | 
			
		||||
    };
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const browser = import_browser.Browser.from((await this._channel.launch(launchOptions)).browser);
 | 
			
		||||
      browser._connectToBrowserType(this, options, logger);
 | 
			
		||||
      return browser;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async launchServer(options = {}) {
 | 
			
		||||
    if (!this._serverLauncher)
 | 
			
		||||
      throw new Error("Launching server is not supported");
 | 
			
		||||
    options = { ...this._playwright._defaultLaunchOptions, ...options };
 | 
			
		||||
    return await this._serverLauncher.launchServer(options);
 | 
			
		||||
  }
 | 
			
		||||
  async launchPersistentContext(userDataDir, options = {}) {
 | 
			
		||||
    const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
 | 
			
		||||
    (0, import_assert.assert)(!options.port, "Cannot specify a port without launching as a server.");
 | 
			
		||||
    options = this._playwright.selectors._withSelectorOptions({
 | 
			
		||||
      ...this._playwright._defaultLaunchOptions,
 | 
			
		||||
      ...this._playwright._defaultContextOptions,
 | 
			
		||||
      ...options
 | 
			
		||||
    });
 | 
			
		||||
    const contextParams = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
 | 
			
		||||
    const persistentParams = {
 | 
			
		||||
      ...contextParams,
 | 
			
		||||
      ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
 | 
			
		||||
      ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
 | 
			
		||||
      env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0,
 | 
			
		||||
      channel: options.channel,
 | 
			
		||||
      userDataDir: this._platform.path().isAbsolute(userDataDir) || !userDataDir ? userDataDir : this._platform.path().resolve(userDataDir),
 | 
			
		||||
      timeout: new import_timeoutSettings.TimeoutSettings(this._platform).launchTimeout(options)
 | 
			
		||||
    };
 | 
			
		||||
    const context = await this._wrapApiCall(async () => {
 | 
			
		||||
      const result = await this._channel.launchPersistentContext(persistentParams);
 | 
			
		||||
      const browser = import_browser.Browser.from(result.browser);
 | 
			
		||||
      browser._connectToBrowserType(this, options, logger);
 | 
			
		||||
      const context2 = import_browserContext.BrowserContext.from(result.context);
 | 
			
		||||
      await context2._initializeHarFromOptions(options.recordHar);
 | 
			
		||||
      return context2;
 | 
			
		||||
    });
 | 
			
		||||
    await this._instrumentation.runAfterCreateBrowserContext(context);
 | 
			
		||||
    return context;
 | 
			
		||||
  }
 | 
			
		||||
  async connect(optionsOrWsEndpoint, options) {
 | 
			
		||||
    if (typeof optionsOrWsEndpoint === "string")
 | 
			
		||||
      return await this._connect({ ...options, wsEndpoint: optionsOrWsEndpoint });
 | 
			
		||||
    (0, import_assert.assert)(optionsOrWsEndpoint.wsEndpoint, "options.wsEndpoint is required");
 | 
			
		||||
    return await this._connect(optionsOrWsEndpoint);
 | 
			
		||||
  }
 | 
			
		||||
  async _connect(params) {
 | 
			
		||||
    const logger = params.logger;
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const deadline = params.timeout ? (0, import_time.monotonicTime)() + params.timeout : 0;
 | 
			
		||||
      const headers = { "x-playwright-browser": this.name(), ...params.headers };
 | 
			
		||||
      const connectParams = {
 | 
			
		||||
        wsEndpoint: params.wsEndpoint,
 | 
			
		||||
        headers,
 | 
			
		||||
        exposeNetwork: params.exposeNetwork ?? params._exposeNetwork,
 | 
			
		||||
        slowMo: params.slowMo,
 | 
			
		||||
        timeout: params.timeout || 0
 | 
			
		||||
      };
 | 
			
		||||
      if (params.__testHookRedirectPortForwarding)
 | 
			
		||||
        connectParams.socksProxyRedirectPortForTest = params.__testHookRedirectPortForwarding;
 | 
			
		||||
      const connection = await (0, import_webSocket.connectOverWebSocket)(this._connection, connectParams);
 | 
			
		||||
      let browser;
 | 
			
		||||
      connection.on("close", () => {
 | 
			
		||||
        for (const context of browser?.contexts() || []) {
 | 
			
		||||
          for (const page of context.pages())
 | 
			
		||||
            page._onClose();
 | 
			
		||||
          context._onClose();
 | 
			
		||||
        }
 | 
			
		||||
        setTimeout(() => browser?._didClose(), 0);
 | 
			
		||||
      });
 | 
			
		||||
      const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
 | 
			
		||||
        if (params.__testHookBeforeCreateBrowser)
 | 
			
		||||
          await params.__testHookBeforeCreateBrowser();
 | 
			
		||||
        const playwright = await connection.initializePlaywright();
 | 
			
		||||
        if (!playwright._initializer.preLaunchedBrowser) {
 | 
			
		||||
          connection.close();
 | 
			
		||||
          throw new Error("Malformed endpoint. Did you use BrowserType.launchServer method?");
 | 
			
		||||
        }
 | 
			
		||||
        playwright.selectors = this._playwright.selectors;
 | 
			
		||||
        browser = import_browser.Browser.from(playwright._initializer.preLaunchedBrowser);
 | 
			
		||||
        browser._connectToBrowserType(this, {}, logger);
 | 
			
		||||
        browser._shouldCloseConnectionOnClose = true;
 | 
			
		||||
        browser.on(import_events.Events.Browser.Disconnected, () => connection.close());
 | 
			
		||||
        return browser;
 | 
			
		||||
      }, deadline);
 | 
			
		||||
      if (!result.timedOut) {
 | 
			
		||||
        return result.result;
 | 
			
		||||
      } else {
 | 
			
		||||
        connection.close();
 | 
			
		||||
        throw new Error(`Timeout ${params.timeout}ms exceeded`);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async connectOverCDP(endpointURLOrOptions, options) {
 | 
			
		||||
    if (typeof endpointURLOrOptions === "string")
 | 
			
		||||
      return await this._connectOverCDP(endpointURLOrOptions, options);
 | 
			
		||||
    const endpointURL = "endpointURL" in endpointURLOrOptions ? endpointURLOrOptions.endpointURL : endpointURLOrOptions.wsEndpoint;
 | 
			
		||||
    (0, import_assert.assert)(endpointURL, "Cannot connect over CDP without wsEndpoint.");
 | 
			
		||||
    return await this.connectOverCDP(endpointURL, endpointURLOrOptions);
 | 
			
		||||
  }
 | 
			
		||||
  async _connectOverCDP(endpointURL, params = {}) {
 | 
			
		||||
    if (this.name() !== "chromium")
 | 
			
		||||
      throw new Error("Connecting over CDP is only supported in Chromium.");
 | 
			
		||||
    const headers = params.headers ? (0, import_headers.headersObjectToArray)(params.headers) : void 0;
 | 
			
		||||
    const result = await this._channel.connectOverCDP({
 | 
			
		||||
      endpointURL,
 | 
			
		||||
      headers,
 | 
			
		||||
      slowMo: params.slowMo,
 | 
			
		||||
      timeout: new import_timeoutSettings.TimeoutSettings(this._platform).timeout(params)
 | 
			
		||||
    });
 | 
			
		||||
    const browser = import_browser.Browser.from(result.browser);
 | 
			
		||||
    browser._connectToBrowserType(this, {}, params.logger);
 | 
			
		||||
    if (result.defaultContext)
 | 
			
		||||
      await this._instrumentation.runAfterCreateBrowserContext(import_browserContext.BrowserContext.from(result.defaultContext));
 | 
			
		||||
    return browser;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  BrowserType
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										51
									
								
								node_modules/playwright-core/lib/client/cdpSession.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								node_modules/playwright-core/lib/client/cdpSession.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var cdpSession_exports = {};
 | 
			
		||||
__export(cdpSession_exports, {
 | 
			
		||||
  CDPSession: () => CDPSession
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(cdpSession_exports);
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
class CDPSession extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(cdpSession) {
 | 
			
		||||
    return cdpSession._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._channel.on("event", ({ method, params }) => {
 | 
			
		||||
      this.emit(method, params);
 | 
			
		||||
    });
 | 
			
		||||
    this.on = super.on;
 | 
			
		||||
    this.addListener = super.addListener;
 | 
			
		||||
    this.off = super.removeListener;
 | 
			
		||||
    this.removeListener = super.removeListener;
 | 
			
		||||
    this.once = super.once;
 | 
			
		||||
  }
 | 
			
		||||
  async send(method, params) {
 | 
			
		||||
    const result = await this._channel.send({ method, params });
 | 
			
		||||
    return result.result;
 | 
			
		||||
  }
 | 
			
		||||
  async detach() {
 | 
			
		||||
    return await this._channel.detach();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  CDPSession
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										201
									
								
								node_modules/playwright-core/lib/client/channelOwner.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								node_modules/playwright-core/lib/client/channelOwner.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var channelOwner_exports = {};
 | 
			
		||||
__export(channelOwner_exports, {
 | 
			
		||||
  ChannelOwner: () => ChannelOwner
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(channelOwner_exports);
 | 
			
		||||
var import_eventEmitter = require("./eventEmitter");
 | 
			
		||||
var import_validator = require("../protocol/validator");
 | 
			
		||||
var import_protocolMetainfo = require("../utils/isomorphic/protocolMetainfo");
 | 
			
		||||
var import_clientStackTrace = require("./clientStackTrace");
 | 
			
		||||
var import_stackTrace = require("../utils/isomorphic/stackTrace");
 | 
			
		||||
class ChannelOwner extends import_eventEmitter.EventEmitter {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    const connection = parent instanceof ChannelOwner ? parent._connection : parent;
 | 
			
		||||
    super(connection._platform);
 | 
			
		||||
    this._objects = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._eventToSubscriptionMapping = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._wasCollected = false;
 | 
			
		||||
    this.setMaxListeners(0);
 | 
			
		||||
    this._connection = connection;
 | 
			
		||||
    this._type = type;
 | 
			
		||||
    this._guid = guid;
 | 
			
		||||
    this._parent = parent instanceof ChannelOwner ? parent : void 0;
 | 
			
		||||
    this._instrumentation = this._connection._instrumentation;
 | 
			
		||||
    this._connection._objects.set(guid, this);
 | 
			
		||||
    if (this._parent) {
 | 
			
		||||
      this._parent._objects.set(guid, this);
 | 
			
		||||
      this._logger = this._parent._logger;
 | 
			
		||||
    }
 | 
			
		||||
    this._channel = this._createChannel(new import_eventEmitter.EventEmitter(connection._platform));
 | 
			
		||||
    this._initializer = initializer;
 | 
			
		||||
  }
 | 
			
		||||
  _setEventToSubscriptionMapping(mapping) {
 | 
			
		||||
    this._eventToSubscriptionMapping = mapping;
 | 
			
		||||
  }
 | 
			
		||||
  _updateSubscription(event, enabled) {
 | 
			
		||||
    const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
 | 
			
		||||
    if (protocolEvent)
 | 
			
		||||
      this._channel.updateSubscription({ event: protocolEvent, enabled }).catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
  on(event, listener) {
 | 
			
		||||
    if (!this.listenerCount(event))
 | 
			
		||||
      this._updateSubscription(event, true);
 | 
			
		||||
    super.on(event, listener);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  addListener(event, listener) {
 | 
			
		||||
    if (!this.listenerCount(event))
 | 
			
		||||
      this._updateSubscription(event, true);
 | 
			
		||||
    super.addListener(event, listener);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  prependListener(event, listener) {
 | 
			
		||||
    if (!this.listenerCount(event))
 | 
			
		||||
      this._updateSubscription(event, true);
 | 
			
		||||
    super.prependListener(event, listener);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  off(event, listener) {
 | 
			
		||||
    super.off(event, listener);
 | 
			
		||||
    if (!this.listenerCount(event))
 | 
			
		||||
      this._updateSubscription(event, false);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  removeListener(event, listener) {
 | 
			
		||||
    super.removeListener(event, listener);
 | 
			
		||||
    if (!this.listenerCount(event))
 | 
			
		||||
      this._updateSubscription(event, false);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  _adopt(child) {
 | 
			
		||||
    child._parent._objects.delete(child._guid);
 | 
			
		||||
    this._objects.set(child._guid, child);
 | 
			
		||||
    child._parent = this;
 | 
			
		||||
  }
 | 
			
		||||
  _dispose(reason) {
 | 
			
		||||
    if (this._parent)
 | 
			
		||||
      this._parent._objects.delete(this._guid);
 | 
			
		||||
    this._connection._objects.delete(this._guid);
 | 
			
		||||
    this._wasCollected = reason === "gc";
 | 
			
		||||
    for (const object of [...this._objects.values()])
 | 
			
		||||
      object._dispose(reason);
 | 
			
		||||
    this._objects.clear();
 | 
			
		||||
  }
 | 
			
		||||
  _debugScopeState() {
 | 
			
		||||
    return {
 | 
			
		||||
      _guid: this._guid,
 | 
			
		||||
      objects: Array.from(this._objects.values()).map((o) => o._debugScopeState())
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  _validatorToWireContext() {
 | 
			
		||||
    return {
 | 
			
		||||
      tChannelImpl: tChannelImplToWire,
 | 
			
		||||
      binary: this._connection.rawBuffers() ? "buffer" : "toBase64",
 | 
			
		||||
      isUnderTest: () => this._platform.isUnderTest()
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  _createChannel(base) {
 | 
			
		||||
    const channel = new Proxy(base, {
 | 
			
		||||
      get: (obj, prop) => {
 | 
			
		||||
        if (typeof prop === "string") {
 | 
			
		||||
          const validator = (0, import_validator.maybeFindValidator)(this._type, prop, "Params");
 | 
			
		||||
          const { internal } = import_protocolMetainfo.methodMetainfo.get(this._type + "." + prop) || {};
 | 
			
		||||
          if (validator) {
 | 
			
		||||
            return async (params) => {
 | 
			
		||||
              return await this._wrapApiCall(async (apiZone) => {
 | 
			
		||||
                const validatedParams = validator(params, "", this._validatorToWireContext());
 | 
			
		||||
                if (!apiZone.internal && !apiZone.reported) {
 | 
			
		||||
                  apiZone.reported = true;
 | 
			
		||||
                  this._instrumentation.onApiCallBegin(apiZone, { type: this._type, method: prop, params });
 | 
			
		||||
                  logApiCall(this._platform, this._logger, `=> ${apiZone.apiName} started`);
 | 
			
		||||
                  return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone);
 | 
			
		||||
                }
 | 
			
		||||
                return await this._connection.sendMessageToServer(this, prop, validatedParams, { internal: true });
 | 
			
		||||
              }, { internal });
 | 
			
		||||
            };
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return obj[prop];
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    channel._object = this;
 | 
			
		||||
    return channel;
 | 
			
		||||
  }
 | 
			
		||||
  async _wrapApiCall(func, options) {
 | 
			
		||||
    const logger = this._logger;
 | 
			
		||||
    const existingApiZone = this._platform.zones.current().data();
 | 
			
		||||
    if (existingApiZone)
 | 
			
		||||
      return await func(existingApiZone);
 | 
			
		||||
    const stackTrace = (0, import_clientStackTrace.captureLibraryStackTrace)(this._platform);
 | 
			
		||||
    const apiZone = { title: options?.title, apiName: stackTrace.apiName, frames: stackTrace.frames, internal: options?.internal ?? false, reported: false, userData: void 0, stepId: void 0 };
 | 
			
		||||
    try {
 | 
			
		||||
      const result = await this._platform.zones.current().push(apiZone).run(async () => await func(apiZone));
 | 
			
		||||
      if (!options?.internal) {
 | 
			
		||||
        logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`);
 | 
			
		||||
        this._instrumentation.onApiCallEnd(apiZone);
 | 
			
		||||
      }
 | 
			
		||||
      return result;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      const innerError = (this._platform.showInternalStackFrames() || this._platform.isUnderTest()) && e.stack ? "\n<inner error>\n" + e.stack : "";
 | 
			
		||||
      if (apiZone.apiName && !apiZone.apiName.includes("<anonymous>"))
 | 
			
		||||
        e.message = apiZone.apiName + ": " + e.message;
 | 
			
		||||
      const stackFrames = "\n" + (0, import_stackTrace.stringifyStackFrames)(stackTrace.frames).join("\n") + innerError;
 | 
			
		||||
      if (stackFrames.trim())
 | 
			
		||||
        e.stack = e.message + stackFrames;
 | 
			
		||||
      else
 | 
			
		||||
        e.stack = "";
 | 
			
		||||
      if (!options?.internal) {
 | 
			
		||||
        const recoveryHandlers = [];
 | 
			
		||||
        apiZone.error = e;
 | 
			
		||||
        this._instrumentation.onApiCallRecovery(apiZone, e, recoveryHandlers);
 | 
			
		||||
        for (const handler of recoveryHandlers) {
 | 
			
		||||
          const recoverResult = await handler();
 | 
			
		||||
          if (recoverResult.status === "recovered")
 | 
			
		||||
            return recoverResult.value;
 | 
			
		||||
        }
 | 
			
		||||
        logApiCall(this._platform, logger, `<= ${apiZone.apiName} failed`);
 | 
			
		||||
        this._instrumentation.onApiCallEnd(apiZone);
 | 
			
		||||
      }
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  toJSON() {
 | 
			
		||||
    return {
 | 
			
		||||
      _type: this._type,
 | 
			
		||||
      _guid: this._guid
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function logApiCall(platform, logger, message) {
 | 
			
		||||
  if (logger && logger.isEnabled("api", "info"))
 | 
			
		||||
    logger.log("api", "info", message, [], { color: "cyan" });
 | 
			
		||||
  platform.log("api", message);
 | 
			
		||||
}
 | 
			
		||||
function tChannelImplToWire(names, arg, path, context) {
 | 
			
		||||
  if (arg._object instanceof ChannelOwner && (names === "*" || names.includes(arg._object._type)))
 | 
			
		||||
    return { guid: arg._object._guid };
 | 
			
		||||
  throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  ChannelOwner
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										64
									
								
								node_modules/playwright-core/lib/client/clientHelper.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								node_modules/playwright-core/lib/client/clientHelper.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var clientHelper_exports = {};
 | 
			
		||||
__export(clientHelper_exports, {
 | 
			
		||||
  addSourceUrlToScript: () => addSourceUrlToScript,
 | 
			
		||||
  envObjectToArray: () => envObjectToArray,
 | 
			
		||||
  evaluationScript: () => evaluationScript
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(clientHelper_exports);
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
function envObjectToArray(env) {
 | 
			
		||||
  const result = [];
 | 
			
		||||
  for (const name in env) {
 | 
			
		||||
    if (!Object.is(env[name], void 0))
 | 
			
		||||
      result.push({ name, value: String(env[name]) });
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
async function evaluationScript(platform, fun, arg, addSourceUrl = true) {
 | 
			
		||||
  if (typeof fun === "function") {
 | 
			
		||||
    const source = fun.toString();
 | 
			
		||||
    const argString = Object.is(arg, void 0) ? "undefined" : JSON.stringify(arg);
 | 
			
		||||
    return `(${source})(${argString})`;
 | 
			
		||||
  }
 | 
			
		||||
  if (arg !== void 0)
 | 
			
		||||
    throw new Error("Cannot evaluate a string with arguments");
 | 
			
		||||
  if ((0, import_rtti.isString)(fun))
 | 
			
		||||
    return fun;
 | 
			
		||||
  if (fun.content !== void 0)
 | 
			
		||||
    return fun.content;
 | 
			
		||||
  if (fun.path !== void 0) {
 | 
			
		||||
    let source = await platform.fs().promises.readFile(fun.path, "utf8");
 | 
			
		||||
    if (addSourceUrl)
 | 
			
		||||
      source = addSourceUrlToScript(source, fun.path);
 | 
			
		||||
    return source;
 | 
			
		||||
  }
 | 
			
		||||
  throw new Error("Either path or content property must be present");
 | 
			
		||||
}
 | 
			
		||||
function addSourceUrlToScript(source, path) {
 | 
			
		||||
  return `${source}
 | 
			
		||||
//# sourceURL=${path.replace(/\n/g, "")}`;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  addSourceUrlToScript,
 | 
			
		||||
  envObjectToArray,
 | 
			
		||||
  evaluationScript
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										55
									
								
								node_modules/playwright-core/lib/client/clientInstrumentation.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								node_modules/playwright-core/lib/client/clientInstrumentation.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var clientInstrumentation_exports = {};
 | 
			
		||||
__export(clientInstrumentation_exports, {
 | 
			
		||||
  createInstrumentation: () => createInstrumentation
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(clientInstrumentation_exports);
 | 
			
		||||
function createInstrumentation() {
 | 
			
		||||
  const listeners = [];
 | 
			
		||||
  return new Proxy({}, {
 | 
			
		||||
    get: (obj, prop) => {
 | 
			
		||||
      if (typeof prop !== "string")
 | 
			
		||||
        return obj[prop];
 | 
			
		||||
      if (prop === "addListener")
 | 
			
		||||
        return (listener) => listeners.push(listener);
 | 
			
		||||
      if (prop === "removeListener")
 | 
			
		||||
        return (listener) => listeners.splice(listeners.indexOf(listener), 1);
 | 
			
		||||
      if (prop === "removeAllListeners")
 | 
			
		||||
        return () => listeners.splice(0, listeners.length);
 | 
			
		||||
      if (prop.startsWith("run")) {
 | 
			
		||||
        return async (...params) => {
 | 
			
		||||
          for (const listener of listeners)
 | 
			
		||||
            await listener[prop]?.(...params);
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      if (prop.startsWith("on")) {
 | 
			
		||||
        return (...params) => {
 | 
			
		||||
          for (const listener of listeners)
 | 
			
		||||
            listener[prop]?.(...params);
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      return obj[prop];
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  createInstrumentation
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										69
									
								
								node_modules/playwright-core/lib/client/clientStackTrace.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								node_modules/playwright-core/lib/client/clientStackTrace.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var clientStackTrace_exports = {};
 | 
			
		||||
__export(clientStackTrace_exports, {
 | 
			
		||||
  captureLibraryStackTrace: () => captureLibraryStackTrace
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(clientStackTrace_exports);
 | 
			
		||||
var import_stackTrace = require("../utils/isomorphic/stackTrace");
 | 
			
		||||
function captureLibraryStackTrace(platform) {
 | 
			
		||||
  const stack = (0, import_stackTrace.captureRawStack)();
 | 
			
		||||
  let parsedFrames = stack.map((line) => {
 | 
			
		||||
    const frame = (0, import_stackTrace.parseStackFrame)(line, platform.pathSeparator, platform.showInternalStackFrames());
 | 
			
		||||
    if (!frame || !frame.file)
 | 
			
		||||
      return null;
 | 
			
		||||
    const isPlaywrightLibrary = !!platform.coreDir && frame.file.startsWith(platform.coreDir);
 | 
			
		||||
    const parsed = {
 | 
			
		||||
      frame,
 | 
			
		||||
      frameText: line,
 | 
			
		||||
      isPlaywrightLibrary
 | 
			
		||||
    };
 | 
			
		||||
    return parsed;
 | 
			
		||||
  }).filter(Boolean);
 | 
			
		||||
  let apiName = "";
 | 
			
		||||
  for (let i = 0; i < parsedFrames.length - 1; i++) {
 | 
			
		||||
    const parsedFrame = parsedFrames[i];
 | 
			
		||||
    if (parsedFrame.isPlaywrightLibrary && !parsedFrames[i + 1].isPlaywrightLibrary) {
 | 
			
		||||
      apiName = apiName || normalizeAPIName(parsedFrame.frame.function);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  function normalizeAPIName(name) {
 | 
			
		||||
    if (!name)
 | 
			
		||||
      return "";
 | 
			
		||||
    const match = name.match(/(API|JS|CDP|[A-Z])(.*)/);
 | 
			
		||||
    if (!match)
 | 
			
		||||
      return name;
 | 
			
		||||
    return match[1].toLowerCase() + match[2];
 | 
			
		||||
  }
 | 
			
		||||
  const filterPrefixes = platform.boxedStackPrefixes();
 | 
			
		||||
  parsedFrames = parsedFrames.filter((f) => {
 | 
			
		||||
    if (filterPrefixes.some((prefix) => f.frame.file.startsWith(prefix)))
 | 
			
		||||
      return false;
 | 
			
		||||
    return true;
 | 
			
		||||
  });
 | 
			
		||||
  return {
 | 
			
		||||
    frames: parsedFrames.map((p) => p.frame),
 | 
			
		||||
    apiName
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  captureLibraryStackTrace
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										68
									
								
								node_modules/playwright-core/lib/client/clock.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								node_modules/playwright-core/lib/client/clock.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var clock_exports = {};
 | 
			
		||||
__export(clock_exports, {
 | 
			
		||||
  Clock: () => Clock
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(clock_exports);
 | 
			
		||||
class Clock {
 | 
			
		||||
  constructor(browserContext) {
 | 
			
		||||
    this._browserContext = browserContext;
 | 
			
		||||
  }
 | 
			
		||||
  async install(options = {}) {
 | 
			
		||||
    await this._browserContext._channel.clockInstall(options.time !== void 0 ? parseTime(options.time) : {});
 | 
			
		||||
  }
 | 
			
		||||
  async fastForward(ticks) {
 | 
			
		||||
    await this._browserContext._channel.clockFastForward(parseTicks(ticks));
 | 
			
		||||
  }
 | 
			
		||||
  async pauseAt(time) {
 | 
			
		||||
    await this._browserContext._channel.clockPauseAt(parseTime(time));
 | 
			
		||||
  }
 | 
			
		||||
  async resume() {
 | 
			
		||||
    await this._browserContext._channel.clockResume({});
 | 
			
		||||
  }
 | 
			
		||||
  async runFor(ticks) {
 | 
			
		||||
    await this._browserContext._channel.clockRunFor(parseTicks(ticks));
 | 
			
		||||
  }
 | 
			
		||||
  async setFixedTime(time) {
 | 
			
		||||
    await this._browserContext._channel.clockSetFixedTime(parseTime(time));
 | 
			
		||||
  }
 | 
			
		||||
  async setSystemTime(time) {
 | 
			
		||||
    await this._browserContext._channel.clockSetSystemTime(parseTime(time));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function parseTime(time) {
 | 
			
		||||
  if (typeof time === "number")
 | 
			
		||||
    return { timeNumber: time };
 | 
			
		||||
  if (typeof time === "string")
 | 
			
		||||
    return { timeString: time };
 | 
			
		||||
  if (!isFinite(time.getTime()))
 | 
			
		||||
    throw new Error(`Invalid date: ${time}`);
 | 
			
		||||
  return { timeNumber: time.getTime() };
 | 
			
		||||
}
 | 
			
		||||
function parseTicks(ticks) {
 | 
			
		||||
  return {
 | 
			
		||||
    ticksNumber: typeof ticks === "number" ? ticks : void 0,
 | 
			
		||||
    ticksString: typeof ticks === "string" ? ticks : void 0
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Clock
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										314
									
								
								node_modules/playwright-core/lib/client/connection.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								node_modules/playwright-core/lib/client/connection.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,314 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var connection_exports = {};
 | 
			
		||||
__export(connection_exports, {
 | 
			
		||||
  Connection: () => Connection
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(connection_exports);
 | 
			
		||||
var import_eventEmitter = require("./eventEmitter");
 | 
			
		||||
var import_android = require("./android");
 | 
			
		||||
var import_artifact = require("./artifact");
 | 
			
		||||
var import_browser = require("./browser");
 | 
			
		||||
var import_browserContext = require("./browserContext");
 | 
			
		||||
var import_browserType = require("./browserType");
 | 
			
		||||
var import_cdpSession = require("./cdpSession");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_clientInstrumentation = require("./clientInstrumentation");
 | 
			
		||||
var import_dialog = require("./dialog");
 | 
			
		||||
var import_electron = require("./electron");
 | 
			
		||||
var import_elementHandle = require("./elementHandle");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_fetch = require("./fetch");
 | 
			
		||||
var import_frame = require("./frame");
 | 
			
		||||
var import_jsHandle = require("./jsHandle");
 | 
			
		||||
var import_jsonPipe = require("./jsonPipe");
 | 
			
		||||
var import_localUtils = require("./localUtils");
 | 
			
		||||
var import_network = require("./network");
 | 
			
		||||
var import_page = require("./page");
 | 
			
		||||
var import_playwright = require("./playwright");
 | 
			
		||||
var import_stream = require("./stream");
 | 
			
		||||
var import_tracing = require("./tracing");
 | 
			
		||||
var import_worker = require("./worker");
 | 
			
		||||
var import_writableStream = require("./writableStream");
 | 
			
		||||
var import_validator = require("../protocol/validator");
 | 
			
		||||
var import_stackTrace = require("../utils/isomorphic/stackTrace");
 | 
			
		||||
class Root extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(connection) {
 | 
			
		||||
    super(connection, "Root", "", {});
 | 
			
		||||
  }
 | 
			
		||||
  async initialize() {
 | 
			
		||||
    return import_playwright.Playwright.from((await this._channel.initialize({
 | 
			
		||||
      sdkLanguage: "javascript"
 | 
			
		||||
    })).playwright);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class DummyChannelOwner extends import_channelOwner.ChannelOwner {
 | 
			
		||||
}
 | 
			
		||||
class Connection extends import_eventEmitter.EventEmitter {
 | 
			
		||||
  constructor(platform, localUtils, instrumentation, headers = []) {
 | 
			
		||||
    super(platform);
 | 
			
		||||
    this._objects = /* @__PURE__ */ new Map();
 | 
			
		||||
    this.onmessage = (message) => {
 | 
			
		||||
    };
 | 
			
		||||
    this._lastId = 0;
 | 
			
		||||
    this._callbacks = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._isRemote = false;
 | 
			
		||||
    this._rawBuffers = false;
 | 
			
		||||
    this._tracingCount = 0;
 | 
			
		||||
    this._instrumentation = instrumentation || (0, import_clientInstrumentation.createInstrumentation)();
 | 
			
		||||
    this._localUtils = localUtils;
 | 
			
		||||
    this._rootObject = new Root(this);
 | 
			
		||||
    this.headers = headers;
 | 
			
		||||
  }
 | 
			
		||||
  markAsRemote() {
 | 
			
		||||
    this._isRemote = true;
 | 
			
		||||
  }
 | 
			
		||||
  isRemote() {
 | 
			
		||||
    return this._isRemote;
 | 
			
		||||
  }
 | 
			
		||||
  useRawBuffers() {
 | 
			
		||||
    this._rawBuffers = true;
 | 
			
		||||
  }
 | 
			
		||||
  rawBuffers() {
 | 
			
		||||
    return this._rawBuffers;
 | 
			
		||||
  }
 | 
			
		||||
  localUtils() {
 | 
			
		||||
    return this._localUtils;
 | 
			
		||||
  }
 | 
			
		||||
  async initializePlaywright() {
 | 
			
		||||
    return await this._rootObject.initialize();
 | 
			
		||||
  }
 | 
			
		||||
  getObjectWithKnownName(guid) {
 | 
			
		||||
    return this._objects.get(guid);
 | 
			
		||||
  }
 | 
			
		||||
  setIsTracing(isTracing) {
 | 
			
		||||
    if (isTracing)
 | 
			
		||||
      this._tracingCount++;
 | 
			
		||||
    else
 | 
			
		||||
      this._tracingCount--;
 | 
			
		||||
  }
 | 
			
		||||
  async sendMessageToServer(object, method, params, options) {
 | 
			
		||||
    if (this._closedError)
 | 
			
		||||
      throw this._closedError;
 | 
			
		||||
    if (object._wasCollected)
 | 
			
		||||
      throw new Error("The object has been collected to prevent unbounded heap growth.");
 | 
			
		||||
    const guid = object._guid;
 | 
			
		||||
    const type = object._type;
 | 
			
		||||
    const id = ++this._lastId;
 | 
			
		||||
    const message = { id, guid, method, params };
 | 
			
		||||
    if (this._platform.isLogEnabled("channel")) {
 | 
			
		||||
      this._platform.log("channel", "SEND> " + JSON.stringify(message));
 | 
			
		||||
    }
 | 
			
		||||
    const location = options.frames?.[0] ? { file: options.frames[0].file, line: options.frames[0].line, column: options.frames[0].column } : void 0;
 | 
			
		||||
    const metadata = { title: options.title, location, internal: options.internal, stepId: options.stepId };
 | 
			
		||||
    if (this._tracingCount && options.frames && type !== "LocalUtils")
 | 
			
		||||
      this._localUtils?.addStackToTracingNoReply({ callData: { stack: options.frames ?? [], id } }).catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
    this._platform.zones.empty.run(() => this.onmessage({ ...message, metadata }));
 | 
			
		||||
    return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, title: options.title, type, method }));
 | 
			
		||||
  }
 | 
			
		||||
  _validatorFromWireContext() {
 | 
			
		||||
    return {
 | 
			
		||||
      tChannelImpl: this._tChannelImplFromWire.bind(this),
 | 
			
		||||
      binary: this._rawBuffers ? "buffer" : "fromBase64",
 | 
			
		||||
      isUnderTest: () => this._platform.isUnderTest()
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  dispatch(message) {
 | 
			
		||||
    if (this._closedError)
 | 
			
		||||
      return;
 | 
			
		||||
    const { id, guid, method, params, result, error, log } = message;
 | 
			
		||||
    if (id) {
 | 
			
		||||
      if (this._platform.isLogEnabled("channel"))
 | 
			
		||||
        this._platform.log("channel", "<RECV " + JSON.stringify(message));
 | 
			
		||||
      const callback = this._callbacks.get(id);
 | 
			
		||||
      if (!callback)
 | 
			
		||||
        throw new Error(`Cannot find command to respond: ${id}`);
 | 
			
		||||
      this._callbacks.delete(id);
 | 
			
		||||
      if (error && !result) {
 | 
			
		||||
        const parsedError = (0, import_errors.parseError)(error);
 | 
			
		||||
        (0, import_stackTrace.rewriteErrorMessage)(parsedError, parsedError.message + formatCallLog(this._platform, log));
 | 
			
		||||
        callback.reject(parsedError);
 | 
			
		||||
      } else {
 | 
			
		||||
        const validator2 = (0, import_validator.findValidator)(callback.type, callback.method, "Result");
 | 
			
		||||
        callback.resolve(validator2(result, "", this._validatorFromWireContext()));
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (this._platform.isLogEnabled("channel"))
 | 
			
		||||
      this._platform.log("channel", "<EVENT " + JSON.stringify(message));
 | 
			
		||||
    if (method === "__create__") {
 | 
			
		||||
      this._createRemoteObject(guid, params.type, params.guid, params.initializer);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const object = this._objects.get(guid);
 | 
			
		||||
    if (!object)
 | 
			
		||||
      throw new Error(`Cannot find object to "${method}": ${guid}`);
 | 
			
		||||
    if (method === "__adopt__") {
 | 
			
		||||
      const child = this._objects.get(params.guid);
 | 
			
		||||
      if (!child)
 | 
			
		||||
        throw new Error(`Unknown new child: ${params.guid}`);
 | 
			
		||||
      object._adopt(child);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (method === "__dispose__") {
 | 
			
		||||
      object._dispose(params.reason);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const validator = (0, import_validator.findValidator)(object._type, method, "Event");
 | 
			
		||||
    object._channel.emit(method, validator(params, "", this._validatorFromWireContext()));
 | 
			
		||||
  }
 | 
			
		||||
  close(cause) {
 | 
			
		||||
    if (this._closedError)
 | 
			
		||||
      return;
 | 
			
		||||
    this._closedError = new import_errors.TargetClosedError(cause);
 | 
			
		||||
    for (const callback of this._callbacks.values())
 | 
			
		||||
      callback.reject(this._closedError);
 | 
			
		||||
    this._callbacks.clear();
 | 
			
		||||
    this.emit("close");
 | 
			
		||||
  }
 | 
			
		||||
  _tChannelImplFromWire(names, arg, path, context) {
 | 
			
		||||
    if (arg && typeof arg === "object" && typeof arg.guid === "string") {
 | 
			
		||||
      const object = this._objects.get(arg.guid);
 | 
			
		||||
      if (!object)
 | 
			
		||||
        throw new Error(`Object with guid ${arg.guid} was not bound in the connection`);
 | 
			
		||||
      if (names !== "*" && !names.includes(object._type))
 | 
			
		||||
        throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
 | 
			
		||||
      return object._channel;
 | 
			
		||||
    }
 | 
			
		||||
    throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
 | 
			
		||||
  }
 | 
			
		||||
  _createRemoteObject(parentGuid, type, guid, initializer) {
 | 
			
		||||
    const parent = this._objects.get(parentGuid);
 | 
			
		||||
    if (!parent)
 | 
			
		||||
      throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
 | 
			
		||||
    let result;
 | 
			
		||||
    const validator = (0, import_validator.findValidator)(type, "", "Initializer");
 | 
			
		||||
    initializer = validator(initializer, "", this._validatorFromWireContext());
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case "Android":
 | 
			
		||||
        result = new import_android.Android(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "AndroidSocket":
 | 
			
		||||
        result = new import_android.AndroidSocket(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "AndroidDevice":
 | 
			
		||||
        result = new import_android.AndroidDevice(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "APIRequestContext":
 | 
			
		||||
        result = new import_fetch.APIRequestContext(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Artifact":
 | 
			
		||||
        result = new import_artifact.Artifact(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "BindingCall":
 | 
			
		||||
        result = new import_page.BindingCall(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Browser":
 | 
			
		||||
        result = new import_browser.Browser(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "BrowserContext":
 | 
			
		||||
        result = new import_browserContext.BrowserContext(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "BrowserType":
 | 
			
		||||
        result = new import_browserType.BrowserType(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "CDPSession":
 | 
			
		||||
        result = new import_cdpSession.CDPSession(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Dialog":
 | 
			
		||||
        result = new import_dialog.Dialog(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Electron":
 | 
			
		||||
        result = new import_electron.Electron(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "ElectronApplication":
 | 
			
		||||
        result = new import_electron.ElectronApplication(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "ElementHandle":
 | 
			
		||||
        result = new import_elementHandle.ElementHandle(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Frame":
 | 
			
		||||
        result = new import_frame.Frame(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "JSHandle":
 | 
			
		||||
        result = new import_jsHandle.JSHandle(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "JsonPipe":
 | 
			
		||||
        result = new import_jsonPipe.JsonPipe(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "LocalUtils":
 | 
			
		||||
        result = new import_localUtils.LocalUtils(parent, type, guid, initializer);
 | 
			
		||||
        if (!this._localUtils)
 | 
			
		||||
          this._localUtils = result;
 | 
			
		||||
        break;
 | 
			
		||||
      case "Page":
 | 
			
		||||
        result = new import_page.Page(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Playwright":
 | 
			
		||||
        result = new import_playwright.Playwright(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Request":
 | 
			
		||||
        result = new import_network.Request(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Response":
 | 
			
		||||
        result = new import_network.Response(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Route":
 | 
			
		||||
        result = new import_network.Route(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Stream":
 | 
			
		||||
        result = new import_stream.Stream(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "SocksSupport":
 | 
			
		||||
        result = new DummyChannelOwner(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Tracing":
 | 
			
		||||
        result = new import_tracing.Tracing(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "WebSocket":
 | 
			
		||||
        result = new import_network.WebSocket(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "WebSocketRoute":
 | 
			
		||||
        result = new import_network.WebSocketRoute(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "Worker":
 | 
			
		||||
        result = new import_worker.Worker(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case "WritableStream":
 | 
			
		||||
        result = new import_writableStream.WritableStream(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        throw new Error("Missing type " + type);
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function formatCallLog(platform, log) {
 | 
			
		||||
  if (!log || !log.some((l) => !!l))
 | 
			
		||||
    return "";
 | 
			
		||||
  return `
 | 
			
		||||
Call log:
 | 
			
		||||
${platform.colors.dim(log.join("\n"))}
 | 
			
		||||
`;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Connection
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										55
									
								
								node_modules/playwright-core/lib/client/consoleMessage.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								node_modules/playwright-core/lib/client/consoleMessage.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var consoleMessage_exports = {};
 | 
			
		||||
__export(consoleMessage_exports, {
 | 
			
		||||
  ConsoleMessage: () => ConsoleMessage
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(consoleMessage_exports);
 | 
			
		||||
var import_jsHandle = require("./jsHandle");
 | 
			
		||||
var import_page = require("./page");
 | 
			
		||||
class ConsoleMessage {
 | 
			
		||||
  constructor(platform, event) {
 | 
			
		||||
    this._page = "page" in event && event.page ? import_page.Page.from(event.page) : null;
 | 
			
		||||
    this._event = event;
 | 
			
		||||
    if (platform.inspectCustom)
 | 
			
		||||
      this[platform.inspectCustom] = () => this._inspect();
 | 
			
		||||
  }
 | 
			
		||||
  page() {
 | 
			
		||||
    return this._page;
 | 
			
		||||
  }
 | 
			
		||||
  type() {
 | 
			
		||||
    return this._event.type;
 | 
			
		||||
  }
 | 
			
		||||
  text() {
 | 
			
		||||
    return this._event.text;
 | 
			
		||||
  }
 | 
			
		||||
  args() {
 | 
			
		||||
    return this._event.args.map(import_jsHandle.JSHandle.from);
 | 
			
		||||
  }
 | 
			
		||||
  location() {
 | 
			
		||||
    return this._event.location;
 | 
			
		||||
  }
 | 
			
		||||
  _inspect() {
 | 
			
		||||
    return this.text();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  ConsoleMessage
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										44
									
								
								node_modules/playwright-core/lib/client/coverage.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								node_modules/playwright-core/lib/client/coverage.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var coverage_exports = {};
 | 
			
		||||
__export(coverage_exports, {
 | 
			
		||||
  Coverage: () => Coverage
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(coverage_exports);
 | 
			
		||||
class Coverage {
 | 
			
		||||
  constructor(channel) {
 | 
			
		||||
    this._channel = channel;
 | 
			
		||||
  }
 | 
			
		||||
  async startJSCoverage(options = {}) {
 | 
			
		||||
    await this._channel.startJSCoverage(options);
 | 
			
		||||
  }
 | 
			
		||||
  async stopJSCoverage() {
 | 
			
		||||
    return (await this._channel.stopJSCoverage()).entries;
 | 
			
		||||
  }
 | 
			
		||||
  async startCSSCoverage(options = {}) {
 | 
			
		||||
    await this._channel.startCSSCoverage(options);
 | 
			
		||||
  }
 | 
			
		||||
  async stopCSSCoverage() {
 | 
			
		||||
    return (await this._channel.stopCSSCoverage()).entries;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Coverage
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										56
									
								
								node_modules/playwright-core/lib/client/dialog.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								node_modules/playwright-core/lib/client/dialog.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var dialog_exports = {};
 | 
			
		||||
__export(dialog_exports, {
 | 
			
		||||
  Dialog: () => Dialog
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(dialog_exports);
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_page = require("./page");
 | 
			
		||||
class Dialog extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(dialog) {
 | 
			
		||||
    return dialog._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._page = import_page.Page.fromNullable(initializer.page);
 | 
			
		||||
  }
 | 
			
		||||
  page() {
 | 
			
		||||
    return this._page;
 | 
			
		||||
  }
 | 
			
		||||
  type() {
 | 
			
		||||
    return this._initializer.type;
 | 
			
		||||
  }
 | 
			
		||||
  message() {
 | 
			
		||||
    return this._initializer.message;
 | 
			
		||||
  }
 | 
			
		||||
  defaultValue() {
 | 
			
		||||
    return this._initializer.defaultValue;
 | 
			
		||||
  }
 | 
			
		||||
  async accept(promptText) {
 | 
			
		||||
    await this._channel.accept({ promptText });
 | 
			
		||||
  }
 | 
			
		||||
  async dismiss() {
 | 
			
		||||
    await this._channel.dismiss();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Dialog
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										62
									
								
								node_modules/playwright-core/lib/client/download.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								node_modules/playwright-core/lib/client/download.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var download_exports = {};
 | 
			
		||||
__export(download_exports, {
 | 
			
		||||
  Download: () => Download
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(download_exports);
 | 
			
		||||
class Download {
 | 
			
		||||
  constructor(page, url, suggestedFilename, artifact) {
 | 
			
		||||
    this._page = page;
 | 
			
		||||
    this._url = url;
 | 
			
		||||
    this._suggestedFilename = suggestedFilename;
 | 
			
		||||
    this._artifact = artifact;
 | 
			
		||||
  }
 | 
			
		||||
  page() {
 | 
			
		||||
    return this._page;
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._url;
 | 
			
		||||
  }
 | 
			
		||||
  suggestedFilename() {
 | 
			
		||||
    return this._suggestedFilename;
 | 
			
		||||
  }
 | 
			
		||||
  async path() {
 | 
			
		||||
    return await this._artifact.pathAfterFinished();
 | 
			
		||||
  }
 | 
			
		||||
  async saveAs(path) {
 | 
			
		||||
    return await this._artifact.saveAs(path);
 | 
			
		||||
  }
 | 
			
		||||
  async failure() {
 | 
			
		||||
    return await this._artifact.failure();
 | 
			
		||||
  }
 | 
			
		||||
  async createReadStream() {
 | 
			
		||||
    return await this._artifact.createReadStream();
 | 
			
		||||
  }
 | 
			
		||||
  async cancel() {
 | 
			
		||||
    return await this._artifact.cancel();
 | 
			
		||||
  }
 | 
			
		||||
  async delete() {
 | 
			
		||||
    return await this._artifact.delete();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Download
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										138
									
								
								node_modules/playwright-core/lib/client/electron.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								node_modules/playwright-core/lib/client/electron.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var electron_exports = {};
 | 
			
		||||
__export(electron_exports, {
 | 
			
		||||
  Electron: () => Electron,
 | 
			
		||||
  ElectronApplication: () => ElectronApplication
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(electron_exports);
 | 
			
		||||
var import_browserContext = require("./browserContext");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_clientHelper = require("./clientHelper");
 | 
			
		||||
var import_consoleMessage = require("./consoleMessage");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_jsHandle = require("./jsHandle");
 | 
			
		||||
var import_waiter = require("./waiter");
 | 
			
		||||
var import_timeoutSettings = require("./timeoutSettings");
 | 
			
		||||
class Electron extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(electron) {
 | 
			
		||||
    return electron._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
  }
 | 
			
		||||
  async launch(options = {}) {
 | 
			
		||||
    options = this._playwright.selectors._withSelectorOptions(options);
 | 
			
		||||
    const params = {
 | 
			
		||||
      ...await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options),
 | 
			
		||||
      env: (0, import_clientHelper.envObjectToArray)(options.env ? options.env : this._platform.env),
 | 
			
		||||
      tracesDir: options.tracesDir,
 | 
			
		||||
      timeout: new import_timeoutSettings.TimeoutSettings(this._platform).launchTimeout(options)
 | 
			
		||||
    };
 | 
			
		||||
    const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
 | 
			
		||||
    this._playwright.selectors._contextsForSelectors.add(app._context);
 | 
			
		||||
    app.once(import_events.Events.ElectronApplication.Close, () => this._playwright.selectors._contextsForSelectors.delete(app._context));
 | 
			
		||||
    await app._context._initializeHarFromOptions(options.recordHar);
 | 
			
		||||
    app._context.tracing._tracesDir = options.tracesDir;
 | 
			
		||||
    return app;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class ElectronApplication extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._windows = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
 | 
			
		||||
    this._context = import_browserContext.BrowserContext.from(initializer.context);
 | 
			
		||||
    for (const page of this._context._pages)
 | 
			
		||||
      this._onPage(page);
 | 
			
		||||
    this._context.on(import_events.Events.BrowserContext.Page, (page) => this._onPage(page));
 | 
			
		||||
    this._channel.on("close", () => {
 | 
			
		||||
      this.emit(import_events.Events.ElectronApplication.Close);
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("console", (event) => this.emit(import_events.Events.ElectronApplication.Console, new import_consoleMessage.ConsoleMessage(this._platform, event)));
 | 
			
		||||
    this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
 | 
			
		||||
      [import_events.Events.ElectronApplication.Console, "console"]
 | 
			
		||||
    ]));
 | 
			
		||||
  }
 | 
			
		||||
  static from(electronApplication) {
 | 
			
		||||
    return electronApplication._object;
 | 
			
		||||
  }
 | 
			
		||||
  process() {
 | 
			
		||||
    return this._connection.toImpl?.(this)?.process();
 | 
			
		||||
  }
 | 
			
		||||
  _onPage(page) {
 | 
			
		||||
    this._windows.add(page);
 | 
			
		||||
    this.emit(import_events.Events.ElectronApplication.Window, page);
 | 
			
		||||
    page.once(import_events.Events.Page.Close, () => this._windows.delete(page));
 | 
			
		||||
  }
 | 
			
		||||
  windows() {
 | 
			
		||||
    return [...this._windows];
 | 
			
		||||
  }
 | 
			
		||||
  async firstWindow(options) {
 | 
			
		||||
    if (this._windows.size)
 | 
			
		||||
      return this._windows.values().next().value;
 | 
			
		||||
    return await this.waitForEvent("window", options);
 | 
			
		||||
  }
 | 
			
		||||
  context() {
 | 
			
		||||
    return this._context;
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.close();
 | 
			
		||||
  }
 | 
			
		||||
  async close() {
 | 
			
		||||
    try {
 | 
			
		||||
      await this._context.close();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if ((0, import_errors.isTargetClosedError)(e))
 | 
			
		||||
        return;
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  async waitForEvent(event, optionsOrPredicate = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
 | 
			
		||||
      const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
 | 
			
		||||
      const waiter = import_waiter.Waiter.createForEvent(this, event);
 | 
			
		||||
      waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
 | 
			
		||||
      if (event !== import_events.Events.ElectronApplication.Close)
 | 
			
		||||
        waiter.rejectOnEvent(this, import_events.Events.ElectronApplication.Close, () => new import_errors.TargetClosedError());
 | 
			
		||||
      const result = await waiter.waitForEvent(this, event, predicate);
 | 
			
		||||
      waiter.dispose();
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async browserWindow(page) {
 | 
			
		||||
    const result = await this._channel.browserWindow({ page: page._channel });
 | 
			
		||||
    return import_jsHandle.JSHandle.from(result.handle);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluate(pageFunction, arg) {
 | 
			
		||||
    const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluateHandle(pageFunction, arg) {
 | 
			
		||||
    const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return import_jsHandle.JSHandle.from(result.handle);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Electron,
 | 
			
		||||
  ElectronApplication
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										281
									
								
								node_modules/playwright-core/lib/client/elementHandle.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								node_modules/playwright-core/lib/client/elementHandle.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,281 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var elementHandle_exports = {};
 | 
			
		||||
__export(elementHandle_exports, {
 | 
			
		||||
  ElementHandle: () => ElementHandle,
 | 
			
		||||
  convertInputFiles: () => convertInputFiles,
 | 
			
		||||
  convertSelectOptionValues: () => convertSelectOptionValues,
 | 
			
		||||
  determineScreenshotType: () => determineScreenshotType
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(elementHandle_exports);
 | 
			
		||||
var import_frame = require("./frame");
 | 
			
		||||
var import_jsHandle = require("./jsHandle");
 | 
			
		||||
var import_assert = require("../utils/isomorphic/assert");
 | 
			
		||||
var import_fileUtils = require("./fileUtils");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
var import_writableStream = require("./writableStream");
 | 
			
		||||
var import_mimeType = require("../utils/isomorphic/mimeType");
 | 
			
		||||
class ElementHandle extends import_jsHandle.JSHandle {
 | 
			
		||||
  static from(handle) {
 | 
			
		||||
    return handle._object;
 | 
			
		||||
  }
 | 
			
		||||
  static fromNullable(handle) {
 | 
			
		||||
    return handle ? ElementHandle.from(handle) : null;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._frame = parent;
 | 
			
		||||
    this._elementChannel = this._channel;
 | 
			
		||||
  }
 | 
			
		||||
  asElement() {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  async ownerFrame() {
 | 
			
		||||
    return import_frame.Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
 | 
			
		||||
  }
 | 
			
		||||
  async contentFrame() {
 | 
			
		||||
    return import_frame.Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
 | 
			
		||||
  }
 | 
			
		||||
  async getAttribute(name) {
 | 
			
		||||
    const value = (await this._elementChannel.getAttribute({ name })).value;
 | 
			
		||||
    return value === void 0 ? null : value;
 | 
			
		||||
  }
 | 
			
		||||
  async inputValue() {
 | 
			
		||||
    return (await this._elementChannel.inputValue()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async textContent() {
 | 
			
		||||
    const value = (await this._elementChannel.textContent()).value;
 | 
			
		||||
    return value === void 0 ? null : value;
 | 
			
		||||
  }
 | 
			
		||||
  async innerText() {
 | 
			
		||||
    return (await this._elementChannel.innerText()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async innerHTML() {
 | 
			
		||||
    return (await this._elementChannel.innerHTML()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isChecked() {
 | 
			
		||||
    return (await this._elementChannel.isChecked()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isDisabled() {
 | 
			
		||||
    return (await this._elementChannel.isDisabled()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isEditable() {
 | 
			
		||||
    return (await this._elementChannel.isEditable()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isEnabled() {
 | 
			
		||||
    return (await this._elementChannel.isEnabled()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isHidden() {
 | 
			
		||||
    return (await this._elementChannel.isHidden()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isVisible() {
 | 
			
		||||
    return (await this._elementChannel.isVisible()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async dispatchEvent(type, eventInit = {}) {
 | 
			
		||||
    await this._elementChannel.dispatchEvent({ type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit) });
 | 
			
		||||
  }
 | 
			
		||||
  async scrollIntoViewIfNeeded(options = {}) {
 | 
			
		||||
    await this._elementChannel.scrollIntoViewIfNeeded({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async hover(options = {}) {
 | 
			
		||||
    await this._elementChannel.hover({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async click(options = {}) {
 | 
			
		||||
    return await this._elementChannel.click({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async dblclick(options = {}) {
 | 
			
		||||
    return await this._elementChannel.dblclick({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async tap(options = {}) {
 | 
			
		||||
    return await this._elementChannel.tap({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async selectOption(values, options = {}) {
 | 
			
		||||
    const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
    return result.values;
 | 
			
		||||
  }
 | 
			
		||||
  async fill(value, options = {}) {
 | 
			
		||||
    return await this._elementChannel.fill({ value, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async selectText(options = {}) {
 | 
			
		||||
    await this._elementChannel.selectText({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async setInputFiles(files, options = {}) {
 | 
			
		||||
    const frame = await this.ownerFrame();
 | 
			
		||||
    if (!frame)
 | 
			
		||||
      throw new Error("Cannot set input files to detached element");
 | 
			
		||||
    const converted = await convertInputFiles(this._platform, files, frame.page().context());
 | 
			
		||||
    await this._elementChannel.setInputFiles({ ...converted, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async focus() {
 | 
			
		||||
    await this._elementChannel.focus();
 | 
			
		||||
  }
 | 
			
		||||
  async type(text, options = {}) {
 | 
			
		||||
    await this._elementChannel.type({ text, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async press(key, options = {}) {
 | 
			
		||||
    await this._elementChannel.press({ key, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async check(options = {}) {
 | 
			
		||||
    return await this._elementChannel.check({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async uncheck(options = {}) {
 | 
			
		||||
    return await this._elementChannel.uncheck({ ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async setChecked(checked, options) {
 | 
			
		||||
    if (checked)
 | 
			
		||||
      await this.check(options);
 | 
			
		||||
    else
 | 
			
		||||
      await this.uncheck(options);
 | 
			
		||||
  }
 | 
			
		||||
  async boundingBox() {
 | 
			
		||||
    const value = (await this._elementChannel.boundingBox()).value;
 | 
			
		||||
    return value === void 0 ? null : value;
 | 
			
		||||
  }
 | 
			
		||||
  async screenshot(options = {}) {
 | 
			
		||||
    const mask = options.mask;
 | 
			
		||||
    const copy = { ...options, mask: void 0, timeout: this._frame._timeout(options) };
 | 
			
		||||
    if (!copy.type)
 | 
			
		||||
      copy.type = determineScreenshotType(options);
 | 
			
		||||
    if (mask) {
 | 
			
		||||
      copy.mask = mask.map((locator) => ({
 | 
			
		||||
        frame: locator._frame._channel,
 | 
			
		||||
        selector: locator._selector
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
    const result = await this._elementChannel.screenshot(copy);
 | 
			
		||||
    if (options.path) {
 | 
			
		||||
      await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
 | 
			
		||||
      await this._platform.fs().promises.writeFile(options.path, result.binary);
 | 
			
		||||
    }
 | 
			
		||||
    return result.binary;
 | 
			
		||||
  }
 | 
			
		||||
  async $(selector) {
 | 
			
		||||
    return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element);
 | 
			
		||||
  }
 | 
			
		||||
  async $$(selector) {
 | 
			
		||||
    const result = await this._elementChannel.querySelectorAll({ selector });
 | 
			
		||||
    return result.elements.map((h) => ElementHandle.from(h));
 | 
			
		||||
  }
 | 
			
		||||
  async $eval(selector, pageFunction, arg) {
 | 
			
		||||
    const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async $$eval(selector, pageFunction, arg) {
 | 
			
		||||
    const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForElementState(state, options = {}) {
 | 
			
		||||
    return await this._elementChannel.waitForElementState({ state, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async waitForSelector(selector, options = {}) {
 | 
			
		||||
    const result = await this._elementChannel.waitForSelector({ selector, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
    return ElementHandle.fromNullable(result.element);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function convertSelectOptionValues(values) {
 | 
			
		||||
  if (values === null)
 | 
			
		||||
    return {};
 | 
			
		||||
  if (!Array.isArray(values))
 | 
			
		||||
    values = [values];
 | 
			
		||||
  if (!values.length)
 | 
			
		||||
    return {};
 | 
			
		||||
  for (let i = 0; i < values.length; i++)
 | 
			
		||||
    (0, import_assert.assert)(values[i] !== null, `options[${i}]: expected object, got null`);
 | 
			
		||||
  if (values[0] instanceof ElementHandle)
 | 
			
		||||
    return { elements: values.map((v) => v._elementChannel) };
 | 
			
		||||
  if ((0, import_rtti.isString)(values[0]))
 | 
			
		||||
    return { options: values.map((valueOrLabel) => ({ valueOrLabel })) };
 | 
			
		||||
  return { options: values };
 | 
			
		||||
}
 | 
			
		||||
function filePayloadExceedsSizeLimit(payloads) {
 | 
			
		||||
  return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= import_fileUtils.fileUploadSizeLimit;
 | 
			
		||||
}
 | 
			
		||||
async function resolvePathsAndDirectoryForInputFiles(platform, items) {
 | 
			
		||||
  let localPaths;
 | 
			
		||||
  let localDirectory;
 | 
			
		||||
  for (const item of items) {
 | 
			
		||||
    const stat = await platform.fs().promises.stat(item);
 | 
			
		||||
    if (stat.isDirectory()) {
 | 
			
		||||
      if (localDirectory)
 | 
			
		||||
        throw new Error("Multiple directories are not supported");
 | 
			
		||||
      localDirectory = platform.path().resolve(item);
 | 
			
		||||
    } else {
 | 
			
		||||
      localPaths ??= [];
 | 
			
		||||
      localPaths.push(platform.path().resolve(item));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (localPaths?.length && localDirectory)
 | 
			
		||||
    throw new Error("File paths must be all files or a single directory");
 | 
			
		||||
  return [localPaths, localDirectory];
 | 
			
		||||
}
 | 
			
		||||
async function convertInputFiles(platform, files, context) {
 | 
			
		||||
  const items = Array.isArray(files) ? files.slice() : [files];
 | 
			
		||||
  if (items.some((item) => typeof item === "string")) {
 | 
			
		||||
    if (!items.every((item) => typeof item === "string"))
 | 
			
		||||
      throw new Error("File paths cannot be mixed with buffers");
 | 
			
		||||
    const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(platform, items);
 | 
			
		||||
    if (context._connection.isRemote()) {
 | 
			
		||||
      const files2 = localDirectory ? (await platform.fs().promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter((f) => f.isFile()).map((f) => platform.path().join(f.path, f.name)) : localPaths;
 | 
			
		||||
      const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
 | 
			
		||||
        rootDirName: localDirectory ? platform.path().basename(localDirectory) : void 0,
 | 
			
		||||
        items: await Promise.all(files2.map(async (file) => {
 | 
			
		||||
          const lastModifiedMs = (await platform.fs().promises.stat(file)).mtimeMs;
 | 
			
		||||
          return {
 | 
			
		||||
            name: localDirectory ? platform.path().relative(localDirectory, file) : platform.path().basename(file),
 | 
			
		||||
            lastModifiedMs
 | 
			
		||||
          };
 | 
			
		||||
        }))
 | 
			
		||||
      }), { internal: true });
 | 
			
		||||
      for (let i = 0; i < files2.length; i++) {
 | 
			
		||||
        const writable = import_writableStream.WritableStream.from(writableStreams[i]);
 | 
			
		||||
        await platform.streamFile(files2[i], writable.stream());
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        directoryStream: rootDir,
 | 
			
		||||
        streams: localDirectory ? void 0 : writableStreams
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      localPaths,
 | 
			
		||||
      localDirectory
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  const payloads = items;
 | 
			
		||||
  if (filePayloadExceedsSizeLimit(payloads))
 | 
			
		||||
    throw new Error("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
 | 
			
		||||
  return { payloads };
 | 
			
		||||
}
 | 
			
		||||
function determineScreenshotType(options) {
 | 
			
		||||
  if (options.path) {
 | 
			
		||||
    const mimeType = (0, import_mimeType.getMimeTypeForPath)(options.path);
 | 
			
		||||
    if (mimeType === "image/png")
 | 
			
		||||
      return "png";
 | 
			
		||||
    else if (mimeType === "image/jpeg")
 | 
			
		||||
      return "jpeg";
 | 
			
		||||
    throw new Error(`path: unsupported mime type "${mimeType}"`);
 | 
			
		||||
  }
 | 
			
		||||
  return options.type;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  ElementHandle,
 | 
			
		||||
  convertInputFiles,
 | 
			
		||||
  convertSelectOptionValues,
 | 
			
		||||
  determineScreenshotType
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										77
									
								
								node_modules/playwright-core/lib/client/errors.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								node_modules/playwright-core/lib/client/errors.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var errors_exports = {};
 | 
			
		||||
__export(errors_exports, {
 | 
			
		||||
  TargetClosedError: () => TargetClosedError,
 | 
			
		||||
  TimeoutError: () => TimeoutError,
 | 
			
		||||
  isTargetClosedError: () => isTargetClosedError,
 | 
			
		||||
  parseError: () => parseError,
 | 
			
		||||
  serializeError: () => serializeError
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(errors_exports);
 | 
			
		||||
var import_serializers = require("../protocol/serializers");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
class TimeoutError extends Error {
 | 
			
		||||
  constructor(message) {
 | 
			
		||||
    super(message);
 | 
			
		||||
    this.name = "TimeoutError";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class TargetClosedError extends Error {
 | 
			
		||||
  constructor(cause) {
 | 
			
		||||
    super(cause || "Target page, context or browser has been closed");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function isTargetClosedError(error) {
 | 
			
		||||
  return error instanceof TargetClosedError;
 | 
			
		||||
}
 | 
			
		||||
function serializeError(e) {
 | 
			
		||||
  if ((0, import_rtti.isError)(e))
 | 
			
		||||
    return { error: { message: e.message, stack: e.stack, name: e.name } };
 | 
			
		||||
  return { value: (0, import_serializers.serializeValue)(e, (value) => ({ fallThrough: value })) };
 | 
			
		||||
}
 | 
			
		||||
function parseError(error) {
 | 
			
		||||
  if (!error.error) {
 | 
			
		||||
    if (error.value === void 0)
 | 
			
		||||
      throw new Error("Serialized error must have either an error or a value");
 | 
			
		||||
    return (0, import_serializers.parseSerializedValue)(error.value, void 0);
 | 
			
		||||
  }
 | 
			
		||||
  if (error.error.name === "TimeoutError") {
 | 
			
		||||
    const e2 = new TimeoutError(error.error.message);
 | 
			
		||||
    e2.stack = error.error.stack || "";
 | 
			
		||||
    return e2;
 | 
			
		||||
  }
 | 
			
		||||
  if (error.error.name === "TargetClosedError") {
 | 
			
		||||
    const e2 = new TargetClosedError(error.error.message);
 | 
			
		||||
    e2.stack = error.error.stack || "";
 | 
			
		||||
    return e2;
 | 
			
		||||
  }
 | 
			
		||||
  const e = new Error(error.error.message);
 | 
			
		||||
  e.stack = error.error.stack || "";
 | 
			
		||||
  e.name = error.error.name;
 | 
			
		||||
  return e;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  TargetClosedError,
 | 
			
		||||
  TimeoutError,
 | 
			
		||||
  isTargetClosedError,
 | 
			
		||||
  parseError,
 | 
			
		||||
  serializeError
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										314
									
								
								node_modules/playwright-core/lib/client/eventEmitter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								node_modules/playwright-core/lib/client/eventEmitter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,314 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var eventEmitter_exports = {};
 | 
			
		||||
__export(eventEmitter_exports, {
 | 
			
		||||
  EventEmitter: () => EventEmitter
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(eventEmitter_exports);
 | 
			
		||||
class EventEmitter {
 | 
			
		||||
  constructor(platform) {
 | 
			
		||||
    this._events = void 0;
 | 
			
		||||
    this._eventsCount = 0;
 | 
			
		||||
    this._maxListeners = void 0;
 | 
			
		||||
    this._pendingHandlers = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._platform = platform;
 | 
			
		||||
    if (this._events === void 0 || this._events === Object.getPrototypeOf(this)._events) {
 | 
			
		||||
      this._events = /* @__PURE__ */ Object.create(null);
 | 
			
		||||
      this._eventsCount = 0;
 | 
			
		||||
    }
 | 
			
		||||
    this._maxListeners = this._maxListeners || void 0;
 | 
			
		||||
    this.on = this.addListener;
 | 
			
		||||
    this.off = this.removeListener;
 | 
			
		||||
  }
 | 
			
		||||
  setMaxListeners(n) {
 | 
			
		||||
    if (typeof n !== "number" || n < 0 || Number.isNaN(n))
 | 
			
		||||
      throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + ".");
 | 
			
		||||
    this._maxListeners = n;
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  getMaxListeners() {
 | 
			
		||||
    return this._maxListeners === void 0 ? this._platform.defaultMaxListeners() : this._maxListeners;
 | 
			
		||||
  }
 | 
			
		||||
  emit(type, ...args) {
 | 
			
		||||
    const events = this._events;
 | 
			
		||||
    if (events === void 0)
 | 
			
		||||
      return false;
 | 
			
		||||
    const handler = events?.[type];
 | 
			
		||||
    if (handler === void 0)
 | 
			
		||||
      return false;
 | 
			
		||||
    if (typeof handler === "function") {
 | 
			
		||||
      this._callHandler(type, handler, args);
 | 
			
		||||
    } else {
 | 
			
		||||
      const len = handler.length;
 | 
			
		||||
      const listeners = handler.slice();
 | 
			
		||||
      for (let i = 0; i < len; ++i)
 | 
			
		||||
        this._callHandler(type, listeners[i], args);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  _callHandler(type, handler, args) {
 | 
			
		||||
    const promise = Reflect.apply(handler, this, args);
 | 
			
		||||
    if (!(promise instanceof Promise))
 | 
			
		||||
      return;
 | 
			
		||||
    let set = this._pendingHandlers.get(type);
 | 
			
		||||
    if (!set) {
 | 
			
		||||
      set = /* @__PURE__ */ new Set();
 | 
			
		||||
      this._pendingHandlers.set(type, set);
 | 
			
		||||
    }
 | 
			
		||||
    set.add(promise);
 | 
			
		||||
    promise.catch((e) => {
 | 
			
		||||
      if (this._rejectionHandler)
 | 
			
		||||
        this._rejectionHandler(e);
 | 
			
		||||
      else
 | 
			
		||||
        throw e;
 | 
			
		||||
    }).finally(() => set.delete(promise));
 | 
			
		||||
  }
 | 
			
		||||
  addListener(type, listener) {
 | 
			
		||||
    return this._addListener(type, listener, false);
 | 
			
		||||
  }
 | 
			
		||||
  on(type, listener) {
 | 
			
		||||
    return this._addListener(type, listener, false);
 | 
			
		||||
  }
 | 
			
		||||
  _addListener(type, listener, prepend) {
 | 
			
		||||
    checkListener(listener);
 | 
			
		||||
    let events = this._events;
 | 
			
		||||
    let existing;
 | 
			
		||||
    if (events === void 0) {
 | 
			
		||||
      events = this._events = /* @__PURE__ */ Object.create(null);
 | 
			
		||||
      this._eventsCount = 0;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (events.newListener !== void 0) {
 | 
			
		||||
        this.emit("newListener", type, unwrapListener(listener));
 | 
			
		||||
        events = this._events;
 | 
			
		||||
      }
 | 
			
		||||
      existing = events[type];
 | 
			
		||||
    }
 | 
			
		||||
    if (existing === void 0) {
 | 
			
		||||
      existing = events[type] = listener;
 | 
			
		||||
      ++this._eventsCount;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (typeof existing === "function") {
 | 
			
		||||
        existing = events[type] = prepend ? [listener, existing] : [existing, listener];
 | 
			
		||||
      } else if (prepend) {
 | 
			
		||||
        existing.unshift(listener);
 | 
			
		||||
      } else {
 | 
			
		||||
        existing.push(listener);
 | 
			
		||||
      }
 | 
			
		||||
      const m = this.getMaxListeners();
 | 
			
		||||
      if (m > 0 && existing.length > m && !existing.warned) {
 | 
			
		||||
        existing.warned = true;
 | 
			
		||||
        const w = new Error("Possible EventEmitter memory leak detected. " + existing.length + " " + String(type) + " listeners added. Use emitter.setMaxListeners() to increase limit");
 | 
			
		||||
        w.name = "MaxListenersExceededWarning";
 | 
			
		||||
        w.emitter = this;
 | 
			
		||||
        w.type = type;
 | 
			
		||||
        w.count = existing.length;
 | 
			
		||||
        if (!this._platform.isUnderTest()) {
 | 
			
		||||
          console.warn(w);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  prependListener(type, listener) {
 | 
			
		||||
    return this._addListener(type, listener, true);
 | 
			
		||||
  }
 | 
			
		||||
  once(type, listener) {
 | 
			
		||||
    checkListener(listener);
 | 
			
		||||
    this.on(type, new OnceWrapper(this, type, listener).wrapperFunction);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  prependOnceListener(type, listener) {
 | 
			
		||||
    checkListener(listener);
 | 
			
		||||
    this.prependListener(type, new OnceWrapper(this, type, listener).wrapperFunction);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  removeListener(type, listener) {
 | 
			
		||||
    checkListener(listener);
 | 
			
		||||
    const events = this._events;
 | 
			
		||||
    if (events === void 0)
 | 
			
		||||
      return this;
 | 
			
		||||
    const list = events[type];
 | 
			
		||||
    if (list === void 0)
 | 
			
		||||
      return this;
 | 
			
		||||
    if (list === listener || list.listener === listener) {
 | 
			
		||||
      if (--this._eventsCount === 0) {
 | 
			
		||||
        this._events = /* @__PURE__ */ Object.create(null);
 | 
			
		||||
      } else {
 | 
			
		||||
        delete events[type];
 | 
			
		||||
        if (events.removeListener)
 | 
			
		||||
          this.emit("removeListener", type, list.listener ?? listener);
 | 
			
		||||
      }
 | 
			
		||||
    } else if (typeof list !== "function") {
 | 
			
		||||
      let position = -1;
 | 
			
		||||
      let originalListener;
 | 
			
		||||
      for (let i = list.length - 1; i >= 0; i--) {
 | 
			
		||||
        if (list[i] === listener || wrappedListener(list[i]) === listener) {
 | 
			
		||||
          originalListener = wrappedListener(list[i]);
 | 
			
		||||
          position = i;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (position < 0)
 | 
			
		||||
        return this;
 | 
			
		||||
      if (position === 0)
 | 
			
		||||
        list.shift();
 | 
			
		||||
      else
 | 
			
		||||
        list.splice(position, 1);
 | 
			
		||||
      if (list.length === 1)
 | 
			
		||||
        events[type] = list[0];
 | 
			
		||||
      if (events.removeListener !== void 0)
 | 
			
		||||
        this.emit("removeListener", type, originalListener || listener);
 | 
			
		||||
    }
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
  off(type, listener) {
 | 
			
		||||
    return this.removeListener(type, listener);
 | 
			
		||||
  }
 | 
			
		||||
  removeAllListeners(type, options) {
 | 
			
		||||
    this._removeAllListeners(type);
 | 
			
		||||
    if (!options)
 | 
			
		||||
      return this;
 | 
			
		||||
    if (options.behavior === "wait") {
 | 
			
		||||
      const errors = [];
 | 
			
		||||
      this._rejectionHandler = (error) => errors.push(error);
 | 
			
		||||
      return this._waitFor(type).then(() => {
 | 
			
		||||
        if (errors.length)
 | 
			
		||||
          throw errors[0];
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (options.behavior === "ignoreErrors")
 | 
			
		||||
      this._rejectionHandler = () => {
 | 
			
		||||
      };
 | 
			
		||||
    return Promise.resolve();
 | 
			
		||||
  }
 | 
			
		||||
  _removeAllListeners(type) {
 | 
			
		||||
    const events = this._events;
 | 
			
		||||
    if (!events)
 | 
			
		||||
      return;
 | 
			
		||||
    if (!events.removeListener) {
 | 
			
		||||
      if (type === void 0) {
 | 
			
		||||
        this._events = /* @__PURE__ */ Object.create(null);
 | 
			
		||||
        this._eventsCount = 0;
 | 
			
		||||
      } else if (events[type] !== void 0) {
 | 
			
		||||
        if (--this._eventsCount === 0)
 | 
			
		||||
          this._events = /* @__PURE__ */ Object.create(null);
 | 
			
		||||
        else
 | 
			
		||||
          delete events[type];
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (type === void 0) {
 | 
			
		||||
      const keys = Object.keys(events);
 | 
			
		||||
      let key;
 | 
			
		||||
      for (let i = 0; i < keys.length; ++i) {
 | 
			
		||||
        key = keys[i];
 | 
			
		||||
        if (key === "removeListener")
 | 
			
		||||
          continue;
 | 
			
		||||
        this._removeAllListeners(key);
 | 
			
		||||
      }
 | 
			
		||||
      this._removeAllListeners("removeListener");
 | 
			
		||||
      this._events = /* @__PURE__ */ Object.create(null);
 | 
			
		||||
      this._eventsCount = 0;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const listeners = events[type];
 | 
			
		||||
    if (typeof listeners === "function") {
 | 
			
		||||
      this.removeListener(type, listeners);
 | 
			
		||||
    } else if (listeners !== void 0) {
 | 
			
		||||
      for (let i = listeners.length - 1; i >= 0; i--)
 | 
			
		||||
        this.removeListener(type, listeners[i]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  listeners(type) {
 | 
			
		||||
    return this._listeners(this, type, true);
 | 
			
		||||
  }
 | 
			
		||||
  rawListeners(type) {
 | 
			
		||||
    return this._listeners(this, type, false);
 | 
			
		||||
  }
 | 
			
		||||
  listenerCount(type) {
 | 
			
		||||
    const events = this._events;
 | 
			
		||||
    if (events !== void 0) {
 | 
			
		||||
      const listener = events[type];
 | 
			
		||||
      if (typeof listener === "function")
 | 
			
		||||
        return 1;
 | 
			
		||||
      if (listener !== void 0)
 | 
			
		||||
        return listener.length;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  eventNames() {
 | 
			
		||||
    return this._eventsCount > 0 && this._events ? Reflect.ownKeys(this._events) : [];
 | 
			
		||||
  }
 | 
			
		||||
  async _waitFor(type) {
 | 
			
		||||
    let promises = [];
 | 
			
		||||
    if (type) {
 | 
			
		||||
      promises = [...this._pendingHandlers.get(type) || []];
 | 
			
		||||
    } else {
 | 
			
		||||
      promises = [];
 | 
			
		||||
      for (const [, pending] of this._pendingHandlers)
 | 
			
		||||
        promises.push(...pending);
 | 
			
		||||
    }
 | 
			
		||||
    await Promise.all(promises);
 | 
			
		||||
  }
 | 
			
		||||
  _listeners(target, type, unwrap) {
 | 
			
		||||
    const events = target._events;
 | 
			
		||||
    if (events === void 0)
 | 
			
		||||
      return [];
 | 
			
		||||
    const listener = events[type];
 | 
			
		||||
    if (listener === void 0)
 | 
			
		||||
      return [];
 | 
			
		||||
    if (typeof listener === "function")
 | 
			
		||||
      return unwrap ? [unwrapListener(listener)] : [listener];
 | 
			
		||||
    return unwrap ? unwrapListeners(listener) : listener.slice();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function checkListener(listener) {
 | 
			
		||||
  if (typeof listener !== "function")
 | 
			
		||||
    throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
 | 
			
		||||
}
 | 
			
		||||
class OnceWrapper {
 | 
			
		||||
  constructor(eventEmitter, eventType, listener) {
 | 
			
		||||
    this._fired = false;
 | 
			
		||||
    this._eventEmitter = eventEmitter;
 | 
			
		||||
    this._eventType = eventType;
 | 
			
		||||
    this._listener = listener;
 | 
			
		||||
    this.wrapperFunction = this._handle.bind(this);
 | 
			
		||||
    this.wrapperFunction.listener = listener;
 | 
			
		||||
  }
 | 
			
		||||
  _handle(...args) {
 | 
			
		||||
    if (this._fired)
 | 
			
		||||
      return;
 | 
			
		||||
    this._fired = true;
 | 
			
		||||
    this._eventEmitter.removeListener(this._eventType, this.wrapperFunction);
 | 
			
		||||
    return this._listener.apply(this._eventEmitter, args);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function unwrapListener(l) {
 | 
			
		||||
  return wrappedListener(l) ?? l;
 | 
			
		||||
}
 | 
			
		||||
function unwrapListeners(arr) {
 | 
			
		||||
  return arr.map((l) => wrappedListener(l) ?? l);
 | 
			
		||||
}
 | 
			
		||||
function wrappedListener(l) {
 | 
			
		||||
  return l.listener;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  EventEmitter
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										98
									
								
								node_modules/playwright-core/lib/client/events.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								node_modules/playwright-core/lib/client/events.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var events_exports = {};
 | 
			
		||||
__export(events_exports, {
 | 
			
		||||
  Events: () => Events
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(events_exports);
 | 
			
		||||
const Events = {
 | 
			
		||||
  AndroidDevice: {
 | 
			
		||||
    WebView: "webview",
 | 
			
		||||
    Close: "close"
 | 
			
		||||
  },
 | 
			
		||||
  AndroidSocket: {
 | 
			
		||||
    Data: "data",
 | 
			
		||||
    Close: "close"
 | 
			
		||||
  },
 | 
			
		||||
  AndroidWebView: {
 | 
			
		||||
    Close: "close"
 | 
			
		||||
  },
 | 
			
		||||
  Browser: {
 | 
			
		||||
    Disconnected: "disconnected"
 | 
			
		||||
  },
 | 
			
		||||
  BrowserContext: {
 | 
			
		||||
    Console: "console",
 | 
			
		||||
    Close: "close",
 | 
			
		||||
    Dialog: "dialog",
 | 
			
		||||
    Page: "page",
 | 
			
		||||
    // Can't use just 'error' due to node.js special treatment of error events.
 | 
			
		||||
    // @see https://nodejs.org/api/events.html#events_error_events
 | 
			
		||||
    WebError: "weberror",
 | 
			
		||||
    BackgroundPage: "backgroundpage",
 | 
			
		||||
    ServiceWorker: "serviceworker",
 | 
			
		||||
    Request: "request",
 | 
			
		||||
    Response: "response",
 | 
			
		||||
    RequestFailed: "requestfailed",
 | 
			
		||||
    RequestFinished: "requestfinished"
 | 
			
		||||
  },
 | 
			
		||||
  BrowserServer: {
 | 
			
		||||
    Close: "close"
 | 
			
		||||
  },
 | 
			
		||||
  Page: {
 | 
			
		||||
    Close: "close",
 | 
			
		||||
    Crash: "crash",
 | 
			
		||||
    Console: "console",
 | 
			
		||||
    Dialog: "dialog",
 | 
			
		||||
    Download: "download",
 | 
			
		||||
    FileChooser: "filechooser",
 | 
			
		||||
    DOMContentLoaded: "domcontentloaded",
 | 
			
		||||
    // Can't use just 'error' due to node.js special treatment of error events.
 | 
			
		||||
    // @see https://nodejs.org/api/events.html#events_error_events
 | 
			
		||||
    PageError: "pageerror",
 | 
			
		||||
    Request: "request",
 | 
			
		||||
    Response: "response",
 | 
			
		||||
    RequestFailed: "requestfailed",
 | 
			
		||||
    RequestFinished: "requestfinished",
 | 
			
		||||
    FrameAttached: "frameattached",
 | 
			
		||||
    FrameDetached: "framedetached",
 | 
			
		||||
    FrameNavigated: "framenavigated",
 | 
			
		||||
    Load: "load",
 | 
			
		||||
    Popup: "popup",
 | 
			
		||||
    WebSocket: "websocket",
 | 
			
		||||
    Worker: "worker"
 | 
			
		||||
  },
 | 
			
		||||
  WebSocket: {
 | 
			
		||||
    Close: "close",
 | 
			
		||||
    Error: "socketerror",
 | 
			
		||||
    FrameReceived: "framereceived",
 | 
			
		||||
    FrameSent: "framesent"
 | 
			
		||||
  },
 | 
			
		||||
  Worker: {
 | 
			
		||||
    Close: "close"
 | 
			
		||||
  },
 | 
			
		||||
  ElectronApplication: {
 | 
			
		||||
    Close: "close",
 | 
			
		||||
    Console: "console",
 | 
			
		||||
    Window: "window"
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Events
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										369
									
								
								node_modules/playwright-core/lib/client/fetch.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								node_modules/playwright-core/lib/client/fetch.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,369 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var fetch_exports = {};
 | 
			
		||||
__export(fetch_exports, {
 | 
			
		||||
  APIRequest: () => APIRequest,
 | 
			
		||||
  APIRequestContext: () => APIRequestContext,
 | 
			
		||||
  APIResponse: () => APIResponse
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(fetch_exports);
 | 
			
		||||
var import_browserContext = require("./browserContext");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_network = require("./network");
 | 
			
		||||
var import_tracing = require("./tracing");
 | 
			
		||||
var import_assert = require("../utils/isomorphic/assert");
 | 
			
		||||
var import_fileUtils = require("./fileUtils");
 | 
			
		||||
var import_headers = require("../utils/isomorphic/headers");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
var import_timeoutSettings = require("./timeoutSettings");
 | 
			
		||||
class APIRequest {
 | 
			
		||||
  constructor(playwright) {
 | 
			
		||||
    this._contexts = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._playwright = playwright;
 | 
			
		||||
  }
 | 
			
		||||
  async newContext(options = {}) {
 | 
			
		||||
    options = {
 | 
			
		||||
      ...this._playwright._defaultContextOptions,
 | 
			
		||||
      ...options
 | 
			
		||||
    };
 | 
			
		||||
    const storageState = typeof options.storageState === "string" ? JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, "utf8")) : options.storageState;
 | 
			
		||||
    const context = APIRequestContext.from((await this._playwright._channel.newRequest({
 | 
			
		||||
      ...options,
 | 
			
		||||
      extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
 | 
			
		||||
      storageState,
 | 
			
		||||
      tracesDir: this._playwright._defaultLaunchOptions?.tracesDir,
 | 
			
		||||
      // We do not expose tracesDir in the API, so do not allow options to accidentally override it.
 | 
			
		||||
      clientCertificates: await (0, import_browserContext.toClientCertificatesProtocol)(this._playwright._platform, options.clientCertificates)
 | 
			
		||||
    })).request);
 | 
			
		||||
    this._contexts.add(context);
 | 
			
		||||
    context._request = this;
 | 
			
		||||
    context._timeoutSettings.setDefaultTimeout(options.timeout ?? this._playwright._defaultContextTimeout);
 | 
			
		||||
    context._tracing._tracesDir = this._playwright._defaultLaunchOptions?.tracesDir;
 | 
			
		||||
    await context._instrumentation.runAfterCreateRequestContext(context);
 | 
			
		||||
    return context;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class APIRequestContext extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(channel) {
 | 
			
		||||
    return channel._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._tracing = import_tracing.Tracing.from(initializer.tracing);
 | 
			
		||||
    this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.dispose();
 | 
			
		||||
  }
 | 
			
		||||
  async dispose(options = {}) {
 | 
			
		||||
    this._closeReason = options.reason;
 | 
			
		||||
    await this._instrumentation.runBeforeCloseRequestContext(this);
 | 
			
		||||
    try {
 | 
			
		||||
      await this._channel.dispose(options);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if ((0, import_errors.isTargetClosedError)(e))
 | 
			
		||||
        return;
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
    this._tracing._resetStackCounter();
 | 
			
		||||
    this._request?._contexts.delete(this);
 | 
			
		||||
  }
 | 
			
		||||
  async delete(url, options) {
 | 
			
		||||
    return await this.fetch(url, {
 | 
			
		||||
      ...options,
 | 
			
		||||
      method: "DELETE"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async head(url, options) {
 | 
			
		||||
    return await this.fetch(url, {
 | 
			
		||||
      ...options,
 | 
			
		||||
      method: "HEAD"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async get(url, options) {
 | 
			
		||||
    return await this.fetch(url, {
 | 
			
		||||
      ...options,
 | 
			
		||||
      method: "GET"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async patch(url, options) {
 | 
			
		||||
    return await this.fetch(url, {
 | 
			
		||||
      ...options,
 | 
			
		||||
      method: "PATCH"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async post(url, options) {
 | 
			
		||||
    return await this.fetch(url, {
 | 
			
		||||
      ...options,
 | 
			
		||||
      method: "POST"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async put(url, options) {
 | 
			
		||||
    return await this.fetch(url, {
 | 
			
		||||
      ...options,
 | 
			
		||||
      method: "PUT"
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async fetch(urlOrRequest, options = {}) {
 | 
			
		||||
    const url = (0, import_rtti.isString)(urlOrRequest) ? urlOrRequest : void 0;
 | 
			
		||||
    const request = (0, import_rtti.isString)(urlOrRequest) ? void 0 : urlOrRequest;
 | 
			
		||||
    return await this._innerFetch({ url, request, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async _innerFetch(options = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      if (this._closeReason)
 | 
			
		||||
        throw new import_errors.TargetClosedError(this._closeReason);
 | 
			
		||||
      (0, import_assert.assert)(options.request || typeof options.url === "string", "First argument must be either URL string or Request");
 | 
			
		||||
      (0, import_assert.assert)((options.data === void 0 ? 0 : 1) + (options.form === void 0 ? 0 : 1) + (options.multipart === void 0 ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
 | 
			
		||||
      (0, import_assert.assert)(options.maxRedirects === void 0 || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
 | 
			
		||||
      (0, import_assert.assert)(options.maxRetries === void 0 || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
 | 
			
		||||
      const url = options.url !== void 0 ? options.url : options.request.url();
 | 
			
		||||
      const method = options.method || options.request?.method();
 | 
			
		||||
      let encodedParams = void 0;
 | 
			
		||||
      if (typeof options.params === "string")
 | 
			
		||||
        encodedParams = options.params;
 | 
			
		||||
      else if (options.params instanceof URLSearchParams)
 | 
			
		||||
        encodedParams = options.params.toString();
 | 
			
		||||
      const headersObj = options.headers || options.request?.headers();
 | 
			
		||||
      const headers = headersObj ? (0, import_headers.headersObjectToArray)(headersObj) : void 0;
 | 
			
		||||
      let jsonData;
 | 
			
		||||
      let formData;
 | 
			
		||||
      let multipartData;
 | 
			
		||||
      let postDataBuffer;
 | 
			
		||||
      if (options.data !== void 0) {
 | 
			
		||||
        if ((0, import_rtti.isString)(options.data)) {
 | 
			
		||||
          if (isJsonContentType(headers))
 | 
			
		||||
            jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);
 | 
			
		||||
          else
 | 
			
		||||
            postDataBuffer = Buffer.from(options.data, "utf8");
 | 
			
		||||
        } else if (Buffer.isBuffer(options.data)) {
 | 
			
		||||
          postDataBuffer = options.data;
 | 
			
		||||
        } else if (typeof options.data === "object" || typeof options.data === "number" || typeof options.data === "boolean") {
 | 
			
		||||
          jsonData = JSON.stringify(options.data);
 | 
			
		||||
        } else {
 | 
			
		||||
          throw new Error(`Unexpected 'data' type`);
 | 
			
		||||
        }
 | 
			
		||||
      } else if (options.form) {
 | 
			
		||||
        if (globalThis.FormData && options.form instanceof FormData) {
 | 
			
		||||
          formData = [];
 | 
			
		||||
          for (const [name, value] of options.form.entries()) {
 | 
			
		||||
            if (typeof value !== "string")
 | 
			
		||||
              throw new Error(`Expected string for options.form["${name}"], found File. Please use options.multipart instead.`);
 | 
			
		||||
            formData.push({ name, value });
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          formData = objectToArray(options.form);
 | 
			
		||||
        }
 | 
			
		||||
      } else if (options.multipart) {
 | 
			
		||||
        multipartData = [];
 | 
			
		||||
        if (globalThis.FormData && options.multipart instanceof FormData) {
 | 
			
		||||
          const form = options.multipart;
 | 
			
		||||
          for (const [name, value] of form.entries()) {
 | 
			
		||||
            if ((0, import_rtti.isString)(value)) {
 | 
			
		||||
              multipartData.push({ name, value });
 | 
			
		||||
            } else {
 | 
			
		||||
              const file = {
 | 
			
		||||
                name: value.name,
 | 
			
		||||
                mimeType: value.type,
 | 
			
		||||
                buffer: Buffer.from(await value.arrayBuffer())
 | 
			
		||||
              };
 | 
			
		||||
              multipartData.push({ name, file });
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          for (const [name, value] of Object.entries(options.multipart))
 | 
			
		||||
            multipartData.push(await toFormField(this._platform, name, value));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (postDataBuffer === void 0 && jsonData === void 0 && formData === void 0 && multipartData === void 0)
 | 
			
		||||
        postDataBuffer = options.request?.postDataBuffer() || void 0;
 | 
			
		||||
      const fixtures = {
 | 
			
		||||
        __testHookLookup: options.__testHookLookup
 | 
			
		||||
      };
 | 
			
		||||
      const result = await this._channel.fetch({
 | 
			
		||||
        url,
 | 
			
		||||
        params: typeof options.params === "object" ? objectToArray(options.params) : void 0,
 | 
			
		||||
        encodedParams,
 | 
			
		||||
        method,
 | 
			
		||||
        headers,
 | 
			
		||||
        postData: postDataBuffer,
 | 
			
		||||
        jsonData,
 | 
			
		||||
        formData,
 | 
			
		||||
        multipartData,
 | 
			
		||||
        timeout: this._timeoutSettings.timeout(options),
 | 
			
		||||
        failOnStatusCode: options.failOnStatusCode,
 | 
			
		||||
        ignoreHTTPSErrors: options.ignoreHTTPSErrors,
 | 
			
		||||
        maxRedirects: options.maxRedirects,
 | 
			
		||||
        maxRetries: options.maxRetries,
 | 
			
		||||
        ...fixtures
 | 
			
		||||
      });
 | 
			
		||||
      return new APIResponse(this, result.response);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async storageState(options = {}) {
 | 
			
		||||
    const state = await this._channel.storageState({ indexedDB: options.indexedDB });
 | 
			
		||||
    if (options.path) {
 | 
			
		||||
      await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
 | 
			
		||||
      await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
 | 
			
		||||
    }
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
async function toFormField(platform, name, value) {
 | 
			
		||||
  const typeOfValue = typeof value;
 | 
			
		||||
  if (isFilePayload(value)) {
 | 
			
		||||
    const payload = value;
 | 
			
		||||
    if (!Buffer.isBuffer(payload.buffer))
 | 
			
		||||
      throw new Error(`Unexpected buffer type of 'data.${name}'`);
 | 
			
		||||
    return { name, file: filePayloadToJson(payload) };
 | 
			
		||||
  } else if (typeOfValue === "string" || typeOfValue === "number" || typeOfValue === "boolean") {
 | 
			
		||||
    return { name, value: String(value) };
 | 
			
		||||
  } else {
 | 
			
		||||
    return { name, file: await readStreamToJson(platform, value) };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function isJsonParsable(value) {
 | 
			
		||||
  if (typeof value !== "string")
 | 
			
		||||
    return false;
 | 
			
		||||
  try {
 | 
			
		||||
    JSON.parse(value);
 | 
			
		||||
    return true;
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    if (e instanceof SyntaxError)
 | 
			
		||||
      return false;
 | 
			
		||||
    else
 | 
			
		||||
      throw e;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class APIResponse {
 | 
			
		||||
  constructor(context, initializer) {
 | 
			
		||||
    this._request = context;
 | 
			
		||||
    this._initializer = initializer;
 | 
			
		||||
    this._headers = new import_network.RawHeaders(this._initializer.headers);
 | 
			
		||||
    if (context._platform.inspectCustom)
 | 
			
		||||
      this[context._platform.inspectCustom] = () => this._inspect();
 | 
			
		||||
  }
 | 
			
		||||
  ok() {
 | 
			
		||||
    return this._initializer.status >= 200 && this._initializer.status <= 299;
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._initializer.url;
 | 
			
		||||
  }
 | 
			
		||||
  status() {
 | 
			
		||||
    return this._initializer.status;
 | 
			
		||||
  }
 | 
			
		||||
  statusText() {
 | 
			
		||||
    return this._initializer.statusText;
 | 
			
		||||
  }
 | 
			
		||||
  headers() {
 | 
			
		||||
    return this._headers.headers();
 | 
			
		||||
  }
 | 
			
		||||
  headersArray() {
 | 
			
		||||
    return this._headers.headersArray();
 | 
			
		||||
  }
 | 
			
		||||
  async body() {
 | 
			
		||||
    return await this._request._wrapApiCall(async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() });
 | 
			
		||||
        if (result.binary === void 0)
 | 
			
		||||
          throw new Error("Response has been disposed");
 | 
			
		||||
        return result.binary;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        if ((0, import_errors.isTargetClosedError)(e))
 | 
			
		||||
          throw new Error("Response has been disposed");
 | 
			
		||||
        throw e;
 | 
			
		||||
      }
 | 
			
		||||
    }, { internal: true });
 | 
			
		||||
  }
 | 
			
		||||
  async text() {
 | 
			
		||||
    const content = await this.body();
 | 
			
		||||
    return content.toString("utf8");
 | 
			
		||||
  }
 | 
			
		||||
  async json() {
 | 
			
		||||
    const content = await this.text();
 | 
			
		||||
    return JSON.parse(content);
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.dispose();
 | 
			
		||||
  }
 | 
			
		||||
  async dispose() {
 | 
			
		||||
    await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() });
 | 
			
		||||
  }
 | 
			
		||||
  _inspect() {
 | 
			
		||||
    const headers = this.headersArray().map(({ name, value }) => `  ${name}: ${value}`);
 | 
			
		||||
    return `APIResponse: ${this.status()} ${this.statusText()}
 | 
			
		||||
${headers.join("\n")}`;
 | 
			
		||||
  }
 | 
			
		||||
  _fetchUid() {
 | 
			
		||||
    return this._initializer.fetchUid;
 | 
			
		||||
  }
 | 
			
		||||
  async _fetchLog() {
 | 
			
		||||
    const { log } = await this._request._channel.fetchLog({ fetchUid: this._fetchUid() });
 | 
			
		||||
    return log;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function filePayloadToJson(payload) {
 | 
			
		||||
  return {
 | 
			
		||||
    name: payload.name,
 | 
			
		||||
    mimeType: payload.mimeType,
 | 
			
		||||
    buffer: payload.buffer
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
async function readStreamToJson(platform, stream) {
 | 
			
		||||
  const buffer = await new Promise((resolve, reject) => {
 | 
			
		||||
    const chunks = [];
 | 
			
		||||
    stream.on("data", (chunk) => chunks.push(chunk));
 | 
			
		||||
    stream.on("end", () => resolve(Buffer.concat(chunks)));
 | 
			
		||||
    stream.on("error", (err) => reject(err));
 | 
			
		||||
  });
 | 
			
		||||
  const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString("utf8") : stream.path;
 | 
			
		||||
  return {
 | 
			
		||||
    name: platform.path().basename(streamPath),
 | 
			
		||||
    buffer
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
function isJsonContentType(headers) {
 | 
			
		||||
  if (!headers)
 | 
			
		||||
    return false;
 | 
			
		||||
  for (const { name, value } of headers) {
 | 
			
		||||
    if (name.toLocaleLowerCase() === "content-type")
 | 
			
		||||
      return value === "application/json";
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
function objectToArray(map) {
 | 
			
		||||
  if (!map)
 | 
			
		||||
    return void 0;
 | 
			
		||||
  const result = [];
 | 
			
		||||
  for (const [name, value] of Object.entries(map)) {
 | 
			
		||||
    if (value !== void 0)
 | 
			
		||||
      result.push({ name, value: String(value) });
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
function isFilePayload(value) {
 | 
			
		||||
  return typeof value === "object" && value["name"] && value["mimeType"] && value["buffer"];
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  APIRequest,
 | 
			
		||||
  APIRequestContext,
 | 
			
		||||
  APIResponse
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										46
									
								
								node_modules/playwright-core/lib/client/fileChooser.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								node_modules/playwright-core/lib/client/fileChooser.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var fileChooser_exports = {};
 | 
			
		||||
__export(fileChooser_exports, {
 | 
			
		||||
  FileChooser: () => FileChooser
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(fileChooser_exports);
 | 
			
		||||
class FileChooser {
 | 
			
		||||
  constructor(page, elementHandle, isMultiple) {
 | 
			
		||||
    this._page = page;
 | 
			
		||||
    this._elementHandle = elementHandle;
 | 
			
		||||
    this._isMultiple = isMultiple;
 | 
			
		||||
  }
 | 
			
		||||
  element() {
 | 
			
		||||
    return this._elementHandle;
 | 
			
		||||
  }
 | 
			
		||||
  isMultiple() {
 | 
			
		||||
    return this._isMultiple;
 | 
			
		||||
  }
 | 
			
		||||
  page() {
 | 
			
		||||
    return this._page;
 | 
			
		||||
  }
 | 
			
		||||
  async setFiles(files, options) {
 | 
			
		||||
    return await this._elementHandle.setInputFiles(files, options);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  FileChooser
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										34
									
								
								node_modules/playwright-core/lib/client/fileUtils.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								node_modules/playwright-core/lib/client/fileUtils.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var fileUtils_exports = {};
 | 
			
		||||
__export(fileUtils_exports, {
 | 
			
		||||
  fileUploadSizeLimit: () => fileUploadSizeLimit,
 | 
			
		||||
  mkdirIfNeeded: () => mkdirIfNeeded
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(fileUtils_exports);
 | 
			
		||||
const fileUploadSizeLimit = 50 * 1024 * 1024;
 | 
			
		||||
async function mkdirIfNeeded(platform, filePath) {
 | 
			
		||||
  await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  fileUploadSizeLimit,
 | 
			
		||||
  mkdirIfNeeded
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										408
									
								
								node_modules/playwright-core/lib/client/frame.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										408
									
								
								node_modules/playwright-core/lib/client/frame.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,408 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __create = Object.create;
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __getProtoOf = Object.getPrototypeOf;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
 | 
			
		||||
  // If the importer is in node compatibility mode or this is not an ESM
 | 
			
		||||
  // file that has been converted to a CommonJS file using a Babel-
 | 
			
		||||
  // compatible transform (i.e. "__esModule" has not been set), then set
 | 
			
		||||
  // "default" to the CommonJS "module.exports" for node compatibility.
 | 
			
		||||
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
 | 
			
		||||
  mod
 | 
			
		||||
));
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var frame_exports = {};
 | 
			
		||||
__export(frame_exports, {
 | 
			
		||||
  Frame: () => Frame,
 | 
			
		||||
  verifyLoadState: () => verifyLoadState
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(frame_exports);
 | 
			
		||||
var import_eventEmitter = require("./eventEmitter");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_clientHelper = require("./clientHelper");
 | 
			
		||||
var import_elementHandle = require("./elementHandle");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_jsHandle = require("./jsHandle");
 | 
			
		||||
var import_locator = require("./locator");
 | 
			
		||||
var network = __toESM(require("./network"));
 | 
			
		||||
var import_types = require("./types");
 | 
			
		||||
var import_waiter = require("./waiter");
 | 
			
		||||
var import_assert = require("../utils/isomorphic/assert");
 | 
			
		||||
var import_locatorUtils = require("../utils/isomorphic/locatorUtils");
 | 
			
		||||
var import_urlMatch = require("../utils/isomorphic/urlMatch");
 | 
			
		||||
var import_timeoutSettings = require("./timeoutSettings");
 | 
			
		||||
class Frame extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._parentFrame = null;
 | 
			
		||||
    this._url = "";
 | 
			
		||||
    this._name = "";
 | 
			
		||||
    this._detached = false;
 | 
			
		||||
    this._childFrames = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._eventEmitter = new import_eventEmitter.EventEmitter(parent._platform);
 | 
			
		||||
    this._eventEmitter.setMaxListeners(0);
 | 
			
		||||
    this._parentFrame = Frame.fromNullable(initializer.parentFrame);
 | 
			
		||||
    if (this._parentFrame)
 | 
			
		||||
      this._parentFrame._childFrames.add(this);
 | 
			
		||||
    this._name = initializer.name;
 | 
			
		||||
    this._url = initializer.url;
 | 
			
		||||
    this._loadStates = new Set(initializer.loadStates);
 | 
			
		||||
    this._channel.on("loadstate", (event) => {
 | 
			
		||||
      if (event.add) {
 | 
			
		||||
        this._loadStates.add(event.add);
 | 
			
		||||
        this._eventEmitter.emit("loadstate", event.add);
 | 
			
		||||
      }
 | 
			
		||||
      if (event.remove)
 | 
			
		||||
        this._loadStates.delete(event.remove);
 | 
			
		||||
      if (!this._parentFrame && event.add === "load" && this._page)
 | 
			
		||||
        this._page.emit(import_events.Events.Page.Load, this._page);
 | 
			
		||||
      if (!this._parentFrame && event.add === "domcontentloaded" && this._page)
 | 
			
		||||
        this._page.emit(import_events.Events.Page.DOMContentLoaded, this._page);
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("navigated", (event) => {
 | 
			
		||||
      this._url = event.url;
 | 
			
		||||
      this._name = event.name;
 | 
			
		||||
      this._eventEmitter.emit("navigated", event);
 | 
			
		||||
      if (!event.error && this._page)
 | 
			
		||||
        this._page.emit(import_events.Events.Page.FrameNavigated, this);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  static from(frame) {
 | 
			
		||||
    return frame._object;
 | 
			
		||||
  }
 | 
			
		||||
  static fromNullable(frame) {
 | 
			
		||||
    return frame ? Frame.from(frame) : null;
 | 
			
		||||
  }
 | 
			
		||||
  page() {
 | 
			
		||||
    return this._page;
 | 
			
		||||
  }
 | 
			
		||||
  _timeout(options) {
 | 
			
		||||
    const timeoutSettings = this._page?._timeoutSettings || new import_timeoutSettings.TimeoutSettings(this._platform);
 | 
			
		||||
    return timeoutSettings.timeout(options || {});
 | 
			
		||||
  }
 | 
			
		||||
  _navigationTimeout(options) {
 | 
			
		||||
    const timeoutSettings = this._page?._timeoutSettings || new import_timeoutSettings.TimeoutSettings(this._platform);
 | 
			
		||||
    return timeoutSettings.navigationTimeout(options || {});
 | 
			
		||||
  }
 | 
			
		||||
  async goto(url, options = {}) {
 | 
			
		||||
    const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
 | 
			
		||||
    return network.Response.fromNullable((await this._channel.goto({ url, ...options, waitUntil, timeout: this._navigationTimeout(options) })).response);
 | 
			
		||||
  }
 | 
			
		||||
  _setupNavigationWaiter(options) {
 | 
			
		||||
    const waiter = new import_waiter.Waiter(this._page, "");
 | 
			
		||||
    if (this._page.isClosed())
 | 
			
		||||
      waiter.rejectImmediately(this._page._closeErrorWithReason());
 | 
			
		||||
    waiter.rejectOnEvent(this._page, import_events.Events.Page.Close, () => this._page._closeErrorWithReason());
 | 
			
		||||
    waiter.rejectOnEvent(this._page, import_events.Events.Page.Crash, new Error("Navigation failed because page crashed!"));
 | 
			
		||||
    waiter.rejectOnEvent(this._page, import_events.Events.Page.FrameDetached, new Error("Navigating frame was detached!"), (frame) => frame === this);
 | 
			
		||||
    const timeout = this._page._timeoutSettings.navigationTimeout(options);
 | 
			
		||||
    waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded.`);
 | 
			
		||||
    return waiter;
 | 
			
		||||
  }
 | 
			
		||||
  async waitForNavigation(options = {}) {
 | 
			
		||||
    return await this._page._wrapApiCall(async () => {
 | 
			
		||||
      const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
 | 
			
		||||
      const waiter = this._setupNavigationWaiter(options);
 | 
			
		||||
      const toUrl = typeof options.url === "string" ? ` to "${options.url}"` : "";
 | 
			
		||||
      waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
 | 
			
		||||
      const navigatedEvent = await waiter.waitForEvent(this._eventEmitter, "navigated", (event) => {
 | 
			
		||||
        if (event.error)
 | 
			
		||||
          return true;
 | 
			
		||||
        waiter.log(`  navigated to "${event.url}"`);
 | 
			
		||||
        return (0, import_urlMatch.urlMatches)(this._page?.context()._options.baseURL, event.url, options.url);
 | 
			
		||||
      });
 | 
			
		||||
      if (navigatedEvent.error) {
 | 
			
		||||
        const e = new Error(navigatedEvent.error);
 | 
			
		||||
        e.stack = "";
 | 
			
		||||
        await waiter.waitForPromise(Promise.reject(e));
 | 
			
		||||
      }
 | 
			
		||||
      if (!this._loadStates.has(waitUntil)) {
 | 
			
		||||
        await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
 | 
			
		||||
          waiter.log(`  "${s}" event fired`);
 | 
			
		||||
          return s === waitUntil;
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request) : null;
 | 
			
		||||
      const response = request ? await waiter.waitForPromise(request._finalRequest()._internalResponse()) : null;
 | 
			
		||||
      waiter.dispose();
 | 
			
		||||
      return response;
 | 
			
		||||
    }, { title: "Wait for navigation" });
 | 
			
		||||
  }
 | 
			
		||||
  async waitForLoadState(state = "load", options = {}) {
 | 
			
		||||
    state = verifyLoadState("state", state);
 | 
			
		||||
    return await this._page._wrapApiCall(async () => {
 | 
			
		||||
      const waiter = this._setupNavigationWaiter(options);
 | 
			
		||||
      if (this._loadStates.has(state)) {
 | 
			
		||||
        waiter.log(`  not waiting, "${state}" event already fired`);
 | 
			
		||||
      } else {
 | 
			
		||||
        await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
 | 
			
		||||
          waiter.log(`  "${s}" event fired`);
 | 
			
		||||
          return s === state;
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      waiter.dispose();
 | 
			
		||||
    }, { title: `Wait for load state "${state}"` });
 | 
			
		||||
  }
 | 
			
		||||
  async waitForURL(url, options = {}) {
 | 
			
		||||
    if ((0, import_urlMatch.urlMatches)(this._page?.context()._options.baseURL, this.url(), url))
 | 
			
		||||
      return await this.waitForLoadState(options.waitUntil, options);
 | 
			
		||||
    await this.waitForNavigation({ url, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async frameElement() {
 | 
			
		||||
    return import_elementHandle.ElementHandle.from((await this._channel.frameElement()).element);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluateHandle(pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
 | 
			
		||||
    const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return import_jsHandle.JSHandle.from(result.handle);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluate(pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
 | 
			
		||||
    const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async _evaluateFunction(functionDeclaration) {
 | 
			
		||||
    const result = await this._channel.evaluateExpression({ expression: functionDeclaration, isFunction: true, arg: (0, import_jsHandle.serializeArgument)(void 0) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async _evaluateExposeUtilityScript(pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
 | 
			
		||||
    const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async $(selector, options) {
 | 
			
		||||
    const result = await this._channel.querySelector({ selector, ...options });
 | 
			
		||||
    return import_elementHandle.ElementHandle.fromNullable(result.element);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForSelector(selector, options = {}) {
 | 
			
		||||
    if (options.visibility)
 | 
			
		||||
      throw new Error("options.visibility is not supported, did you mean options.state?");
 | 
			
		||||
    if (options.waitFor && options.waitFor !== "visible")
 | 
			
		||||
      throw new Error("options.waitFor is not supported, did you mean options.state?");
 | 
			
		||||
    const result = await this._channel.waitForSelector({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
    return import_elementHandle.ElementHandle.fromNullable(result.element);
 | 
			
		||||
  }
 | 
			
		||||
  async dispatchEvent(selector, type, eventInit, options = {}) {
 | 
			
		||||
    await this._channel.dispatchEvent({ selector, type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit), ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async $eval(selector, pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
 | 
			
		||||
    const result = await this._channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async $$eval(selector, pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
 | 
			
		||||
    const result = await this._channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
 | 
			
		||||
    return (0, import_jsHandle.parseResult)(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async $$(selector) {
 | 
			
		||||
    const result = await this._channel.querySelectorAll({ selector });
 | 
			
		||||
    return result.elements.map((e) => import_elementHandle.ElementHandle.from(e));
 | 
			
		||||
  }
 | 
			
		||||
  async _queryCount(selector, options) {
 | 
			
		||||
    return (await this._channel.queryCount({ selector, ...options })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async content() {
 | 
			
		||||
    return (await this._channel.content()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async setContent(html, options = {}) {
 | 
			
		||||
    const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
 | 
			
		||||
    await this._channel.setContent({ html, ...options, waitUntil, timeout: this._navigationTimeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  name() {
 | 
			
		||||
    return this._name || "";
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._url;
 | 
			
		||||
  }
 | 
			
		||||
  parentFrame() {
 | 
			
		||||
    return this._parentFrame;
 | 
			
		||||
  }
 | 
			
		||||
  childFrames() {
 | 
			
		||||
    return Array.from(this._childFrames);
 | 
			
		||||
  }
 | 
			
		||||
  isDetached() {
 | 
			
		||||
    return this._detached;
 | 
			
		||||
  }
 | 
			
		||||
  async addScriptTag(options = {}) {
 | 
			
		||||
    const copy = { ...options };
 | 
			
		||||
    if (copy.path) {
 | 
			
		||||
      copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
 | 
			
		||||
      copy.content = (0, import_clientHelper.addSourceUrlToScript)(copy.content, copy.path);
 | 
			
		||||
    }
 | 
			
		||||
    return import_elementHandle.ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element);
 | 
			
		||||
  }
 | 
			
		||||
  async addStyleTag(options = {}) {
 | 
			
		||||
    const copy = { ...options };
 | 
			
		||||
    if (copy.path) {
 | 
			
		||||
      copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
 | 
			
		||||
      copy.content += "/*# sourceURL=" + copy.path.replace(/\n/g, "") + "*/";
 | 
			
		||||
    }
 | 
			
		||||
    return import_elementHandle.ElementHandle.from((await this._channel.addStyleTag({ ...copy })).element);
 | 
			
		||||
  }
 | 
			
		||||
  async click(selector, options = {}) {
 | 
			
		||||
    return await this._channel.click({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async dblclick(selector, options = {}) {
 | 
			
		||||
    return await this._channel.dblclick({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async dragAndDrop(source, target, options = {}) {
 | 
			
		||||
    return await this._channel.dragAndDrop({ source, target, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async tap(selector, options = {}) {
 | 
			
		||||
    return await this._channel.tap({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async fill(selector, value, options = {}) {
 | 
			
		||||
    return await this._channel.fill({ selector, value, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async _highlight(selector) {
 | 
			
		||||
    return await this._channel.highlight({ selector });
 | 
			
		||||
  }
 | 
			
		||||
  locator(selector, options) {
 | 
			
		||||
    return new import_locator.Locator(this, selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByTestId(testId) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTestIdSelector)((0, import_locator.testIdAttributeName)(), testId));
 | 
			
		||||
  }
 | 
			
		||||
  getByAltText(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByLabel(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByPlaceholder(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByText(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByTitle(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByRole(role, options = {}) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
 | 
			
		||||
  }
 | 
			
		||||
  frameLocator(selector) {
 | 
			
		||||
    return new import_locator.FrameLocator(this, selector);
 | 
			
		||||
  }
 | 
			
		||||
  async focus(selector, options = {}) {
 | 
			
		||||
    await this._channel.focus({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async textContent(selector, options = {}) {
 | 
			
		||||
    const value = (await this._channel.textContent({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
    return value === void 0 ? null : value;
 | 
			
		||||
  }
 | 
			
		||||
  async innerText(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.innerText({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async innerHTML(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.innerHTML({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async getAttribute(selector, name, options = {}) {
 | 
			
		||||
    const value = (await this._channel.getAttribute({ selector, name, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
    return value === void 0 ? null : value;
 | 
			
		||||
  }
 | 
			
		||||
  async inputValue(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.inputValue({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isChecked(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.isChecked({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isDisabled(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.isDisabled({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isEditable(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.isEditable({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isEnabled(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.isEnabled({ selector, ...options, timeout: this._timeout(options) })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isHidden(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.isHidden({ selector, ...options })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async isVisible(selector, options = {}) {
 | 
			
		||||
    return (await this._channel.isVisible({ selector, ...options })).value;
 | 
			
		||||
  }
 | 
			
		||||
  async hover(selector, options = {}) {
 | 
			
		||||
    await this._channel.hover({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async selectOption(selector, values, options = {}) {
 | 
			
		||||
    return (await this._channel.selectOption({ selector, ...(0, import_elementHandle.convertSelectOptionValues)(values), ...options, timeout: this._timeout(options) })).values;
 | 
			
		||||
  }
 | 
			
		||||
  async setInputFiles(selector, files, options = {}) {
 | 
			
		||||
    const converted = await (0, import_elementHandle.convertInputFiles)(this._platform, files, this.page().context());
 | 
			
		||||
    await this._channel.setInputFiles({ selector, ...converted, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async type(selector, text, options = {}) {
 | 
			
		||||
    await this._channel.type({ selector, text, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async press(selector, key, options = {}) {
 | 
			
		||||
    await this._channel.press({ selector, key, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async check(selector, options = {}) {
 | 
			
		||||
    await this._channel.check({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async uncheck(selector, options = {}) {
 | 
			
		||||
    await this._channel.uncheck({ selector, ...options, timeout: this._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async setChecked(selector, checked, options) {
 | 
			
		||||
    if (checked)
 | 
			
		||||
      await this.check(selector, options);
 | 
			
		||||
    else
 | 
			
		||||
      await this.uncheck(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForTimeout(timeout) {
 | 
			
		||||
    await this._channel.waitForTimeout({ waitTimeout: timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async waitForFunction(pageFunction, arg, options = {}) {
 | 
			
		||||
    if (typeof options.polling === "string")
 | 
			
		||||
      (0, import_assert.assert)(options.polling === "raf", "Unknown polling option: " + options.polling);
 | 
			
		||||
    const result = await this._channel.waitForFunction({
 | 
			
		||||
      ...options,
 | 
			
		||||
      pollingInterval: options.polling === "raf" ? void 0 : options.polling,
 | 
			
		||||
      expression: String(pageFunction),
 | 
			
		||||
      isFunction: typeof pageFunction === "function",
 | 
			
		||||
      arg: (0, import_jsHandle.serializeArgument)(arg),
 | 
			
		||||
      timeout: this._timeout(options)
 | 
			
		||||
    });
 | 
			
		||||
    return import_jsHandle.JSHandle.from(result.handle);
 | 
			
		||||
  }
 | 
			
		||||
  async title() {
 | 
			
		||||
    return (await this._channel.title()).value;
 | 
			
		||||
  }
 | 
			
		||||
  async _expect(expression, options) {
 | 
			
		||||
    const params = { expression, ...options, isNot: !!options.isNot };
 | 
			
		||||
    params.expectedValue = (0, import_jsHandle.serializeArgument)(options.expectedValue);
 | 
			
		||||
    const result = await this._channel.expect(params);
 | 
			
		||||
    if (result.received !== void 0)
 | 
			
		||||
      result.received = (0, import_jsHandle.parseResult)(result.received);
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function verifyLoadState(name, waitUntil) {
 | 
			
		||||
  if (waitUntil === "networkidle0")
 | 
			
		||||
    waitUntil = "networkidle";
 | 
			
		||||
  if (!import_types.kLifecycleEvents.has(waitUntil))
 | 
			
		||||
    throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
 | 
			
		||||
  return waitUntil;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Frame,
 | 
			
		||||
  verifyLoadState
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										87
									
								
								node_modules/playwright-core/lib/client/harRouter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								node_modules/playwright-core/lib/client/harRouter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var harRouter_exports = {};
 | 
			
		||||
__export(harRouter_exports, {
 | 
			
		||||
  HarRouter: () => HarRouter
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(harRouter_exports);
 | 
			
		||||
class HarRouter {
 | 
			
		||||
  static async create(localUtils, file, notFoundAction, options) {
 | 
			
		||||
    const { harId, error } = await localUtils.harOpen({ file });
 | 
			
		||||
    if (error)
 | 
			
		||||
      throw new Error(error);
 | 
			
		||||
    return new HarRouter(localUtils, harId, notFoundAction, options);
 | 
			
		||||
  }
 | 
			
		||||
  constructor(localUtils, harId, notFoundAction, options) {
 | 
			
		||||
    this._localUtils = localUtils;
 | 
			
		||||
    this._harId = harId;
 | 
			
		||||
    this._options = options;
 | 
			
		||||
    this._notFoundAction = notFoundAction;
 | 
			
		||||
  }
 | 
			
		||||
  async _handle(route) {
 | 
			
		||||
    const request = route.request();
 | 
			
		||||
    const response = await this._localUtils.harLookup({
 | 
			
		||||
      harId: this._harId,
 | 
			
		||||
      url: request.url(),
 | 
			
		||||
      method: request.method(),
 | 
			
		||||
      headers: await request.headersArray(),
 | 
			
		||||
      postData: request.postDataBuffer() || void 0,
 | 
			
		||||
      isNavigationRequest: request.isNavigationRequest()
 | 
			
		||||
    });
 | 
			
		||||
    if (response.action === "redirect") {
 | 
			
		||||
      route._platform.log("api", `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
 | 
			
		||||
      await route._redirectNavigationRequest(response.redirectURL);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (response.action === "fulfill") {
 | 
			
		||||
      if (response.status === -1)
 | 
			
		||||
        return;
 | 
			
		||||
      await route.fulfill({
 | 
			
		||||
        status: response.status,
 | 
			
		||||
        headers: Object.fromEntries(response.headers.map((h) => [h.name, h.value])),
 | 
			
		||||
        body: response.body
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (response.action === "error")
 | 
			
		||||
      route._platform.log("api", "HAR: " + response.message);
 | 
			
		||||
    if (this._notFoundAction === "abort") {
 | 
			
		||||
      await route.abort();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    await route.fallback();
 | 
			
		||||
  }
 | 
			
		||||
  async addContextRoute(context) {
 | 
			
		||||
    await context.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
 | 
			
		||||
  }
 | 
			
		||||
  async addPageRoute(page) {
 | 
			
		||||
    await page.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.dispose();
 | 
			
		||||
  }
 | 
			
		||||
  dispose() {
 | 
			
		||||
    this._localUtils.harClose({ harId: this._harId }).catch(() => {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  HarRouter
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										84
									
								
								node_modules/playwright-core/lib/client/input.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								node_modules/playwright-core/lib/client/input.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var input_exports = {};
 | 
			
		||||
__export(input_exports, {
 | 
			
		||||
  Keyboard: () => Keyboard,
 | 
			
		||||
  Mouse: () => Mouse,
 | 
			
		||||
  Touchscreen: () => Touchscreen
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(input_exports);
 | 
			
		||||
class Keyboard {
 | 
			
		||||
  constructor(page) {
 | 
			
		||||
    this._page = page;
 | 
			
		||||
  }
 | 
			
		||||
  async down(key) {
 | 
			
		||||
    await this._page._channel.keyboardDown({ key });
 | 
			
		||||
  }
 | 
			
		||||
  async up(key) {
 | 
			
		||||
    await this._page._channel.keyboardUp({ key });
 | 
			
		||||
  }
 | 
			
		||||
  async insertText(text) {
 | 
			
		||||
    await this._page._channel.keyboardInsertText({ text });
 | 
			
		||||
  }
 | 
			
		||||
  async type(text, options = {}) {
 | 
			
		||||
    await this._page._channel.keyboardType({ text, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async press(key, options = {}) {
 | 
			
		||||
    await this._page._channel.keyboardPress({ key, ...options });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class Mouse {
 | 
			
		||||
  constructor(page) {
 | 
			
		||||
    this._page = page;
 | 
			
		||||
  }
 | 
			
		||||
  async move(x, y, options = {}) {
 | 
			
		||||
    await this._page._channel.mouseMove({ x, y, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async down(options = {}) {
 | 
			
		||||
    await this._page._channel.mouseDown({ ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async up(options = {}) {
 | 
			
		||||
    await this._page._channel.mouseUp(options);
 | 
			
		||||
  }
 | 
			
		||||
  async click(x, y, options = {}) {
 | 
			
		||||
    await this._page._channel.mouseClick({ x, y, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async dblclick(x, y, options = {}) {
 | 
			
		||||
    await this._page._wrapApiCall(async () => {
 | 
			
		||||
      await this.click(x, y, { ...options, clickCount: 2 });
 | 
			
		||||
    }, { title: "Double click" });
 | 
			
		||||
  }
 | 
			
		||||
  async wheel(deltaX, deltaY) {
 | 
			
		||||
    await this._page._channel.mouseWheel({ deltaX, deltaY });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class Touchscreen {
 | 
			
		||||
  constructor(page) {
 | 
			
		||||
    this._page = page;
 | 
			
		||||
  }
 | 
			
		||||
  async tap(x, y) {
 | 
			
		||||
    await this._page._channel.touchscreenTap({ x, y });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  Keyboard,
 | 
			
		||||
  Mouse,
 | 
			
		||||
  Touchscreen
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										109
									
								
								node_modules/playwright-core/lib/client/jsHandle.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								node_modules/playwright-core/lib/client/jsHandle.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var jsHandle_exports = {};
 | 
			
		||||
__export(jsHandle_exports, {
 | 
			
		||||
  JSHandle: () => JSHandle,
 | 
			
		||||
  assertMaxArguments: () => assertMaxArguments,
 | 
			
		||||
  parseResult: () => parseResult,
 | 
			
		||||
  serializeArgument: () => serializeArgument
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(jsHandle_exports);
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_serializers = require("../protocol/serializers");
 | 
			
		||||
class JSHandle extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(handle) {
 | 
			
		||||
    return handle._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._preview = this._initializer.preview;
 | 
			
		||||
    this._channel.on("previewUpdated", ({ preview }) => this._preview = preview);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluate(pageFunction, arg) {
 | 
			
		||||
    const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: serializeArgument(arg) });
 | 
			
		||||
    return parseResult(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async _evaluateFunction(functionDeclaration) {
 | 
			
		||||
    const result = await this._channel.evaluateExpression({ expression: functionDeclaration, isFunction: true, arg: serializeArgument(void 0) });
 | 
			
		||||
    return parseResult(result.value);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluateHandle(pageFunction, arg) {
 | 
			
		||||
    const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: serializeArgument(arg) });
 | 
			
		||||
    return JSHandle.from(result.handle);
 | 
			
		||||
  }
 | 
			
		||||
  async getProperty(propertyName) {
 | 
			
		||||
    const result = await this._channel.getProperty({ name: propertyName });
 | 
			
		||||
    return JSHandle.from(result.handle);
 | 
			
		||||
  }
 | 
			
		||||
  async getProperties() {
 | 
			
		||||
    const map = /* @__PURE__ */ new Map();
 | 
			
		||||
    for (const { name, value } of (await this._channel.getPropertyList()).properties)
 | 
			
		||||
      map.set(name, JSHandle.from(value));
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
  async jsonValue() {
 | 
			
		||||
    return parseResult((await this._channel.jsonValue()).value);
 | 
			
		||||
  }
 | 
			
		||||
  asElement() {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.dispose();
 | 
			
		||||
  }
 | 
			
		||||
  async dispose() {
 | 
			
		||||
    try {
 | 
			
		||||
      await this._channel.dispose();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if ((0, import_errors.isTargetClosedError)(e))
 | 
			
		||||
        return;
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  toString() {
 | 
			
		||||
    return this._preview;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function serializeArgument(arg) {
 | 
			
		||||
  const handles = [];
 | 
			
		||||
  const pushHandle = (channel) => {
 | 
			
		||||
    handles.push(channel);
 | 
			
		||||
    return handles.length - 1;
 | 
			
		||||
  };
 | 
			
		||||
  const value = (0, import_serializers.serializeValue)(arg, (value2) => {
 | 
			
		||||
    if (value2 instanceof JSHandle)
 | 
			
		||||
      return { h: pushHandle(value2._channel) };
 | 
			
		||||
    return { fallThrough: value2 };
 | 
			
		||||
  });
 | 
			
		||||
  return { value, handles };
 | 
			
		||||
}
 | 
			
		||||
function parseResult(value) {
 | 
			
		||||
  return (0, import_serializers.parseSerializedValue)(value, void 0);
 | 
			
		||||
}
 | 
			
		||||
function assertMaxArguments(count, max) {
 | 
			
		||||
  if (count > max)
 | 
			
		||||
    throw new Error("Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.");
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  JSHandle,
 | 
			
		||||
  assertMaxArguments,
 | 
			
		||||
  parseResult,
 | 
			
		||||
  serializeArgument
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										39
									
								
								node_modules/playwright-core/lib/client/jsonPipe.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								node_modules/playwright-core/lib/client/jsonPipe.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var jsonPipe_exports = {};
 | 
			
		||||
__export(jsonPipe_exports, {
 | 
			
		||||
  JsonPipe: () => JsonPipe
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(jsonPipe_exports);
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
class JsonPipe extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(jsonPipe) {
 | 
			
		||||
    return jsonPipe._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
  }
 | 
			
		||||
  channel() {
 | 
			
		||||
    return this._channel;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  JsonPipe
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										60
									
								
								node_modules/playwright-core/lib/client/localUtils.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								node_modules/playwright-core/lib/client/localUtils.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var localUtils_exports = {};
 | 
			
		||||
__export(localUtils_exports, {
 | 
			
		||||
  LocalUtils: () => LocalUtils
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(localUtils_exports);
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
class LocalUtils extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this.devices = {};
 | 
			
		||||
    for (const { name, descriptor } of initializer.deviceDescriptors)
 | 
			
		||||
      this.devices[name] = descriptor;
 | 
			
		||||
  }
 | 
			
		||||
  async zip(params) {
 | 
			
		||||
    return await this._channel.zip(params);
 | 
			
		||||
  }
 | 
			
		||||
  async harOpen(params) {
 | 
			
		||||
    return await this._channel.harOpen(params);
 | 
			
		||||
  }
 | 
			
		||||
  async harLookup(params) {
 | 
			
		||||
    return await this._channel.harLookup(params);
 | 
			
		||||
  }
 | 
			
		||||
  async harClose(params) {
 | 
			
		||||
    return await this._channel.harClose(params);
 | 
			
		||||
  }
 | 
			
		||||
  async harUnzip(params) {
 | 
			
		||||
    return await this._channel.harUnzip(params);
 | 
			
		||||
  }
 | 
			
		||||
  async tracingStarted(params) {
 | 
			
		||||
    return await this._channel.tracingStarted(params);
 | 
			
		||||
  }
 | 
			
		||||
  async traceDiscarded(params) {
 | 
			
		||||
    return await this._channel.traceDiscarded(params);
 | 
			
		||||
  }
 | 
			
		||||
  async addStackToTracingNoReply(params) {
 | 
			
		||||
    return await this._channel.addStackToTracingNoReply(params);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  LocalUtils
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										366
									
								
								node_modules/playwright-core/lib/client/locator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								node_modules/playwright-core/lib/client/locator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,366 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var locator_exports = {};
 | 
			
		||||
__export(locator_exports, {
 | 
			
		||||
  FrameLocator: () => FrameLocator,
 | 
			
		||||
  Locator: () => Locator,
 | 
			
		||||
  setTestIdAttribute: () => setTestIdAttribute,
 | 
			
		||||
  testIdAttributeName: () => testIdAttributeName
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(locator_exports);
 | 
			
		||||
var import_elementHandle = require("./elementHandle");
 | 
			
		||||
var import_locatorGenerators = require("../utils/isomorphic/locatorGenerators");
 | 
			
		||||
var import_locatorUtils = require("../utils/isomorphic/locatorUtils");
 | 
			
		||||
var import_stringUtils = require("../utils/isomorphic/stringUtils");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
var import_time = require("../utils/isomorphic/time");
 | 
			
		||||
class Locator {
 | 
			
		||||
  constructor(frame, selector, options) {
 | 
			
		||||
    this._frame = frame;
 | 
			
		||||
    this._selector = selector;
 | 
			
		||||
    if (options?.hasText)
 | 
			
		||||
      this._selector += ` >> internal:has-text=${(0, import_stringUtils.escapeForTextSelector)(options.hasText, false)}`;
 | 
			
		||||
    if (options?.hasNotText)
 | 
			
		||||
      this._selector += ` >> internal:has-not-text=${(0, import_stringUtils.escapeForTextSelector)(options.hasNotText, false)}`;
 | 
			
		||||
    if (options?.has) {
 | 
			
		||||
      const locator = options.has;
 | 
			
		||||
      if (locator._frame !== frame)
 | 
			
		||||
        throw new Error(`Inner "has" locator must belong to the same frame.`);
 | 
			
		||||
      this._selector += ` >> internal:has=` + JSON.stringify(locator._selector);
 | 
			
		||||
    }
 | 
			
		||||
    if (options?.hasNot) {
 | 
			
		||||
      const locator = options.hasNot;
 | 
			
		||||
      if (locator._frame !== frame)
 | 
			
		||||
        throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
 | 
			
		||||
      this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector);
 | 
			
		||||
    }
 | 
			
		||||
    if (options?.visible !== void 0)
 | 
			
		||||
      this._selector += ` >> visible=${options.visible ? "true" : "false"}`;
 | 
			
		||||
    if (this._frame._platform.inspectCustom)
 | 
			
		||||
      this[this._frame._platform.inspectCustom] = () => this._inspect();
 | 
			
		||||
  }
 | 
			
		||||
  async _withElement(task, options) {
 | 
			
		||||
    const timeout = this._frame._timeout({ timeout: options.timeout });
 | 
			
		||||
    const deadline = timeout ? (0, import_time.monotonicTime)() + timeout : 0;
 | 
			
		||||
    return await this._frame._wrapApiCall(async () => {
 | 
			
		||||
      const result = await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, state: "attached", timeout });
 | 
			
		||||
      const handle = import_elementHandle.ElementHandle.fromNullable(result.element);
 | 
			
		||||
      if (!handle)
 | 
			
		||||
        throw new Error(`Could not resolve ${this._selector} to DOM Element`);
 | 
			
		||||
      try {
 | 
			
		||||
        return await task(handle, deadline ? deadline - (0, import_time.monotonicTime)() : 0);
 | 
			
		||||
      } finally {
 | 
			
		||||
        await handle.dispose();
 | 
			
		||||
      }
 | 
			
		||||
    }, { title: options.title, internal: options.internal });
 | 
			
		||||
  }
 | 
			
		||||
  _equals(locator) {
 | 
			
		||||
    return this._frame === locator._frame && this._selector === locator._selector;
 | 
			
		||||
  }
 | 
			
		||||
  page() {
 | 
			
		||||
    return this._frame.page();
 | 
			
		||||
  }
 | 
			
		||||
  async boundingBox(options) {
 | 
			
		||||
    return await this._withElement((h) => h.boundingBox(), { title: "Bounding box", timeout: options?.timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async check(options = {}) {
 | 
			
		||||
    return await this._frame.check(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async click(options = {}) {
 | 
			
		||||
    return await this._frame.click(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async dblclick(options = {}) {
 | 
			
		||||
    await this._frame.dblclick(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async dispatchEvent(type, eventInit = {}, options) {
 | 
			
		||||
    return await this._frame.dispatchEvent(this._selector, type, eventInit, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async dragTo(target, options = {}) {
 | 
			
		||||
    return await this._frame.dragAndDrop(this._selector, target._selector, {
 | 
			
		||||
      strict: true,
 | 
			
		||||
      ...options
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async evaluate(pageFunction, arg, options) {
 | 
			
		||||
    return await this._withElement((h) => h.evaluate(pageFunction, arg), { title: "Evaluate", timeout: options?.timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async _evaluateFunction(functionDeclaration, options) {
 | 
			
		||||
    return await this._withElement((h) => h._evaluateFunction(functionDeclaration), { title: "Evaluate", timeout: options?.timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async evaluateAll(pageFunction, arg) {
 | 
			
		||||
    return await this._frame.$$eval(this._selector, pageFunction, arg);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluateHandle(pageFunction, arg, options) {
 | 
			
		||||
    return await this._withElement((h) => h.evaluateHandle(pageFunction, arg), { title: "Evaluate", timeout: options?.timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async fill(value, options = {}) {
 | 
			
		||||
    return await this._frame.fill(this._selector, value, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async clear(options = {}) {
 | 
			
		||||
    await this._frame._wrapApiCall(() => this.fill("", options), { title: "Clear" });
 | 
			
		||||
  }
 | 
			
		||||
  async _highlight() {
 | 
			
		||||
    return await this._frame._highlight(this._selector);
 | 
			
		||||
  }
 | 
			
		||||
  async highlight() {
 | 
			
		||||
    return await this._frame._highlight(this._selector);
 | 
			
		||||
  }
 | 
			
		||||
  locator(selectorOrLocator, options) {
 | 
			
		||||
    if ((0, import_rtti.isString)(selectorOrLocator))
 | 
			
		||||
      return new Locator(this._frame, this._selector + " >> " + selectorOrLocator, options);
 | 
			
		||||
    if (selectorOrLocator._frame !== this._frame)
 | 
			
		||||
      throw new Error(`Locators must belong to the same frame.`);
 | 
			
		||||
    return new Locator(this._frame, this._selector + " >> internal:chain=" + JSON.stringify(selectorOrLocator._selector), options);
 | 
			
		||||
  }
 | 
			
		||||
  getByTestId(testId) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
 | 
			
		||||
  }
 | 
			
		||||
  getByAltText(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByLabel(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByPlaceholder(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByText(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByTitle(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByRole(role, options = {}) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
 | 
			
		||||
  }
 | 
			
		||||
  frameLocator(selector) {
 | 
			
		||||
    return new FrameLocator(this._frame, this._selector + " >> " + selector);
 | 
			
		||||
  }
 | 
			
		||||
  filter(options) {
 | 
			
		||||
    return new Locator(this._frame, this._selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async elementHandle(options) {
 | 
			
		||||
    return await this._frame.waitForSelector(this._selector, { strict: true, state: "attached", ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async elementHandles() {
 | 
			
		||||
    return await this._frame.$$(this._selector);
 | 
			
		||||
  }
 | 
			
		||||
  contentFrame() {
 | 
			
		||||
    return new FrameLocator(this._frame, this._selector);
 | 
			
		||||
  }
 | 
			
		||||
  describe(description) {
 | 
			
		||||
    return new Locator(this._frame, this._selector + " >> internal:describe=" + JSON.stringify(description));
 | 
			
		||||
  }
 | 
			
		||||
  first() {
 | 
			
		||||
    return new Locator(this._frame, this._selector + " >> nth=0");
 | 
			
		||||
  }
 | 
			
		||||
  last() {
 | 
			
		||||
    return new Locator(this._frame, this._selector + ` >> nth=-1`);
 | 
			
		||||
  }
 | 
			
		||||
  nth(index) {
 | 
			
		||||
    return new Locator(this._frame, this._selector + ` >> nth=${index}`);
 | 
			
		||||
  }
 | 
			
		||||
  and(locator) {
 | 
			
		||||
    if (locator._frame !== this._frame)
 | 
			
		||||
      throw new Error(`Locators must belong to the same frame.`);
 | 
			
		||||
    return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(locator._selector));
 | 
			
		||||
  }
 | 
			
		||||
  or(locator) {
 | 
			
		||||
    if (locator._frame !== this._frame)
 | 
			
		||||
      throw new Error(`Locators must belong to the same frame.`);
 | 
			
		||||
    return new Locator(this._frame, this._selector + ` >> internal:or=` + JSON.stringify(locator._selector));
 | 
			
		||||
  }
 | 
			
		||||
  async focus(options) {
 | 
			
		||||
    return await this._frame.focus(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async blur(options) {
 | 
			
		||||
    await this._frame._channel.blur({ selector: this._selector, strict: true, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  // options are only here for testing
 | 
			
		||||
  async count(_options) {
 | 
			
		||||
    return await this._frame._queryCount(this._selector, _options);
 | 
			
		||||
  }
 | 
			
		||||
  async _resolveSelector() {
 | 
			
		||||
    return await this._frame._channel.resolveSelector({ selector: this._selector });
 | 
			
		||||
  }
 | 
			
		||||
  async getAttribute(name, options) {
 | 
			
		||||
    return await this._frame.getAttribute(this._selector, name, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async hover(options = {}) {
 | 
			
		||||
    return await this._frame.hover(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async innerHTML(options) {
 | 
			
		||||
    return await this._frame.innerHTML(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async innerText(options) {
 | 
			
		||||
    return await this._frame.innerText(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async inputValue(options) {
 | 
			
		||||
    return await this._frame.inputValue(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async isChecked(options) {
 | 
			
		||||
    return await this._frame.isChecked(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async isDisabled(options) {
 | 
			
		||||
    return await this._frame.isDisabled(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async isEditable(options) {
 | 
			
		||||
    return await this._frame.isEditable(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async isEnabled(options) {
 | 
			
		||||
    return await this._frame.isEnabled(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async isHidden(options) {
 | 
			
		||||
    return await this._frame.isHidden(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async isVisible(options) {
 | 
			
		||||
    return await this._frame.isVisible(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async press(key, options = {}) {
 | 
			
		||||
    return await this._frame.press(this._selector, key, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async screenshot(options = {}) {
 | 
			
		||||
    const mask = options.mask;
 | 
			
		||||
    return await this._withElement((h, timeout) => h.screenshot({ ...options, mask, timeout }), { title: "Screenshot", timeout: options.timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async ariaSnapshot(options) {
 | 
			
		||||
    const result = await this._frame._channel.ariaSnapshot({ ...options, selector: this._selector, timeout: this._frame._timeout(options) });
 | 
			
		||||
    return result.snapshot;
 | 
			
		||||
  }
 | 
			
		||||
  async scrollIntoViewIfNeeded(options = {}) {
 | 
			
		||||
    return await this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({ ...options, timeout }), { title: "Scroll into view", timeout: options.timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async selectOption(values, options = {}) {
 | 
			
		||||
    return await this._frame.selectOption(this._selector, values, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async selectText(options = {}) {
 | 
			
		||||
    return await this._withElement((h, timeout) => h.selectText({ ...options, timeout }), { title: "Select text", timeout: options.timeout });
 | 
			
		||||
  }
 | 
			
		||||
  async setChecked(checked, options) {
 | 
			
		||||
    if (checked)
 | 
			
		||||
      await this.check(options);
 | 
			
		||||
    else
 | 
			
		||||
      await this.uncheck(options);
 | 
			
		||||
  }
 | 
			
		||||
  async setInputFiles(files, options = {}) {
 | 
			
		||||
    return await this._frame.setInputFiles(this._selector, files, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async tap(options = {}) {
 | 
			
		||||
    return await this._frame.tap(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async textContent(options) {
 | 
			
		||||
    return await this._frame.textContent(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async type(text, options = {}) {
 | 
			
		||||
    return await this._frame.type(this._selector, text, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async pressSequentially(text, options = {}) {
 | 
			
		||||
    return await this.type(text, options);
 | 
			
		||||
  }
 | 
			
		||||
  async uncheck(options = {}) {
 | 
			
		||||
    return await this._frame.uncheck(this._selector, { strict: true, ...options });
 | 
			
		||||
  }
 | 
			
		||||
  async all() {
 | 
			
		||||
    return new Array(await this.count()).fill(0).map((e, i) => this.nth(i));
 | 
			
		||||
  }
 | 
			
		||||
  async allInnerTexts() {
 | 
			
		||||
    return await this._frame.$$eval(this._selector, (ee) => ee.map((e) => e.innerText));
 | 
			
		||||
  }
 | 
			
		||||
  async allTextContents() {
 | 
			
		||||
    return await this._frame.$$eval(this._selector, (ee) => ee.map((e) => e.textContent || ""));
 | 
			
		||||
  }
 | 
			
		||||
  async waitFor(options) {
 | 
			
		||||
    await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options, timeout: this._frame._timeout(options) });
 | 
			
		||||
  }
 | 
			
		||||
  async _expect(expression, options) {
 | 
			
		||||
    return this._frame._expect(expression, {
 | 
			
		||||
      ...options,
 | 
			
		||||
      selector: this._selector
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  _inspect() {
 | 
			
		||||
    return this.toString();
 | 
			
		||||
  }
 | 
			
		||||
  toString() {
 | 
			
		||||
    return (0, import_locatorGenerators.asLocator)("javascript", this._selector);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class FrameLocator {
 | 
			
		||||
  constructor(frame, selector) {
 | 
			
		||||
    this._frame = frame;
 | 
			
		||||
    this._frameSelector = selector;
 | 
			
		||||
  }
 | 
			
		||||
  locator(selectorOrLocator, options) {
 | 
			
		||||
    if ((0, import_rtti.isString)(selectorOrLocator))
 | 
			
		||||
      return new Locator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selectorOrLocator, options);
 | 
			
		||||
    if (selectorOrLocator._frame !== this._frame)
 | 
			
		||||
      throw new Error(`Locators must belong to the same frame.`);
 | 
			
		||||
    return new Locator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selectorOrLocator._selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByTestId(testId) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
 | 
			
		||||
  }
 | 
			
		||||
  getByAltText(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByLabel(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByPlaceholder(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByText(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByTitle(text, options) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
 | 
			
		||||
  }
 | 
			
		||||
  getByRole(role, options = {}) {
 | 
			
		||||
    return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
 | 
			
		||||
  }
 | 
			
		||||
  owner() {
 | 
			
		||||
    return new Locator(this._frame, this._frameSelector);
 | 
			
		||||
  }
 | 
			
		||||
  frameLocator(selector) {
 | 
			
		||||
    return new FrameLocator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selector);
 | 
			
		||||
  }
 | 
			
		||||
  first() {
 | 
			
		||||
    return new FrameLocator(this._frame, this._frameSelector + " >> nth=0");
 | 
			
		||||
  }
 | 
			
		||||
  last() {
 | 
			
		||||
    return new FrameLocator(this._frame, this._frameSelector + ` >> nth=-1`);
 | 
			
		||||
  }
 | 
			
		||||
  nth(index) {
 | 
			
		||||
    return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
let _testIdAttributeName = "data-testid";
 | 
			
		||||
function testIdAttributeName() {
 | 
			
		||||
  return _testIdAttributeName;
 | 
			
		||||
}
 | 
			
		||||
function setTestIdAttribute(attributeName) {
 | 
			
		||||
  _testIdAttributeName = attributeName;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  FrameLocator,
 | 
			
		||||
  Locator,
 | 
			
		||||
  setTestIdAttribute,
 | 
			
		||||
  testIdAttributeName
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										744
									
								
								node_modules/playwright-core/lib/client/network.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										744
									
								
								node_modules/playwright-core/lib/client/network.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,744 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var network_exports = {};
 | 
			
		||||
__export(network_exports, {
 | 
			
		||||
  RawHeaders: () => RawHeaders,
 | 
			
		||||
  Request: () => Request,
 | 
			
		||||
  Response: () => Response,
 | 
			
		||||
  Route: () => Route,
 | 
			
		||||
  RouteHandler: () => RouteHandler,
 | 
			
		||||
  WebSocket: () => WebSocket,
 | 
			
		||||
  WebSocketRoute: () => WebSocketRoute,
 | 
			
		||||
  WebSocketRouteHandler: () => WebSocketRouteHandler,
 | 
			
		||||
  validateHeaders: () => validateHeaders
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(network_exports);
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_fetch = require("./fetch");
 | 
			
		||||
var import_frame = require("./frame");
 | 
			
		||||
var import_waiter = require("./waiter");
 | 
			
		||||
var import_worker = require("./worker");
 | 
			
		||||
var import_assert = require("../utils/isomorphic/assert");
 | 
			
		||||
var import_headers = require("../utils/isomorphic/headers");
 | 
			
		||||
var import_urlMatch = require("../utils/isomorphic/urlMatch");
 | 
			
		||||
var import_manualPromise = require("../utils/isomorphic/manualPromise");
 | 
			
		||||
var import_multimap = require("../utils/isomorphic/multimap");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
var import_stackTrace = require("../utils/isomorphic/stackTrace");
 | 
			
		||||
var import_mimeType = require("../utils/isomorphic/mimeType");
 | 
			
		||||
class Request extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._redirectedFrom = null;
 | 
			
		||||
    this._redirectedTo = null;
 | 
			
		||||
    this._failureText = null;
 | 
			
		||||
    this._fallbackOverrides = {};
 | 
			
		||||
    this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
 | 
			
		||||
    if (this._redirectedFrom)
 | 
			
		||||
      this._redirectedFrom._redirectedTo = this;
 | 
			
		||||
    this._provisionalHeaders = new RawHeaders(initializer.headers);
 | 
			
		||||
    this._timing = {
 | 
			
		||||
      startTime: 0,
 | 
			
		||||
      domainLookupStart: -1,
 | 
			
		||||
      domainLookupEnd: -1,
 | 
			
		||||
      connectStart: -1,
 | 
			
		||||
      secureConnectionStart: -1,
 | 
			
		||||
      connectEnd: -1,
 | 
			
		||||
      requestStart: -1,
 | 
			
		||||
      responseStart: -1,
 | 
			
		||||
      responseEnd: -1
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  static from(request) {
 | 
			
		||||
    return request._object;
 | 
			
		||||
  }
 | 
			
		||||
  static fromNullable(request) {
 | 
			
		||||
    return request ? Request.from(request) : null;
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._fallbackOverrides.url || this._initializer.url;
 | 
			
		||||
  }
 | 
			
		||||
  resourceType() {
 | 
			
		||||
    return this._initializer.resourceType;
 | 
			
		||||
  }
 | 
			
		||||
  method() {
 | 
			
		||||
    return this._fallbackOverrides.method || this._initializer.method;
 | 
			
		||||
  }
 | 
			
		||||
  postData() {
 | 
			
		||||
    return (this._fallbackOverrides.postDataBuffer || this._initializer.postData)?.toString("utf-8") || null;
 | 
			
		||||
  }
 | 
			
		||||
  postDataBuffer() {
 | 
			
		||||
    return this._fallbackOverrides.postDataBuffer || this._initializer.postData || null;
 | 
			
		||||
  }
 | 
			
		||||
  postDataJSON() {
 | 
			
		||||
    const postData = this.postData();
 | 
			
		||||
    if (!postData)
 | 
			
		||||
      return null;
 | 
			
		||||
    const contentType = this.headers()["content-type"];
 | 
			
		||||
    if (contentType?.includes("application/x-www-form-urlencoded")) {
 | 
			
		||||
      const entries = {};
 | 
			
		||||
      const parsed = new URLSearchParams(postData);
 | 
			
		||||
      for (const [k, v] of parsed.entries())
 | 
			
		||||
        entries[k] = v;
 | 
			
		||||
      return entries;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      return JSON.parse(postData);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new Error("POST data is not a valid JSON object: " + postData);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @deprecated
 | 
			
		||||
   */
 | 
			
		||||
  headers() {
 | 
			
		||||
    if (this._fallbackOverrides.headers)
 | 
			
		||||
      return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers();
 | 
			
		||||
    return this._provisionalHeaders.headers();
 | 
			
		||||
  }
 | 
			
		||||
  async _actualHeaders() {
 | 
			
		||||
    if (this._fallbackOverrides.headers)
 | 
			
		||||
      return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers);
 | 
			
		||||
    if (!this._actualHeadersPromise) {
 | 
			
		||||
      this._actualHeadersPromise = this._wrapApiCall(async () => {
 | 
			
		||||
        return new RawHeaders((await this._channel.rawRequestHeaders()).headers);
 | 
			
		||||
      }, { internal: true });
 | 
			
		||||
    }
 | 
			
		||||
    return await this._actualHeadersPromise;
 | 
			
		||||
  }
 | 
			
		||||
  async allHeaders() {
 | 
			
		||||
    return (await this._actualHeaders()).headers();
 | 
			
		||||
  }
 | 
			
		||||
  async headersArray() {
 | 
			
		||||
    return (await this._actualHeaders()).headersArray();
 | 
			
		||||
  }
 | 
			
		||||
  async headerValue(name) {
 | 
			
		||||
    return (await this._actualHeaders()).get(name);
 | 
			
		||||
  }
 | 
			
		||||
  async response() {
 | 
			
		||||
    return Response.fromNullable((await this._channel.response()).response);
 | 
			
		||||
  }
 | 
			
		||||
  async _internalResponse() {
 | 
			
		||||
    return Response.fromNullable((await this._channel.response()).response);
 | 
			
		||||
  }
 | 
			
		||||
  frame() {
 | 
			
		||||
    if (!this._initializer.frame) {
 | 
			
		||||
      (0, import_assert.assert)(this.serviceWorker());
 | 
			
		||||
      throw new Error("Service Worker requests do not have an associated frame.");
 | 
			
		||||
    }
 | 
			
		||||
    const frame = import_frame.Frame.from(this._initializer.frame);
 | 
			
		||||
    if (!frame._page) {
 | 
			
		||||
      throw new Error([
 | 
			
		||||
        "Frame for this navigation request is not available, because the request",
 | 
			
		||||
        "was issued before the frame is created. You can check whether the request",
 | 
			
		||||
        "is a navigation request by calling isNavigationRequest() method."
 | 
			
		||||
      ].join("\n"));
 | 
			
		||||
    }
 | 
			
		||||
    return frame;
 | 
			
		||||
  }
 | 
			
		||||
  _safePage() {
 | 
			
		||||
    return import_frame.Frame.fromNullable(this._initializer.frame)?._page || null;
 | 
			
		||||
  }
 | 
			
		||||
  serviceWorker() {
 | 
			
		||||
    return this._initializer.serviceWorker ? import_worker.Worker.from(this._initializer.serviceWorker) : null;
 | 
			
		||||
  }
 | 
			
		||||
  isNavigationRequest() {
 | 
			
		||||
    return this._initializer.isNavigationRequest;
 | 
			
		||||
  }
 | 
			
		||||
  redirectedFrom() {
 | 
			
		||||
    return this._redirectedFrom;
 | 
			
		||||
  }
 | 
			
		||||
  redirectedTo() {
 | 
			
		||||
    return this._redirectedTo;
 | 
			
		||||
  }
 | 
			
		||||
  failure() {
 | 
			
		||||
    if (this._failureText === null)
 | 
			
		||||
      return null;
 | 
			
		||||
    return {
 | 
			
		||||
      errorText: this._failureText
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  timing() {
 | 
			
		||||
    return this._timing;
 | 
			
		||||
  }
 | 
			
		||||
  async sizes() {
 | 
			
		||||
    const response = await this.response();
 | 
			
		||||
    if (!response)
 | 
			
		||||
      throw new Error("Unable to fetch sizes for failed request");
 | 
			
		||||
    return (await response._channel.sizes()).sizes;
 | 
			
		||||
  }
 | 
			
		||||
  _setResponseEndTiming(responseEndTiming) {
 | 
			
		||||
    this._timing.responseEnd = responseEndTiming;
 | 
			
		||||
    if (this._timing.responseStart === -1)
 | 
			
		||||
      this._timing.responseStart = responseEndTiming;
 | 
			
		||||
  }
 | 
			
		||||
  _finalRequest() {
 | 
			
		||||
    return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
 | 
			
		||||
  }
 | 
			
		||||
  _applyFallbackOverrides(overrides) {
 | 
			
		||||
    if (overrides.url)
 | 
			
		||||
      this._fallbackOverrides.url = overrides.url;
 | 
			
		||||
    if (overrides.method)
 | 
			
		||||
      this._fallbackOverrides.method = overrides.method;
 | 
			
		||||
    if (overrides.headers)
 | 
			
		||||
      this._fallbackOverrides.headers = overrides.headers;
 | 
			
		||||
    if ((0, import_rtti.isString)(overrides.postData))
 | 
			
		||||
      this._fallbackOverrides.postDataBuffer = Buffer.from(overrides.postData, "utf-8");
 | 
			
		||||
    else if (overrides.postData instanceof Buffer)
 | 
			
		||||
      this._fallbackOverrides.postDataBuffer = overrides.postData;
 | 
			
		||||
    else if (overrides.postData)
 | 
			
		||||
      this._fallbackOverrides.postDataBuffer = Buffer.from(JSON.stringify(overrides.postData), "utf-8");
 | 
			
		||||
  }
 | 
			
		||||
  _fallbackOverridesForContinue() {
 | 
			
		||||
    return this._fallbackOverrides;
 | 
			
		||||
  }
 | 
			
		||||
  _targetClosedScope() {
 | 
			
		||||
    return this.serviceWorker()?._closedScope || this._safePage()?._closedOrCrashedScope || new import_manualPromise.LongStandingScope();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class Route extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._handlingPromise = null;
 | 
			
		||||
    this._didThrow = false;
 | 
			
		||||
  }
 | 
			
		||||
  static from(route) {
 | 
			
		||||
    return route._object;
 | 
			
		||||
  }
 | 
			
		||||
  request() {
 | 
			
		||||
    return Request.from(this._initializer.request);
 | 
			
		||||
  }
 | 
			
		||||
  async _raceWithTargetClose(promise) {
 | 
			
		||||
    return await this.request()._targetClosedScope().safeRace(promise);
 | 
			
		||||
  }
 | 
			
		||||
  async _startHandling() {
 | 
			
		||||
    this._handlingPromise = new import_manualPromise.ManualPromise();
 | 
			
		||||
    return await this._handlingPromise;
 | 
			
		||||
  }
 | 
			
		||||
  async fallback(options = {}) {
 | 
			
		||||
    this._checkNotHandled();
 | 
			
		||||
    this.request()._applyFallbackOverrides(options);
 | 
			
		||||
    this._reportHandled(false);
 | 
			
		||||
  }
 | 
			
		||||
  async abort(errorCode) {
 | 
			
		||||
    await this._handleRoute(async () => {
 | 
			
		||||
      await this._raceWithTargetClose(this._channel.abort({ errorCode }));
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async _redirectNavigationRequest(url) {
 | 
			
		||||
    await this._handleRoute(async () => {
 | 
			
		||||
      await this._raceWithTargetClose(this._channel.redirectNavigationRequest({ url }));
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async fetch(options = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async fulfill(options = {}) {
 | 
			
		||||
    await this._handleRoute(async () => {
 | 
			
		||||
      await this._innerFulfill(options);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async _handleRoute(callback) {
 | 
			
		||||
    this._checkNotHandled();
 | 
			
		||||
    try {
 | 
			
		||||
      await callback();
 | 
			
		||||
      this._reportHandled(true);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      this._didThrow = true;
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  async _innerFulfill(options = {}) {
 | 
			
		||||
    let fetchResponseUid;
 | 
			
		||||
    let { status: statusOption, headers: headersOption, body } = options;
 | 
			
		||||
    if (options.json !== void 0) {
 | 
			
		||||
      (0, import_assert.assert)(options.body === void 0, "Can specify either body or json parameters");
 | 
			
		||||
      body = JSON.stringify(options.json);
 | 
			
		||||
    }
 | 
			
		||||
    if (options.response instanceof import_fetch.APIResponse) {
 | 
			
		||||
      statusOption ??= options.response.status();
 | 
			
		||||
      headersOption ??= options.response.headers();
 | 
			
		||||
      if (body === void 0 && options.path === void 0) {
 | 
			
		||||
        if (options.response._request._connection === this._connection)
 | 
			
		||||
          fetchResponseUid = options.response._fetchUid();
 | 
			
		||||
        else
 | 
			
		||||
          body = await options.response.body();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    let isBase64 = false;
 | 
			
		||||
    let length = 0;
 | 
			
		||||
    if (options.path) {
 | 
			
		||||
      const buffer = await this._platform.fs().promises.readFile(options.path);
 | 
			
		||||
      body = buffer.toString("base64");
 | 
			
		||||
      isBase64 = true;
 | 
			
		||||
      length = buffer.length;
 | 
			
		||||
    } else if ((0, import_rtti.isString)(body)) {
 | 
			
		||||
      isBase64 = false;
 | 
			
		||||
      length = Buffer.byteLength(body);
 | 
			
		||||
    } else if (body) {
 | 
			
		||||
      length = body.length;
 | 
			
		||||
      body = body.toString("base64");
 | 
			
		||||
      isBase64 = true;
 | 
			
		||||
    }
 | 
			
		||||
    const headers = {};
 | 
			
		||||
    for (const header of Object.keys(headersOption || {}))
 | 
			
		||||
      headers[header.toLowerCase()] = String(headersOption[header]);
 | 
			
		||||
    if (options.contentType)
 | 
			
		||||
      headers["content-type"] = String(options.contentType);
 | 
			
		||||
    else if (options.json)
 | 
			
		||||
      headers["content-type"] = "application/json";
 | 
			
		||||
    else if (options.path)
 | 
			
		||||
      headers["content-type"] = (0, import_mimeType.getMimeTypeForPath)(options.path) || "application/octet-stream";
 | 
			
		||||
    if (length && !("content-length" in headers))
 | 
			
		||||
      headers["content-length"] = String(length);
 | 
			
		||||
    await this._raceWithTargetClose(this._channel.fulfill({
 | 
			
		||||
      status: statusOption || 200,
 | 
			
		||||
      headers: (0, import_headers.headersObjectToArray)(headers),
 | 
			
		||||
      body,
 | 
			
		||||
      isBase64,
 | 
			
		||||
      fetchResponseUid
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
  async continue(options = {}) {
 | 
			
		||||
    await this._handleRoute(async () => {
 | 
			
		||||
      this.request()._applyFallbackOverrides(options);
 | 
			
		||||
      await this._innerContinue(
 | 
			
		||||
        false
 | 
			
		||||
        /* isFallback */
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  _checkNotHandled() {
 | 
			
		||||
    if (!this._handlingPromise)
 | 
			
		||||
      throw new Error("Route is already handled!");
 | 
			
		||||
  }
 | 
			
		||||
  _reportHandled(done) {
 | 
			
		||||
    const chain = this._handlingPromise;
 | 
			
		||||
    this._handlingPromise = null;
 | 
			
		||||
    chain.resolve(done);
 | 
			
		||||
  }
 | 
			
		||||
  async _innerContinue(isFallback) {
 | 
			
		||||
    const options = this.request()._fallbackOverridesForContinue();
 | 
			
		||||
    return await this._raceWithTargetClose(this._channel.continue({
 | 
			
		||||
      url: options.url,
 | 
			
		||||
      method: options.method,
 | 
			
		||||
      headers: options.headers ? (0, import_headers.headersObjectToArray)(options.headers) : void 0,
 | 
			
		||||
      postData: options.postDataBuffer,
 | 
			
		||||
      isFallback
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class WebSocketRoute extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._connected = false;
 | 
			
		||||
    this._server = {
 | 
			
		||||
      onMessage: (handler) => {
 | 
			
		||||
        this._onServerMessage = handler;
 | 
			
		||||
      },
 | 
			
		||||
      onClose: (handler) => {
 | 
			
		||||
        this._onServerClose = handler;
 | 
			
		||||
      },
 | 
			
		||||
      connectToServer: () => {
 | 
			
		||||
        throw new Error(`connectToServer must be called on the page-side WebSocketRoute`);
 | 
			
		||||
      },
 | 
			
		||||
      url: () => {
 | 
			
		||||
        return this._initializer.url;
 | 
			
		||||
      },
 | 
			
		||||
      close: async (options = {}) => {
 | 
			
		||||
        await this._channel.closeServer({ ...options, wasClean: true }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      send: (message) => {
 | 
			
		||||
        if ((0, import_rtti.isString)(message))
 | 
			
		||||
          this._channel.sendToServer({ message, isBase64: false }).catch(() => {
 | 
			
		||||
          });
 | 
			
		||||
        else
 | 
			
		||||
          this._channel.sendToServer({ message: message.toString("base64"), isBase64: true }).catch(() => {
 | 
			
		||||
          });
 | 
			
		||||
      },
 | 
			
		||||
      async [Symbol.asyncDispose]() {
 | 
			
		||||
        await this.close();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    this._channel.on("messageFromPage", ({ message, isBase64 }) => {
 | 
			
		||||
      if (this._onPageMessage)
 | 
			
		||||
        this._onPageMessage(isBase64 ? Buffer.from(message, "base64") : message);
 | 
			
		||||
      else if (this._connected)
 | 
			
		||||
        this._channel.sendToServer({ message, isBase64 }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("messageFromServer", ({ message, isBase64 }) => {
 | 
			
		||||
      if (this._onServerMessage)
 | 
			
		||||
        this._onServerMessage(isBase64 ? Buffer.from(message, "base64") : message);
 | 
			
		||||
      else
 | 
			
		||||
        this._channel.sendToPage({ message, isBase64 }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("closePage", ({ code, reason, wasClean }) => {
 | 
			
		||||
      if (this._onPageClose)
 | 
			
		||||
        this._onPageClose(code, reason);
 | 
			
		||||
      else
 | 
			
		||||
        this._channel.closeServer({ code, reason, wasClean }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("closeServer", ({ code, reason, wasClean }) => {
 | 
			
		||||
      if (this._onServerClose)
 | 
			
		||||
        this._onServerClose(code, reason);
 | 
			
		||||
      else
 | 
			
		||||
        this._channel.closePage({ code, reason, wasClean }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  static from(route) {
 | 
			
		||||
    return route._object;
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._initializer.url;
 | 
			
		||||
  }
 | 
			
		||||
  async close(options = {}) {
 | 
			
		||||
    await this._channel.closePage({ ...options, wasClean: true }).catch(() => {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  connectToServer() {
 | 
			
		||||
    if (this._connected)
 | 
			
		||||
      throw new Error("Already connected to the server");
 | 
			
		||||
    this._connected = true;
 | 
			
		||||
    this._channel.connect().catch(() => {
 | 
			
		||||
    });
 | 
			
		||||
    return this._server;
 | 
			
		||||
  }
 | 
			
		||||
  send(message) {
 | 
			
		||||
    if ((0, import_rtti.isString)(message))
 | 
			
		||||
      this._channel.sendToPage({ message, isBase64: false }).catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
    else
 | 
			
		||||
      this._channel.sendToPage({ message: message.toString("base64"), isBase64: true }).catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
  onMessage(handler) {
 | 
			
		||||
    this._onPageMessage = handler;
 | 
			
		||||
  }
 | 
			
		||||
  onClose(handler) {
 | 
			
		||||
    this._onPageClose = handler;
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.close();
 | 
			
		||||
  }
 | 
			
		||||
  async _afterHandle() {
 | 
			
		||||
    if (this._connected)
 | 
			
		||||
      return;
 | 
			
		||||
    await this._channel.ensureOpened().catch(() => {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class WebSocketRouteHandler {
 | 
			
		||||
  constructor(baseURL, url, handler) {
 | 
			
		||||
    this._baseURL = baseURL;
 | 
			
		||||
    this.url = url;
 | 
			
		||||
    this.handler = handler;
 | 
			
		||||
  }
 | 
			
		||||
  static prepareInterceptionPatterns(handlers) {
 | 
			
		||||
    const patterns = [];
 | 
			
		||||
    let all = false;
 | 
			
		||||
    for (const handler of handlers) {
 | 
			
		||||
      if ((0, import_rtti.isString)(handler.url))
 | 
			
		||||
        patterns.push({ glob: handler.url });
 | 
			
		||||
      else if ((0, import_rtti.isRegExp)(handler.url))
 | 
			
		||||
        patterns.push({ regexSource: handler.url.source, regexFlags: handler.url.flags });
 | 
			
		||||
      else
 | 
			
		||||
        all = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (all)
 | 
			
		||||
      return [{ glob: "**/*" }];
 | 
			
		||||
    return patterns;
 | 
			
		||||
  }
 | 
			
		||||
  matches(wsURL) {
 | 
			
		||||
    return (0, import_urlMatch.urlMatches)(this._baseURL, wsURL, this.url, true);
 | 
			
		||||
  }
 | 
			
		||||
  async handle(webSocketRoute) {
 | 
			
		||||
    const handler = this.handler;
 | 
			
		||||
    await handler(webSocketRoute);
 | 
			
		||||
    await webSocketRoute._afterHandle();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class Response extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._finishedPromise = new import_manualPromise.ManualPromise();
 | 
			
		||||
    this._provisionalHeaders = new RawHeaders(initializer.headers);
 | 
			
		||||
    this._request = Request.from(this._initializer.request);
 | 
			
		||||
    Object.assign(this._request._timing, this._initializer.timing);
 | 
			
		||||
  }
 | 
			
		||||
  static from(response) {
 | 
			
		||||
    return response._object;
 | 
			
		||||
  }
 | 
			
		||||
  static fromNullable(response) {
 | 
			
		||||
    return response ? Response.from(response) : null;
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._initializer.url;
 | 
			
		||||
  }
 | 
			
		||||
  ok() {
 | 
			
		||||
    return this._initializer.status === 0 || this._initializer.status >= 200 && this._initializer.status <= 299;
 | 
			
		||||
  }
 | 
			
		||||
  status() {
 | 
			
		||||
    return this._initializer.status;
 | 
			
		||||
  }
 | 
			
		||||
  statusText() {
 | 
			
		||||
    return this._initializer.statusText;
 | 
			
		||||
  }
 | 
			
		||||
  fromServiceWorker() {
 | 
			
		||||
    return this._initializer.fromServiceWorker;
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @deprecated
 | 
			
		||||
   */
 | 
			
		||||
  headers() {
 | 
			
		||||
    return this._provisionalHeaders.headers();
 | 
			
		||||
  }
 | 
			
		||||
  async _actualHeaders() {
 | 
			
		||||
    if (!this._actualHeadersPromise) {
 | 
			
		||||
      this._actualHeadersPromise = (async () => {
 | 
			
		||||
        return new RawHeaders((await this._channel.rawResponseHeaders()).headers);
 | 
			
		||||
      })();
 | 
			
		||||
    }
 | 
			
		||||
    return await this._actualHeadersPromise;
 | 
			
		||||
  }
 | 
			
		||||
  async allHeaders() {
 | 
			
		||||
    return (await this._actualHeaders()).headers();
 | 
			
		||||
  }
 | 
			
		||||
  async headersArray() {
 | 
			
		||||
    return (await this._actualHeaders()).headersArray().slice();
 | 
			
		||||
  }
 | 
			
		||||
  async headerValue(name) {
 | 
			
		||||
    return (await this._actualHeaders()).get(name);
 | 
			
		||||
  }
 | 
			
		||||
  async headerValues(name) {
 | 
			
		||||
    return (await this._actualHeaders()).getAll(name);
 | 
			
		||||
  }
 | 
			
		||||
  async finished() {
 | 
			
		||||
    return await this.request()._targetClosedScope().race(this._finishedPromise);
 | 
			
		||||
  }
 | 
			
		||||
  async body() {
 | 
			
		||||
    return (await this._channel.body()).binary;
 | 
			
		||||
  }
 | 
			
		||||
  async text() {
 | 
			
		||||
    const content = await this.body();
 | 
			
		||||
    return content.toString("utf8");
 | 
			
		||||
  }
 | 
			
		||||
  async json() {
 | 
			
		||||
    const content = await this.text();
 | 
			
		||||
    return JSON.parse(content);
 | 
			
		||||
  }
 | 
			
		||||
  request() {
 | 
			
		||||
    return this._request;
 | 
			
		||||
  }
 | 
			
		||||
  frame() {
 | 
			
		||||
    return this._request.frame();
 | 
			
		||||
  }
 | 
			
		||||
  async serverAddr() {
 | 
			
		||||
    return (await this._channel.serverAddr()).value || null;
 | 
			
		||||
  }
 | 
			
		||||
  async securityDetails() {
 | 
			
		||||
    return (await this._channel.securityDetails()).value || null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class WebSocket extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(webSocket) {
 | 
			
		||||
    return webSocket._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._isClosed = false;
 | 
			
		||||
    this._page = parent;
 | 
			
		||||
    this._channel.on("frameSent", (event) => {
 | 
			
		||||
      if (event.opcode === 1)
 | 
			
		||||
        this.emit(import_events.Events.WebSocket.FrameSent, { payload: event.data });
 | 
			
		||||
      else if (event.opcode === 2)
 | 
			
		||||
        this.emit(import_events.Events.WebSocket.FrameSent, { payload: Buffer.from(event.data, "base64") });
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("frameReceived", (event) => {
 | 
			
		||||
      if (event.opcode === 1)
 | 
			
		||||
        this.emit(import_events.Events.WebSocket.FrameReceived, { payload: event.data });
 | 
			
		||||
      else if (event.opcode === 2)
 | 
			
		||||
        this.emit(import_events.Events.WebSocket.FrameReceived, { payload: Buffer.from(event.data, "base64") });
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("socketError", ({ error }) => this.emit(import_events.Events.WebSocket.Error, error));
 | 
			
		||||
    this._channel.on("close", () => {
 | 
			
		||||
      this._isClosed = true;
 | 
			
		||||
      this.emit(import_events.Events.WebSocket.Close, this);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._initializer.url;
 | 
			
		||||
  }
 | 
			
		||||
  isClosed() {
 | 
			
		||||
    return this._isClosed;
 | 
			
		||||
  }
 | 
			
		||||
  async waitForEvent(event, optionsOrPredicate = {}) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
 | 
			
		||||
      const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
 | 
			
		||||
      const waiter = import_waiter.Waiter.createForEvent(this, event);
 | 
			
		||||
      waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
 | 
			
		||||
      if (event !== import_events.Events.WebSocket.Error)
 | 
			
		||||
        waiter.rejectOnEvent(this, import_events.Events.WebSocket.Error, new Error("Socket error"));
 | 
			
		||||
      if (event !== import_events.Events.WebSocket.Close)
 | 
			
		||||
        waiter.rejectOnEvent(this, import_events.Events.WebSocket.Close, new Error("Socket closed"));
 | 
			
		||||
      waiter.rejectOnEvent(this._page, import_events.Events.Page.Close, () => this._page._closeErrorWithReason());
 | 
			
		||||
      const result = await waiter.waitForEvent(this, event, predicate);
 | 
			
		||||
      waiter.dispose();
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function validateHeaders(headers) {
 | 
			
		||||
  for (const key of Object.keys(headers)) {
 | 
			
		||||
    const value = headers[key];
 | 
			
		||||
    if (!Object.is(value, void 0) && !(0, import_rtti.isString)(value))
 | 
			
		||||
      throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class RouteHandler {
 | 
			
		||||
  constructor(platform, baseURL, url, handler, times = Number.MAX_SAFE_INTEGER) {
 | 
			
		||||
    this.handledCount = 0;
 | 
			
		||||
    this._ignoreException = false;
 | 
			
		||||
    this._activeInvocations = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._baseURL = baseURL;
 | 
			
		||||
    this._times = times;
 | 
			
		||||
    this.url = url;
 | 
			
		||||
    this.handler = handler;
 | 
			
		||||
    this._savedZone = platform.zones.current().pop();
 | 
			
		||||
  }
 | 
			
		||||
  static prepareInterceptionPatterns(handlers) {
 | 
			
		||||
    const patterns = [];
 | 
			
		||||
    let all = false;
 | 
			
		||||
    for (const handler of handlers) {
 | 
			
		||||
      if ((0, import_rtti.isString)(handler.url))
 | 
			
		||||
        patterns.push({ glob: handler.url });
 | 
			
		||||
      else if ((0, import_rtti.isRegExp)(handler.url))
 | 
			
		||||
        patterns.push({ regexSource: handler.url.source, regexFlags: handler.url.flags });
 | 
			
		||||
      else
 | 
			
		||||
        all = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (all)
 | 
			
		||||
      return [{ glob: "**/*" }];
 | 
			
		||||
    return patterns;
 | 
			
		||||
  }
 | 
			
		||||
  matches(requestURL) {
 | 
			
		||||
    return (0, import_urlMatch.urlMatches)(this._baseURL, requestURL, this.url);
 | 
			
		||||
  }
 | 
			
		||||
  async handle(route) {
 | 
			
		||||
    return await this._savedZone.run(async () => this._handleImpl(route));
 | 
			
		||||
  }
 | 
			
		||||
  async _handleImpl(route) {
 | 
			
		||||
    const handlerInvocation = { complete: new import_manualPromise.ManualPromise(), route };
 | 
			
		||||
    this._activeInvocations.add(handlerInvocation);
 | 
			
		||||
    try {
 | 
			
		||||
      return await this._handleInternal(route);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (this._ignoreException)
 | 
			
		||||
        return false;
 | 
			
		||||
      if ((0, import_errors.isTargetClosedError)(e)) {
 | 
			
		||||
        (0, import_stackTrace.rewriteErrorMessage)(e, `"${e.message}" while running route callback.
 | 
			
		||||
Consider awaiting \`await page.unrouteAll({ behavior: 'ignoreErrors' })\`
 | 
			
		||||
before the end of the test to ignore remaining routes in flight.`);
 | 
			
		||||
      }
 | 
			
		||||
      throw e;
 | 
			
		||||
    } finally {
 | 
			
		||||
      handlerInvocation.complete.resolve();
 | 
			
		||||
      this._activeInvocations.delete(handlerInvocation);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  async stop(behavior) {
 | 
			
		||||
    if (behavior === "ignoreErrors") {
 | 
			
		||||
      this._ignoreException = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      const promises = [];
 | 
			
		||||
      for (const activation of this._activeInvocations) {
 | 
			
		||||
        if (!activation.route._didThrow)
 | 
			
		||||
          promises.push(activation.complete);
 | 
			
		||||
      }
 | 
			
		||||
      await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  async _handleInternal(route) {
 | 
			
		||||
    ++this.handledCount;
 | 
			
		||||
    const handledPromise = route._startHandling();
 | 
			
		||||
    const handler = this.handler;
 | 
			
		||||
    const [handled] = await Promise.all([
 | 
			
		||||
      handledPromise,
 | 
			
		||||
      handler(route, route.request())
 | 
			
		||||
    ]);
 | 
			
		||||
    return handled;
 | 
			
		||||
  }
 | 
			
		||||
  willExpire() {
 | 
			
		||||
    return this.handledCount + 1 >= this._times;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class RawHeaders {
 | 
			
		||||
  constructor(headers) {
 | 
			
		||||
    this._headersMap = new import_multimap.MultiMap();
 | 
			
		||||
    this._headersArray = headers;
 | 
			
		||||
    for (const header of headers)
 | 
			
		||||
      this._headersMap.set(header.name.toLowerCase(), header.value);
 | 
			
		||||
  }
 | 
			
		||||
  static _fromHeadersObjectLossy(headers) {
 | 
			
		||||
    const headersArray = Object.entries(headers).map(([name, value]) => ({
 | 
			
		||||
      name,
 | 
			
		||||
      value
 | 
			
		||||
    })).filter((header) => header.value !== void 0);
 | 
			
		||||
    return new RawHeaders(headersArray);
 | 
			
		||||
  }
 | 
			
		||||
  get(name) {
 | 
			
		||||
    const values = this.getAll(name);
 | 
			
		||||
    if (!values || !values.length)
 | 
			
		||||
      return null;
 | 
			
		||||
    return values.join(name.toLowerCase() === "set-cookie" ? "\n" : ", ");
 | 
			
		||||
  }
 | 
			
		||||
  getAll(name) {
 | 
			
		||||
    return [...this._headersMap.get(name.toLowerCase())];
 | 
			
		||||
  }
 | 
			
		||||
  headers() {
 | 
			
		||||
    const result = {};
 | 
			
		||||
    for (const name of this._headersMap.keys())
 | 
			
		||||
      result[name] = this.get(name);
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
  headersArray() {
 | 
			
		||||
    return this._headersArray;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  RawHeaders,
 | 
			
		||||
  Request,
 | 
			
		||||
  Response,
 | 
			
		||||
  Route,
 | 
			
		||||
  RouteHandler,
 | 
			
		||||
  WebSocket,
 | 
			
		||||
  WebSocketRoute,
 | 
			
		||||
  WebSocketRouteHandler,
 | 
			
		||||
  validateHeaders
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										709
									
								
								node_modules/playwright-core/lib/client/page.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										709
									
								
								node_modules/playwright-core/lib/client/page.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,709 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var page_exports = {};
 | 
			
		||||
__export(page_exports, {
 | 
			
		||||
  BindingCall: () => BindingCall,
 | 
			
		||||
  Page: () => Page
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(page_exports);
 | 
			
		||||
var import_accessibility = require("./accessibility");
 | 
			
		||||
var import_artifact = require("./artifact");
 | 
			
		||||
var import_channelOwner = require("./channelOwner");
 | 
			
		||||
var import_clientHelper = require("./clientHelper");
 | 
			
		||||
var import_coverage = require("./coverage");
 | 
			
		||||
var import_download = require("./download");
 | 
			
		||||
var import_elementHandle = require("./elementHandle");
 | 
			
		||||
var import_errors = require("./errors");
 | 
			
		||||
var import_events = require("./events");
 | 
			
		||||
var import_fileChooser = require("./fileChooser");
 | 
			
		||||
var import_frame = require("./frame");
 | 
			
		||||
var import_harRouter = require("./harRouter");
 | 
			
		||||
var import_input = require("./input");
 | 
			
		||||
var import_jsHandle = require("./jsHandle");
 | 
			
		||||
var import_network = require("./network");
 | 
			
		||||
var import_video = require("./video");
 | 
			
		||||
var import_waiter = require("./waiter");
 | 
			
		||||
var import_worker = require("./worker");
 | 
			
		||||
var import_timeoutSettings = require("./timeoutSettings");
 | 
			
		||||
var import_assert = require("../utils/isomorphic/assert");
 | 
			
		||||
var import_fileUtils = require("./fileUtils");
 | 
			
		||||
var import_headers = require("../utils/isomorphic/headers");
 | 
			
		||||
var import_stringUtils = require("../utils/isomorphic/stringUtils");
 | 
			
		||||
var import_urlMatch = require("../utils/isomorphic/urlMatch");
 | 
			
		||||
var import_manualPromise = require("../utils/isomorphic/manualPromise");
 | 
			
		||||
var import_rtti = require("../utils/isomorphic/rtti");
 | 
			
		||||
class Page extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
    this._frames = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._workers = /* @__PURE__ */ new Set();
 | 
			
		||||
    this._closed = false;
 | 
			
		||||
    this._closedOrCrashedScope = new import_manualPromise.LongStandingScope();
 | 
			
		||||
    this._routes = [];
 | 
			
		||||
    this._webSocketRoutes = [];
 | 
			
		||||
    this._bindings = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._video = null;
 | 
			
		||||
    this._closeWasCalled = false;
 | 
			
		||||
    this._harRouters = [];
 | 
			
		||||
    this._locatorHandlers = /* @__PURE__ */ new Map();
 | 
			
		||||
    this._browserContext = parent;
 | 
			
		||||
    this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform, this._browserContext._timeoutSettings);
 | 
			
		||||
    this.accessibility = new import_accessibility.Accessibility(this._channel);
 | 
			
		||||
    this.keyboard = new import_input.Keyboard(this);
 | 
			
		||||
    this.mouse = new import_input.Mouse(this);
 | 
			
		||||
    this.request = this._browserContext.request;
 | 
			
		||||
    this.touchscreen = new import_input.Touchscreen(this);
 | 
			
		||||
    this.clock = this._browserContext.clock;
 | 
			
		||||
    this._mainFrame = import_frame.Frame.from(initializer.mainFrame);
 | 
			
		||||
    this._mainFrame._page = this;
 | 
			
		||||
    this._frames.add(this._mainFrame);
 | 
			
		||||
    this._viewportSize = initializer.viewportSize;
 | 
			
		||||
    this._closed = initializer.isClosed;
 | 
			
		||||
    this._opener = Page.fromNullable(initializer.opener);
 | 
			
		||||
    this._channel.on("bindingCall", ({ binding }) => this._onBinding(BindingCall.from(binding)));
 | 
			
		||||
    this._channel.on("close", () => this._onClose());
 | 
			
		||||
    this._channel.on("crash", () => this._onCrash());
 | 
			
		||||
    this._channel.on("download", ({ url, suggestedFilename, artifact }) => {
 | 
			
		||||
      const artifactObject = import_artifact.Artifact.from(artifact);
 | 
			
		||||
      this.emit(import_events.Events.Page.Download, new import_download.Download(this, url, suggestedFilename, artifactObject));
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("fileChooser", ({ element, isMultiple }) => this.emit(import_events.Events.Page.FileChooser, new import_fileChooser.FileChooser(this, import_elementHandle.ElementHandle.from(element), isMultiple)));
 | 
			
		||||
    this._channel.on("frameAttached", ({ frame }) => this._onFrameAttached(import_frame.Frame.from(frame)));
 | 
			
		||||
    this._channel.on("frameDetached", ({ frame }) => this._onFrameDetached(import_frame.Frame.from(frame)));
 | 
			
		||||
    this._channel.on("locatorHandlerTriggered", ({ uid }) => this._onLocatorHandlerTriggered(uid));
 | 
			
		||||
    this._channel.on("route", ({ route }) => this._onRoute(import_network.Route.from(route)));
 | 
			
		||||
    this._channel.on("webSocketRoute", ({ webSocketRoute }) => this._onWebSocketRoute(import_network.WebSocketRoute.from(webSocketRoute)));
 | 
			
		||||
    this._channel.on("video", ({ artifact }) => {
 | 
			
		||||
      const artifactObject = import_artifact.Artifact.from(artifact);
 | 
			
		||||
      this._forceVideo()._artifactReady(artifactObject);
 | 
			
		||||
    });
 | 
			
		||||
    this._channel.on("viewportSizeChanged", ({ viewportSize }) => this._viewportSize = viewportSize);
 | 
			
		||||
    this._channel.on("webSocket", ({ webSocket }) => this.emit(import_events.Events.Page.WebSocket, import_network.WebSocket.from(webSocket)));
 | 
			
		||||
    this._channel.on("worker", ({ worker }) => this._onWorker(import_worker.Worker.from(worker)));
 | 
			
		||||
    this.coverage = new import_coverage.Coverage(this._channel);
 | 
			
		||||
    this.once(import_events.Events.Page.Close, () => this._closedOrCrashedScope.close(this._closeErrorWithReason()));
 | 
			
		||||
    this.once(import_events.Events.Page.Crash, () => this._closedOrCrashedScope.close(new import_errors.TargetClosedError()));
 | 
			
		||||
    this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
 | 
			
		||||
      [import_events.Events.Page.Console, "console"],
 | 
			
		||||
      [import_events.Events.Page.Dialog, "dialog"],
 | 
			
		||||
      [import_events.Events.Page.Request, "request"],
 | 
			
		||||
      [import_events.Events.Page.Response, "response"],
 | 
			
		||||
      [import_events.Events.Page.RequestFinished, "requestFinished"],
 | 
			
		||||
      [import_events.Events.Page.RequestFailed, "requestFailed"],
 | 
			
		||||
      [import_events.Events.Page.FileChooser, "fileChooser"]
 | 
			
		||||
    ]));
 | 
			
		||||
  }
 | 
			
		||||
  static from(page) {
 | 
			
		||||
    return page._object;
 | 
			
		||||
  }
 | 
			
		||||
  static fromNullable(page) {
 | 
			
		||||
    return page ? Page.from(page) : null;
 | 
			
		||||
  }
 | 
			
		||||
  _onFrameAttached(frame) {
 | 
			
		||||
    frame._page = this;
 | 
			
		||||
    this._frames.add(frame);
 | 
			
		||||
    if (frame._parentFrame)
 | 
			
		||||
      frame._parentFrame._childFrames.add(frame);
 | 
			
		||||
    this.emit(import_events.Events.Page.FrameAttached, frame);
 | 
			
		||||
  }
 | 
			
		||||
  _onFrameDetached(frame) {
 | 
			
		||||
    this._frames.delete(frame);
 | 
			
		||||
    frame._detached = true;
 | 
			
		||||
    if (frame._parentFrame)
 | 
			
		||||
      frame._parentFrame._childFrames.delete(frame);
 | 
			
		||||
    this.emit(import_events.Events.Page.FrameDetached, frame);
 | 
			
		||||
  }
 | 
			
		||||
  async _onRoute(route) {
 | 
			
		||||
    route._context = this.context();
 | 
			
		||||
    const routeHandlers = this._routes.slice();
 | 
			
		||||
    for (const routeHandler of routeHandlers) {
 | 
			
		||||
      if (this._closeWasCalled || this._browserContext._closingStatus !== "none")
 | 
			
		||||
        return;
 | 
			
		||||
      if (!routeHandler.matches(route.request().url()))
 | 
			
		||||
        continue;
 | 
			
		||||
      const index = this._routes.indexOf(routeHandler);
 | 
			
		||||
      if (index === -1)
 | 
			
		||||
        continue;
 | 
			
		||||
      if (routeHandler.willExpire())
 | 
			
		||||
        this._routes.splice(index, 1);
 | 
			
		||||
      const handled = await routeHandler.handle(route);
 | 
			
		||||
      if (!this._routes.length)
 | 
			
		||||
        this._updateInterceptionPatterns({ internal: true }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
      if (handled)
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    await this._browserContext._onRoute(route);
 | 
			
		||||
  }
 | 
			
		||||
  async _onWebSocketRoute(webSocketRoute) {
 | 
			
		||||
    const routeHandler = this._webSocketRoutes.find((route) => route.matches(webSocketRoute.url()));
 | 
			
		||||
    if (routeHandler)
 | 
			
		||||
      await routeHandler.handle(webSocketRoute);
 | 
			
		||||
    else
 | 
			
		||||
      await this._browserContext._onWebSocketRoute(webSocketRoute);
 | 
			
		||||
  }
 | 
			
		||||
  async _onBinding(bindingCall) {
 | 
			
		||||
    const func = this._bindings.get(bindingCall._initializer.name);
 | 
			
		||||
    if (func) {
 | 
			
		||||
      await bindingCall.call(func);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    await this._browserContext._onBinding(bindingCall);
 | 
			
		||||
  }
 | 
			
		||||
  _onWorker(worker) {
 | 
			
		||||
    this._workers.add(worker);
 | 
			
		||||
    worker._page = this;
 | 
			
		||||
    this.emit(import_events.Events.Page.Worker, worker);
 | 
			
		||||
  }
 | 
			
		||||
  _onClose() {
 | 
			
		||||
    this._closed = true;
 | 
			
		||||
    this._browserContext._pages.delete(this);
 | 
			
		||||
    this._browserContext._backgroundPages.delete(this);
 | 
			
		||||
    this._disposeHarRouters();
 | 
			
		||||
    this.emit(import_events.Events.Page.Close, this);
 | 
			
		||||
  }
 | 
			
		||||
  _onCrash() {
 | 
			
		||||
    this.emit(import_events.Events.Page.Crash, this);
 | 
			
		||||
  }
 | 
			
		||||
  context() {
 | 
			
		||||
    return this._browserContext;
 | 
			
		||||
  }
 | 
			
		||||
  async opener() {
 | 
			
		||||
    if (!this._opener || this._opener.isClosed())
 | 
			
		||||
      return null;
 | 
			
		||||
    return this._opener;
 | 
			
		||||
  }
 | 
			
		||||
  mainFrame() {
 | 
			
		||||
    return this._mainFrame;
 | 
			
		||||
  }
 | 
			
		||||
  frame(frameSelector) {
 | 
			
		||||
    const name = (0, import_rtti.isString)(frameSelector) ? frameSelector : frameSelector.name;
 | 
			
		||||
    const url = (0, import_rtti.isObject)(frameSelector) ? frameSelector.url : void 0;
 | 
			
		||||
    (0, import_assert.assert)(name || url, "Either name or url matcher should be specified");
 | 
			
		||||
    return this.frames().find((f) => {
 | 
			
		||||
      if (name)
 | 
			
		||||
        return f.name() === name;
 | 
			
		||||
      return (0, import_urlMatch.urlMatches)(this._browserContext._options.baseURL, f.url(), url);
 | 
			
		||||
    }) || null;
 | 
			
		||||
  }
 | 
			
		||||
  frames() {
 | 
			
		||||
    return [...this._frames];
 | 
			
		||||
  }
 | 
			
		||||
  setDefaultNavigationTimeout(timeout) {
 | 
			
		||||
    this._timeoutSettings.setDefaultNavigationTimeout(timeout);
 | 
			
		||||
  }
 | 
			
		||||
  setDefaultTimeout(timeout) {
 | 
			
		||||
    this._timeoutSettings.setDefaultTimeout(timeout);
 | 
			
		||||
  }
 | 
			
		||||
  _forceVideo() {
 | 
			
		||||
    if (!this._video)
 | 
			
		||||
      this._video = new import_video.Video(this, this._connection);
 | 
			
		||||
    return this._video;
 | 
			
		||||
  }
 | 
			
		||||
  video() {
 | 
			
		||||
    if (!this._browserContext._options.recordVideo)
 | 
			
		||||
      return null;
 | 
			
		||||
    return this._forceVideo();
 | 
			
		||||
  }
 | 
			
		||||
  async $(selector, options) {
 | 
			
		||||
    return await this._mainFrame.$(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForSelector(selector, options) {
 | 
			
		||||
    return await this._mainFrame.waitForSelector(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async dispatchEvent(selector, type, eventInit, options) {
 | 
			
		||||
    return await this._mainFrame.dispatchEvent(selector, type, eventInit, options);
 | 
			
		||||
  }
 | 
			
		||||
  async evaluateHandle(pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
 | 
			
		||||
    return await this._mainFrame.evaluateHandle(pageFunction, arg);
 | 
			
		||||
  }
 | 
			
		||||
  async $eval(selector, pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
 | 
			
		||||
    return await this._mainFrame.$eval(selector, pageFunction, arg);
 | 
			
		||||
  }
 | 
			
		||||
  async $$eval(selector, pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
 | 
			
		||||
    return await this._mainFrame.$$eval(selector, pageFunction, arg);
 | 
			
		||||
  }
 | 
			
		||||
  async $$(selector) {
 | 
			
		||||
    return await this._mainFrame.$$(selector);
 | 
			
		||||
  }
 | 
			
		||||
  async addScriptTag(options = {}) {
 | 
			
		||||
    return await this._mainFrame.addScriptTag(options);
 | 
			
		||||
  }
 | 
			
		||||
  async addStyleTag(options = {}) {
 | 
			
		||||
    return await this._mainFrame.addStyleTag(options);
 | 
			
		||||
  }
 | 
			
		||||
  async exposeFunction(name, callback) {
 | 
			
		||||
    await this._channel.exposeBinding({ name });
 | 
			
		||||
    const binding = (source, ...args) => callback(...args);
 | 
			
		||||
    this._bindings.set(name, binding);
 | 
			
		||||
  }
 | 
			
		||||
  async exposeBinding(name, callback, options = {}) {
 | 
			
		||||
    await this._channel.exposeBinding({ name, needsHandle: options.handle });
 | 
			
		||||
    this._bindings.set(name, callback);
 | 
			
		||||
  }
 | 
			
		||||
  async setExtraHTTPHeaders(headers) {
 | 
			
		||||
    (0, import_network.validateHeaders)(headers);
 | 
			
		||||
    await this._channel.setExtraHTTPHeaders({ headers: (0, import_headers.headersObjectToArray)(headers) });
 | 
			
		||||
  }
 | 
			
		||||
  url() {
 | 
			
		||||
    return this._mainFrame.url();
 | 
			
		||||
  }
 | 
			
		||||
  async content() {
 | 
			
		||||
    return await this._mainFrame.content();
 | 
			
		||||
  }
 | 
			
		||||
  async setContent(html, options) {
 | 
			
		||||
    return await this._mainFrame.setContent(html, options);
 | 
			
		||||
  }
 | 
			
		||||
  async goto(url, options) {
 | 
			
		||||
    return await this._mainFrame.goto(url, options);
 | 
			
		||||
  }
 | 
			
		||||
  async reload(options = {}) {
 | 
			
		||||
    const waitUntil = (0, import_frame.verifyLoadState)("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
 | 
			
		||||
    return import_network.Response.fromNullable((await this._channel.reload({ ...options, waitUntil, timeout: this._timeoutSettings.navigationTimeout(options) })).response);
 | 
			
		||||
  }
 | 
			
		||||
  async addLocatorHandler(locator, handler, options = {}) {
 | 
			
		||||
    if (locator._frame !== this._mainFrame)
 | 
			
		||||
      throw new Error(`Locator must belong to the main frame of this page`);
 | 
			
		||||
    if (options.times === 0)
 | 
			
		||||
      return;
 | 
			
		||||
    const { uid } = await this._channel.registerLocatorHandler({ selector: locator._selector, noWaitAfter: options.noWaitAfter });
 | 
			
		||||
    this._locatorHandlers.set(uid, { locator, handler, times: options.times });
 | 
			
		||||
  }
 | 
			
		||||
  async _onLocatorHandlerTriggered(uid) {
 | 
			
		||||
    let remove = false;
 | 
			
		||||
    try {
 | 
			
		||||
      const handler = this._locatorHandlers.get(uid);
 | 
			
		||||
      if (handler && handler.times !== 0) {
 | 
			
		||||
        if (handler.times !== void 0)
 | 
			
		||||
          handler.times--;
 | 
			
		||||
        await handler.handler(handler.locator);
 | 
			
		||||
      }
 | 
			
		||||
      remove = handler?.times === 0;
 | 
			
		||||
    } finally {
 | 
			
		||||
      if (remove)
 | 
			
		||||
        this._locatorHandlers.delete(uid);
 | 
			
		||||
      this._channel.resolveLocatorHandlerNoReply({ uid, remove }).catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  async removeLocatorHandler(locator) {
 | 
			
		||||
    for (const [uid, data] of this._locatorHandlers) {
 | 
			
		||||
      if (data.locator._equals(locator)) {
 | 
			
		||||
        this._locatorHandlers.delete(uid);
 | 
			
		||||
        await this._channel.unregisterLocatorHandler({ uid }).catch(() => {
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  async waitForLoadState(state, options) {
 | 
			
		||||
    return await this._mainFrame.waitForLoadState(state, options);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForNavigation(options) {
 | 
			
		||||
    return await this._mainFrame.waitForNavigation(options);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForURL(url, options) {
 | 
			
		||||
    return await this._mainFrame.waitForURL(url, options);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForRequest(urlOrPredicate, options = {}) {
 | 
			
		||||
    const predicate = async (request) => {
 | 
			
		||||
      if ((0, import_rtti.isString)(urlOrPredicate) || (0, import_rtti.isRegExp)(urlOrPredicate))
 | 
			
		||||
        return (0, import_urlMatch.urlMatches)(this._browserContext._options.baseURL, request.url(), urlOrPredicate);
 | 
			
		||||
      return await urlOrPredicate(request);
 | 
			
		||||
    };
 | 
			
		||||
    const trimmedUrl = trimUrl(urlOrPredicate);
 | 
			
		||||
    const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : void 0;
 | 
			
		||||
    return await this._waitForEvent(import_events.Events.Page.Request, { predicate, timeout: options.timeout }, logLine);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForResponse(urlOrPredicate, options = {}) {
 | 
			
		||||
    const predicate = async (response) => {
 | 
			
		||||
      if ((0, import_rtti.isString)(urlOrPredicate) || (0, import_rtti.isRegExp)(urlOrPredicate))
 | 
			
		||||
        return (0, import_urlMatch.urlMatches)(this._browserContext._options.baseURL, response.url(), urlOrPredicate);
 | 
			
		||||
      return await urlOrPredicate(response);
 | 
			
		||||
    };
 | 
			
		||||
    const trimmedUrl = trimUrl(urlOrPredicate);
 | 
			
		||||
    const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : void 0;
 | 
			
		||||
    return await this._waitForEvent(import_events.Events.Page.Response, { predicate, timeout: options.timeout }, logLine);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForEvent(event, optionsOrPredicate = {}) {
 | 
			
		||||
    return await this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`);
 | 
			
		||||
  }
 | 
			
		||||
  _closeErrorWithReason() {
 | 
			
		||||
    return new import_errors.TargetClosedError(this._closeReason || this._browserContext._effectiveCloseReason());
 | 
			
		||||
  }
 | 
			
		||||
  async _waitForEvent(event, optionsOrPredicate, logLine) {
 | 
			
		||||
    return await this._wrapApiCall(async () => {
 | 
			
		||||
      const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
 | 
			
		||||
      const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
 | 
			
		||||
      const waiter = import_waiter.Waiter.createForEvent(this, event);
 | 
			
		||||
      if (logLine)
 | 
			
		||||
        waiter.log(logLine);
 | 
			
		||||
      waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
 | 
			
		||||
      if (event !== import_events.Events.Page.Crash)
 | 
			
		||||
        waiter.rejectOnEvent(this, import_events.Events.Page.Crash, new Error("Page crashed"));
 | 
			
		||||
      if (event !== import_events.Events.Page.Close)
 | 
			
		||||
        waiter.rejectOnEvent(this, import_events.Events.Page.Close, () => this._closeErrorWithReason());
 | 
			
		||||
      const result = await waiter.waitForEvent(this, event, predicate);
 | 
			
		||||
      waiter.dispose();
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async goBack(options = {}) {
 | 
			
		||||
    const waitUntil = (0, import_frame.verifyLoadState)("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
 | 
			
		||||
    return import_network.Response.fromNullable((await this._channel.goBack({ ...options, waitUntil, timeout: this._timeoutSettings.navigationTimeout(options) })).response);
 | 
			
		||||
  }
 | 
			
		||||
  async goForward(options = {}) {
 | 
			
		||||
    const waitUntil = (0, import_frame.verifyLoadState)("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
 | 
			
		||||
    return import_network.Response.fromNullable((await this._channel.goForward({ ...options, waitUntil, timeout: this._timeoutSettings.navigationTimeout(options) })).response);
 | 
			
		||||
  }
 | 
			
		||||
  async requestGC() {
 | 
			
		||||
    await this._channel.requestGC();
 | 
			
		||||
  }
 | 
			
		||||
  async emulateMedia(options = {}) {
 | 
			
		||||
    await this._channel.emulateMedia({
 | 
			
		||||
      media: options.media === null ? "no-override" : options.media,
 | 
			
		||||
      colorScheme: options.colorScheme === null ? "no-override" : options.colorScheme,
 | 
			
		||||
      reducedMotion: options.reducedMotion === null ? "no-override" : options.reducedMotion,
 | 
			
		||||
      forcedColors: options.forcedColors === null ? "no-override" : options.forcedColors,
 | 
			
		||||
      contrast: options.contrast === null ? "no-override" : options.contrast
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async setViewportSize(viewportSize) {
 | 
			
		||||
    this._viewportSize = viewportSize;
 | 
			
		||||
    await this._channel.setViewportSize({ viewportSize });
 | 
			
		||||
  }
 | 
			
		||||
  viewportSize() {
 | 
			
		||||
    return this._viewportSize || null;
 | 
			
		||||
  }
 | 
			
		||||
  async evaluate(pageFunction, arg) {
 | 
			
		||||
    (0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
 | 
			
		||||
    return await this._mainFrame.evaluate(pageFunction, arg);
 | 
			
		||||
  }
 | 
			
		||||
  async _evaluateFunction(functionDeclaration) {
 | 
			
		||||
    return this._mainFrame._evaluateFunction(functionDeclaration);
 | 
			
		||||
  }
 | 
			
		||||
  async addInitScript(script, arg) {
 | 
			
		||||
    const source = await (0, import_clientHelper.evaluationScript)(this._platform, script, arg);
 | 
			
		||||
    await this._channel.addInitScript({ source });
 | 
			
		||||
  }
 | 
			
		||||
  async route(url, handler, options = {}) {
 | 
			
		||||
    this._routes.unshift(new import_network.RouteHandler(this._platform, this._browserContext._options.baseURL, url, handler, options.times));
 | 
			
		||||
    await this._updateInterceptionPatterns({ title: "Route requests" });
 | 
			
		||||
  }
 | 
			
		||||
  async routeFromHAR(har, options = {}) {
 | 
			
		||||
    const localUtils = this._connection.localUtils();
 | 
			
		||||
    if (!localUtils)
 | 
			
		||||
      throw new Error("Route from har is not supported in thin clients");
 | 
			
		||||
    if (options.update) {
 | 
			
		||||
      await this._browserContext._recordIntoHAR(har, this, options);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const harRouter = await import_harRouter.HarRouter.create(localUtils, har, options.notFound || "abort", { urlMatch: options.url });
 | 
			
		||||
    this._harRouters.push(harRouter);
 | 
			
		||||
    await harRouter.addPageRoute(this);
 | 
			
		||||
  }
 | 
			
		||||
  async routeWebSocket(url, handler) {
 | 
			
		||||
    this._webSocketRoutes.unshift(new import_network.WebSocketRouteHandler(this._browserContext._options.baseURL, url, handler));
 | 
			
		||||
    await this._updateWebSocketInterceptionPatterns({ title: "Route WebSockets" });
 | 
			
		||||
  }
 | 
			
		||||
  _disposeHarRouters() {
 | 
			
		||||
    this._harRouters.forEach((router) => router.dispose());
 | 
			
		||||
    this._harRouters = [];
 | 
			
		||||
  }
 | 
			
		||||
  async unrouteAll(options) {
 | 
			
		||||
    await this._unrouteInternal(this._routes, [], options?.behavior);
 | 
			
		||||
    this._disposeHarRouters();
 | 
			
		||||
  }
 | 
			
		||||
  async unroute(url, handler) {
 | 
			
		||||
    const removed = [];
 | 
			
		||||
    const remaining = [];
 | 
			
		||||
    for (const route of this._routes) {
 | 
			
		||||
      if ((0, import_urlMatch.urlMatchesEqual)(route.url, url) && (!handler || route.handler === handler))
 | 
			
		||||
        removed.push(route);
 | 
			
		||||
      else
 | 
			
		||||
        remaining.push(route);
 | 
			
		||||
    }
 | 
			
		||||
    await this._unrouteInternal(removed, remaining, "default");
 | 
			
		||||
  }
 | 
			
		||||
  async _unrouteInternal(removed, remaining, behavior) {
 | 
			
		||||
    this._routes = remaining;
 | 
			
		||||
    if (behavior && behavior !== "default") {
 | 
			
		||||
      const promises = removed.map((routeHandler) => routeHandler.stop(behavior));
 | 
			
		||||
      await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
    await this._updateInterceptionPatterns({ title: "Unroute requests" });
 | 
			
		||||
  }
 | 
			
		||||
  async _updateInterceptionPatterns(options) {
 | 
			
		||||
    const patterns = import_network.RouteHandler.prepareInterceptionPatterns(this._routes);
 | 
			
		||||
    await this._wrapApiCall(() => this._channel.setNetworkInterceptionPatterns({ patterns }), options);
 | 
			
		||||
  }
 | 
			
		||||
  async _updateWebSocketInterceptionPatterns(options) {
 | 
			
		||||
    const patterns = import_network.WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes);
 | 
			
		||||
    await this._wrapApiCall(() => this._channel.setWebSocketInterceptionPatterns({ patterns }), options);
 | 
			
		||||
  }
 | 
			
		||||
  async screenshot(options = {}) {
 | 
			
		||||
    const mask = options.mask;
 | 
			
		||||
    const copy = { ...options, mask: void 0, timeout: this._timeoutSettings.timeout(options) };
 | 
			
		||||
    if (!copy.type)
 | 
			
		||||
      copy.type = (0, import_elementHandle.determineScreenshotType)(options);
 | 
			
		||||
    if (mask) {
 | 
			
		||||
      copy.mask = mask.map((locator) => ({
 | 
			
		||||
        frame: locator._frame._channel,
 | 
			
		||||
        selector: locator._selector
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
    const result = await this._channel.screenshot(copy);
 | 
			
		||||
    if (options.path) {
 | 
			
		||||
      await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
 | 
			
		||||
      await this._platform.fs().promises.writeFile(options.path, result.binary);
 | 
			
		||||
    }
 | 
			
		||||
    return result.binary;
 | 
			
		||||
  }
 | 
			
		||||
  async _expectScreenshot(options) {
 | 
			
		||||
    const mask = options?.mask ? options?.mask.map((locator2) => ({
 | 
			
		||||
      frame: locator2._frame._channel,
 | 
			
		||||
      selector: locator2._selector
 | 
			
		||||
    })) : void 0;
 | 
			
		||||
    const locator = options.locator ? {
 | 
			
		||||
      frame: options.locator._frame._channel,
 | 
			
		||||
      selector: options.locator._selector
 | 
			
		||||
    } : void 0;
 | 
			
		||||
    return await this._channel.expectScreenshot({
 | 
			
		||||
      ...options,
 | 
			
		||||
      isNot: !!options.isNot,
 | 
			
		||||
      locator,
 | 
			
		||||
      mask
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  async title() {
 | 
			
		||||
    return await this._mainFrame.title();
 | 
			
		||||
  }
 | 
			
		||||
  async bringToFront() {
 | 
			
		||||
    await this._channel.bringToFront();
 | 
			
		||||
  }
 | 
			
		||||
  async [Symbol.asyncDispose]() {
 | 
			
		||||
    await this.close();
 | 
			
		||||
  }
 | 
			
		||||
  async close(options = {}) {
 | 
			
		||||
    this._closeReason = options.reason;
 | 
			
		||||
    this._closeWasCalled = true;
 | 
			
		||||
    try {
 | 
			
		||||
      if (this._ownedContext)
 | 
			
		||||
        await this._ownedContext.close();
 | 
			
		||||
      else
 | 
			
		||||
        await this._channel.close(options);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if ((0, import_errors.isTargetClosedError)(e) && !options.runBeforeUnload)
 | 
			
		||||
        return;
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  isClosed() {
 | 
			
		||||
    return this._closed;
 | 
			
		||||
  }
 | 
			
		||||
  async click(selector, options) {
 | 
			
		||||
    return await this._mainFrame.click(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async dragAndDrop(source, target, options) {
 | 
			
		||||
    return await this._mainFrame.dragAndDrop(source, target, options);
 | 
			
		||||
  }
 | 
			
		||||
  async dblclick(selector, options) {
 | 
			
		||||
    await this._mainFrame.dblclick(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async tap(selector, options) {
 | 
			
		||||
    return await this._mainFrame.tap(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async fill(selector, value, options) {
 | 
			
		||||
    return await this._mainFrame.fill(selector, value, options);
 | 
			
		||||
  }
 | 
			
		||||
  locator(selector, options) {
 | 
			
		||||
    return this.mainFrame().locator(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByTestId(testId) {
 | 
			
		||||
    return this.mainFrame().getByTestId(testId);
 | 
			
		||||
  }
 | 
			
		||||
  getByAltText(text, options) {
 | 
			
		||||
    return this.mainFrame().getByAltText(text, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByLabel(text, options) {
 | 
			
		||||
    return this.mainFrame().getByLabel(text, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByPlaceholder(text, options) {
 | 
			
		||||
    return this.mainFrame().getByPlaceholder(text, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByText(text, options) {
 | 
			
		||||
    return this.mainFrame().getByText(text, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByTitle(text, options) {
 | 
			
		||||
    return this.mainFrame().getByTitle(text, options);
 | 
			
		||||
  }
 | 
			
		||||
  getByRole(role, options = {}) {
 | 
			
		||||
    return this.mainFrame().getByRole(role, options);
 | 
			
		||||
  }
 | 
			
		||||
  frameLocator(selector) {
 | 
			
		||||
    return this.mainFrame().frameLocator(selector);
 | 
			
		||||
  }
 | 
			
		||||
  async focus(selector, options) {
 | 
			
		||||
    return await this._mainFrame.focus(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async textContent(selector, options) {
 | 
			
		||||
    return await this._mainFrame.textContent(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async innerText(selector, options) {
 | 
			
		||||
    return await this._mainFrame.innerText(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async innerHTML(selector, options) {
 | 
			
		||||
    return await this._mainFrame.innerHTML(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async getAttribute(selector, name, options) {
 | 
			
		||||
    return await this._mainFrame.getAttribute(selector, name, options);
 | 
			
		||||
  }
 | 
			
		||||
  async inputValue(selector, options) {
 | 
			
		||||
    return await this._mainFrame.inputValue(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async isChecked(selector, options) {
 | 
			
		||||
    return await this._mainFrame.isChecked(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async isDisabled(selector, options) {
 | 
			
		||||
    return await this._mainFrame.isDisabled(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async isEditable(selector, options) {
 | 
			
		||||
    return await this._mainFrame.isEditable(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async isEnabled(selector, options) {
 | 
			
		||||
    return await this._mainFrame.isEnabled(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async isHidden(selector, options) {
 | 
			
		||||
    return await this._mainFrame.isHidden(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async isVisible(selector, options) {
 | 
			
		||||
    return await this._mainFrame.isVisible(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async hover(selector, options) {
 | 
			
		||||
    return await this._mainFrame.hover(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async selectOption(selector, values, options) {
 | 
			
		||||
    return await this._mainFrame.selectOption(selector, values, options);
 | 
			
		||||
  }
 | 
			
		||||
  async setInputFiles(selector, files, options) {
 | 
			
		||||
    return await this._mainFrame.setInputFiles(selector, files, options);
 | 
			
		||||
  }
 | 
			
		||||
  async type(selector, text, options) {
 | 
			
		||||
    return await this._mainFrame.type(selector, text, options);
 | 
			
		||||
  }
 | 
			
		||||
  async press(selector, key, options) {
 | 
			
		||||
    return await this._mainFrame.press(selector, key, options);
 | 
			
		||||
  }
 | 
			
		||||
  async check(selector, options) {
 | 
			
		||||
    return await this._mainFrame.check(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async uncheck(selector, options) {
 | 
			
		||||
    return await this._mainFrame.uncheck(selector, options);
 | 
			
		||||
  }
 | 
			
		||||
  async setChecked(selector, checked, options) {
 | 
			
		||||
    return await this._mainFrame.setChecked(selector, checked, options);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForTimeout(timeout) {
 | 
			
		||||
    return await this._mainFrame.waitForTimeout(timeout);
 | 
			
		||||
  }
 | 
			
		||||
  async waitForFunction(pageFunction, arg, options) {
 | 
			
		||||
    return await this._mainFrame.waitForFunction(pageFunction, arg, options);
 | 
			
		||||
  }
 | 
			
		||||
  workers() {
 | 
			
		||||
    return [...this._workers];
 | 
			
		||||
  }
 | 
			
		||||
  async pause(_options) {
 | 
			
		||||
    if (this._platform.isJSDebuggerAttached())
 | 
			
		||||
      return;
 | 
			
		||||
    const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout();
 | 
			
		||||
    const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout();
 | 
			
		||||
    this._browserContext.setDefaultNavigationTimeout(0);
 | 
			
		||||
    this._browserContext.setDefaultTimeout(0);
 | 
			
		||||
    this._instrumentation?.onWillPause({ keepTestTimeout: !!_options?.__testHookKeepTestTimeout });
 | 
			
		||||
    await this._closedOrCrashedScope.safeRace(this.context()._channel.pause());
 | 
			
		||||
    this._browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout);
 | 
			
		||||
    this._browserContext.setDefaultTimeout(defaultTimeout);
 | 
			
		||||
  }
 | 
			
		||||
  async pdf(options = {}) {
 | 
			
		||||
    const transportOptions = { ...options };
 | 
			
		||||
    if (transportOptions.margin)
 | 
			
		||||
      transportOptions.margin = { ...transportOptions.margin };
 | 
			
		||||
    if (typeof options.width === "number")
 | 
			
		||||
      transportOptions.width = options.width + "px";
 | 
			
		||||
    if (typeof options.height === "number")
 | 
			
		||||
      transportOptions.height = options.height + "px";
 | 
			
		||||
    for (const margin of ["top", "right", "bottom", "left"]) {
 | 
			
		||||
      const index = margin;
 | 
			
		||||
      if (options.margin && typeof options.margin[index] === "number")
 | 
			
		||||
        transportOptions.margin[index] = transportOptions.margin[index] + "px";
 | 
			
		||||
    }
 | 
			
		||||
    const result = await this._channel.pdf(transportOptions);
 | 
			
		||||
    if (options.path) {
 | 
			
		||||
      const platform = this._platform;
 | 
			
		||||
      await platform.fs().promises.mkdir(platform.path().dirname(options.path), { recursive: true });
 | 
			
		||||
      await platform.fs().promises.writeFile(options.path, result.pdf);
 | 
			
		||||
    }
 | 
			
		||||
    return result.pdf;
 | 
			
		||||
  }
 | 
			
		||||
  async _snapshotForAI(options = {}) {
 | 
			
		||||
    const result = await this._channel.snapshotForAI({ timeout: this._timeoutSettings.timeout(options) });
 | 
			
		||||
    return result.snapshot;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
class BindingCall extends import_channelOwner.ChannelOwner {
 | 
			
		||||
  static from(channel) {
 | 
			
		||||
    return channel._object;
 | 
			
		||||
  }
 | 
			
		||||
  constructor(parent, type, guid, initializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
  }
 | 
			
		||||
  async call(func) {
 | 
			
		||||
    try {
 | 
			
		||||
      const frame = import_frame.Frame.from(this._initializer.frame);
 | 
			
		||||
      const source = {
 | 
			
		||||
        context: frame._page.context(),
 | 
			
		||||
        page: frame._page,
 | 
			
		||||
        frame
 | 
			
		||||
      };
 | 
			
		||||
      let result;
 | 
			
		||||
      if (this._initializer.handle)
 | 
			
		||||
        result = await func(source, import_jsHandle.JSHandle.from(this._initializer.handle));
 | 
			
		||||
      else
 | 
			
		||||
        result = await func(source, ...this._initializer.args.map(import_jsHandle.parseResult));
 | 
			
		||||
      this._channel.resolve({ result: (0, import_jsHandle.serializeArgument)(result) }).catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      this._channel.reject({ error: (0, import_errors.serializeError)(e) }).catch(() => {
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function trimUrl(param) {
 | 
			
		||||
  if ((0, import_rtti.isRegExp)(param))
 | 
			
		||||
    return `/${(0, import_stringUtils.trimStringWithEllipsis)(param.source, 50)}/${param.flags}`;
 | 
			
		||||
  if ((0, import_rtti.isString)(param))
 | 
			
		||||
    return `"${(0, import_stringUtils.trimStringWithEllipsis)(param, 50)}"`;
 | 
			
		||||
}
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  BindingCall,
 | 
			
		||||
  Page
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										74
									
								
								node_modules/playwright-core/lib/client/platform.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								node_modules/playwright-core/lib/client/platform.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __defProp = Object.defineProperty;
 | 
			
		||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
			
		||||
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
			
		||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
			
		||||
var __export = (target, all) => {
 | 
			
		||||
  for (var name in all)
 | 
			
		||||
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
			
		||||
};
 | 
			
		||||
var __copyProps = (to, from, except, desc) => {
 | 
			
		||||
  if (from && typeof from === "object" || typeof from === "function") {
 | 
			
		||||
    for (let key of __getOwnPropNames(from))
 | 
			
		||||
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
			
		||||
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
			
		||||
  }
 | 
			
		||||
  return to;
 | 
			
		||||
};
 | 
			
		||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
			
		||||
var platform_exports = {};
 | 
			
		||||
__export(platform_exports, {
 | 
			
		||||
  emptyPlatform: () => emptyPlatform
 | 
			
		||||
});
 | 
			
		||||
module.exports = __toCommonJS(platform_exports);
 | 
			
		||||
var import_colors = require("../utils/isomorphic/colors");
 | 
			
		||||
const noopZone = {
 | 
			
		||||
  push: () => noopZone,
 | 
			
		||||
  pop: () => noopZone,
 | 
			
		||||
  run: (func) => func(),
 | 
			
		||||
  data: () => void 0
 | 
			
		||||
};
 | 
			
		||||
const emptyPlatform = {
 | 
			
		||||
  name: "empty",
 | 
			
		||||
  boxedStackPrefixes: () => [],
 | 
			
		||||
  calculateSha1: async () => {
 | 
			
		||||
    throw new Error("Not implemented");
 | 
			
		||||
  },
 | 
			
		||||
  colors: import_colors.webColors,
 | 
			
		||||
  createGuid: () => {
 | 
			
		||||
    throw new Error("Not implemented");
 | 
			
		||||
  },
 | 
			
		||||
  defaultMaxListeners: () => 10,
 | 
			
		||||
  env: {},
 | 
			
		||||
  fs: () => {
 | 
			
		||||
    throw new Error("Not implemented");
 | 
			
		||||
  },
 | 
			
		||||
  inspectCustom: void 0,
 | 
			
		||||
  isDebugMode: () => false,
 | 
			
		||||
  isJSDebuggerAttached: () => false,
 | 
			
		||||
  isLogEnabled(name) {
 | 
			
		||||
    return false;
 | 
			
		||||
  },
 | 
			
		||||
  isUnderTest: () => false,
 | 
			
		||||
  log(name, message) {
 | 
			
		||||
  },
 | 
			
		||||
  path: () => {
 | 
			
		||||
    throw new Error("Function not implemented.");
 | 
			
		||||
  },
 | 
			
		||||
  pathSeparator: "/",
 | 
			
		||||
  showInternalStackFrames: () => false,
 | 
			
		||||
  streamFile(path, writable) {
 | 
			
		||||
    throw new Error("Streams are not available");
 | 
			
		||||
  },
 | 
			
		||||
  streamReadable: (channel) => {
 | 
			
		||||
    throw new Error("Streams are not available");
 | 
			
		||||
  },
 | 
			
		||||
  streamWritable: (channel) => {
 | 
			
		||||
    throw new Error("Streams are not available");
 | 
			
		||||
  },
 | 
			
		||||
  zones: { empty: noopZone, current: () => noopZone }
 | 
			
		||||
};
 | 
			
		||||
// Annotate the CommonJS export names for ESM import in node:
 | 
			
		||||
0 && (module.exports = {
 | 
			
		||||
  emptyPlatform
 | 
			
		||||
});
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user