<?php
/**
 * Plugin Name: AuctionForge Keepalive
 * Description: Auto-reactivates AuctionForge if a failed plugin update or
 *   misbehaving theme has dropped it from active_plugins, and sweeps stale
 *   WordPress upgrader debris that would otherwise produce silent
 *   "Could not create directory" failures on the next update. Lives in
 *   wp-content/mu-plugins/ — WordPress can't deactivate must-use plugins,
 *   so this watchdog keeps running even when AF itself has been knocked
 *   out by a bad release.
 *
 *   Smoke-checks the Plugin Name header before reactivation so a genuinely
 *   corrupt AF file doesn't fatal-loop the site. Records markers
 *   (auctionforge_auto_reactivated_at, _count, _failed_at) that the AF
 *   sync stream picks up so wpdeploy sees what happened.
 *
 *   Installed automatically by AuctionForge ≥ 3.7.0.4 — do not edit by
 *   hand; AF will overwrite this file when its version of the keepalive
 *   bumps. Keep KEEPALIVE_VERSION below in sync with the source template.
 *
 * Author: SmoothByte IT
 * Version: 1.1.0
 *
 * KEEPALIVE_VERSION: 1.1.0
 */

if (!defined('ABSPATH')) exit;

add_action('plugins_loaded', function () {
    // Already loaded? Nothing to do.
    if (defined('AUCTIONFORGE_VERSION') || class_exists('\auctionforge\inc\controllers\BidspiritLots', false)) {
        return;
    }

    $expected = 'auctionforge/auctionforge.php';
    $plugin_dir = defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR : ABSPATH . 'wp-content/plugins';
    $path = $plugin_dir . '/' . $expected;

    if (!file_exists($path)) return;

    $active = (array) get_option('active_plugins', []);
    if (in_array($expected, $active, true)) return;

    $head = @file_get_contents($path, false, null, 0, 4096);
    if ($head === false || stripos($head, 'Plugin Name:') === false) {
        update_option('auctionforge_auto_reactivated_failed_at', current_time('mysql', true), 'no');
        return;
    }

    $active[] = $expected;
    sort($active);
    update_option('active_plugins', array_values(array_unique($active)));

    update_option('auctionforge_auto_reactivated_at', current_time('mysql', true), 'no');
    update_option('auctionforge_auto_reactivated_count',
        ((int) get_option('auctionforge_auto_reactivated_count', 0)) + 1, 'no');

    if (file_exists($path)) {
        include_once $path;
    }
}, 1);

/**
 * Sweep stale upgrader debris.
 *
 * WP 6.x's atomic-rollback flow leaves an `upgrade-temp-backup/` directory
 * around when its post-install cleanup misfires (the user-facing symptom is
 * "Update failed: Could not create directory" even though the new code is
 * already on disk). Stale `wp-content/upgrade/<plugin-tmp>/` and
 * `.maintenance` lockfiles from crashed upgrades have the same effect on
 * the *next* upgrade attempt. None of WP's own scheduled cleanup catches
 * these reliably, so we sweep them ourselves.
 *
 * Transient-guarded to once per hour so this stays effectively free on
 * every page load. Wrapped in try/catch — never throws, never blocks a
 * request, never touches anything younger than its safety threshold (so
 * an actually-in-flight upgrade is left alone).
 */
add_action('init', function () {
    try {
        $key = 'auctionforge_keepalive_sweep_at';
        // Bail out fast if we've swept within the last hour.
        if (get_transient($key)) return;
        // Mark BEFORE the work so concurrent requests don't pile on.
        set_transient($key, time(), HOUR_IN_SECONDS);

        $now      = time();
        $wpc      = WP_CONTENT_DIR;
        $abspath  = rtrim(ABSPATH, '/');
        $removed  = [];

        // --- 1. Stale .maintenance lockfile (>5 min old). A real upgrade
        //         only holds it for ~30s; anything older is from a crash.
        $maint = $abspath . '/.maintenance';
        if (is_file($maint) && ($now - @filemtime($maint)) > 300) {
            if (@unlink($maint)) $removed[] = '.maintenance';
        }

        // --- 2. Debris in wp-content/upgrade/ (>60 min old). Each entry is
        //         either a leftover unpack dir or a partial download zip.
        $upgrade_dir = $wpc . '/upgrade';
        if (is_dir($upgrade_dir)) {
            foreach ((array) @scandir($upgrade_dir) as $entry) {
                if ($entry === '.' || $entry === '..' || $entry === '') continue;
                $p = $upgrade_dir . '/' . $entry;
                if (($now - @filemtime($p)) <= 3600) continue;
                if (is_dir($p)) {
                    if (auctionforge_keepalive_rrmdir($p)) $removed[] = 'upgrade/' . $entry;
                } else {
                    if (@unlink($p)) $removed[] = 'upgrade/' . $entry;
                }
            }
        }

        // --- 3. Stale upgrade-temp-backup/ (>30 min old). WP creates this
        //         per-upgrade and is supposed to remove it on success;
        //         when the cleanup misfires it sits there forever and
        //         poisons the next upgrade attempt.
        $temp_backup = $wpc . '/upgrade-temp-backup';
        if (is_dir($temp_backup) && ($now - @filemtime($temp_backup)) > 1800) {
            if (auctionforge_keepalive_rrmdir($temp_backup)) {
                $removed[] = 'upgrade-temp-backup';
            }
        }

        if (!empty($removed)) {
            update_option('auctionforge_keepalive_sweep_last_at', current_time('mysql', true), 'no');
            update_option('auctionforge_keepalive_sweep_last_removed', implode(',', $removed), 'no');
        }
    } catch (\Throwable $t) {
        // Never let the sweep break the request. Best-effort only.
    }
}, 999);

if (!function_exists('auctionforge_keepalive_rrmdir')) {
    function auctionforge_keepalive_rrmdir($dir) {
        if (!is_dir($dir)) return @unlink($dir);
        $entries = @scandir($dir);
        if ($entries === false) return false;
        foreach ($entries as $e) {
            if ($e === '.' || $e === '..') continue;
            $p = $dir . '/' . $e;
            if (is_dir($p) && !is_link($p)) {
                auctionforge_keepalive_rrmdir($p);
            } else {
                @unlink($p);
            }
        }
        return @rmdir($dir);
    }
}
