<?php
defined('ABSPATH') || exit;
// IV1 WP Tools – Virus Scanner AJAX & Funktionen

define('IV1WPT_VIRUS_SCANNER_IGNORELIST_OPTION', 'iv1wpt_virusscanner_ignorelist');

// ────────────────────────────────────────────────────────────────────────────
// Malware-Signaturen (Signatur-basierter Scanner)
// ────────────────────────────────────────────────────────────────────────────

function iv1wpt_virusscanner_get_signatures(): array {
    return [
        // PHP Code Obfuskierung / Execution
        'eval_base64'           => '/eval\s*\(\s*base64_decode\s*\(/i',
        'eval_gzinflate'        => '/eval\s*\(\s*gzinflate\s*\(/i',
        'eval_gzuncompress'     => '/eval\s*\(\s*gzuncompress\s*\(/i',
        'eval_str_rot13'        => '/eval\s*\(\s*str_rot13\s*\(/i',
        'eval_rawurldecode'     => '/eval\s*\(\s*rawurldecode\s*\(/i',
        'preg_replace_e'        => '/preg_replace\s*\(\s*[\'"].*\/e[\'"].*\)/i',
        'assert_eval'           => '/assert\s*\(\s*(base64_decode|gzinflate|str_rot13)\s*\(/i',
        'create_function'       => '/create_function\s*\(\s*[\'"][^\'"]*[\'"]\s*,/i',

        // Shell-Backdoors / Remote Code Execution
        'system_call'           => '/\b(system|exec|passthru|shell_exec|popen|proc_open)\s*\(\s*\$_(GET|POST|REQUEST|COOKIE|SERVER)/i',
        'backdoor_c99'          => '/\$_(?:GET|POST|REQUEST|COOKIE)\s*\[\s*[\'"](?:cmd|command|c|exec|shell|payload)[\'"]/',
        'backdoor_fx29'         => '/\bfx29shell\b|\bfx29\b/i',
        'phpspy'                => '/\bphpSpy\b|\bphp-spy\b/i',
        'weevely'               => '/\$[a-zA-Z_]\w*\s*=\s*str_split\s*\(.+\);\s*\$[a-zA-Z_]\w*\s*=\s*array_map/i',
        'FilesMan'              => '/FilesMan|WSO Shell|r57shell|c99shell/i',

        // Obfuskierter Code
        'hex_encode'            => '/\\\\x[0-9a-fA-F]{2}\\\\x[0-9a-fA-F]{2}\\\\x[0-9a-fA-F]{2}\\\\x[0-9a-fA-F]{2}\\\\x[0-9a-fA-F]{2}/i',
        'long_variable'         => '/\$[a-zA-Z_]\w*\s*=\s*[\'"][a-zA-Z0-9+\/=]{200,}[\'"]/i',
        'char_concat'           => '/chr\s*\(\s*\d+\s*\)\s*\.\s*chr\s*\(\s*\d+\s*\)\s*\.\s*chr\s*\(\s*\d+\s*\)/i',

        // Versteckte iframes / Redirects
        'hidden_iframe'         => '/<iframe[^>]+(?:style\s*=\s*[\'"][^>]*(?:display\s*:\s*none|width\s*:\s*0|height\s*:\s*0)|width\s*=\s*[\'"]0[\'"]|height\s*=\s*[\'"]0[\'"]).{0,200}src\s*=/is',
        'document_write_iframe' => '/document\.write\s*\(\s*[\'"].*<iframe/i',
        'hidden_redirect'       => '/header\s*\(\s*[\'"]Location:\s*https?:\/\/[^\'"\s]+[\'"]\s*\)/i',

        // Datei-/Netzwerk-Funktionen mit verdächtigen Quellen
        'file_get_url'          => '/file_get_contents\s*\(\s*[\'"]https?:\/\//i',
        'curl_external'         => '/curl_setopt.*CURLOPT_URL.*https?:\/\//i',
        'fsockopen_external'    => '/fsockopen\s*\(\s*[\'"][^\'"\s]+[\'"]/i',

        // WordPress-spezifische Injektionen
        'wp_options_inject'     => '/update_option\s*\(\s*[\'"](?:siteurl|home|admin_email)[\'"].*\$_(GET|POST|REQUEST|COOKIE)/i',
        'add_action_inject'     => '/add_action\s*\(\s*[\'"]init[\'"].*eval\s*\(/i',
        'wp_users_inject'       => '/wp_users.*(?:INSERT|UPDATE).*user_pass/i',

        // Bekannte Malware-Strings
        'darkmailer'            => '/DarkMailer|FuCk SECurity|r57 shell/i',
        'encoded_payload'       => '/\bpassthru\b.*\bbase64_decode\b/i',
        'mail_spam'             => '/mail\s*\(.*\$_(GET|POST|REQUEST|COOKIE)\[/i',
        'wp_spam_inject'        => '/wp_insert_post.*spam|spam.*wp_insert_post/i',
    ];
}

