149 lines
4.1 KiB
PHP
149 lines
4.1 KiB
PHP
|
|
#!/usr/bin/env php
|
||
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Cleanup Orphan Uploads
|
||
|
|
*
|
||
|
|
* Removes uploaded files that are no longer associated with any ticket.
|
||
|
|
* Run periodically via cron: 0 2 * * * php /path/to/cleanup_orphan_uploads.php
|
||
|
|
*/
|
||
|
|
|
||
|
|
require_once dirname(__DIR__) . '/config/config.php';
|
||
|
|
|
||
|
|
$conn = new mysqli(
|
||
|
|
$GLOBALS['config']['DB_HOST'],
|
||
|
|
$GLOBALS['config']['DB_USER'],
|
||
|
|
$GLOBALS['config']['DB_PASS'],
|
||
|
|
$GLOBALS['config']['DB_NAME']
|
||
|
|
);
|
||
|
|
|
||
|
|
if ($conn->connect_error) {
|
||
|
|
die("Database connection failed: " . $conn->connect_error . "\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
$uploadsDir = dirname(__DIR__) . '/uploads';
|
||
|
|
$dryRun = in_array('--dry-run', $argv);
|
||
|
|
|
||
|
|
if ($dryRun) {
|
||
|
|
echo "DRY RUN MODE - No files will be deleted\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "Scanning uploads directory: $uploadsDir\n";
|
||
|
|
|
||
|
|
// Get all valid ticket IDs from database
|
||
|
|
$ticketIds = [];
|
||
|
|
$result = $conn->query("SELECT ticket_id FROM tickets");
|
||
|
|
while ($row = $result->fetch_assoc()) {
|
||
|
|
$ticketIds[] = $row['ticket_id'];
|
||
|
|
}
|
||
|
|
echo "Found " . count($ticketIds) . " tickets in database\n";
|
||
|
|
|
||
|
|
// Get all attachment records
|
||
|
|
$attachments = [];
|
||
|
|
$result = $conn->query("SELECT ticket_id, filename FROM ticket_attachments");
|
||
|
|
if ($result) {
|
||
|
|
while ($row = $result->fetch_assoc()) {
|
||
|
|
$key = $row['ticket_id'] . '/' . $row['filename'];
|
||
|
|
$attachments[$key] = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
echo "Found " . count($attachments) . " attachment records in database\n";
|
||
|
|
|
||
|
|
// Scan uploads directory
|
||
|
|
$orphanedFolders = [];
|
||
|
|
$orphanedFiles = [];
|
||
|
|
$totalSize = 0;
|
||
|
|
|
||
|
|
$ticketDirs = glob($uploadsDir . '/*', GLOB_ONLYDIR);
|
||
|
|
foreach ($ticketDirs as $ticketDir) {
|
||
|
|
$ticketId = basename($ticketDir);
|
||
|
|
|
||
|
|
// Skip non-ticket directories
|
||
|
|
if (!preg_match('/^\d{9}$/', $ticketId)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if ticket exists
|
||
|
|
if (!in_array($ticketId, $ticketIds)) {
|
||
|
|
// Ticket doesn't exist - entire folder is orphaned
|
||
|
|
$orphanedFolders[] = $ticketDir;
|
||
|
|
$folderSize = 0;
|
||
|
|
foreach (glob($ticketDir . '/*') as $file) {
|
||
|
|
if (is_file($file)) {
|
||
|
|
$folderSize += filesize($file);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$totalSize += $folderSize;
|
||
|
|
echo "Orphan folder (ticket deleted): $ticketDir (" . formatBytes($folderSize) . ")\n";
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check individual files
|
||
|
|
$files = glob($ticketDir . '/*');
|
||
|
|
foreach ($files as $file) {
|
||
|
|
if (is_file($file)) {
|
||
|
|
$filename = basename($file);
|
||
|
|
$key = $ticketId . '/' . $filename;
|
||
|
|
|
||
|
|
if (!isset($attachments[$key])) {
|
||
|
|
$orphanedFiles[] = $file;
|
||
|
|
$fileSize = filesize($file);
|
||
|
|
$totalSize += $fileSize;
|
||
|
|
echo "Orphan file (no DB record): $file (" . formatBytes($fileSize) . ")\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "\n=== Summary ===\n";
|
||
|
|
echo "Orphaned folders: " . count($orphanedFolders) . "\n";
|
||
|
|
echo "Orphaned files: " . count($orphanedFiles) . "\n";
|
||
|
|
echo "Total size to recover: " . formatBytes($totalSize) . "\n";
|
||
|
|
|
||
|
|
if (!$dryRun && ($orphanedFolders || $orphanedFiles)) {
|
||
|
|
echo "\nDeleting orphaned items...\n";
|
||
|
|
|
||
|
|
foreach ($orphanedFiles as $file) {
|
||
|
|
if (unlink($file)) {
|
||
|
|
echo "Deleted: $file\n";
|
||
|
|
} else {
|
||
|
|
echo "Failed to delete: $file\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($orphanedFolders as $folder) {
|
||
|
|
deleteDirectory($folder);
|
||
|
|
echo "Deleted folder: $folder\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "Cleanup complete!\n";
|
||
|
|
} elseif ($dryRun) {
|
||
|
|
echo "\nRun without --dry-run to delete these items.\n";
|
||
|
|
} else {
|
||
|
|
echo "\nNo orphaned items found.\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
$conn->close();
|
||
|
|
|
||
|
|
function formatBytes($bytes) {
|
||
|
|
if ($bytes >= 1073741824) {
|
||
|
|
return number_format($bytes / 1073741824, 2) . ' GB';
|
||
|
|
} elseif ($bytes >= 1048576) {
|
||
|
|
return number_format($bytes / 1048576, 2) . ' MB';
|
||
|
|
} elseif ($bytes >= 1024) {
|
||
|
|
return number_format($bytes / 1024, 2) . ' KB';
|
||
|
|
} else {
|
||
|
|
return $bytes . ' bytes';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function deleteDirectory($dir) {
|
||
|
|
if (!is_dir($dir)) return;
|
||
|
|
|
||
|
|
$files = array_diff(scandir($dir), ['.', '..']);
|
||
|
|
foreach ($files as $file) {
|
||
|
|
$path = "$dir/$file";
|
||
|
|
is_dir($path) ? deleteDirectory($path) : unlink($path);
|
||
|
|
}
|
||
|
|
rmdir($dir);
|
||
|
|
}
|