<?php
/**
 * Name: Track 404s
 * Description: Track 404 error pages with requested URLs, timestamps, hit counts, referrer information, and user agents to identify broken links and fix dead pages
 * Category: Utilities
 * Icon: 404
 * Tags: 404, tracking, errors, seo, analytics, monitoring
 * Added: 2025-01-15
 * UseCases: [
 *   {
 *     "title": "Find & Fix Broken Links",
 *     "description": "Monitor which pages are returning 404 errors and fix them by creating redirects or updating links, improving user experience and SEO."
 *   },
 *   {
 *     "title": "Identify External Link Issues",
 *     "description": "See where 404 errors are coming from (referrers) to identify which external sites are linking to broken URLs on your site."
 *   },
 *   {
 *     "title": "Clean Up Old Content",
 *     "description": "Discover old URLs that are still being accessed but no longer exist, helping you decide whether to restore content or redirect to alternatives."
 *   }
 * ]
 * WhereToFind: {
 *   "location": "Switchboard → Track 404s",
 *   "url": "admin.php?page=track-404s",
 *   "instructions": "View 404 logs with URLs, hit counts, and referrers",
 *   "newTab": false
 * }
 *
 * @package Switchboard
 * @since 1.0.0
 */

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

class SwitchboardTrack404s {

    private $table_name;
    private $option_name = 'switchboard_track-404s';
    private $settings;
    private $tracked_in_this_request = false; // Prevent double-tracking

    public function __construct() {
        global $wpdb;
        $this->table_name = $wpdb->prefix . 'switchboard_404_log';
        $this->settings = $this->get_settings();

        // Setup database table (checked on admin_init)
        add_action('admin_init', [$this, 'maybe_create_table']);

        // Track 404s
        add_action('template_redirect', [$this, 'track_404']);

        // Admin menu
        add_action('admin_menu', [$this, 'add_admin_menu']);

        // AJAX handlers
        add_action('wp_ajax_track_404s_get_logs', [$this, 'ajax_get_logs']);
        add_action('wp_ajax_track_404s_get_stats', [$this, 'ajax_get_stats']);
        add_action('wp_ajax_track_404s_export', [$this, 'ajax_export']);
        add_action('wp_ajax_track_404s_delete', [$this, 'ajax_delete']);
        add_action('wp_ajax_track_404s_delete_all', [$this, 'ajax_delete_all']);

        // Setup cron for cleanup
        add_action('switchboard_track_404s_cleanup', [$this, 'auto_cleanup']);

        // Enqueue admin scripts
        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);

