open($p, ZipArchive::CREATE); $z->addFromString('updprobe/plugin.json', json_encode(['slug' => 'updprobe', 'name' => 'Update-Probe', 'version' => $version, 'permissions' => $perms])); // Das Plugin liest beim Laden die neue Spalte: ohne eingespielte Änderung käme es zu einem Fehler $z->addFromString('updprobe/plugin.php', " $sql) $z->addFromString('updprobe/migrations/' . $name, $sql); $z->close(); return $p; }; $reboot = function (): void { $state = &plugin_state(); $state['booted'] = false; plugins_boot(); }; $cols = fn() => array_column($pdo->query('PRAGMA table_info(plugin_updprobe_items)')->fetchAll(), 'name'); // Version 1 mit Tabelle $v1 = $mkZip('1.0.0', ['001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)', '001_items.mysql.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL)']); $r = plugin_install_zip($pdo, $v1); $check($r['ok'] && !$r['updated'], 'Version 1 lässt sich installieren'); $check(plugin_install_message($r, plugins_installed($pdo)['updprobe']) === 'Update-Probe 1.0.0 installiert (nicht signiert). Es ist noch nicht aktiv.', 'Meldung bei Neuinstallation: ' . plugin_install_message($r, plugins_installed($pdo)['updprobe'])); $check(plugin_set_active($pdo, 'updprobe', true, ['db.migrations', 'hooks.cron'])['ok'], 'Version 1 lässt sich aktivieren'); $check($cols() === ['id', 'name'], 'Tabelle von Version 1 ist da'); $reboot(); $check(($GLOBALS['updprobe_loaded'] ?? 0) === 1, 'Plugin wird geladen'); $hash1 = $pdo->query("SELECT package_hash FROM plugins WHERE slug = 'updprobe'")->fetchColumn(); $check(get_setting('plugin_migrated.updprobe', '') === $hash1, 'Paketstand von Version 1 ist als eingespielt vermerkt'); $migrationsBefore = (int) $pdo->query("SELECT COUNT(*) FROM schema_migrations WHERE migration LIKE 'plugin_updprobe_%'")->fetchColumn(); // Version 2 bringt eine neue Spalte mit; das Plugin bleibt aktiv $v2 = $mkZip('1.1.0', [ '001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)', '001_items.mysql.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL)', '002_extra.sqlite.sql' => 'ALTER TABLE plugin_updprobe_items ADD COLUMN extra TEXT NULL', '002_extra.mysql.sql' => 'ALTER TABLE plugin_updprobe_items ADD COLUMN extra VARCHAR(50) NULL', ]); $r2 = plugin_install_zip($pdo, $v2); $row = plugins_installed($pdo)['updprobe']; $check($r2['ok'] && $r2['updated'] && (int) $row['is_active'] === 1, 'Update eines aktiven Plugins: bleibt aktiv'); $msg = plugin_install_message($r2, $row); $check(str_contains($msg, 'auf Version 1.1.0 aktualisiert') && str_contains($msg, 'läuft weiter') && !str_contains($msg, 'noch nicht aktiv'), 'Meldung beim Update eines aktiven Plugins: ' . $msg); $check(!in_array('extra', $cols(), true), 'unmittelbar nach dem Update ist die neue Spalte noch nicht da (läuft beim nächsten Aufruf)'); $reboot(); $check(in_array('extra', $cols(), true), 'beim nächsten Aufruf wird die neue Spalte automatisch eingespielt'); $hash2 = $pdo->query("SELECT package_hash FROM plugins WHERE slug = 'updprobe'")->fetchColumn(); $check($hash2 !== $hash1 && get_setting('plugin_migrated.updprobe', '') === $hash2, 'Paketstand von Version 2 ist vermerkt'); $check((int) $pdo->query("SELECT COUNT(*) FROM schema_migrations WHERE migration LIKE 'plugin_updprobe_%'")->fetchColumn() === $migrationsBefore + 1, 'nur die neue Änderung wurde eingespielt'); $reboot(); $check((int) $pdo->query("SELECT COUNT(*) FROM schema_migrations WHERE migration LIKE 'plugin_updprobe_%'")->fetchColumn() === $migrationsBefore + 1, 'weitere Aufrufe spielen nichts mehr ein'); $check((int) $pdo->query("SELECT fail_count FROM plugins WHERE slug = 'updprobe'")->fetchColumn() === 0, 'keine Fehler gezählt'); // Version 3 mit kaputter Änderung: Fehler wird sichtbar, das Plugin wird nicht stillschweigend weiterbenutzt $v3 = $mkZip('1.2.0', [ '001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)', '002_extra.sqlite.sql' => 'ALTER TABLE plugin_updprobe_items ADD COLUMN extra TEXT NULL', '003_kaputt.sqlite.sql' => 'ALTER TABLE plugin_gibt_es_nicht ADD COLUMN x TEXT NULL', ]); plugin_install_zip($pdo, $v3); $reboot(); $p = plugins_installed($pdo)['updprobe']; $check((int) $p['fail_count'] >= 1 && str_contains((string) $p['last_error'], 'Datenbank-Änderung fehlgeschlagen'), 'fehlerhafte Änderung wird gemeldet: ' . $p['last_error']); $check(get_setting('plugin_migrated.updprobe', '') === $hash2, 'der kaputte Stand gilt nicht als eingespielt (nächster Versuch beim nächsten Aufruf)'); // Update mit neuer Berechtigung (nicht signiert): Plugin wird angehalten, Meldung sagt es $v4 = $mkZip('1.3.0', ['001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)'], ['db.migrations', 'hooks.cron', 'ui.menu']); plugin_set_active($pdo, 'updprobe', true, ['db.migrations', 'hooks.cron']); $r4 = plugin_install_zip($pdo, $v4); $check($r4['needs_reapproval'] === ['ui.menu'] && (int) plugins_installed($pdo)['updprobe']['is_active'] === 0, 'nicht signiertes Update mit neuer Berechtigung wird angehalten'); $check(str_contains(plugin_install_message($r4, plugins_installed($pdo)['updprobe']), 'zusätzliche Berechtigungen') && str_contains(plugin_install_message($r4, plugins_installed($pdo)['updprobe']), 'nicht aktiv'), 'Meldung nennt die zusätzlichen Berechtigungen und dass das Plugin nicht aktiv ist'); // Update eines inaktiven Plugins ohne neue Berechtigungen plugin_set_active($pdo, 'updprobe', true, ['db.migrations', 'hooks.cron', 'ui.menu']); plugin_set_active($pdo, 'updprobe', false); $v5 = $mkZip('1.4.0', ['001_items.sqlite.sql' => 'CREATE TABLE IF NOT EXISTS plugin_updprobe_items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)'], ['db.migrations', 'hooks.cron', 'ui.menu']); $r5 = plugin_install_zip($pdo, $v5); $check(str_contains(plugin_install_message($r5, plugins_installed($pdo)['updprobe']), 'weiterhin nicht aktiv'), 'Meldung beim Update eines inaktiven Plugins'); // Ohne Berechtigung db.migrations kein automatisches Einspielen $check(!str_contains((string) file_get_contents(dirname(__DIR__) . '/includes/plugins.php'), "plugin_ensure_migrated(\$slug, (string) (\$row['package_hash'] ?? ''));\n") || str_contains((string) file_get_contents(dirname(__DIR__) . '/includes/plugins.php'), "in_array('db.migrations', \$manifest['permissions'], true)) plugin_ensure_migrated"), 'nur Plugins mit der Berechtigung db.migrations bekommen automatische Änderungen'); foreach (scandir($pluginDir) ?: [] as $entry) { if ($entry === '.' || $entry === '..') continue; is_dir($pluginDir . '/' . $entry) ? plugin_rmdir($pluginDir . '/' . $entry) : @unlink($pluginDir . '/' . $entry); } @rmdir($pluginDir); foreach ([$v1, $v2, $v3, $v4, $v5] as $z) @unlink($z); if ($failures) { fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n"); exit(1); } echo 'Plugin update migration checks passed.' . "\n";