Files
feadulta/scripts/verify_carta_lang_links.php
rafa 6b61250b0b Sincronizar mu-plugins/ y scripts/ con el estado real del sitio (2026-07-16)
Este repo llevaba desde el 28-jun sin actualizarse (salvo sync_carta_from_prod.py).
Se pone al dia con 3 semanas de trabajo que solo vivian en el checkout local
completo del proyecto:

- Fix del buscador (issue #178): AND obligatorio en FULLTEXT (antes devolvia
  practicamente todo el sitio con cualquier busqueda), exclusion de paginas
  indice residuales de la migracion K2, soporte de frase exacta entre comillas.
- Nuevos mu-plugins ya desplegados a prod: fea-carta-id-api, fea-cloudflare-realip,
  fea-crear-autor-api, fea-gsc-verification, fea-legacy-redirect (cutover
  Joomla->WP, ver issue #162).
- TTS multi-voz por autor, scripts de traduccion, sync de audio a prod,
  homenajes Mardones/Galarreta.

Se mantiene la estructura plana del repo (mu-plugins/ + scripts/, sin el
wordpress/wp-content/ del checkout completo) tal y como documenta el README:
es la convencion establecida para este repo, mas limpia para quien solo
necesita leer el codigo. Anadido .gitignore (cache de Python que no debia
commitearse).

Fuente: checkout local completo del proyecto, rama main (commit 69e849d).
2026-07-15 20:26:43 -04:00

145 lines
5.3 KiB
PHP

<?php
/**
* verify_carta_lang_links.php
*
* QA de una carta traducida: para cada idioma (en/fr/it/pt), revisa TODOS los
* enlaces internos de feadulta.com y comprueba que apuntan a contenido en ese
* mismo idioma (prefijo /en/, /fr/, /it/, /pt/ y slug correcto).
*
* Detecta dos tipos de fallo:
* - SIN_TRADUCIR: el link apunta al artículo en español porque nunca se
* tradujo/creó su equivalente en ese idioma.
* - MISMATCH: el link tiene prefijo de idioma pero el slug no es el
* correcto (p.ej. suffix -2 divergente entre local y prod), y resuelve a
* contenido de OTRO idioma o a nada.
*
* Solo reporta, no modifica nada (a diferencia de repoint_carta_links.php).
*
* Uso:
* FEA_WP_LOAD=/web/wp-load.php CARTA=54254 php verify_carta_lang_links.php
* CARTA=54254 php verify_carta_lang_links.php (usa /var/www/html/wp-load.php, para Docker local)
*/
require getenv('FEA_WP_LOAD') ?: '/var/www/html/wp-load.php';
$CARTA = (int)(getenv('CARTA') ?: 0);
if (!$CARTA) {
fwrite(STDERR, "Falta CARTA=<id del post en español>\n");
exit(1);
}
$LANGS = ['en', 'fr', 'it', 'pt'];
function resolve_post_from_href($href) {
if (preg_match('~[?&]p=(\d+)~', $href, $m)) {
return (int)$m[1];
}
$path = preg_replace('~^https?://[^/]+~i', '', $href);
$path = preg_replace('~[?#].*$~', '', $path);
$path = preg_replace('~^/fea~', '', $path);
$path = preg_replace('~^/(en|fr|it|pt|es)(/|$)~', '/', $path);
$segs = array_values(array_filter(explode('/', $path), 'strlen'));
if (count($segs) !== 1) {
return 0; // categorías, home, rutas multi-segmento: no aplica
}
$p = get_page_by_path($segs[0], OBJECT, ['post', 'page']);
return $p ? $p->ID : 0;
}
function href_lang_prefix($href) {
$path = preg_replace('~^https?://[^/]+~i', '', $href);
$path = preg_replace('~^/fea~', '', $path);
if (preg_match('~^/(en|fr|it|pt)(/|$)~', $path, $m)) {
return $m[1];
}
return null;
}
$translations = pll_get_post_translations($CARTA);
if (empty($translations)) {
fwrite(STDERR, "El post $CARTA no tiene grupo de traducciones Polylang.\n");
exit(1);
}
echo "Verificando enlaces de la carta ES=$CARTA\n";
echo "Traducciones: " . json_encode($translations) . "\n\n";
$total_sin_traducir = 0;
$total_mismatch = 0;
$total_rotos = 0;
foreach ($LANGS as $lang) {
if (empty($translations[$lang])) {
echo "[$lang] SIN TRADUCCIÓN DE LA CARTA (no existe post en este idioma)\n\n";
continue;
}
$pid = $translations[$lang];
$post = get_post($pid);
if (!$post) {
echo "[$lang] post $pid no encontrado\n\n";
continue;
}
// Captura cualquier href absoluto http(s) — funciona tanto en local (dominio
// Tailscale) como en prod (www.feadulta.com); resolve_post_from_href() ya
// descarta lo que no resuelva a un post interno (externos, mailto, etc.).
preg_match_all('~href="(https?://[^"]+)"~i', $post->post_content, $m);
$hrefs = array_values(array_unique($m[1]));
$issues = [];
foreach ($hrefs as $href) {
$target_id = resolve_post_from_href($href);
if (!$target_id) {
continue; // home, categorías, rutas no resolubles a un post concreto
}
$target_lang = pll_get_post_language($target_id) ?: 'es';
$prefix = href_lang_prefix($href);
if ($target_lang === $lang && $prefix === $lang) {
continue; // todo correcto
}
// Averiguar el ES de origen del contenido apuntado, para buscar la traducción correcta
if ($target_lang === 'es') {
$es_id = $target_id;
} else {
$tr = pll_get_post_translations($target_id);
$es_id = $tr['es'] ?? null;
}
$correct_id = $es_id ? pll_get_post($es_id, $lang) : null;
if ($correct_id && $correct_id != $target_id) {
$correct_url = get_permalink($correct_id);
$issues[] = [
'tipo' => 'MISMATCH',
'detalle' => "href=\"$href\" resuelve a post $target_id (lang=$target_lang) correcto: $correct_url (post $correct_id)",
];
$total_mismatch++;
} elseif ($correct_id && $correct_id == $target_id && $prefix !== $lang) {
// resuelve al post correcto pero con URL/slug distinta a la actual (permalink cambió)
$correct_url = get_permalink($correct_id);
$issues[] = [
'tipo' => 'MISMATCH',
'detalle' => "href=\"$href\" no coincide con el permalink actual: $correct_url (post $correct_id)",
];
$total_mismatch++;
} elseif (!$correct_id && $target_lang !== $lang) {
$issues[] = [
'tipo' => 'SIN_TRADUCIR',
'detalle' => "href=\"$href\" -> post $target_id (lang=$target_lang) sin traducción en '$lang'",
];
$total_sin_traducir++;
}
}
echo "[$lang] post $pid " . count($hrefs) . " enlaces internos, " . count($issues) . " con problemas\n";
foreach ($issues as $issue) {
echo " {$issue['tipo']}: {$issue['detalle']}\n";
}
echo "\n";
}
echo "RESUMEN: $total_sin_traducir sin traducir, $total_mismatch mismatch de idioma/slug\n";
exit(($total_sin_traducir + $total_mismatch + $total_rotos) > 0 ? 2 : 0);