Add verify_carta_lang_links.php QA script for multilingual carta links
Reports internal links whose language prefix/slug doesn't match the target post's actual language (SIN_TRADUCIR / MISMATCH), so link bugs like the silence/silence-2 cross-language mixup on carta 734 get caught before they reach prod.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<?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);
|
||||
Reference in New Issue
Block a user