<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

class Vulnity_File_Editor_Alert extends Vulnity_Alert_Base {
    
    private $cooldown_duration = 900; // 15 minutos de cooldown
    
    public function __construct() {
        $this->alert_type = 'file_editor_used';
        parent::__construct();
    }
    
    protected function register_hooks() {
        add_action('admin_init', array($this, 'detect_file_editor_access'));
        add_action('admin_init', array($this, 'maybe_monitor_file_edit_request'), 1);
    }

    /**
     * Dispatches monitoring for the core file editor AJAX action using a prefixed callback.
     */
    public function maybe_monitor_file_edit_request() {
        if (!wp_doing_ajax()) {
            return;
        }

        $action = $this->get_sanitized_request_value('action');
        if ($action !== 'edit-theme-plugin-file') {
            return;
        }

        if (!$this->user_can_modify_files()) {
            return;
        }

        $file = $this->get_sanitized_request_value('file');
        $type = $this->detect_editor_request_type();
        if ($type === 'unknown' || empty($file) || !$this->user_can_edit_context($type)) {
            return;
        }

        $nonce = $this->get_editor_nonce();
        $context = $this->build_editor_context($type, $file);
        if (!$this->validate_editor_nonce($nonce, $type, $context)) {
            return;
        }

        $this->monitor_ajax_file_edit($type, $file, $nonce, $context);
    }

    private function user_can_modify_files() {
        return current_user_can('edit_themes') || current_user_can('edit_plugins');
    }

    private function get_sanitized_request_value($key, $default = '') {
        $value = filter_input(INPUT_POST, $key, FILTER_UNSAFE_RAW);

        if (null === $value) {
            $value = filter_input(INPUT_GET, $key, FILTER_UNSAFE_RAW);
        }

        if (null === $value) {
            return $default;
        }

        if (is_array($value)) {
            $value = function_exists('wp_unslash') ? wp_unslash($value) : $value;
            $value = array_map('sanitize_text_field', (array) $value);

            return implode(',', $value);
        }

        return sanitize_text_field(function_exists('wp_unslash') ? wp_unslash($value) : $value);
    }

    private function get_editor_nonce() {
        $nonce = $this->get_sanitized_request_value('nonce');

        if ($nonce === '') {
            $nonce = $this->get_sanitized_request_value('_wpnonce');
        }

        return $nonce;
    }

    private function build_editor_context($type, $file) {
        if ('theme' === $type) {
            $stylesheet = $this->get_sanitized_request_value('theme', get_stylesheet());
            return (!empty($stylesheet) && !empty($file)) ? 'edit-theme_' . $stylesheet . '_' . $file : '';
        }

        if ('plugin' === $type) {
            return !empty($file) ? 'edit-plugin_' . $file : '';
        }

        return '';
    }

    /**
     * Validate editor nonce against contextual and core actions.
     *
     * @param string $nonce   Nonce value.
     * @param string $type    Editor type: theme|plugin|unknown.
     * @param string $context Contextual nonce action.
     *
     * @return bool
     */
    private function validate_editor_nonce($nonce, $type, $context) {
        if (empty($nonce)) {
            return false;
        }

        if (!empty($context) && wp_verify_nonce($nonce, $context)) {
            return true;
        }

        if ('theme' === $type && wp_verify_nonce($nonce, 'edit-theme')) {
            return true;
        }

        if ('plugin' === $type && wp_verify_nonce($nonce, 'edit-plugin')) {
            return true;
        }

        return false;
    }

    private function user_can_edit_context($type) {
        if ('theme' === $type) {
            return current_user_can('edit_themes');
        }

        if ('plugin' === $type) {
            return current_user_can('edit_plugins');
        }

        return $this->user_can_modify_files();
    }

    private function detect_editor_request_type() {
        if ($this->get_sanitized_request_value('theme') !== '') {
            return 'theme';
        }

        if ($this->get_sanitized_request_value('plugin') !== '') {
            return 'plugin';
        }

        return 'unknown';
    }