// ────────────────────────────────────────────────────────────────────────────
// Hilfsfunktionen
// ────────────────────────────────────────────────────────────────────────────

/**
 * Ignoreliste laden
 */
function iv1wpt_virusscanner_get_ignorelist(): array {
    $raw = get_option(IV1WPT_VIRUS_SCANNER_IGNORELIST_OPTION, '');
    if (empty($raw)) return [];
    $lines = explode("\n", $raw);
    $list = [];
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line !== '' && $line[0] !== '#') {
            $list[] = wp_normalize_path($line);
        }
    }
    return $list;
}

/**
 * Prüft ob eine Datei auf der Ignoreliste steht
 */
function iv1wpt_virusscanner_is_ignored(string $path, array $ignorelist): bool {
    $path = wp_normalize_path($path);
    foreach ($ignorelist as $entry) {
        // Exakter Match oder Präfix-Match (Verzeichnis)
        if ($path === $entry || strpos($path, rtrim($entry, '/') . '/') === 0) {
            return true;
        }
        // Relativer Pfad
        if (strpos($path, $entry) !== false) {
            return true;
        }
    }
    return false;
}

/**
 * Dateierweiterungen die gescannt werden sollen
 */
function iv1wpt_virusscanner_get_scannable_extensions(): array {
    return ['php', 'php3', 'php4', 'php5', 'php7', 'phtml', 'pht', 'js', 'html', 'htm', 'shtml'];
}

/**
 * Scan-Pfade
 */
function iv1wpt_virusscanner_get_scan_paths(string $scope): array {
    switch ($scope) {
        case 'uploads':
            return [WP_CONTENT_DIR . '/uploads'];
        case 'plugins':
            return [WP_CONTENT_DIR . '/plugins'];
        case 'themes':
            return [WP_CONTENT_DIR . '/themes'];
        case 'core':
            return [ABSPATH . 'wp-admin', ABSPATH . 'wp-includes'];
        case 'full':
        default:
            return [
                ABSPATH . 'wp-admin',
                ABSPATH . 'wp-includes',
                WP_CONTENT_DIR . '/plugins',
                WP_CONTENT_DIR . '/themes',
                WP_CONTENT_DIR . '/uploads',
                ABSPATH . 'wp-content',
            ];
    }
}

// ────────────────────────────────────────────────────────────────────────────
// AJAX: Signatur-Scan (Dateisystem)
// ────────────────────────────────────────────────────────────────────────────

