<?php
/**
 * Plugin Name: BW Lock Files
 * Description: Protects uploaded files (PDFs, documents, etc.) from unauthorized access. Works on any hosting including Nginx/Cloudways.
 * Version: 2.0.0
 * Author: Bowden Web
 * Requires at least: 5.0
 * Requires PHP: 7.4
 */

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

define('BW_LOCK_FILES_VERSION', '2.0.0');
define('BW_LOCK_FILES_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('BW_LOCK_FILES_PLUGIN_URL', plugin_dir_url(__FILE__));

class BW_Lock_Files {

    private static $instance = null;

    // File extensions to protect (non-images)
    private $protected_extensions = [
        'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
        'zip', 'rar', '7z', 'tar', 'gz',
        'txt', 'rtf', 'csv', 'xml', 'json',
        'mp3', 'mp4', 'wav', 'avi', 'mov', 'wmv', 'flv', 'mkv',
        'psd', 'ai', 'eps', 'indd'
    ];

    // Extensions to allow through (images, styles, scripts, fonts)
    private $allowed_extensions = [
        // Images
        'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'ico', 'bmp', 'tiff', 'tif',
        // Styles and scripts
        'css', 'js', 'map',
        // Fonts
        'woff', 'woff2', 'ttf', 'eot', 'otf',
    ];

    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        // Initialize
        add_action('init', [$this, 'init']);
        add_action('init', [$this, 'register_rewrite_rules']);
        add_action('template_redirect', [$this, 'handle_file_request']);

        // Intercept requests to original upload URLs
        add_action('template_redirect', [$this, 'intercept_original_urls'], 5);

        // Filter attachment URLs to return protected URLs
        add_filter('wp_get_attachment_url', [$this, 'filter_attachment_url'], 10, 2);

        // Admin
        add_action('admin_menu', [$this, 'add_admin_menu']);
        add_action('admin_init', [$this, 'handle_admin_actions']);
        add_action('admin_enqueue_scripts', [$this, 'admin_scripts']);

        // AJAX handlers
        add_action('wp_ajax_bw_lock_files_migrate', [$this, 'ajax_migrate']);
        add_action('wp_ajax_bw_lock_files_scan', [$this, 'ajax_scan']);

        // Activation/Deactivation
        register_activation_hook(__FILE__, [$this, 'activate']);
        register_deactivation_hook(__FILE__, [$this, 'deactivate']);

        // Flush rewrite rules on activation
        add_action('admin_init', [$this, 'maybe_flush_rules']);
    }

    public function init() {
        // Create protected directory if it doesn't exist
        $this->get_protected_dir();
    }

    /**
     * Get the protected storage directory
     * Tries to use a location outside webroot first, falls back to wp-content
     */
    public function get_protected_dir() {
        // Option 1: Try one level above webroot (works on most hosts including Cloudways)
        $above_webroot = dirname(ABSPATH) . '/bw-protected-files';

        // Check if we can write there
        if (!file_exists($above_webroot)) {
            @mkdir($above_webroot, 0755, true);
        }

        if (is_writable($above_webroot) || (file_exists($above_webroot) && is_writable(dirname($above_webroot)))) {
            // Create index.php for extra protection
            $index_file = $above_webroot . '/index.php';
            if (!file_exists($index_file)) {
                file_put_contents($index_file, '<?php // Silence is golden');
            }
            return $above_webroot;
        }

        // Option 2: Fall back to wp-content (files will have randomized names)
        $wp_content_protected = WP_CONTENT_DIR . '/bw-protected';
        if (!file_exists($wp_content_protected)) {
            mkdir($wp_content_protected, 0755, true);
        }

        // Create protective files
        $index_file = $wp_content_protected . '/index.php';
        if (!file_exists($index_file)) {
            file_put_contents($index_file, '<?php // Silence is golden');
        }

        $htaccess = $wp_content_protected . '/.htaccess';
        if (!file_exists($htaccess)) {
            file_put_contents($htaccess, "Order deny,allow\nDeny from all");
        }

        return $wp_content_protected;
    }

    /**
     * Check if protected directory is outside webroot
     */
    public function is_outside_webroot() {
        $protected_dir = $this->get_protected_dir();
        return strpos($protected_dir, ABSPATH) === false;
    }

    /**
     * Register rewrite rules for clean download URLs
     */
    public function register_rewrite_rules() {
        add_rewrite_rule(
            '^bw-file/([0-9]+)/?$',
            'index.php?bw_file_id=$matches[1]',
            'top'
        );
        add_rewrite_rule(
            '^bw-file/([0-9]+)/(.+)$',
            'index.php?bw_file_id=$matches[1]&bw_file_name=$matches[2]',
            'top'
        );

        add_rewrite_tag('%bw_file_id%', '([0-9]+)');
        add_rewrite_tag('%bw_file_name%', '(.+)');
    }

    public function maybe_flush_rules() {
        if (get_option('bw_lock_files_flush_rules')) {
            flush_rewrite_rules();
            delete_option('bw_lock_files_flush_rules');
        }
    }

    public function activate() {
        $this->create_tables();
        $this->get_protected_dir();
        update_option('bw_lock_files_flush_rules', true);
    }

    public function deactivate() {
        flush_rewrite_rules();
    }

    /**
     * Create database tables for file tracking
     */
    private function create_tables() {
        global $wpdb;

        $charset_collate = $wpdb->get_charset_collate();
        $table_name = $wpdb->prefix . 'bw_protected_files';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            original_url varchar(500) NOT NULL,
            original_path varchar(500) NOT NULL,
            protected_path varchar(500) NOT NULL,
            file_name varchar(255) NOT NULL,
            file_type varchar(50) NOT NULL,
            file_size bigint(20) NOT NULL DEFAULT 0,
            created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY original_url (original_url(191)),
            KEY original_path (original_path(191))
        ) $charset_collate;";

        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }

    /**
     * Handle incoming file requests
     */
    public function handle_file_request() {
        $file_id = get_query_var('bw_file_id');

        if (!$file_id) {
            return;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . 'bw_protected_files';

        $file = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM $table_name WHERE id = %d",
            $file_id
        ));

        if (!$file) {
            status_header(404);
            wp_die('File not found', 'Not Found', ['response' => 404]);
        }

        // Check if user is logged in
        if (!is_user_logged_in()) {
            // Build return URL
            $return_url = home_url('/bw-file/' . $file_id . '/' . urlencode($file->file_name));
            wp_redirect(wp_login_url($return_url));
            exit;
        }

        // Serve the file
        $this->serve_file($file->protected_path, $file->file_name, $file->file_type);
    }

    /**
     * Intercept requests to original upload URLs and redirect to protected URLs
     */
    public function intercept_original_urls() {
        // Only handle requests to wp-content/uploads
        $request_uri = $_SERVER['REQUEST_URI'] ?? '';
        if (strpos($request_uri, '/wp-content/uploads/') === false) {
            return;
        }

        // Extract the relative path from the URL
        if (preg_match('#/wp-content/uploads/(.+)$#', $request_uri, $matches)) {
            $relative_path = urldecode($matches[1]);

            // Remove query string if present
            if (($pos = strpos($relative_path, '?')) !== false) {
                $relative_path = substr($relative_path, 0, $pos);
            }

            // Build the original full path as it would have been stored
            $uploads_dir = wp_upload_dir();
            $original_path = $uploads_dir['basedir'] . '/' . $relative_path;

            // Check if this file has been protected
            global $wpdb;
            $table_name = $wpdb->prefix . 'bw_protected_files';

            $file = $wpdb->get_row($wpdb->prepare(
                "SELECT id, file_name FROM $table_name WHERE original_path = %s",
                $original_path
            ));

            if ($file) {
                // File is protected - check if user is logged in
                if (!is_user_logged_in()) {
                    $return_url = home_url('/bw-file/' . $file->id . '/' . urlencode($file->file_name));
                    wp_redirect(wp_login_url($return_url));
                    exit;
                }

                // User is logged in - redirect to protected URL
                $protected_url = home_url('/bw-file/' . $file->id . '/' . urlencode($file->file_name));
                wp_redirect($protected_url);
                exit;
            }
        }
    }

    /**
     * Filter attachment URLs to return protected URLs for migrated files
     */
    public function filter_attachment_url($url, $attachment_id) {
        // Get the file path from attachment meta
        $file = get_post_meta($attachment_id, '_wp_attached_file', true);
        if (!$file) {
            return $url;
        }

        // Build the full original path
        $uploads_dir = wp_upload_dir();
        $original_path = $uploads_dir['basedir'] . '/' . $file;

        // Check if this file has been protected
        global $wpdb;
        $table_name = $wpdb->prefix . 'bw_protected_files';

        $protected = $wpdb->get_row($wpdb->prepare(
            "SELECT id, file_name FROM $table_name WHERE original_path = %s",
            $original_path
        ));

        if ($protected) {
            // Return the protected URL instead
            return home_url('/bw-file/' . $protected->id . '/' . urlencode($protected->file_name));
        }

        return $url;
    }

    /**
     * Serve a protected file
     */
    private function serve_file($file_path, $file_name, $mime_type) {
        if (!file_exists($file_path)) {
            status_header(404);
            wp_die('File not found on disk', 'Not Found', ['response' => 404]);
        }

        $filesize = filesize($file_path);

        // Clear output buffers
        while (ob_get_level()) {
            ob_end_clean();
        }

        // Prevent caching
        nocache_headers();

        // Set headers
        header('Content-Type: ' . $mime_type);
        header('Content-Length: ' . $filesize);
        header('Content-Disposition: inline; filename="' . $file_name . '"');
        header('X-Content-Type-Options: nosniff');
        header('Cache-Control: private, no-cache, no-store, must-revalidate');

        // Output file
        readfile($file_path);
        exit;
    }

    /**
     * Check if a file should be protected based on extension
     */
    public function should_protect_file($file_path) {
        $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
        return !in_array($extension, $this->allowed_extensions);
    }

    /**
     * Add admin menu
     */
    public function add_admin_menu() {
        add_management_page(
            'BW Lock Files',
            'BW Lock Files',
            'manage_options',
            'bw-lock-files',
            [$this, 'render_admin_page']
        );
    }

    /**
     * Enqueue admin scripts
     */
    public function admin_scripts($hook) {
        if ($hook !== 'tools_page_bw-lock-files') {
            return;
        }

        wp_enqueue_style('bw-lock-files-admin', BW_LOCK_FILES_PLUGIN_URL . 'admin.css', [], BW_LOCK_FILES_VERSION);
        wp_enqueue_script('bw-lock-files-admin', BW_LOCK_FILES_PLUGIN_URL . 'admin.js', ['jquery'], BW_LOCK_FILES_VERSION, true);
        wp_localize_script('bw-lock-files-admin', 'bwLockFiles', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('bw_lock_files_nonce'),
        ]);
    }

    /**
     * Handle admin actions
     */
    public function handle_admin_actions() {
        // Handle any form submissions here
    }

    /**
     * AJAX: Scan uploads for files to migrate
     */
    public function ajax_scan() {
        check_ajax_referer('bw_lock_files_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Permission denied');
        }

        $uploads_dir = wp_upload_dir();
        $base_dir = $uploads_dir['basedir'];

        $files = $this->scan_directory($base_dir);

        $files_to_migrate = [];
        foreach ($files as $file) {
            if ($this->should_protect_file($file)) {
                $relative_path = str_replace($base_dir . '/', '', $file);
                $url = $uploads_dir['baseurl'] . '/' . $relative_path;
                $files_to_migrate[] = [
                    'path' => $file,
                    'relative' => $relative_path,
                    'url' => $url,
                    'name' => basename($file),
                    'size' => filesize($file),
                ];
            }
        }

        wp_send_json_success([
            'files' => $files_to_migrate,
            'count' => count($files_to_migrate),
            'total_size' => array_sum(array_column($files_to_migrate, 'size')),
        ]);
    }

    /**
     * Recursively scan directory for files
     */
    private function scan_directory($dir) {
        $files = [];

        if (!is_dir($dir)) {
            return $files;
        }

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $files[] = $file->getPathname();
            }
        }

        return $files;
    }

    /**
     * AJAX: Migrate a single file
     */
    public function ajax_migrate() {
        check_ajax_referer('bw_lock_files_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Permission denied');
        }

        $file_path = sanitize_text_field($_POST['file_path'] ?? '');
        $file_url = esc_url_raw($_POST['file_url'] ?? '');

        if (!$file_path || !file_exists($file_path)) {
            wp_send_json_error('File not found: ' . $file_path);
        }

        $result = $this->migrate_file($file_path, $file_url);

        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
        }

        wp_send_json_success($result);
    }

    /**
     * Migrate a single file to protected storage
     */
    public function migrate_file($file_path, $file_url) {
        global $wpdb;

        $uploads_dir = wp_upload_dir();
        $protected_dir = $this->get_protected_dir();
        $table_name = $wpdb->prefix . 'bw_protected_files';

        // Check if already migrated
        $existing = $wpdb->get_var($wpdb->prepare(
            "SELECT id FROM $table_name WHERE original_path = %s",
            $file_path
        ));

        if ($existing) {
            return ['status' => 'skipped', 'message' => 'Already migrated', 'file_id' => $existing];
        }

        // Get file info
        $file_name = basename($file_path);
        $relative_path = str_replace($uploads_dir['basedir'] . '/', '', $file_path);
        $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
        $mime_type = mime_content_type($file_path);
        $file_size = filesize($file_path);

        // Generate protected path - preserve directory structure
        $protected_path = $protected_dir . '/' . $relative_path;
        $protected_subdir = dirname($protected_path);

        // Create subdirectory if needed
        if (!file_exists($protected_subdir)) {
            mkdir($protected_subdir, 0755, true);
        }

        // Move file to protected storage
        if (!rename($file_path, $protected_path)) {
            // Try copy + delete if rename fails (cross-filesystem)
            if (!copy($file_path, $protected_path)) {
                return new WP_Error('move_failed', 'Could not move file to protected storage');
            }
            unlink($file_path);
        }

        // Insert into database
        $wpdb->insert($table_name, [
            'original_url' => $file_url,
            'original_path' => $file_path,
            'protected_path' => $protected_path,
            'file_name' => $file_name,
            'file_type' => $mime_type,
            'file_size' => $file_size,
        ]);

        $file_id = $wpdb->insert_id;

        if (!$file_id) {
            // Rollback - move file back
            rename($protected_path, $file_path);
            return new WP_Error('db_failed', 'Could not save file record to database');
        }

        // Generate new URL
        $new_url = home_url('/bw-file/' . $file_id . '/' . urlencode($file_name));

        // Update all references in the database
        $this->update_content_references($file_url, $new_url);

        return [
            'status' => 'success',
            'file_id' => $file_id,
            'old_url' => $file_url,
            'new_url' => $new_url,
        ];
    }

    /**
     * Update all content references from old URL to new URL
     */
    private function update_content_references($old_url, $new_url) {
        global $wpdb;

        // Parse URL to get path for matching variations
        $old_path = parse_url($old_url, PHP_URL_PATH);

        // Variations to search for (with and without domain, http/https, etc.)
        $search_patterns = [
            $old_url,
            str_replace('https://', 'http://', $old_url),
            $old_path, // Just the path
        ];

        foreach ($search_patterns as $search) {
            // Update post content
            $wpdb->query($wpdb->prepare(
                "UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, %s, %s) WHERE post_content LIKE %s",
                $search,
                $new_url,
                '%' . $wpdb->esc_like($search) . '%'
            ));

            // Update post meta
            $wpdb->query($wpdb->prepare(
                "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_value LIKE %s",
                $search,
                $new_url,
                '%' . $wpdb->esc_like($search) . '%'
            ));

            // Update options (widgets, theme mods, etc.)
            $wpdb->query($wpdb->prepare(
                "UPDATE {$wpdb->options} SET option_value = REPLACE(option_value, %s, %s) WHERE option_value LIKE %s",
                $search,
                $new_url,
                '%' . $wpdb->esc_like($search) . '%'
            ));
        }
    }

    /**
     * Get migration statistics
     */
    public function get_stats() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'bw_protected_files';

        $stats = [
            'migrated_count' => 0,
            'migrated_size' => 0,
            'protected_dir' => $this->get_protected_dir(),
            'is_outside_webroot' => $this->is_outside_webroot(),
        ];

        $result = $wpdb->get_row("SELECT COUNT(*) as count, SUM(file_size) as size FROM $table_name");

        if ($result) {
            $stats['migrated_count'] = (int) $result->count;
            $stats['migrated_size'] = (int) $result->size;
        }

        return $stats;
    }

    /**
     * Get list of protected files
     */
    public function get_protected_files($limit = 100, $offset = 0) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'bw_protected_files';

        return $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM $table_name ORDER BY created_at DESC LIMIT %d OFFSET %d",
            $limit,
            $offset
        ));
    }

    /**
     * Render admin page
     */
    public function render_admin_page() {
        $stats = $this->get_stats();
        $protected_files = $this->get_protected_files(50);

        ?>
        <div class="wrap bw-lock-files-admin">
            <h1>BW Lock Files</h1>

            <div class="bw-status-cards">
                <div class="bw-card">
                    <h3>Storage Location</h3>
                    <p class="bw-stat"><?php echo $stats['is_outside_webroot'] ? '✅ Outside Webroot' : '⚠️ Inside wp-content'; ?></p>
                    <p class="bw-detail"><code><?php echo esc_html($stats['protected_dir']); ?></code></p>
                    <?php if (!$stats['is_outside_webroot']): ?>
                        <p class="bw-warning">Files are stored with randomized names for security since they're inside the webroot.</p>
                    <?php endif; ?>
                </div>

                <div class="bw-card">
                    <h3>Protected Files</h3>
                    <p class="bw-stat"><?php echo number_format($stats['migrated_count']); ?></p>
                    <p class="bw-detail"><?php echo size_format($stats['migrated_size']); ?> total</p>
                </div>
            </div>

            <div class="bw-section">
                <h2>Migration Tool</h2>
                <p>Scan your uploads folder for files that should be protected, then migrate them to secure storage.</p>

                <div class="bw-migration-controls">
                    <button type="button" id="bw-scan-files" class="button button-secondary">
                        Scan Uploads Folder
                    </button>
                    <button type="button" id="bw-migrate-files" class="button button-primary" disabled>
                        Migrate Files
                    </button>
                </div>

                <div id="bw-scan-results" style="display: none;">
                    <h3>Scan Results</h3>
                    <p><strong id="bw-file-count">0</strong> files to migrate (<strong id="bw-file-size">0 MB</strong>)</p>
                    <div id="bw-file-list" class="bw-file-list"></div>
                </div>

                <div id="bw-migration-progress" style="display: none;">
                    <h3>Migration Progress</h3>
                    <div class="bw-progress-bar">
                        <div class="bw-progress-fill" style="width: 0%"></div>
                    </div>
                    <p id="bw-progress-text">Migrating: <span>0</span> / <span>0</span></p>
                    <div id="bw-migration-log" class="bw-log"></div>
                </div>
            </div>

            <?php if (!empty($protected_files)): ?>
            <div class="bw-section">
                <h2>Protected Files</h2>
                <table class="wp-list-table widefat fixed striped">
                    <thead>
                        <tr>
                            <th>File</th>
                            <th>Type</th>
                            <th>Size</th>
                            <th>Protected URL</th>
                            <th>Date</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($protected_files as $file): ?>
                        <tr>
                            <td><?php echo esc_html($file->file_name); ?></td>
                            <td><?php echo esc_html($file->file_type); ?></td>
                            <td><?php echo size_format($file->file_size); ?></td>
                            <td>
                                <a href="<?php echo esc_url(home_url('/bw-file/' . $file->id . '/' . urlencode($file->file_name))); ?>" target="_blank">
                                    View
                                </a>
                            </td>
                            <td><?php echo esc_html($file->created_at); ?></td>
                        </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
            <?php endif; ?>
        </div>
        <?php
    }
}

// Initialize plugin
BW_Lock_Files::get_instance();