    public function detect_file_editor_access() {
        global $pagenow;

        $editor_pages = array('theme-editor.php', 'plugin-editor.php');

        if (!in_array($pagenow, $editor_pages)) {
            return;
        }

        $file = '';
        $type = '';
        $context = '';

        if ($pagenow === 'theme-editor.php') {
            $type = 'theme';
            $file = $this->get_sanitized_request_value('file', 'style.css');
        } elseif ($pagenow === 'plugin-editor.php') {
            $type = 'plugin';
            $file = $this->get_sanitized_request_value('file');
            if (empty($file)) {
                $file = $this->get_sanitized_request_value('plugin');
            }
        }

        if (empty($type) || !$this->user_can_edit_context($type)) {
            return;
        }

        $nonce = $this->get_editor_nonce();
        $context = $this->build_editor_context($type, $file);
        $request_method = strtoupper((string) vulnity_get_server_var('REQUEST_METHOD'));

        // Detectar modificación de archivo por varios métodos
        $is_modification = false;
        $content = '';

        list($has_newcontent, $sanitized_newcontent) = $request_method === 'POST'
            ? $this->get_sanitized_post_newcontent($nonce, $type, $context)
            : array(false, '');

        // Método 1: Verificar el action de WordPress
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce is validated before processing POST modifications
        $post_action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) : '';
        if (
            $request_method === 'POST' &&
            $this->validate_editor_nonce($nonce, $type, $context) &&
            !empty($post_action) &&
            $post_action === 'update'
        ) {
            $is_modification = true;
            $content = $sanitized_newcontent;
        }

        // Método 2: Verificar si hay contenido nuevo siendo enviado
        if (!$is_modification && $request_method === 'POST' && $has_newcontent) {
            $is_modification = true;
            $content = $sanitized_newcontent;
        }
        
