mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Index page: remove Mots-clés button, move export to bulk selection, fix ZipArchive error, move DB export to paramètres, sticky thead
- Remove 'Mots-clés' button from toolbar (redundant with admin sidebar tags) - Replace export dialog with 'Exporter CSV' + 'Exporter fichiers' buttons in bulk selection bar - Export dispatcher now accepts ?ids=1,2,3 for per-selection export - All ExportController/Database methods accept optional thesisIds array - Graceful error message when ZipArchive extension is missing on server - Move DB export (SQLite download) to paramètres → Maintenance section - Sticky table column headers (position: sticky, top: 0, z-index: 5) for index page table
This commit is contained in:
@@ -21,6 +21,18 @@ $doCsv = !empty($_GET['csv']);
|
||||
$doFiles = !empty($_GET['files']);
|
||||
$doDb = !empty($_GET['db']);
|
||||
|
||||
// Optional: filter by selected thesis IDs (bulk selection export)
|
||||
$idsRaw = $_GET['ids'] ?? '';
|
||||
$selectedIds = [];
|
||||
if ($idsRaw !== '') {
|
||||
foreach (explode(',', $idsRaw) as $id) {
|
||||
$id = (int) trim($id);
|
||||
if ($id > 0) {
|
||||
$selectedIds[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$doCsv && !$doFiles && !$doDb) {
|
||||
$doCsv = true;
|
||||
}
|
||||
@@ -40,7 +52,7 @@ if ($doCsv) {
|
||||
|
||||
$out = fopen('php://output', 'w');
|
||||
fputcsv($out, ExportController::CSV_HEADERS, ',', '"', '');
|
||||
$rows = $controller->exportAllTheses();
|
||||
$rows = $controller->exportAllTheses($selectedIds);
|
||||
foreach ($rows as $csvLine) {
|
||||
fputcsv($out, $csvLine, ',', '"', '');
|
||||
}
|
||||
@@ -53,10 +65,14 @@ if ($doCsv) {
|
||||
}
|
||||
|
||||
if ($doFiles) {
|
||||
if (!class_exists('ZipArchive')) {
|
||||
http_response_code(500);
|
||||
exit('Module PHP <code>zip</code> non installé sur le serveur. Contactez l\'administrateur système (<code>apt install php8.4-zip</code>).');
|
||||
}
|
||||
try {
|
||||
$zipPath = $controller->createExportZip();
|
||||
$zipPath = $controller->createExportZip($selectedIds);
|
||||
$fileSize = filesize($zipPath);
|
||||
$fileCount = count($controller->getAllThesisFiles());
|
||||
$fileCount = count($controller->getAllThesisFiles($selectedIds));
|
||||
|
||||
AdminLogger::make()->logFilesExport($fileCount, (int)$fileSize);
|
||||
|
||||
|
||||
@@ -74,6 +74,16 @@
|
||||
padding: 0 0 var(--space-2xl);
|
||||
}
|
||||
|
||||
#admin-table-wrap table thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#admin-table-wrap table thead th {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.admin-body main > table tbody tr:nth-child(even) {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
@@ -40,16 +40,17 @@ class ExportController
|
||||
// ── Files export ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Fetch all thesis file records with their thesis identifier.
|
||||
* Fetch thesis file records with their thesis identifier.
|
||||
*
|
||||
* @param int[] $thesisIds Optional filter by thesis IDs.
|
||||
* @return list<array{id:int, thesis_id:int, identifier:?string, file_type:string,
|
||||
* file_path:string, file_name:string, file_size:?int,
|
||||
* mime_type:?string, description:?string, sort_order:int,
|
||||
* display_label:?string, file_hash:?string}>
|
||||
*/
|
||||
public function getAllThesisFiles(): array
|
||||
public function getAllThesisFiles(array $thesisIds = []): array
|
||||
{
|
||||
return $this->db->getAllThesisFilesForExport();
|
||||
return $this->db->getAllThesisFilesForExport($thesisIds);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,12 +59,13 @@ class ExportController
|
||||
* The manifest maps identifier → { title, files: [{type, path, name, size, mime, hash, label}] }
|
||||
* and is used on restore to re-link files to DB records.
|
||||
*
|
||||
* @param int[] $thesisIds Optional filter by thesis IDs.
|
||||
* @return array
|
||||
*/
|
||||
public function buildExportManifest(): array
|
||||
public function buildExportManifest(array $thesisIds = []): array
|
||||
{
|
||||
$files = $this->getAllThesisFiles();
|
||||
$theses = $this->db->getAllThesesForExport();
|
||||
$files = $this->getAllThesisFiles($thesisIds);
|
||||
$theses = $this->db->getAllThesesForExport($thesisIds);
|
||||
|
||||
// Index theses by id for O(1) lookup
|
||||
$byId = [];
|
||||
@@ -115,17 +117,18 @@ class ExportController
|
||||
* Returns the path to the temporary zip file. Caller is responsible
|
||||
* for unlink() after streaming.
|
||||
*
|
||||
* @param int[] $thesisIds Optional filter by thesis IDs.
|
||||
* @param string|null $baseDir Base directory path for files inside the zip.
|
||||
* Defaults to "files" (so files are at "files/theses/...").
|
||||
* @return string Absolute path to the generated zip file.
|
||||
* @throws Exception if zip creation fails.
|
||||
*/
|
||||
public function createExportZip(?string $baseDir = null): string
|
||||
public function createExportZip(array $thesisIds = [], ?string $baseDir = null): string
|
||||
{
|
||||
$baseDir ??= 'files';
|
||||
$storageRoot = defined('STORAGE_ROOT') ? STORAGE_ROOT : APP_ROOT . '/storage';
|
||||
$files = $this->getAllThesisFiles();
|
||||
$manifest = $this->buildExportManifest();
|
||||
$files = $this->getAllThesisFiles($thesisIds);
|
||||
$manifest = $this->buildExportManifest($thesisIds);
|
||||
|
||||
$tmpPath = tempnam(sys_get_temp_dir(), 'xamxam-export-');
|
||||
if ($tmpPath === false) {
|
||||
@@ -214,12 +217,13 @@ class ExportController
|
||||
*
|
||||
* Uses batch queries (one per related table) to avoid N+1.
|
||||
*
|
||||
* @param int[] $thesisIds Optional filter by thesis IDs.
|
||||
* @return list<list<string>> Each inner list has CSV_HEADERS_COUNT elements.
|
||||
*/
|
||||
public function exportAllTheses(): array
|
||||
public function exportAllTheses(array $thesisIds = []): array
|
||||
{
|
||||
// 1) Base thesis data (includes license_name via migration; fallback to license_type from the view)
|
||||
$theses = $this->db->getAllThesesForExport();
|
||||
$theses = $this->db->getAllThesesForExport($thesisIds);
|
||||
if ($theses === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -2540,9 +2540,12 @@ class Database
|
||||
* Fetch all theses (admin — includes unpublished) with every column
|
||||
* needed for the CSV export.
|
||||
*/
|
||||
public function getAllThesesForExport(): array
|
||||
/**
|
||||
* @param int[] $thesisIds Optional filter by thesis IDs.
|
||||
*/
|
||||
public function getAllThesesForExport(array $thesisIds = []): array
|
||||
{
|
||||
return $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT
|
||||
t.id, t.identifier, t.title, t.subtitle, t.year,
|
||||
o.name AS orientation,
|
||||
@@ -2561,88 +2564,143 @@ class Database
|
||||
LEFT JOIN finality_types ft ON t.finality_id = ft.id
|
||||
LEFT JOIN access_types at ON t.access_type_id = at.id
|
||||
LEFT JOIN license_types lt ON t.license_id = lt.id
|
||||
ORDER BY t.year DESC, t.title ASC
|
||||
')->fetchAll();
|
||||
';
|
||||
if ($thesisIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($thesisIds), '?'));
|
||||
$sql .= " WHERE t.id IN ($placeholders)";
|
||||
$stmt = $this->pdo->prepare($sql . ' ORDER BY t.year DESC, t.title ASC');
|
||||
$stmt->execute($thesisIds);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql . ' ORDER BY t.year DESC, t.title ASC')->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* All thesis→author rows with author name and email.
|
||||
* @param int[] $thesisIds Optional filter.
|
||||
*/
|
||||
public function getAllThesisAuthorsForExport(): array
|
||||
public function getAllThesisAuthorsForExport(array $thesisIds = []): array
|
||||
{
|
||||
return $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT ta.thesis_id, a.name, a.email
|
||||
FROM thesis_authors ta
|
||||
JOIN authors a ON a.id = ta.author_id
|
||||
ORDER BY ta.thesis_id, ta.author_order
|
||||
')->fetchAll();
|
||||
';
|
||||
if ($thesisIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($thesisIds), '?'));
|
||||
$sql .= " WHERE ta.thesis_id IN ($placeholders)";
|
||||
$stmt = $this->pdo->prepare($sql . ' ORDER BY ta.thesis_id, ta.author_order');
|
||||
$stmt->execute($thesisIds);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql . ' ORDER BY ta.thesis_id, ta.author_order')->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* All thesis→supervisor rows with name.
|
||||
* @param int[] $thesisIds Optional filter.
|
||||
*/
|
||||
public function getAllThesisSupervisorsForExport(): array
|
||||
public function getAllThesisSupervisorsForExport(array $thesisIds = []): array
|
||||
{
|
||||
return $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT ts.thesis_id, s.name, ts.role, ts.is_external, ts.is_ulb
|
||||
FROM thesis_supervisors ts
|
||||
JOIN supervisors s ON s.id = ts.supervisor_id
|
||||
ORDER BY ts.thesis_id, ts.supervisor_order
|
||||
')->fetchAll();
|
||||
';
|
||||
if ($thesisIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($thesisIds), '?'));
|
||||
$sql .= " WHERE ts.thesis_id IN ($placeholders)";
|
||||
$stmt = $this->pdo->prepare($sql . ' ORDER BY ts.thesis_id, ts.supervisor_order');
|
||||
$stmt->execute($thesisIds);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql . ' ORDER BY ts.thesis_id, ts.supervisor_order')->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* All thesis→tag rows with tag name.
|
||||
* @param int[] $thesisIds Optional filter.
|
||||
*/
|
||||
public function getAllThesisTagsForExport(): array
|
||||
public function getAllThesisTagsForExport(array $thesisIds = []): array
|
||||
{
|
||||
return $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT tt.thesis_id, t.name
|
||||
FROM thesis_tags tt
|
||||
JOIN tags t ON t.id = tt.tag_id
|
||||
ORDER BY tt.thesis_id, t.name
|
||||
')->fetchAll();
|
||||
';
|
||||
if ($thesisIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($thesisIds), '?'));
|
||||
$sql .= " WHERE tt.thesis_id IN ($placeholders)";
|
||||
$stmt = $this->pdo->prepare($sql . ' ORDER BY tt.thesis_id, t.name');
|
||||
$stmt->execute($thesisIds);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql . ' ORDER BY tt.thesis_id, t.name')->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* All thesis→language rows with language name.
|
||||
* @param int[] $thesisIds Optional filter.
|
||||
*/
|
||||
public function getAllThesisLanguagesForExport(): array
|
||||
public function getAllThesisLanguagesForExport(array $thesisIds = []): array
|
||||
{
|
||||
return $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT tl.thesis_id, l.name
|
||||
FROM thesis_languages tl
|
||||
JOIN languages l ON l.id = tl.language_id
|
||||
ORDER BY tl.thesis_id, l.name
|
||||
')->fetchAll();
|
||||
';
|
||||
if ($thesisIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($thesisIds), '?'));
|
||||
$sql .= " WHERE tl.thesis_id IN ($placeholders)";
|
||||
$stmt = $this->pdo->prepare($sql . ' ORDER BY tl.thesis_id, l.name');
|
||||
$stmt->execute($thesisIds);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql . ' ORDER BY tl.thesis_id, l.name')->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* All thesis→format rows with format name.
|
||||
* @param int[] $thesisIds Optional filter.
|
||||
*/
|
||||
public function getAllThesisFormatsForExport(): array
|
||||
public function getAllThesisFormatsForExport(array $thesisIds = []): array
|
||||
{
|
||||
return $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT tf.thesis_id, ft.name
|
||||
FROM thesis_formats tf
|
||||
JOIN format_types ft ON ft.id = tf.format_id
|
||||
ORDER BY tf.thesis_id, ft.name
|
||||
')->fetchAll();
|
||||
';
|
||||
if ($thesisIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($thesisIds), '?'));
|
||||
$sql .= " WHERE tf.thesis_id IN ($placeholders)";
|
||||
$stmt = $this->pdo->prepare($sql . ' ORDER BY tf.thesis_id, ft.name');
|
||||
$stmt->execute($thesisIds);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql . ' ORDER BY tf.thesis_id, ft.name')->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* All thesis files for the file-export ZIP.
|
||||
* Includes every thesis_files column + the thesis identifier for manifest
|
||||
* construction.
|
||||
* @param int[] $thesisIds Optional filter.
|
||||
*/
|
||||
public function getAllThesisFilesForExport(): array
|
||||
public function getAllThesisFilesForExport(array $thesisIds = []): array
|
||||
{
|
||||
return $this->pdo->query('
|
||||
$sql = '
|
||||
SELECT tf.*, t.identifier
|
||||
FROM thesis_files tf
|
||||
JOIN theses t ON t.id = tf.thesis_id
|
||||
ORDER BY t.year DESC, t.title ASC, tf.sort_order ASC
|
||||
')->fetchAll();
|
||||
';
|
||||
if ($thesisIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($thesisIds), '?'));
|
||||
$sql .= " WHERE tf.thesis_id IN ($placeholders)";
|
||||
$stmt = $this->pdo->prepare($sql . ' ORDER BY t.year DESC, t.title ASC, tf.sort_order ASC');
|
||||
$stmt->execute($thesisIds);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
return $this->pdo->query($sql . ' ORDER BY t.year DESC, t.title ASC, tf.sort_order ASC')->fetchAll();
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
|
||||
@@ -31,6 +31,8 @@ $sortArrow = function(string $col) use ($sortCol, $sortDir): string {
|
||||
<div class="admin-bulk-btns">
|
||||
<button type="button" class="btn btn--sm btn--green admin-btn-publish" onclick="confirmBulk('publish')">Publier</button>
|
||||
<button type="button" class="btn btn--sm btn--muted admin-btn-unpublish" onclick="confirmBulk('unpublish')">Dépublier</button>
|
||||
<button type="button" class="btn btn--sm" onclick="confirmExport()">Exporter CSV</button>
|
||||
<button type="button" class="btn btn--sm" onclick="confirmExportFiles()">Exporter fichiers</button>
|
||||
<button type="button" class="btn btn--sm btn--red admin-btn-delete" onclick="confirmBulk('delete')">Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script>
|
||||
function toggleAll(src){document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb=>cb.checked=src.checked);updateBulk();}
|
||||
function updateBulk(){const n=document.querySelectorAll('input[name="selected_theses[]"]:checked').length;const b=document.getElementById('bulk-actions');document.getElementById('selected-count').textContent=n;b.style.display=n>0?'flex':'none';}
|
||||
function confirmBulk(act){const n=document.querySelectorAll('input[name="selected_theses[]"]:checked').length;if(!n){document.getElementById('no-selection-dialog').showModal();return;}document.getElementById('bulk-action-input').value=act;if(act==='delete'){document.getElementById('bulk-delete-count').textContent=n;document.getElementById('bulk-delete-dialog').showModal();}else{document.getElementById('bulk-confirm-word').textContent=act==='publish'?'Publier':'Dépublier';document.getElementById('bulk-confirm-count').textContent=n;document.getElementById('bulk-confirm-dialog').showModal();}}
|
||||
function execBulk(){const a=document.getElementById('bulk-action-input').value;const f=document.getElementById('bulk-form');f.action = a==='delete' ? 'actions/delete.php' : 'actions/publish.php';const c=document.getElementById('bulk-checkboxes');c.innerHTML='';document.querySelectorAll('input[name="selected_theses[]"]:checked').forEach(cb=>{const inp=document.createElement('input');inp.type='hidden';inp.name='selected_theses[]';inp.value=cb.value;c.appendChild(inp);});f.submit();}
|
||||
function getSelectedIds(){return Array.from(document.querySelectorAll('input[name="selected_theses[]"]:checked')).map(cb=>cb.value);}
|
||||
function confirmBulk(act){const ids=getSelectedIds();if(!ids.length){document.getElementById('no-selection-dialog').showModal();return;}const n=ids.length;document.getElementById('bulk-action-input').value=act;if(act==='delete'){document.getElementById('bulk-delete-count').textContent=n;document.getElementById('bulk-delete-dialog').showModal();}else{document.getElementById('bulk-confirm-word').textContent=act=='publish'?'Publier':'Dépublier';document.getElementById('bulk-confirm-count').textContent=n;document.getElementById('bulk-confirm-dialog').showModal();}}
|
||||
function execBulk(){const a=document.getElementById('bulk-action-input').value;const f=document.getElementById('bulk-form');f.action = a=='delete' ? 'actions/delete.php' : 'actions/publish.php';const c=document.getElementById('bulk-checkboxes');c.innerHTML='';getSelectedIds().forEach(id=>{const inp=document.createElement('input');inp.type='hidden';inp.name='selected_theses[]';inp.value=id;c.appendChild(inp);});f.submit();}
|
||||
function confirmExport(){const ids=getSelectedIds();if(!ids.length){document.getElementById('no-selection-dialog').showModal();return;}window.location.href='/admin/actions/export.php?csv=1&ids='+ids.join(',');}
|
||||
function confirmExportFiles(){const ids=getSelectedIds();if(!ids.length){document.getElementById('no-selection-dialog').showModal();return;}window.location.href='/admin/actions/export.php?files=1&ids='+ids.join(',');}
|
||||
function confirmDelete(id,title){document.getElementById('delete-thesis-title').textContent=title;document.getElementById('delete-thesis-dialog').showModal();document.getElementById('delete-dialog-confirm').onclick=function(){document.getElementById('delete-form-'+id).submit();};}
|
||||
document.addEventListener('DOMContentLoaded',()=>{document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb=>cb.addEventListener('change',updateBulk));});
|
||||
document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb=>cb.addEventListener('change',updateBulk));updateBulk();});
|
||||
@@ -31,7 +34,6 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
|
||||
</div>
|
||||
<div class="admin-btn-group">
|
||||
<a href="/admin/add.php" class="btn btn--primary btn--sm">+ Ajouter un TFE</a>
|
||||
<a href="/admin/tags.php" class="btn btn--primary btn--sm">Mots-clés</a>
|
||||
<?php if ($trashCount > 0): ?>
|
||||
<a href="/admin/index.php?tab=trash" class="btn btn--sm <?= $tab === 'trash' ? 'btn--primary' : 'btn--secondary' ?>">
|
||||
Corbeille (<?= $trashCount ?>)
|
||||
@@ -47,10 +49,6 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
|
||||
onclick="document.getElementById('import-dialog').showModal()">
|
||||
Importer
|
||||
</button>
|
||||
<button type="button" class="btn btn--primary btn--sm" id="export-dialog-btn"
|
||||
onclick="document.getElementById('export-dialog').showModal()">
|
||||
Exporter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -244,43 +242,6 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
|
||||
<?php endif; ?>
|
||||
</dialog>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
EXPORT DIALOG
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<dialog id="export-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="export-dialog-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h2 id="export-dialog-title">Exporter</h2>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="document.getElementById('export-dialog').close()">✕</button>
|
||||
</div>
|
||||
|
||||
<form method="get" action="/admin/actions/export.php" class="admin-form">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="csv" value="1" checked>
|
||||
CSV (tableau des métadonnées)
|
||||
</label>
|
||||
</div>
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="files" value="1" checked>
|
||||
Fichiers (archive ZIP)
|
||||
</label>
|
||||
</div>
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="db" value="1">
|
||||
Base de données (SQLite)
|
||||
</label>
|
||||
</div>
|
||||
<div class="admin-form-footer">
|
||||
<button type="submit" class="btn btn--primary">Exporter</button>
|
||||
<button type="button" class="btn btn--secondary"
|
||||
onclick="document.getElementById('export-dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
TMP CLEANUP DIALOG
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
|
||||
@@ -65,6 +65,16 @@
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top:var(--space-l);margin-bottom:var(--space-xs);font-size:var(--step-0)">Export de la base de données</h3>
|
||||
<p style="margin-bottom:var(--space-s)">
|
||||
Télécharger une copie complète de la base de données SQLite
|
||||
(<code>xamxam.db</code>) pour sauvegarde manuelle.
|
||||
</p>
|
||||
<form method="get" action="/admin/actions/export.php" class="param-form">
|
||||
<input type="hidden" name="db" value="1">
|
||||
<button type="submit" class="btn btn--primary">Télécharger la base de données</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user