logger = TigerStyleSEO_Backup_Logger::instance(); } /** * Validate backup by ID */ public function validate_backup($backup_id) { try { $storage_manager = new TigerStyleSEO_Storage_Manager(); // Check if backup exists if (!$storage_manager->backup_exists($backup_id)) { throw new Exception(__('Backup does not exist', 'tigerstyle-heat')); } // Download backup for validation $backup_file = $storage_manager->download_backup($backup_id); // Extract backup temporarily $temp_dir = $this->extract_backup_for_validation($backup_file); try { // Validate backup structure and integrity $result = $this->validate_backup_directory($temp_dir); return $result; } finally { // Cleanup temporary files $this->cleanup_temp_directory($temp_dir); } } catch (Exception $e) { $this->logger->error('Backup validation failed: ' . $e->getMessage(), array( 'backup_id' => $backup_id )); throw $e; } } /** * Validate backup directory structure and integrity */ public function validate_backup_directory($backup_dir, $manifest = null) { $validation_results = array( 'valid' => true, 'errors' => array(), 'warnings' => array(), 'checks' => array() ); try { // Load manifest if not provided if (!$manifest) { $manifest = $this->load_manifest($backup_dir); } // Check required files $validation_results['checks']['manifest'] = $this->validate_manifest($backup_dir, $manifest); $validation_results['checks']['structure'] = $this->validate_backup_structure($backup_dir, $manifest); $validation_results['checks']['database'] = $this->validate_database_backup($backup_dir, $manifest); $validation_results['checks']['files'] = $this->validate_files_backup($backup_dir, $manifest); $validation_results['checks']['checksums'] = $this->validate_checksums($backup_dir, $manifest); // Collect errors and warnings foreach ($validation_results['checks'] as $check_name => $check_result) { if (!$check_result['valid']) { $validation_results['valid'] = false; $validation_results['errors'] = array_merge( $validation_results['errors'], $check_result['errors'] ); } $validation_results['warnings'] = array_merge( $validation_results['warnings'], $check_result['warnings'] ); } $this->logger->info('Backup validation completed', array( 'backup_dir' => basename($backup_dir), 'valid' => $validation_results['valid'], 'error_count' => count($validation_results['errors']), 'warning_count' => count($validation_results['warnings']) )); } catch (Exception $e) { $validation_results['valid'] = false; $validation_results['errors'][] = $e->getMessage(); $this->logger->error('Backup validation exception: ' . $e->getMessage(), array( 'backup_dir' => $backup_dir )); } return $validation_results; } /** * Load and validate manifest file */ private function load_manifest($backup_dir) { $manifest_file = $backup_dir . '/manifest.json'; if (!file_exists($manifest_file)) { throw new Exception(__('Manifest file not found', 'tigerstyle-heat')); } $manifest_content = file_get_contents($manifest_file); $manifest = json_decode($manifest_content, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception(__('Invalid manifest format: ', 'tigerstyle-heat') . json_last_error_msg()); } return $manifest; } /** * Validate manifest structure and content */ private function validate_manifest($backup_dir, $manifest) { $result = array( 'valid' => true, 'errors' => array(), 'warnings' => array() ); // Check required manifest fields $required_fields = array( 'backup_id', 'created_at', 'wordpress_version', 'site_url', 'plugin_version', 'options' ); foreach ($required_fields as $field) { if (!isset($manifest[$field])) { $result['errors'][] = sprintf(__('Missing required manifest field: %s', 'tigerstyle-heat'), $field); $result['valid'] = false; } } // Validate manifest data types if (isset($manifest['created_at']) && !$this->is_valid_datetime($manifest['created_at'])) { $result['errors'][] = __('Invalid created_at format in manifest', 'tigerstyle-heat'); $result['valid'] = false; } if (isset($manifest['site_url']) && !filter_var($manifest['site_url'], FILTER_VALIDATE_URL)) { $result['warnings'][] = __('Invalid site_url format in manifest', 'tigerstyle-heat'); } // Check WordPress version compatibility if (isset($manifest['wordpress_version'])) { $current_wp_version = get_bloginfo('version'); if (version_compare($manifest['wordpress_version'], $current_wp_version, '>')) { $result['warnings'][] = sprintf( __('Backup was created with newer WordPress version (%s) than current (%s)', 'tigerstyle-heat'), $manifest['wordpress_version'], $current_wp_version ); } } // Validate options structure if (isset($manifest['options']) && !is_array($manifest['options'])) { $result['errors'][] = __('Invalid options structure in manifest', 'tigerstyle-heat'); $result['valid'] = false; } return $result; } /** * Validate backup directory structure */ private function validate_backup_structure($backup_dir, $manifest) { $result = array( 'valid' => true, 'errors' => array(), 'warnings' => array() ); // Check for manifest file if (!file_exists($backup_dir . '/manifest.json')) { $result['errors'][] = __('Manifest file is missing', 'tigerstyle-heat'); $result['valid'] = false; } // Check for database backup if included if (isset($manifest['options']['include_database']) && $manifest['options']['include_database']) { if (!file_exists($backup_dir . '/database.sql')) { $result['errors'][] = __('Database backup file is missing', 'tigerstyle-heat'); $result['valid'] = false; } } // Check for files backup if included if (isset($manifest['options']['include_files']) && $manifest['options']['include_files']) { if (!is_dir($backup_dir . '/files')) { $result['errors'][] = __('Files backup directory is missing', 'tigerstyle-heat'); $result['valid'] = false; } } return $result; } /** * Validate database backup */ private function validate_database_backup($backup_dir, $manifest) { $result = array( 'valid' => true, 'errors' => array(), 'warnings' => array() ); // Skip if database backup not included if (!isset($manifest['options']['include_database']) || !$manifest['options']['include_database']) { return $result; } $sql_file = $backup_dir . '/database.sql'; if (!file_exists($sql_file)) { $result['errors'][] = __('Database backup file not found', 'tigerstyle-heat'); $result['valid'] = false; return $result; } // Check file size $file_size = filesize($sql_file); if ($file_size === 0) { $result['errors'][] = __('Database backup file is empty', 'tigerstyle-heat'); $result['valid'] = false; return $result; } // Validate SQL content structure $sql_validation = $this->validate_sql_file($sql_file); if (!$sql_validation['valid']) { $result['errors'] = array_merge($result['errors'], $sql_validation['errors']); $result['warnings'] = array_merge($result['warnings'], $sql_validation['warnings']); $result['valid'] = false; } // Validate checksum if available if (isset($manifest['database_info']['checksum'])) { $actual_checksum = md5_file($sql_file); if ($actual_checksum !== $manifest['database_info']['checksum']) { $result['errors'][] = __('Database backup checksum mismatch', 'tigerstyle-heat'); $result['valid'] = false; } } // Check file size against manifest if (isset($manifest['database_info']['file_size'])) { if ($file_size !== $manifest['database_info']['file_size']) { $result['warnings'][] = __('Database backup file size differs from manifest', 'tigerstyle-heat'); } } return $result; } /** * Validate SQL file structure */ private function validate_sql_file($sql_file) { $result = array( 'valid' => true, 'errors' => array(), 'warnings' => array() ); $handle = fopen($sql_file, 'r'); if (!$handle) { $result['errors'][] = __('Cannot read database backup file', 'tigerstyle-heat'); $result['valid'] = false; return $result; } $line_count = 0; $has_wp_tables = false; $has_create_statements = false; $has_insert_statements = false; try { while (($line = fgets($handle)) !== false && $line_count < 1000) { // Check first 1000 lines $line = trim($line); $line_count++; // Skip empty lines and comments if (empty($line) || strpos($line, '--') === 0) { continue; } // Check for WordPress table patterns if (preg_match('/CREATE TABLE.*wp_\w+/', $line) || preg_match('/INSERT INTO.*wp_\w+/', $line)) { $has_wp_tables = true; } // Check for CREATE TABLE statements if (strpos($line, 'CREATE TABLE') !== false) { $has_create_statements = true; } // Check for INSERT statements if (strpos($line, 'INSERT INTO') !== false) { $has_insert_statements = true; } // Check for SQL syntax errors (basic validation) if (!$this->is_valid_sql_line($line)) { $result['warnings'][] = sprintf(__('Potential SQL syntax issue at line %d', 'tigerstyle-heat'), $line_count); } } } finally { fclose($handle); } // Validate SQL content if (!$has_wp_tables) { $result['warnings'][] = __('No WordPress tables found in database backup', 'tigerstyle-heat'); } if (!$has_create_statements) { $result['errors'][] = __('No CREATE TABLE statements found in database backup', 'tigerstyle-heat'); $result['valid'] = false; } if (!$has_insert_statements) { $result['warnings'][] = __('No INSERT statements found in database backup', 'tigerstyle-heat'); } if ($line_count === 0) { $result['errors'][] = __('Database backup file appears to be empty', 'tigerstyle-heat'); $result['valid'] = false; } return $result; } /** * Validate files backup */ private function validate_files_backup($backup_dir, $manifest) { $result = array( 'valid' => true, 'errors' => array(), 'warnings' => array() ); // Skip if files backup not included if (!isset($manifest['options']['include_files']) || !$manifest['options']['include_files']) { return $result; } $files_dir = $backup_dir . '/files'; if (!is_dir($files_dir)) { $result['errors'][] = __('Files backup directory not found', 'tigerstyle-heat'); $result['valid'] = false; return $result; } // Check for essential WordPress files $essential_files = array( 'wp-config.php', 'wp-content' ); foreach ($essential_files as $file) { $file_path = $files_dir . '/' . $file; if (!file_exists($file_path)) { $result['warnings'][] = sprintf(__('Essential file/directory missing: %s', 'tigerstyle-heat'), $file); } } // Validate file manifest if available if (isset($manifest['files']) && is_array($manifest['files'])) { $file_validation = $this->validate_file_manifest($files_dir, $manifest['files']); $result['errors'] = array_merge($result['errors'], $file_validation['errors']); $result['warnings'] = array_merge($result['warnings'], $file_validation['warnings']); if (!$file_validation['valid']) { $result['valid'] = false; } } return $result; } /** * Validate file manifest against actual files */ private function validate_file_manifest($files_dir, $file_manifest) { $result = array( 'valid' => true, 'errors' => array(), 'warnings' => array() ); $checked_files = 0; $missing_files = 0; $checksum_mismatches = 0; foreach ($file_manifest as $relative_path => $file_info) { $full_path = $files_dir . '/' . $relative_path; $checked_files++; if (!file_exists($full_path)) { $missing_files++; if ($missing_files <= 10) { // Limit error messages $result['errors'][] = sprintf(__('Missing file: %s', 'tigerstyle-heat'), $relative_path); } $result['valid'] = false; continue; } // Validate file size if (isset($file_info['size'])) { $actual_size = filesize($full_path); if ($actual_size !== $file_info['size']) { $result['warnings'][] = sprintf(__('File size mismatch: %s', 'tigerstyle-heat'), $relative_path); } } // Validate checksum (sample validation for performance) if (isset($file_info['checksum']) && $checked_files % 10 === 0) { // Check every 10th file $actual_checksum = md5_file($full_path); if ($actual_checksum !== $file_info['checksum']) { $checksum_mismatches++; if ($checksum_mismatches <= 5) { // Limit error messages $result['warnings'][] = sprintf(__('Checksum mismatch: %s', 'tigerstyle-heat'), $relative_path); } } } } if ($missing_files > 10) { $result['errors'][] = sprintf(__('... and %d more missing files', 'tigerstyle-heat'), $missing_files - 10); } if ($checksum_mismatches > 5) { $result['warnings'][] = sprintf(__('... and %d more checksum mismatches', 'tigerstyle-heat'), $checksum_mismatches - 5); } return $result; } /** * Validate backup checksums */ private function validate_checksums($backup_dir, $manifest) { $result = array( 'valid' => true, 'errors' => array(), 'warnings' => array() ); if (!isset($manifest['checksums']) || !is_array($manifest['checksums'])) { $result['warnings'][] = __('No checksums found in manifest', 'tigerstyle-heat'); return $result; } foreach ($manifest['checksums'] as $file => $expected_checksum) { $file_path = $backup_dir . '/' . $file; if (!file_exists($file_path)) { continue; // File validation handled elsewhere } $actual_checksum = md5_file($file_path); if ($actual_checksum !== $expected_checksum) { $result['errors'][] = sprintf(__('Checksum mismatch for file: %s', 'tigerstyle-heat'), $file); $result['valid'] = false; } } return $result; } /** * Extract backup for validation */ private function extract_backup_for_validation($backup_file) { $upload_dir = wp_upload_dir(); $temp_dir = $upload_dir['basedir'] . '/tigerstyle-validation/' . uniqid('validation_'); if (!wp_mkdir_p($temp_dir)) { throw new Exception(__('Failed to create validation directory', 'tigerstyle-heat')); } $compression_manager = new TigerStyleSEO_Compression_Manager(); $compression_method = $this->detect_compression_method($backup_file); $compression_manager->extract_archive($backup_file, $temp_dir, $compression_method); return $temp_dir; } /** * Detect compression method from filename */ private function detect_compression_method($filename) { $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); switch ($extension) { case 'zip': return 'zip'; case 'gz': return 'tar.gz'; case 'bz2': return 'tar.bz2'; default: return 'none'; } } /** * Cleanup temporary directory */ private function cleanup_temp_directory($temp_dir) { if (!is_dir($temp_dir)) { return; } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($temp_dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $file) { if ($file->isDir()) { rmdir($file->getPathname()); } else { unlink($file->getPathname()); } } rmdir($temp_dir); } /** * Validate datetime format */ private function is_valid_datetime($datetime) { $d = DateTime::createFromFormat('Y-m-d H:i:s', $datetime); return $d && $d->format('Y-m-d H:i:s') === $datetime; } /** * Basic SQL line validation */ private function is_valid_sql_line($line) { // Skip empty lines and comments if (empty($line) || strpos($line, '--') === 0) { return true; } // Check for common SQL syntax patterns $sql_keywords = array( 'CREATE', 'DROP', 'INSERT', 'UPDATE', 'DELETE', 'SELECT', 'ALTER', 'SET', 'START', 'COMMIT', 'ROLLBACK' ); $line_upper = strtoupper($line); // Check if line starts with SQL keyword or is part of a multi-line statement foreach ($sql_keywords as $keyword) { if (strpos($line_upper, $keyword) === 0) { return true; } } // Check for VALUES clause (part of INSERT statements) if (strpos($line_upper, 'VALUES') !== false || strpos($line_upper, '(') === 0) { return true; } // Check for properly quoted strings and escaped characters if (preg_match('/^[^\']*(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'[^\']*)*$/', $line)) { return true; } return false; } /** * Quick backup validation (without full extraction) */ public function quick_validate_backup($backup_id) { try { $storage_manager = new TigerStyleSEO_Storage_Manager(); // Check if backup exists if (!$storage_manager->backup_exists($backup_id)) { return array('valid' => false, 'error' => __('Backup does not exist', 'tigerstyle-heat')); } // Get backup info $backup_info = $storage_manager->get_backup_info($backup_id); // Basic checks if (empty($backup_info['file_size']) || $backup_info['file_size'] < 1000) { return array('valid' => false, 'error' => __('Backup file is too small', 'tigerstyle-heat')); } // For local backups, check if file exists and is readable if ($backup_info['storage_type'] === 'local') { if (!file_exists($backup_info['file_path']) || !is_readable($backup_info['file_path'])) { return array('valid' => false, 'error' => __('Backup file is not accessible', 'tigerstyle-heat')); } } return array('valid' => true, 'message' => __('Backup appears to be valid', 'tigerstyle-heat')); } catch (Exception $e) { return array('valid' => false, 'error' => $e->getMessage()); } } /** * Validate backup before restore (comprehensive) */ public function validate_for_restore($backup_id) { // Perform quick validation first $quick_result = $this->quick_validate_backup($backup_id); if (!$quick_result['valid']) { return $quick_result; } // Perform full validation $full_result = $this->validate_backup($backup_id); // Add restore-specific validations if ($full_result['valid']) { // Check compatibility with current WordPress version // Check available disk space // Check database permissions // etc. } return $full_result; } }