        if ($is_modification) {
            // Enviar alerta inmediata de modificación
            $this->send_immediate_modification_alert(array(
                'action' => 'file_modified',
                'type' => $type,
                'file' => $file,
                'content' => $content
            ));
        } else {
            // Enviar alerta de acceso al editor (con cooldown)
            $this->send_editor_access_alert(array(
                'type' => $type,
                'file' => $file,
                'pagenow' => $pagenow
            ));
        }
    }
    
    /**
     * Determina la severidad basada en el tipo de archivo
     */
    private function get_severity_by_file($file) {
        $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        
        // PHP files - Critical (possible RCE)
        if ($extension === 'php') {
            return array(
                'severity' => 'critical',
                'risk' => 'Possible RCE (Remote Code Execution)'
            );
        }
        
        // Frontend files - Medium (possible defacement)
        $frontend_extensions = array('html', 'htm', 'css', 'js', 'json');
        if (in_array($extension, $frontend_extensions)) {
            return array(
                'severity' => 'medium',
                'risk' => 'Possible defacement'
            );
        }
        
        // Text and other files - Low
        return array(
            'severity' => 'low',
            'risk' => 'Configuration or documentation change'
        );
    }
    
    /**
     * Envía alerta de acceso al editor (con cooldown de 15 minutos)
     */
    private function send_editor_access_alert($data) {
        $current_user_info = $this->get_current_user_info();
        
        // Verificar si estamos en cooldown
        $cooldown_key = 'vulnity_editor_access_cooldown_' . $current_user_info['user_id'] . '_' . $data['type'];
        $cooldown_until = get_transient($cooldown_key);
        
        if ($cooldown_until && $cooldown_until > time()) {
            // Estamos en cooldown, no enviar alerta
            return;
        }
        
        $title = sprintf('Editor Access: %s editor opened', ucfirst($data['type']));
        $message = sprintf(
            'User "%s" from IP %s accessed the %s editor',
            $current_user_info['user_login'],
            $current_user_info['user_ip'],
            $data['type']
        );
        
        if (!empty($data['file'])) {
            $message .= sprintf(' and viewed file "%s"', $data['file']);
        }
        
        $details = array(
            'action' => 'editor_accessed',
            'editor_type' => $data['type'],
            'editor_page' => $data['pagenow'],
            'file_viewed' => !empty($data['file']) ? $data['file'] : 'index/default',
            'user_id' => $current_user_info['user_id'],
            'user_login' => $current_user_info['user_login'],
            'user_email' => $current_user_info['user_email'],
            'user_ip' => $current_user_info['user_ip'],
            'user_agent' => vulnity_get_server_var('HTTP_USER_AGENT') ?: 'Unknown',
            'timestamp' => current_time('mysql')
        );
        
        // Enviar alerta de severidad baja
        $this->create_alert(array(
            'severity' => 'low',
            'title' => $title,
            'message' => $message,
            'details' => $details
        ));
        
        // Establecer cooldown de 15 minutos
        set_transient($cooldown_key, time() + $this->cooldown_duration, $this->cooldown_duration);
    }
    
    /**
     * Envía alerta inmediata cuando se modifica un archivo
     */
    private function send_immediate_modification_alert($data) {
        $current_user_info = $this->get_current_user_info();
        
        // Asegurar que 'type' existe
        if (!isset($data['type']) || empty($data['type'])) {
            $data['type'] = 'unknown';
        }
        
        // Determinar severidad basada en el archivo
        $file_info = $this->get_severity_by_file($data['file']);
        
        $title = sprintf('FILE MODIFIED: %s via %s editor', $data['file'], ucfirst($data['type']));
        $message = sprintf(
            'File "%s" was modified via %s editor by user "%s" from IP %s. Risk: %s',
            $data['file'],
            $data['type'],
            $current_user_info['user_login'],
            $current_user_info['user_ip'],
            $file_info['risk']
        );
        
        $details = array(
            'action' => 'file_modified',
            'editor_type' => $data['type'],
            'file' => $data['file'],
            'file_extension' => pathinfo($data['file'], PATHINFO_EXTENSION),
            'risk_assessment' => $file_info['risk'],
            'user_id' => $current_user_info['user_id'],
            'user_login' => $current_user_info['user_login'],
            'user_email' => $current_user_info['user_email'],
            'user_ip' => $current_user_info['user_ip'],
            'user_agent' => vulnity_get_server_var('HTTP_USER_AGENT') ?: 'Unknown',
            'timestamp' => current_time('mysql')
        );
        
        if (isset($data['content'])) {
            $details['content_length'] = strlen($data['content']);
            $details['content_hash'] = md5($data['content']);
            
            // Detectar patrones sospechosos en el contenido
            $suspicious_patterns = array(
                'base64_decode' => 'Base64 encoding detected',
                'eval(' => 'Code evaluation detected',
                'exec(' => 'System command execution detected',
                'system(' => 'System command execution detected',
                'shell_exec(' => 'Shell command execution detected',
                'passthru(' => 'Command passthrough detected',
                'assert(' => 'Assertion detected',
                'create_function' => 'Dynamic function creation detected',
                'include_once' => 'File inclusion detected',
                'require_once' => 'File inclusion detected',
                'wp_remote_' => 'Remote request function detected',
                'curl_exec' => 'CURL execution detected',
                'file_get_contents' => 'File reading function detected',
                'file_put_contents' => 'File writing function detected',
                'fopen' => 'File operation detected',
                'fsockopen' => 'Socket connection detected',
                'pfsockopen' => 'Persistent socket detected',
                'stream_socket_client' => 'Stream socket detected',
                'mysqli_' => 'Direct database access detected',
                '$_POST' => 'POST data access detected',
                '$_GET' => 'GET data access detected',
                '$_REQUEST' => 'REQUEST data access detected',
                '$_FILES' => 'File upload handling detected',
                'preg_replace' => 'Regular expression replacement detected',
                'str_rot13' => 'ROT13 encoding detected',
                'gzinflate' => 'Compression detected',
                'gzuncompress' => 'Decompression detected',
                'hex2bin' => 'Hex conversion detected'
            );
            
            $found_patterns = array();
            foreach ($suspicious_patterns as $pattern => $description) {
                if (stripos($data['content'], $pattern) !== false) {
                    $found_patterns[] = $description;
                }
            }
            
            if (!empty($found_patterns)) {
                $details['suspicious_patterns'] = $found_patterns;
                $details['suspicious_pattern_count'] = count($found_patterns);
                $message .= ' - WARNING: ' . count($found_patterns) . ' malware code detected!';
                // Elevar severidad si hay patrones sospechosos
                if ($file_info['severity'] !== 'critical') {
                    $file_info['severity'] = 'critical';
                }
            }
        }
        
        // Enviar alerta con severidad basada en el tipo de archivo
        $this->create_alert(array(
            'severity' => $file_info['severity'],
            'title' => $title,
            'message' => $message,
            'details' => $details
        ));
        
        // Resetear el cooldown de acceso ya que hubo una modificación
        $cooldown_key = 'vulnity_editor_access_cooldown_' . $current_user_info['user_id'] . '_' . $data['type'];
        set_transient($cooldown_key, time() + $this->cooldown_duration, $this->cooldown_duration);
    }
    
    /**
     * Monitorea ediciones via AJAX (WordPress 4.9+)
     */
    public function monitor_ajax_file_edit($type = 'unknown', $file = '', $nonce = '', $context = '') {
        if (!$this->user_can_modify_files()) {
            return;
        }

        // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is validated via validate_editor_nonce() / get_sanitized_post_newcontent().
        if ($file === '' && isset($_POST['file'])) {
            $file = sanitize_text_field(wp_unslash($_POST['file']));
        }

        if ($type === 'unknown') {
            $type = isset($_POST['theme']) ? 'theme' : (isset($_POST['plugin']) ? 'plugin' : 'unknown');
        }

        if ($nonce === '') {
            $nonce = $this->get_editor_nonce();
        }

        if ($context === '') {
            $context = $this->build_editor_context($type, $file);
        }

        if ($file === '' || !$this->validate_editor_nonce($nonce, $type, $context)) {
            return;
        }

        list($has_newcontent, $content) = $this->get_sanitized_post_newcontent($nonce, $type, $context);
        if (!$has_newcontent) {
            return;
        }
        // phpcs:enable WordPress.Security.NonceVerification.Missing

        $this->send_immediate_modification_alert(array(
            'action' => 'file_modified_ajax',
            'type' => $type,
            'file' => $file,
            'content' => $content
        ));
    }

    /**
     * Retrieves and sanitizes the posted editor content.
     *
     * @return array{0: bool, 1: string} Tuple indicating presence of content and its sanitized value.
     */
    private function get_sanitized_post_newcontent($nonce, $type, $context) {
        if (!$this->validate_editor_nonce($nonce, $type, $context)) {
            return array(false, '');
        }

        $raw_array_value = filter_input(INPUT_POST, 'newcontent', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY);
        if (null !== $raw_array_value) {
            $unslashed_array = function_exists('wp_unslash') ? wp_unslash($raw_array_value) : $raw_array_value;
            if (!is_array($unslashed_array)) {
                $unslashed_array = (array) $unslashed_array;
            }

            $sanitized_segments = array_map('sanitize_textarea_field', $unslashed_array);
            return array(true, implode("\n", $sanitized_segments));
        }

        $raw_value = filter_input(INPUT_POST, 'newcontent', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        if (null !== $raw_value) {
            $unslashed_value = function_exists('wp_unslash') ? wp_unslash($raw_value) : $raw_value;
            return array(true, sanitize_textarea_field($unslashed_value));
        }

        return array(false, '');
    }

    /**
     * Implementación del método abstracto evaluate requerido por la clase padre
     */
    protected function evaluate($data) {
        // Este método es requerido por la clase base pero no lo usamos directamente
        // ya que hemos implementado nuestra propia lógica de evaluación
        
        // Si por alguna razón se llama este método, podemos redirigir a nuestra lógica
        if (isset($data['action']) && $data['action'] === 'file_modified') {
            $this->send_immediate_modification_alert($data);
        } else if (isset($data['action']) && $data['action'] === 'editor_accessed') {
            $this->send_editor_access_alert($data);
        }
    }
    
    /**
     * Obtiene la IP del usuario
     */
    private function get_user_ip() {
        return function_exists('vulnity_resolve_client_ip')
            ? vulnity_resolve_client_ip()
            : 'unknown';
    }

}
