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('/]*>.*?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( '/]*>.*?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( '/]*>.*?\[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( '/]*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'; } } }