Unifi-Voucher-Tool/updater/UpdateController.php
Claude 43149074c9
Updater-System (OpenNIT-Modell) im isolierten updater/-Ordner
Zieht Quellcode + DB-Migrationen über einen HTTP-Update-Proxy nach.
Vollständig isoliert: eigener Namespace Updater\, eigener Autoloader,
eigene Settings (updater/storage/updater-settings.json), eigenes
Migrations-System (_updater_migrations), eigener AuditLogger.

Komponenten (alle in updater/):
- UpdateManager: Version/.version, Maintenance, Progress, checkForUpdates,
  installUpdate (Staging + PROTECTED_PATHS + Migrationen + opcache + finally)
- MigrationRunner: MySQL-Tracking, string-/kommentar-bewusster SQL-Splitter,
  isIgnorableSqlError, 60s-Lockfile-Cache
- UpdateController + admin/update.php (dünner Entry-Shim), Inline-Admin-UI
  mit Channel-Selector, Update-Check, Progress-Bar, Migrations-Tab
- UpdaterFactory (zentraler Channel-Fallback), AuditLogger (audit_log)
- Templates: maintenance.html, update.php; routes.php (Doku)
- README.md mit vollständiger Rückbau-Anleitung

Einzige Bestandscode-Änderung: 4-Zeilen-Maintenance-Hook in index.php
(markiert mit "// Updater maintenance hook").

Proxy: update.loheide.eu/openvouchertool[-development]
2026-06-05 18:51:51 +00:00

122 lines
3.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Updater;
/**
* UpdateController bedient die Admin-Seite /admin/update sowie deren
* AJAX-Actions (check, install, progress, set_channel, migrations).
*
* Nutzt die vorhandenen Projekt-Klassen \Database und \Auth per Injection.
* Die eigentliche Logik liegt im UpdateManager; dieser Controller ist nur
* die duenne Auslieferungs-/Routing-Schicht.
*/
class UpdateController
{
/** @var \Database */
private $db;
/** @var \Auth */
private $auth;
/** @var UpdateManager */
private $manager;
public function __construct(\Database $db, \Auth $auth)
{
$this->db = $db;
$this->auth = $auth;
$this->manager = UpdaterFactory::create($db, new AuditLogger($db));
}
public function handle(): void
{
$action = $_GET['action'] ?? $_POST['action'] ?? '';
switch ($action) {
case 'check': $this->actionCheck(); break;
case 'install': $this->actionInstall(); break;
case 'progress': $this->actionProgress(); break;
case 'set_channel': $this->actionSetChannel(); break;
case 'migrations': $this->actionMigrations(); break;
default: $this->renderPage();
}
}
// ------------------------------------------------------------- AJAX-Actions
private function actionCheck(): void
{
try {
$this->json($this->manager->checkForUpdates());
} catch (\Throwable $e) {
$this->json(['error' => $e->getMessage()], 502);
}
}
private function actionInstall(): void
{
if (!$this->auth->validateCsrfToken($_POST['csrf_token'] ?? '')) {
$this->json(['error' => 'Ungültiges Sicherheits-Token'], 403);
return;
}
// Session-Lock freigeben, damit parallele Progress-Polls nicht blockieren.
if (session_status() === PHP_SESSION_ACTIVE) {
session_write_close();
}
@set_time_limit(300);
$userId = $_SESSION['user_id'] ?? null;
$result = $this->manager->installUpdate($userId !== null ? (int)$userId : null);
$this->json($result, $result['success'] ? 200 : 500);
}
private function actionProgress(): void
{
// Kein CSRF noetig (read-only). Session-Lock sofort freigeben.
if (session_status() === PHP_SESSION_ACTIVE) {
session_write_close();
}
$this->json($this->manager->getProgress());
}
private function actionSetChannel(): void
{
if (!$this->auth->validateCsrfToken($_POST['csrf_token'] ?? '')) {
$this->json(['error' => 'Ungültiges Sicherheits-Token'], 403);
return;
}
$channel = $_POST['channel'] ?? 'stable';
UpdaterFactory::saveChannel($channel);
$this->json(['success' => true, 'channel' => $channel]);
}
private function actionMigrations(): void
{
try {
$runner = new MigrationRunner(
$this->db->getConnection(),
__DIR__ . '/migrations',
__DIR__ . '/storage'
);
$this->json(['migrations' => $runner->status()]);
} catch (\Throwable $e) {
$this->json(['error' => $e->getMessage()], 500);
}
}
// ------------------------------------------------------------------- Render
private function renderPage(): void
{
$manager = $this->manager;
$auth = $this->auth;
$currentSha = $manager->getCurrentVersion();
$channel = $manager->getChannel();
$csrfToken = $auth->getCsrfToken();
require __DIR__ . '/templates/update.php';
}
private function json($data, int $status = 200): void
{
if (!headers_sent()) {
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
}
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}