<?php
/**
 * Name: Security Headers
 * Description: Add HTTP security headers to protect against common attacks including XSS, clickjacking, MIME sniffing, and other vulnerabilities for enhanced site security
 * Category: Security
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Prevent XSS Attacks",
 *     "description": "Add X-XSS-Protection and Content-Security-Policy headers to block cross-site scripting attempts and code injection."
 *   },
 *   {
 *     "title": "Block Clickjacking",
 *     "description": "Set X-Frame-Options header to prevent site from being embedded in malicious iframes for phishing attacks."
 *   },
 *   {
 *     "title": "Enforce HTTPS",
 *     "description": "Enable HSTS (HTTP Strict Transport Security) to force browsers to only use HTTPS connections to your site."
 *   }
 * ]
 * Added: 2025-07-03
 * Tags: security, headers, xss, clickjacking, https, csp
 * WhereToFind: {
 *   "location": "Configure in module settings",
 *   "url": "",
 *   "instructions": "Enable security headers - added to all HTTP responses",
 *   "newTab": false
 * }
 */

// Prevent direct access
if (!defined('ABSPATH')) exit;

class SwitchboardSecurityHeaders {

    public function __construct() {
        add_action('send_headers', [$this, 'add_security_headers']);
    }

    /**
     * Register data locations for cleanup system
     *
     * @return array Data storage locations
     */
    public function register_data_locations() {
        return [
            'options' => [
                'switchboard_security-headers_settings',
            ],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [],
            'user_meta' => [],
        ];
    }
    
    public function add_security_headers() {
        $settings = get_option('switchboard_security-headers_settings', []);
        $headers_setting = isset($settings['headers']) ? $settings['headers'] : '';

        // Validate it's a string before exploding
        if (!is_string($headers_setting)) {
            $headers_setting = '';
        }

        $headers = !empty($headers_setting) ? explode(',', $headers_setting) : ['xss', 'frame', 'content-type', 'referrer'];

        // Allowed headers whitelist
        $allowed_headers = ['xss', 'frame', 'content-type', 'referrer', 'hsts', 'csp'];

        foreach ($headers as $header) {
            $header = trim($header);

            // Only process whitelisted headers
            if (!in_array($header, $allowed_headers, true)) {
                continue;
            }

            switch ($header) {
                case 'xss':
                    header('X-XSS-Protection: 1; mode=block');
                    break;
                    
                case 'frame':
                    header('X-Frame-Options: SAMEORIGIN');
                    break;
                    
                case 'content-type':
                    header('X-Content-Type-Options: nosniff');
                    break;
                    
                case 'referrer':
                    header('Referrer-Policy: strict-origin-when-cross-origin');
                    break;
                    
                case 'hsts':
                    header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
                    break;
                    
                case 'csp':
                    // Allow inline scripts/styles and common CDNs for WordPress compatibility
                    $csp = "default-src 'self'; ";
                    $csp .= "script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googleapis.com *.gstatic.com *.googletagmanager.com *.google-analytics.com *.facebook.net *.fbcdn.net cdn.jsdelivr.net cdnjs.cloudflare.com unpkg.com *.userback.io; ";
                    $csp .= "style-src 'self' 'unsafe-inline' *.googleapis.com fonts.googleapis.com rsms.me cdn.jsdelivr.net cdnjs.cloudflare.com unpkg.com; ";
                    $csp .= "img-src 'self' data: blob: *.googleapis.com *.gstatic.com *.gravatar.com *.wp.com *.facebook.com *.fbcdn.net *.google-analytics.com *.googletagmanager.com; ";
                    $csp .= "font-src 'self' data: *.googleapis.com *.gstatic.com fonts.gstatic.com rsms.me cdn.jsdelivr.net cdnjs.cloudflare.com; ";
                    $csp .= "connect-src 'self' *.googleapis.com *.google-analytics.com *.googletagmanager.com *.facebook.com *.userback.io wss://*.userback.io; ";
                    $csp .= "frame-src 'self' *.youtube.com *.youtube-nocookie.com *.vimeo.com *.facebook.com *.twitter.com;";
                    header("Content-Security-Policy: " . $csp);
                    break;
            }
        }
    }

