1, 'category__in' => [$cat], 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', 'suppress_filters' => false, ]); if (!$cartas) return $cache[$lang] = 0; $carta_id = (int) $cartas[0]->ID; if ($lang !== 'es' && function_exists('pll_get_post')) { $trans = pll_get_post($carta_id, $lang); if ($trans) $carta_id = (int) $trans; } return $cache[$lang] = $carta_id; } /** * Parsea el HTML de la carta y devuelve los post_ids agrupados por sección. */ function fea_parse_carta_sections($carta_id) { static $mem = []; $carta_id = (int) $carta_id; if (!$carta_id) return []; if (isset($mem[$carta_id])) return $mem[$carta_id]; $tk = 'fea_carta_sections_' . $carta_id; $cached = get_transient($tk); if (is_array($cached)) return $mem[$carta_id] = $cached; $post = get_post($carta_id); if (!$post) return $mem[$carta_id] = []; $sections = fea_extract_sections_from_html($post->post_content); set_transient($tk, $sections, 15 * MINUTE_IN_SECONDS); return $mem[$carta_id] = $sections; } /** * Extrae secciones del HTML. Pública para tests/CLI. */ function fea_extract_sections_from_html($html) { $section_patterns = [ 'evangelio' => '/Evangelio\s+y\s+comentarios\s+al\s+Evangelio/iu', 'articulos' => '/Art[ií]culos\s+seleccionados\s+para\s+la\s+semana/iu', 'eucaristia' => '/Para\s+unas\s+eucarist[ií]as\s+m[áa]s\s+participativas/iu', 'multimedia' => '/Material\s+multimedia/iu', 'effa' => '/Escuela\s+EFFA/iu', ]; $positions = []; foreach ($section_patterns as $slug => $regex) { if (preg_match($regex, $html, $m, PREG_OFFSET_CAPTURE)) { $positions[$slug] = $m[0][1]; } } if (empty($positions)) return []; asort($positions); $slugs = array_keys($positions); $offsets = array_values($positions); $offsets[] = strlen($html); $sections = []; for ($i = 0; $i < count($slugs); $i++) { $segment = substr($html, $offsets[$i], $offsets[$i+1] - $offsets[$i]); $ids = fea_resolve_links_in_html($segment); if ($ids) $sections[$slugs[$i]] = $ids; } return $sections; } /** * Extrae los href de un fragmento HTML y los resuelve a wp_posts.ID. */ function fea_resolve_links_in_html($html) { if (!preg_match_all('/href=["\']([^"\']+)["\']/i', $html, $m)) return []; $ids = []; $seen = []; foreach ($m[1] as $url) { $pid = fea_url_to_post_id($url); if ($pid && !isset($seen[$pid])) { $seen[$pid] = true; $ids[] = $pid; } } return $ids; } /** * Resuelve una URL (WP o Joomla legacy) a wp_posts.ID o null. */ function fea_url_to_post_id($url) { global $wpdb; // Joomla legacy: /item/-...html if (preg_match('~/item/(\d+)-[^/"]+\.html~i', $url, $m)) { $k2 = (int) $m[1]; if ($k2 > 0) { $pid = $wpdb->get_var($wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_fgj2wp_old_k2_id' AND meta_value=%s LIMIT 1", (string) $k2 )); if ($pid) return (int) $pid; } return null; } // Enlace interno WP: deriva el slug del path, relativo al home. Agnóstico al // entorno → funciona en local (home en .../fea) y en prod (home en la raíz). // No depende de un prefijo /fea/ hardcodeado (issue #91). $host = wp_parse_url($url, PHP_URL_HOST); $home_host = wp_parse_url(home_url(), PHP_URL_HOST); if ($host && $home_host && strcasecmp($host, $home_host) !== 0) { return null; // host externo → no es un artículo nuestro } $path = (string) wp_parse_url($url, PHP_URL_PATH); if ($path === '') return null; $home_path = rtrim((string) wp_parse_url(home_url('/'), PHP_URL_PATH), '/'); if ($home_path !== '' && strpos($path, $home_path . '/') === 0) { $path = substr($path, strlen($home_path)); } $seg = explode('/', trim($path, '/')); $slug = $seg[0] ?? ''; if ($slug === '' || in_array($slug, ['wp-admin','wp-content','category','tag','author','page','en','fr','it','pt'], true)) { return null; } $pid = $wpdb->get_var($wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_name=%s AND post_status='publish' AND post_type='post' ORDER BY post_date DESC LIMIT 1", $slug )); return $pid ? (int) $pid : null; } /** * Devuelve los WP_Post objects de una sección de la carta vigente, * o array vacío si no hay carta o no hay links resueltos en esa sección. */ function fea_carta_section_posts($section_slug, $lang = null) { $lang = $lang ?: (function_exists('fea_current_lang') ? fea_current_lang() : 'es'); // El parser de secciones reconoce las cabeceras SOLO en español // (fea_extract_sections_from_html). Las cartas traducidas tienen las // cabeceras en su idioma → 0 secciones. Por eso parseamos SIEMPRE la // carta ES y luego mapeamos cada link a su traducción del idioma destino. $carta_id = fea_get_current_carta_id($lang); if (!$carta_id) return []; $carta_es = $carta_id; if ($lang !== 'es' && function_exists('pll_get_post')) { $es = pll_get_post($carta_id, 'es'); if ($es) $carta_es = (int) $es; } $sections = fea_parse_carta_sections($carta_es); $ids = $sections[$section_slug] ?? []; if (!$ids) return []; $posts = []; foreach ($ids as $pid) { // Mapear el artículo ES a su traducción del idioma de la portada. // Si no hay traducción, se mantiene el ES (degradación elegante). if ($lang !== 'es' && function_exists('pll_get_post')) { $tr = pll_get_post((int) $pid, $lang); if ($tr) $pid = (int) $tr; } $p = get_post($pid); if ($p && $p->post_status === 'publish') $posts[] = $p; } return $posts; } /** * Invalida transients de secciones al guardar/editar un post. */ add_action('save_post_post', function($post_id, $post, $update) { $cats = wp_get_post_categories($post_id); $watch = [6, 21, 22]; if (array_intersect($cats, $watch)) { global $wpdb; $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_fea_carta_sections_%'"); $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_fea_carta_sections_%'"); } }, 10, 3);