From 250466696bec5350825f29c8918a6184d49e1e6d Mon Sep 17 00:00:00 2001 From: rafa Date: Wed, 15 Jul 2026 19:14:43 -0400 Subject: [PATCH] Buscador: AND obligatorio en FULLTEXT, excluir paginas-indice K2 y soportar frase exacta entre comillas Tres bugs corregidos (issue gitea.feadulta.com#178): - OR implicito en Boolean Mode devolvia casi todo el sitio (~24.700/24.780 posts) para cualquier busqueda de varias palabras; ahora cada palabra es AND obligatorio (+). - Paginas-indice residuales K2 (categorias 37/38, listados por autor) puntuaban altisimo en relevancia por su tamano y tapaban el articulo real; excluidas de resultados de busqueda. - Palabras <3 caracteres forzadas como termino obligatorio rompian el calculo de relevancia (score 0 en filas que si coincidian); se descartan antes de construir la query. - Anadido soporte real de frase exacta: termino entre comillas dobles se envia a MySQL Boolean Mode como frase, en vez de descartarse silenciosamente. --- .../mu-plugins/fea-search-advanced.php | 16 +++++++++ .../mu-plugins/fea-search-fulltext.php | 36 +++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/wordpress/wp-content/mu-plugins/fea-search-advanced.php b/wordpress/wp-content/mu-plugins/fea-search-advanced.php index 9d6d5fd..b8ef800 100644 --- a/wordpress/wp-content/mu-plugins/fea-search-advanced.php +++ b/wordpress/wp-content/mu-plugins/fea-search-advanced.php @@ -32,6 +32,16 @@ defined('FEA_AUTORES_EXCLUIR') or define('FEA_AUTORES_EXCLUIR', [ */ defined('FEA_CATS_CARTA_EXCLUIR') or define('FEA_CATS_CARTA_EXCLUIR', [6, 21, 22]); +/** + * Categorías RESIDUALES de la migración K2: páginas-índice por autor (listado de enlaces + * a sus artículos), no son artículos. 37 = "Lista de autores habituales", 38 = "Lista + * completa de autores por orden alfabético" (~1.169 posts). Por su gran tamaño (varios KB + * de títulos enlazados) el ranking FULLTEXT las puntúa altísimo y tapan el resultado real + * (bug reportado en feedback: "Tu verdadero ser" de Fray Marcos sacaba la ficha-índice del + * autor antes que el artículo). Se excluyen de los resultados de búsqueda. + */ +defined('FEA_CATS_RESIDUAL_EXCLUIR') or define('FEA_CATS_RESIDUAL_EXCLUIR', [37, 38]); + // ───────────────────────────────────────────────────────────────── // i18n mínimo (es / en / fr / it / pt) @@ -139,6 +149,12 @@ add_action('pre_get_posts', function (WP_Query $q): void { // Categoría (tema) if ($fea_cat > 0) $q->set('cat', $fea_cat); + // Excluir páginas-índice residuales K2 (ver FEA_CATS_RESIDUAL_EXCLUIR) de los + // resultados de búsqueda. No aplica si el usuario ya filtró por una categoría concreta. + if ($is_search && $fea_cat <= 0) { + $q->set('category__not_in', FEA_CATS_RESIDUAL_EXCLUIR); + } + // Cita bíblica: coincidencia por PREFIJO (el valor empieza por el término, ej. "Jn"). // Usamos REGEXP '^' con el término escapado para evitar metacaracteres. if ($fea_cita !== '') { diff --git a/wordpress/wp-content/mu-plugins/fea-search-fulltext.php b/wordpress/wp-content/mu-plugins/fea-search-fulltext.php index 7fb43e7..d97781e 100644 --- a/wordpress/wp-content/mu-plugins/fea-search-fulltext.php +++ b/wordpress/wp-content/mu-plugins/fea-search-fulltext.php @@ -6,7 +6,7 @@ * relevancia FULLTEXT si no se pide otro criterio de orden. Degradación elegante: * si no hay término o el índice FULLTEXT no existe, usa el comportamiento nativo. * Convive con fea-search-advanced.php (filtros pre_get_posts de autor/cat/cita/fecha). - * Version: 1.1 + * Version: 1.3 */ if (!defined('ABSPATH')) exit; @@ -38,16 +38,46 @@ function fea_ft_index_exists(): bool { } /** - * Calcula el término FULLTEXT en Boolean Mode (cada palabra con prefijo *). + * Calcula el término FULLTEXT en Boolean Mode. + * + * Caso normal (sin comillas): cada palabra requerida con prefijo + y sufijo *. El '+' + * fuerza AND entre palabras — sin él, Boolean Mode las une con OR implícito y cualquier + * post que contenga UNA sola palabra común (p.ej. "ser") entra en el resultado, lo que en + * la práctica devolvía casi todo el sitio (~24.700 de ~24.780 posts, bug #8 reportado en + * feedback: "Tu verdadero ser" devolvía cientos de artículos irrelevantes). + * + * Caso frase exacta (entrecomillado, ej. `"tu verdadero ser"`): Boolean Mode soporta + * nativamente búsqueda de frase entre comillas dobles (coincidencia de adyacencia, no + * solo de palabras sueltas). Reenviamos el contenido saneado tal cual entre comillas, sin + * partirlo en palabras con + ni *, para que el usuario que escribe entre comillas obtenga + * de verdad una búsqueda de cadena exacta. + * * Devuelve '' si el término sanitizado queda vacío. */ function fea_ft_boolean_term(string $raw): string { + // Frase exacta: todo el término entre comillas dobles. + if (preg_match('/^"(.*)"$/us', $raw, $m)) { + $phrase = trim(substr(preg_replace('/[^\p{L}\p{N}\s\'\-]/u', '', $m[1]), 0, 200)); + if ($phrase === '') return ''; + return '"' . $phrase . '"'; + } + $term = trim(substr(preg_replace('/[^\p{L}\p{N}\s\'\-]/u', '', $raw), 0, 200)); if ($term === '') return ''; global $wpdb; $words = preg_split('/\s+/', $term); - return implode('* ', array_map(fn($w) => $wpdb->esc_like($w), $words)) . '*'; + + // Descartamos palabras por debajo de innodb_ft_min_token_size (3 en este servidor): + // MySQL las excluye del índice, y forzarlas igualmente como término obligatorio ('+') + // no aporta nada al filtrado (WP igual las ignora) pero SÍ rompe el cálculo de + // relevancia: si un término '+' no existe en el índice, MATCH()...AGAINST() en modo + // boolean puede devolver 0 aunque la fila cumpla el resto de términos, dejando el + // ORDER BY sin poder distinguir resultados relevantes de irrelevantes. + $words = array_values(array_filter($words, fn($w) => mb_strlen($w) >= 3)); + if (empty($words)) return ''; + + return implode(' ', array_map(fn($w) => '+' . $wpdb->esc_like($w) . '*', $words)); } /**