add_action('wp_ajax_iv1wpt_virusscanner_scan_files', 'iv1wpt_virusscanner_scan_files_ajax');
function iv1wpt_virusscanner_scan_files_ajax(): void {
    check_ajax_referer('iv1wpt_ajax');

    if (!current_user_can('manage_options')) {
        wp_send_json_error(__('No permission.', 'iv1-wp-tools'));
    }

    @set_time_limit(300);
    @ini_set('memory_limit', '256M');

    $scope      = sanitize_key($_POST['scope'] ?? 'full');
    $signatures = iv1wpt_virusscanner_get_signatures();
    $ignorelist = iv1wpt_virusscanner_get_ignorelist();
    $extensions = iv1wpt_virusscanner_get_scannable_extensions();
    $scan_paths = iv1wpt_virusscanner_get_scan_paths($scope);

    $findings    = [];
    $scanned     = 0;
    $skipped     = 0;
    $errors      = 0;

    foreach ($scan_paths as $scan_path) {
        $scan_path = wp_normalize_path($scan_path);
        if (!is_dir($scan_path)) {
            continue;
        }

        try {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($scan_path, FilesystemIterator::SKIP_DOTS),
                RecursiveIteratorIterator::SELF_FIRST
            );

            foreach ($iterator as $file) {
                if (!$file->isFile()) {
                    continue;
                }

                $full_path = wp_normalize_path($file->getPathname());

                // Ignoreliste prüfen
                if (iv1wpt_virusscanner_is_ignored($full_path, $ignorelist)) {
                    $skipped++;
                    continue;
                }

                $ext = strtolower($file->getExtension());

                // Nur definierte Erweiterungen scannen
                if (!in_array($ext, $extensions, true)) {
                    continue;
                }

                // Dateigröße begrenzen (max 2 MB für Performance)
                if ($file->getSize() > 2 * 1024 * 1024) {
                    $skipped++;
                    continue;
                }

                $content = @file_get_contents($full_path);
                if ($content === false) {
                    $errors++;
                    continue;
                }

                $scanned++;

                foreach ($signatures as $sig_id => $pattern) {
                    if (@preg_match($pattern, $content, $matches)) {
                        // Kontext: Zeile finden
                        $line_num = 0;
                        $context  = '';
                        $lines    = explode("\n", $content);
                        foreach ($lines as $i => $line) {
                            if (@preg_match($pattern, $line)) {
                                $line_num = $i + 1;
                                $context  = trim(substr($line, 0, 200));
                                break;
                            }
                        }

                        $relative = ltrim(str_replace(wp_normalize_path(ABSPATH), '', $full_path), '/');

                        $findings[] = [
                            'file'      => $relative,
                            'signature' => $sig_id,
                            'line'      => $line_num,
                            'context'   => $context,
                            'severity'  => iv1wpt_virusscanner_get_severity($sig_id),
                        ];
                        // Pro Datei nur einen Fund melden (erste Übereinstimmung)
                        break;
                    }
                }
            }
        } catch (Exception $e) {
            $errors++;
        }
    }

    // Nach Schweregrad sortieren
    usort($findings, function ($a, $b) {
        $order = ['critical' => 0, 'high' => 1, 'medium' => 2, 'low' => 3];
        return ($order[$a['severity']] ?? 9) - ($order[$b['severity']] ?? 9);
    });

    wp_send_json_success([
        'message'  => sprintf(
            /* translators: %d: scanned files, %d: findings */
            __('%1$d files scanned, %2$d suspicious file(s) found.', 'iv1-wp-tools'),
            $scanned,
            count($findings)
        ),
        'scanned'  => $scanned,
        'skipped'  => $skipped,
        'errors'   => $errors,
        'findings' => $findings,
    ]);
}

/**
 * Schweregrad je Signatur
 */
