<?php
/**
 * Plugin Name: OpenPanel Self-Hosted
 * Plugin URI: https://github.com/airano-ir/mcphub
 * Description: OpenPanel analytics with full Self-Hosted instance support. Ad-blocker resistant proxy, privacy-friendly cookie-less tracking. Fork of the official OpenPanel plugin with self-hosted enhancements.
 * Version: 1.1.1
 * Author: OpenPanel / MCP Hub
 * Author URI: https://github.com/airano-ir
 * License: GPLv2 or later
 * Requires at least: 5.8
 * Requires PHP: 7.4
 * Tested up to: 6.9
 * Text Domain: openpanel-self-hosted
 */

if (!defined('ABSPATH')) { exit; }

final class OP_WP_Proxy {
    const VERSION = '1.1.1';
    const OPTION_KEY   = 'op_wp_proxy_settings';
    const TRANSIENT_JS = 'op_wp_op1_js';
    const REST_NS      = 'openpanel';
    const REST_ROUTE   = '/(?P<path>.*)'; // wildcard path passthrough
    const CACHE_TIMEOUT = WEEK_IN_SECONDS;

    // Cloud defaults
    const CLOUD_JS_URL    = 'https://openpanel.dev/op1.js';
    const CLOUD_API_BASE  = 'https://api.openpanel.dev/';

    public function __construct() {
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_menu', [$this, 'add_settings_page']);
        add_action('wp_enqueue_scripts', [$this, 'inject_inline_sdk'], 0);
        add_action('rest_api_init', [$this, 'register_proxy_route']);
        add_action('admin_init', [$this, 'handle_cache_clear']);
    }