    /**
     * Render settings panel for Security Headers
     * 
     * Displays settings interface for adding HTTP security headers with:
     * - Checkbox for X-XSS-Protection header
     * - Checkbox for X-Frame-Options header
     * - Checkbox for X-Content-Type-Options header
     * - Checkbox for Referrer-Policy header
     * - Checkbox for HSTS (HTTPS only) header
     * - Checkbox for Content-Security-Policy header
     * 
     * This module adds HTTP security headers to all outgoing responses using the
     * WordPress 'send_headers' action. The headers setting is stored as a comma-separated
     * string (e.g., "xss,frame,content-type,referrer") which gets exploded into an array
     * for processing. Each header type corresponds to a specific security protection:
     * XSS protection blocks cross-site scripting, Frame-Options prevents clickjacking,
     * Content-Type-Options stops MIME sniffing, Referrer-Policy controls referrer data,
     * HSTS enforces HTTPS connections, and CSP provides content security controls.
     * 
     * The module validates all header values against a whitelist before applying them
     * to ensure only safe, recognized security headers are sent.
     * 
     * @param array $settings Current module settings with 'headers' key (comma-separated string)
     * @return void Outputs HTML directly
     */
    public function render_settings($settings = []) {
        $headers_setting = isset($settings['headers']) ? $settings['headers'] : '';
        
        // Handle both string (comma-separated) and array formats
        if (is_string($headers_setting) && !empty($headers_setting)) {
            $headers_array = array_map('trim', explode(',', $headers_setting));
        } elseif (is_array($headers_setting)) {
            $headers_array = $headers_setting;
        } else {
            $headers_array = ['xss', 'frame', 'content-type', 'referrer']; // Default headers
        }
        
        $headers_string = implode(',', $headers_array);
        
        $header_options = [
            'xss' => [
                'label' => esc_html__('X-XSS-Protection', 'wpswitchboard'),
                'description' => esc_html__('Protects against cross-site scripting (XSS) attacks', 'wpswitchboard')
            ],
            'frame' => [
                'label' => esc_html__('X-Frame-Options', 'wpswitchboard'),
                'description' => esc_html__('Prevents clickjacking by blocking iframe embedding', 'wpswitchboard')
            ],
            'content-type' => [
                'label' => esc_html__('X-Content-Type-Options', 'wpswitchboard'),
                'description' => esc_html__('Prevents MIME type sniffing attacks', 'wpswitchboard')
            ],
            'referrer' => [
                'label' => esc_html__('Referrer-Policy', 'wpswitchboard'),
                'description' => esc_html__('Controls how much referrer information is sent', 'wpswitchboard')
            ],
            'hsts' => [
                'label' => esc_html__('HSTS (HTTPS only)', 'wpswitchboard'),
                'description' => esc_html__('Forces browsers to use HTTPS connections only', 'wpswitchboard')
            ],
            'csp' => [
                'label' => esc_html__('Content-Security-Policy', 'wpswitchboard'),
                'description' => esc_html__('Controls which resources can be loaded. Allows inline scripts/styles and common CDNs (Google, YouTube, Vimeo).', 'wpswitchboard')
            ]
        ];
        ?>
        <input type="hidden" name="module" value="security-headers">
        
        <div class="sb-sp-group" style="margin-bottom: 24px;">
            <h3 class="sb-sp-heading">Security Headers to Add</h3>
            <p class="sb-sp-description">Select which HTTP security headers to add to all responses. These headers help protect against common web vulnerabilities.</p>
            
            <?php foreach ($header_options as $key => $option): ?>
                <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"><?php echo esc_html($option['label']); ?></strong>
                            <label class="sb-module-toggle">
                                <input type="checkbox"
                                       name="setting_headers[]"
                                       value="<?php echo esc_attr($key); ?>"
                                       <?php echo (in_array($key, $headers_array) ? 'checked' : ''); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                        <p class="sb-module-option-text"><?php echo esc_html($option['description']); ?></p>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>
        
        <div class="sb-sp-info-box sb-sp-info-box-tip">
            <h4 class="sb-sp-heading-sm">Important Notes</h4>
            <ul class="sb-sp-list">
                <li class="sb-sp-list-item"><strong>HSTS:</strong> Only enable if your site is fully HTTPS. Enabling on HTTP sites can cause issues.</li>
                <li class="sb-sp-list-item"><strong>Content-Security-Policy:</strong> Can break sites if not configured properly. Test thoroughly before enabling.</li>
                <li class="sb-sp-list-item">These headers are added to all HTTP responses automatically.</li>
            </ul>
        </div>
        
        <script>
        (function() {
            // Convert headers array to comma-separated string before form submission
            jQuery(document).ready(function($) {
                $(document).on('submit', 'form', function() {
                    var checkboxes = $(this).find('input[name="setting_headers[]"]:checked');
                    var values = checkboxes.map(function() { return $(this).val(); }).get();
                    // Remove the array input and add a single string input
                    $(this).find('input[name="setting_headers[]"]').remove();
                    $(this).append('<input type="hidden" name="setting_headers" value="' + values.join(',') + '">');
                });
            });
        })();
        </script>
        <?php
    }
}

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