Init Git Prod Mars 2026

This commit is contained in:
Oleg Barykin committed 2026-03-05 10:25:39 +01:00
commit d81efe93b1
362 files changed
+86627

No files matched your search

+206
View File
@@ -0,0 +1,206 @@
<?php
if (($_GET['page'] ?? '') !== 'backup_db') return;
require_once __DIR__ . '/../../public/includes/init_admin.php';
$roles=(array)($_SESSION['role']??[]);
if(!in_array('admin',$roles,true)){
echo '<div class="container my-4"><div class="alert alert-danger">'
. htmlspecialchars((string)($translations['backup_access_denied'] ?? 'Accès refusé.'), ENT_QUOTES, 'UTF-8')
. '</div></div>';
return;
}
if (!isset($pdo)) { $db=realpath(__DIR__.'/../../includes/db.php'); if($db) require_once $db; }
if (!isset($pdo) || !($pdo instanceof PDO)) {
echo '<div class="container my-4"><div class="alert alert-danger">'
. htmlspecialchars((string)($translations['backup_db_unavailable'] ?? 'DB indisponible.'), ENT_QUOTES, 'UTF-8')
. '</div></div>';
return;
}
try{$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);}catch(Throwable){}
require_once __DIR__.'/_lib_audit.php';
// reauth (10 min)
$mustReauth=function(int $ttl=600){$t=(int)($_SESSION['admin_reauth_at']??0);return ($t===0||(time()-$t)>$ttl);};
if ($mustReauth()){ $qs=http_build_query(['page'=>'reauth','redir'=>($_SERVER['REQUEST_URI']??'index.php?page=maintenance')]); header('Location:?'.$qs); exit; }
$e=static fn($s)=>htmlspecialchars((string)$s,ENT_QUOTES,'UTF-8');
if (empty($_SESSION['csrf_token'])) $_SESSION['csrf_token']=bin2hex(random_bytes(32));
$CSRF=$_SESSION['csrf_token'];
$siteRoot=realpath(__DIR__.'/../../');
$backupDir=$siteRoot.'/backups/db';
@is_dir($backupDir) || @mkdir($backupDir,0755,true);
$msg=null; $ok=null;
// Params
$retentionDays = max(0,(int)($_POST['retention'] ?? ($_GET['retention'] ?? 30)));
$useGzip = true;
if ($_SERVER['REQUEST_METHOD']==='POST') {
if (!hash_equals($CSRF, $_POST['csrf'] ?? '')) {
$msg = (string)($translations['backup_csrf_invalid'] ?? 'CSRF invalide.');
}
else {
$act = $_POST['action'] ?? '';
if ($act==='create') {
// Dump via PDO (fallback si mysqldump non dispo)
$dbName = $pdo->query("SELECT DATABASE()")->fetchColumn();
$fname = 'db_'.$dbName.'_'.date('Ymd_His').'.sql';
$tmp = $backupDir.'/'.$fname;
// --- export ---
$fh = @fopen($tmp,'wb');
if(!$fh){
$tpl = (string)($translations['backup_cannot_write_dir'] ?? "Impossible d'écrire dans {DIR}");
$msg = str_replace('{DIR}', (string)$backupDir, $tpl);
}
else {
fwrite($fh, "-- Backup of `$dbName` on ".date('c')."\nSET FOREIGN_KEY_CHECKS=0;\n");
// tables
$tables = $pdo->query("SHOW FULL TABLES WHERE Table_Type='BASE TABLE'")->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $t) {
// DDL
$row = $pdo->query("SHOW CREATE TABLE `$t`")->fetch(PDO::FETCH_ASSOC);
fwrite($fh, "\n-- Structure `$t`\nDROP TABLE IF EXISTS `$t`;\n".$row['Create Table'].";\n");
// DML
$st = $pdo->query("SELECT * FROM `$t`", PDO::FETCH_ASSOC);
$cols = array_map(fn($c)=>'`'.$c.'`', array_keys($st->fetch() ?: []));
if ($cols) {
// Refaire la requête pour ne pas “perdre” la première ligne
$st = $pdo->query("SELECT * FROM `$t`", PDO::FETCH_ASSOC);
$colList = implode(',', $cols);
$placeholders = '(' . implode(',', array_fill(0, count($cols), '?')) . ')';
$batch=[]; $n=0;
foreach($st as $row){
$vals = array_values($row);
$vals = array_map(function($v) use($pdo){ return isset($v)?$pdo->quote((string)$v):"NULL"; }, $vals);
$batch[] = '('.implode(',', $vals).')';
if (++$n % 200 === 0) { fwrite($fh, "INSERT INTO `$t` ($colList) VALUES\n".implode(",\n",$batch).";\n"); $batch=[]; }
}
if ($batch){ fwrite($fh, "INSERT INTO `$t` ($colList) VALUES\n".implode(",\n",$batch).";\n"); }
}
}
fwrite($fh, "SET FOREIGN_KEY_CHECKS=1;\n");
fclose($fh);
// gzip
if ($useGzip) {
$gz = $tmp.'.gz';
$gzh = gzopen($gz,'wb9');
$src = fopen($tmp,'rb');
while(!feof($src)){ gzwrite($gzh, fread($src, 1<<20)); }
fclose($src); gzclose($gzh);
@unlink($tmp);
$final=$gz;
} else { $final=$tmp; }
$tpl = (string)($translations['backup_created_ok'] ?? 'Sauvegarde créée : {FILE}');
$ok = str_replace('{FILE}', basename($final), $tpl);
AdminAudit::log($pdo, [
'admin_id'=>(int)($_SESSION['user_id']??0),
'action'=>'backup_create',
'scope'=>'maintenance',
'meta'=>['file'=>basename($final),'dir'=>'backups/db']
]);
}
}
elseif ($act==='prune') {
$cut = time()-($retentionDays*86400);
$n=0; foreach (glob($backupDir.'/db_*.sql*') as $f){ if(filemtime($f)<$cut){ @unlink($f); $n++; } }
$tpl = (string)($translations['backup_retention_applied'] ?? 'Rétention appliquée : {N} fichier(s) supprimé(s) (> {D} j)');
$ok = str_replace(['{N}','{D}'], [(string)$n, (string)$retentionDays], $tpl);
AdminAudit::log($pdo, [
'admin_id'=>(int)($_SESSION['user_id']??0),
'action'=>'backup_prune',
'scope'=>'maintenance',
'meta'=>['retention_days'=>$retentionDays]
]);
}
}
}
// Liste
$files = array_map('basename', glob($backupDir.'/db_*.sql*') ?: []);
usort($files, fn($a,$b)=>strcmp($b,$a)); // plus récent d'abord
?>
<div class="shadow p-4 rounded bg-light">
<div class="container my-4">
<div class="d-flex align-items-center gap-2 mb-3">
<h3 class="m-0"><?= $e($translations['backup_title'] ?? 'Sauvegardes de la base') ?></h3>
<span class="badge text-bg-secondary">
<?php
$tpl = (string)($translations['backup_files_badge'] ?? '{N} fichier(s)');
echo $e(str_replace('{N}', (string)count($files), $tpl));
?>
</span>
</div>
<?php if($ok): ?><div class="alert alert-success"><?= $e($ok) ?></div><?php endif; ?>
<?php if($msg): ?><div class="alert alert-danger"><?= $e($msg) ?></div><?php endif; ?>
<div class="card mb-3">
<div class="card-body">
<form method="post" class="row g-3">
<input type="hidden" name="csrf" value="<?= $e($CSRF) ?>">
<div class="col-12 d-flex gap-2">
<button class="btn btn-dark" name="action" value="create"
onclick="return confirm('<?= $e($translations['backup_confirm_create'] ?? 'Créer un dump SQL compressé ?') ?>')">
<?= $e($translations['backup_btn_create'] ?? 'Sauvegarder') ?>
</button>
<div class="ms-auto"></div>
<div class="input-group" style="max-width:320px">
<span class="input-group-text"><?= $e($translations['backup_retention_label'] ?? 'Rétention (jours)') ?></span>
<input class="form-control" type="number" min="0" name="retention" value="<?= (int)$retentionDays ?>">
<button class="btn btn-outline-danger" name="action" value="prune"
onclick="return confirm('<?= $e($translations['backup_confirm_prune'] ?? 'Supprimer les backups plus anciens ?') ?>')">
<?= $e($translations['backup_btn_prune'] ?? 'Go') ?>
</button>
</div>
</div>
</form>
</div>
<div class="card-footer small text-muted">
<?php
$tpl = (string)($translations['backup_folder_line'] ?? 'Dossier: {DIR}');
echo $e(str_replace('{DIR}', (string)$backupDir, $tpl));
?>
</div>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-sm mb-0 mobile-cards">
<thead><tr>
<th><?= $e($translations['backup_th_file'] ?? 'Fichier') ?></th>
<th><?= $e($translations['backup_th_size'] ?? 'Taille') ?></th>
<th><?= $e($translations['backup_th_date'] ?? 'Date') ?></th>
<th></th>
</tr></thead>
<tbody>
<?php foreach($files as $f):
$full=$backupDir.'/'.$f; ?>
<tr>
<td><code><?= $e($f) ?></code></td>
<td><?= number_format((filesize($full)?:0)/1024,1) ?> KB</td>
<td><?= date('Y-m-d H:i:s', filemtime($full)?:time()) ?></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-primary"
href="/admin/tools/backup_download.php?f=<?= $e(urlencode($f)) ?>&csrf=<?= $e($CSRF) ?>">
<?= $e($translations['backup_btn_download'] ?? 'Télécharger') ?>
</a>
</td>
</tr>
<?php endforeach; if(!$files): ?>
<tr><td colspan="4" class="text-muted p-3"><?= $e($translations['backup_none_yet'] ?? 'Aucun backup encore.') ?></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>