- Añadir PageVisibility use case y repositorio - Implementar PageTypeDetector para detectar home/single/page/archive - Actualizar FieldMappers con soporte show_on_[page_type] - Extender FormBuilders con UI de visibilidad por página - Refactorizar Renderers para evaluar visibilidad dinámica - Limpiar schemas removiendo campos de visibilidad legacy - Añadir MigrationCommand para migrar configuraciones existentes - Implementar adsense-loader.js para carga lazy de ads - Actualizar front-page.php con nueva estructura - Extender DIContainer con nuevos servicios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
353 lines
13 KiB
PHP
353 lines
13 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace ROITheme\Shared\Infrastructure\CLI;
|
|
|
|
use WP_CLI;
|
|
|
|
/**
|
|
* Comando WP-CLI para limpiar contenido Thrive congelado de páginas
|
|
*
|
|
* LIMPIEZA QUIRÚRGICA CON VALIDACIONES DE SEGURIDAD:
|
|
* - Elimina H2 con data-shortcode="tcb_post_title"
|
|
* - Elimina paginación rota ([tcb_pagination_current_page], [tcb_pagination_total_pages])
|
|
* - PRESERVA todo el demás contenido incluyendo shortcodes [roi_apu_search]
|
|
* - Verifica que shortcodes importantes NO sean eliminados
|
|
* - Aborta si se detecta pérdida excesiva de contenido (>50%)
|
|
*
|
|
* USO:
|
|
* wp roi-theme clean_thrive --dry-run # Ver qué se limpiaría (OBLIGATORIO primero)
|
|
* wp roi-theme clean_thrive # Ejecutar limpieza real
|
|
*
|
|
* SEGURIDAD:
|
|
* - Verifica preservación de shortcodes [roi_apu_search]
|
|
* - Máximo 50% de reducción de contenido permitida
|
|
* - Valida cada preg_replace para evitar null returns
|
|
*/
|
|
final class CleanThriveContentCommand
|
|
{
|
|
/**
|
|
* IDs de páginas buscar-apus afectadas
|
|
*/
|
|
private const AFFECTED_PAGE_IDS = [
|
|
107264, 107312, 107340, 107345, 107351, 107357, 107362,
|
|
107369, 107374, 107379, 107384, 107389, 107395, 107399,
|
|
107403, 107407, 107411, 107416, 107421, 107425, 185752
|
|
];
|
|
|
|
/**
|
|
* Otras páginas con contenido Thrive (Blog, Curso)
|
|
*/
|
|
private const OTHER_AFFECTED_IDS = [252030, 290709];
|
|
|
|
/**
|
|
* Shortcodes que DEBEN ser preservados
|
|
*/
|
|
private const PROTECTED_SHORTCODES = [
|
|
'[roi_apu_search',
|
|
'[roi_',
|
|
];
|
|
|
|
/**
|
|
* Máximo porcentaje de contenido que puede eliminarse
|
|
*/
|
|
private const MAX_CONTENT_LOSS_PERCENT = 50;
|
|
|
|
public function __invoke(array $args, array $assoc_args): void
|
|
{
|
|
$dryRun = isset($assoc_args['dry-run']);
|
|
$includeOthers = isset($assoc_args['include-others']);
|
|
$force = isset($assoc_args['force']);
|
|
|
|
$pageIds = self::AFFECTED_PAGE_IDS;
|
|
if ($includeOthers) {
|
|
$pageIds = array_merge($pageIds, self::OTHER_AFFECTED_IDS);
|
|
}
|
|
|
|
WP_CLI::log('');
|
|
WP_CLI::log('╔══════════════════════════════════════════════════════════════════╗');
|
|
WP_CLI::log('║ LIMPIEZA QUIRÚRGICA DE CONTENIDO THRIVE CONGELADO (v2.0) ║');
|
|
WP_CLI::log('║ Con validaciones de seguridad para proteger shortcodes ║');
|
|
WP_CLI::log('╚══════════════════════════════════════════════════════════════════╝');
|
|
WP_CLI::log('');
|
|
|
|
if ($dryRun) {
|
|
WP_CLI::warning('MODO DRY-RUN: No se modificará ningún contenido');
|
|
} else {
|
|
WP_CLI::error('MODO REAL DESHABILITADO: Ejecuta primero con --dry-run', false);
|
|
WP_CLI::log('');
|
|
WP_CLI::log('Para ejecutar la limpieza real, primero revisa el dry-run:');
|
|
WP_CLI::log(' wp roi-theme clean_thrive --dry-run');
|
|
WP_CLI::log('');
|
|
WP_CLI::log('Si el dry-run es correcto y deseas ejecutar:');
|
|
WP_CLI::log(' wp roi-theme clean_thrive --force');
|
|
|
|
if (!$force) {
|
|
return;
|
|
}
|
|
WP_CLI::warning('MODO REAL CON --force: Se modificará el contenido');
|
|
}
|
|
|
|
WP_CLI::log('');
|
|
WP_CLI::log('Páginas a procesar: ' . count($pageIds));
|
|
WP_CLI::log('Shortcodes protegidos: ' . implode(', ', self::PROTECTED_SHORTCODES));
|
|
WP_CLI::log('Máxima pérdida permitida: ' . self::MAX_CONTENT_LOSS_PERCENT . '%');
|
|
WP_CLI::log('');
|
|
|
|
$totalH2Removed = 0;
|
|
$totalPaginationRemoved = 0;
|
|
$totalBytesFreed = 0;
|
|
$pagesModified = 0;
|
|
$pagesSkipped = 0;
|
|
$errors = [];
|
|
|
|
foreach ($pageIds as $id) {
|
|
$page = get_post($id);
|
|
if (!$page) {
|
|
WP_CLI::warning("Página {$id} no encontrada, saltando...");
|
|
continue;
|
|
}
|
|
|
|
$originalContent = $page->post_content;
|
|
$originalSize = strlen($originalContent);
|
|
|
|
// Verificar si tiene contenido Thrive que limpiar
|
|
$hasThrive = strpos($originalContent, 'tcb_post_title') !== false ||
|
|
strpos($originalContent, 'tcb_pagination') !== false;
|
|
|
|
if (!$hasThrive) {
|
|
WP_CLI::log(sprintf(
|
|
"[SIN THRIVE] ID %d: %s",
|
|
$id,
|
|
mb_substr($page->post_title, 0, 50)
|
|
));
|
|
continue;
|
|
}
|
|
|
|
// Contar elementos antes de limpiar
|
|
$h2Count = preg_match_all('/<h2[^>]*>.*?data-shortcode="tcb_post_title".*?<\/h2>/s', $originalContent);
|
|
|
|
// Contar shortcodes protegidos antes
|
|
$protectedBefore = $this->countProtectedShortcodes($originalContent);
|
|
|
|
// Limpiar contenido con validación
|
|
$cleanResult = $this->cleanContentSafely($originalContent);
|
|
|
|
if ($cleanResult['error']) {
|
|
$errors[] = "ID {$id}: {$cleanResult['error']}";
|
|
WP_CLI::error(sprintf(
|
|
"[ERROR] ID %d: %s - %s",
|
|
$id,
|
|
mb_substr($page->post_title, 0, 40),
|
|
$cleanResult['error']
|
|
), false);
|
|
$pagesSkipped++;
|
|
continue;
|
|
}
|
|
|
|
$cleanedContent = $cleanResult['content'];
|
|
$newSize = strlen($cleanedContent);
|
|
|
|
// Contar shortcodes protegidos después
|
|
$protectedAfter = $this->countProtectedShortcodes($cleanedContent);
|
|
|
|
// VALIDACIÓN CRÍTICA: Verificar shortcodes protegidos
|
|
if ($protectedAfter < $protectedBefore) {
|
|
$errors[] = "ID {$id}: Se perderían shortcodes protegidos ({$protectedBefore} → {$protectedAfter})";
|
|
WP_CLI::error(sprintf(
|
|
"[ABORTADO] ID %d: Se perderían shortcodes protegidos (%d → %d)",
|
|
$id,
|
|
$protectedBefore,
|
|
$protectedAfter
|
|
), false);
|
|
$pagesSkipped++;
|
|
continue;
|
|
}
|
|
|
|
// Verificar pérdida excesiva de contenido
|
|
$lossPercent = $originalSize > 0 ? (($originalSize - $newSize) / $originalSize) * 100 : 0;
|
|
if ($lossPercent > self::MAX_CONTENT_LOSS_PERCENT) {
|
|
$errors[] = "ID {$id}: Pérdida excesiva de contenido ({$lossPercent}%)";
|
|
WP_CLI::error(sprintf(
|
|
"[ABORTADO] ID %d: Pérdida excesiva %.1f%% (máx %d%%)",
|
|
$id,
|
|
$lossPercent,
|
|
self::MAX_CONTENT_LOSS_PERCENT
|
|
), false);
|
|
$pagesSkipped++;
|
|
continue;
|
|
}
|
|
|
|
// Verificar si hubo cambios
|
|
$hasChanges = $originalContent !== $cleanedContent;
|
|
$bytesSaved = $originalSize - $newSize;
|
|
|
|
// Contar paginación removida
|
|
$paginationRemoved = (
|
|
strpos($originalContent, 'tcb_pagination_current_page') !== false &&
|
|
strpos($cleanedContent, 'tcb_pagination_current_page') === false
|
|
) ? 1 : 0;
|
|
|
|
if ($hasChanges) {
|
|
$pagesModified++;
|
|
$totalH2Removed += $h2Count;
|
|
$totalPaginationRemoved += $paginationRemoved;
|
|
$totalBytesFreed += $bytesSaved;
|
|
|
|
$status = $dryRun ? '[DRY-RUN]' : '[LIMPIADO]';
|
|
WP_CLI::log(sprintf(
|
|
"%s ID %d: %s",
|
|
$status,
|
|
$id,
|
|
mb_substr($page->post_title, 0, 50) . (mb_strlen($page->post_title) > 50 ? '...' : '')
|
|
));
|
|
WP_CLI::log(sprintf(
|
|
" → H2 eliminados: %d | Paginación: %s | Pérdida: %.1f%%",
|
|
$h2Count,
|
|
$paginationRemoved ? 'Sí' : 'No',
|
|
$lossPercent
|
|
));
|
|
WP_CLI::log(sprintf(
|
|
" → Shortcodes [roi_*] preservados: %d | Bytes liberados: %s",
|
|
$protectedAfter,
|
|
$this->formatBytes($bytesSaved)
|
|
));
|
|
|
|
if (!$dryRun && $force) {
|
|
wp_update_post([
|
|
'ID' => $id,
|
|
'post_content' => $cleanedContent
|
|
]);
|
|
}
|
|
} else {
|
|
WP_CLI::log(sprintf(
|
|
"[SIN CAMBIOS] ID %d: %s",
|
|
$id,
|
|
mb_substr($page->post_title, 0, 50)
|
|
));
|
|
}
|
|
}
|
|
|
|
WP_CLI::log('');
|
|
WP_CLI::log('════════════════════════════════════════════════════════════════════');
|
|
WP_CLI::log('RESUMEN:');
|
|
WP_CLI::log(sprintf(' Páginas modificadas: %d', $pagesModified));
|
|
WP_CLI::log(sprintf(' Páginas omitidas: %d', $pagesSkipped));
|
|
WP_CLI::log(sprintf(' Total H2 eliminados: %d', $totalH2Removed));
|
|
WP_CLI::log(sprintf(' Paginaciones removidas: %d', $totalPaginationRemoved));
|
|
WP_CLI::log(sprintf(' Espacio liberado: %s', $this->formatBytes($totalBytesFreed)));
|
|
WP_CLI::log('════════════════════════════════════════════════════════════════════');
|
|
|
|
if (count($errors) > 0) {
|
|
WP_CLI::log('');
|
|
WP_CLI::warning('ERRORES ENCONTRADOS:');
|
|
foreach ($errors as $error) {
|
|
WP_CLI::log(" - {$error}");
|
|
}
|
|
}
|
|
|
|
if ($dryRun && $pagesModified > 0 && count($errors) === 0) {
|
|
WP_CLI::log('');
|
|
WP_CLI::success('Dry-run completado SIN errores.');
|
|
WP_CLI::log('');
|
|
WP_CLI::warning('Para ejecutar la limpieza real:');
|
|
WP_CLI::log(' wp roi-theme clean_thrive --force');
|
|
} elseif (!$dryRun && $force && $pagesModified > 0) {
|
|
WP_CLI::log('');
|
|
WP_CLI::success('Limpieza completada exitosamente.');
|
|
WP_CLI::log('');
|
|
WP_CLI::warning('IMPORTANTE: Purga el caché del sitio para ver los cambios.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limpia el contenido con validaciones de seguridad
|
|
*
|
|
* @return array{content: string, error: string|null}
|
|
*/
|
|
private function cleanContentSafely(string $content): array
|
|
{
|
|
$originalContent = $content;
|
|
|
|
// 1. Eliminar H2 con data-shortcode="tcb_post_title"
|
|
$result = preg_replace(
|
|
'/<h2[^>]*>.*?data-shortcode="tcb_post_title".*?<\/h2>/s',
|
|
'',
|
|
$content
|
|
);
|
|
if ($result === null) {
|
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón H2'];
|
|
}
|
|
$content = $result;
|
|
|
|
// 2. Eliminar paginación Thrive rota
|
|
$result = preg_replace(
|
|
'/<p[^>]*>.*?\[tcb_pagination_current_page\].*?\[tcb_pagination_total_pages\].*?<\/p>/s',
|
|
'',
|
|
$content
|
|
);
|
|
if ($result === null) {
|
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón paginación'];
|
|
}
|
|
$content = $result;
|
|
|
|
// 3. Eliminar botones de paginación Thrive
|
|
$result = preg_replace(
|
|
'/<p[^>]*data-button_layout="[^"]*"[^>]*data-page="[^"]*"[^>]*>.*?<\/p>/s',
|
|
'',
|
|
$content
|
|
);
|
|
if ($result === null) {
|
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en patrón botones'];
|
|
}
|
|
$content = $result;
|
|
|
|
// 4. Eliminar shortcodes Thrive huérfanos
|
|
$content = str_replace('[tcb_pagination_current_page]', '', $content);
|
|
$content = str_replace('[tcb_pagination_total_pages]', '', $content);
|
|
|
|
// 5. Limpiar múltiples líneas vacías (con validación)
|
|
$result = preg_replace('/(\r?\n){3,}/', "\n\n", $content);
|
|
if ($result === null) {
|
|
return ['content' => $originalContent, 'error' => 'preg_replace falló en limpieza líneas'];
|
|
}
|
|
$content = $result;
|
|
|
|
// 6. Trim
|
|
$content = trim($content);
|
|
|
|
// Validación final: no retornar vacío si original tenía contenido
|
|
if (empty($content) && !empty($originalContent)) {
|
|
return ['content' => $originalContent, 'error' => 'El contenido quedó vacío'];
|
|
}
|
|
|
|
return ['content' => $content, 'error' => null];
|
|
}
|
|
|
|
/**
|
|
* Cuenta shortcodes protegidos en el contenido
|
|
*/
|
|
private function countProtectedShortcodes(string $content): int
|
|
{
|
|
$count = 0;
|
|
foreach (self::PROTECTED_SHORTCODES as $shortcode) {
|
|
$count += substr_count($content, $shortcode);
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Formatea bytes a formato legible
|
|
*/
|
|
private function formatBytes(int $bytes): string
|
|
{
|
|
if ($bytes < 1024) {
|
|
return $bytes . ' B';
|
|
} elseif ($bytes < 1048576) {
|
|
return round($bytes / 1024, 1) . ' KB';
|
|
} else {
|
|
return round($bytes / 1048576, 2) . ' MB';
|
|
}
|
|
}
|
|
}
|