    /** ---------------- Settings ---------------- */
    public function register_settings() {
        register_setting(self::OPTION_KEY, self::OPTION_KEY, [
            'type' => 'array',
            'show_in_rest' => false,
            'sanitize_callback' => function($input) {
                $out = [];
                $out['hosting_mode'] = isset($input['hosting_mode']) && $input['hosting_mode'] === 'selfhosted' ? 'selfhosted' : 'cloud';
                $out['client_id'] = isset($input['client_id']) ? sanitize_text_field($input['client_id']) : '';
                $out['api_url'] = isset($input['api_url']) ? esc_url_raw(untrailingslashit($input['api_url'])) : '';
                $out['dashboard_url'] = isset($input['dashboard_url']) ? esc_url_raw(untrailingslashit($input['dashboard_url'])) : '';
                $out['track_screen'] = !empty($input['track_screen']) ? 1 : 0;
                $out['track_outgoing'] = !empty($input['track_outgoing']) ? 1 : 0;
                $out['track_attributes'] = !empty($input['track_attributes']) ? 1 : 0;
                return $out;
            }
        ]);

        // Section: Hosting Mode
        add_settings_section('op_hosting', __('Hosting Mode', 'openpanel-self-hosted'), function() {
            echo '<p>' . esc_html__('Choose between OpenPanel Cloud or your own Self-Hosted instance.', 'openpanel-self-hosted') . '</p>';
        }, self::OPTION_KEY);

        add_settings_field('hosting_mode', __('Mode', 'openpanel-self-hosted'), function() {
            $opts = get_option(self::OPTION_KEY);
            $mode = isset($opts['hosting_mode']) ? $opts['hosting_mode'] : 'cloud';
            ?>
            <label>
                <input type="radio" name="<?php echo esc_attr(self::OPTION_KEY); ?>[hosting_mode]" value="cloud" <?php checked($mode, 'cloud'); ?> class="op-hosting-mode">
                <?php esc_html_e('Cloud (openpanel.dev)', 'openpanel-self-hosted'); ?>
            </label><br>
            <label>
                <input type="radio" name="<?php echo esc_attr(self::OPTION_KEY); ?>[hosting_mode]" value="selfhosted" <?php checked($mode, 'selfhosted'); ?> class="op-hosting-mode">
                <?php esc_html_e('Self-Hosted', 'openpanel-self-hosted'); ?>
            </label>
            <p class="description"><?php esc_html_e('Select Self-Hosted if you run your own OpenPanel instance.', 'openpanel-self-hosted'); ?></p>
            <?php
        }, self::OPTION_KEY, 'op_hosting');

        // Section: Self-Hosted URLs
        add_settings_section('op_selfhosted', __('Self-Hosted Settings', 'openpanel-self-hosted'), function() {
            echo '<p>' . esc_html__('Configure your Self-Hosted OpenPanel URLs. Only required if using Self-Hosted mode.', 'openpanel-self-hosted') . '</p>';
        }, self::OPTION_KEY);

        add_settings_field('api_url', __('API URL', 'openpanel-self-hosted'), function() {
            $opts = get_option(self::OPTION_KEY);
            printf('<input type="url" name="%s[api_url]" value="%s" class="regular-text op-selfhosted-field" placeholder="https://api.openpanel.yourdomain.com"/>',
                esc_attr(self::OPTION_KEY),
                isset($opts['api_url']) ? esc_attr($opts['api_url']) : ''
            );
            echo '<p class="description">' . esc_html__('Your OpenPanel API endpoint (e.g., https://api.openpanel.yourdomain.com)', 'openpanel-self-hosted') . '</p>';
        }, self::OPTION_KEY, 'op_selfhosted');

        add_settings_field('dashboard_url', __('Dashboard URL', 'openpanel-self-hosted'), function() {
            $opts = get_option(self::OPTION_KEY);
            printf('<input type="url" name="%s[dashboard_url]" value="%s" class="regular-text op-selfhosted-field" placeholder="https://openpanel.yourdomain.com"/>',
                esc_attr(self::OPTION_KEY),
                isset($opts['dashboard_url']) ? esc_attr($opts['dashboard_url']) : ''
            );
            echo '<p class="description">' . esc_html__('Your OpenPanel Dashboard URL — used to load op1.js from your server instead of the CDN (e.g., https://openpanel.yourdomain.com)', 'openpanel-self-hosted') . '</p>';
        }, self::OPTION_KEY, 'op_selfhosted');

        // Section: Main Settings
        add_settings_section('op_main', __('OpenPanel Settings', 'openpanel-self-hosted'), function() {
            echo '<p>' . esc_html__('Set your OpenPanel Client ID. The SDK and requests are served from your domain to avoid ad blockers.', 'openpanel-self-hosted') . '</p>';
        }, self::OPTION_KEY);

        add_settings_field('client_id', __('Client ID', 'openpanel-self-hosted'), function() {
            $opts = get_option(self::OPTION_KEY);
            printf('<input type="text" name="%s[client_id]" value="%s" class="regular-text" placeholder="op_client_..."/>',
                esc_attr(self::OPTION_KEY),
                isset($opts['client_id']) ? esc_attr($opts['client_id']) : ''
            );
        }, self::OPTION_KEY, 'op_main');

        add_settings_field('toggles', __('Auto-tracking (optional)', 'openpanel-self-hosted'), function() {
            $o = get_option(self::OPTION_KEY);
            // Default track_screen to true if not set
            $track_screen = isset($o['track_screen']) ? !empty($o['track_screen']) : true;
            ?>
            <label><input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[track_screen]" <?php checked($track_screen); ?>> <?php esc_html_e('Track page views automatically', 'openpanel-self-hosted'); ?></label><br>
            <label><input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[track_outgoing]" <?php checked(!empty($o['track_outgoing'])); ?>> <?php esc_html_e('Track clicks on outgoing links', 'openpanel-self-hosted'); ?></label><br>
            <label><input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[track_attributes]" <?php checked(!empty($o['track_attributes'])); ?>> <?php esc_html_e('Track additional page attributes', 'openpanel-self-hosted'); ?></label>
            <?php
        }, self::OPTION_KEY, 'op_main');
    }

    public function add_settings_page() {
        add_options_page(
            'OpenPanel',
            'OpenPanel',
            'manage_options',
            'op-wp-proxy',
            [$this, 'render_settings_page']
        );
    }

