settingsPath(); if (!file_exists($path)) { return ['enabled' => true, 'interval_minutes' => 30, 'last_run' => null]; } return json_decode((string) file_get_contents($path), true) ?? ['enabled' => true, 'interval_minutes' => 30, 'last_run' => null]; } private function writeCronSettings(array $settings): void { file_put_contents( $this->settingsPath(), json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" ); } private function boards(): BoardRepository { return new BoardRepository(database()); } private function readLog(): string { $path = $this->logPath(); if (!file_exists($path) || !is_readable($path)) { return ''; } $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; $slice = array_slice($lines, -self::LOG_LINES); return implode("\n", array_reverse($slice)); } public function index(): mixed { if ($guard = AuthService::requireLogin()) { return $guard; } return $this->view('admin.cron', [ 'pageTitle' => 'PrintStream Import', 'boards' => $this->boards()->getAll(), 'settings' => $this->loadCronSettings(), 'log' => $this->readLog(), 'logLines' => self::LOG_LINES, ]); } public function saveSettings(Request $request): mixed { if ($guard = AuthService::requireLogin()) { return $guard; } $settings = $this->loadCronSettings(); $settings['enabled'] = $request->input('enabled') === 'on'; $settings['interval_minutes'] = max(1, min(120, (int) $request->input('interval_minutes', 30))); $this->writeCronSettings($settings); return $this->redirect('/admin/cron'); } public function runNow(): mixed { if ($guard = AuthService::requireLogin()) { return $guard; } // Prefer the known Docker CLI binary; fall back to PHP_BINARY for other environments. $php = is_executable('/usr/local/bin/php') ? '/usr/local/bin/php' : PHP_BINARY; $script = dirname(__DIR__, 2) . '/scripts/import-printstream.php'; $log = $this->logPath(); set_time_limit(120); $output = []; $returnCode = 0; exec(escapeshellarg($php) . ' ' . escapeshellarg($script) . ' --force 2>&1', $output, $returnCode); if (!empty($output)) { file_put_contents($log, implode("\n", $output) . "\n", FILE_APPEND | LOCK_EX); } return $this->redirect('/admin/cron'); } public function toggleBoard(int $id): mixed { if ($guard = AuthService::requireLogin()) { return $guard; } $db = database(); $row = $db->first('SELECT import_from_printstream FROM boards WHERE id = :id', ['id' => $id]); if ($row === null) { return $this->redirect('/admin/cron'); } $newVal = ((int) $row['import_from_printstream']) === 0 ? 1 : 0; $db->execute( 'UPDATE boards SET import_from_printstream = :v, updated_at = :t, updated_by = :u WHERE id = :id', [ 'v' => $newVal, 't' => date('Y-m-d H:i:s'), 'u' => AuthService::getCurrentUsername(), 'id' => $id, ] ); return $this->redirect('/admin/cron'); } }