['es' => 'Búsqueda avanzada', 'en' => 'Advanced search', 'fr' => 'Recherche avancée', 'it' => 'Ricerca avanzata', 'pt' => 'Pesquisa avançada'], 'word' => ['es' => 'Palabra o frase', 'en' => 'Word or phrase', 'fr' => 'Mot ou phrase', 'it' => 'Parola o frase', 'pt' => 'Palavra ou frase'], 'author' => ['es' => 'Autor', 'en' => 'Author', 'fr' => 'Auteur', 'it' => 'Autore', 'pt' => 'Autor'], 'all_authors' => ['es' => '— Cualquier autor —', 'en' => '— Any author —', 'fr' => '— Tout auteur —', 'it' => '— Qualsiasi autore —', 'pt' => '— Qualquer autor —'], 'topic' => ['es' => 'Categoría', 'en' => 'Category', 'fr' => 'Catégorie', 'it' => 'Categoria', 'pt' => 'Categoria'], 'all_topics' => ['es' => '— Cualquier categoría —', 'en' => '— Any category —', 'fr' => '— Toute catégorie —', 'it' => '— Qualsiasi categoria —', 'pt' => '— Qualquer categoria —'], 'biblical_ref' => ['es' => 'Cita bíblica', 'en' => 'Biblical reference', 'fr' => 'Référence biblique', 'it' => 'Citazione biblica', 'pt' => 'Referência bíblica'], 'biblical_ph' => ['es' => 'Ej: Jn 3', 'en' => 'E.g. Jn 3', 'fr' => 'Ex: Jn 3', 'it' => 'Es: Gv 3', 'pt' => 'Ex: Jo 3'], 'date_from' => ['es' => 'Desde', 'en' => 'From', 'fr' => 'Du', 'it' => 'Dal', 'pt' => 'De'], 'date_to' => ['es' => 'Hasta', 'en' => 'To', 'fr' => "Jusqu'au", 'it' => 'Al', 'pt' => 'Até'], 'search_btn' => ['es' => 'Buscar', 'en' => 'Search', 'fr' => 'Rechercher', 'it' => 'Cerca', 'pt' => 'Pesquisar'], 'reset_btn' => ['es' => 'Limpiar', 'en' => 'Clear', 'fr' => 'Effacer', 'it' => 'Cancella', 'pt' => 'Limpar'], 'results' => ['es' => 'resultado(s)', 'en' => 'result(s)', 'fr' => 'résultat(s)', 'it' => 'risultato/i', 'pt' => 'resultado(s)'], 'no_results' => ['es' => 'Sin resultados. Prueba con otros términos.', 'en' => 'No results. Try other terms.', 'fr' => 'Aucun résultat. Essayez d\'autres termes.', 'it' => 'Nessun risultato. Prova con altri termini.', 'pt' => 'Sem resultados. Tente outros termos.'], 'active_filters' => ['es' => 'Filtros activos:', 'en' => 'Active filters:', 'fr' => 'Filtres actifs:', 'it' => 'Filtri attivi:', 'pt' => 'Filtros ativos:'], 'filter_author' => ['es' => 'Autor', 'en' => 'Author', 'fr' => 'Auteur', 'it' => 'Autore', 'pt' => 'Autor'], 'filter_topic' => ['es' => 'Categoría', 'en' => 'Category', 'fr' => 'Catégorie', 'it' => 'Categoria', 'pt' => 'Categoria'], 'filter_cita' => ['es' => 'Cita', 'en' => 'Ref.', 'fr' => 'Réf.', 'it' => 'Cit.', 'pt' => 'Ref.'], 'filter_date' => ['es' => 'Fecha', 'en' => 'Date', 'fr' => 'Date', 'it' => 'Data', 'pt' => 'Data'], 'by' => ['es' => 'por', 'en' => 'by', 'fr' => 'par', 'it' => 'di', 'pt' => 'por'], ]; $row = $strings[$key] ?? []; return $row[$lang] ?? $row['es'] ?? $key; } // ───────────────────────────────────────────────────────────────── // Query vars // ───────────────────────────────────────────────────────────────── add_filter('query_vars', function (array $vars): array { $vars[] = 'fea_author'; $vars[] = 'fea_cat'; $vars[] = 'fea_cita'; $vars[] = 'fea_date_from'; $vars[] = 'fea_date_to'; return $vars; }); // ───────────────────────────────────────────────────────────────── // pre_get_posts — aplica los filtros avanzados // ───────────────────────────────────────────────────────────────── add_action('pre_get_posts', function (WP_Query $q): void { if (is_admin() || !$q->is_main_query()) return; // Leer los parámetros avanzados desde $_GET directamente (más fiable en pre_get_posts) $fea_author = isset($_GET['fea_author']) ? (int)$_GET['fea_author'] : 0; $fea_cat = isset($_GET['fea_cat']) ? (int)$_GET['fea_cat'] : 0; $fea_cita = isset($_GET['fea_cita']) ? sanitize_text_field($_GET['fea_cita']) : ''; $fea_dfr = isset($_GET['fea_date_from']) ? sanitize_text_field($_GET['fea_date_from']) : ''; $fea_dto = isset($_GET['fea_date_to']) ? sanitize_text_field($_GET['fea_date_to']) : ''; $has_adv = ($fea_author > 0 || $fea_cat > 0 || $fea_cita !== '' || $fea_dfr !== '' || $fea_dto !== ''); $is_search = $q->is_search(); // Activar si: es búsqueda, o si hay vars avanzadas (con o sin ?s=) if (!$is_search && !$has_adv) return; // Si hay filtros avanzados pero no ?s=, convertimos el query en listado de posts // (evitamos que WP muestre la home o una 404) if ($has_adv && !$is_search) { $q->set('post_type', 'post'); $q->set('post_status', 'publish'); // Forzamos is_search para que el template search se active $q->is_home = false; $q->is_front_page = false; $q->is_archive = false; $q->is_search = true; } // Autor if ($fea_author > 0) $q->set('author', $fea_author); // Categoría (tema) if ($fea_cat > 0) $q->set('cat', $fea_cat); // 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 !== '') { $regex = '^' . preg_quote($fea_cita, '/'); $q->set('meta_query', [ [ 'key' => '_cita_evangelio', 'value' => $regex, 'compare' => 'REGEXP', ], ]); } // Fechas: la UI usa → formato YYYY-MM-DD (fecha completa). // Tratamos los límites como fechas exactas, inclusive. if ($fea_dfr !== '' || $fea_dto !== '') { $date_query = ['inclusive' => true]; if ($fea_dfr !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $fea_dfr)) { [$y, $m, $d] = array_map('intval', explode('-', $fea_dfr)); $date_query['after'] = ['year' => $y, 'month' => $m, 'day' => $d]; } if ($fea_dto !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $fea_dto)) { [$y, $m, $d] = array_map('intval', explode('-', $fea_dto)); $date_query['before'] = ['year' => $y, 'month' => $m, 'day' => $d]; } // Sólo aplicamos si quedó al menos un límite válido if (isset($date_query['after']) || isset($date_query['before'])) { $q->set('date_query', [$date_query]); } } }); // ───────────────────────────────────────────────────────────────── // Helpers: obtener datos del formulario // ───────────────────────────────────────────────────────────────── /** Devuelve los autores elegibles (≥30 posts, sin excluidos), cacheado por request. */ function fea_adv_get_authors(): array { static $cache = null; if ($cache !== null) return $cache; global $wpdb; $excl = implode(',', array_map('intval', FEA_AUTORES_EXCLUIR)); // Excluimos por ID y, de forma robusta, cualquier display_name que contenga "Testament" // (Nuevo/Antiguo Testamento, New/Old Testament, Nouveau/Ancien Testament, etc.). $cache = $wpdb->get_results(" SELECT u.ID, u.display_name, COUNT(p.ID) as cnt FROM {$wpdb->users} u JOIN {$wpdb->posts} p ON p.post_author = u.ID WHERE p.post_status = 'publish' AND p.post_type = 'post' AND u.ID NOT IN ({$excl}) AND u.display_name NOT LIKE '%Testament%' GROUP BY u.ID HAVING cnt >= 30 ORDER BY u.display_name "); return $cache; } /** * Lista CURADA de categorías-sección reales (term_ids ES, idioma canónico), en orden * editorial. Las 153 categorías reales de la BD incluyen categorías-autor y residuales * de la migración K2 → desplegable inmanejable; por eso se cura a las secciones del sitio. * En idiomas ≠ ES se traduce cada term con Polylang. */ defined('FEA_CATS_CURADA') or define('FEA_CATS_CURADA', [ 1650, // Artículos 1647, // Comentarios al evangelio 1645, // Lecturas bíblicas 1648, // Eucaristía 1646, // Comentario editorial 1649, // Multimedia 63, // EFFA 14, // A la fuente cada día 23, // Cartas que nos llegan 41, // Noticias de alcance 24, // Tablón de anuncios 54, // Canciones religiosas 45, // Canciones-plegarias ]); /** * Etiqueta bonita en el desplegable (solo ES) para categorías cuyo nombre en BD quedó * sin formatear en la migración K2 (no toca el dato del término). */ defined('FEA_CATS_LABEL') or define('FEA_CATS_LABEL', [ 14 => 'A la fuente cada día', // en BD es "Alafuentecadadia" ]); /** * Devuelve las categorías del selector (lista curada FEA_CATS_CURADA), traducidas al * idioma activo (Polylang) y conservando el orden editorial. * Devuelve array de objetos {term_id, name} con el term_id DEL IDIOMA ACTUAL. */ function fea_adv_get_categories(): array { static $cache = null; if ($cache !== null) return $cache; $lang = function_exists('pll_current_language') ? pll_current_language() : ''; $default = function_exists('pll_default_language') ? pll_default_language() : 'es'; $out = []; foreach (FEA_CATS_CURADA as $es_id) { $tid = (int) $es_id; if ($lang && $lang !== $default && function_exists('pll_get_term')) { $tr = pll_get_term($es_id, $lang); if ($tr) $tid = (int) $tr; } $term = get_term($tid, 'category'); if (!$term || is_wp_error($term)) continue; $name = $term->name; if ((!$lang || $lang === $default) && isset(FEA_CATS_LABEL[$es_id])) { $name = FEA_CATS_LABEL[$es_id]; } $out[] = (object) ['term_id' => (int) $term->term_id, 'name' => $name]; } $cache = $out; return $cache; } /** URL base del idioma actual (para el action del form). */ function fea_adv_lang_base(): string { $base = home_url('/'); if (function_exists('pll_current_language')) { $lang = pll_current_language(); $default = function_exists('pll_default_language') ? pll_default_language() : 'es'; if ($lang && $lang !== $default) $base = home_url('/' . $lang . '/'); } return $base; } /** Nombre traducido de una categoría (vía Polylang si está disponible). */ function fea_adv_cat_name(int $cat_id, string $fallback): string { if (function_exists('pll_current_language')) { $lang = pll_current_language(); $default = function_exists('pll_default_language') ? pll_default_language() : 'es'; if ($lang !== $default) { $translated_id = function_exists('pll_get_term') ? pll_get_term($cat_id, $lang) : 0; if ($translated_id) { $term = get_term($translated_id); if ($term && !is_wp_error($term)) return $term->name; } } } $term = get_term($cat_id, 'category'); return ($term && !is_wp_error($term)) ? $term->name : $fallback; } // ───────────────────────────────────────────────────────────────── // Renderiza el formulario avanzado // ───────────────────────────────────────────────────────────────── function fea_adv_form_html(): string { $action = esc_url(fea_adv_lang_base()); $s = esc_attr(get_search_query()); // Leer de $_GET para fiabilidad (get_query_var puede llegar vacío si la var no estaba registrada aún) $sel_aut = isset($_GET['fea_author']) ? (int)$_GET['fea_author'] : 0; $sel_cat = isset($_GET['fea_cat']) ? (int)$_GET['fea_cat'] : 0; $sel_cit = isset($_GET['fea_cita']) ? esc_attr(sanitize_text_field($_GET['fea_cita'])) : ''; $sel_dfr = isset($_GET['fea_date_from'])? esc_attr(sanitize_text_field($_GET['fea_date_from'])) : ''; $sel_dto = isset($_GET['fea_date_to']) ? esc_attr(sanitize_text_field($_GET['fea_date_to'])) : ''; $authors = fea_adv_get_authors(); $cats = fea_adv_get_categories(); $t_title = esc_html(fea_adv_t('search_advanced')); $t_word = esc_html(fea_adv_t('word')); $t_author = esc_html(fea_adv_t('author')); $t_allaut = esc_html(fea_adv_t('all_authors')); $t_topic = esc_html(fea_adv_t('topic')); $t_alltop = esc_html(fea_adv_t('all_topics')); $t_cita = esc_html(fea_adv_t('biblical_ref')); $t_citaph = esc_attr(fea_adv_t('biblical_ph')); $t_dfr = esc_html(fea_adv_t('date_from')); $t_dto = esc_html(fea_adv_t('date_to')); $t_btn = esc_html(fea_adv_t('search_btn')); $t_reset = esc_html(fea_adv_t('reset_btn')); $html = ''; return $html; } // ───────────────────────────────────────────────────────────────── // Chips de filtros activos + contador de resultados // ───────────────────────────────────────────────────────────────── function fea_adv_chips_html(): string { $chips = []; $aut_id = isset($_GET['fea_author']) ? (int)$_GET['fea_author'] : 0; if ($aut_id > 0) { $udata = get_userdata($aut_id); $name = $udata ? esc_html($udata->display_name) : $aut_id; $chips[] = fea_adv_chip(fea_adv_t('filter_author') . ': ' . $name, 'fea_author'); } $cat_id = isset($_GET['fea_cat']) ? (int)$_GET['fea_cat'] : 0; if ($cat_id > 0) { $term = get_term($cat_id, 'category'); $name = ($term && !is_wp_error($term)) ? esc_html($term->name) : $cat_id; $chips[] = fea_adv_chip(fea_adv_t('filter_topic') . ': ' . $name, 'fea_cat'); } $cita = isset($_GET['fea_cita']) ? sanitize_text_field($_GET['fea_cita']) : ''; if ($cita !== '') { $chips[] = fea_adv_chip(fea_adv_t('filter_cita') . ': ' . esc_html($cita), 'fea_cita'); } $dfr = isset($_GET['fea_date_from']) ? sanitize_text_field($_GET['fea_date_from']) : ''; $dto = isset($_GET['fea_date_to']) ? sanitize_text_field($_GET['fea_date_to']) : ''; if ($dfr !== '' || $dto !== '') { $label = fea_adv_t('filter_date') . ': ' . ($dfr ?: '?') . ' – ' . ($dto ?: '?'); $chips[] = fea_adv_chip(esc_html($label), 'fea_date_from', 'fea_date_to'); } if (empty($chips)) return ''; return '
' . esc_html(fea_adv_t('active_filters')) . '' . implode('', $chips) . '
'; } /** Genera un chip con botón ✕ que elimina el filtro de la URL. */ function fea_adv_chip(string $label, string ...$remove_params): string { $url = remove_query_arg($remove_params); return '' . $label . ' ×'; } // ───────────────────────────────────────────────────────────────── // Inyección en la página de resultados (template search) // ───────────────────────────────────────────────────────────────── /** * Inyecta el formulario avanzado + chips antes del primer bloque wp:query-title * del template de búsqueda, modificando el HTML renderizado del bloque principal. */ add_filter('render_block', function (string $html, array $block): string { if (is_admin()) return $html; // Inyectar en búsquedas y cuando hay filtros avanzados activos $has_adv_get = !empty($_GET['fea_author']) || !empty($_GET['fea_cat']) || !empty($_GET['fea_cita']) || !empty($_GET['fea_date_from']) || !empty($_GET['fea_date_to']); if (!is_search() && !is_page('buscar') && !$has_adv_get) return $html; if (($block['blockName'] ?? '') !== 'core/query-title') return $html; $form = fea_adv_form_html(); $chips = fea_adv_chips_html(); // Contador de resultados (sólo cuando hay consulta activa) $counter = ''; if (is_search() || $has_adv_get) { global $wp_query; if ($wp_query && $wp_query->found_posts !== null) { $n = (int) $wp_query->found_posts; $counter = '
' . $n . ' ' . esc_html(fea_adv_t('results')) . '
'; } } return $form . $chips . $counter . $html; }, 10, 2); /** * Añade byline (autor) en cada tarjeta fea-archive-card dentro del template search. * Lo hacemos inyectando tras wp:post-date en el contexto correcto. */ add_filter('render_block', function (string $html, array $block): string { if (is_admin()) return $html; $has_adv_get2 = !empty($_GET['fea_author']) || !empty($_GET['fea_cat']) || !empty($_GET['fea_cita']) || !empty($_GET['fea_date_from']) || !empty($_GET['fea_date_to']); if (!is_search() && !is_page('buscar') && !$has_adv_get2) return $html; if (($block['blockName'] ?? '') !== 'core/post-date') return $html; $post_id = $block['attrs']['postId'] ?? (in_the_loop() ? get_the_ID() : 0); if (!$post_id) $post_id = get_the_ID(); if (!$post_id) return $html; $author_id = (int) get_post_field('post_author', $post_id); $author_name = get_the_author_meta('display_name', $author_id); if (!$author_name) return $html; $author_url = get_author_posts_url($author_id); $byline = '
' . esc_html(fea_adv_t('by')) . ' ' . '' . esc_html($author_name) . '
'; return $html . $byline; }, 10, 2); // ───────────────────────────────────────────────────────────────── // Enlace «Búsqueda avanzada» desde la barra fea-search // ───────────────────────────────────────────────────────────────── add_filter('render_block', function (string $html, array $block): string { if (is_admin()) return $html; if (($block['blockName'] ?? '') !== 'core/template-part') return $html; $slug = $block['attrs']['slug'] ?? ''; if (!in_array($slug, ['header', 'cabecera-portada'], true)) return $html; // Sólo inyectamos el enlace si ya hay una barra de búsqueda (.fea-search-bar) // Buscamos la barra y le añadimos el enlace de búsqueda avanzada. if (strpos($html, 'fea-search-bar') === false) return $html; $adv_url = home_url('/buscar/'); if (function_exists('pll_current_language')) { $lang = pll_current_language(); $default = function_exists('pll_default_language') ? pll_default_language() : 'es'; if ($lang && $lang !== $default) { // Intenta obtener la página /buscar traducida $page = get_page_by_path('buscar'); if ($page) { $tl = function_exists('pll_get_post') ? pll_get_post($page->ID, $lang) : 0; if ($tl) $adv_url = get_permalink($tl); } } } $label = esc_html(fea_adv_t('search_advanced')); $link = ''; // Insertamos el enlace justo al final del bloque .fea-search-bar (cerrando el div externo) // fea-search.php genera: // Reemplazamos la ÚLTIMA ocurrencia del cierre del div de .fea-search-bar $marker = ''; $pos = strpos($html, 'fea-search-bar'); if ($pos !== false) { // Buscamos el que cierra .fea-search-bar (el wrapper externo) // La estructura es: // Hay dos : uno cierra el form y otro cierra .fea-search-bar // Usamos una sustitución segura: buscamos el patrón exacto del cierre $html = preg_replace('#()#', '$1' . $link, $html, 1); } return $html; }, 25, 2); // ───────────────────────────────────────────────────────────────── // Página /buscar — inyectar formulario vía the_content // ───────────────────────────────────────────────────────────────── add_filter('the_content', function (string $content): string { if (!is_page('buscar')) return $content; // Envoltura: título + formulario + contenido original de la página $form = fea_adv_form_html(); return $form . '
' . $content . '
'; }); // Nota: la página /buscar se crea con scripts/create_buscar_page.php (ya ejecutado). // El formulario se inyecta en the_content (hook arriba) y vía render_block en search. // ───────────────────────────────────────────────────────────────── // CSS // ───────────────────────────────────────────────────────────────── add_action('wp_head', function (): void { if (!is_search() && !is_page('buscar') && empty($_GET['fea_author']) && empty($_GET['fea_cat']) && empty($_GET['fea_cita']) && empty($_GET['fea_date_from']) && empty($_GET['fea_date_to'])) return; ?>