        // Settings save handler
        add_action('switchboard_save_module_settings', [$this, 'save_settings'], 10, 2);
    }

    /**
     * Get data for email report
     *
     * @param array $date_range ['from' => 'Y-m-d', 'to' => 'Y-m-d']
     * @return array Report data
     */
    public function get_report_data($date_range) {
        global $wpdb;

        $from = $date_range['from'];
        $to = $date_range['to'];

        // Total 404s
        $total_404s = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE DATE(created_at) BETWEEN %s AND %s",
            $from,
            $to
        ));

        // Unique URLs
        $unique_urls = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT url) FROM {$this->table_name} WHERE DATE(created_at) BETWEEN %s AND %s",
            $from,
            $to
        ));

        // Top 404 URLs
        $top_urls = $wpdb->get_results($wpdb->prepare(
            "SELECT url, COUNT(*) as hits
             FROM {$this->table_name}
             WHERE DATE(created_at) BETWEEN %s AND %s
             GROUP BY url
             ORDER BY hits DESC
             LIMIT 5",
            $from,
            $to
        ), ARRAY_A);

        return [
            'total_404s'   => intval($total_404s),
            'unique_urls'  => intval($unique_urls),
            'top_404_urls' => $top_urls,
        ];
    }

    /**
     * Called when module is activated
     * Schedules cron events
     */
    public function activate() {
        if (!wp_next_scheduled('switchboard_track_404s_cleanup')) {
            wp_schedule_event(time(), 'daily', 'switchboard_track_404s_cleanup');
        }
    }

    /**
     * Called when module is deactivated
     * Clears all scheduled cron events
     */
    public function deactivate() {
        wp_clear_scheduled_hook('switchboard_track_404s_cleanup');
    }

    /**
     * Get settings with defaults
     */
    private function get_settings() {
        $defaults = [
            'retention_days' => 30,
            'exclude_bots' => true,
            'ignore_patterns' => '.env,.git,wp-config,xmlrpc,wp-admin.php,.sql,.well-known,favicon.ico,apple-touch-icon,wp-login.php,administrator,phpmyadmin,pma,mysql,adminer,.php,robots.txt,sitemap.xml',
            'track_referrer' => true,
        ];

        $options = get_option($this->option_name, []);
        return wp_parse_args($options, $defaults);
    }

    /**
     * Save settings from Switchboard settings page
     */
    public function save_settings($module, $settings) {
        if ($module !== 'track-404s') {
            return;
        }

        $saved_settings = [
            'retention_days' => isset($settings['retention_days']) ? intval($settings['retention_days']) : 30,
            'exclude_bots' => isset($settings['exclude_bots']) ? true : false,
            'ignore_patterns' => isset($settings['ignore_patterns']) ? sanitize_textarea_field($settings['ignore_patterns']) : '',
            'track_referrer' => isset($settings['track_referrer']) ? true : false,
        ];

        update_option($this->option_name, $saved_settings);
        $this->settings = $saved_settings;
    }

    /**
     * Create database table
     */
    public function create_table() {
        global $wpdb;

        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            url VARCHAR(500) NOT NULL,
            url_hash CHAR(64) NOT NULL,
            hit_count INT UNSIGNED DEFAULT 1,
            first_seen DATETIME NOT NULL,
            last_seen DATETIME NOT NULL,
            referrer VARCHAR(500) DEFAULT NULL,
            user_agent VARCHAR(255) DEFAULT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY url_hash (url_hash),
            KEY last_seen (last_seen),
            KEY hit_count (hit_count)
        ) ENGINE=InnoDB $charset_collate;";

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

    /**
     * Maybe create table on admin init
     */
    public function maybe_create_table() {
        global $wpdb;
        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $this->table_name)) != $this->table_name) {
            $this->create_table();
        }
    }

    /**
     * Track 404 errors
     */
    public function track_404() {
        // Prevent double-tracking in the same request
        if ($this->tracked_in_this_request) {
            return;
        }

        if (!is_404()) {
            return;
        }

        // SECURITY FIX: Sanitize REQUEST_URI
        $url = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';

        if (empty($url)) {
            return;
        }

        // Check if URL should be ignored
        if ($this->should_ignore_url($url)) {
            return;
        }

        // Bot filter
        if ($this->settings['exclude_bots'] && $this->is_bot()) {
            return;
        }

        // Mark as tracked for this request
        $this->tracked_in_this_request = true;

        // SECURITY FIX: Sanitize referrer
        $referrer = null;
        if ($this->settings['track_referrer'] && !empty($_SERVER['HTTP_REFERER'])) {
            $referrer = esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER']));
        }

        // SECURITY FIX: Sanitize user agent
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(substr(wp_unslash($_SERVER['HTTP_USER_AGENT']), 0, 255)) : null;

        // SECURITY FIX: Sanitize IP address
        $ip_address = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';

        // Rate limiting: Check if this IP has tracked this URL in the last 60 seconds
        $rate_limit_key = 'track_404_' . md5($url . $ip_address);
        $last_tracked = get_transient($rate_limit_key);

        if ($last_tracked) {
            return; // Skip tracking - same IP hit the same URL too recently
        }

        // Set rate limit transient (60 second cooldown)
        set_transient($rate_limit_key, time(), 60);

        // Hash URL for unique constraint
        $url_hash = hash('sha256', $url);

        // Insert or update
        global $wpdb;

        // Check if exists
        $existing = $wpdb->get_row($wpdb->prepare(
            "SELECT id, hit_count FROM {$this->table_name} WHERE url_hash = %s",
            $url_hash
        ));

        $now = current_time('mysql', true);

        if ($existing) {
            // Update existing
            $wpdb->update(
                $this->table_name,
                [
                    'hit_count' => $existing->hit_count + 1,
                    'last_seen' => $now,
                    'referrer' => $referrer, // Update to latest referrer
                    'user_agent' => $user_agent,
                ],
                ['id' => $existing->id],
                ['%d', '%s', '%s', '%s'],
                ['%d']
            );
        } else {
            // Insert new

            $wpdb->insert(
                $this->table_name,
                [
                    'url' => $url,
                    'url_hash' => $url_hash,
                    'hit_count' => 1,
                    'first_seen' => $now,
                    'last_seen' => $now,
                    'referrer' => $referrer,
                    'user_agent' => $user_agent,
                ],
                ['%s', '%s', '%d', '%s', '%s', '%s', '%s']
            );
        }
    }

    /**
     * Check if URL should be ignored
     */
    private function should_ignore_url($url) {
        $patterns = $this->settings['ignore_patterns'];

        if (empty($patterns)) {
            return false;
        }

        // Split by comma
        $patterns_array = array_map('trim', explode(',', $patterns));

        foreach ($patterns_array as $pattern) {
            if (empty($pattern)) {
                continue;
            }

            // Simple string matching
            if (stripos($url, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if current request is from a bot
     */
    private function is_bot() {
        $ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

        if (empty($ua)) {
            return true;
        }

        // Common bot patterns
        $bot_patterns = [
            'bot', 'crawler', 'spider', 'crawling', 'slurp', 'mediapartners',
            'adsbot', 'bingbot', 'googlebot', 'bingpreview', 'msnbot', 'duckduckbot',
            'teoma', 'yandex', 'baidu', 'facebookexternalhit', 'facebot',
            'ia_archiver', 'wget', 'curl', 'python-requests', 'http_request',
            'Go-http-client', 'Headless', 'PhantomJS', 'Scrapy'
        ];

        foreach ($bot_patterns as $pattern) {
            if (stripos($ua, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Add admin menu
     */
    public function add_admin_menu() {
        add_submenu_page(
            'switchboard',
            'Track 404s',
            'Track 404s',
            'manage_options',
            'track-404s',
            [$this, 'render_admin_page']
        );
    }

    /**
     * Render admin page
     */
    public function render_admin_page() {
        $dashboard_file = plugin_dir_path(__FILE__) . '../../assets/track-404s/admin.php';
        if (file_exists($dashboard_file)) {
            include $dashboard_file;
        }
    }

    /**
     * Enqueue admin assets
     */
    public function enqueue_admin_assets($hook) {
        // Check if we're on the track-404s page (check both hook and GET parameter)
        $is_track_404s_page = (
            (isset($_GET['page']) && $_GET['page'] === 'track-404s') ||
            strpos($hook, 'track-404s') !== false
        );

        if (!$is_track_404s_page) {
            return;
        }

        // Enqueue the main Switchboard admin CSS (contains all sb-mp-* and sb-sp-* classes)
        if (defined('SWITCHBOARD_URL') && defined('SWITCHBOARD_VERSION')) {
            wp_enqueue_style(
                'switchboard-admin',
                SWITCHBOARD_URL . 'switchboard-admin.css',
                [],
                SWITCHBOARD_VERSION
            );
        }

        // Ensure jQuery is loaded
        wp_enqueue_script('jquery');

        wp_enqueue_script(
            'track-404s-admin',
            plugins_url('../../assets/track-404s/admin.js', __FILE__),
            ['jquery'],
            '1.0.0',
            true
        );

        wp_localize_script('track-404s-admin', 'track404sAdmin', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('track_404s_admin'),
            'siteUrl' => home_url()
        ]);
    }

    /**
     * AJAX: Get logs
     */
    public function ajax_get_logs() {
        check_ajax_referer('track_404s_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(esc_html__('Unauthorized', 'wpswitchboard'));
        }

        global $wpdb;

        $orderby = isset($_POST['orderby']) ? sanitize_text_field($_POST['orderby']) : 'last_seen';
        $order = isset($_POST['order']) ? sanitize_text_field($_POST['order']) : 'DESC';
        $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
        $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
        $limit = 50;

        // SECURITY FIX: Validate orderby (whitelist to prevent SQL injection)
        $allowed_orderby = ['url', 'hit_count', 'first_seen', 'last_seen'];
        if (!in_array($orderby, $allowed_orderby, true)) {
            $orderby = 'last_seen';
        }

        // SECURITY FIX: Validate order direction (strict validation)
        $order = strtoupper($order) === 'ASC' ? 'ASC' : 'DESC';

        $filter = isset($_POST['filter']) ? sanitize_text_field($_POST['filter']) : 'pages_only';

        // Build query
        $conditions = [];

        if (!empty($search)) {
            $conditions[] = $wpdb->prepare("(url LIKE %s OR referrer LIKE %s)", '%' . $wpdb->esc_like($search) . '%', '%' . $wpdb->esc_like($search) . '%');
        }

        // Filter out file/asset URLs by default
        if ($filter === 'pages_only') {
            $conditions[] = "url NOT REGEXP '\\\\.(css|js|map|jpg|jpeg|png|gif|svg|webp|avif|ico|woff2?|ttf|eot|pdf|zip|xml|json|txt|php)(\\\\?|$)'";
        }

        $where = !empty($conditions) ? ' WHERE ' . implode(' AND ', $conditions) : '';

        // SECURITY FIX: Safe because $orderby and $order are whitelisted above
        $query = "SELECT * FROM {$this->table_name} {$where} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d";
        $logs = $wpdb->get_results($wpdb->prepare($query, $limit, $offset), ARRAY_A);

        // Get total count
        $total = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name} {$where}");

        wp_send_json_success([
            'logs' => $logs,
            'total' => intval($total),
            'offset' => $offset,
            'limit' => $limit
        ]);
    }

    /**
     * AJAX: Get stats
     */
    public function ajax_get_stats() {
        check_ajax_referer('track_404s_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(esc_html__('Unauthorized', 'wpswitchboard'));
        }

        global $wpdb;

        $filter = isset($_POST['filter']) ? sanitize_text_field($_POST['filter']) : 'pages_only';
        $file_filter = '';
        if ($filter === 'pages_only') {
            $file_filter = " AND url NOT REGEXP '\\\\.(css|js|map|jpg|jpeg|png|gif|svg|webp|avif|ico|woff2?|ttf|eot|pdf|zip|xml|json|txt|php)(\\\\?|$)'";
        }

        // Total 404 hits
        $total_hits = $wpdb->get_var("SELECT SUM(hit_count) FROM {$this->table_name} WHERE 1=1{$file_filter}");

        // Unique URLs
        $unique_urls = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name} WHERE 1=1{$file_filter}");

        // Top 5 404s
        $top_404s = $wpdb->get_results(
            "SELECT url, hit_count, last_seen FROM {$this->table_name} WHERE 1=1{$file_filter} ORDER BY hit_count DESC LIMIT 5",
            ARRAY_A
        );

        // Recent 404s (last 5)
        $recent_404s = $wpdb->get_results(
            "SELECT url, hit_count, last_seen FROM {$this->table_name} WHERE 1=1{$file_filter} ORDER BY last_seen DESC LIMIT 5",
            ARRAY_A
        );

        // New 404s today
        $new_today = $wpdb->get_var(
            "SELECT COUNT(*) FROM {$this->table_name} WHERE DATE(first_seen) = CURDATE(){$file_filter}"
        );

        wp_send_json_success([
            'total_hits' => intval($total_hits),
            'unique_urls' => intval($unique_urls),
            'new_today' => intval($new_today),
            'top_404s' => $top_404s,
            'recent_404s' => $recent_404s
        ]);
    }

    /**
     * AJAX: Export CSV
     */
    public function ajax_export() {
        check_ajax_referer('track_404s_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Unauthorized', 'wpswitchboard'));
        }

        global $wpdb;

        $logs = $wpdb->get_results("SELECT url, hit_count, first_seen, last_seen, referrer FROM {$this->table_name} ORDER BY hit_count DESC", ARRAY_A);

        // Output CSV
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename="404-logs-' . date('Y-m-d') . '.csv"');
        header('Pragma: no-cache');
        header('Expires: 0');

        $output = fopen('php://output', 'w');
        fputcsv($output, ['URL', 'Hit Count', 'First Seen', 'Last Seen', 'Referrer']);

        foreach ($logs as $log) {
            // SECURITY FIX: Prevent CSV injection by prefixing potentially dangerous characters
            $safe_log = array_map(function($value) {
                if ($value === null) {
                    return '';
                }
                $value = (string) $value;
                // If value starts with =, +, -, @, tab, or carriage return, prefix with single quote
                if (preg_match('/^[=+\-@\t\r]/', $value)) {
                    return "'" . $value;
                }
                return $value;
            }, $log);

            fputcsv($output, $safe_log);
        }

        fclose($output);
        exit;
    }

    /**
     * AJAX: Delete single entry
     */
    public function ajax_delete() {
        check_ajax_referer('track_404s_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(esc_html__('Unauthorized', 'wpswitchboard'));
        }

        $id = isset($_POST['id']) ? intval($_POST['id']) : 0;

        if (!$id) {
            wp_send_json_error(esc_html__('Invalid ID', 'wpswitchboard'));
        }

        global $wpdb;
        $deleted = $wpdb->delete($this->table_name, ['id' => $id], ['%d']);

        if ($deleted) {
            wp_send_json_success();
        } else {
            wp_send_json_error(esc_html__('Failed to delete', 'wpswitchboard'));
        }
    }

    /**
     * AJAX: Delete all entries
     */
    public function ajax_delete_all() {
        check_ajax_referer('track_404s_admin', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(esc_html__('Unauthorized', 'wpswitchboard'));
        }

        global $wpdb;
        $wpdb->query("TRUNCATE TABLE {$this->table_name}");

        wp_send_json_success();
    }

    /**
     * Auto cleanup based on retention setting
     */
    public function auto_cleanup() {
        $retention_days = $this->settings['retention_days'];
        if ($retention_days <= 0) {
            return;
        }

        global $wpdb;
        $wpdb->query($wpdb->prepare(
            "DELETE FROM {$this->table_name} WHERE last_seen < DATE_SUB(NOW(), INTERVAL %d DAY)",
            $retention_days
        ));
    }

    /**
     * Render settings panel
     *
     * @param array $settings Current module settings
     * @return void Outputs HTML directly
     */
    public function render_settings($settings = []) {
        $retention_days = isset($settings['retention_days']) ? intval($settings['retention_days']) : 30;
        $exclude_bots = isset($settings['exclude_bots']) ? $settings['exclude_bots'] : true;
        $ignore_patterns = isset($settings['ignore_patterns']) ? $settings['ignore_patterns'] : '.env,.git,wp-config,xmlrpc,wp-admin.php,.sql,.well-known,favicon.ico,apple-touch-icon,wp-login.php,administrator,phpmyadmin,pma,mysql,adminer,.php,robots.txt,sitemap.xml';
        $track_referrer = isset($settings['track_referrer']) ? $settings['track_referrer'] : true;

        $html = '
            <input type="hidden" name="module" value="track-404s">

            <div class="sb-sp-alert sb-sp-alert-warning">
                <div class="sb-sp-alert-title">Track 404 Errors</div>
                <div class="sb-sp-text-sm">Monitor broken links and identify SEO issues by tracking 404 errors</div>
            </div>

            <h3 class="sb-sp-heading">Tracking Settings</h3>

            <div class="sb-sp-group">
                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <strong class="sb-module-option-title">Exclude Bots & Crawlers</strong>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="setting_exclude_bots" value="1" ' . checked($exclude_bots, true, false) . '>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                        <p class="sb-module-option-text">Filter out automated bots and scanners (recommended)</p>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <strong class="sb-module-option-title">Track Referrer URLs</strong>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="setting_track_referrer" value="1" ' . checked($track_referrer, true, false) . '>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                        <p class="sb-module-option-text">Record where visitors came from to identify broken internal links</p>
                    </div>
                </div>
            </div>

            <div class="sb-sp-divider"></div>

            <h3 class="sb-sp-heading">Ignore Patterns</h3>
            <p class="sb-sp-description">Enter URL patterns to ignore (comma-separated). Filters out automated browser requests, bot scans, and non-actionable 404s.</p>
            <textarea name="setting_ignore_patterns" rows="4" class="sb-sp-textarea">' . esc_textarea($ignore_patterns) . '</textarea>
            <p class="sb-sp-description">Includes: .well-known paths (browser features), common bot attack paths (wp-login.php, xmlrpc, phpmyadmin), browser auto-requests (favicon.ico, apple-touch-icon)</p>

            <div class="sb-sp-divider"></div>

            <h3 class="sb-sp-heading">Data Retention</h3>

            <div class="sb-sp-group sb-sp-field-horizontal">
                <div class="sb-sp-field-horizontal-left">
                    <label for="setting_retention_days" class="sb-sp-label">Auto-Delete Logs Older Than</label>
                    <p class="sb-sp-description">Set to 0 to keep logs forever. Automatic cleanup runs daily.</p>
                </div>
                <div style="display: flex; align-items: center; gap: 8px;">
                    <input type="number" name="setting_retention_days" id="setting_retention_days" value="' . esc_attr($retention_days) . '" min="0" max="365" class="sb-sp-input" style="width: 100px;">
                    <span class="sb-sp-text">days</span>
                </div>
            </div>

            <div class="sb-sp-tip-box">
                <h4 class="sb-sp-heading-sm">Use Cases</h4>
                <ul class="sb-sp-list sb-sp-list-spaced sb-sp-text-sm">
                    <li><strong>Fix Broken Links:</strong> Identify which URLs are returning 404 errors</li>
                    <li><strong>SEO Optimization:</strong> Find and redirect popular 404 pages</li>
                    <li><strong>Security Monitoring:</strong> Detect scanning attempts and suspicious activity</li>
                    <li><strong>Content Planning:</strong> Discover what content users are looking for</li>
                </ul>
            </div>

            <div class="sb-sp-alert sb-sp-alert-warning">
                <div class="sb-sp-text-semibold">View 404 Logs</div>
                <div class="sb-sp-text-sm">
                    After enabling, visit <strong>Switchboard → Track 404s</strong> to view your logs and export data.
                </div>
            </div>
        ';

        echo $html;

    }

    /**
     * Register data locations for cleanup system
     *
     * @return array Data storage locations
     */
    public function register_data_locations() {
        return [
            'options' => [
                'switchboard_track-404s',
                'switchboard_track-404s_last_run',
            ],
            'option_patterns' => [],
            'tables' => [
                'switchboard_404_log',
            ],
            'post_meta' => [],
            'user_meta' => [],
        ];
    }
}

// Initialize and register with data system
$switchboard_instance = new SwitchboardTrack404s();
Switchboard_Data_Registry::get_instance()->auto_register($switchboard_instance, __FILE__);
