tigerstyle-heat/includes/backup/class-compression-manager.php
Ryan Malloy 0028738e33 Initial commit: TigerStyle Heat v2.0.0
Make your WordPress site irresistible. Natural SEO attraction with:
- robots.txt management
- sitemap.xml generation
- LLMs.txt support
- Google integration (Analytics, Search Console, Tag Manager)
- Schema.org structured data
- Open Graph / Twitter Card meta tags
- AMP support
- Visual elements gallery
- Built-in backup/restore module

Includes build.sh and .distignore for WordPress-installable release ZIPs.
2026-05-27 13:41:35 -06:00

643 lines
21 KiB
PHP

<?php
/**
* Compression Manager for TigerStyle SEO Backup System
* Supports multiple compression formats with graceful fallback
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class TigerStyleSEO_Compression_Manager {
/**
* Single instance
*/
private static $instance = null;
/**
* Supported compression methods in order of preference
*/
private $compression_methods = array();
/**
* Logger instance
*/
private $logger = null;
/**
* Get instance
*/
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->init();
}
/**
* Initialize compression manager
*/
private function init() {
$this->logger = TigerStyleSEO_Backup_Logger::instance();
$this->detect_compression_methods();
}
/**
* Detect available compression methods
*/
private function detect_compression_methods() {
$this->compression_methods = array();
// Check for ZIP support (most widely available)
if (class_exists('ZipArchive')) {
$this->compression_methods['zip'] = array(
'name' => 'ZIP',
'extension' => '.zip',
'mime_type' => 'application/zip',
'available' => true,
'compression_ratio' => 0.7,
'speed' => 'fast'
);
}
// Check for TAR.GZ support (better compression)
if (extension_loaded('zlib') && class_exists('PharData')) {
$this->compression_methods['tar.gz'] = array(
'name' => 'TAR.GZ',
'extension' => '.tar.gz',
'mime_type' => 'application/gzip',
'available' => true,
'compression_ratio' => 0.6,
'speed' => 'medium'
);
}
// Check for TAR.BZ2 support (best compression)
if (extension_loaded('bz2') && class_exists('PharData')) {
$this->compression_methods['tar.bz2'] = array(
'name' => 'TAR.BZ2',
'extension' => '.tar.bz2',
'mime_type' => 'application/bzip2',
'available' => true,
'compression_ratio' => 0.5,
'speed' => 'slow'
);
}
// Check for 7Z support (if available)
if (extension_loaded('rar') || $this->command_exists('7z')) {
$this->compression_methods['7z'] = array(
'name' => '7ZIP',
'extension' => '.7z',
'mime_type' => 'application/x-7z-compressed',
'available' => true,
'compression_ratio' => 0.4,
'speed' => 'very_slow'
);
}
$this->logger->info("Detected compression methods", array(
'methods' => array_keys($this->compression_methods)
));
}
/**
* Get best available compression method
*/
public function get_best_compression_method($priority = 'balanced') {
if (empty($this->compression_methods)) {
return false;
}
switch ($priority) {
case 'speed':
$preferred_order = array('zip', 'tar.gz', 'tar.bz2', '7z');
break;
case 'compression':
$preferred_order = array('7z', 'tar.bz2', 'tar.gz', 'zip');
break;
case 'balanced':
default:
$preferred_order = array('tar.gz', 'zip', 'tar.bz2', '7z');
break;
}
foreach ($preferred_order as $method) {
if (isset($this->compression_methods[$method]) && $this->compression_methods[$method]['available']) {
return $method;
}
}
// Return first available method as fallback
return array_keys($this->compression_methods)[0];
}
/**
* Compress directory
*/
public function compress_directory($directory, $backup_id, $method = null) {
if (!$method) {
$method = $this->get_best_compression_method();
}
if (!$method || !isset($this->compression_methods[$method])) {
$this->logger->error("Compression method not available", array('method' => $method));
return false;
}
$backup_location = get_option('backup_location', WP_CONTENT_DIR . '/tigerstyle-backups/');
$compressed_file = $backup_location . $backup_id . $this->compression_methods[$method]['extension'];
// Ensure backup directory exists
wp_mkdir_p(dirname($compressed_file));
$this->logger->info("Starting compression", array(
'method' => $method,
'directory' => $directory,
'output' => $compressed_file
));
$start_time = microtime(true);
$success = false;
try {
switch ($method) {
case 'zip':
$success = $this->create_zip_archive($directory, $compressed_file);
break;
case 'tar.gz':
$success = $this->create_tar_gz_archive($directory, $compressed_file);
break;
case 'tar.bz2':
$success = $this->create_tar_bz2_archive($directory, $compressed_file);
break;
case '7z':
$success = $this->create_7z_archive($directory, $compressed_file);
break;
default:
$this->logger->error("Unknown compression method", array('method' => $method));
return false;
}
if ($success && file_exists($compressed_file)) {
$duration = microtime(true) - $start_time;
$original_size = $this->get_directory_size($directory);
$compressed_size = filesize($compressed_file);
$compression_ratio = $compressed_size / $original_size;
$this->logger->info("Compression completed", array(
'method' => $method,
'duration' => round($duration, 2) . 's',
'original_size' => $this->format_bytes($original_size),
'compressed_size' => $this->format_bytes($compressed_size),
'compression_ratio' => round($compression_ratio * 100, 1) . '%'
));
return $compressed_file;
} else {
throw new Exception("Compression failed - output file not created");
}
} catch (Exception $e) {
$this->logger->error("Compression failed", array(
'method' => $method,
'error' => $e->getMessage()
));
// Try fallback method
$fallback_methods = array_keys($this->compression_methods);
$current_index = array_search($method, $fallback_methods);
if ($current_index !== false && isset($fallback_methods[$current_index + 1])) {
$fallback_method = $fallback_methods[$current_index + 1];
$this->logger->info("Trying fallback compression method", array('method' => $fallback_method));
return $this->compress_directory($directory, $backup_id, $fallback_method);
}
return false;
}
}
/**
* Create ZIP archive
*/
private function create_zip_archive($directory, $output_file) {
$zip = new ZipArchive();
$result = $zip->open($output_file, ZipArchive::CREATE | ZipArchive::OVERWRITE);
if ($result !== TRUE) {
throw new Exception("Cannot create ZIP archive: " . $result);
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
$file_path = $file->getPathname();
$relative_path = substr($file_path, strlen($directory) + 1);
if ($file->isDir()) {
$zip->addEmptyDir($relative_path);
} else {
$zip->addFile($file_path, $relative_path);
}
}
$result = $zip->close();
return $result;
}
/**
* Create TAR.GZ archive
*/
private function create_tar_gz_archive($directory, $output_file) {
$tar_file = str_replace('.gz', '', $output_file);
// Create TAR first
$phar = new PharData($tar_file);
$phar->buildFromDirectory($directory);
// Compress to GZ
$phar->compress(Phar::GZ);
// Remove uncompressed TAR file
if (file_exists($tar_file)) {
unlink($tar_file);
}
return file_exists($output_file);
}
/**
* Create TAR.BZ2 archive
*/
private function create_tar_bz2_archive($directory, $output_file) {
$tar_file = str_replace('.bz2', '', $output_file);
// Create TAR first
$phar = new PharData($tar_file);
$phar->buildFromDirectory($directory);
// Compress to BZ2
$phar->compress(Phar::BZ2);
// Remove uncompressed TAR file
if (file_exists($tar_file)) {
unlink($tar_file);
}
return file_exists($output_file);
}
/**
* Create ZIP archive using PHP ZipArchive (replaces 7z for security)
*/
private function create_7z_archive($directory, $output_file) {
if (!extension_loaded('zip')) {
throw new Exception("ZIP extension not available");
}
// Convert .7z extension to .zip for compatibility
if (pathinfo($output_file, PATHINFO_EXTENSION) === '7z') {
$output_file = substr($output_file, 0, -2) . 'zip';
}
$zip = new ZipArchive();
$result = $zip->open($output_file, ZipArchive::CREATE | ZipArchive::OVERWRITE);
if ($result !== TRUE) {
throw new Exception("Cannot create ZIP archive: " . $this->getZipError($result));
}
// Add all files from directory recursively
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
$file_path = $file->getPathname();
$relative_path = substr($file_path, strlen($directory) + 1);
if ($file->isDir()) {
$zip->addEmptyDir($relative_path);
} elseif ($file->isFile()) {
// Use maximum compression level (9)
$zip->addFile($file_path, $relative_path);
$zip->setCompressionName($relative_path, ZipArchive::CM_DEFLATE, 9);
}
}
$close_result = $zip->close();
if (!$close_result) {
throw new Exception("Failed to close ZIP archive");
}
return file_exists($output_file);
}
/**
* Extract compressed archive
*/
public function extract_archive($archive_file, $destination_directory) {
if (!file_exists($archive_file)) {
throw new Exception("Archive file not found: " . $archive_file);
}
$method = $this->detect_compression_method($archive_file);
if (!$method) {
throw new Exception("Cannot determine compression method for: " . $archive_file);
}
// Ensure destination directory exists
wp_mkdir_p($destination_directory);
$this->logger->info("Extracting archive", array(
'method' => $method,
'archive' => $archive_file,
'destination' => $destination_directory
));
$start_time = microtime(true);
try {
switch ($method) {
case 'zip':
$success = $this->extract_zip_archive($archive_file, $destination_directory);
break;
case 'tar.gz':
case 'tar.bz2':
$success = $this->extract_tar_archive($archive_file, $destination_directory);
break;
case '7z':
$success = $this->extract_7z_archive($archive_file, $destination_directory);
break;
default:
throw new Exception("Unsupported compression method: " . $method);
}
if ($success) {
$duration = microtime(true) - $start_time;
$this->logger->info("Extraction completed", array(
'method' => $method,
'duration' => round($duration, 2) . 's'
));
return true;
} else {
throw new Exception("Extraction failed");
}
} catch (Exception $e) {
$this->logger->error("Extraction failed", array(
'method' => $method,
'error' => $e->getMessage()
));
return false;
}
}
/**
* Extract ZIP archive
*/
private function extract_zip_archive($archive_file, $destination) {
$zip = new ZipArchive();
$result = $zip->open($archive_file);
if ($result !== TRUE) {
throw new Exception("Cannot open ZIP archive: " . $result);
}
$result = $zip->extractTo($destination);
$zip->close();
return $result;
}
/**
* Extract TAR archive (GZ or BZ2)
*/
private function extract_tar_archive($archive_file, $destination) {
$phar = new PharData($archive_file);
$phar->extractTo($destination);
return true;
}
/**
* Extract 7Z archive
*/
private function extract_7z_archive($archive_file, $destination) {
if (!extension_loaded('zip')) {
throw new Exception("ZIP extension not available");
}
$zip = new ZipArchive();
$result = $zip->open($archive_file);
if ($result !== TRUE) {
throw new Exception("Cannot open ZIP archive: " . $this->getZipError($result));
}
// Ensure destination directory exists
if (!is_dir($destination)) {
wp_mkdir_p($destination);
}
// Extract with security validation
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
// Security check: prevent directory traversal
if (strpos($entry, '../') !== false || strpos($entry, '..\\') !== false) {
$zip->close();
throw new Exception("Archive contains unsafe path: " . $entry);
}
}
$extract_result = $zip->extractTo($destination);
$zip->close();
if (!$extract_result) {
throw new Exception("Failed to extract ZIP archive");
}
return true;
}
/**
* Detect compression method from file extension
*/
private function detect_compression_method($file_path) {
$extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
// Handle compound extensions
if ($extension === 'gz' && strtolower(pathinfo(pathinfo($file_path, PATHINFO_FILENAME), PATHINFO_EXTENSION)) === 'tar') {
return 'tar.gz';
}
if ($extension === 'bz2' && strtolower(pathinfo(pathinfo($file_path, PATHINFO_FILENAME), PATHINFO_EXTENSION)) === 'tar') {
return 'tar.bz2';
}
$extension_map = array(
'zip' => 'zip',
'7z' => '7z',
'gz' => 'tar.gz',
'bz2' => 'tar.bz2'
);
return isset($extension_map[$extension]) ? $extension_map[$extension] : null;
}
/**
* Get directory size
*/
private function get_directory_size($directory) {
$size = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$size += $file->getSize();
}
}
return $size;
}
/**
* Check if PHP extension/feature exists (replaces shell command detection)
*/
private function command_exists($command) {
switch ($command) {
case '7z':
// 7z now uses ZIP extension for security
return extension_loaded('zip');
case 'zip':
return extension_loaded('zip');
case 'tar':
case 'gzip':
return class_exists('PharData') && extension_loaded('zlib');
case 'bzip2':
return class_exists('PharData') && extension_loaded('bz2');
default:
return false;
}
}
/**
* Get human-readable ZIP error message
*/
private function getZipError($code) {
switch($code) {
case ZipArchive::ER_OK: return 'No error';
case ZipArchive::ER_MULTIDISK: return 'Multi-disk zip archives not supported';
case ZipArchive::ER_RENAME: return 'Renaming temporary file failed';
case ZipArchive::ER_CLOSE: return 'Closing zip archive failed';
case ZipArchive::ER_SEEK: return 'Seek error';
case ZipArchive::ER_READ: return 'Read error';
case ZipArchive::ER_WRITE: return 'Write error';
case ZipArchive::ER_CRC: return 'CRC error';
case ZipArchive::ER_ZIPCLOSED: return 'Containing zip archive was closed';
case ZipArchive::ER_NOENT: return 'No such file';
case ZipArchive::ER_EXISTS: return 'File already exists';
case ZipArchive::ER_OPEN: return 'Can not open file';
case ZipArchive::ER_TMPOPEN: return 'Failure to create temporary file';
case ZipArchive::ER_ZLIB: return 'Zlib error';
case ZipArchive::ER_MEMORY: return 'Memory allocation failure';
case ZipArchive::ER_CHANGED: return 'Entry has been changed';
case ZipArchive::ER_COMPNOTSUPP: return 'Compression method not supported';
case ZipArchive::ER_EOF: return 'Premature EOF';
case ZipArchive::ER_INVAL: return 'Invalid argument';
case ZipArchive::ER_NOZIP: return 'Not a zip archive';
case ZipArchive::ER_INTERNAL: return 'Internal error';
case ZipArchive::ER_INCONS: return 'Zip archive inconsistent';
case ZipArchive::ER_REMOVE: return 'Can not remove file';
case ZipArchive::ER_DELETED: return 'Entry has been deleted';
default: return "Unknown error code: $code";
}
}
/**
* Format bytes for display
*/
private function format_bytes($bytes, $precision = 2) {
$units = array('B', 'KB', 'MB', 'GB', 'TB');
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
$bytes /= 1024;
}
return round($bytes, $precision) . ' ' . $units[$i];
}
/**
* Get available compression methods
*/
public function get_available_methods() {
return $this->compression_methods;
}
/**
* Validate compressed file
*/
public function validate_archive($archive_file) {
if (!file_exists($archive_file)) {
return false;
}
$method = $this->detect_compression_method($archive_file);
if (!$method) {
return false;
}
try {
switch ($method) {
case 'zip':
$zip = new ZipArchive();
$result = $zip->open($archive_file, ZipArchive::CHECKCONS);
$zip->close();
return $result === TRUE;
case 'tar.gz':
case 'tar.bz2':
$phar = new PharData($archive_file);
return $phar->valid();
case '7z':
// 7z files are now treated as ZIP files for security
$zip = new ZipArchive();
$result = $zip->open($archive_file, ZipArchive::CHECKCONS);
if ($result === TRUE) {
$zip->close();
return true;
}
return false;
default:
return false;
}
} catch (Exception $e) {
$this->logger->warning("Archive validation failed", array(
'file' => $archive_file,
'error' => $e->getMessage()
));
return false;
}
}
}