Files
feadulta/mu-plugins/fea-homepage.php
T

2131 lines
93 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Plugin Name: Fe Adulta — Homepage
* Description: Portada con selección editorial via ACF.
* Version: 1.4
*/
// ── Flush rewrite rules una sola vez tras cambios de configuración ────────
add_action('init', function() {
if (get_option('fea_flush_needed')) {
flush_rewrite_rules(true);
delete_option('fea_flush_needed');
}
});
// ── Portada multilingüe: sirve la página traducida en el home de cada idioma ──
// Polylang free no traduce page_on_front automáticamente.
// Este hook intercepta el query del home de idioma y carga la página correcta.
add_action('pre_get_posts', function(WP_Query $query) {
if (!$query->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 <title>/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">&rsaquo;</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) . '">&lsaquo; ' . 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)) . ' &rsaquo;</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>&nbsp;&nbsp;<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);