/**
* 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