function iv1wpt_virusscanner_get_severity(string $sig_id): string {
    $critical = ['eval_base64', 'eval_gzinflate', 'eval_gzuncompress', 'system_call', 'backdoor_c99',
                 'backdoor_fx29', 'phpspy', 'weevely', 'FilesMan', 'encoded_payload', 'create_function'];
    $high     = ['eval_str_rot13', 'eval_rawurldecode', 'preg_replace_e', 'assert_eval', 'darkmailer',
                 'wp_options_inject', 'add_action_inject', 'wp_users_inject', 'mail_spam'];
    $medium   = ['hidden_iframe', 'document_write_iframe', 'hidden_redirect', 'file_get_url',
                 'fsockopen_external', 'curl_external', 'wp_spam_inject'];

    if (in_array($sig_id, $critical, true)) return 'critical';
    if (in_array($sig_id, $high, true))    return 'high';
    if (in_array($sig_id, $medium, true))  return 'medium';
    return 'low';
}

// ────────────────────────────────────────────────────────────────────────────
// AJAX: PHP-Dateien in Upload-Verzeichnis (verdächtige Dateien)
// ────────────────────────────────────────────────────────────────────────────

add_action('wp_ajax_iv1wpt_virusscanner_scan_uploads', 'iv1wpt_virusscanner_scan_uploads_ajax');
function iv1wpt_virusscanner_scan_uploads_ajax(): void {
    check_ajax_referer('iv1wpt_ajax');

    if (!current_user_can('manage_options')) {
        wp_send_json_error(__('No permission.', 'iv1-wp-tools'));
    }

    $upload_dir = wp_upload_dir();
    $base_path  = wp_normalize_path($upload_dir['basedir']);
    $ignorelist = iv1wpt_virusscanner_get_ignorelist();

    // Dateitypen die NICHT im Upload-Verzeichnis sein sollten
    $suspicious_extensions = ['php', 'php3', 'php4', 'php5', 'php7', 'phtml', 'pht',
                               'asp', 'aspx', 'jsp', 'cgi', 'pl', 'py', 'rb', 'sh', 'bash'];

    $findings = [];
    $scanned  = 0;

    if (!is_dir($base_path)) {
        wp_send_json_error(__('Upload directory not found.', 'iv1-wp-tools'));
    }

    try {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($base_path, FilesystemIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if (!$file->isFile()) continue;

            $full_path = wp_normalize_path($file->getPathname());

            if (iv1wpt_virusscanner_is_ignored($full_path, $ignorelist)) continue;

            $scanned++;
            $ext = strtolower($file->getExtension());

            if (in_array($ext, $suspicious_extensions, true)) {
                $relative = ltrim(str_replace(wp_normalize_path(ABSPATH), '', $full_path), '/');
                $findings[] = [
                    'file'     => $relative,
                    'type'     => strtoupper($ext),
                    'size'     => $file->getSize(),
                    'mtime'    => wp_date('d.m.Y H:i', $file->getMTime()),
                    'severity' => 'critical',
                    'reason'   => __('Executable file in upload directory', 'iv1-wp-tools'),
                ];
            }
        }
    } catch (Exception $e) {
        wp_send_json_error(__('Error while scanning upload directory.', 'iv1-wp-tools'));
    }

    wp_send_json_success([
        'message'  => sprintf(
            /* translators: %d: scanned, %d: findings */
            __('%1$d files scanned, %2$d suspicious file(s) found in upload directory.', 'iv1-wp-tools'),
            $scanned,
            count($findings)
        ),
        'scanned'  => $scanned,
        'findings' => $findings,
    ]);
}

// ────────────────────────────────────────────────────────────────────────────
// AJAX: Datenbank-Scan
// ────────────────────────────────────────────────────────────────────────────

