Comment to 'UNA 14 Timeline Cleanup Script – Expert Review Requested'
  • @Michael Newton

    Please examine code for your approval and correction and yes, the above method is included: Final Code from my side... The minute I have the replica setup, I will start testing and give feedback to the community how things go

    <?php

    /**

    * UNA Timeline Cleanup Script - Safe Version

    * Deletes old timeline posts and their associated media files properly

    *

    * IMPORTANT: Test with $dryRun = true first!

    */

    // Bootstrap UNA

    require_once __DIR__ . '/inc/header.inc.php';

    // ===== CONFIGURATION =====

    $daysOld = 60; // Delete posts older than this many days

    $dryRun = true; // SET TO FALSE when ready to actually delete

    $batch = 200; // Process this many posts per run

    $lockFile = __DIR__ . '/timeline_cleanup.lock';

    $logFile = __DIR__ . '/timeline_cleanup.log';

    // ===== LOCK CHECK (prevent concurrent runs) =====

    if (file_exists($lockFile)) {

    $lockAge = time() - filemtime($lockFile);

    if ($lockAge < 3600) { // 1 hour lock timeout

    die("Script is already running (lock file exists)\n");

    }

    unlink($lockFile); // Remove stale lock

    }

    file_put_contents($lockFile, getmypid() . "\n" . date('Y-m-d H:i:s'));

    // ===== SETUP =====

    $cutoff = time() - (86400 * $daysOld);

    $db = BxDolDb::getInstance();

    // Start logging

    $logMsg = "\n" . str_repeat('=', 60) . "\n";

    $logMsg .= "Timeline Cleanup Run: " . date('Y-m-d H:i:s') . "\n";

    $logMsg .= "Mode: " . ($dryRun ? "DRY RUN (no deletions)" : "LIVE (deleting)") . "\n";

    $logMsg .= "Cutoff date: " . date('Y-m-d H:i:s', $cutoff) . "\n";

    $logMsg .= str_repeat('=', 60) . "\n";

    file_put_contents($logFile, $logMsg, FILE_APPEND);

    // ===== GET OLD EVENTS =====

    try {

    $eventIds = $db->getColumn("

    SELECT `id`

    FROM `bx_timeline_events`

    WHERE `type` = 'post'

    AND `date` < ?

    ORDER BY `date` ASC

    LIMIT ?

    ", [$cutoff, $batch]);

    } catch (Exception $e) {

    $error = "ERROR: Database query failed: " . $e->getMessage() . "\n";

    file_put_contents($logFile, $error, FILE_APPEND);

    unlink($lockFile);

    die($error);

    }

    if (empty($eventIds)) {

    $msg = "No old events found to process.\n";

    echo $msg;

    file_put_contents($logFile, $msg, FILE_APPEND);

    unlink($lockFile);

    exit(0);

    }

    echo "Found " . count($eventIds) . " events to process\n";

    file_put_contents($logFile, "Found " . count($eventIds) . " events\n", FILE_APPEND);

    // ===== LOAD TIMELINE MODULE =====

    $timeline = BxDolModule::getInstance('bx_timeline');

    if (!$timeline) {

    $error = "FATAL ERROR: Timeline module not found!\n";

    file_put_contents($logFile, $error, FILE_APPEND);

    unlink($lockFile);

    die($error);

    }

    // ===== PROCESS DELETIONS =====

    $deleted = 0;

    $failed = 0;

    foreach ($eventIds as $eventId) {

    $eventId = (int)$eventId;

    // Get event details for logging

    $aEvent = $db->getRow("SELECT * FROM `bx_timeline_events` WHERE `id` = ?", [$eventId]);

    if (empty($aEvent)) {

    $msg = "Event $eventId not found (already deleted?)\n";

    echo $msg;

    file_put_contents($logFile, $msg, FILE_APPEND);

    continue;

    }

    if ($dryRun) {

    $msg = "[DRY RUN] Would delete event: $eventId (owner: {$aEvent['owner_id']}, date: " .

    date('Y-m-d', $aEvent['date']) . ")\n";

    echo $msg;

    file_put_contents($logFile, $msg, FILE_APPEND);

    $deleted++;

    continue;

    }

    // ===== ACTUAL DELETION (uses UNA's native method) =====

    try {

    // This is the safe method that handles all cleanup:

    // - Deletes from bx_timeline_events

    // - Removes associated media files from storage

    // - Cleans up comments, reactions, votes, reports

    // - Triggers all necessary hooks

    $result = $timeline->deleteEvent($aEvent);

    if ($result) {

    $deleted++;

    $msg = "✓ Deleted event: $eventId (owner: {$aEvent['owner_id']})\n";

    echo $msg;

    file_put_contents($logFile, $msg, FILE_APPEND);

    } else {

    $failed++;

    $msg = "✗ Failed to delete event: $eventId\n";

    echo $msg;

    file_put_contents($logFile, $msg, FILE_APPEND);

    }

    } catch (Exception $e) {

    $failed++;

    $msg = "✗ Exception deleting event $eventId: " . $e->getMessage() . "\n";

    echo $msg;

    file_put_contents($logFile, $msg, FILE_APPEND);

    }

    // Small delay to prevent overwhelming the system

    usleep(50000); // 0.05 seconds

    }

    // ===== SUMMARY =====

    $summary = "\n" . str_repeat('-', 60) . "\n";

    $summary .= "Summary:\n";

    $summary .= " Total found: " . count($eventIds) . "\n";

    $summary .= " Successfully deleted: $deleted\n";

    $summary .= " Failed: $failed\n";

    $summary .= str_repeat('-', 60) . "\n\n";

    echo $summary;

    file_put_contents($logFile, $summary, FILE_APPEND);

    // ===== CLEANUP =====

    unlink($lockFile);

    // ===== NEXT STEPS MESSAGE =====

    if ($dryRun) {

    echo "\n";

    echo "DRY RUN COMPLETE - No actual deletions performed.\n";

    echo "Review the log file: $logFile\n";

    echo "\n";

    echo "To perform actual deletions:\n";

    echo "1. Check the log to verify correct events are targeted\n";

    echo "2. Set \$dryRun = false in the script\n";

    echo "3. Start with a small batch (e.g., \$batch = 10)\n";

    echo "4. Monitor disk usage before/after\n";

    echo "5. Gradually increase batch size if successful\n";

    echo "\n";

    } else {

    echo "\nTo continue cleanup, run this script again.\n";

    echo "It will process the next $batch events.\n";

    }