![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/private_html/admin_includes/ |
<?php
/**
* Image Compression Tool
* Admin interface for compressing existing images
*/
require_once __DIR__ . '/../utils/image_compression.php';
// Handle AJAX compression request
if (isset($_POST['action']) && $_POST['action'] === 'compress_images') {
header('Content-Type: application/json');
$dry_run = isset($_POST['dry_run']) && $_POST['dry_run'] === '1';
$limit = isset($_POST['limit']) ? (int)$_POST['limit'] : null;
$directory = $_POST['directory'] ?? 'all';
$directories = [
'track_covers' => [
'path' => 'uploads/track_covers/',
'function' => 'compressTrackCover',
'name' => 'Track Covers'
],
'profile_images' => [
'path' => 'uploads/profile_images/',
'function' => 'compressProfileImage',
'name' => 'Profile Images'
],
'cover_images' => [
'path' => 'uploads/cover_images/',
'function' => 'compressImage',
'name' => 'Cover Images',
'options' => [
'max_width' => 1920,
'max_height' => 1080,
'quality' => 85,
'convert_png_to_jpeg' => true,
'max_file_size' => 500 * 1024,
]
],
];
$results = [];
$total_processed = 0;
$total_saved = 0;
$total_errors = 0;
$dirs_to_process = $directory === 'all' ? array_keys($directories) : [$directory];
foreach ($dirs_to_process as $dir_key) {
if (!isset($directories[$dir_key])) continue;
$config = $directories[$dir_key];
$full_path = __DIR__ . '/../' . $config['path'];
if (!is_dir($full_path)) {
continue;
}
$files = glob($full_path . '*');
$files = array_filter($files, 'is_file');
$dir_results = [
'directory' => $config['name'],
'files' => [],
'processed' => 0,
'saved' => 0,
'errors' => 0,
];
$processed = 0;
foreach ($files as $filepath) {
if ($limit && $processed >= $limit) break;
$filename = basename($filepath);
$original_size = filesize($filepath);
// Skip if already small
if ($original_size < 50 * 1024) continue;
if ($dry_run) {
$image_info = @getimagesize($filepath);
if ($image_info) {
$estimated_savings = (int)($original_size * 0.5);
$dir_results['files'][] = [
'filename' => $filename,
'original_size' => $original_size,
'estimated_savings' => $estimated_savings,
'status' => 'dry_run'
];
$dir_results['saved'] += $estimated_savings;
} else {
$dir_results['errors']++;
}
} else {
if ($config['function'] === 'compressImage' && isset($config['options'])) {
$result = compressImage($filepath, $config['options']);
} elseif ($config['function'] === 'compressTrackCover') {
$result = compressTrackCover($filepath);
} elseif ($config['function'] === 'compressProfileImage') {
$result = compressProfileImage($filepath);
} else {
$result = compressImage($filepath);
}
if ($result['success']) {
if (!empty($result['skipped'])) {
$dir_results['files'][] = [
'filename' => $filename,
'original_size' => $original_size,
'new_size' => $original_size,
'saved_bytes' => 0,
'status' => 'skipped',
'message' => $result['message'] ?? 'Already optimized'
];
} else {
$dir_results['files'][] = [
'filename' => $filename,
'original_size' => $result['original_size'],
'new_size' => $result['new_size'],
'saved_bytes' => $result['saved_bytes'],
'status' => 'compressed',
'message' => $result['message'] ?? 'Compressed'
];
$dir_results['saved'] += $result['saved_bytes'];
}
} else {
$dir_results['files'][] = [
'filename' => $filename,
'original_size' => $original_size,
'status' => 'error',
'error' => $result['error'] ?? 'Unknown error'
];
$dir_results['errors']++;
}
}
$processed++;
}
$dir_results['processed'] = $processed;
$total_processed += $processed;
$total_saved += $dir_results['saved'];
$total_errors += $dir_results['errors'];
$results[] = $dir_results;
}
echo json_encode([
'success' => true,
'dry_run' => $dry_run,
'results' => $results,
'summary' => [
'total_processed' => $total_processed,
'total_saved' => $total_saved,
'total_errors' => $total_errors,
]
]);
exit;
}
// Get directory statistics
function getDirectoryStats($path) {
$full_path = __DIR__ . '/../' . $path;
if (!is_dir($full_path)) {
return ['count' => 0, 'total_size' => 0, 'large_files' => 0];
}
$files = glob($full_path . '*');
$files = array_filter($files, 'is_file');
$total_size = 0;
$large_files = 0;
foreach ($files as $file) {
$size = filesize($file);
$total_size += $size;
if ($size > 200 * 1024) { // Files larger than 200KB
$large_files++;
}
}
return [
'count' => count($files),
'total_size' => $total_size,
'large_files' => $large_files
];
}
$track_covers_stats = getDirectoryStats('uploads/track_covers/');
$profile_images_stats = getDirectoryStats('uploads/profile_images/');
$cover_images_stats = getDirectoryStats('uploads/cover_images/');
?>
<div class="admin-section">
<h2 class="admin-title">
<i class="fas fa-compress"></i>
Image Compression Tool
</h2>
<p class="admin-subtitle">Compress and optimize existing uploaded images to reduce file sizes and improve page load times</p>
<div class="compression-stats" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 2rem;">
<div class="stat-card" style="background: rgba(102, 126, 234, 0.1); border: 1px solid rgba(102, 126, 234, 0.3); border-radius: 12px; padding: 1.5rem;">
<h3 style="margin: 0 0 0.5rem 0; color: #a5b4fc; font-size: 1.2rem;">
<i class="fas fa-music"></i> Track Covers
</h3>
<div style="font-size: 2rem; font-weight: bold; color: white; margin-bottom: 0.5rem;">
<?= number_format($track_covers_stats['count']) ?>
</div>
<div style="color: #a0aec0; font-size: 0.9rem;">
<?= formatBytes($track_covers_stats['total_size']) ?> total
<?php if ($track_covers_stats['large_files'] > 0): ?>
<br><span style="color: #fbbf24;"><?= $track_covers_stats['large_files'] ?> large files (>200KB)</span>
<?php endif; ?>
</div>
</div>
<div class="stat-card" style="background: rgba(118, 75, 162, 0.1); border: 1px solid rgba(118, 75, 162, 0.3); border-radius: 12px; padding: 1.5rem;">
<h3 style="margin: 0 0 0.5rem 0; color: #c084fc; font-size: 1.2rem;">
<i class="fas fa-user"></i> Profile Images
</h3>
<div style="font-size: 2rem; font-weight: bold; color: white; margin-bottom: 0.5rem;">
<?= number_format($profile_images_stats['count']) ?>
</div>
<div style="color: #a0aec0; font-size: 0.9rem;">
<?= formatBytes($profile_images_stats['total_size']) ?> total
<?php if ($profile_images_stats['large_files'] > 0): ?>
<br><span style="color: #fbbf24;"><?= $profile_images_stats['large_files'] ?> large files (>200KB)</span>
<?php endif; ?>
</div>
</div>
<div class="stat-card" style="background: rgba(79, 172, 254, 0.1); border: 1px solid rgba(79, 172, 254, 0.3); border-radius: 12px; padding: 1.5rem;">
<h3 style="margin: 0 0 0.5rem 0; color: #60a5fa; font-size: 1.2rem;">
<i class="fas fa-image"></i> Cover Images
</h3>
<div style="font-size: 2rem; font-weight: bold; color: white; margin-bottom: 0.5rem;">
<?= number_format($cover_images_stats['count']) ?>
</div>
<div style="color: #a0aec0; font-size: 0.9rem;">
<?= formatBytes($cover_images_stats['total_size']) ?> total
<?php if ($cover_images_stats['large_files'] > 0): ?>
<br><span style="color: #fbbf24;"><?= $cover_images_stats['large_files'] ?> large files (>200KB)</span>
<?php endif; ?>
</div>
</div>
</div>
<div class="compression-controls" style="background: rgba(255, 255, 255, 0.05); border-radius: 12px; padding: 2rem; margin-bottom: 2rem;">
<h3 style="margin-top: 0; color: white; margin-bottom: 1.5rem;">
<i class="fas fa-cog"></i> Compression Settings
</h3>
<form id="compressionForm" style="display: flex; flex-direction: column; gap: 1.5rem;">
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<label style="color: #a0aec0; font-weight: 600;">Directory to Process</label>
<select name="directory" id="directory" style="padding: 0.8rem; border-radius: 8px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 1rem;">
<option value="all">All Directories</option>
<option value="track_covers">Track Covers Only</option>
<option value="profile_images">Profile Images Only</option>
<option value="cover_images">Cover Images Only</option>
</select>
</div>
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<label style="color: #a0aec0; font-weight: 600;">Limit (optional)</label>
<input type="number" name="limit" id="limit" placeholder="Leave empty to process all files" min="1" style="padding: 0.8rem; border-radius: 8px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 1rem;">
<small style="color: #718096;">Maximum number of files to process per directory</small>
</div>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<button type="button" id="dryRunBtn" class="btn" style="background: linear-gradient(135deg, #f59e0b, #d97706); border: none; color: white; padding: 1rem 2rem; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.3s ease;">
<i class="fas fa-eye"></i> Dry Run (Preview)
</button>
<button type="button" id="compressBtn" class="btn" style="background: linear-gradient(135deg, #667eea, #764ba2); border: none; color: white; padding: 1rem 2rem; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.3s ease;">
<i class="fas fa-compress"></i> Compress Images
</button>
</div>
</form>
</div>
<div id="compressionResults" style="display: none; background: rgba(255, 255, 255, 0.05); border-radius: 12px; padding: 2rem;">
<h3 style="margin-top: 0; color: white; margin-bottom: 1.5rem;">
<i class="fas fa-chart-bar"></i> Compression Results
</h3>
<div id="resultsContent"></div>
</div>
<div style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.3); border-radius: 12px; padding: 1.5rem; margin-top: 2rem;">
<h4 style="margin-top: 0; color: #60a5fa;">
<i class="fas fa-info-circle"></i> About Image Compression
</h4>
<ul style="color: #a0aec0; line-height: 1.8; margin: 0; padding-left: 1.5rem;">
<li>Track covers are compressed to max 1920x1920px, 500KB file size</li>
<li>Profile images are compressed to max 800x800px, 200KB file size</li>
<li>Cover images are compressed to max 1920x1080px, 500KB file size</li>
<li>PNG/GIF images without transparency are converted to JPEG for better compression</li>
<li>Images smaller than 50KB are skipped automatically</li>
<li><strong>All future uploads are automatically compressed</strong></li>
</ul>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('compressionForm');
const dryRunBtn = document.getElementById('dryRunBtn');
const compressBtn = document.getElementById('compressBtn');
const resultsDiv = document.getElementById('compressionResults');
const resultsContent = document.getElementById('resultsContent');
function showLoading() {
resultsDiv.style.display = 'block';
resultsContent.innerHTML = '<div style="text-align: center; padding: 2rem;"><i class="fas fa-spinner fa-spin" style="font-size: 2rem; color: #667eea;"></i><p style="color: #a0aec0; margin-top: 1rem;">Processing images...</p></div>';
dryRunBtn.disabled = true;
compressBtn.disabled = true;
}
function hideLoading() {
dryRunBtn.disabled = false;
compressBtn.disabled = false;
}
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
function displayResults(data) {
let html = '';
if (data.dry_run) {
html += '<div style="background: rgba(251, 191, 36, 0.1); border: 1px solid rgba(251, 191, 36, 0.3); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem; color: #fbbf24;"><i class="fas fa-exclamation-triangle"></i> <strong>Dry Run Mode:</strong> No files were modified. This is a preview of what would happen.</div>';
}
// Summary
html += '<div style="background: rgba(102, 126, 234, 0.1); border: 1px solid rgba(102, 126, 234, 0.3); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem;">';
html += '<h4 style="margin-top: 0; color: white; margin-bottom: 1rem;">Summary</h4>';
html += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">';
html += '<div><strong style="color: #a0aec0;">Files Processed:</strong><br><span style="color: white; font-size: 1.5rem; font-weight: bold;">' + data.summary.total_processed + '</span></div>';
html += '<div><strong style="color: #a0aec0;">Space Saved:</strong><br><span style="color: #4ade80; font-size: 1.5rem; font-weight: bold;">' + formatBytes(data.summary.total_saved) + '</span></div>';
if (data.summary.total_errors > 0) {
html += '<div><strong style="color: #a0aec0;">Errors:</strong><br><span style="color: #f87171; font-size: 1.5rem; font-weight: bold;">' + data.summary.total_errors + '</span></div>';
}
html += '</div></div>';
// Detailed results per directory
data.results.forEach(function(dirResult) {
if (dirResult.processed === 0) return;
html += '<div style="margin-bottom: 2rem;">';
html += '<h4 style="color: white; margin-bottom: 1rem;"><i class="fas fa-folder"></i> ' + dirResult.directory + '</h4>';
html += '<div style="background: rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 1rem; max-height: 400px; overflow-y: auto;">';
html += '<table style="width: 100%; border-collapse: collapse;">';
html += '<thead><tr style="border-bottom: 1px solid rgba(255, 255, 255, 0.1);">';
html += '<th style="text-align: left; padding: 0.8rem; color: #a0aec0;">File</th>';
html += '<th style="text-align: right; padding: 0.8rem; color: #a0aec0;">Original</th>';
html += '<th style="text-align: right; padding: 0.8rem; color: #a0aec0;">New Size</th>';
html += '<th style="text-align: right; padding: 0.8rem; color: #a0aec0;">Saved</th>';
html += '<th style="text-align: center; padding: 0.8rem; color: #a0aec0;">Status</th>';
html += '</tr></thead><tbody>';
dirResult.files.forEach(function(file) {
html += '<tr style="border-bottom: 1px solid rgba(255, 255, 255, 0.05);">';
html += '<td style="padding: 0.8rem; color: white;">' + file.filename + '</td>';
html += '<td style="text-align: right; padding: 0.8rem; color: #a0aec0;">' + formatBytes(file.original_size) + '</td>';
if (file.status === 'compressed') {
html += '<td style="text-align: right; padding: 0.8rem; color: #4ade80;">' + formatBytes(file.new_size) + '</td>';
html += '<td style="text-align: right; padding: 0.8rem; color: #4ade80; font-weight: bold;">-' + formatBytes(file.saved_bytes) + '</td>';
html += '<td style="text-align: center; padding: 0.8rem;"><span style="background: rgba(74, 222, 128, 0.2); color: #4ade80; padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.85rem;">✓ Compressed</span></td>';
} else if (file.status === 'skipped') {
html += '<td style="text-align: right; padding: 0.8rem; color: #a0aec0;">' + formatBytes(file.new_size) + '</td>';
html += '<td style="text-align: right; padding: 0.8rem; color: #a0aec0;">-</td>';
html += '<td style="text-align: center; padding: 0.8rem;"><span style="background: rgba(156, 163, 175, 0.2); color: #9ca3af; padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.85rem;">⊘ Skipped</span></td>';
} else if (file.status === 'dry_run') {
html += '<td style="text-align: right; padding: 0.8rem; color: #fbbf24;">~' + formatBytes(file.original_size - file.estimated_savings) + '</td>';
html += '<td style="text-align: right; padding: 0.8rem; color: #fbbf24;">~' + formatBytes(file.estimated_savings) + '</td>';
html += '<td style="text-align: center; padding: 0.8rem;"><span style="background: rgba(251, 191, 36, 0.2); color: #fbbf24; padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.85rem;">👁 Preview</span></td>';
} else {
html += '<td style="text-align: right; padding: 0.8rem; color: #a0aec0;">-</td>';
html += '<td style="text-align: right; padding: 0.8rem; color: #a0aec0;">-</td>';
html += '<td style="text-align: center; padding: 0.8rem;"><span style="background: rgba(248, 113, 113, 0.2); color: #f87171; padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.85rem;">✗ Error</span></td>';
}
html += '</tr>';
});
html += '</tbody></table>';
html += '</div>';
html += '<div style="margin-top: 1rem; color: #a0aec0;">';
html += 'Processed: <strong style="color: white;">' + dirResult.processed + '</strong> files';
if (!data.dry_run) {
html += ' | Saved: <strong style="color: #4ade80;">' + formatBytes(dirResult.saved) + '</strong>';
}
if (dirResult.errors > 0) {
html += ' | Errors: <strong style="color: #f87171;">' + dirResult.errors + '</strong>';
}
html += '</div>';
html += '</div>';
});
resultsContent.innerHTML = html;
hideLoading();
}
function runCompression(dryRun) {
showLoading();
const formData = new FormData();
formData.append('action', 'compress_images');
formData.append('dry_run', dryRun ? '1' : '0');
formData.append('directory', document.getElementById('directory').value);
const limit = document.getElementById('limit').value;
if (limit) {
formData.append('limit', limit);
}
fetch('?tab=image-compression', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
displayResults(data);
} else {
resultsContent.innerHTML = '<div style="color: #f87171; padding: 1rem;"><i class="fas fa-exclamation-circle"></i> Error: ' + (data.error || 'Unknown error') + '</div>';
hideLoading();
}
})
.catch(error => {
resultsContent.innerHTML = '<div style="color: #f87171; padding: 1rem;"><i class="fas fa-exclamation-circle"></i> Network error: ' + error.message + '</div>';
hideLoading();
});
}
dryRunBtn.addEventListener('click', function() {
if (confirm('Run a dry run to preview what would happen? No files will be modified.')) {
runCompression(true);
}
});
compressBtn.addEventListener('click', function() {
if (confirm('This will compress images and may take some time. Continue?')) {
runCompression(false);
}
});
});
</script>