add_action('wp_ajax_iv1wpt_virusscanner_scan_database', 'iv1wpt_virusscanner_scan_database_ajax');
function iv1wpt_virusscanner_scan_database_ajax(): void {
    check_ajax_referer('iv1wpt_ajax');

    if (!current_user_can('manage_options')) {
        wp_send_json_error(__('No permission.', 'iv1-wp-tools'));
    }

    global $wpdb;

    @set_time_limit(300);

    // DB-Muster
    $db_patterns = [
        'eval_base64'     => "eval(base64_decode",
        'hidden_iframe'   => "<iframe",
        'javascript_obf'  => "document.write(unescape",
        'base64_long'     => "base64_encode",
        'exec_shell'      => "shell_exec",
        'passthru'        => "passthru(",
        'system_call'     => "<? system(",
        'obf_script'      => "<script>eval(",
        'wp_redirect_hack'=> "wp_redirect.*base64",
        'spam_links'      => '<a href="http',
        'pharma_hack'     => 'viagra|cialis|pharmacy|casino|poker',
        'seo_injection'   => '<style>.qliqx{position:absolute',
    ];

    $findings = [];
    $ignorelist = iv1wpt_virusscanner_get_ignorelist();

    // WordPress-Kerntabellen durchsuchen
    $tables_to_scan = [
        $wpdb->posts        => ['post_content', 'post_excerpt', 'post_title'],
        $wpdb->postmeta     => ['meta_value'],
        $wpdb->options      => ['option_value'],
        $wpdb->comments     => ['comment_content', 'comment_author_url'],
        $wpdb->usermeta     => ['meta_value'],
    ];

    foreach ($tables_to_scan as $table => $columns) {
        // Auf Ignoreliste? (Tabellenname)
        if (iv1wpt_virusscanner_is_ignored($table, $ignorelist)) continue;

        // Prüfen ob Tabelle existiert
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if (!$exists) continue;

        foreach ($columns as $column) {
            foreach ($db_patterns as $pattern_id => $pattern) {
                // Einfache LIKE-Suche (kein Regex in SQL für maximale Kompatibilität)
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $sql = "SELECT COUNT(*) FROM `{$table}` WHERE `{$column}` LIKE %s";
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
                $count = (int) $wpdb->get_var($wpdb->prepare($sql, '%' . $wpdb->esc_like($pattern) . '%'));

                if ($count > 0) {
                    $findings[] = [
                        'table'    => $table,
                        'column'   => $column,
                        'pattern'  => $pattern_id,
                        'count'    => $count,
                        'severity' => iv1wpt_virusscanner_get_db_severity($pattern_id),
                    ];
                }
            }
        }
    }

    // Nach Schweregrad sortieren
    usort($findings, function ($a, $b) {
        $order = ['critical' => 0, 'high' => 1, 'medium' => 2, 'low' => 3];
        return ($order[$a['severity']] ?? 9) - ($order[$b['severity']] ?? 9);
    });

    wp_send_json_success([
        'message'  => sprintf(
            /* translators: %d: findings */
            __('Database scan completed. %d suspicious entry(ies) found.', 'iv1-wp-tools'),
            count($findings)
        ),
        'findings' => $findings,
    ]);
}

/**
 * DB-Schweregrad
 */
function iv1wpt_virusscanner_get_db_severity(string $pattern_id): string {
    $critical = ['eval_base64', 'exec_shell', 'passthru', 'system_call', 'obf_script'];
    $high     = ['javascript_obf', 'wp_redirect_hack', 'seo_injection'];
    $medium   = ['hidden_iframe', 'base64_long', 'spam_links', 'pharma_hack'];
    if (in_array($pattern_id, $critical, true)) return 'critical';
    if (in_array($pattern_id, $high, true))     return 'high';
    if (in_array($pattern_id, $medium, true))   return 'medium';
    return 'low';
}


/**
 * SHA256-Hash einer Datei
 */
function sha256_file_hash(string $path): string|false {
    return hash_file('sha256', $path);
}

// ────────────────────────────────────────────────────────────────────────────
// AJAX: Ignoreliste speichern
// ────────────────────────────────────────────────────────────────────────────

