Add diagnostic script for malformed HTML lists
Phase 4.4 Accessibility: Script to scan database for posts with invalid list structures (<ul> containing non-<li> children). Read-only analysis, no modifications. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
307
Shared/Infrastructure/Scripts/scan-malformed-lists.php
Normal file
307
Shared/Infrastructure/Scripts/scan-malformed-lists.php
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script de Diagnóstico: Listas HTML Mal Formadas
|
||||||
|
*
|
||||||
|
* PROPÓSITO: Identificar posts con estructura de listas inválida
|
||||||
|
* - <ul> conteniendo <ul> como hijo directo (en lugar de dentro de <li>)
|
||||||
|
* - <ol> conteniendo <ol> como hijo directo
|
||||||
|
*
|
||||||
|
* BASE DE DATOS: preciosunitarios_seo
|
||||||
|
* TABLA: datos_seo_pagina
|
||||||
|
* CAMPO: html
|
||||||
|
*
|
||||||
|
* IMPORTANTE: Este script SOLO LEE, no modifica ningún dato.
|
||||||
|
*
|
||||||
|
* @package ROI_Theme
|
||||||
|
* @since Phase 4.4 Accessibility
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Configuración de errores para debugging
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('memory_limit', '512M');
|
||||||
|
set_time_limit(300); // 5 minutos máximo
|
||||||
|
|
||||||
|
// Credenciales de base de datos (ajustar según servidor)
|
||||||
|
$db_config = [
|
||||||
|
'host' => 'localhost',
|
||||||
|
'database' => 'preciosunitarios_seo',
|
||||||
|
'username' => 'root', // Cambiar en producción
|
||||||
|
'password' => '', // Cambiar en producción
|
||||||
|
'charset' => 'utf8mb4'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Patrones regex para detectar listas mal formadas
|
||||||
|
$malformed_patterns = [
|
||||||
|
// <ul> seguido directamente de <ul> (sin estar dentro de <li>)
|
||||||
|
'ul_direct_ul' => '/<ul[^>]*>\s*(?:<li[^>]*>.*?<\/li>\s*)*<ul/is',
|
||||||
|
|
||||||
|
// Patrón más específico: </li> seguido de <ul> (hermanos en lugar de anidados)
|
||||||
|
'li_sibling_ul' => '/<\/li>\s*<ul[^>]*>/is',
|
||||||
|
|
||||||
|
// <ol> seguido directamente de <ol>
|
||||||
|
'ol_direct_ol' => '/<ol[^>]*>\s*(?:<li[^>]*>.*?<\/li>\s*)*<ol/is',
|
||||||
|
|
||||||
|
// </li> seguido de <ol> (hermanos)
|
||||||
|
'li_sibling_ol' => '/<\/li>\s*<ol[^>]*>/is',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conectar a la base de datos
|
||||||
|
*/
|
||||||
|
function connectDatabase(array $config): ?mysqli {
|
||||||
|
$conn = new mysqli(
|
||||||
|
$config['host'],
|
||||||
|
$config['username'],
|
||||||
|
$config['password'],
|
||||||
|
$config['database']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
echo "Error de conexión: " . $conn->connect_error . "\n";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->set_charset($config['charset']);
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analizar HTML en busca de listas mal formadas
|
||||||
|
*/
|
||||||
|
function analyzeMalformedLists(string $html, array $patterns): array {
|
||||||
|
$issues = [];
|
||||||
|
|
||||||
|
foreach ($patterns as $pattern_name => $pattern) {
|
||||||
|
if (preg_match_all($pattern, $html, $matches, PREG_OFFSET_CAPTURE)) {
|
||||||
|
foreach ($matches[0] as $match) {
|
||||||
|
$position = $match[1];
|
||||||
|
$context = getContextAroundPosition($html, $position, 100);
|
||||||
|
|
||||||
|
$issues[] = [
|
||||||
|
'type' => $pattern_name,
|
||||||
|
'position' => $position,
|
||||||
|
'context' => $context
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener contexto alrededor de una posición
|
||||||
|
*/
|
||||||
|
function getContextAroundPosition(string $html, int $position, int $length = 100): string {
|
||||||
|
$start = max(0, $position - $length);
|
||||||
|
$end = min(strlen($html), $position + $length);
|
||||||
|
|
||||||
|
$context = substr($html, $start, $end - $start);
|
||||||
|
|
||||||
|
// Limpiar para mostrar
|
||||||
|
$context = preg_replace('/\s+/', ' ', $context);
|
||||||
|
$context = htmlspecialchars($context);
|
||||||
|
|
||||||
|
if ($start > 0) {
|
||||||
|
$context = '...' . $context;
|
||||||
|
}
|
||||||
|
if ($end < strlen($html)) {
|
||||||
|
$context .= '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contar total de listas en el HTML
|
||||||
|
*/
|
||||||
|
function countListElements(string $html): array {
|
||||||
|
$ul_count = preg_match_all('/<ul[^>]*>/i', $html);
|
||||||
|
$ol_count = preg_match_all('/<ol[^>]*>/i', $html);
|
||||||
|
$li_count = preg_match_all('/<li[^>]*>/i', $html);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ul' => $ul_count,
|
||||||
|
'ol' => $ol_count,
|
||||||
|
'li' => $li_count
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// EJECUCIÓN PRINCIPAL
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
echo "==============================================\n";
|
||||||
|
echo " DIAGNÓSTICO: Listas HTML Mal Formadas\n";
|
||||||
|
echo " Base de datos: {$db_config['database']}\n";
|
||||||
|
echo " Tabla: datos_seo_pagina\n";
|
||||||
|
echo " Fecha: " . date('Y-m-d H:i:s') . "\n";
|
||||||
|
echo "==============================================\n\n";
|
||||||
|
|
||||||
|
// Conectar
|
||||||
|
$conn = connectDatabase($db_config);
|
||||||
|
if (!$conn) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✓ Conexión establecida\n\n";
|
||||||
|
|
||||||
|
// Obtener estructura de la tabla
|
||||||
|
echo "Verificando estructura de tabla...\n";
|
||||||
|
$result = $conn->query("DESCRIBE datos_seo_pagina");
|
||||||
|
if ($result) {
|
||||||
|
echo "Columnas encontradas:\n";
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
echo " - {$row['Field']} ({$row['Type']})\n";
|
||||||
|
}
|
||||||
|
echo "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contar registros totales
|
||||||
|
$result = $conn->query("SELECT COUNT(*) as total FROM datos_seo_pagina WHERE html IS NOT NULL AND html != ''");
|
||||||
|
$total = $result->fetch_assoc()['total'];
|
||||||
|
echo "Total de registros con HTML: {$total}\n\n";
|
||||||
|
|
||||||
|
// Procesar en lotes
|
||||||
|
$batch_size = 100;
|
||||||
|
$offset = 0;
|
||||||
|
$affected_posts = [];
|
||||||
|
$total_issues = 0;
|
||||||
|
$processed = 0;
|
||||||
|
|
||||||
|
echo "Iniciando análisis...\n";
|
||||||
|
echo "─────────────────────────────────────────────\n";
|
||||||
|
|
||||||
|
while ($offset < $total) {
|
||||||
|
$query = "SELECT id, url, html FROM datos_seo_pagina
|
||||||
|
WHERE html IS NOT NULL AND html != ''
|
||||||
|
ORDER BY id
|
||||||
|
LIMIT {$batch_size} OFFSET {$offset}";
|
||||||
|
|
||||||
|
$result = $conn->query($query);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
echo "Error en consulta: " . $conn->error . "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$processed++;
|
||||||
|
$id = $row['id'];
|
||||||
|
$url = $row['url'] ?? 'N/A';
|
||||||
|
$html = $row['html'];
|
||||||
|
|
||||||
|
$issues = analyzeMalformedLists($html, $malformed_patterns);
|
||||||
|
|
||||||
|
if (!empty($issues)) {
|
||||||
|
$list_counts = countListElements($html);
|
||||||
|
|
||||||
|
$affected_posts[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'url' => $url,
|
||||||
|
'issues' => $issues,
|
||||||
|
'list_counts' => $list_counts
|
||||||
|
];
|
||||||
|
|
||||||
|
$total_issues += count($issues);
|
||||||
|
|
||||||
|
// Mostrar progreso para posts afectados
|
||||||
|
echo "\n[ID: {$id}] " . count($issues) . " problema(s) encontrado(s)\n";
|
||||||
|
echo "URL: {$url}\n";
|
||||||
|
echo "Listas: UL={$list_counts['ul']}, OL={$list_counts['ol']}, LI={$list_counts['li']}\n";
|
||||||
|
|
||||||
|
foreach ($issues as $idx => $issue) {
|
||||||
|
echo " Problema " . ($idx + 1) . ": {$issue['type']} (pos: {$issue['position']})\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mostrar progreso cada 500 registros
|
||||||
|
if ($processed % 500 == 0) {
|
||||||
|
echo "\rProcesados: {$processed}/{$total}...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset += $batch_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n\n";
|
||||||
|
echo "==============================================\n";
|
||||||
|
echo " RESUMEN DEL ANÁLISIS\n";
|
||||||
|
echo "==============================================\n\n";
|
||||||
|
|
||||||
|
echo "Registros analizados: {$processed}\n";
|
||||||
|
echo "Posts con problemas: " . count($affected_posts) . "\n";
|
||||||
|
echo "Total de incidencias: {$total_issues}\n\n";
|
||||||
|
|
||||||
|
if (count($affected_posts) > 0) {
|
||||||
|
echo "─────────────────────────────────────────────\n";
|
||||||
|
echo "DETALLE DE POSTS AFECTADOS\n";
|
||||||
|
echo "─────────────────────────────────────────────\n\n";
|
||||||
|
|
||||||
|
// Agrupar por tipo de problema
|
||||||
|
$by_type = [];
|
||||||
|
foreach ($affected_posts as $post) {
|
||||||
|
foreach ($post['issues'] as $issue) {
|
||||||
|
$type = $issue['type'];
|
||||||
|
if (!isset($by_type[$type])) {
|
||||||
|
$by_type[$type] = [];
|
||||||
|
}
|
||||||
|
$by_type[$type][] = $post['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Por tipo de problema:\n";
|
||||||
|
foreach ($by_type as $type => $ids) {
|
||||||
|
$unique_ids = array_unique($ids);
|
||||||
|
echo " - {$type}: " . count($unique_ids) . " posts\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n─────────────────────────────────────────────\n";
|
||||||
|
echo "LISTA DE IDs AFECTADOS (para revisión manual)\n";
|
||||||
|
echo "─────────────────────────────────────────────\n\n";
|
||||||
|
|
||||||
|
$ids_list = array_column($affected_posts, 'id');
|
||||||
|
echo "IDs: " . implode(', ', $ids_list) . "\n";
|
||||||
|
|
||||||
|
// Generar archivo de reporte
|
||||||
|
$report_file = __DIR__ . '/malformed-lists-report-' . date('Ymd-His') . '.json';
|
||||||
|
$report_data = [
|
||||||
|
'generated_at' => date('Y-m-d H:i:s'),
|
||||||
|
'database' => $db_config['database'],
|
||||||
|
'table' => 'datos_seo_pagina',
|
||||||
|
'total_analyzed' => $processed,
|
||||||
|
'total_affected' => count($affected_posts),
|
||||||
|
'total_issues' => $total_issues,
|
||||||
|
'by_type' => array_map(function($ids) {
|
||||||
|
return array_values(array_unique($ids));
|
||||||
|
}, $by_type),
|
||||||
|
'affected_posts' => $affected_posts
|
||||||
|
];
|
||||||
|
|
||||||
|
if (file_put_contents($report_file, json_encode($report_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
|
||||||
|
echo "\n✓ Reporte JSON guardado en:\n {$report_file}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Muestra de contexto para análisis
|
||||||
|
echo "\n─────────────────────────────────────────────\n";
|
||||||
|
echo "MUESTRA DE CONTEXTO (primeros 3 posts)\n";
|
||||||
|
echo "─────────────────────────────────────────────\n\n";
|
||||||
|
|
||||||
|
$sample = array_slice($affected_posts, 0, 3);
|
||||||
|
foreach ($sample as $post) {
|
||||||
|
echo "POST ID: {$post['id']}\n";
|
||||||
|
echo "URL: {$post['url']}\n";
|
||||||
|
foreach ($post['issues'] as $idx => $issue) {
|
||||||
|
echo " [{$issue['type']}]\n";
|
||||||
|
echo " Contexto: {$issue['context']}\n\n";
|
||||||
|
}
|
||||||
|
echo "───────────────────────\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
echo "✓ No se encontraron listas mal formadas.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
echo "\n✓ Análisis completado.\n";
|
||||||
Reference in New Issue
Block a user