diff --git a/wordpress/wp-content/mu-plugins/fea-carta-portada.php b/wordpress/wp-content/mu-plugins/fea-carta-portada.php
new file mode 100644
index 0000000..c5461fc
--- /dev/null
+++ b/wordpress/wp-content/mu-plugins/fea-carta-portada.php
@@ -0,0 +1,184 @@
+ 1,
+ 'category__in' => [$cat],
+ 'post_status' => 'publish',
+ 'orderby' => 'date',
+ 'order' => 'DESC',
+ 'suppress_filters' => false,
+ ]);
+ if (!$cartas) return $cache[$lang] = 0;
+
+ $carta_id = (int) $cartas[0]->ID;
+
+ if ($lang !== 'es' && function_exists('pll_get_post')) {
+ $trans = pll_get_post($carta_id, $lang);
+ if ($trans) $carta_id = (int) $trans;
+ }
+
+ return $cache[$lang] = $carta_id;
+}
+
+/**
+ * Parsea el HTML de la carta y devuelve los post_ids agrupados por sección.
+ */
+function fea_parse_carta_sections($carta_id) {
+ static $mem = [];
+ $carta_id = (int) $carta_id;
+ if (!$carta_id) return [];
+ if (isset($mem[$carta_id])) return $mem[$carta_id];
+
+ $tk = 'fea_carta_sections_' . $carta_id;
+ $cached = get_transient($tk);
+ if (is_array($cached)) return $mem[$carta_id] = $cached;
+
+ $post = get_post($carta_id);
+ if (!$post) return $mem[$carta_id] = [];
+
+ $sections = fea_extract_sections_from_html($post->post_content);
+
+ set_transient($tk, $sections, 15 * MINUTE_IN_SECONDS);
+ return $mem[$carta_id] = $sections;
+}
+
+/**
+ * Extrae secciones del HTML. Pública para tests/CLI.
+ */
+function fea_extract_sections_from_html($html) {
+ $section_patterns = [
+ 'evangelio' => '/Evangelio\s+y\s+comentarios\s+al\s+Evangelio/iu',
+ 'articulos' => '/Art[ií]culos\s+seleccionados\s+para\s+la\s+semana/iu',
+ 'eucaristia' => '/Para\s+unas\s+eucarist[ií]as\s+m[áa]s\s+participativas/iu',
+ 'multimedia' => '/Material\s+multimedia/iu',
+ 'effa' => '/Escuela\s+EFFA/iu',
+ ];
+
+ $positions = [];
+ foreach ($section_patterns as $slug => $regex) {
+ if (preg_match($regex, $html, $m, PREG_OFFSET_CAPTURE)) {
+ $positions[$slug] = $m[0][1];
+ }
+ }
+ if (empty($positions)) return [];
+ asort($positions);
+
+ $slugs = array_keys($positions);
+ $offsets = array_values($positions);
+ $offsets[] = strlen($html);
+
+ $sections = [];
+ for ($i = 0; $i < count($slugs); $i++) {
+ $segment = substr($html, $offsets[$i], $offsets[$i+1] - $offsets[$i]);
+ $ids = fea_resolve_links_in_html($segment);
+ if ($ids) $sections[$slugs[$i]] = $ids;
+ }
+ return $sections;
+}
+
+/**
+ * Extrae los href de un fragmento HTML y los resuelve a wp_posts.ID.
+ */
+function fea_resolve_links_in_html($html) {
+ if (!preg_match_all('/href=["\']([^"\']+)["\']/i', $html, $m)) return [];
+ $ids = [];
+ $seen = [];
+ foreach ($m[1] as $url) {
+ $pid = fea_url_to_post_id($url);
+ if ($pid && !isset($seen[$pid])) {
+ $seen[$pid] = true;
+ $ids[] = $pid;
+ }
+ }
+ return $ids;
+}
+
+/**
+ * Resuelve una URL (WP o Joomla legacy) a wp_posts.ID o null.
+ */
+function fea_url_to_post_id($url) {
+ global $wpdb;
+
+ if (preg_match('~(?:^|/)fea/([a-z0-9\-]+)/?(?:[?#]|$)~i', $url, $m)) {
+ $slug = $m[1];
+ if (in_array($slug, ['wp-admin','wp-content','category','tag','author','page','en','fr','it','pt'], true)) {
+ return null;
+ }
+ $pid = $wpdb->get_var($wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts}
+ WHERE post_name=%s AND post_status='publish' AND post_type='post'
+ ORDER BY post_date DESC LIMIT 1",
+ $slug
+ ));
+ if ($pid) return (int) $pid;
+ }
+
+ if (preg_match('~/item/(\d+)-[^/"]+\.html~i', $url, $m)) {
+ $k2 = (int) $m[1];
+ if ($k2 > 0) {
+ $pid = $wpdb->get_var($wpdb->prepare(
+ "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_fgj2wp_old_k2_id' AND meta_value=%s LIMIT 1",
+ (string) $k2
+ ));
+ if ($pid) return (int) $pid;
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Devuelve los WP_Post objects de una sección de la carta vigente,
+ * o array vacío si no hay carta o no hay links resueltos en esa sección.
+ */
+function fea_carta_section_posts($section_slug, $lang = null) {
+ $carta_id = fea_get_current_carta_id($lang);
+ if (!$carta_id) return [];
+ $sections = fea_parse_carta_sections($carta_id);
+ $ids = $sections[$section_slug] ?? [];
+ if (!$ids) return [];
+ $posts = [];
+ foreach ($ids as $pid) {
+ $p = get_post($pid);
+ if ($p && $p->post_status === 'publish') $posts[] = $p;
+ }
+ return $posts;
+}
+
+/**
+ * Invalida transients de secciones al guardar/editar un post.
+ */
+add_action('save_post_post', function($post_id, $post, $update) {
+ $cats = wp_get_post_categories($post_id);
+ $watch = [6, 21, 22];
+ if (array_intersect($cats, $watch)) {
+ global $wpdb;
+ $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_fea_carta_sections_%'");
+ $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_fea_carta_sections_%'");
+ }
+}, 10, 3);
diff --git a/wordpress/wp-content/mu-plugins/fea-homepage.php b/wordpress/wp-content/mu-plugins/fea-homepage.php
old mode 100644
new mode 100755
index d09ec13..ede84a1
--- a/wordpress/wp-content/mu-plugins/fea-homepage.php
+++ b/wordpress/wp-content/mu-plugins/fea-homepage.php
@@ -437,6 +437,56 @@ add_action('wp_head', function() {
echo '';
}, 20);
+// ── H1 semántico oculto para páginas sin título visible ───────────────────
+add_action('wp_head', function() {
+ ?>
+
+ ' . esc_html($title) . '' . $content;
+}, 5);
+
+// ── Ocultar metadatos de post en páginas estáticas ────────────────────────
+add_action('wp_head', function() {
+ if (!fea_hide_static_meta()) return;
+ ?>
+
+ 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';
@@ -625,22 +710,26 @@ add_shortcode('fea_carta_semana_hero', function() {
});
// ── 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();
- // Selección editorial ACF (solo para ES; otros idiomas usan fallback)
- $posts = [];
- if ($lang === 'es' && function_exists('get_field')) {
+ // 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;
}
}
- // Fallback: últimos artículos en el idioma actual
+ // 3) Fallback: últimos artículos en el idioma actual
if (empty($posts)) {
$posts = get_posts([
'posts_per_page' => 9,
@@ -661,30 +750,33 @@ add_shortcode('fea_articulos_semana', function($atts) {
});
// ── Shortcode: [fea_evangelio] ────────────────────────────────────────────
-// Editorial (cat 1646) primero, luego comentarios (cat 1647). Máx 7 en total.
+// 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);
- $editorial = get_posts([
- 'posts_per_page' => 1,
- 'category__in' => [fea_cat(1646)],
- 'post_status' => 'publish',
- 'orderby' => 'date',
- 'order' => 'DESC',
- ]);
+ $posts = function_exists('fea_carta_section_posts') ? fea_carta_section_posts('evangelio', $lang) : [];
- $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 (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 = ''
@@ -695,18 +787,24 @@ add_shortcode('fea_evangelio', function($atts) {
});
// ── 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 = get_posts([
- 'posts_per_page' => 6,
- 'category__in' => [fea_cat(1648)],
- 'post_status' => 'publish',
- 'orderby' => 'date',
- 'order' => 'DESC',
- ]);
+ $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 = ''
@@ -717,14 +815,17 @@ add_shortcode('fea_eucaristia', function($atts) {
});
// ── 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();
$labels = fea_labels();
$atts = shortcode_atts(['titulo' => $labels['multimedia']], $atts);
$page = fea_front_page_id();
- $posts = [];
- if ($lang === 'es' && function_exists('get_field')) {
+ $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;
diff --git a/wordpress/wp-content/mu-plugins/fea-slider-sync.php b/wordpress/wp-content/mu-plugins/fea-slider-sync.php
new file mode 100644
index 0000000..837f364
--- /dev/null
+++ b/wordpress/wp-content/mu-plugins/fea-slider-sync.php
@@ -0,0 +1,185 @@
+ 0) return false;
+ }
+
+ $files = fea_slider_home_files();
+
+ $uploads = wp_upload_dir();
+ $reldir = '$upload$/' . FEA_SLIDER_DIRNAME; // SS3 variable placeholder
+
+ // Leer slides actuales del slider 2 → mapeo imagen → row
+ $existing = $wpdb->get_results($wpdb->prepare(
+ "SELECT id, title, params, ordering FROM {$wpdb->prefix}nextend2_smartslider3_slides WHERE slider=%d ORDER BY ordering",
+ FEA_SLIDER_ID
+ ), ARRAY_A);
+
+ $by_image = [];
+ foreach ($existing as $row) {
+ $p = json_decode($row['params'], true) ?: [];
+ $img = isset($p['backgroundImage']) ? basename($p['backgroundImage']) : null;
+ if ($img) $by_image[$img] = $row;
+ }
+
+ $keep_ids = [];
+ $ordering = 0;
+ foreach ($files as $file) {
+ $ordering++;
+ if (isset($by_image[$file])) {
+ // Reutilizar slide existente — actualizar params si fuera necesario
+ $row = $by_image[$file];
+ $p = json_decode($row['params'], true) ?: [];
+ $expected = $reldir . '/' . $file;
+ $needs_update = false;
+ if (($p['backgroundImage'] ?? '') !== $expected) { $p['backgroundImage'] = $expected; $needs_update = true; }
+ if (($p['background-type'] ?? '') !== 'image') { $p['background-type'] = 'image'; $needs_update = true; }
+ if ($needs_update) {
+ $wpdb->update(
+ $wpdb->prefix . 'nextend2_smartslider3_slides',
+ ['params' => wp_json_encode($p), 'ordering' => $ordering],
+ ['id' => $row['id']],
+ ['%s','%d'], ['%d']
+ );
+ } else {
+ $wpdb->update(
+ $wpdb->prefix . 'nextend2_smartslider3_slides',
+ ['ordering' => $ordering],
+ ['id' => $row['id']],
+ ['%d'], ['%d']
+ );
+ }
+ $keep_ids[] = (int) $row['id'];
+ } else {
+ // Crear slide nuevo
+ $title = pathinfo($file, PATHINFO_FILENAME);
+ $params = wp_json_encode([
+ 'background-type' => 'image',
+ 'backgroundImage' => $reldir . '/' . $file,
+ 'version' => '3.5.1.32',
+ ]);
+ $img_url = $reldir . '/' . $file;
+ $wpdb->insert(
+ $wpdb->prefix . 'nextend2_smartslider3_slides',
+ [
+ 'slider' => FEA_SLIDER_ID,
+ 'title' => $title,
+ 'description' => '',
+ 'params' => $params,
+ 'slide' => '[]',
+ 'thumbnail' => $img_url,
+ 'publish_up' => '1970-01-01 00:00:00',
+ 'publish_down' => '1970-01-01 00:00:00',
+ 'published' => 1,
+ 'first' => 0,
+ 'generator_id' => 0,
+ 'ordering' => $ordering,
+ ],
+ ['%d','%s','%s','%s','%s','%s','%s','%s','%d','%d','%d','%d']
+ );
+ $keep_ids[] = (int) $wpdb->insert_id;
+ }
+ }
+
+ // Borrar slides cuya imagen ya no está en el directorio
+ if ($keep_ids) {
+ $in = implode(',', array_map('intval', $keep_ids));
+ $wpdb->query($wpdb->prepare(
+ "DELETE FROM {$wpdb->prefix}nextend2_smartslider3_slides WHERE slider=%d AND id NOT IN ($in)",
+ FEA_SLIDER_ID
+ ));
+ } else {
+ $wpdb->query($wpdb->prepare(
+ "DELETE FROM {$wpdb->prefix}nextend2_smartslider3_slides WHERE slider=%d",
+ FEA_SLIDER_ID
+ ));
+ }
+
+ update_option(FEA_SLIDER_OPT_KEY, $mtime);
+
+ // Marcar slider como cambiado para que SS3 regenere su manifest
+ $wpdb->update(
+ $wpdb->prefix . 'nextend2_section_storage',
+ ['value' => (string) $mtime],
+ ['application' => 'smartslider', 'section' => 'sliderChanged', 'referenceKey' => (string) FEA_SLIDER_ID],
+ ['%s'], ['%s','%s','%s']
+ );
+
+ return count($files);
+}
+
+// Ejecutar sync en cada carga de portada (la comprobación de mtime evita trabajo si nada cambió).
+add_action('template_redirect', function() {
+ if (!is_front_page()) return;
+ fea_slider_home_sync_now();
+}, 5);
+
+// Sync también al entrar al admin (por si el editor sube ficheros desde wp-admin).
+add_action('admin_init', function() {
+ fea_slider_home_sync_now();
+});
+
+// WP-CLI helper: `wp eval "fea_slider_home_sync_now(true);"` fuerza resync ignorando mtime.