Move site content between installations as a package

System now exports a content package and imports one, by upload or from
var/pakete. Pages with their revisions, areas, menus, media, forms,
redirects, terms, translations, design and settings are replaced;
submissions, statistics, tasks, plugin state and service keys stay, and
accounts only come along when that is chosen explicitly. A backup comes
first, the site is in maintenance meanwhile, and every file and document
in the package is checked before anything is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5.5 committed 2026-10-12 16:31:06 +02:00
1 parent a57b829a15
commit 32e42f973b
13 files changed
+973 -2

No files matched your search

+38
View File
@@ -234,6 +234,44 @@
</div>
{% endif %}
<div class="card" id="paket">
<div class="card-head"><h2>Inhalte übertragen</h2><span class="muted">Export und Import · var/pakete</span></div>
<div class="card-body">
<p class="muted">Ein Inhaltspaket enthält Seiten mit allen Fassungen, Bereiche, Menüs, Medien und PDFs, Formulare, Weiterleitungen, Kategorien, Übersetzungen, Design und Einstellungen. Nicht darin: Anfragen, Statistik, Zugangsdaten zu Diensten (Turnstile) und – ohne Häkchen – die Konten.</p>
<h3>Exportieren</h3>
<form action="{{ path('admin_package_export') }}" method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('system') }}">
<label class="check-label"><input type="checkbox" name="benutzer" value="1"> Konten mit Passwörtern mitnehmen</label>
<p class="field-help">Nur nötig, wenn die andere Website noch kein Konto hat. Das Paket enthält dann die Passwort-Hashes – nicht weitergeben.</p>
<button class="secondary" type="submit">Paket herunterladen</button>
</form>
<h3>Importieren</h3>
<form action="{{ path('admin_package_import') }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('system') }}">
<label for="paket-datei">Paket hochladen (.zip)</label>
<input id="paket-datei" type="file" name="paket" accept=".zip,application/zip">
<p class="field-help">Höchstens {{ uploadLimit }} je Upload (Einstellung des Hostings). Größere Pakete per FTP nach <code>var/pakete/</code> legen, dann erscheinen sie hier zur Auswahl.</p>
{% if packages is not empty %}
<fieldset class="field-group">
<legend>Oder ein Paket aus var/pakete/</legend>
{% for package in packages %}
<label class="check-label"><input type="radio" name="wartend" value="{{ package.name }}">
<code>{{ package.name }}</code> – {% if package.manifest %}„{{ package.manifest.website }}“, Fassung {{ package.manifest.fassung }}, {{ package.manifest.inhalt.page|default(0) }} Seiten, {{ package.manifest.medien }} Dateien{% if package.manifest.benutzer %}, mit Konten{% endif %}{% else %}<span class="muted">kein gültiges Paket</span>{% endif %}
</label>
{% endfor %}
</fieldset>
{% endif %}
<label class="check-label"><input type="checkbox" name="benutzer" value="1"> Konten und Passwörter aus dem Paket übernehmen</label>
<p class="field-help">Ohne Häkchen bleiben Benutzer und Passwörter dieser Website, wie sie sind.</p>
<label class="check-label"><input type="checkbox" name="bestaetigt" value="1" required> Ich weiß, dass die Inhalte dieser Website ersetzt werden.</label>
<p class="field-help">Vorher wird automatisch eine Sicherung angelegt; während des Imports antwortet die Website mit der Wartungsseite.</p>
<button class="primary" type="submit">Paket importieren</button>
</form>
</div>
</div>
<div class="card">
<div class="card-head"><h2>Sicherungen</h2><span class="muted">{{ backups|length }} vorhanden · var/backups</span></div>
<div class="card-body">