add_action('wp_ajax_iv1wpt_virusscanner_save_ignorelist', 'iv1wpt_virusscanner_save_ignorelist_ajax');
function iv1wpt_virusscanner_save_ignorelist_ajax(): void {
    check_ajax_referer('iv1wpt_ajax');

    if (!current_user_can('manage_options')) {
        wp_send_json_error(__('No permission.', 'iv1-wp-tools'));
    }

    $ignorelist = sanitize_textarea_field($_POST['ignorelist'] ?? '');
    update_option(IV1WPT_VIRUS_SCANNER_IGNORELIST_OPTION, $ignorelist);


    wp_send_json_success(['message' => __('Settings saved.', 'iv1-wp-tools')]);
}

// ────────────────────────────────────────────────────────────────────────────
// AJAX: Dateiinhalt lesen (für Modal-Anzeige)
// ────────────────────────────────────────────────────────────────────────────

add_action('wp_ajax_iv1wpt_virusscanner_read_file', 'iv1wpt_virusscanner_read_file_ajax');
function iv1wpt_virusscanner_read_file_ajax(): void {
    check_ajax_referer('iv1wpt_ajax');
    if (!current_user_can('manage_options')) {
        wp_send_json_error(__('No permission.', 'iv1-wp-tools'));
    }

    $relative = sanitize_text_field($_POST['file'] ?? '');
    if (empty($relative)) {
        wp_send_json_error(__('No file specified.', 'iv1-wp-tools'));
    }

    // Pfad-Traversal verhindern
    $full_path = wp_normalize_path(ABSPATH . ltrim($relative, '/'));
    if (strpos($full_path, wp_normalize_path(ABSPATH)) !== 0) {
        wp_send_json_error(__('Invalid file path.', 'iv1-wp-tools'));
    }

    if (!file_exists($full_path) || !is_file($full_path)) {
        wp_send_json_error(__('File not found.', 'iv1-wp-tools'));
    }

    if (filesize($full_path) > 2 * 1024 * 1024) {
        wp_send_json_error(__('File too large to display (max 2 MB).', 'iv1-wp-tools'));
    }

    $content = @file_get_contents($full_path);
    if ($content === false) {
        wp_send_json_error(__('File could not be read.', 'iv1-wp-tools'));
    }

    wp_send_json_success([
        'file'    => $relative,
        'content' => $content,
        'size'    => filesize($full_path),
        'mtime'   => wp_date('d.m.Y H:i:s', filemtime($full_path)),
    ]);
}

// ────────────────────────────────────────────────────────────────────────────
// AJAX: Datei löschen
// ────────────────────────────────────────────────────────────────────────────

add_action('wp_ajax_iv1wpt_virusscanner_delete_file', 'iv1wpt_virusscanner_delete_file_ajax');
function iv1wpt_virusscanner_delete_file_ajax(): void {
    check_ajax_referer('iv1wpt_ajax');
    if (!current_user_can('manage_options')) {
        wp_send_json_error(__('No permission.', 'iv1-wp-tools'));
    }

    $relative = sanitize_text_field($_POST['file'] ?? '');
    if (empty($relative)) {
        wp_send_json_error(__('No file specified.', 'iv1-wp-tools'));
    }

    $full_path = wp_normalize_path(ABSPATH . ltrim($relative, '/'));
    if (strpos($full_path, wp_normalize_path(ABSPATH)) !== 0) {
        wp_send_json_error(__('Invalid file path.', 'iv1-wp-tools'));
    }

    if (!file_exists($full_path) || !is_file($full_path)) {
        wp_send_json_error(__('File not found.', 'iv1-wp-tools'));
    }

    if (!@unlink($full_path)) {
        wp_send_json_error(__('File could not be deleted. Check file permissions.', 'iv1-wp-tools'));
    }

    wp_send_json_success([
		 /* translators: %s: Deleted file path. */
        'message' => sprintf(__('File "%s" was successfully deleted.', 'iv1-wp-tools'), $relative),
        'file'    => $relative,
    ]);
}

// ────────────────────────────────────────────────────────────────────────────
// AJAX: Datenbank-Datensätze anzeigen (für einen Fund)
// ────────────────────────────────────────────────────────────────────────────

