<?php
/**
 * Name: IP Whitelist/Blacklist
 * Description: Control site access by IP address through whitelist or blacklist rules to restrict or allow specific visitors and protect from malicious traffic
 * Category: Security
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Block Malicious IPs",
 *     "description": "Blacklist specific IP addresses or CIDR ranges (e.g., 192.168.1.0/24) that are attacking your login page or sending spam."
 *   },
 *   {
 *     "title": "Office-Only Access",
 *     "description": "Whitelist your company's static IP addresses to restrict admin access only from office network for maximum security."
 *   },
 *   {
 *     "title": "Geographic Blocking",
 *     "description": "Block entire IP ranges from countries where you don't do business to reduce spam and automated attack traffic."
 *   }
 * ]
 * Added: 2025-07-14
 * Beta: true
 * Tags: ip, whitelist, blacklist, access-control, security
 * WhereToFind: {
 *   "location": "Frontend (Test with IP)",
 *   "url": "/",
 *   "instructions": "Access site from blacklisted IP - should be blocked",
 *   "newTab": true
 * }
 */

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

class SwitchboardIpWhitelistBlacklist {

    public function __construct() {
        // Run IP check immediately - don't wait for init hook
        // This ensures it runs before any page output
        $this->check_ip_access();
    }

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

    public function check_ip_access() {
        // Skip for logged-in admins
        if (current_user_can('manage_options')) {
            return;
        }

        $settings = get_option('switchboard_ip-whitelist-blacklist_settings', []);
        $mode = isset($settings['mode']) ? $settings['mode'] : 'blacklist';
        $ips = isset($settings['ips']) ? $settings['ips'] : '';

        if (empty($ips) || !is_string($ips)) {
            return;
        }

        // Handle both \n and \r\n line endings
        $ip_list = array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $ips)));
        $current_ip = $this->get_client_ip();

        if ($mode === 'whitelist') {
            // Whitelist mode: only allow listed IPs or IPs in ranges
            if (!$this->ip_in_list($current_ip, $ip_list)) {
                wp_die(
                    esc_html__('Access denied. Your IP address is not whitelisted.', 'wpswitchboard'),
                    esc_html__('Access Denied', 'wpswitchboard'),
                    ['response' => 403]
                );
            }
        } else {
            // Blacklist mode: block listed IPs or IPs in ranges
            if ($this->ip_in_list($current_ip, $ip_list)) {
                wp_die(
                    esc_html__('Access denied. Your IP address has been blocked.', 'wpswitchboard'),
                    esc_html__('Access Denied', 'wpswitchboard'),
                    ['response' => 403]
                );
            }
        }
    }

    private function ip_in_list($ip, $list) {
        foreach ($list as $entry) {
            // Check if entry is a CIDR range (contains /)
            if (strpos($entry, '/') !== false) {
                if ($this->ip_in_cidr($ip, $entry)) {
                    return true;
                }
            } else {
                // Exact IP match
                if ($ip === $entry) {
                    return true;
                }
            }
        }
        return false;
    }

    private function ip_in_cidr($ip, $cidr) {
        // Parse CIDR notation (e.g., 192.168.1.0/24)
        list($subnet, $mask) = explode('/', $cidr);

        // Validate subnet IP
        if (filter_var($subnet, FILTER_VALIDATE_IP) === false) {
            return false;
        }

        // Convert IPs to long integers
        $ip_long = ip2long($ip);
        $subnet_long = ip2long($subnet);

        // Handle invalid conversions
        if ($ip_long === false || $subnet_long === false) {
            return false;
        }

        // Calculate network mask
        $mask = (int)$mask;
        if ($mask < 0 || $mask > 32) {
            return false;
        }

        $mask_long = -1 << (32 - $mask);

        // Check if IP is in subnet
        return ($ip_long & $mask_long) === ($subnet_long & $mask_long);
    }

    /**
     * Render settings panel for IP Whitelist/Blacklist
     * 
     * Displays comprehensive IP access control interface with:
     * - Current admin and IP address information card
     * - "Add My IP" button with dynamic state management
     * - Whitelist/Blacklist mode toggle with tab interface
     * - Contextual warning messages for each mode
     * - Textarea for entering IP addresses and CIDR ranges (e.g., 192.168.1.0/24)
     * - Extensive inline CSS for modern Shadcn-style UI
     * - Inline JavaScript for mode switching and add current IP functionality
     * 
     * This module provides site-wide IP-based access control by checking visitor IPs
     * against a list in two modes: whitelist (only listed IPs allowed) or blacklist
     * (listed IPs blocked). Administrators with manage_options capability always have
     * access regardless of IP rules.
     * 
     * The module runs IP checks immediately in constructor (before init hook) to ensure
     * it executes before any page output. It supports both individual IPs and CIDR notation
     * for IP ranges. The check_ip_access() method uses wp_die() with 403 status to block
     * unauthorized access.
     * 
     * Security note: Uses REMOTE_ADDR exclusively (not X-Forwarded-For or similar headers)
     * as those can be easily spoofed and should NOT be trusted for access control.
     * 
     * UI Features:
     * - Detects localhost IPs and displays warning
     * - Shows whether current IP is already in list
     * - Provides visual feedback when adding current IP
     * - Dynamic description updates when switching modes
     * - Inline examples for IP and CIDR notation
     * 
     * @param array $settings Current module settings with 'mode' and 'ips' keys
     * @return void Outputs HTML directly with extensive CSS and JavaScript
     */
    public function render_settings($settings = []) {
        $mode = isset($settings['mode']) ? $settings['mode'] : 'blacklist';
        $ips = isset($settings['ips']) ? $settings['ips'] : '';
        $current_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
        $current_user = wp_get_current_user();
        $is_whitelisted = strpos($ips, $current_ip) !== false;
        
        // Display localhost IP in a user-friendly way
        $display_ip = $current_ip;
        $ip_note = '';
        if ($current_ip === '::1' || $current_ip === '127.0.0.1') {
            $ip_note = ' <span class="sb-sp-text-danger">(localhost - not a real IP)</span>';
        }
        ?>
        <input type="hidden" name="module" value="ip-whitelist-blacklist">
        <input type="hidden" name="setting_mode" id="setting_mode" value="<?php echo esc_attr($mode); ?>" data-sb-ip-mode>

        <div class="sb-sp-info-box sb-sp-info-box-info" style="margin-bottom: 24px;">
            <h4 class="sb-sp-heading-sm">Current Information</h4>
            <div style="margin-bottom: 12px;">
                <strong style="display: inline-block; min-width: 120px;"><?php esc_html_e('Current Admin:', 'wpswitchboard'); ?></strong>
                <span><?php echo esc_html($current_user->display_name . ' (' . $current_user->user_login . ')'); ?></span>
            </div>
            <div style="margin-bottom: 12px;">
                <strong style="display: inline-block; min-width: 120px;"><?php esc_html_e('Your IP Address:', 'wpswitchboard'); ?></strong>
                <code style="background: #f3f4f6; padding: 2px 6px; border-radius: 3px; font-family: 'Monaco', 'Menlo', monospace; font-size: 13px;"><?php echo esc_html($display_ip); ?></code><?php echo $ip_note; ?>
            </div>
            <?php if ($is_whitelisted): ?>
                <div style="display: inline-flex; align-items: center; gap: 6px; background: #f0fdf4; border: 1px solid #86efac; color: #166534; padding: 6px 12px; border-radius: 6px; font-size: 13px; font-weight: 500;">
                    <span class="dashicons dashicons-yes" style="color: var(--primary-500); font-size: 16px; width: 16px; height: 16px;"></span>
                    <?php esc_html_e('Your IP is in the list', 'wpswitchboard'); ?>
                </div>
            <?php else: ?>
                <button type="button" class="button button-small" data-sb-add-ip data-ip="<?php echo esc_attr($current_ip); ?>"><?php esc_html_e('Add My IP to List', 'wpswitchboard'); ?></button>
            <?php endif; ?>
        </div>

        <div class="sb-sp-group" style="margin-bottom: 24px;">
            <label class="sb-sp-label"><?php esc_html_e('Access Control Mode', 'wpswitchboard'); ?></label>
            <div class="sb-sp-button-group" style="margin-bottom: 16px;">
                <button type="button" class="button <?php echo ($mode === 'blacklist' ? 'button-primary' : 'button-secondary'); ?>" data-sb-mode="blacklist">
                    <?php esc_html_e('Blacklist', 'wpswitchboard'); ?>
                </button>
                <button type="button" class="button <?php echo ($mode === 'whitelist' ? 'button-primary' : 'button-secondary'); ?>" data-sb-mode="whitelist">
                    <?php esc_html_e('Whitelist', 'wpswitchboard'); ?>
                </button>
            </div>
        </div>

        <div class="sb-sp-info-box <?php echo ($mode === 'whitelist' ? 'sb-sp-info-box-tip' : 'sb-sp-info-box-info'); ?>" style="margin-bottom: 24px;" data-sb-mode-description>
            <?php if ($mode === 'whitelist'): ?>
                <h4 class="sb-sp-heading-sm"><?php esc_html_e('Whitelist Mode', 'wpswitchboard'); ?></h4>
                <p class="sb-sp-description" style="margin: 0;"><?php esc_html_e('Only IP addresses in the list below can access your site. Everyone else will be blocked. Use with caution!', 'wpswitchboard'); ?></p>
            <?php else: ?>
                <h4 class="sb-sp-heading-sm"><?php esc_html_e('Blacklist Mode', 'wpswitchboard'); ?></h4>
                <p class="sb-sp-description" style="margin: 0;"><?php esc_html_e('IP addresses in the list below will be blocked from accessing your site. Everyone else can access normally.', 'wpswitchboard'); ?></p>
            <?php endif; ?>
        </div>

        <div class="sb-sp-group" style="margin-bottom: 24px;">
            <label for="setting_ips" class="sb-sp-label"><?php esc_html_e('IP Addresses & Ranges', 'wpswitchboard'); ?></label>
            <textarea name="setting_ips" id="setting_ips" rows="8" class="sb-sp-input" style="width: 100%; font-family: 'Monaco', 'Menlo', 'Courier New', monospace; font-size: 13px; line-height: 1.6;"><?php echo esc_textarea($ips); ?></textarea>
            <p class="sb-sp-description"><?php esc_html_e('Enter one IP address or CIDR range per line.', 'wpswitchboard'); ?></p>
            <p class="sb-sp-description"><?php esc_html_e('Examples: 192.168.1.100 (single IP) or 192.168.1.0/24 (entire subnet)', 'wpswitchboard'); ?></p>
        </div>

        <div class="sb-sp-info-box sb-sp-info-box-tip">
            <h4 class="sb-sp-heading-sm"><?php esc_html_e('Important Note', 'wpswitchboard'); ?></h4>
            <p class="sb-sp-description" style="margin: 0;"><?php esc_html_e('Administrators always have access regardless of IP restrictions.', 'wpswitchboard'); ?></p>
        </div>

        <script>
        (function() {
            var i18n = <?php echo wp_json_encode([
                'whitelistMode' => esc_html__('Whitelist Mode:', 'wpswitchboard'),
                'whitelistDesc' => esc_html__('Only IP addresses in the list below can access your site. Everyone else will be blocked. Use with caution!', 'wpswitchboard'),
                'blacklistMode' => esc_html__('Blacklist Mode:', 'wpswitchboard'),
                'blacklistDesc' => esc_html__('IP addresses in the list below will be blocked from accessing your site. Everyone else can access normally.', 'wpswitchboard'),
                'ipAdded' => esc_html__('IP Added', 'wpswitchboard'),
            ]); ?>;

            document.querySelectorAll('[data-sb-mode]').forEach(function(btn) {
                btn.addEventListener('click', function(e) {
                    e.preventDefault();
                    var mode = this.dataset.sbMode;
                    document.querySelector('[data-sb-ip-mode]').value = mode;
                    document.querySelectorAll('[data-sb-mode]').forEach(function(b) {
                        b.classList.remove('button-primary');
                        b.classList.add('button-secondary');
                    });
                    this.classList.remove('button-secondary');
                    this.classList.add('button-primary');
                    var desc = document.querySelector('[data-sb-mode-description]');
                    if (mode === 'whitelist') {
                        desc.innerHTML = '<h4 class="sb-sp-heading-sm">' + i18n.whitelistMode + '</h4><p class="sb-sp-description" style="margin: 0;">' + i18n.whitelistDesc + '</p>';
                        desc.className = 'sb-sp-info-box sb-sp-info-box-tip';
                    } else {
                        desc.innerHTML = '<h4 class="sb-sp-heading-sm">' + i18n.blacklistMode + '</h4><p class="sb-sp-description" style="margin: 0;">' + i18n.blacklistDesc + '</p>';
                        desc.className = 'sb-sp-info-box sb-sp-info-box-info';
                    }
                });
            });

            var addBtn = document.querySelector('[data-sb-add-ip]');
            if (addBtn) {
                addBtn.addEventListener('click', function(e) {
                    e.preventDefault();
                    var textarea = document.getElementById('setting_ips');
                    var ip = this.dataset.ip;
                    textarea.value = textarea.value.trim() ? textarea.value.trim() + '\n' + ip : ip;
                    this.innerHTML = i18n.ipAdded;
                    this.disabled = true;
                    this.style.background = '#f0fdf4';
                    this.style.borderColor = '#86efac';
                    this.style.color = '#166534';
                });
            }
        })();
        </script>
        <?php
    }

    private function get_client_ip() {
        // For security restrictions, ONLY trust REMOTE_ADDR
        // Other headers can be easily spoofed and should NOT be used for access control
        $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';

        // Validate the IP
        if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
            return '0.0.0.0';
        }

        return $ip;
    }
}

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