/** * Obtiene la lista de archivos que deben incluirse en el backup * * @return array Lista de archivos con información * @since 1.0.0 */ private function get_files_list(): array { $this->log_message("Escaneando archivos del sitio..."); $files = []; $wordpress_root = ABSPATH; // Directorios principales a incluir $include_dirs = [ 'wp-content', 'wp-config.php' ]; // Agregar archivos de WordPress core solo si es necesario $include_wp_core = apply_filters('awb_include_wordpress_core', false); if ($include_wp_core) { $include_dirs = array_merge($include_dirs, [ 'wp-admin', 'wp-includes', 'index.php', '.htaccess' ]); } foreach ($include_dirs as $item) { $full_path = $wordpress_root . $item; if (is_file($full_path)) { if (!$this->file_manager->should_exclude_path($full_path)) { $files[] = [ 'path' => $full_path, 'relative_path' => $item ]; } } elseif (is_dir($full_path)) { $dir_files = $this->scan_directory($full_path, $item); $files = array_merge($files, $dir_files); } } $this->log_message("Escaneo completado: " . count($files) . " archivos encontrados"); return $files; } /** * Escanea un directorio recursivamente * * @param string $directory Directorio a escanear * @param string $relative_base Base relativa para las rutas * @return array Lista de archivos en el directorio * @since 1.0.0 */ private function scan_directory(string $directory, string $relative_base): array { $files = []; if (!is_dir($directory) || !is_readable($directory)) { return $files; } try { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { $file_path = $file->getRealPath(); // Skip si no se puede leer el archivo if (!$file_path || !is_readable($file_path)) { continue; } // Verificar si debe excluirse if ($this->file_manager->should_exclude_path($file_path)) { continue; } // Crear ruta relativa $relative_path = $relative_base . '/' . $iterator->getSubPathName(); $relative_path = str_replace('\\', '/', $relative_path); $files[] = [ 'path' => $file_path, 'relative_path' => $relative_path ]; } } catch (Exception $e) { $this->add_warning("Error escaneando directorio {$directory}: " . $e->getMessage()); } return $files; } /** * Verifica la integridad de los archivos de backup creados * * @return array Resultado de la verificación * @since 1.0.0 */ private function verify_backup_integrity(): array { $this->update_progress('Verificando integridad del backup...', 92); $this->backup_state['current_phase'] = 'verification'; $this->log_message("=== VERIFICACIÓN DE INTEGRIDAD ==="); $errors = []; $warnings = []; // Verificar archivos de backup existentes $backup_files = $this->file_manager->list_backup_files(); $current_backup_files = array_filter($backup_files, function($file) { return strpos($file['filename'], date('Y-m-d_H-i')) !== false; }); foreach ($current_backup_files as $file) { $verification = $this->file_manager->verify_backup_integrity($file['filename']); if (!$verification['is_valid']) { $errors[] = "Archivo {$file['filename']}: " . implode(', ', $verification['errors']); } else { $this->log_message("✓ Archivo {$file['filename']} verificado correctamente"); } if (!empty($verification['warnings'])) { $warnings = array_merge($warnings, $verification['warnings']); } } // Registrar advertencias foreach ($warnings as $warning) { $this->add_warning($warning); } if (!empty($errors)) { return [ 'success' => false, 'error' => 'Errores de integridad encontrados: ' . implode(', ', $errors) ]; } $this->log_message("Verificación de integridad completada exitosamente"); return ['success' => true]; } /** * Completa el proceso de backup exitosamente * * @return array Resultado del backup completado * @since 1.0.0 */ private function complete_backup(): array { $this->update_progress('Finalizando backup...', 95); $this->backup_state['current_phase'] = 'completion'; $this->backup_state['end_time'] = microtime(true); $this->backup_stats['total_processing_time'] = $this->backup_state['end_time'] - $this->backup_state['start_time']; $this->backup_stats['peak_memory_usage'] = memory_get_peak_usage(true); // Generar token de descarga para archivos de backup $download_tokens = []; $backup_files = $this->get_current_backup_files(); foreach ($backup_files as $file) { $token = $this->file_manager->generate_download_token($file['filename']); if ($token) { $download_tokens[$file['type']] = [ 'filename' => $file['filename'], 'token' => $token, 'size' => $file['size'], 'size_formatted' => $file['size_formatted'] ]; } } // Compilar estadísticas finales $total_size = array_sum(array_column($backup_files, 'size')); $total_files = $this->backup_stats['files_backup']['file_count'] ?? 0; $backup_data = [ 'backup_id' => $this->get_current_backup_id(), 'files_count' => $total_files, 'total_size' => $total_size, 'processing_time' => round($this->backup_stats['total_processing_time']), 'backup_files' => $backup_files, 'download_tokens' => $download_tokens, 'stats' => $this->backup_stats, 'warnings' => $this->backup_state['warnings'] ]; // Cerrar archivo de log if ($this->log_handle) { $this->log_message("=== BACKUP COMPLETADO EXITOSAMENTE ==="); $this->log_message("Tiempo total: " . round($this->backup_stats['total_processing_time']) . " segundos"); $this->log_message("Archivos procesados: " . number_format($total_files)); $this->log_message("Tamaño total: " . $this->format_file_size($total_size)); $this->log_message("Memoria pico: " . $this->format_file_size($this->backup_stats['peak_memory_usage'])); $this->file_manager->close_log_file($this->log_handle); $this->log_handle = null; } // Actualizar estado final $this->backup_state['status'] = 'completed'; $this->update_progress('Backup completado exitosamente', 100); // Limpiar estado de progreso $this->clear_backup_progress(); // Enviar notificación de éxito if ($this->config_manager->are_notifications_enabled()) { // Determinar si es un backup grande $max_size_gb = $this->config_manager->get_backup_setting('max_size_gb', 5); $size_gb = $total_size / (1024 * 1024 * 1024); if ($size_gb > $max_size_gb) { // Usar el primer token disponible para notificación $primary_token = reset($download_tokens)['token'] ?? ''; $this->notification_system->send_large_backup_notification($backup_data, $primary_token); } else { $primary_token = reset($download_tokens)['token'] ?? ''; $this->notification_system->send_backup_success_notification($backup_data, $primary_token); } } // Registrar backup en el log del sistema $this->record_backup_completion($backup_data); return [ 'success' => true, 'backup_id' => $backup_data['backup_id'], 'message' => 'Backup completado exitosamente', 'data' => $backup_data ]; } /** * Finaliza el backup con un error * * @param string $error_message Mensaje de error * @since 1.0.0 */ private function finish_backup_with_error(string $error_message): void { $this->backup_state['status'] = 'failed'; $this->backup_state['end_time'] = microtime(true); $this->add_error($error_message); // Registrar en log if ($this->log_handle) { $this->log_message("ERROR: " . $error_message); $this->log_message("=== BACKUP FALLÓ ==="); $this->file_manager->close_log_file($this->log_handle); $this->log_handle = null; } // Limpiar archivos parciales $this->cleanup_partial_backup_files(); // Limpiar estado de progreso $this->clear_backup_progress(); // Enviar notificación de error if ($this->config_manager->are_notifications_enabled()) { $context_data = [ 'error' => $error_message, 'processing_time' => round($this->backup_state['end_time'] - $this->backup_state['start_time']), 'phase' => $this->backup_state['current_phase'], 'warnings' => $this->backup_state['warnings'] ]; $this->notification_system->send_backup_error_notification($error_message, $context_data); } error_log("AWB: Backup falló: " . $error_message); } /** * Maneja solicitud de backup manual desde AJAX * * @since 1.0.0 */ public function handle_manual_backup_request(): void { // Verificar permisos if (!current_user_can('manage_options')) { wp_die('No tienes permisos para realizar esta acción', 403); } // Verificar nonce if (!wp_verify_nonce($_POST['nonce'] ?? '', 'awb_manual_backup')) { wp_die('Token de seguridad inválido', 403); } // Ejecutar backup $result = $this->create_full_backup(); wp_send_json($result); } /** * Maneja solicitud de cancelación de backup * * @since 1.0.0 */ public function handle_cancel_backup_request(): void { if (!current_user_can('manage_options')) { wp_die('No tienes permisos para realizar esta acción', 403); } if (!wp_verify_nonce($_POST['nonce'] ?? '', 'awb_cancel_backup')) { wp_die('Token de seguridad inválido', 403); } $result = $this->cancel_current_backup(); wp_send_json($result); } /** * Maneja solicitud de estado del backup * * @since 1.0.0 */ public function handle_backup_status_request(): void { if (!current_user_can('manage_options')) { wp_die('No tienes permisos para realizar esta acción', 403); } $status = $this->get_backup_status(); wp_send_json($status); } /** * Crea un backup programado (llamado por cron) * * @since 1.0.0 */ public function create_scheduled_backup(): void { // Agregar información de contexto para backups programados $options = [ 'triggered_by' => 'cron', 'scheduled_time' => current_time('mysql') ]; $result = $this->create_full_backup($options); if (!$result['success']) { error_log('AWB: Backup programado falló: ' . $result['error']); } } /** * Cancela el backup actual en progreso * * @return array Resultado de la cancelación * @since 1.0.0 */ public function cancel_current_backup(): array { if (!$this->is_backup_in_progress()) { return [ 'success' => false, 'error' => 'No hay backup en progreso para cancelar' ]; } $this->backup_state['status'] = 'cancelled'; $this->backup_state['end_time'] = microtime(true); // Registrar cancelación en log if ($this->log_handle) { $this->log_message("=== BACKUP CANCELADO POR USUARIO ==="); $this->file_manager->close_log_file($this->log_handle); $this->log_handle = null; } // Limpiar archivos parciales $this->cleanup_partial_backup_files(); // Limpiar estado de progreso $this->clear_backup_progress(); return [ 'success' => true, 'message' => 'Backup cancelado exitosamente' ]; } /** * Obtiene el estado actual del backup * * @return array Estado del backup * @since 1.0.0 */ public function get_backup_status(): array { $saved_progress = get_transient('awb_backup_progress'); if ($saved_progress) { return array_merge($this->backup_state, $saved_progress); } return $this->backup_state; } /** * Verifica si hay un backup en progreso * * @return bool True si hay backup en progreso * @since 1.0.0 */ public function is_backup_in_progress(): bool { $status = $this->get_backup_status(); return in_array($status['status'], ['running'], true); } /** * Estima el tamaño del backup * * @return int Tamaño estimado en bytes * @since 1.0.0 */ private function estimate_backup_size(): int { // Estimar tamaño de base de datos $db_info = $this->database_handler->get_database_summary(); $db_size = ($db_info['total_size_mb'] ?? 0) * 1024 * 1024; // Estimar tamaño de archivos (wp-content principalmente) $wp_content_size = $this->estimate_directory_size(WP_CONTENT_DIR); // Agregar archivos adicionales (wp-config, .htaccess, etc.) $additional_files_size = 1024 * 1024; // 1MB estimado $total_uncompressed = $db_size + $wp_content_size + $additional_files_size; // Estimar compresión (aproximadamente 70% de reducción para archivos típicos) $compression_ratio = 0.3; $estimated_compressed = $total_uncompressed * $compression_ratio; return (int) $estimated_compressed; } /** * Estima el tamaño de un directorio * * @param string $directory Directorio a estimar * @return int Tamaño estimado en bytes * @since 1.0.0 */ private function estimate_directory_size(string $directory): int { if (!is_dir($directory)) { return 0; } $size = 0; $sample_count = 0; $max_samples = 1000; // Limitar muestras para evitar timeout try { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if ($sample_count >= $max_samples) { break; } if ($file->isFile()) { $file_path = $file->getRealPath(); if ($file_path && !$this->file_manager->should_exclude_path($file_path)) { $size += $file->getSize(); $sample_count++; } } } // Si tomamos muestras, extrapolar al total if ($sample_count >= $max_samples) { // Estimar basado en muestras - esto es aproximado $size *= 2; // Factor de extrapolación conservador } } catch (Exception $e) { // Si no podemos estimar, usar un valor por defecto conservador $size = 100 * 1024 * 1024; // 100MB } return $size; } /** * Configura el entorno PHP para el backup * * @since 1.0.0 */ private function configure_php_environment(): void { // Configurar límite de memoria @ini_set('memory_limit', $this->backup_config['memory_limit']); // Configurar límite de tiempo @set_time_limit($this->backup_config['time_limit']); // Desactivar límite de tiempo para CLI if (php_sapi_name() === 'cli') { @set_time_limit(0); } // Configurar handling de errores @ini_set('display_errors', 0); @ini_set('log_errors', 1); // Incrementar límites para operaciones de archivo @ini_set('max_input_time', $this->backup_config['time_limit']); @ini_set('upload_max_filesize', '2G'); @ini_set('post_max_size', '2G'); } /** * Verifica si debe pausar para liberar recursos * * @return bool True si debe pausar * @since 1.0.0 */ private function should_pause_for_resources(): bool { // Verificar memoria $memory_usage = memory_get_usage(true); $memory_limit = $this->convert_to_bytes($this->backup_config['memory_limit']); if ($memory_usage > ($memory_limit * 0.8)) { return true; } // Verificar tiempo de ejecución $execution_time = microtime(true) - $this->backup_state['start_time']; if ($execution_time > ($this->backup_config['time_limit'] * 0.8)) { return true; } return false; } /** * Convierte una cadena de memoria a bytes * * @param string $memory_string Cadena de memoria * @return int Bytes * @since 1.0.0 */ private function convert_to_bytes(string $memory_string): int { $memory_string = trim($memory_string); $last_char = strtolower(substr($memory_string, -1)); $value = (int) $memory_string; switch ($last_char) { case 'g': $value *= 1024; case 'm': $value *= 1024; case 'k': $value *= 1024; } return $value; } /** * Obtiene los archivos de backup del proceso actual * * @return array Lista de archivos de backup * @since 1.0.0 */ private function get_current_backup_files(): array { $files = []; $current_date = date('Y-m-d_H-i'); $all_backup_files = $this->file_manager->list_backup_files(); foreach ($all_backup_files as $file) { if (strpos($file['filename'], $current_date) !== false) { $file['type'] = $this->determine_backup_file_type($file['filename']); $files[] = $file; } } return $files; } /** * Determina el tipo de archivo de backup * * @param string $filename Nombre del archivo * @return string Tipo de archivo * @since 1.0.0 */ private function determine_backup_file_type(string $filename): string { if (strpos($filename, '_database_') !== false) { return 'database'; } elseif (strpos($filename, '_files_') !== false) { return 'files'; } return 'unknown'; } /** * Obtiene el ID del backup actual * * @return string ID del backup * @since 1.0.0 */ private function get_current_backup_id(): string { return 'backup_' . date('Y-m-d_H-i-s'); } /** * Limpia archivos de backup parciales/fallidos * * @since 1.0.0 */ private function cleanup_partial_backup_files(): void { $current_date = date('Y-m-d_H-i'); $backup_files = $this->file_manager->list_backup_files(); foreach ($backup_files as $file) { if (strpos($file['filename'], $current_date) !== false) { $this->file_manager->delete_backup_file($file['filename']); } } } /** * Registra la finalización del backup en los logs del sistema * * @param array $backup_data Datos del backup * @since 1.0.0 */ private function record_backup_completion(array $backup_data): void { global $wpdb; // Insertar registro en la tabla de logs $wpdb->insert( $wpdb->prefix . 'awb_backup_logs', [ 'backup_date' => current_time('mysql'), 'backup_size' => $backup_data['total_size'], 'files_count' => $backup_data['files_count'], 'processing_time' => $backup_data['processing_time'], 'status' => 'completed', 'log_file_path' => $this->get_current_log_file_path() ], ['%s', '%d', '%d', '%d', '%s', '%s'] ); } /** * Obtiene la ruta del archivo de log actual * * @return string Ruta del archivo de log * @since 1.0.0 */ private function get_current_log_file_path(): string { $backup_id = $this->get_current_backup_id(); $log_filename = $this->file_manager->generate_log_filename($backup_id); return $this->file_manager->get_log_file_path($log_filename); } /** * Actualiza el progreso del backup * * @param string $message Mensaje de progreso * @param int $percentage Porcentaje de progreso * @since 1.0.0 */ private function update_progress(string $message, int $percentage): void { $this->backup_state['progress_percentage'] = min(100, max(0, $percentage)); $this->backup_state['current_message'] = $message; $this->save_backup_progress(); $this->log_message("Progreso {$percentage}%: {$message}"); } /** * Guarda el progreso del backup en transients * * @param string $backup_id ID del backup (opcional) * @since 1.0.0 */ private function save_backup_progress(string $backup_id = null): void { $progress_data = [ 'backup_id' => $backup_id ?? $this->get_current_backup_id(), 'status' => $this->backup_state['status'], 'current_phase' => $this->backup_state['current_phase'], 'progress_percentage' => $this->backup_state['progress_percentage'], 'current_message' => $this->backup_state['current_message'] ?? '', 'files_processed' => $this->backup_state['files_processed'], 'total_files' => $this->backup_state['total_files'], 'current_file' => $this->backup_state['current_file'], 'start_time' => $this->backup_state['start_time'], 'errors' => $this->backup_state['errors'], 'warnings' => $this->backup_state['warnings'] ]; set_transient('awb_backup_progress', $progress_data, HOUR_IN_SECONDS); } /** * Limpia el progreso del backup guardado * * @since 1.0.0 */ private function clear_backup_progress(): void { delete_transient('awb_backup_progress'); } /** * Resetea el estado del backup * * @since 1.0.0 */ private function reset_backup_state(): void { $this->backup_state = [ 'status' => 'idle', 'current_phase' => null, 'progress_percentage' => 0, 'start_time' => 0, 'end_time' => 0, 'files_processed' => 0, 'total_files' => 0, 'current_file' => null, 'errors' => [], 'warnings' => [] ]; } /** * Resetea las estadísticas del backup * * @since 1.0.0 */ private function reset_backup_stats(): void { $this->backup_stats = [ 'files_backup' => [ 'file_count' => 0, 'total_size' => 0, 'compressed_size' => 0, 'compression_ratio' => 0, 'processing_time' => 0 ], 'database_backup' => [ 'table_count' => 0, 'row_count' => 0, 'file_size' => 0, 'processing_time' => 0 ], 'total_processing_time' => 0, 'peak_memory_usage' => 0 ]; } /** * Agrega un error al estado del backup * * @param string $error Mensaje de error * @since 1.0.0 */ private function add_error(string $error): void { $this->backup_state['errors'][] = [ 'message' => $error, 'timestamp' => current_time('mysql'), 'phase' => $this->backup_state['current_phase'] ]; } /** * Agrega una advertencia al estado del backup * * @param string $warning Mensaje de advertencia * @since 1.0.0 */ private function add_warning(string $warning): void { $this->backup_state['warnings'][] = [ 'message' => $warning, 'timestamp' => current_time('mysql'), 'phase' => $this->backup_state['current_phase'] ]; } /** * Registra un mensaje en el log * * @param string $message Mensaje a registrar * @param string $level Nivel del mensaje * @since 1.0.0 */ private function log_message(string $message, string $level = 'INFO'): void { if ($this->log_handle) { $this->file_manager->write_to_log($this->log_handle, $message, $level); } } /** * Formatea un tamaño de archivo para lectura humana * * @param int $bytes Tamaño en bytes * @return string Tamaño formateado * @since 1.0.0 */ private function format_file_size(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, 2) . ' ' . $units[$i]; } /** * Limpia procesos de backup huérfanos * * @since 1.0.0 */ public function cleanup_orphaned_processes(): void { // Limpiar transients de progreso antiguos $progress = get_transient('awb_backup_progress'); if ($progress && isset($progress['start_time'])) { $elapsed = time() - $progress['start_time']; // Si han pasado más de 2 horas, considerar huérfano if ($elapsed > 7200) { delete_transient('awb_backup_progress'); error_log('AWB: Proceso de backup huérfano limpiado'); } } } } } /** * Limpia todas las tareas programadas * * @since 1.0.0 */ public function clear_all_scheduled_tasks(): void { foreach ($this->cron_hooks as $hook) { wp_clear_scheduled_hook($hook); } $this->scheduler_state['backup_scheduled'] = false; $this->scheduler_state['maintenance_active'] = false; $this->scheduler_state['health_checks_active'] = false; $this->save_scheduler_state(); error_log('AWB: Todas las tareas programadas han sido limpiadas'); } /** * Limpia solo la programación de backup * * @since 1.0.0 */ private function clear_backup_schedule(): void { wp_clear_scheduled_hook($this->cron_hooks['backup']); $this->scheduler_state['backup_scheduled'] = false; $this->scheduler_state['next_backup_time'] = 0; } /** * Obtiene el estado completo del programador * * @return array Estado del programador * @since 1.0.0 */ public function get_scheduler_status(): array { // Actualizar información en tiempo real $next_backup = wp_next_scheduled($this->cron_hooks['backup']); $next_cleanup = wp_next_scheduled($this->cron_hooks['cleanup']); $next_maintenance = wp_next_scheduled($this->cron_hooks['maintenance']); $next_health_check = wp_next_scheduled($this->cron_hooks['health_check']); $status = [ 'backup' => [ 'scheduled' => (bool) $next_backup, 'next_run' => $next_backup, 'next_run_formatted' => $next_backup ? date('Y-m-d H:i:s', $next_backup) : 'No programado', 'interval' => $this->scheduler_state['backup_interval'], 'last_run' => $this->scheduler_state['last_backup_time'], 'last_run_formatted' => $this->scheduler_state['last_backup_time'] ? date('Y-m-d H:i:s', $this->scheduler_state['last_backup_time']) : 'Nunca' ], 'cleanup' => [ 'scheduled' => (bool) $next_cleanup, 'next_run' => $next_cleanup, 'next_run_formatted' => $next_cleanup ? date('Y-m-d H:i:s', $next_cleanup) : 'No programado' ], 'maintenance' => [ 'scheduled' => (bool) $next_maintenance, 'next_run' => $next_maintenance, 'next_run_formatted' => $next_maintenance ? date('Y-m-d H:i:s', $next_maintenance) : 'No programado', 'active' => $this->scheduler_state['maintenance_active'] ], 'health_check' => [ 'scheduled' => (bool) $next_health_check, 'next_run' => $next_health_check, 'next_run_formatted' => $next_health_check ? date('Y-m-d H:i:s', $next_health_check) : 'No programado', 'active' => $this->scheduler_state['health_checks_active'] ], 'system' => [ 'cron_enabled' => $this->is_wp_cron_enabled(), 'timezone' => wp_timezone_string(), 'server_time' => current_time('mysql'), 'utc_time' => gmdate('Y-m-d H:i:s'), 'next_cron_run' => $this->get_next_cron_run() ] ]; return $status; } /** * Obtiene información del próximo backup * * @return array Información del próximo backup * @since 1.0.0 */ public function get_next_backup_info(): array { $next_backup = wp_next_scheduled($this->cron_hooks['backup']); $interval_days = $this->config_manager->get_backup_setting('interval_days', 7); $backup_time = $this->config_manager->get_backup_setting('backup_time', '03:00'); $info = [ 'scheduled' => (bool) $next_backup, 'timestamp' => $next_backup, 'formatted_time' => $next_backup ? date('Y-m-d H:i:s', $next_backup) : 'No programado', 'interval_days' => $interval_days, 'backup_time' => $backup_time, 'recurrence' => $this->scheduler_state['backup_interval'] ]; if ($next_backup) { $now = time(); $time_until = $next_backup - $now; if ($time_until > 0) { $info['time_until'] = $this->format_time_duration($time_until); $info['time_until_seconds'] = $time_until; } else { $info['time_until'] = 'Vencido'; $info['time_until_seconds'] = 0; } } return $info; } /** * Verifica si WP-Cron está habilitado * * @return bool True si está habilitado * @since 1.0.0 */ private function is_wp_cron_enabled(): bool { return !defined('DISABLE_WP_CRON') || !DISABLE_WP_CRON; } /** * Obtiene el tiempo del próximo cron run del sistema * * @return int Timestamp del próximo cron * @since 1.0.0 */ private function get_next_cron_run(): int { $crons = wp_get_ready_cron_jobs(); if (empty($crons)) { return 0; } $next_times = array_keys($crons); return min($next_times); } /** * Verifica condiciones del sistema antes de ejecutar backup * * @return bool True si las condiciones son favorables * @since 1.0.0 */ private function verify_backup_conditions(): bool { // Verificar carga del servidor (si está disponible) if (function_exists('sys_getloadavg')) { $load = sys_getloadavg(); if ($load[0] > 5.0) { // Carga muy alta error_log('AWB: Postponiendo backup - carga del servidor muy alta: ' . $load[0]); return false; } } // Verificar uso de memoria $memory_usage = memory_get_usage(true); $memory_limit = ini_get('memory_limit'); $memory_limit_bytes = $this->convert_memory_to_bytes($memory_limit); if ($memory_usage > ($memory_limit_bytes * 0.8)) { error_log('AWB: Postponiendo backup - uso de memoria muy alto'); return false; } // Verificar espacio en disco $file_manager = new AWB_File_Manager($this->config_manager); $available_space = $file_manager->get_available_space(); if ($available_space !== false && $available_space < (1024 * 1024 * 1024)) { // Menos de 1GB error_log('AWB: Postponiendo backup - espacio en disco insuficiente'); return false; } // Verificar si hay otros procesos de backup en curso if ($this->backup_creator->is_backup_in_progress()) { error_log('AWB: Postponiendo backup - ya hay backup en progreso'); return false; } return true; } /** * Maneja intentos concurrentes de backup * * @since 1.0.0 */ private function handle_concurrent_backup_attempt(): void { // Re-programar backup para más tarde $retry_time = time() + (30 * MINUTE_IN_SECONDS); // 30 minutos después wp_schedule_single_event($retry_time, $this->cron_hooks['backup']); error_log('AWB: Backup concurrente detectado, re-programado para: ' . date('Y-m-d H:i:s', $retry_time)); } /** * Re-programa backup debido a condiciones desfavorables * * @since 1.0.0 */ private function reschedule_backup_due_to_conditions(): void { // Re-programar backup para 1 hora después $retry_time = time() + HOUR_IN_SECONDS; wp_schedule_single_event($retry_time, $this->cron_hooks['backup']); error_log('AWB: Backup postponed due to system conditions, rescheduled for: ' . date('Y-m-d H:i:s', $retry_time)); } /** * Maneja backup programado exitoso * * @param array $result Resultado del backup * @since 1.0.0 */ private function handle_successful_scheduled_backup(array $result): void { // Actualizar tiempo del último backup exitoso $this->scheduler_state['last_backup_time'] = time(); $this->save_scheduler_state(); // Registrar estadísticas del backup $this->record_backup_statistics($result, 'success'); } /** * Maneja backup programado fallido * * @param array $result Resultado del backup * @since 1.0.0 */ private function handle_failed_scheduled_backup(array $result): void { // Registrar estadísticas del backup fallido $this->record_backup_statistics($result, 'failed'); // Programar reintento si está habilitado if (isset($result['auto_retry']) && $result['auto_retry']) { $retry_time = time() + (2 * HOUR_IN_SECONDS); // 2 horas después wp_schedule_single_event($retry_time, $this->cron_hooks['backup']); error_log('AWB: Backup fallido, programando reintento para: ' . date('Y-m-d H:i:s', $retry_time)); } } /** * Registra estadísticas del backup * * @param array $result Resultado del backup * @param string $status Estado del backup * @since 1.0.0 */ private function record_backup_statistics(array $result, string $status): void { $stats = get_option('awb_backup_statistics', []); $date = current_time('Y-m-d'); if (!isset($stats[$date])) { $stats[$date] = [ 'successful' => 0, 'failed' => 0, 'total_size' => 0, 'total_time' => 0 ]; } $stats[$date][$status === 'success' ? 'successful' : 'failed']++; if ($status === 'success' && isset($result['data'])) { $stats[$date]['total_size'] += $result['data']['total_size'] ?? 0; $stats[$date]['total_time'] += $result['data']['processing_time'] ?? 0; } // Mantener solo los últimos 90 días $stats = array_slice($stats, -90, null, true); update_option('awb_backup_statistics', $stats); } /** * Optimiza las tablas del plugin * * @since 1.0.0 */ private function optimize_plugin_tables(): void { global $wpdb; $tables = [ $wpdb->prefix . 'awb_backup_logs', $wpdb->prefix . 'awb_download_tokens' ]; foreach ($tables as $table) { $wpdb->query("OPTIMIZE TABLE `{$table}`"); } } /** * Verifica integridad de backups existentes * * @since 1.0.0 */ private function verify_existing_backups_integrity(): void { $file_manager = new AWB_File_Manager($this->config_manager); $backup_files = $file_manager->list_backup_files(); $corrupted_files = []; foreach ($backup_files as $file) { $verification = $file_manager->verify_backup_integrity($file['filename']); if (!$verification['is_valid']) { $corrupted_files[] = $file['filename']; error_log("AWB: Archivo corrupto detectado: {$file['filename']}"); } } if (!empty($corrupted_files)) { // Notificar sobre archivos corruptos $this->send_corruption_alert($corrupted_files); } } /** * Limpia logs antiguos * * @since 1.0.0 */ private function cleanup_old_logs(): void { global $wpdb; // Eliminar logs de backup mayores a 90 días $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->prefix}awb_backup_logs WHERE backup_date < DATE_SUB(NOW(), INTERVAL %d DAY)", 90 )); // Limpiar logs de archivos $file_manager = new AWB_File_Manager($this->config_manager); $logs_directory = $file_manager->get_log_file_path(''); if (is_dir($logs_directory)) { $cutoff_time = time() - (90 * DAY_IN_SECONDS); $files = glob($logs_directory . '/*.log'); foreach ($files as $file) { if (filemtime($file) < $cutoff_time) { unlink($file); } } } } /** * Verifica y repara la programación de tareas * * @since 1.0.0 */ private function verify_and_repair_schedule(): void { $issues_found = false; // Verificar backup principal if (!wp_next_scheduled($this->cron_hooks['backup'])) { error_log('AWB: Backup no programado, reparando...'); $this->schedule_backup_task(); $issues_found = true; } // Verificar limpieza if (!wp_next_scheduled($this->cron_hooks['cleanup'])) { error_log('AWB: Limpieza no programada, reparando...'); wp_schedule_event(time(), 'daily', $this->cron_hooks['cleanup']); $issues_found = true; } // Verificar mantenimiento if (!wp_next_scheduled($this->cron_hooks['maintenance'])) { error_log('AWB: Mantenimiento no programado, reparando...'); $this->schedule_maintenance_tasks(); $issues_found = true; } // Verificar health checks if (!wp_next_scheduled($this->cron_hooks['health_check'])) { error_log('AWB: Health checks no programados, reparando...'); $this->schedule_health_checks(); $issues_found = true; } if ($issues_found) { error_log('AWB: Problemas de programación reparados'); } } /** * Limpia archivos temporales * * @since 1.0.0 */ private function cleanup_temporary_files(): void { $temp_dirs = [ sys_get_temp_dir(), WP_CONTENT_DIR . '/tmp', WP_CONTENT_DIR . '/temp' ]; foreach ($temp_dirs as $temp_dir) { if (!is_dir($temp_dir)) { continue; } $files = glob($temp_dir . '/awb_*'); foreach ($files as $file) { if (is_file($file) && (time() - filemtime($file)) > DAY_IN_SECONDS) { unlink($file); } } } } /** * Estima el espacio liberado por la limpieza * * @param int $deleted_files_count Número de archivos eliminados * @return int Espacio estimado en bytes * @since 1.0.0 */ private function estimate_space_freed(int $deleted_files_count): int { // Estimación basada en tamaño promedio de backup $average_backup_size = 50 * 1024 * 1024; // 50MB promedio return $deleted_files_count * $average_backup_size; } /** * Envía alerta de problemas de salud * * @param array $health_issues Lista de problemas * @since 1.0.0 */ private function send_health_alert(array $health_issues): void { $notification_system = new AWB_Notification_System($this->config_manager); $error_message = 'Problemas críticos detectados en el sistema de backup: ' . implode(', ', $health_issues); $context = [ 'health_issues' => $health_issues, 'check_time' => current_time('mysql'), 'system_status' => $this->get_scheduler_status() ]; $notification_system->send_backup_error_notification($error_message, $context); } /** * Envía alerta de corrupción de archivos * * @param array $corrupted_files Lista de archivos corruptos * @since 1.0.0 */ private function send_corruption_alert(array $corrupted_files): void { $notification_system = new AWB_Notification_System($this->config_manager); $error_message = 'Archivos de backup corruptos detectados: ' . implode(', ', $corrupted_files); $context = [ 'corrupted_files' => $corrupted_files, 'detection_time' => current_time('mysql'), 'recommendation' => 'Se recomienda crear un nuevo backup inmediatamente' ]; $notification_system->send_backup_error_notification($error_message, $context); } /** * Verifica cambios de zona horaria * * @since 1.0.0 */ public function check_timezone_changes(): void { $current_timezone = wp_timezone_string(); $saved_timezone = get_option('awb_current_timezone', ''); if ($saved_timezone && $saved_timezone !== $current_timezone) { error_log("AWB: Cambio de zona horaria detectado: {$saved_timezone} -> {$current_timezone}"); // Re-programar todas las tareas con la nueva zona horaria $this->clear_all_scheduled_tasks(); $this->schedule_initial_tasks(); } update_option('awb_current_timezone', $current_timezone); } /** * Limpia tareas huérfanas * * @since 1.0.0 */ public function cleanup_orphaned_tasks(): void { $crons = wp_get_scheduled_events(); $orphaned_count = 0; foreach ($crons as $timestamp => $cron) { foreach ($cron as $hook => $events) { // Verificar si es un hook de nuestro plugin pero no está en nuestra lista if (strpos($hook, 'awb_') === 0 && !in_array($hook, $this->cron_hooks)) { wp_clear_scheduled_hook($hook); $orphaned_count++; } } } if ($orphaned_count > 0) { error_log("AWB: {$orphaned_count} tareas huérfanas eliminadas"); } } /** * Convierte string de memoria a bytes * * @param string $memory_string String de memoria * @return int Bytes * @since 1.0.0 */ private function convert_memory_to_bytes(string $memory_string): int { $memory_string = trim($memory_string); $last_char = strtolower(substr($memory_string, -1)); $value = (int) $memory_string; switch ($last_char) { case 'g': $value *= 1024; case 'm': $value *= 1024; case 'k': $value *= 1024; } return $value; } /** * Formatea duración de tiempo en formato legible * * @param int $seconds Segundos * @return string Duración formateada * @since 1.0.0 */ private function format_time_duration(int $seconds): string { if ($seconds < 60) { return $seconds . ' segundos'; } elseif ($seconds < 3600) { return round($seconds / 60) . ' minutos'; } elseif ($seconds < 86400) { return round($seconds / 3600, 1) . ' horas'; } else { return round($seconds / 86400, 1) . ' días'; } } /** * Obtiene estadísticas de backups * * @param int $days Número de días a incluir * @return array Estadísticas * @since 1.0.0 */ public function get_backup_statistics(int $days = 30): array { $stats = get_option('awb_backup_statistics', []); $end_date = current_time('Y-m-d'); $start_date = date('Y-m-d', strtotime("-{$days} days")); $filtered_stats = []; $totals = [ 'successful' => 0, 'failed' => 0, 'total_size' => 0, 'total_time' => 0 ]; foreach ($stats as $date => $day_stats) { if ($date >= $start_date && $date <= $end_date) { $filtered_stats[$date] = $day_stats; $totals['successful'] += $day_stats['successful']; $totals['failed'] += $day_stats['failed']; $totals['total_size'] += $day_stats['total_size']; $totals['total_time'] += $day_stats['total_time']; } } $total_backups = $totals['successful'] + $totals['failed']; $success_rate = $total_backups > 0 ? ($totals['successful'] / $total_backups) * 100 : 0; return [ 'period_days' => $days, 'daily_stats' => $filtered_stats, 'totals' => $totals, 'success_rate' => round($success_rate, 2), 'average_size' => $totals['successful'] > 0 ? $totals['total_size'] / $totals['successful'] : 0, 'average_time' => $totals['successful'] > 0 ? $totals['total_time'] / $totals['successful'] : 0 ]; } /** * Obtiene información de cron para debugging * * @return array Información de debugging * @since 1.0.0 */ public function get_cron_debug_info(): array { $crons = wp_get_scheduled_events(); $awb_crons = []; foreach ($crons as $timestamp => $cron) { foreach ($cron as $hook => $events) { if (strpos($hook, 'awb_') === 0) { $awb_crons[] = [ 'hook' => $hook, 'timestamp' => $timestamp, 'formatted_time' => date('Y-m-d H:i:s', $timestamp), 'args' => $events[0]['args'] ?? [], 'interval' => $events[0]['interval'] ?? null ]; } } } return [ 'wp_cron_enabled' => $this->is_wp_cron_enabled(), 'current_time' => current_time('mysql'), 'timezone' => wp_timezone_string(), 'awb_scheduled_events' => $awb_crons, 'total_scheduled_events' => count($crons), 'next_cron_run' => $this->get_next_cron_run() ]; } }
Parse error: syntax error, unexpected identifier "awbCreateManualBackup", expecting "function" in /home/litl/litl.com.ar/wp-content/plugins/advanced-wordpress-backup/includes/class-awb-admin-interface.php on line 1313