$pluginDir, 'DESKHUB_TESTING' => '1', 'DESKHUB_CATALOG_DEFAULTS' => __DIR__ . '/fixtures/no-catalog.php', 'DESKHUB_SQLITE_PATH' => $db, 'DESKHUB_CJ_MARKER' => $marker] as $k => $v) putenv("{$k}={$v}"); require_once dirname(__DIR__) . '/includes/bootstrap.php'; $failures = []; $check = function (bool $ok, string $what) use (&$failures): void { if (!$ok) $failures[] = $what; }; $pdo = get_db(); // Kleines Plugin, das bei jedem Lauf eine Zeile in eine Datei schreibt $zipPath = sys_get_temp_dir() . '/cj-' . bin2hex(random_bytes(3)) . '.zip'; $zip = new ZipArchive(); $zip->open($zipPath, ZipArchive::CREATE); $zip->addFromString('cronprobe/plugin.json', json_encode(['slug' => 'cronprobe', 'name' => 'Cron-Probe', 'version' => '1.0.0', 'permissions' => ['hooks.cron']])); $zip->addFromString('cronprobe/plugin.php', "close(); $check(plugin_install_zip($pdo, $zipPath)['ok'], 'Test-Plugin lässt sich installieren'); $check(plugin_set_active($pdo, 'cronprobe', true, ['hooks.cron'])['ok'], 'Test-Plugin lässt sich aktivieren'); $php = escapeshellarg(PHP_BINARY); $script = escapeshellarg(dirname(__DIR__) . '/cron/run_plugins.php'); exec("{$php} {$script} 2>&1", $out, $code); $check($code === 0 && str_contains(implode("\n", $out), '1 aktive'), 'Cronjob löst die Plugin-Aufgaben aus: ' . implode(' | ', $out)); $n = fn(string $w) => substr_count((string) @file_get_contents($marker), $w); $check(is_file($marker) && $n('lauf') === 1 && $n('minute') === 1, 'beide Hooks des Plugins wurden genau einmal ausgeführt (Minute und 5-Minuten-Lauf)'); unset($out); exec("{$php} {$script} 2>&1", $out, $code); $check($code === 0 && str_contains(implode("\n", $out), 'Übersprungen') && $n('lauf') === 1 && $n('minute') === 1, 'direkt danach läuft er nicht doppelt'); $last = (string) $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'cron_run_plugins_last_run'")->fetchColumn(); $check($last !== '' && strtotime($last) > time() - 60, 'letzter Lauf ist vermerkt (Anzeige unter Einstellungen → Cron-Jobs)'); $srcRow = $pdo->query("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ('cron_run_plugins_source','cron_run_plugins_last_cli')")->fetchAll(PDO::FETCH_KEY_PAIR); $check(($srcRow['cron_run_plugins_source'] ?? '') === 'cron' && strtotime((string) ($srcRow['cron_run_plugins_last_cli'] ?? '')) > time() - 60, 'der Lauf wird als „echter Cronjob“ vermerkt (Quelle und Zeit)'); // Der eingebaute Hintergrund-Betrieb hat die Minute schon belegt: Der Lauf wird übersprungen, der echte Cronjob gilt trotzdem als lebendig $pdo->prepare("DELETE FROM settings WHERE setting_key = 'cron_run_plugins_last_cli'")->execute(); unset($out); exec("{$php} {$script} 2>&1", $out, $code); $lastCli = (string) $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'cron_run_plugins_last_cli'")->fetchColumn(); $check(str_contains(implode(" ", $out), 'Übersprungen') && $lastCli !== '' && strtotime($lastCli) > time() - 60, 'auch ein übersprungener Cron-Lauf wird als „echter Cronjob lebt“ vermerkt'); // Eine Minute später: das Minuten-Ereignis kommt wieder, der 5-Minuten-Lauf noch nicht $setAgo = fn(string $key, int $sec) => $pdo->prepare('UPDATE settings SET setting_value = ? WHERE setting_key = ?')->execute([date('Y-m-d H:i:s', time() - $sec), $key]); $setAgo('cron_run_plugins_last_run', 70); unset($out); exec("{$php} {$script} 2>&1", $out, $code); $check($n('minute') === 2 && $n('lauf') === 1, 'nach einer Minute: Minuten-Ereignis ja, 5-Minuten-Ereignis noch nicht'); // Fünf Minuten später: beide $setAgo('cron_run_plugins_last_run', 70); $setAgo('cron_plugins_run_last', 310); unset($out); exec("{$php} {$script} 2>&1", $out, $code); $check($n('minute') === 3 && $n('lauf') === 2, 'nach fünf Minuten: beide Ereignisse'); // Der Webzugriff bleibt gesperrt $src = (string) file_get_contents(dirname(__DIR__) . '/cron/run_plugins.php'); $check(str_contains($src, "php_sapi_name() !== 'cli' && !defined('DESKHUB_CRON_RUNNER')") && str_contains($src, 'Kein Web-Zugriff'), 'Webzugriff auf das Skript ist gesperrt'); // Eintrag im Crontab-Block und im eingebauten Hintergrund-Betrieb require_once dirname(__DIR__) . '/includes/crontab_installer.php'; $block = crontab_marker_block(); $check(preg_match('#^\* \* \* \* \* .*/cron/run_plugins\.php#m', $block) === 1, 'Crontab-Block enthält den Plugin-Job jede Minute'); $defs = pseudo_cron_job_definitions(); $check(isset($defs['run_plugins']) && $defs['run_plugins']['fn'] === 'run_plugins_cron' && $defs['run_plugins']['interval'] === 60 && is_file(dirname(__DIR__) . '/cron/' . $defs['run_plugins']['file']), 'eingebauter Hintergrund-Betrieb führt den Job (jede Minute)'); $settingsSrc = (string) file_get_contents(dirname(__DIR__) . '/staff/settings.php'); // Jeder Job der Statusliste braucht eine Anzeige „letzter Lauf“ (sonst bleibt die Zeit leer) preg_match('/\$cronJobs = \[(.*?)\n\];/s', $settingsSrc, $mj); preg_match('/\$cronLastRun = \[(.*?)\n \];/s', $settingsSrc, $ml); preg_match_all("/^\s*'([a-z_]+)'\s*=>/m", $mj[1] ?? '', $jobKeys); preg_match_all("/^\s*'([a-z_]+)'\s*=>/m", $ml[1] ?? '', $lastKeys); $check(count($jobKeys[1]) >= 10 && array_diff($jobKeys[1], $lastKeys[1]) === [], 'jeder Job der Statusliste hat eine Anzeige des letzten Laufs (fehlt: ' . implode(', ', array_diff($jobKeys[1], $lastKeys[1])) . ')'); $check(str_contains($settingsSrc, "get_setting('cron_run_plugins_last_run')"), 'der Plugin-Job liest seinen letzten Lauf aus der Einstellung, die das Skript schreibt'); $check(str_contains($settingsSrc, "'run_plugins'") && str_contains($settingsSrc, 'run_plugins_cron'), 'Job steht in der Statusliste unter Einstellungen → Cron-Jobs'); $check(!str_contains((string) file_get_contents(dirname(__DIR__) . '/includes/pseudo_cron.php'), "plugin_hook('cron.run'"), 'Plugin-Aufgaben werden nicht noch ein zweites Mal im Hintergrund-Betrieb angestoßen'); $check(plugin_hook_permission('cron.minute') === 'hooks.cron' && plugin_hook_permission('cron.run') === 'hooks.cron', 'das Minuten-Ereignis braucht dieselbe Berechtigung wie cron.run'); foreach (scandir($pluginDir) ?: [] as $entry) { if ($entry === '.' || $entry === '..') continue; is_dir($pluginDir . '/' . $entry) ? plugin_rmdir($pluginDir . '/' . $entry) : @unlink($pluginDir . '/' . $entry); } @rmdir($pluginDir); @unlink($zipPath); @unlink($db); @unlink($marker); if ($failures) { fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n"); exit(1); } echo 'Plugin cron job checks passed.' . "\n";