is_main_query()) return; if (!function_exists('pll_current_language') || !function_exists('pll_get_post')) return; // Solo actuar cuando WP cree que está en el home/front page if (!$query->is_home() && !$query->is_front_page()) return; $lang = pll_current_language(); if (!$lang || $lang === 'es') return; $translated_id = pll_get_post(26542, $lang); if (!$translated_id) return; // Forzar carga de la página traducida como si fuera la portada estática $query->set('page_id', $translated_id); $query->set('post_type', 'page'); $query->is_home = false; $query->is_front_page = true; $query->is_singular = true; $query->is_page = true; }, 1); // ── Multimedia: 30 entradas por página en su archivo (#63) ── add_action('pre_get_posts', function(WP_Query $query) { if (is_admin() || !$query->is_main_query()) return; if ($query->is_category('multimedia')) { $query->set('posts_per_page', 30); } }); // ── Tablón de anuncios (#97): ocultar del listado los anuncios de más de 12 meses ── // Ventana móvil, no destructiva: los posts siguen publicados/accesibles por URL; solo // se excluyen del archivo de la categoría para que el Tablón no parezca desactualizado. // Cubre las 5 categorías Polylang (ES + en/fr/it/pt). add_action('pre_get_posts', function(WP_Query $query) { if (is_admin() || !$query->is_main_query()) return; if ($query->is_category(['tablon-de-anuncios', 'tablon-de-anuncios-en', 'tablon-de-anuncios-fr', 'tablon-de-anuncios-it', 'tablon-de-anuncios-pt'])) { $query->set('date_query', [[ 'after' => date('Y-m-d', strtotime('-12 months')), 'inclusive' => true, ]]); } }); // ── Normalizar títulos (TODO CAPS legacy → frase) en todo el front #63 #73 ── // Mismo criterio que la portada (fea_title). Cubre listados/búsqueda/home, el artículo // (single), el /SEO y los feeds. El wp-admin se deja SIN tocar para que el editor // vea el dato real (en mayúsculas) al editar. add_filter('the_title', function($title, $post_id = 0) { if (is_admin() || !function_exists('fea_title')) return $title; // Artículo individual: solo el título del propio post mostrado (no widgets/relacionados). if (is_singular()) { if (in_the_loop() || (int) $post_id === (int) get_queried_object_id()) { return fea_title($title); } return $title; } // Listados (incluida portada) dentro del loop. if ((is_archive() || is_search() || is_home() || is_front_page()) && in_the_loop()) { return fea_title($title); } return $title; }, 20, 2); // <title> del documento — núcleo WP (cuando Yoast no lo sobrescribe). add_filter('document_title_parts', function($parts) { if (is_admin() || !function_exists('fea_title')) return $parts; if (!empty($parts['title'])) $parts['title'] = fea_title($parts['title']); return $parts; }, 20); // <title>/OG/Twitter vía Yoast: normaliza solo la porción del título del post. $fea_seo_title = function($title) { if (is_admin() || !is_singular() || !function_exists('fea_title')) return $title; $raw = get_post_field('post_title', get_queried_object_id()); if ($raw && mb_strpos($title, $raw) !== false) { $title = str_replace($raw, fea_title($raw), $title); } return $title; }; add_filter('wpseo_title', $fea_seo_title, 20); add_filter('wpseo_opengraph_title', $fea_seo_title, 20); add_filter('wpseo_twitter_title', $fea_seo_title, 20); // Título del item en feeds RSS. add_filter('the_title_rss', function($title) { return function_exists('fea_title') ? fea_title($title) : $title; }, 20); // Asegura que option_page_on_front devuelve la página traducida al idioma actual // (necesario para is_front_page() y para que el template FSE lo reconozca) add_filter('option_page_on_front', function($value) { if (!function_exists('pll_current_language') || !function_exists('pll_get_post')) return $value; static $running = false; if ($running) return $value; // evitar recursión $running = true; $lang = pll_current_language(); $running = false; if (!$lang || $lang === 'es') return $value; $translated = pll_get_post((int) $value, $lang); return $translated ?: $value; }); // ── Traducir links de categoría "Esta semana" / "Semana pasada" al idioma actual ─ // Las categorías traducidas EN/FR/IT/PT dan 404 (bug WP_Tax_Query AND 0=1 con Polylang). // Solución: redirigir ES cat URL → post traducido directamente (no a la categoría). // Para "Otras semanas" (cartas-de-otras-semanas, count=729): se deja como está. add_action('wp_footer', function() { if (!function_exists('pll_current_language') || !function_exists('pll_get_post')) return; $lang = pll_current_language(); if (!$lang || $lang === 'es') return; global $wpdb; $map = []; // Categorías de carta única (count=1) → redirigir al post traducido $single_carta_cats = [ 'cartasemana' => 6, 'carta-semana-pasada' => 22, ]; foreach ($single_carta_cats as $slug => $es_cat_id) { // term_taxonomy_id de la categoría ES (bypassa Polylang) $tt_id = (int) $wpdb->get_var($wpdb->prepare( "SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE term_id=%d AND taxonomy='category'", $es_cat_id )); if (!$tt_id) continue; // Post ES más reciente en esa categoría $es_post_id = (int) $wpdb->get_var($wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->term_relationships} tr ON p.ID=tr.object_id WHERE tr.term_taxonomy_id=%d AND p.post_status='publish' ORDER BY p.post_date DESC LIMIT 1", $tt_id )); if (!$es_post_id) continue; // Post traducido $trans_id = pll_get_post($es_post_id, $lang); if (!$trans_id) continue; $trans_url = get_permalink($trans_id); if ($trans_url) { $map[home_url('/category/' . $slug . '/')] = $trans_url; } } if (empty($map)) return; ?> <script> (function(){ var map = <?php echo json_encode($map); ?>; document.querySelectorAll('a[href]').forEach(function(a){ var clean = a.href.split('?')[0]; if (map[clean]) a.href = map[clean]; }); })(); </script> <?php }, 25); // ── Foto de perfil de autor via ACF (campo en perfil de usuario) ────────── add_action('acf/init', function() { if (!function_exists('acf_add_local_field_group')) return; acf_add_local_field_group([ 'key' => 'group_user_foto_perfil', 'title' => 'Foto de perfil', 'fields' => [[ 'key' => 'field_user_foto_perfil', 'label' => 'Foto', 'name' => 'foto_perfil', 'type' => 'image', 'instructions' => 'Sube una foto cuadrada del autor (mínimo 100×100px).', 'return_format' => 'url', 'preview_size' => 'thumbnail', 'upload_folder' => 'autores', ]], 'location' => [[ ['param' => 'user_form', 'operator' => '==', 'value' => 'all'], ]], ]); }); // Usar la foto del autor en lugar del Gravatar cuando existe // Lee el attachment ID guardado en el meta 'foto_perfil' (campo ACF) add_filter('get_avatar_url', function($url, $id_or_email, $args) { $user_id = null; if (is_numeric($id_or_email)) $user_id = (int) $id_or_email; elseif ($id_or_email instanceof WP_User) $user_id = $id_or_email->ID; elseif (is_string($id_or_email)) { $user = get_user_by('email', $id_or_email); if ($user) $user_id = $user->ID; } if (!$user_id) return $url; $attach_id = get_user_meta($user_id, 'foto_perfil', true); if ($attach_id) { $foto = wp_get_attachment_image_url((int) $attach_id, 'full'); if ($foto) return $foto; } return $url; }, 10, 3); // ── Ordenar por fecha los resultados del buscador ACF en campos de portada ─ add_filter('acf/fields/relationship/query/key=field_portada_articulos', function($args) { $args['orderby'] = 'date'; $args['order'] = 'DESC'; return $args; }); add_filter('acf/fields/relationship/query/key=field_portada_multimedia', function($args) { $args['orderby'] = 'date'; $args['order'] = 'DESC'; return $args; }); // ── Campos ACF para la portada ──────────────────────────────────────────── add_action('acf/init', function() { if (!function_exists('acf_add_local_field_group')) return; $front_page_id = (int) get_option('page_on_front'); acf_add_local_field_group([ 'key' => 'group_portada_fea', 'title' => 'Contenido de la portada', 'fields' => [ [ 'key' => 'field_portada_articulos', 'label' => 'Artículos seleccionados', 'name' => 'portada_articulos', 'type' => 'relationship', 'instructions' => 'Elige los artículos que aparecerán en la portada esta semana (máx. 9). Puedes buscar por título.', 'post_type' => ['post'], 'post_status' => ['publish', 'draft'], 'filters' => ['search', 'taxonomy'], 'elements' => ['featured_image'], 'min' => 0, 'max' => 9, 'return_format' => 'object', 'query_args' => ['orderby' => 'date', 'order' => 'DESC'], ], [ 'key' => 'field_portada_multimedia', 'label' => 'Multimedia seleccionado', 'name' => 'portada_multimedia', 'type' => 'relationship', 'instructions' => 'Elige los vídeos o audios para la portada (máx. 4).', 'post_type' => ['post'], 'post_status' => ['publish', 'draft'], 'filters' => ['search'], 'elements' => ['featured_image'], 'min' => 0, 'max' => 4, 'return_format' => 'object', 'query_args' => ['orderby' => 'date', 'order' => 'DESC'], ], ], 'location' => [[ ['param' => 'page', 'operator' => '==', 'value' => (string) $front_page_id], ]], 'position' => 'normal', 'style' => 'default', 'label_placement' => 'top', ]); }); // ── Selector de idioma (dropdown con bandera + 2 letras) ───────────────── add_action('wp_head', function() { if (!function_exists('pll_the_languages')) return; ?> <style> #fea-lang-switcher { position: relative; display: inline-flex; align-items: center; font-family: inherit; z-index: 9999; } #fea-lang-btn { display: inline-flex; align-items: center; gap: 5px; background: none; border: 1px solid rgba(0,0,0,0.25); border-radius: 5px; padding: 4px 9px; cursor: pointer; font-size: 0.82rem; font-weight: 700; color: inherit; line-height: 1.4; white-space: nowrap; } #fea-lang-btn:hover { background: rgba(0,0,0,0.06); } #fea-lang-btn .arrow { font-size: 0.6rem; opacity: 0.6; } #fea-lang-dropdown { display: none; position: absolute; top: calc(100% + 4px); right: 0; background: #fff; border: 1px solid #ddd; border-radius: 7px; box-shadow: 0 6px 16px rgba(0,0,0,0.13); min-width: 90px; padding: 4px 0; list-style: none; margin: 0; } #fea-lang-switcher.open #fea-lang-dropdown { display: block; } #fea-lang-dropdown li { margin: 0; padding: 0; } #fea-lang-dropdown a { display: flex; align-items: center; gap: 7px; padding: 7px 14px; font-size: 0.82rem; font-weight: 600; text-decoration: none; color: #222; white-space: nowrap; } #fea-lang-dropdown a:hover { background: #f5f5f5; } #fea-lang-dropdown a[aria-current] { font-weight: 700; color: #000; background: #efefef; } #fea-lang-dropdown a.fea-lang-untrans { opacity: 0.45; } #fea-lang-dropdown a.fea-lang-untrans::after { content: "·"; margin-left: 4px; opacity: 0.7; } </style> <?php }); add_action('wp_footer', function() { if (!function_exists('pll_the_languages')) return; $flags = ['es' => '🇪🇸', 'en' => '🇬🇧', 'fr' => '🇫🇷', 'it' => '🇮🇹', 'pt' => '🇵🇹']; $langs = pll_the_languages(['raw' => 1, 'hide_if_no_translation' => 0]); if (!$langs) return; $current = array_filter($langs, fn($l) => $l['current_lang']); $current = $current ? array_values($current)[0] : null; $cur_slug = $current ? $current['slug'] : 'es'; $cur_flag = $flags[$cur_slug] ?? '🌐'; $cur_code = strtoupper($cur_slug); $items = ''; foreach ($langs as $l) { $flag = $flags[$l['slug']] ?? '🌐'; $code = strtoupper($l['slug']); $cur = $l['current_lang'] ? ' aria-current="true"' : ''; // Marcar los idiomas SIN traducción de esta página (Polylang enlaza al inicio del // idioma): se atenúan y avisan, para no hacer creer que traducen el contenido actual. $notr = !empty($l['no_translation']) && empty($l['current_lang']); $cls = $notr ? ' class="fea-lang-untrans"' : ''; $ttl = $notr ? ' title="Esta página no está traducida — irás al inicio en ' . esc_attr($code) . '"' : ''; $items .= '<li><a href="' . esc_url($l['url']) . '"' . $cur . $cls . $ttl . '>' . $flag . ' ' . $code . '</a></li>'; } ?> <div id="fea-lang-switcher" role="navigation" aria-label="Idioma / Language"> <button id="fea-lang-btn" aria-haspopup="listbox" aria-expanded="false" onclick="var s=this.closest('#fea-lang-switcher'),o=s.classList.toggle('open');this.setAttribute('aria-expanded',o)"> <?= $cur_flag ?> <?= $cur_code ?> <span class="arrow">▼</span> </button> <ul id="fea-lang-dropdown" role="listbox"><?= $items ?></ul> </div> <script> (function() { var sw = document.getElementById('fea-lang-switcher'); if (!sw) return; // Cerrar al clicar fuera document.addEventListener('click', function(e) { if (!sw.contains(e.target)) { sw.classList.remove('open'); document.getElementById('fea-lang-btn').setAttribute('aria-expanded', 'false'); } }); // Inyectar en el nav principal del header function inject() { // Buscar la barra de navegación principal (primer nivel dentro del header) var header = document.querySelector('header.wp-block-template-part'); if (!header) header = document.querySelector('header'); if (!header) return; // Buscar el div/group que contiene el menú de navegación var navGroup = header.querySelector('.wp-block-navigation__container'); if (!navGroup) navGroup = header.querySelector('nav'); if (!navGroup) { // Fallback: poner en el header con posición absoluta header.style.position = 'relative'; sw.style.cssText = 'position:absolute;top:50%;right:1.5rem;transform:translateY(-50%);'; header.appendChild(sw); return; } // Crear li wrapper para el nav var li = document.createElement('li'); li.className = 'wp-block-navigation-item'; li.style.cssText = 'display:flex;align-items:center;margin-left:0.5rem;'; li.appendChild(sw); // Insertar DESPUÉS del buscador (si existe) var searchItem = navGroup.querySelector('.wp-block-search'); if (searchItem) { // Subir hasta el li/div hermano directo del navGroup var anchor = searchItem; while (anchor.parentNode && anchor.parentNode !== navGroup) { anchor = anchor.parentNode; } anchor.parentNode.insertBefore(li, anchor.nextSibling); } else { navGroup.appendChild(li); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', inject); } else { inject(); } })(); </script> <?php }, 20); // ── Centrar bloque slider+librería (header template, todas las páginas) ─── add_action('wp_head', function() { ?> <style> /* El bloque de columnas con el slider usa márgenes negativos del FSE que lo desplazan. Lo forzamos a centrar con max-width explícito. */ .wp-block-columns:has(.wp-block-nextend-smartslider3) { max-width: min(var(--wp--style--global--wide-size, 1340px), 100%); margin-left: auto !important; margin-right: auto !important; box-sizing: border-box; padding-left: var(--wp--preset--spacing--30); padding-right: var(--wp--preset--spacing--30); } /* Ocultar columna librería en tablet */ @media (max-width: 900px) { .fea-slider-block .wp-block-column:last-child { display: none !important; } } /* Ocultar slider en móvil (banner superior sigue visible) */ @media (max-width: 600px) { .fea-slider-block { display: none !important; } } /* Buscador del header: reducir altura */ .wp-block-search__input { padding-top: 0.25rem !important; padding-bottom: 0.25rem !important; line-height: 1.3 !important; } .wp-block-search__button { padding-top: 0.25rem !important; padding-bottom: 0.25rem !important; } </style> <?php }); // ── Estilos ─────────────────────────────────────────────────────────────── add_action('wp_head', function() { if (!fea_is_front_page()) return; ?> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&display=swap" rel="stylesheet"> <style> /* ── Hero: Carta (izq) + Carrusel (der) en banda beige full-width ── */ .fea-hero-band { max-width: none !important; width: 100vw; margin-left: calc(50% - 50vw) !important; margin-right: calc(50% - 50vw) !important; background: linear-gradient(180deg, #efe9e1, #f4f0ea); border-bottom: 1px solid #e4ddd1; margin-bottom: 3rem; } .fea-hero-inner { max-width: 1180px; margin: 0 auto; padding: 2.75rem 28px; display: grid; grid-template-columns: minmax(260px, 400px) minmax(0, 1fr); gap: 2.5rem; align-items: center; } .fea-hero-text { min-width: 0; } .fea-hero-link { display: block; text-decoration: none; color: inherit; } .fea-hero-link:hover .fea-hero-title { text-decoration: underline; text-underline-offset: 4px; } .fea-section-label { display: inline-block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; color: #8b1a2e; margin-bottom: 0.7rem; } .fea-hero-title { font-family: 'Fraunces', Georgia, serif; font-size: clamp(2rem, 3.4vw, 3rem); font-weight: 600; line-height: 1.08; letter-spacing: -0.01em; margin: 0 0 1rem; color: #2a2320; } .fea-hero-meta { display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; color: #6f655c; } .fea-hero-cta { display: inline-block; margin-top: 1.25rem; background: #8b1a2e; color: #fff; font-weight: 600; font-size: 0.92rem; padding: 0.7rem 1.4rem; border-radius: 8px; } .fea-hero-slider { min-width: 0; overflow: hidden; } .fea-hero-slider .n2-ss-align { max-width: 100% !important; width: 100% !important; } @media (max-width: 860px) { .fea-hero-inner { grid-template-columns: 1fr; gap: 1.75rem; } } /* Ancho de las secciones de portada: contenedor centrado (aire a los lados) */ body.home .fea-section, .fea-front .fea-section { max-width: 1180px !important; margin-left: auto !important; margin-right: auto !important; } /* ── Secciones — dirección "Cálido editorial" (Mockup A, #57) ── */ .fea-section { margin-bottom: 3.75rem; padding-top: 0.5rem; } .fea-section-title { display: flex; align-items: center; gap: 1rem; font-family: 'Fraunces', Georgia, serif; font-size: 1.65rem; font-weight: 600; letter-spacing: -0.01em; text-transform: none; color: #2a2320; margin: 0 0 1.6rem; padding: 0; border: 0; } .fea-section-title::after { content: ''; display: inline-block; flex: 0 0 46px; height: 3px; background: #8b1a2e; border-radius: 2px; } .fea-section-head { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.6rem; } .fea-section-head .fea-section-title { flex: 1 1 auto; min-width: 0; margin-bottom: 0; } .fea-section-more { flex: 0 0 auto; color: #8b1a2e; font-size: 0.86rem; font-weight: 700; text-decoration: none; white-space: nowrap; } .fea-section-more:hover { text-decoration: underline; text-underline-offset: 3px; } @media (max-width: 480px) { .fea-section-head { gap: 0.65rem; } .fea-section-head .fea-section-title { font-size: 1.45rem; gap: 0.65rem; } .fea-section-head .fea-section-title::after { flex-basis: 28px; } .fea-section-more { font-size: 0.8rem; } } .fea-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.6rem; } @media (max-width: 980px) { .fea-grid { grid-template-columns: repeat(3, 1fr); } } @media (max-width: 680px) { .fea-grid { grid-template-columns: 1fr 1fr; } } @media (max-width: 420px) { .fea-grid { grid-template-columns: 1fr; } } .fea-card { background: #fff; border: 1px solid #efe7d8; border-radius: 14px; padding: 1.5rem 1rem; text-align: center; transition: transform .18s ease, box-shadow .18s ease; } .fea-card:hover { transform: translateY(-5px); box-shadow: 0 18px 36px -20px rgba(42,35,32,.45); } /* Ocultar visualmente el texto de accesibilidad del Smart Slider (nombres de fichero con guiones bajos) */ .n2-ss-slide--focus { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; } .fea-card-avatar-link { display: block; } .fea-card-avatar { box-shadow: 0 0 0 3px #f7ecd9, 0 0 0 4px #e7d7bb; transition: transform .2s ease; } .fea-card:hover .fea-card-avatar { transform: scale(1.05); } .fea-card-author { color: #8b1a2e; font-weight: 600; font-size: 0.9rem; margin: 0.95rem 0 0.5rem; line-height: 1.25; } .fea-card-title { font-size: 0.98rem; font-weight: 600; line-height: 1.32; margin: 0; } .fea-card-title a { text-decoration: none; color: #2a2320; } .fea-card-title a:hover { text-decoration: underline; text-underline-offset: 3px; } /* ── Footer portada: contener anchos, aire a los lados (no entre medias) ── */ /* Reduce el espaciado preset 80 (enorme) solo dentro del footer, sin tocar listas */ .fea-footer-portada { max-width: 1180px; margin-left: auto; margin-right: auto; --wp--preset--spacing--80: 3rem; } /* Las 3 imágenes (Librería / Noticias / Portal-EFFA): columnas iguales, aires iguales */ .fea-footer-portada .wp-block-columns { max-width: 900px; margin-left: auto; margin-right: auto; gap: 2.5rem !important; align-items: start; justify-content: center; } .fea-footer-portada .wp-block-columns .wp-block-column { flex: 1 1 0 !important; width: auto !important; min-width: 0; display: flex; flex-direction: column; align-items: center; text-align: center; } .fea-footer-portada .wp-block-columns .wp-block-column img { max-width: 100%; width: auto; height: auto; margin-left: auto; margin-right: auto; } /* Las columnas de enlaces: centrarlas (aire a los lados, no entre medias) */ .fea-footer-portada .wp-block-group.is-content-justification-space-between { justify-content: center !important; gap: 3.5rem !important; } </style> <?php }); // ── Slider del hero a prueba de fuego: llena la columna y, si es más estrecha // que el ancho base del slider (Smart Slider no baja de él), lo escala con transform ── add_action('wp_footer', function() { if (!fea_is_front_page()) return; ?> <script> (function () { var els = function () { return { c: document.querySelector('.fea-hero-slider'), s: document.querySelector('.fea-hero-slider .n2-ss-slider') }; }; var lastW = -1, busy = false, raf = 0; function apply() { var e = els(); if (!e.c || !e.s) return; // reset para medir el tamaño natural que decide Smart Slider e.s.style.transform = ''; e.s.style.transformOrigin = ''; e.c.style.height = ''; // que Smart Slider recalcule (crece hasta llenar la columna si cabe) busy = true; window.dispatchEvent(new Event('resize')); busy = false; requestAnimationFrame(function () { var avail = e.c.clientWidth; var rect = e.s.getBoundingClientRect(); if (rect.width > avail + 1) { // columna más estrecha que el slider → escalar var k = avail / rect.width; e.s.style.transformOrigin = 'top left'; e.s.style.transform = 'scale(' + k + ')'; e.c.style.height = Math.round(rect.height * k) + 'px'; } }); } function schedule() { cancelAnimationFrame(raf); raf = requestAnimationFrame(apply); } function onChange(force) { if (busy) return; var c = els().c; if (!c) return; var w = c.clientWidth; if (!force && w === lastW) return; // solo reaccionar a cambios de ANCHO (evita bucle por la altura) lastW = w; schedule(); } window.addEventListener('load', function () { onChange(true); setTimeout(function(){onChange(true);}, 300); setTimeout(function(){onChange(true);}, 800); }); window.addEventListener('resize', function () { onChange(false); }); if ('ResizeObserver' in window) { var c = els().c; if (c) new ResizeObserver(function () { onChange(false); }).observe(c); } })(); </script> <?php }, 99); // ── Estilos para listados/archivos (categoría, autor, fecha, búsqueda) #63 ── add_action('wp_head', function() { if (is_admin() || fea_is_front_page()) return; if (!(is_archive() || is_search() || is_home())) return; ?> <link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&display=swap" rel="stylesheet"> <style> /* Título del archivo: serif con barra granate (coherente con la portada) */ .wp-block-query-title { font-family: 'Fraunces', Georgia, serif !important; font-weight: 600 !important; color: #2a2320 !important; letter-spacing: -0.01em; display: flex; align-items: center; gap: 1rem; } .wp-block-query-title::after { content: ''; flex: 0 0 46px; height: 3px; background: #8b1a2e; border-radius: 2px; } /* Anchura coherente con la portada */ .wp-block-query.alignwide { max-width: 1180px !important; margin-left: auto !important; margin-right: auto !important; } /* Grid de tarjetas */ .fea-archive-grid { display: grid !important; grid-template-columns: repeat(3, 1fr) !important; gap: 1.5rem !important; } @media (max-width: 900px) { .fea-archive-grid { grid-template-columns: repeat(2, 1fr) !important; } } @media (max-width: 560px) { .fea-archive-grid { grid-template-columns: 1fr !important; } } .fea-archive-card { background: #fff; border: 1px solid #efe7d8; border-radius: 14px; padding: 1.4rem 1.3rem; height: 100%; display: flex; flex-direction: column; transition: transform .18s ease, box-shadow .18s ease; } .fea-archive-card:hover { transform: translateY(-4px); box-shadow: 0 18px 36px -20px rgba(42,35,32,.45); } .fea-archive-title { margin: 0 !important; } .fea-archive-title a { font-family: 'Fraunces', Georgia, serif; font-weight: 600; color: #2a2320; text-decoration: none; line-height: 1.25; } .fea-archive-card:hover .fea-archive-title a { color: #8b1a2e; } .fea-archive-date { margin: 0.45rem 0 0 !important; } .fea-archive-date a, .fea-archive-date { color: #8b1a2e !important; font-weight: 600; text-decoration: none; } .fea-archive-excerpt { margin: 0.6rem 0 0 !important; color: #6f655c; font-size: 0.92rem; line-height: 1.45; } .fea-archive-excerpt a { display: none; } </style> <?php }); // ── Ocultar título de página en todas las portadas ──────────────────────── add_action('wp_head', function() { if (!fea_is_front_page()) return; echo '<style>.wp-block-post-title { display:none !important; }</style>'; }, 20); // ── H1 semántico oculto para páginas sin título visible ─────────────────── add_action('wp_head', function() { ?> <style> .fea-sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; clip-path: inset(50%) !important; white-space: nowrap !important; border: 0 !important; } </style> <?php }, 20); add_filter('the_content', function($content) { if (is_admin() || !is_main_query() || !in_the_loop()) return $content; if (preg_match('/<h1\b/i', $content)) return $content; $title = ''; if (fea_is_front_page()) { $title = get_bloginfo('name') ?: 'Fe Adulta'; } elseif (fea_is_escuela_page()) { $title = 'Escuela de Formación en Fe Adulta'; } if (!$title) return $content; return '<h1 class="fea-sr-only fea-page-h1">' . esc_html($title) . '</h1>' . $content; }, 5); // ── Título <title> para páginas sin post_title propio (Escuela) ─────────── add_filter('document_title_parts', function($parts) { if (fea_is_escuela_page()) { $parts['title'] = 'Escuela de Formación en Fe Adulta'; } return $parts; }); // ── Ocultar metadatos de post en páginas estáticas ──────────────────────── add_action('wp_head', function() { if (!fea_hide_static_meta()) return; ?> <style> .wp-block-group:has(> .wp-block-avatar):has(.wp-block-post-author-name), .wp-block-post-author-name, .wp-block-post-terms.taxonomy-category { display: none !important; } </style> <?php }, 20); // ── Byline personalizado en artículos individuales ──────────────────────── // ── Byline personalizado: se gestiona desde el template FSE (ID 42359) ──── // El template wp_template 'single' ya contiene wp:avatar + wp:post-author-name // + wp:post-terms. Este hook solo añade los estilos necesarios. add_action('astra_single_header_bottom', function() { if (!is_single()) return; $author_id = (int) get_the_author_meta('ID'); $author_name = get_the_author_meta('display_name'); $avatar_url = get_avatar_url($author_id, ['size' => 48]); $author_url = get_author_posts_url($author_id); $cat_str = ''; $cats = get_the_category(); if ($cats) { $cat_url = get_category_link($cats[0]->term_id); $cat_str = '<a href="' . esc_url($cat_url) . '" class="fea-byline-cat">' . esc_html($cats[0]->name) . '</a>'; } echo '<div class="fea-byline">' . '<a href="' . esc_url($author_url) . '" class="fea-byline-avatar-link">' . '<img src="' . esc_url($avatar_url) . '" alt="' . esc_attr($author_name) . '" width="48" height="48" class="fea-byline-avatar">' . '</a>' . '<div class="fea-byline-info">' . '<a href="' . esc_url($author_url) . '" class="fea-byline-name">' . esc_html($author_name) . '</a>' . $cat_str . '</div>' . '</div>'; }); add_action('wp_head', function() { if (!is_single()) return; ?> <style> .fea-byline { display: flex; align-items: center; gap: 0.75rem; margin-top: 0.75rem; } .fea-byline-avatar-link { flex-shrink: 0; } .fea-byline-avatar { border-radius: 50%; display: block; } .fea-byline-info { display: flex; flex-direction: column; gap: 0.2rem; } .fea-byline-name { font-size: 0.9rem; font-weight: 600; color: #222; text-decoration: none; } .fea-byline-name:hover { text-decoration: underline; } .fea-byline-cat { font-size: 0.78rem; color: #888; text-decoration: none; } .fea-byline-cat:hover { text-decoration: underline; color: #555; } .wp-block-group:has(> .wp-block-avatar):has(.wp-block-post-author-name) { align-items: center; gap: 0.7rem; } .wp-block-group:has(> .wp-block-avatar):has(.wp-block-post-author-name) > .wp-block-avatar { flex: 0 0 54px; } .wp-block-group:has(> .wp-block-avatar):has(.wp-block-post-author-name) > .wp-block-avatar img { width: 54px !important; height: 54px !important; } .fea-post-date-inline { font-size: 0.74rem; line-height: 1.2; color: #888; margin-top: 0.08rem; } .fea-post-date-inline time { white-space: nowrap; } @media (max-width: 720px) { .wp-block-group:has(> .wp-block-avatar):has(.wp-block-post-author-name) > .wp-block-avatar { flex-basis: 50px; } .wp-block-group:has(> .wp-block-avatar):has(.wp-block-post-author-name) > .wp-block-avatar img { width: 50px !important; height: 50px !important; } } </style> <?php }); add_filter('render_block_core/post-terms', function($block_content, $block) { if (!is_single() || get_post_type() !== 'post') return $block_content; $taxonomy = $block['attrs']['term'] ?? ''; if ($taxonomy !== 'category') return $block_content; if (strpos($block_content, 'fea-post-date-inline') !== false) return $block_content; $date_attr = esc_attr(get_the_date('c')); $date_text = esc_html(ucfirst(wp_date(get_option('date_format'), get_post_timestamp()))); if ($date_text === '') return $block_content; return $block_content . '<div class="fea-post-date-inline"><time datetime="' . $date_attr . '">' . $date_text . '</time></div>'; }, 10, 2); // ── Helpers ─────────────────────────────────────────────────────────────── function fea_title(string $title): string { $lower = mb_strtolower($title, 'UTF-8'); $out = mb_strtoupper(mb_substr($lower, 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($lower, 1, null, 'UTF-8'); // Capitalizar también la primera letra tras separadores de cláusula (citas bíblicas // dobles "Isaías 5,1 / Filipenses 4,6", subtítulos "Título: Subtítulo", ¿…?, ¡…!). $out = preg_replace_callback('/([\/:¿¡] *)(\p{Ll})/u', function ($m) { return $m[1] . mb_strtoupper($m[2], 'UTF-8'); }, $out); return $out; } /** Lista de libros bíblicos (para avatar genérico de lecturas/eucaristías). #61 */ function fea_libros_biblicos(): array { return [ 'Nuevo Testamento','Antiguo Testamento', 'Génesis','Éxodo','Levítico','Números','Deuteronomio','Josué','Jueces','Rut', 'Samuel','Reyes','Crónicas','Esdras','Nehemías','Tobías','Judit','Ester','Macabeos', 'Job','Salmos','Salmo','Proverbios','Eclesiastés','Cantar','Sabiduría','Eclesiástico','Sirácide', 'Isaías','Jeremías','Lamentaciones','Baruc','Ezequiel','Daniel', 'Oseas','Joel','Amós','Abdías','Jonás','Miqueas','Nahúm','Habacuc','Sofonías','Ageo','Zacarías','Malaquías', 'Hechos','Romanos','Corintios','Gálatas','Efesios','Filipenses','Colosenses','Tesalonicenses', 'Timoteo','Tito','Filemón','Hebreos','Santiago','Pedro','Judas','Apocalipsis', ]; } /** Devuelve la abreviatura del evangelista si el texto es una cita de evangelio, o ''. #61 */ function fea_evangelista_de_texto(string $txt): string { if (preg_match('/^\s*(Mateo|Mt|Marcos|Mc|Lucas|Lc|Juan|Jn)\b\.?\s*\d/iu', $txt, $m)) { $k = mb_strtolower($m[1], 'UTF-8'); $map = ['mateo'=>'mateo-angel','mt'=>'mateo-angel','marcos'=>'marcos-leon','mc'=>'marcos-leon', 'lucas'=>'lucas-toro','lc'=>'lucas-toro','juan'=>'juan-aguila','jn'=>'juan-aguila']; return $map[$k] ?? ''; } return ''; } /** Devuelve 'antiguo-testamento' / 'nuevo-testamento' si el texto es esa firma, o ''. #66 */ function fea_testamento_de_texto(string $txt): string { $t = mb_strtolower(trim($txt), 'UTF-8'); if ($t === 'antiguo testamento') return 'antiguo-testamento'; if ($t === 'nuevo testamento') return 'nuevo-testamento'; return ''; } /** True si el texto empieza por un libro bíblico (cita) o es el nombre de un libro. #61 */ function fea_es_libro_biblico(string $txt): bool { $txt = trim($txt); foreach (fea_libros_biblicos() as $libro) { if (mb_strtolower($txt, 'UTF-8') === mb_strtolower($libro, 'UTF-8')) return true; if (preg_match('/^' . preg_quote($libro, '/') . '\b/iu', $txt)) return true; } return false; } /** * URL del avatar para un post de portada/listado. #61 * - Citas de evangelio (Mt/Mc/Lc/Jn + número) → símbolo del evangelista. * - Lecturas/eucaristías firmadas por un libro bíblico → Biblia. * - Resto → avatar real del autor. */ function fea_avatar_url(object $post, int $size, int $author_id, string $author_name): string { $base = content_url('uploads/avatares/evangelistas/'); // Solo la LECTURA en sí (cuyo título es la cita, p.ej. "Mateo 9, 36") lleva símbolo de // evangelista; los comentaristas humanos conservan su avatar aunque comenten ese evangelio. $title = (string) $post->post_title; // Las traducciones tienen el título en su idioma (Matteo, Geremia, Romani…) que no // reconocemos. Usamos el título del ORIGINAL ES, donde sí detectamos el libro → la // traducción hereda el mismo símbolo que el ES (#135). if (function_exists('pll_get_post_language') && pll_get_post_language($post->ID) !== 'es') { $es = function_exists('pll_get_post') ? pll_get_post($post->ID, 'es') : 0; if ($es) { $esp = get_post($es); if ($esp) $title = (string) $esp->post_title; } } if ($ev = fea_evangelista_de_texto($title)) return $base . $ev . '.svg'; // "Antiguo Testamento" / "Nuevo Testamento" llevan símbolo propio (no el genérico). #66 if ($t = fea_testamento_de_texto($title) ?: fea_testamento_de_texto($author_name)) return $base . $t . '.svg'; if (fea_es_libro_biblico($title) || fea_es_libro_biblico($author_name)) return $base . 'biblia.svg'; return get_avatar_url($author_id, ['size' => $size, 'default' => 'identicon']); } function fea_card(object $post): string { $author_id = $post->post_author; $author_name = get_the_author_meta('display_name', $author_id); $avatar_url = fea_avatar_url($post, 96, (int) $author_id, (string) $author_name); $url = get_permalink($post->ID); $title = fea_title($post->post_title); return '<article class="fea-card">' . '<a href="' . esc_url($url) . '" class="fea-card-avatar-link" aria-label="' . esc_attr($author_name) . '">' . '<span class="fea-card-avatar" style="display:block;width:84px;height:84px;border-radius:50%;overflow:hidden;margin:0 auto;">' . '<img src="' . esc_url($avatar_url) . '" alt="' . esc_attr($author_name) . '" width="84" height="84" class="fea-avatar" style="width:100%;height:100%;object-fit:cover;display:block;" loading="lazy">' . '</span>' . '</a>' . '<div class="fea-card-author">' . esc_html($author_name) . '</div>' . '<h3 class="fea-card-title"><a href="' . esc_url($url) . '">' . esc_html($title) . '</a></h3>' . '</article>'; } /** IDs de las páginas de portada en todos los idiomas. */ function fea_front_page_ids(): array { return [26542, 42756, 42757, 42758, 43889]; // ES=26542, PT=42756, IT=42757, FR=42758, EN=43889 (FR/PT corregidos #75) } /** True si la página actual es alguna de las portadas (multilingüe). */ function fea_is_front_page(): bool { if (is_front_page()) return true; $id = get_queried_object_id(); return $id && in_array($id, fea_front_page_ids(), true); } /** True para la landing de Escuela, que no muestra título visible. */ function fea_is_escuela_page(): bool { if (!is_page()) return false; $page = get_queried_object(); return $page instanceof WP_Post && $page->post_name === 'escuela'; } /** True para contenidos importados que funcionan como páginas institucionales. */ function fea_hide_static_meta(): bool { if (is_admin() || fea_is_front_page()) return false; $post = get_queried_object(); if (!$post instanceof WP_Post) return false; if ($post->post_type === 'page') return true; $slugs = [ 'colaboradores', 'contactar', 'multimedia', 'ayuda', 'video-tutorial', 'como-usar-el-buscador-avanzado', 'portal', 'paraponeraldialafe', 'alta', 'alta-en-effa', 'regala', 'catalogo-de-publicaciones-2018', 'nueva-politica-de-privacidad', ]; return in_array($post->post_name, $slugs, true); } /** Devuelve el idioma actual de Polylang, o 'es' si no está activo. */ function fea_current_lang(): string { return (function_exists('pll_current_language') ? pll_current_language() : null) ?: 'es'; } /** Etiquetas de sección traducidas por idioma. */ function fea_labels(): array { $all = [ 'es' => [ 'carta' => 'Carta de la semana', 'evangelio' => 'Comentarios al evangelio', 'articulos' => 'Artículos de esta semana', 'eucaristia' => 'Para una eucaristía más participativa', 'multimedia' => 'Multimedia', ], 'en' => [ 'carta' => 'Letter of the week', 'evangelio' => 'Gospel commentary', 'articulos' => 'Articles of the week', 'eucaristia' => 'For a more participatory Eucharist', 'multimedia' => 'Multimedia', ], 'fr' => [ 'carta' => 'Lettre de la semaine', 'evangelio' => 'Commentaires de l\'évangile', 'articulos' => 'Articles de la semaine', 'eucaristia' => 'Pour une eucharistie plus participative', 'multimedia' => 'Multimédia', ], 'it' => [ 'carta' => 'Lettera della settimana', 'evangelio' => 'Commenti al vangelo', 'articulos' => 'Articoli della settimana', 'eucaristia' => 'Per una eucaristia più partecipativa', 'multimedia' => 'Multimedia', ], 'pt' => [ 'carta' => 'Carta da semana', 'evangelio' => 'Comentários ao evangelho', 'articulos' => 'Artigos da semana', 'eucaristia' => 'Para uma eucaristia mais participativa', 'multimedia' => 'Multimédia', ], ]; return $all[fea_current_lang()] ?? $all['es']; } /** * ID de la página de portada para el idioma actual. */ function fea_front_page_id(): int { $es_front = 26542; $lang = fea_current_lang(); if ($lang === 'es') return $es_front; if (function_exists('pll_get_post')) { $translated = pll_get_post($es_front, $lang); if ($translated) return (int) $translated; } return $es_front; } /** * Traduce un term_id de categoría ES al idioma actual. * Si no hay traducción disponible, devuelve el ID original. */ function fea_cat(int $es_cat_id): int { $lang = fea_current_lang(); if ($lang === 'es') return $es_cat_id; if (function_exists('pll_get_term')) { $translated = pll_get_term($es_cat_id, $lang); if ($translated) return (int) $translated; } return $es_cat_id; } // ── Enlace "Evangelio del día · fecha" ──────────────────────────────────── // Píldora discreta, estilo "fecha con contenido", a la página del Evangelio de // cada día. Se inserta dentro del hero de la carta (portada). Multiidioma. function fea_eed_link_html() { $tz = new DateTimeZone('Europe/Madrid'); $now = new DateTimeImmutable('now', $tz); $d = (int) $now->format('j'); $m = (int) $now->format('n'); $lang = function_exists('fea_current_lang') ? fea_current_lang() : 'es'; $LBL = ['es'=>'Evangelio del día','en'=>'Gospel of the day','fr'=>'Évangile du jour','it'=>'Vangelo del giorno','pt'=>'Evangelho do dia']; $MES = [ 'es'=>[1=>'enero','febrero','marzo','abril','mayo','junio','julio','agosto','septiembre','octubre','noviembre','diciembre'], 'en'=>[1=>'January','February','March','April','May','June','July','August','September','October','November','December'], 'fr'=>[1=>'janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'], 'it'=>[1=>'gennaio','febbraio','marzo','aprile','maggio','giugno','luglio','agosto','settembre','ottobre','novembre','dicembre'], 'pt'=>[1=>'janeiro','fevereiro','março','abril','maio','junho','julho','agosto','setembro','outubro','novembro','dezembro'], ]; $L = isset($MES[$lang]) ? $lang : 'es'; $mes = $MES[$L][$m]; if ($L === 'es' || $L === 'pt') $fecha = $d . ' de ' . $mes; elseif ($L === 'en') $fecha = $mes . ' ' . $d; else $fecha = $d . ' ' . $mes; // fr, it // get_posts usa suppress_filters=true → Polylang no filtra y siempre // devuelve la página ES. Resolvemos la traducción del idioma actual. $pg = get_posts(['name'=>'evangelio-de-cada-dia','post_type'=>'post','post_status'=>'publish','numberposts'=>1]); $pid = $pg ? (int) $pg[0]->ID : 0; if ($pid && $lang !== 'es' && function_exists('pll_get_post')) { $tr = pll_get_post($pid, $lang); if ($tr) $pid = (int) $tr; } $url = $pid ? get_permalink($pid) : home_url('/evangelio-de-cada-dia/'); $css = '<style>' . '.fea-eed-link{margin:1.6rem 0 0}' . '.fea-eed-link a{display:inline-flex;align-items:center;gap:.5rem;color:#8b1a2e;text-decoration:none;font-size:.92rem;line-height:1}' . '.fea-eed-link a:hover{text-decoration:underline;text-underline-offset:3px}' . '.fea-eed-link .lbl{font-weight:700}' . '.fea-eed-link .sep{color:#caa9b0}' . '.fea-eed-link .fecha{color:#6f655c}' . '.fea-eed-link .arrow{color:#8b1a2e;font-weight:700}' . '</style>'; return $css . '<div class="fea-eed-link"><a href="' . esc_url($url) . '">' . '<span class="lbl">' . esc_html($LBL[$L]) . '</span>' . '<span class="sep">·</span>' . '<span class="fecha">' . esc_html($fecha) . '</span>' . '<span class="arrow">›</span></a></div>'; } // ── Shortcode: [fea_carta_semana_hero] ──────────────────────────────────── add_shortcode('fea_carta_semana_hero', function() { $lang = fea_current_lang(); $labels = fea_labels(); $cartas = get_posts([ 'posts_per_page' => 1, 'category__in' => [fea_cat(6)], 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ]); if (!$cartas) return ''; $c = $cartas[0]; $url = get_permalink($c->ID); $fecha = date_i18n('j F Y', strtotime($c->post_date)); $author_name = get_the_author_meta('display_name', $c->post_author); $avatar_url = get_avatar_url($c->post_author, ['size' => 32, 'default' => 'identicon']); $slider = do_shortcode('[smartslider3 slider="2"]'); return '<section class="fea-hero-band"><div class="fea-hero-inner">' . '<div class="fea-hero-text">' . '<a href="' . esc_url($url) . '" class="fea-hero-link">' . '<span class="fea-section-label">' . esc_html($labels['carta']) . '</span>' . '<h2 class="fea-hero-title">' . esc_html(fea_title($c->post_title)) . '</h2>' . '<div class="fea-hero-meta">' . '<img src="' . esc_url($avatar_url) . '" alt="' . esc_attr($author_name) . '" width="32" height="32" class="fea-avatar" style="width:32px;height:32px;border-radius:50%;object-fit:cover;">' . '<span>' . esc_html($author_name) . ' · ' . $fecha . '</span>' . '</div>' . '<span class="fea-hero-cta">' . esc_html($labels['carta'] ? 'Leer la carta' : 'Leer la carta') . ' →</span>' . '</a>' . fea_eed_link_html() . '</div>' . '<div class="fea-hero-slider">' . $slider . '</div>' . '</div></section>'; }); // ── Shortcode: [fea_articulos_semana] ───────────────────────────────────── // Fuente principal: links de la sección "Artículos seleccionados" de la carta vigente. // Fallbacks: ACF portada_articulos → últimos por categoría. add_shortcode('fea_articulos_semana', function($atts) { $lang = fea_current_lang(); $labels = fea_labels(); $atts = shortcode_atts(['titulo' => $labels['articulos']], $atts); $page = fea_front_page_id(); // 1) Fuente principal: sección "Artículos seleccionados" de la carta vigente $posts = function_exists('fea_carta_section_posts') ? fea_carta_section_posts('articulos', $lang) : []; // 2) Fallback ACF (solo ES) if (empty($posts) && $lang === 'es' && function_exists('get_field')) { $seleccion = get_field('portada_articulos', $page) ?: []; foreach ($seleccion as $p) { if ($p->post_status === 'publish') $posts[] = $p; } } // 3) Fallback: últimos artículos en el idioma actual if (empty($posts)) { $posts = get_posts([ 'posts_per_page' => 9, 'category__in' => [fea_cat(1650)], 'category__not_in' => array_map('fea_cat', [6, 21, 22, 23, 26, 58, 40, 1645, 1646, 1647, 1648, 1649, 1651, 1652]), 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ]); } if (!$posts) return ''; $html = '<section class="fea-section">' . '<h2 class="fea-section-title">' . esc_html($atts['titulo']) . '</h2>' . '<div class="fea-grid">'; foreach ($posts as $post) $html .= fea_card($post); return $html . '</div></section>'; }); // ── Shortcode: [fea_evangelio] ──────────────────────────────────────────── // Fuente principal: sección "Evangelio y comentarios" de la carta vigente. // Fallback: editorial cat 1646 + comentarios cat 1647 por fecha. add_shortcode('fea_evangelio', function($atts) { $lang = fea_current_lang(); $labels = fea_labels(); $atts = shortcode_atts(['titulo' => $labels['evangelio']], $atts); $posts = function_exists('fea_carta_section_posts') ? fea_carta_section_posts('evangelio', $lang) : []; if (empty($posts)) { $editorial = get_posts([ 'posts_per_page' => 1, 'category__in' => [fea_cat(1646)], 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ]); $comentarios = get_posts([ 'posts_per_page' => 6, 'category__in' => [fea_cat(1647)], 'category__not_in' => [fea_cat(1646)], 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ]); $posts = array_merge($editorial, $comentarios); } if (!$posts) return ''; $html = '<section class="fea-section">' . '<h2 class="fea-section-title">' . esc_html($atts['titulo']) . '</h2>' . '<div class="fea-grid">'; foreach ($posts as $post) $html .= fea_card($post); return $html . '</div></section>'; }); // ── Shortcode: [fea_evangelio_diario] ───────────────────────────────────── // "El Evangelio de cada día": dos devocionales diarios indexados por día del año. // · A la fuente cada día (texto, Fray Marcos) → categoría term_id 14 // · Otro evangelio es posible (vídeo YouTube) → categoría term_id 15 // Los posts están titulados "D mes" (ej. "21 junio"). Se muestra el de HOY // (zona horaria del sitio) o el día indicado por ?fed=M-D, con pestañas para // que el usuario elija formato y navegación día anterior / siguiente. // Contenido solo en ES (devocional sin traducción) → categorías 14/15 fijas. add_shortcode('fea_evangelio_diario', function($atts) { $MESES = [1=>'enero',2=>'febrero',3=>'marzo',4=>'abril',5=>'mayo',6=>'junio', 7=>'julio',8=>'agosto',9=>'septiembre',10=>'octubre',11=>'noviembre',12=>'diciembre']; // Día litúrgico de referencia: España (la web es ES); el servidor va en UTC. $tz = new DateTimeZone('Europe/Madrid'); $now = new DateTimeImmutable('now', $tz); $m = (int) $now->format('n'); $d = (int) $now->format('j'); if (!empty($_GET['fed']) && preg_match('/^(\d{1,2})-(\d{1,2})$/', $_GET['fed'], $mm)) { $gm = (int) $mm[1]; $gd = (int) $mm[2]; if ($gm >= 1 && $gm <= 12 && $gd >= 1 && $gd <= 31) { $m = $gm; $d = $gd; } } $titulo_dia = $d . ' ' . $MESES[$m]; // "21 junio" $find = function($cat) use ($titulo_dia) { global $wpdb; $id = $wpdb->get_var($wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id WHERE tt.taxonomy = 'category' AND tt.term_id = %d AND p.post_type = 'post' AND p.post_status = 'publish' AND LOWER(TRIM(p.post_title)) = %s ORDER BY p.ID ASC LIMIT 1", $cat, $titulo_dia)); return $id ? get_post((int) $id) : null; }; $texto = $find(14); $video = $find(15); // navegación por día del calendario (año irrelevante; usamos un año bisiesto fijo) $base = get_permalink(); $cur = DateTimeImmutable::createFromFormat('!Y-n-j', '2024-' . $m . '-' . $d, $tz); $prev = $cur->modify('-1 day'); $next = $cur->modify('+1 day'); $lbl = function($dt) use ($MESES) { return ((int)$dt->format('j')) . ' ' . substr($MESES[(int)$dt->format('n')], 0, 3); }; $href = function($dt) use ($base) { return esc_url(add_query_arg('fed', $dt->format('n') . '-' . $dt->format('j'), $base)); }; $render = function($post, $tipo) { if (!$post) { return '<p class="fea-eed-empty">No hay ' . ($tipo === 'video' ? 'vídeo' : 'texto') . ' disponible para este día.</p>'; } $c = apply_filters('the_content', $post->post_content); return '<div class="fea-eed-art">' . $c . '</div>'; }; $css = '<style>' . '.fea-eed{max-width:760px;margin:0 auto}' . '.fea-eed-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;margin:.2rem 0 1.1rem}' . '.fea-eed-date{font-size:1.15rem;color:#8b1a2e;margin:0;text-align:center;flex:1}' . '.fea-eed-nav{white-space:nowrap;color:#8b1a2e;text-decoration:none;font-weight:600;font-size:.9rem;padding:.3rem .6rem;border:1px solid #e2cdd2;border-radius:8px}' . '.fea-eed-nav:hover{background:#faf3f4}' . '.fea-eed-tabs input{position:absolute;opacity:0;pointer-events:none}' . '.fea-eed-labels{display:flex;gap:.5rem;border-bottom:2px solid #e2cdd2;margin-bottom:1.2rem}' . '.fea-eed-labels label{flex:1;text-align:center;cursor:pointer;padding:.6rem 1rem;font-weight:700;color:#888;border:2px solid transparent;border-bottom:none;border-radius:8px 8px 0 0;margin-bottom:-2px}' . '#fed-texto:checked~.fea-eed-labels label[for=fed-texto],' . '#fed-video:checked~.fea-eed-labels label[for=fed-video]{color:#8b1a2e;border-color:#e2cdd2;background:#faf3f4}' . '.fea-eed-panel{display:none}' . '#fed-texto:checked~.fea-eed-panel-texto{display:block}' . '#fed-video:checked~.fea-eed-panel-video{display:block}' . '.fea-eed-art iframe{max-width:100%}' . '.fea-eed-panel-video .fea-eed-art iframe{width:100%;max-width:600px;height:auto;aspect-ratio:16/9;display:block;margin:0 auto}' . '.fea-eed-art h1{font-size:1.4rem;line-height:1.3;text-align:center}' . '.fea-eed-empty{color:#888;font-style:italic;text-align:center;padding:1.5rem}' . '.wp-block-post-title{text-align:center}' . '</style>'; $h = $css . '<div class="fea-eed">'; $h .= '<div class="fea-eed-head">' . '<a class="fea-eed-nav" href="' . $href($prev) . '">‹ ' . esc_html($lbl($prev)) . '</a>' . '<h2 class="fea-eed-date">' . esc_html($d . ' de ' . $MESES[$m]) . '</h2>' . '<a class="fea-eed-nav" href="' . $href($next) . '">' . esc_html($lbl($next)) . ' ›</a>' . '</div>'; $h .= '<div class="fea-eed-tabs">' . '<input type="radio" name="fed-tab" id="fed-texto" checked>' . '<input type="radio" name="fed-tab" id="fed-video">' . '<div class="fea-eed-labels">' . '<label for="fed-texto">A la fuente cada día</label>' . '<label for="fed-video">Otro evangelio es posible (vídeo)</label>' . '</div>' . '<div class="fea-eed-panel fea-eed-panel-texto">' . $render($texto, 'texto') . '</div>' . '<div class="fea-eed-panel fea-eed-panel-video">' . $render($video, 'video') . '</div>' . '</div></div>'; return $h; }); // ── Shortcode: [fea_eucaristia] ─────────────────────────────────────────── // Fuente principal: sección "Para unas eucaristías más participativas" de la carta. // Fallback: cat 1648 por fecha. add_shortcode('fea_eucaristia', function($atts) { $lang = fea_current_lang(); $labels = fea_labels(); $atts = shortcode_atts(['titulo' => $labels['eucaristia']], $atts); $posts = function_exists('fea_carta_section_posts') ? fea_carta_section_posts('eucaristia', $lang) : []; if (empty($posts)) { $posts = get_posts([ 'posts_per_page' => 6, 'category__in' => [fea_cat(1648)], 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ]); } if (!$posts) return ''; $html = '<section class="fea-section">' . '<h2 class="fea-section-title">' . esc_html($atts['titulo']) . '</h2>' . '<div class="fea-grid">'; foreach ($posts as $post) $html .= fea_card($post); return $html . '</div></section>'; }); // ── Shortcode: [fea_multimedia] ─────────────────────────────────────────── // Fuente principal: sección "Material multimedia" de la carta vigente. // Fallbacks: ACF portada_multimedia → últimos por categoría. add_shortcode('fea_multimedia', function($atts) { $lang = fea_current_lang(); // Multimedia es contenido ES y externo (sin traducir) → solo se muestra en // español de momento; en otros idiomas la sección se oculta (#135). if ($lang !== 'es') return ''; $labels = fea_labels(); $atts = shortcode_atts(['titulo' => $labels['multimedia']], $atts); $page = fea_front_page_id(); $more_labels = [ 'es' => 'Ver más', 'en' => 'View all', 'fr' => 'Voir plus', 'it' => 'Vedi tutto', 'pt' => 'Ver mais', ]; $more_label = $more_labels[$lang] ?? $more_labels['es']; $index_id = 18977; if ($lang !== 'es' && function_exists('pll_get_post')) { $translated_index = (int) pll_get_post($index_id, $lang); if ($translated_index) $index_id = $translated_index; } $index_url = get_permalink($index_id); $posts = function_exists('fea_carta_section_posts') ? fea_carta_section_posts('multimedia', $lang) : []; if (empty($posts) && $lang === 'es' && function_exists('get_field')) { $seleccion = get_field('portada_multimedia', $page) ?: []; foreach ($seleccion as $p) { if ($p->post_status === 'publish') $posts[] = $p; } } if (empty($posts)) { $posts = get_posts([ 'posts_per_page' => 4, 'category__in' => array_map('fea_cat', [1649, 26, 58]), 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ]); } if (!$posts) return ''; $html = '<section class="fea-section">' . '<div class="fea-section-head">' . '<h2 class="fea-section-title">' . esc_html($atts['titulo']) . '</h2>' . ($index_url ? '<a class="fea-section-more" href="' . esc_url($index_url) . '">' . esc_html($more_label) . ' <span aria-hidden="true">→</span></a>' : '') . '</div>' . '<div class="fea-grid">'; foreach ($posts as $post) $html .= fea_card($post); return $html . '</div></section>'; }); // ── Shortcode: [fea_noticia_centro] — bloque central del footer (Noticias) ─── add_shortcode('fea_noticia_centro', function() { $uploads = wp_upload_dir()['baseurl']; $cat_url = get_category_link(fea_cat(41)); $latest = get_posts([ 'posts_per_page' => 1, 'category__in' => [fea_cat(41)], 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ]); $title = !empty($latest) ? fea_title($latest[0]->post_title) : ''; $url = !empty($latest) ? get_permalink($latest[0]->ID) : $cat_url; $html = '<a href="' . esc_url($cat_url) . '">'; $html .= '<img src="' . esc_url($uploads . '/recursos/noticias_2025.jpg') . '" ' . 'alt="Noticias de alcance" width="300" height="340" ' . 'style="display:block;margin:0 auto;" />'; $html .= '</a>'; if ($title) { $html .= '<p style="text-align:center;margin-top:0.5rem;font-size:0.85rem;font-weight:600;line-height:1.3;">' . '<a href="' . esc_url($url) . '" style="color:#0000cc;">' . esc_html($title) . '</a></p>'; } return $html; }); // ── Reescribir links internos al idioma activo (Polylang) ───────────────── add_filter('the_content', function($content) { if (!function_exists('pll_current_language') || !function_exists('pll_get_post')) return $content; $lang = pll_current_language(); if (!$lang || $lang === 'es') return $content; // Los archivos de cartas pueden contener cientos de enlaces. Resolver cada uno // con url_to_postid() en un listado agota memoria; la reescritura solo aporta // valor al mostrar el contenido completo de una entrada o página. if (!is_singular()) return $content; return preg_replace_callback( '/<a\s([^>]*\s)?href=["\']([^"\']+)["\']([^>]*)>/i', function($m) use ($lang) { $href = $m[2]; $home = home_url(); if (strpos($href, $home) === false) return $m[0]; $post_id = url_to_postid($href); if (!$post_id) return $m[0]; $translated_id = pll_get_post($post_id, $lang); if (!$translated_id || $translated_id === $post_id) return $m[0]; $new_url = get_permalink($translated_id); if (!$new_url) return $m[0]; return str_replace($href, $new_url, $m[0]); }, $content ); }, 20); // ── Ordenar categoría Evangelios y Comentarios por título ASC ───────────── add_action('pre_get_posts', function($query) { if ($query->is_main_query() && $query->is_category('evangelios-y-comentarios')) { $query->set('orderby', 'title'); $query->set('order', 'ASC'); } }); // ── Acordeón de versículos en posts de Evangelios y Comentarios ─────────── add_action('wp_footer', function() { if (!is_single()) return; global $post; if (!has_category('evangelios-y-comentarios', $post)) return; ?> <style> .versiculo-group { margin: 0.5em 0; } .versiculo-toggle { display: flex; align-items: center; gap: 10px; cursor: default; margin: 0.8em 0 0; } .versiculo-toggle .v-icon { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; min-width: 22px; border-radius: 50%; background: #c8860a; color: white; font-size: 18px; line-height: 1; cursor: pointer; user-select: none; font-weight: bold; transition: background 0.15s; } .versiculo-group.open .v-icon { background: #6b3080; } .v-icon::after { content: '+'; } .versiculo-group.open .v-icon::after { content: '\2212'; } .versiculo-body { display: none; padding-left: 4px; } .versiculo-group.open .versiculo-body { display: block; } </style> <script> document.addEventListener('DOMContentLoaded', function() { var content = document.querySelector('.wp-block-post-content, .entry-content'); if (!content) return; var verseRe = /^(JUAN|LUCAS|MARCOS|MATEO)\s+\d/i; function isVerse(el) { if (el.tagName !== 'P') return false; var links = el.querySelectorAll('a'); if (links.length !== 1) return false; return verseRe.test(el.textContent.trim()); } var children = Array.from(content.children); var i = 0; while (i < children.length) { var child = children[i]; if (isVerse(child)) { var group = document.createElement('div'); group.className = 'versiculo-group'; var header = document.createElement('div'); header.className = 'versiculo-toggle'; var icon = document.createElement('span'); icon.className = 'v-icon'; var link = child.querySelector('a').cloneNode(true); header.appendChild(icon); header.appendChild(link); group.appendChild(header); var body = document.createElement('div'); body.className = 'versiculo-body'; child.parentNode.insertBefore(group, child); child.remove(); i++; while (i < children.length && !isVerse(children[i])) { body.appendChild(children[i]); i++; } group.appendChild(body); icon.addEventListener('click', function() { this.closest('.versiculo-group').classList.toggle('open'); }); } else { i++; } } }); </script> <?php }, 30); // ── Avatares en artículos de versículos (Evangelios y Comentarios) ───────── add_action('wp_footer', function() { if (!is_single()) return; global $post, $wpdb; if (!has_category('evangelios-y-comentarios', $post)) return; // Extraer todos los slugs de links internos del contenido preg_match_all('/<a\s[^>]*href=["\']([^"\']+)["\']/', $post->post_content, $m); $slugs = []; foreach ($m[1] as $url) { $slug = basename(rtrim(parse_url($url, PHP_URL_PATH), '/')); if ($slug) { $slugs[] = $slug; } } $slugs = array_values(array_unique(array_filter($slugs))); if (empty($slugs)) return; // Una sola query: slug → author_id $ph = implode(',', array_fill(0, count($slugs), '%s')); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_name, post_author FROM {$wpdb->posts} WHERE post_name IN ($ph) AND post_status IN ('publish','draft') AND post_type='post'", $slugs ) ); // Construir mapa slug → avatar_url (reutilizando cache de autor) $author_cache = []; $avatar_map = []; foreach ($rows as $row) { $aid = (int) $row->post_author; if (!isset($author_cache[$aid])) { $user = get_userdata($aid); $author_cache[$aid] = ['src' => get_avatar_url($aid, ['size' => 40, 'default' => 'identicon']), 'name' => $user ? $user->display_name : '']; } if ($author_cache[$aid]) { $avatar_map[$row->post_name] = $author_cache[$aid]; } } if (empty($avatar_map)) return; ?> <style> .versiculo-body ul { padding-left: 0; list-style: none; } .versiculo-body li { display: flex; align-items: center; gap: 7px; margin-bottom: 4px; } .fea-li-avatar { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; flex-shrink: 0; } .transl-toggle { font-size: 11px; color: #888; cursor: pointer; margin-left: 6px; user-select: none; white-space: nowrap; flex-shrink: 0; } .transl-toggle:hover { color: #777; } .transl-block { display: none; margin: 2px 0 6px 32px; font-size: 11px; line-height: 1.8; } .transl-block.open { display: block; } .transl-block a { color: #aaa; display: block; } .transl-block a:hover { color: #555; } </style> <script> (function() { var map = <?php echo json_encode($avatar_map); ?>; function slug(url) { return url.replace(/\/$/, '').split('/').pop().split('?')[0].split('#')[0]; } document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.versiculo-body li').forEach(function(li) { var a = li.querySelector('a'); if (!a) return; var entry = map[slug(a.href)]; if (!entry) return; var img = document.createElement('img'); img.src = entry.src; img.title = entry.name; img.alt = ''; img.className = 'fea-li-avatar'; li.insertBefore(img, li.firstChild); }); // Colapsar traducciones (p margin-left:90px) bajo "(…)" document.querySelectorAll('.versiculo-body').forEach(function(body) { Array.from(body.children).forEach(function(el) { if (el.tagName !== 'P') return; var ml = parseInt(el.style.marginLeft || '0'); if (ml < 60) return; var prev = el.previousElementSibling; while (prev && prev.tagName !== 'UL') prev = prev.previousElementSibling; if (!prev) return; var lis = prev.querySelectorAll('li'); if (!lis.length) return; var lastLi = lis[lis.length - 1]; el.classList.add('transl-block'); var btn = document.createElement('span'); btn.className = 'transl-toggle'; btn.textContent = 'Otros idiomas (\u2026)'; lastLi.appendChild(btn); btn.addEventListener('click', function(e) { e.stopPropagation(); el.classList.toggle('open'); }); }); }); }); })(); </script> <?php }, 31); // ── Shortcodes lista de autores ──────────────────────────────────────────── // IDs excluidos: Fe Adulta (1,890), Ediciones Feadulta (1540), José Chicharro (1049) if (!defined('FEA_AUTORES_EXCLUIR')) define('FEA_AUTORES_EXCLUIR', [1, 890, 1049, 1540]); if (!defined('FEA_LANG_ES_TTID')) define('FEA_LANG_ES_TTID', 1404); function fea_autores_query($min_count = 0, $extra_exclude = []) { global $wpdb; $excl = implode(',', array_merge(FEA_AUTORES_EXCLUIR, $extra_exclude)); $ttid = FEA_LANG_ES_TTID; $having = $min_count > 0 ? "HAVING cnt >= $min_count" : ''; $order = $min_count > 0 ? 'cnt DESC' : 'u.display_name ASC'; return $wpdb->get_results(" SELECT u.ID, u.display_name, COUNT(*) as cnt FROM {$wpdb->posts} p JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID JOIN {$wpdb->users} u ON u.ID = p.post_author WHERE p.post_type = 'post' AND p.post_status = 'publish' AND tr.term_taxonomy_id = $ttid AND u.ID NOT IN ($excl) GROUP BY p.post_author $having ORDER BY $order "); } function fea_autores_html($rows, $show_count = false, $extra_class = "") { $extra = $extra_class ? ' ' . $extra_class : ''; $out = '<ul class="fea-autores-lista' . $extra . '" style="list-style:none;padding-left:0;">'; foreach ($rows as $r) { $url = esc_url(get_author_posts_url($r->ID)); $name = esc_html($r->display_name); $avatar = esc_url(get_avatar_url($r->ID, ['size' => 40, 'default' => 'identicon'])); $cnt = $show_count ? ' <span class="fea-autor-cnt">(' . $r->cnt . ')</span>' : ''; $out .= '<li><span style="display:inline-block;width:40px;height:40px;min-width:40px;border-radius:50%;overflow:hidden;flex-shrink:0;"><img src="' . $avatar . '" width="40" height="40" alt="" loading="lazy" style="width:40px;height:40px;object-fit:cover;display:block;"></span>  <a href="' . $url . '">' . $name . '</a>' . $cnt . '</li>'; } $out .= '</ul>'; return $out; } add_shortcode('fea_autores_habituales', function() { $rows = fea_autores_query(30, [948, 1048]); if (empty($rows)) return '<p>No hay datos disponibles.</p>'; $n = count($rows); $out = '<details class="fea-autores-section"><summary class="fea-autores-summary">Autores habituales <span class="fea-autor-cnt">(' . $n . ' autores)</span></summary>'; $out .= fea_autores_html($rows, true); $out .= '</details>'; return $out; }); add_shortcode('fea_autores_completo', function() { $rows = fea_autores_query(0); if (empty($rows)) return '<p>No hay datos disponibles.</p>'; $n = count($rows); $out = '<details class="fea-autores-section"><summary class="fea-autores-summary">Lista completa por orden alfabético <span class="fea-autor-cnt">(' . $n . ' autores)</span></summary>'; $out .= fea_autores_html($rows, false, 'fea-autores-completo'); $out .= '</details>'; return $out; }); add_action('wp_head', function() { if (!is_page('autores-lista')) return; ?> <style> .fea-autores-section { margin-bottom: 1.5em; } .fea-autores-summary { font-size: 1.3rem; font-weight: 600; cursor: pointer; padding: 0.5em 0; list-style: none; display: flex; align-items: center; gap: 0.5em; border-bottom: 2px solid #046bd2; margin-bottom: 0.8em; user-select: none; } .fea-autores-summary::-webkit-details-marker { display: none; } .fea-autores-summary::before { content: "\25B6"; font-size: 0.75em; color: #046bd2; transition: transform 0.2s; display: inline-block; } details[open] > .fea-autores-summary::before { transform: rotate(90deg); } .fea-autores-lista { list-style: none; padding: 0; margin: 0.5em 0 1em; } .fea-autores-lista li { display: flex; align-items: center; gap: 12px; padding: 3px 0; border-bottom: 1px solid #f5f5f5; } .fea-autores-lista li img.fea-autor-avatar { width: 40px; height: 40px; min-width: 40px; border-radius: 50%; clip-path: circle(50%); object-fit: cover; display: block; flex-shrink: 0; } .fea-autor-cnt { color: #888; font-size: 0.85em; } .fea-autores-completo { column-count: 3; column-gap: 2em; } .fea-autores-completo li { break-inside: avoid; } @media (max-width: 700px) { .fea-autores-completo { column-count: 2; } } @media (max-width: 480px) { .fea-autores-completo { column-count: 1; } } </style> <?php }, 20); // ── Aumentar posts por página en archivos de autor ───────────────────────── add_action('pre_get_posts', function($query) { if ($query->is_main_query() && $query->is_author()) { $query->set('posts_per_page', 30); } }); // ── Shortcodes EFFA (Escuela de Formación en Fe Adulta) ──────────────────── // Sección de vídeos (subpáginas): thumbnail YT o primera imagen del contenido add_shortcode('effa_seccion', function($atts) { $atts = shortcode_atts(['cat' => '', 'num' => -1], $atts); if (!$atts['cat']) return ''; $posts = get_posts([ 'numberposts' => (int) $atts['num'], 'category_name' => $atts['cat'], 'post_status' => 'publish', 'orderby' => 'meta_value', 'meta_key' => '_effa_joomla_alias', 'order' => 'ASC', ]); if (!$posts) return '<p>No hay contenido en esta sección todavía.</p>'; $items = []; foreach ($posts as $p) { $url = get_permalink($p->ID); $thumb = get_the_post_thumbnail_url($p->ID, 'medium'); if (!$thumb && preg_match('~youtube\.com/watch\?v=([a-zA-Z0-9_-]+)~', $p->post_content, $m)) { $thumb = 'https://img.youtube.com/vi/' . $m[1] . '/mqdefault.jpg'; } if (!$thumb && preg_match('~<img[^>]+src="([^"]+)"~', $p->post_content, $mi)) { $thumb = $mi[1]; } $cell = '<td style="width:25%;padding:6px;vertical-align:top;">'; $cell .= '<a href="' . esc_url($url) . '" style="text-decoration:none;color:inherit;display:block;">'; if ($thumb) $cell .= '<img src="' . esc_url($thumb) . '" style="width:100%;height:auto;display:block;border-radius:4px;">'; $cell .= '<strong style="display:block;font-size:0.85rem;margin-top:5px;color:#222;">' . esc_html(fea_title($p->post_title)) . '</strong>'; $cell .= '</a>'; $cell .= '</td>'; $items[] = $cell; } $html = '<table class="effa-proyecto-table" style="width:100%;border-collapse:collapse;table-layout:fixed;">'; foreach (array_chunk($items, 4) as $row) { while (count($row) < 4) $row[] = '<td></td>'; $html .= '<tr>' . implode('', $row) . '</tr>'; } $html .= '</table>'; return $html; }); // Hub del proyecto (8 artículos de presentación): primera imagen + extracto add_shortcode('effa_proyecto', function() { $posts = get_posts([ 'numberposts' => -1, 'category_name' => 'proyecat', 'post_status' => 'publish', 'orderby' => 'name', 'order' => 'ASC', ]); if (!$posts) return ''; $items = []; foreach ($posts as $p) { $url = get_permalink($p->ID); preg_match('~<img[^>]+src="([^"]+)"~', $p->post_content, $mi); $thumb = $mi[1] ?? ''; $text = trim(preg_replace('/\s+/', ' ', wp_strip_all_tags($p->post_content))); $excerpt = wp_trim_words($text, 12, '…'); $cell = '<td style="width:25%;padding:6px;vertical-align:top;">'; $cell .= '<a href="' . esc_url($url) . '" style="text-decoration:none;color:inherit;display:block;">'; if ($thumb) $cell .= '<img src="' . esc_url($thumb) . '" style="width:100%;height:auto;display:block;border-radius:4px;">'; $cell .= '<strong style="display:block;font-size:0.85rem;margin-top:5px;color:#222;">' . esc_html(fea_title($p->post_title)) . '</strong>'; $cell .= '</a>'; $cell .= '<span style="display:block;font-size:0.75rem;color:#666;margin-top:3px;line-height:1.3;">' . esc_html($excerpt) . '</span>'; $cell .= '</td>'; $items[] = $cell; } $html = '<table class="effa-proyecto-table" style="width:100%;border-collapse:collapse;table-layout:fixed;">'; foreach (array_chunk($items, 4) as $row) { while (count($row) < 4) $row[] = '<td></td>'; $html .= '<tr>' . implode('', $row) . '</tr>'; } $html .= '</table>'; return $html; }); // CSS EFFA (tabs nav, logo, CTA, cards) add_action('wp_head', function() { global $post; if (!$post || (strpos($post->post_content, 'effa_') === false && strpos($post->post_name ?? '', 'effa') === false)) return; ?> <style> .effa-logo { display: block; margin: 0 auto 1rem; max-width: 200px; } .effa-intro { text-align: center; margin-bottom: 1.5rem; } .effa-nav { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1.5rem 0 2rem; padding: 0 0 1rem; list-style: none; border-bottom: 2px solid #e5e5e5; } .effa-nav li { list-style: none; } .effa-nav a { display: inline-block; padding: 0.35em 1em; border: 1px solid #ccc; border-radius: 999px; font-size: 0.85rem; text-decoration: none; color: #444; transition: background 0.15s, color 0.15s, border-color 0.15s; } .effa-nav a:hover, .effa-nav a.active { background: #E89A1A; color: #fff; border-color: #E89A1A; } .effa-cta-wrap { text-align: center; margin: 2rem 0 1rem; } .effa-cta { display: inline-block; padding: 0.6em 1.8em; background: #E89A1A; color: #fff; border-radius: 999px; text-decoration: none; font-weight: 700; font-size: 1rem; } .effa-cta:hover { background: #c97d10; color: #fff; } /* Tarjetas EFFA: reflota de 4 columnas a 2 en movil */ @media (max-width: 600px) { .effa-proyecto-table tr { display: flex; flex-wrap: wrap; } .effa-proyecto-table td { width: 50% !important; box-sizing: border-box; } .effa-proyecto-table td:empty { display: none; } } </style> <?php }, 20); // ── Índice dinámico de evangelio por libro ───────────────────────────────── // "Jn 4, 5-42" → "jn-4-5-42" (anchor ID format) function fea_cita_to_anchor(string $cita): string { return strtolower(preg_replace('/[,\.\s]+/', '-', trim($cita))); } // Parse "Jn 4, 5-42" → [4, 5] for numeric sorting function fea_cita_sort_key(string $cita): array { preg_match('/(\d+),\s*(\d+)/', $cita, $m); return [(int)($m[1] ?? 0), (int)($m[2] ?? 0)]; } add_shortcode('fea_citas_evangelio', function($atts) { $atts = shortcode_atts(['libro' => 'Jn'], $atts); $libro = preg_replace('/[^A-Za-z]/', '', $atts['libro']); // sanitize global $wpdb; $cat_com = 1647; // One query: all posts + author + avatar email in this book $rows = $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_title, p.post_author, pm.meta_value AS cita, u.display_name AS autor_name, u.user_email AS autor_email FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_cita_evangelio' JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id AND tt.term_id = %d JOIN {$wpdb->users} u ON u.ID = p.post_author WHERE p.post_status = 'publish' AND pm.meta_value LIKE %s ORDER BY pm.meta_value, p.post_date ASC", $cat_com, $libro . ' %' )); if (empty($rows)) return '<p>No hay comentarios para este evangelio.</p>'; // Group by cita $groups = []; foreach ($rows as $r) $groups[$r->cita][] = $r; // Sort groups numerically by chapter, then verse uksort($groups, function($a, $b) { [$ca, $va] = fea_cita_sort_key($a); [$cb, $vb] = fea_cita_sort_key($b); return $ca !== $cb ? $ca - $cb : $va - $vb; }); // Avatar URL by email (Gravatar, with fallback) $avatar_cache = []; $avatar_url = function(string $email, int $id) use (&$avatar_cache): string { if (!isset($avatar_cache[$id])) { $url = get_avatar_url($id, ['size' => 40, 'default' => 'mystery']); $avatar_cache[$id] = $url ?: 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($email))) . '?s=40&d=mystery'; } return $avatar_cache[$id]; }; $html = '<div class="fea-indice-evangelio">'; foreach ($groups as $cita => $posts) { $n = count($posts); $anchor = fea_cita_to_anchor($cita); $html .= '<details class="fea-cita-group" id="' . esc_attr($anchor) . '">'; $html .= '<summary class="fea-cita-summary">' . '<span class="fea-cita-ref">' . esc_html($cita) . '</span>' . ' <span class="fea-cita-count">' . $n . ' comentario' . ($n !== 1 ? 's' : '') . '</span>' . '</summary>'; $html .= '<ul class="fea-cita-lista">'; foreach ($posts as $p) { $av = $avatar_url($p->autor_email, $p->post_author); $html .= '<li class="fea-cita-item">' . '<span class="fea-cita-avatar">' . '<img src="' . esc_url($av) . '" width="40" height="40" alt="" loading="lazy">' . '</span>' . '<span class="fea-cita-meta">' . '<a href="' . esc_url(get_permalink($p->ID)) . '">' . esc_html(fea_title($p->post_title)) . '</a>' . '<span class="fea-cita-autor">' . esc_html($p->autor_name) . '</span>' . '</span>' . '</li>'; } $html .= '</ul></details>'; } $html .= '</div>'; return $html; }); add_action('wp_head', function() { global $post; if (!$post || !has_shortcode($post->post_content, 'fea_citas_evangelio')) return; ?> <style> .fea-indice-evangelio { margin: 1.5rem 0; } .fea-cita-group { border-bottom: 1px solid #e5e5e5; padding: 0; } .fea-cita-summary { display: flex; align-items: baseline; gap: 0.6rem; padding: 0.75rem 0.5rem; cursor: pointer; list-style: none; user-select: none; } .fea-cita-summary::-webkit-details-marker { display: none; } .fea-cita-summary::before { content: '›'; font-size: 1.1rem; color: #046bd2; transition: transform 0.15s; display: inline-block; flex-shrink: 0; } .fea-cita-group[open] > .fea-cita-summary::before { transform: rotate(90deg); } .fea-cita-ref { font-weight: 700; font-size: 0.95rem; } .fea-cita-count { font-size: 0.8rem; color: #888; } .fea-cita-lista { list-style: none; padding: 0 0 0.75rem 1.5rem; margin: 0; display: flex; flex-direction: column; gap: 0.5rem; } .fea-cita-item { display: flex; align-items: center; gap: 0.75rem; } .fea-cita-avatar { display: inline-block; width: 40px; height: 40px; min-width: 40px; border-radius: 50%; overflow: hidden; flex-shrink: 0; } .fea-cita-avatar img { width: 40px; height: 40px; object-fit: cover; display: block; } .fea-cita-meta { display: flex; flex-direction: column; gap: 0.1rem; line-height: 1.3; } .fea-cita-meta a { font-size: 0.9rem; } .fea-cita-autor { font-size: 0.78rem; color: #888; } </style> <?php }, 20); // ── Pie de comentario: carta, cita evangelio, otros comentarios ──────────── // Add id anchors to verse headings in the 4 gospel index pages add_filter('the_content', function($content) { global $post; $index_ids = [17874, 17881, 17882, 17883]; if (!isset($post->ID) || !in_array($post->ID, $index_ids)) return $content; $bookmap = ['JUAN'=>'Jn','LUCAS'=>'Lc','MARCOS'=>'Mc','MATEO'=>'Mt']; return preg_replace_callback( '~<p([^>]*)>\s*(<a[^>]*>)\s*((JUAN|LUCAS|MARCOS|MATEO)\s+(\d+),\s*([\d\-–\s]+))\s*(</a>)\s*</p>~i', function($m) use ($bookmap) { $abbr = $bookmap[strtoupper($m[4])] ?? ''; if (!$abbr) return $m[0]; $anchor = strtolower($abbr . '-' . $m[5] . '-' . preg_replace('/[\s–\-]+/', '-', trim($m[6]))); $anchor = rtrim($anchor, '-'); return '<p' . $m[1] . ' id="' . $anchor . '">' . $m[2] . $m[3] . $m[7] . '</p>'; }, $content ); }, 9); // priority 9 so it runs before the_content shortcode processing add_filter('the_content', function($content) { if (!is_single()) return $content; global $post; if (!in_category('comentarios-al-evangelio', $post)) return $content; $carta_id = get_post_meta($post->ID, '_carta_id', true); $cita = get_post_meta($post->ID, '_cita_evangelio', true); if (!$carta_id && !$cita) return $content; $html = '<div class="fea-com-footer">'; // --- Carta de la semana --- if ($carta_id) { $carta = get_post($carta_id); if ($carta && $carta->post_status === 'publish') { $fecha = date_i18n('j F Y', strtotime($carta->post_date)); $html .= '<div class="fea-com-row fea-com-carta">' . '<span class="fea-com-label">Carta de la semana</span>' . '<a href="' . esc_url(get_permalink($carta_id)) . '">' . esc_html(fea_title($carta->post_title)) . ' <span class="fea-com-fecha">(' . $fecha . ')</span>' . '</a>' . '</div>'; } } // --- Cita del evangelio — link directo al anchor en el índice --- if ($cita) { $book_post_id = ['Mt' => 43908, 'Mc' => 43909, 'Lc' => 43907, 'Jn' => 43906]; $abbr = substr($cita, 0, 2); $anchor = fea_cita_to_anchor($cita); $index_url = isset($book_post_id[$abbr]) ? esc_url(get_permalink($book_post_id[$abbr]) . '#' . $anchor) : ''; $html .= '<div class="fea-com-row fea-com-cita">' . '<span class="fea-com-label">Evangelio</span>' . ($index_url ? '<a href="' . $index_url . '">' . esc_html($cita) . '</a>' : '<span>' . esc_html($cita) . '</span>') . '</div>'; } // --- Otros comentarios de esta misma cita --- if ($cita) { $others = get_posts([ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 30, 'post__not_in' => [$post->ID], 'category_name' => 'comentarios-al-evangelio', 'meta_key' => '_cita_evangelio', 'meta_value' => $cita, 'orderby' => 'date', 'order' => 'ASC', ]); if (!empty($others)) { $html .= '<div class="fea-com-row fea-com-otros">' . '<span class="fea-com-label">Otros comentarios sobre ' . esc_html($cita) . '</span>' . '<ul class="fea-com-lista">'; foreach ($others as $o) { $html .= '<li><a href="' . esc_url(get_permalink($o->ID)) . '">' . esc_html(fea_title($o->post_title)) . '</a></li>'; } $html .= '</ul></div>'; } } $html .= '</div>'; // .fea-com-footer return $content . $html; }); add_action('wp_head', function() { if (!is_single()) return; global $post; if (!$post || !in_category('comentarios-al-evangelio', $post)) return; ?> <style> .fea-com-footer { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 2px solid #e5e5e5; font-size: 0.9rem; } .fea-com-row { display: flex; flex-wrap: wrap; gap: 0.4rem 0.8rem; align-items: baseline; margin-bottom: 1rem; } .fea-com-label { font-weight: 700; color: #555; white-space: nowrap; flex-shrink: 0; } .fea-com-label::after { content: ':'; margin-right: 0.2em; } .fea-com-fecha { color: #888; font-size: 0.85em; } .fea-com-lista { list-style: none; padding: 0; margin: 0.3rem 0 0; display: flex; flex-wrap: wrap; gap: 0.3rem 0; flex-direction: column; } .fea-com-lista li::before { content: '›'; margin-right: 0.4em; color: #046bd2; } .fea-com-otros .fea-com-label { display: block; margin-bottom: 0.4rem; } .fea-com-otros { flex-direction: column; align-items: flex-start; } </style> <?php }, 20);