add_action('wp_ajax_iv1wpt_virusscanner_db_records', 'iv1wpt_virusscanner_db_records_ajax');
function iv1wpt_virusscanner_db_records_ajax(): void {
    check_ajax_referer('iv1wpt_ajax');
    if (!current_user_can('manage_options')) {
        wp_send_json_error(__('No permission.', 'iv1-wp-tools'));
    }

    global $wpdb;

    $table      = sanitize_text_field($_POST['table']      ?? '');
    $column     = sanitize_text_field($_POST['column']     ?? '');
    $pattern_id = sanitize_key($_POST['pattern_id']        ?? '');

    if (empty($table) || empty($column) || empty($pattern_id)) {
        wp_send_json_error(__('Missing parameters.', 'iv1-wp-tools'));
    }

    // Suchmuster serverseitig nachschlagen — verhindert Client-Manipulation,
    // löst HTML-Stripping durch sanitize_text_field und OR-Suche (pharma_hack).
    $db_patterns = [
        'eval_base64'      => ['eval(base64_decode'],
        'hidden_iframe'    => ['<iframe'],
        'javascript_obf'   => ['document.write(unescape'],
        'base64_long'      => ['base64_encode'],
        'exec_shell'       => ['shell_exec'],
        'passthru'         => ['passthru('],
        'system_call'      => ['<? system('],
        'obf_script'       => ['<script>eval('],
        'wp_redirect_hack' => ['wp_redirect'],
        'spam_links'       => ['<a href="http'],
        'pharma_hack'      => ['viagra', 'cialis', 'pharmacy', 'casino', 'poker'],
        'seo_injection'    => ['<style>.qliqx{position:absolute'],
    ];

    if (!isset($db_patterns[$pattern_id])) {
        wp_send_json_error(__('Unknown pattern.', 'iv1-wp-tools'));
    }

    $search_terms = $db_patterns[$pattern_id];

    // Sicherheit: Nur bekannte WP-Tabellen erlauben
    $allowed_tables = [
        $wpdb->posts, $wpdb->postmeta, $wpdb->options,
        $wpdb->comments, $wpdb->usermeta,
    ];
    if (!in_array($table, $allowed_tables, true)) {
        wp_send_json_error(__('Table not allowed.', 'iv1-wp-tools'));
    }

    // Primärschlüssel je Tabelle
    $pk_map = [
        $wpdb->posts    => 'ID',
        $wpdb->postmeta => 'meta_id',
        $wpdb->options  => 'option_id',
        $wpdb->comments => 'comment_ID',
        $wpdb->usermeta => 'umeta_id',
    ];
    $pk = $pk_map[$table] ?? 'ID';

    // WHERE-Klausel: ein oder mehrere Suchbegriffe → OR-Verknüpfung
    $where_parts  = [];
    $prepare_args = [];
    foreach ($search_terms as $term) {
        $where_parts[]  = "`{$column}` LIKE %s";
        $prepare_args[] = '%' . $wpdb->esc_like($term) . '%';
    }
    $where = implode(' OR ', $where_parts);

    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    $sql     = "SELECT `{$pk}`, `{$column}` FROM `{$table}` WHERE {$where} LIMIT 50";
    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    $results = $wpdb->get_results($wpdb->prepare($sql, ...$prepare_args));

    if ($results === null) {
        wp_send_json_error(__('Database error.', 'iv1-wp-tools'));
    }

    $rows = [];
    foreach ($results as $row) {
        $rows[] = [
            'id'      => $row->$pk,
            'pk'      => $pk,
            'content' => $row->$column,
        ];
    }

    wp_send_json_success([
        'table'      => $table,
        'column'     => $column,
        'pattern_id' => $pattern_id,
        'pk'         => $pk,
        'rows'       => $rows,
        'total'      => count($rows),
    ]);
}