    public function handle_cache_clear() {
        if (isset($_POST['op_clear_cache']) && current_user_can('manage_options')) {
            if (isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'op_clear_cache_nonce')) {
                delete_transient(self::TRANSIENT_JS);
                add_action('admin_notices', function() {
                    echo '<div class="notice notice-success is-dismissible"><p>' . 
                         esc_html__('OpenPanel cache cleared successfully. The latest op1.js will be fetched on the next page load.', 'openpanel-self-hosted') . 
                         '</p></div>';
                });
            }
        }
    }

    public function render_settings_page() {
        $opts = get_option(self::OPTION_KEY);
        $mode = isset($opts['hosting_mode']) ? $opts['hosting_mode'] : 'cloud';
        ?>
        <div class="wrap">
            <h1>OpenPanel</h1>
            <form method="post" action="options.php">
                <?php
                settings_fields(self::OPTION_KEY);
                do_settings_sections(self::OPTION_KEY);
                submit_button(__('Save Settings', 'openpanel-self-hosted'));
                ?>
            </form>

            <hr style="margin: 2rem 0;">

            <h2><?php esc_html_e('Cache Management', 'openpanel-self-hosted'); ?></h2>
            <p><?php esc_html_e('Clear the cached op1.js file to force fetch the latest version from OpenPanel.', 'openpanel-self-hosted'); ?></p>

            <form method="post" action="">
                <?php wp_nonce_field('op_clear_cache_nonce'); ?>
                <input type="submit" name="op_clear_cache" class="button button-secondary" value="<?php esc_attr_e('Clear Cache & Force Refresh', 'openpanel-self-hosted'); ?>">
            </form>

            <?php
            // Check if transient exists and get expiration info
            $cached_js = get_transient(self::TRANSIENT_JS);
            $timeout_option = '_transient_timeout_' . self::TRANSIENT_JS;
            $cached_time = get_option($timeout_option);

            if ($cached_js !== false && $cached_time) {
                $time_remaining = $cached_time - time();
                if ($time_remaining > 0) {
                    echo '<p style="margin-top:1rem;color:#666;">' .
                         /* translators: %s: human readable time difference */
                         sprintf(esc_html__('Cache expires in %s', 'openpanel-self-hosted'), esc_html(human_time_diff(time(), $cached_time))) .
                         '</p>';
                } else {
                    echo '<p style="margin-top:1rem;color:#666;">' .
                         esc_html__('Cache has expired and will refresh on next page load.', 'openpanel-self-hosted') .
                         '</p>';
                }
            } else {
                echo '<p style="margin-top:1rem;color:#666;">' .
                     esc_html__('No cached version found. op1.js will be fetched on next page load.', 'openpanel-self-hosted') .
                     '</p>';
            }
            ?>

            <p style="margin-top:1rem;color:#666;">
                <?php esc_html_e('The plugin fetches and inlines op1.js (cached for 1 week). If fetching fails, it falls back to the CDN script.', 'openpanel-self-hosted'); ?>
            </p>

            <hr style="margin: 2rem 0;">

            <h2><?php esc_html_e('Current Configuration', 'openpanel-self-hosted'); ?></h2>
            <table class="widefat" style="max-width: 600px;">
                <tr>
                    <td><strong><?php esc_html_e('Mode', 'openpanel-self-hosted'); ?></strong></td>
                    <td><?php echo esc_html($mode === 'selfhosted' ? 'Self-Hosted' : 'Cloud'); ?></td>
                </tr>
                <tr>
                    <td><strong><?php esc_html_e('API URL', 'openpanel-self-hosted'); ?></strong></td>
                    <td><code><?php echo esc_html($this->get_api_base()); ?></code></td>
                </tr>
                <tr>
                    <td><strong><?php esc_html_e('JS URL', 'openpanel-self-hosted'); ?></strong></td>
                    <td><code><?php echo esc_html($this->get_js_url()); ?></code></td>
                </tr>
                <tr>
                    <td><strong><?php esc_html_e('Proxy Endpoint', 'openpanel-self-hosted'); ?></strong></td>
                    <td><code><?php echo esc_html(rest_url(self::REST_NS . '/')); ?></code></td>
                </tr>
            </table>
        </div>

        <style>
            .op-selfhosted-section { transition: opacity 0.3s; }
            .op-selfhosted-section.hidden { opacity: 0.5; pointer-events: none; }
        </style>
        <script>
        jQuery(document).ready(function($) {
            function toggleSelfHostedFields() {
                var mode = $('input[name="<?php echo esc_js(self::OPTION_KEY); ?>[hosting_mode]"]:checked').val();
                var $selfHostedFields = $('.op-selfhosted-field').closest('tr');
                var $selfHostedSection = $selfHostedFields.closest('table');

                if (mode === 'selfhosted') {
                    $selfHostedFields.show();
                } else {
                    $selfHostedFields.hide();
                }
            }

            toggleSelfHostedFields();
            $('input[name="<?php echo esc_js(self::OPTION_KEY); ?>[hosting_mode]"]').on('change', toggleSelfHostedFields);
        });
        </script>
        <?php
    }

    /** ---------------- Helper Methods ---------------- */

    /**
     * Get the API base URL based on hosting mode
     */
    private function get_api_base() {
        $opts = get_option(self::OPTION_KEY);
        $mode = isset($opts['hosting_mode']) ? $opts['hosting_mode'] : 'cloud';

        if ($mode === 'selfhosted' && !empty($opts['api_url'])) {
            return trailingslashit($opts['api_url']);
        }

        return self::CLOUD_API_BASE;
    }

    /**
     * Get the JS URL based on hosting mode
     * Cloud: loads from official CDN (openpanel.dev)
     * Self-hosted: loads from dashboard URL if configured, otherwise falls back to CDN
     */
    private function get_js_url() {
        $opts = get_option(self::OPTION_KEY);
        $mode = isset($opts['hosting_mode']) ? $opts['hosting_mode'] : 'cloud';

        if ($mode === 'selfhosted' && !empty($opts['dashboard_url'])) {
            return trailingslashit($opts['dashboard_url']) . 'op1.js';
        }

        return self::CLOUD_JS_URL;
    }

    /**
     * Get allowed hosts for proxy validation
     */
    private function get_allowed_hosts() {
        $hosts = [
            'api.openpanel.dev',
            'openpanel.dev'
        ];

        $opts = get_option(self::OPTION_KEY);
        $mode = isset($opts['hosting_mode']) ? $opts['hosting_mode'] : 'cloud';

        if ($mode === 'selfhosted') {
            if (!empty($opts['api_url'])) {
                $parsed = wp_parse_url($opts['api_url']);
                if (isset($parsed['host'])) {
                    $hosts[] = $parsed['host'];
                }
            }
            if (!empty($opts['dashboard_url'])) {
                $parsed = wp_parse_url($opts['dashboard_url']);
                if (isset($parsed['host'])) {
                    $hosts[] = $parsed['host'];
                }
            }
        }

        return array_unique($hosts);
    }

    /** ---------------- Inline SDK ---------------- */
    public function inject_inline_sdk() {
        if (is_admin()) return;

        $opts = get_option(self::OPTION_KEY);
        $clientId = isset($opts['client_id']) ? trim($opts['client_id']) : '';
        if ($clientId === '') return; // don't inject if not configured

        // For self-hosted, also require API URL to be configured
        $mode = isset($opts['hosting_mode']) ? $opts['hosting_mode'] : 'cloud';
        if ($mode === 'selfhosted' && empty($opts['api_url'])) {
            return; // don't inject if self-hosted but not configured
        }

        $apiUrl = untrailingslashit( rest_url(self::REST_NS . '/') );
        $jsUrl = $this->get_js_url();

        $init = [
            'clientId'           => $clientId,
            'apiUrl'             => $apiUrl,
            'trackScreenViews'   => isset($opts['track_screen']) ? !empty($opts['track_screen']) : true,
            'trackOutgoingLinks' => !empty($opts['track_outgoing']),
            'trackAttributes'    => !empty($opts['track_attributes']),
        ];

        $bootstrap = "(function(){window.op=window.op||function(){(window.op.q=window.op.q||[]).push(arguments)};window.op('init'," . wp_json_encode($init) . ");})();";

        $op_js = get_transient(self::TRANSIENT_JS);
        if ($op_js === false) {
            $res = wp_remote_get($jsUrl, ['timeout' => 8]);
            if (!is_wp_error($res) && 200 === wp_remote_retrieve_response_code($res)) {
                $op_js = wp_remote_retrieve_body($res);
                set_transient(self::TRANSIENT_JS, $op_js, self::CACHE_TIMEOUT);
            }
        }

        wp_register_script('op-inline-stub', false, [], self::VERSION, true);
        wp_enqueue_script('op-inline-stub');

        wp_add_inline_script('op-inline-stub', $bootstrap, 'before');

        if (!empty($op_js)) {
            // Validate cached JavaScript content before output
            if ($this->is_valid_javascript_content($op_js)) {
                wp_add_inline_script('op-inline-stub', $op_js, 'after');
            } else {
                // Fall back to external script if cached content appears invalid or unsafe
                wp_enqueue_script('openpanel-op1', $jsUrl, [], self::VERSION, true);
            }
        } else {
            wp_enqueue_script('openpanel-op1', $jsUrl, [], self::VERSION, true);
        }
    }

    /** ---------------- Proxy Route ---------------- */
    public function register_proxy_route() {
        register_rest_route(self::REST_NS, self::REST_ROUTE, [
            'methods'  => \WP_REST_Server::ALLMETHODS,
            // INTENTIONALLY PUBLIC ENDPOINT: This endpoint must be publicly accessible to receive
            // analytics data from frontend JavaScript running in users' browsers. No authentication
            // is required as this acts as a proxy for OpenPanel analytics collection.
            // 
            // Security measures in place:
            // 1. Only proxies to whitelisted OpenPanel API endpoints (is_valid_proxy_target)
            // 2. All input data is sanitized and validated before forwarding
            // 3. Proper CORS headers are set for same-origin requests only
            // 4. No sensitive WordPress data is exposed through this endpoint
            'permission_callback' => '__return_true',
            'callback' => [$this, 'proxy_request'],
            'args'     => [
                'path' => [
                    'description' => 'Remaining path to forward',
                    'required' => false,
                ],
            ],
        ]);
    }

    public function proxy_request(\WP_REST_Request $request) {
        try {
            // Handle CORS preflight quickly
            if (strtoupper($request->get_method()) === 'OPTIONS') {
                $resp = new \WP_REST_Response(null, 204);
                $this->add_cors_headers($resp);
                return $resp;
            }

            $path = ltrim($request->get_param('path') ?? '', '/');
            $api_base = $this->get_api_base();
            $target = rtrim($api_base, '/') . '/' . $path;

            // Security: Ensure we only proxy to OpenPanel API endpoints
            if (!$this->is_valid_proxy_target($target)) {
                $resp = new \WP_REST_Response(['error' => 'invalid_target', 'message' => 'Invalid proxy target'], 403);
                $this->add_cors_headers($resp);
                return $resp;
            }

            $method = strtoupper($request->get_method());
            $body   = $request->get_body();

            $incoming = $this->collect_request_headers();

            $query = $request->get_query_params();
            if (!empty($query)) {
                $target = add_query_arg($query, $target);
            }

            $args = [
                'method'  => $method,
                'timeout' => 10,
                'headers' => $incoming,
                'body'    => in_array($method, ['POST','PUT','PATCH','DELETE'], true) ? $body : null,
            ];

            $res = wp_remote_request($target, $args);

            if (is_wp_error($res)) {
                $resp = new \WP_REST_Response(['error' => 'proxy_failed', 'message' => $res->get_error_message()], 502);
                $this->add_cors_headers($resp);
                $resp->header('Cache-Control', 'no-store');
                return $resp;
            }

            $code = wp_remote_retrieve_response_code($res);
            $bodyOut = wp_remote_retrieve_body($res);

            // Handle empty response (common for tracking endpoints returning 202)
            if (empty($bodyOut)) {
                $resp = new \WP_REST_Response(['success' => true], $code ?: 202);
                $resp->header('Content-Type', 'application/json; charset=utf-8');
                $resp->header('Cache-Control', 'no-store');
                $this->add_cors_headers($resp);
                return $resp;
            }

            // Try to decode JSON response, otherwise use raw body
            $decoded = json_decode($bodyOut, true);
            $responseData = (json_last_error() === JSON_ERROR_NONE && $decoded !== null) ? $decoded : ['raw' => $bodyOut];

            $resp = new \WP_REST_Response($responseData, $code);

            // Set Content-Type header (skip copying other headers to avoid compatibility issues)
            $resp->header('Content-Type', 'application/json; charset=utf-8');
            $resp->header('Cache-Control', 'no-store');
            $this->add_cors_headers($resp);
            return $resp;
        } catch (\Exception $e) {
            $resp = new \WP_REST_Response(['error' => 'exception', 'message' => $e->getMessage()], 500);
            $this->add_cors_headers($resp);
            return $resp;
        } catch (\Error $e) {
            $resp = new \WP_REST_Response(['error' => 'error', 'message' => $e->getMessage()], 500);
            $this->add_cors_headers($resp);
            return $resp;
        }
    }

    private function add_cors_headers(\WP_REST_Response $resp) {
        $origin = get_site_url();
        $resp->header('Access-Control-Allow-Origin', $origin);
        $resp->header('Access-Control-Allow-Credentials', 'true');
        $resp->header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
        $resp->header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
        $resp->header('Vary', 'Origin');
    }

    private function collect_request_headers() {
        $headers = [];

        // Content-Type is a special header NOT prefixed with HTTP_ in PHP
        // This is critical for OpenPanel API which requires application/json
        if (!empty($_SERVER['CONTENT_TYPE'])) {
            $headers['Content-Type'] = sanitize_text_field(wp_unslash($_SERVER['CONTENT_TYPE']));
        }

        foreach ($_SERVER as $name => $value) {
            // Sanitize the header name and ensure it starts with HTTP_
            $name = sanitize_text_field($name);
            if (strpos($name, 'HTTP_') === 0) {
                // Extract and sanitize the header suffix
                $header_suffix = substr($name, 5);
                $header_suffix = sanitize_text_field($header_suffix);
                $header = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $header_suffix))));
                // remove hop-by-hop headers and Origin (we'll set it ourselves)
                $lk = strtolower($header);
                if (in_array($lk, ['host','content-length','accept-encoding','connection','keep-alive','transfer-encoding','upgrade','via','origin'], true)) {
                    continue;
                }
                // Sanitize the header value
                $headers[$header] = sanitize_text_field($value);
            }
        }
        if (!isset($headers['User-Agent'])) {
            $headers['User-Agent'] = 'OpenPanel-WP-Proxy';
        }
        // Set Origin header to WordPress site URL (required for OpenPanel CORS)
        $headers['Origin'] = get_site_url();
        return $headers;
    }

    private function is_valid_proxy_target($target) {
        $allowed_hosts = $this->get_allowed_hosts();

        $parsed = wp_parse_url($target);
        if (!$parsed || !isset($parsed['host'])) {
            return false;
        }

        return in_array($parsed['host'], $allowed_hosts, true) &&
               (empty($parsed['scheme']) || in_array($parsed['scheme'], ['https', 'http'], true));
    }

    private function is_valid_javascript_content($js_content) {
        // Comprehensive validation to ensure the content is safe JavaScript
        if (empty($js_content) || !is_string($js_content)) {
            return false;
        }
        
        // Note: We don't modify $js_content with wp_kses here because we need to preserve
        // the original JavaScript content for validation. wp_add_inline_script() handles
        // proper escaping when outputting to the page.
        
        // Ensure it doesn't contain HTML tags or script injection attempts
        if (preg_match('/<(?:script|iframe|object|embed|form|input|meta|link)/i', $js_content)) {
            return false;
        }
        
        // Check for potential XSS patterns
        if (preg_match('/(?:javascript:|data:|vbscript:|on\w+\s*=)/i', $js_content)) {
            return false;
        }
        
        // Check that it's a reasonable size for a JavaScript file (typical op1.js is ~5KB)
        $trimmed = trim($js_content);
        if (strlen($trimmed) < 10 || strlen($trimmed) > 20480) { // Max 20KB
            return false;
        }
        
        // Should contain typical JavaScript patterns (works for both minified and unminified)
        return (strpos($trimmed, 'function') !== false || 
                strpos($trimmed, 'var ') !== false || 
                strpos($trimmed, 'let ') !== false || 
                strpos($trimmed, 'const ') !== false ||
                strpos($trimmed, '=>') !== false ||
                // Handle minified code patterns like the actual op1.js
                preg_match('/[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*function/', $trimmed) ||
                preg_match('/\(\s*function\s*\(/', $trimmed));
    }
}

new OP_WP_Proxy();
