This commit is contained in:
Flatlogic Bot 2026-04-07 17:47:38 +00:00
parent f55942bf0d
commit a26f2122a4
11 changed files with 1430 additions and 349 deletions

0
.perm_test_apache Normal file
View File

0
.perm_test_exec Normal file
View File

624
admin.php Normal file
View File

@ -0,0 +1,624 @@
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
auth_bootstrap();
if (!auth_is_admin()) {
header('Location: index.php');
exit;
}
$flash = auth_flash_get();
$flash_type = $flash['type'] ?? '';
$flash_message = $flash['message'] ?? '';
$edit_cl_auth_id = isset($_GET['edit']) ? (int) $_GET['edit'] : 0;
$edit_cl_auth_user = '';
$edit_cl_auth_right = 'member';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrf_token = isset($_POST['csrf_token']) ? (string) $_POST['csrf_token'] : null;
if (!auth_validate_csrf($csrf_token)) {
auth_flash_set('error', 'Jeton CSRF invalide.');
header('Location: admin.php');
exit;
}
$admin_action = (string) ($_POST['admin_action'] ?? '');
if ($admin_action === 'create') {
$submitted_cl_auth_user = trim((string) ($_POST['cl_auth_user'] ?? ''));
$submitted_cl_auth_pass = (string) ($_POST['cl_auth_pass'] ?? '');
$submitted_cl_auth_right = (string) ($_POST['cl_auth_right'] ?? 'member');
if ($submitted_cl_auth_user === '' || $submitted_cl_auth_pass === '') {
auth_flash_set('error', 'Le login et le mot de passe sont obligatoires.');
header('Location: admin.php');
exit;
}
if (!in_array($submitted_cl_auth_right, ['admin', 'member'], true)) {
auth_flash_set('error', 'Droit utilisateur invalide.');
header('Location: admin.php');
exit;
}
$stmt_duplicate_user = db()->prepare('SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_user = :cl_auth_user');
$stmt_duplicate_user->execute([
'cl_auth_user' => $submitted_cl_auth_user,
]);
$cl_auth_user_total = (int) $stmt_duplicate_user->fetchColumn();
if ($cl_auth_user_total > 0) {
auth_flash_set('error', 'Ce login existe déjà.');
header('Location: admin.php');
exit;
}
$cl_auth_user = $submitted_cl_auth_user;
$cl_auth_pass = password_hash($submitted_cl_auth_pass, PASSWORD_DEFAULT);
$cl_auth_right = $submitted_cl_auth_right;
$stmt_create_user = db()->prepare(
'INSERT INTO tbl_auth (cl_auth_user, cl_auth_pass, cl_auth_right) VALUES (:cl_auth_user, :cl_auth_pass, :cl_auth_right)'
);
$stmt_create_user->execute([
'cl_auth_user' => $cl_auth_user,
'cl_auth_pass' => $cl_auth_pass,
'cl_auth_right' => $cl_auth_right,
]);
auth_flash_set('success', 'Compte créé avec succès.');
header('Location: admin.php');
exit;
}
if ($admin_action === 'update') {
$cl_auth_id = (int) ($_POST['cl_auth_id'] ?? 0);
$submitted_cl_auth_user = trim((string) ($_POST['cl_auth_user'] ?? ''));
$submitted_cl_auth_pass = (string) ($_POST['cl_auth_pass'] ?? '');
$submitted_cl_auth_right = (string) ($_POST['cl_auth_right'] ?? 'member');
if ($cl_auth_id <= 0 || $submitted_cl_auth_user === '') {
auth_flash_set('error', 'Données de modification invalides.');
header('Location: admin.php');
exit;
}
if (!in_array($submitted_cl_auth_right, ['admin', 'member'], true)) {
auth_flash_set('error', 'Droit utilisateur invalide.');
header('Location: admin.php?edit=' . $cl_auth_id);
exit;
}
$stmt_tbl_auth = db()->prepare('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth WHERE cl_auth_id = :cl_auth_id LIMIT 1');
$stmt_tbl_auth->execute([
'cl_auth_id' => $cl_auth_id,
]);
$tbl_auth = $stmt_tbl_auth->fetch();
if (!$tbl_auth) {
auth_flash_set('error', 'Utilisateur introuvable.');
header('Location: admin.php');
exit;
}
$current_cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$current_cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$current_cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$current_cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($current_cl_auth_id, $current_cl_auth_user);
$stmt_duplicate_user = db()->prepare(
'SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_user = :cl_auth_user AND cl_auth_id <> :cl_auth_id'
);
$stmt_duplicate_user->execute([
'cl_auth_user' => $submitted_cl_auth_user,
'cl_auth_id' => $cl_auth_id,
]);
$cl_auth_user_total = (int) $stmt_duplicate_user->fetchColumn();
if ($cl_auth_user_total > 0) {
auth_flash_set('error', 'Ce login existe déjà.');
header('Location: admin.php?edit=' . $cl_auth_id);
exit;
}
if ($current_cl_auth_right === 'admin' && $submitted_cl_auth_right !== 'admin') {
$stmt_admin_total = db()->query("SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_right = 'admin'");
$cl_auth_admin_total = (int) $stmt_admin_total->fetchColumn();
if ($cl_auth_admin_total <= 1) {
auth_flash_set('error', 'Impossible de rétrograder le dernier administrateur.');
header('Location: admin.php?edit=' . $cl_auth_id);
exit;
}
}
$cl_auth_user = $submitted_cl_auth_user;
$cl_auth_right = $submitted_cl_auth_right;
$cl_auth_pass = $current_cl_auth_pass;
if ($submitted_cl_auth_pass !== '') {
$cl_auth_pass = password_hash($submitted_cl_auth_pass, PASSWORD_DEFAULT);
}
$stmt_update_user = db()->prepare(
'UPDATE tbl_auth
SET cl_auth_user = :cl_auth_user,
cl_auth_pass = :cl_auth_pass,
cl_auth_right = :cl_auth_right
WHERE cl_auth_id = :cl_auth_id'
);
$stmt_update_user->execute([
'cl_auth_user' => $cl_auth_user,
'cl_auth_pass' => $cl_auth_pass,
'cl_auth_right' => $cl_auth_right,
'cl_auth_id' => $cl_auth_id,
]);
if (isset($_SESSION['user']) && $_SESSION['user'] === $tbl_auth['cl_auth_user']) {
$_SESSION['user'] = $cl_auth_user;
$_SESSION['role'] = $cl_auth_right;
}
auth_flash_set('success', 'Compte modifié avec succès.');
header('Location: admin.php');
exit;
}
if ($admin_action === 'delete') {
$cl_auth_id = (int) ($_POST['cl_auth_id'] ?? 0);
if ($cl_auth_id <= 0) {
auth_flash_set('error', 'Suppression impossible.');
header('Location: admin.php');
exit;
}
$stmt_tbl_auth = db()->prepare('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth WHERE cl_auth_id = :cl_auth_id LIMIT 1');
$stmt_tbl_auth->execute([
'cl_auth_id' => $cl_auth_id,
]);
$tbl_auth = $stmt_tbl_auth->fetch();
if (!$tbl_auth) {
auth_flash_set('error', 'Utilisateur introuvable.');
header('Location: admin.php');
exit;
}
$cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($cl_auth_pass);
if ($cl_auth_right === 'admin') {
$stmt_admin_total = db()->query("SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_right = 'admin'");
$cl_auth_admin_total = (int) $stmt_admin_total->fetchColumn();
if ($cl_auth_admin_total <= 1) {
auth_flash_set('error', 'Impossible de supprimer le dernier administrateur.');
header('Location: admin.php');
exit;
}
}
$stmt_delete_user = db()->prepare('DELETE FROM tbl_auth WHERE cl_auth_id = :cl_auth_id');
$stmt_delete_user->execute([
'cl_auth_id' => $cl_auth_id,
]);
if (isset($_SESSION['user']) && $_SESSION['user'] === $cl_auth_user) {
header('Location: logout.php');
exit;
}
auth_flash_set('success', 'Compte supprimé avec succès.');
header('Location: admin.php');
exit;
}
}
if ($edit_cl_auth_id > 0) {
$stmt_edit_user = db()->prepare('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth WHERE cl_auth_id = :cl_auth_id LIMIT 1');
$stmt_edit_user->execute([
'cl_auth_id' => $edit_cl_auth_id,
]);
$tbl_auth = $stmt_edit_user->fetch();
if ($tbl_auth) {
$edit_cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$edit_cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$edit_cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$edit_cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($edit_cl_auth_pass);
} else {
$edit_cl_auth_id = 0;
auth_flash_set('error', 'Utilisateur introuvable.');
header('Location: admin.php');
exit;
}
}
$stmt_users = db()->query('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth ORDER BY cl_auth_user ASC');
$tbl_auth_all = $stmt_users->fetchAll();
$csrf_token = auth_csrf_token();
[$default_admin_user, $default_admin_password] = auth_default_admin_credentials();
$current_session_user = isset($_SESSION['user']) ? (string) $_SESSION['user'] : '';
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Administration Sécure | R.E.A.C.T.</title>
<link rel="stylesheet" type="text/css" href="css/styles.css">
<link rel="stylesheet" type="text/css" href="css/default.css">
<style>
:root {
--primary: #a29b78;
--primary-glow: rgba(162, 155, 120, 0.4);
--bg-dark: #080a0f;
--card-bg: rgba(20, 24, 33, 0.85);
--border-glow: rgba(162, 155, 120, 0.25);
--danger: #ff4d4d;
--success: #00ff88;
}
body {
background: radial-gradient(circle at top right, #1a1f2e, var(--bg-dark));
background-attachment: fixed;
color: #e0e0e0;
font-family: 'Electrolize', sans-serif;
margin: 0;
overflow-x: hidden;
min-height: 100vh;
}
.admin-layout {
display: flex;
flex-direction: column;
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.admin-topbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
background: var(--card-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--border-glow);
border-radius: 12px;
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.topbar-info h1 {
margin: 0;
font-size: 1.5rem;
letter-spacing: 2px;
text-transform: uppercase;
background: linear-gradient(90deg, #fff, var(--primary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.topbar-info p {
margin: 0.25rem 0 0;
font-size: 0.85rem;
color: var(--primary);
opacity: 0.8;
}
.topbar-actions {
display: flex;
gap: 1rem;
}
.btn-modern {
padding: 0.6rem 1.2rem;
border: 1px solid var(--primary);
background: transparent;
color: #fff;
font-family: 'Electrolize', sans-serif;
font-size: 0.9rem;
text-transform: uppercase;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 4px;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn-modern:hover {
background: var(--primary);
color: var(--bg-dark);
box-shadow: 0 0 15px var(--primary-glow);
}
.btn-modern.danger {
border-color: var(--danger);
color: var(--danger);
}
.btn-modern.danger:hover {
background: var(--danger);
color: #fff;
box-shadow: 0 0 15px rgba(255, 77, 77, 0.3);
}
.admin-content {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
}
@media (max-width: 1024px) {
.admin-content { grid-template-columns: 1fr; }
}
.glass-card {
background: var(--card-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--border-glow);
border-radius: 12px;
padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.glass-card h2 {
margin-top: 0;
margin-bottom: 1.5rem;
font-size: 1.25rem;
color: var(--primary);
border-bottom: 1px solid var(--border-glow);
padding-bottom: 0.75rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.85rem;
color: #aaa;
text-transform: uppercase;
}
.form-control {
width: 100%;
padding: 0.8rem 1rem;
background: rgba(0, 0, 0, 0.3);
border: 1px solid #444;
border-radius: 4px;
color: #fff;
font-family: 'Electrolize', sans-serif;
transition: border-color 0.3s;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
background: rgba(0, 0, 0, 0.5);
}
.modern-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
}
.modern-table th {
text-align: left;
padding: 1rem;
font-size: 0.8rem;
text-transform: uppercase;
color: var(--primary);
opacity: 0.7;
}
.modern-table td {
padding: 1rem;
background: rgba(255, 255, 255, 0.03);
border-top: 1px solid rgba(255, 255, 255, 0.05);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.modern-table td:first-child {
border-left: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 8px 0 0 8px;
}
.modern-table td:last-child {
border-right: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 0 8px 8px 0;
}
.modern-table tr:hover td {
background: rgba(162, 155, 120, 0.05);
}
.badge {
padding: 0.25rem 0.6rem;
border-radius: 4px;
font-size: 0.75rem;
text-transform: uppercase;
}
.badge-admin { background: rgba(162, 155, 120, 0.2); color: var(--primary); border: 1px solid var(--primary); }
.badge-member { background: rgba(255, 255, 255, 0.1); color: #ccc; border: 1px solid #555; }
.flash {
padding: 1rem 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
font-size: 0.9rem;
border-left: 4px solid var(--primary);
background: rgba(162, 155, 120, 0.1);
}
.flash.error { border-color: var(--danger); background: rgba(255, 77, 77, 0.1); color: #ffbaba; }
.flash.success { border-color: var(--success); background: rgba(0, 255, 136, 0.1); color: #baffda; }
.row-actions {
display: flex;
gap: 0.5rem;
}
.btn-mini {
padding: 0.3rem 0.6rem;
font-size: 0.75rem;
}
.user-id {
font-family: monospace;
color: var(--primary);
font-weight: bold;
}
.empty-state {
padding: 2rem;
text-align: center;
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<div class="admin-layout">
<header class="admin-topbar">
<div class="topbar-info">
<h1>R.E.A.C.T. Core Admin</h1>
<p>Niveau d\'accès : <strong>Administrateur</strong> | Session : <strong><?php echo htmlspecialchars($current_session_user, ENT_QUOTES, 'UTF-8'); ?></strong></p>
</div>
<div class="topbar-actions">
<a href="index.php" class="btn-modern">Retour au site</a>
<a href="logout.php" class="btn-modern danger">Session End</a>
</div>
</header>
<?php if ($flash_message !== ''): ?>
<div class="flash <?php echo htmlspecialchars($flash_type, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo htmlspecialchars($flash_message, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<?php if ($default_admin_user === 'admin'): ?>
<div class="flash">
<strong style="color: var(--primary);">Sécurité critique :</strong> Les identifiants par défaut sont actifs.
(<code><?php echo htmlspecialchars($default_admin_user, ENT_QUOTES, 'UTF-8'); ?></code> / <code><?php echo htmlspecialchars($default_admin_password, ENT_QUOTES, 'UTF-8'); ?></code>)
<br><small>Veuillez modifier ces accès dès maintenant.</small>
</div>
<?php endif; ?>
<main class="admin-content">
<!-- Form Card -->
<section class="glass-card">
<h2><?php echo $edit_cl_auth_id > 0 ? 'Mise à jour sujet' : 'Nouveau sujet'; ?></h2>
<form method="post" action="admin.php<?php echo $edit_cl_auth_id > 0 ? '?edit=' . $edit_cl_auth_id : ''; ?>">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="admin_action" value="<?php echo $edit_cl_auth_id > 0 ? 'update' : 'create'; ?>">
<?php if ($edit_cl_auth_id > 0):
?><input type="hidden" name="cl_auth_id" value="<?php echo $edit_cl_auth_id; ?>">
<?php endif; ?>
<div class="form-group">
<label for="cl_auth_user">Identifiant Système</label>
<input class="form-control" id="cl_auth_user" name="cl_auth_user" type="text" required value="<?php echo htmlspecialchars($edit_cl_auth_user, ENT_QUOTES, 'UTF-8'); ?>" placeholder="Nom d\'utilisateur">
</div>
<div class="form-group">
<label for="cl_auth_pass"><?php echo $edit_cl_auth_id > 0 ? 'Nouveau Secret (vide = inchangé)' : 'Secret d\'accès'; ?></label>
<input class="form-control" id="cl_auth_pass" name="cl_auth_pass" type="password" <?php echo $edit_cl_auth_id > 0 ? '' : 'required'; ?> placeholder="••••••••">
</div>
<div class="form-group">
<label for="cl_auth_right">Niveau d\'accréditation</label>
<select class="form-control" id="cl_auth_right" name="cl_auth_right">
<option value="admin" <?php echo $edit_cl_auth_right === 'admin' ? 'selected' : ''; ?>>Admin</option>
<option value="member" <?php echo $edit_cl_auth_right === 'member' ? 'selected' : ''; ?>>Membre</option>
</select>
</div>
<div style="display: flex; gap: 10px; margin-top: 2rem;">
<button class="btn-modern" style="flex: 2;" type="submit">
<?php echo $edit_cl_auth_id > 0 ? 'Appliquer' : 'Initialiser'; ?>
</button>
<?php if ($edit_cl_auth_id > 0):
?><a class="btn-modern danger" style="flex: 1;" href="admin.php">Annuler</a>
<?php endif; ?>
</div>
</form>
</section>
<!-- Table Card -->
<section class="glass-card">
<h2>Base de données sujets</h2>
<div style="overflow-x: auto;">
<table class="modern-table">
<thead>
<tr>
<th>UID</th>
<th>Sujet</th>
<th>Accréditation</th>
<th style="text-align: right;">Opérations</th>
</tr>
</thead>
<tbody>
<?php if (empty($tbl_auth_all)): ?>
<tr>
<td colspan="4" class="empty-state">Aucun sujet détecté dans la base.</td>
</tr>
<?php else:
?><?php foreach ($tbl_auth_all as $tbl_auth):
?><?php
$cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$cl_auth_right = (string) $tbl_auth['cl_auth_right'];
?>
<tr>
<td><span class="user-id">#<?php echo sprintf('%03d', $cl_auth_id); ?></span></td>
<td><strong><?php echo htmlspecialchars($cl_auth_user, ENT_QUOTES, 'UTF-8'); ?></strong></td>
<td>
<span class="badge <?php echo $cl_auth_right === 'admin' ? 'badge-admin' : 'badge-member'; ?>">
<?php echo $cl_auth_right; ?>
</span>
</td>
<td>
<div class="row-actions" style="justify-content: flex-end;">
<a class="btn-modern btn-mini" href="admin.php?edit=<?php echo $cl_auth_id; ?>">Editer</a>
<form method="post" action="admin.php" onsubmit="return confirm('Terminer ce sujet ? Cette action est irréversible.');">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="admin_action" value="delete">
<input type="hidden" name="cl_auth_id" value="<?php echo $cl_auth_id; ?>">
<button class="btn-modern btn-mini danger" type="submit">X</button>
</form>
</div>
</td>
</tr>
<?php endforeach;
?><?php endif; ?>
</tbody>
</table>
</div>
</section>
</main>
</div>
</body>
</html>

View File

@ -1,3 +1,4 @@
[hidden] { display: none !important; }
@font-face { @font-face {
font-family: 'Electrolize'; font-family: 'Electrolize';
src: url('../fonts/Electrolize-Regular.ttf') format('truetype'); src: url('../fonts/Electrolize-Regular.ttf') format('truetype');
@ -309,4 +310,49 @@ a:hover {
.connexion-bouton:hover { .connexion-bouton:hover {
background-color: rgb(0 0 0 / 20%); background-color: rgb(0 0 0 / 20%);
cursor: pointer; cursor: pointer;
} }
/* Auth / admin helpers */
.connexion-div-menu {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.connexion-div-menu #accountLabel {
display: inline-block;
}
.connexion-div-menu.is-authenticated {
cursor: default;
}
.connexion-actions[hidden] { display: none !important; }
.connexion-actions {
display: inline-flex;
align-items: center;
gap: 10px;
}
.connexion-actions a {
color: #f4e3b2;
text-decoration: none;
border-bottom: 1px solid rgba(244, 227, 178, 0.35);
}
.connexion-actions a:hover {
color: #ffffff;
border-bottom-color: rgba(255, 255, 255, 0.8);
}
.login-status {
min-height: 18px;
}
.login-status.is-error {
color: #ff8080;
}
.login-status.is-success {
color: #9fe29f;
}

111
db/auth.php Normal file
View File

@ -0,0 +1,111 @@
<?php
require_once __DIR__ . '/config.php';
function auth_start_session(): void
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
}
function auth_bootstrap(): void
{
static $auth_bootstrap_done = false;
if ($auth_bootstrap_done) {
return;
}
$pdo = db();
$pdo->exec(
"CREATE TABLE IF NOT EXISTS tbl_auth (
cl_auth_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cl_auth_user VARCHAR(190) NOT NULL UNIQUE,
cl_auth_pass VARCHAR(255) NOT NULL,
cl_auth_right ENUM('admin', 'member') NOT NULL DEFAULT 'member'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$sql_count_admin = "SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_right = 'admin'";
$stmt_count_admin = $pdo->query($sql_count_admin);
$cl_auth_admin_total = (int) $stmt_count_admin->fetchColumn();
if ($cl_auth_admin_total === 0) {
[$cl_auth_user, $plain_default_password] = auth_default_admin_credentials();
$cl_auth_pass = password_hash($plain_default_password, PASSWORD_DEFAULT);
$cl_auth_right = 'admin';
$stmt_insert_admin = $pdo->prepare(
'INSERT INTO tbl_auth (cl_auth_user, cl_auth_pass, cl_auth_right) VALUES (:cl_auth_user, :cl_auth_pass, :cl_auth_right)'
);
$stmt_insert_admin->execute([
'cl_auth_user' => $cl_auth_user,
'cl_auth_pass' => $cl_auth_pass,
'cl_auth_right' => $cl_auth_right,
]);
}
$auth_bootstrap_done = true;
}
function auth_default_admin_credentials(): array
{
return ['admin', 'ReactAdmin!2026'];
}
function auth_csrf_token(): string
{
auth_start_session();
if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function auth_validate_csrf(?string $csrf_token): bool
{
auth_start_session();
if (!isset($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
return false;
}
if ($csrf_token === null) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $csrf_token);
}
function auth_is_admin(): bool
{
auth_start_session();
return isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}
function auth_flash_set(string $flash_type, string $flash_message): void
{
auth_start_session();
$_SESSION['flash'] = [
'type' => $flash_type,
'message' => $flash_message,
];
}
function auth_flash_get(): ?array
{
auth_start_session();
if (!isset($_SESSION['flash']) || !is_array($_SESSION['flash'])) {
return null;
}
$flash = $_SESSION['flash'];
unset($_SESSION['flash']);
return $flash;
}

17
db/config.php Normal file
View File

@ -0,0 +1,17 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_39514');
define('DB_USER', 'app_39514');
define('DB_PASS', 'ee6da88c-09af-4b48-b728-7a55edfb4e42');
function db() {
static $pdo;
if (!$pdo) {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
return $pdo;
}

View File

@ -1,172 +1,190 @@
<!DOCTYPE html> <?php
<html lang="fr"> require_once __DIR__ . '/db/auth.php';
<head>
<meta charset="UTF-8"> auth_start_session();
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> auth_bootstrap();
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion."> $session_cl_auth_user = isset($_SESSION['user']) ? (string) $_SESSION['user'] : '';
$session_cl_auth_right = isset($_SESSION['role']) ? (string) $_SESSION['role'] : '';
<title>Rapid Emergency & Action Combat Team / Star Citizen</title> $is_authenticated = $session_cl_auth_user !== '';
?>
<link rel="stylesheet" type="text/css" href="css/styles.css"> <!DOCTYPE html>
<link rel="stylesheet" type="text/css" href="css/default.css" /> <html lang="fr">
<link rel="stylesheet" type="text/css" href="css/component.css" /> <head>
<link rel="stylesheet" type="text/css" href="css/switch.css" /> <meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="css/styles-modal.css" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<script src="js/modernizr.custom.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head> <meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion.">
<body> <title>Rapid Emergency & Action Combat Team / Star Citizen</title>
<div class="connexion-div-menu md-trigger" data-modal="modal-Login"><a href="#">Connexion</a></div>
<link rel="stylesheet" type="text/css" href="css/styles.css">
<div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN"> <link rel="stylesheet" type="text/css" href="css/default.css" />
<a class="se-switch__btn" href="index.php" data-site="se1">🇫🇷</a> <link rel="stylesheet" type="text/css" href="css/component.css" />
<a class="se-switch__btn is-active" href="index-en.php" data-site="se2" aria-current="page">🇬🇧</a> <link rel="stylesheet" type="text/css" href="css/switch.css" />
</div> <link rel="stylesheet" type="text/css" href="css/styles-modal.css" />
<script src="js/modernizr.custom.js"></script>
<div class="md-modal md-effect-1" id="modal-Login"> </head>
<div class="md-content">
<h3> <body>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" /> <div class="connexion-div-menu <?php echo $is_authenticated ? 'is-authenticated' : 'md-trigger'; ?>" data-login-label="Login" <?php echo $is_authenticated ? '' : 'data-modal="modal-Login"'; ?> id="accountPanel">
LOGIN INTERFACE <span id="accountLabel"><?php echo htmlspecialchars($is_authenticated ? $session_cl_auth_user : 'Login', ENT_QUOTES, 'UTF-8'); ?></span>
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a> <span class="connexion-actions" id="accountActions" <?php echo $is_authenticated ? '' : 'hidden'; ?>>
</h3> <a id="adminLink" href="admin.php" <?php echo $session_cl_auth_right === 'admin' ? '' : 'hidden'; ?>>Admin</a>
<div> <a id="logoutLink" href="logout.php">Déconnexion</a>
<p class="txt-center">Enter your credentials to access the secure area.*</p> </span>
<form> </div>
<div>
<p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="username" placeholder="RID" /></p> <div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN">
<p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="password" placeholder="PASSWORD" /></p> <a class="se-switch__btn" href="index.php" data-site="se1">🇫🇷</a>
</div> <a class="se-switch__btn is-active" href="index-en.php" data-site="se2" aria-current="page">🇬🇧</a>
<div> </div>
<p class="txt-center"><input class="connexion-bouton" type="submit" value="Authenticate" /></p>
</div> <div class="md-modal md-effect-1" id="modal-Login">
</form> <div class="md-content">
<p class="txt-center txt-s10">* Access under high surveillance. By confirming your credentials, you agree to the R.E.A.C.T. Initiative security protocols. Any breach of protocol is subject to sanctions.</p> <h3>
</div> <img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
</div> LOGIN INTERFACE
</div> <a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div class="md-modal md-effect-1" id="modal-About"> <div>
<div class="md-content"> <p class="txt-center">Enter your credentials to access the secure area.*</p>
<h3> <form class="js-login-form" method="post" action="login.php">
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" /> <div>
MORE INFORMATIONS <p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="cl_auth_user" placeholder="RID" required /></p>
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a> <p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="cl_auth_pass" placeholder="PASSWORD" required /></p>
</h3> </div>
<div> <div>
<img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" /> <p class="txt-center"><input class="connexion-bouton" type="submit" value="Authenticate" /></p>
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p> <p class="txt-center login-status" id="loginStatus" aria-live="polite"></p>
<p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p> </div>
<p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p> </form>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p> <p class="txt-center txt-s10">* Access under high surveillance. By confirming your credentials, you agree to the R.E.A.C.T. Initiative security protocols. Any breach of protocol is subject to sanctions.</p>
<p class="ul-style"> </div>
Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br> </div>
Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br> </div>
Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br> <div class="md-modal md-effect-1" id="modal-About">
Protection des civils en détresse et aide aux infrastructures endommagées.</br> <div class="md-content">
Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles. <h3>
</p> <img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
</div> MORE INFORMATIONS
</div> <a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</div> </h3>
<div>
<div class="center-page-bops"></div> <img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" />
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p>
<div class="center-div-menu"> <p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p>
<div class="padding50"> <p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p>
<p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p> <p class="ul-style">
<!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> --> Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br>
<p class="txt-justify padding5">Founded to combat extreme criminality across the Verse. R.E.A.C.T. is an independent operational label, uniting dedicated pilots from all horizons to neutralize threats where the law is failing.</p> Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br>
<p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— R.E.A.C.T. is not an organization, but a tactical standard ——————</p> Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
<p class="txt-justify padding5">It is a cross-org initiative designed for players who share the same vision: protecting citizens and NPCs from hostile entities. Wearing the R.E.A.C.T. badge doesnt mean leaving your organization; it means joining a rapid response network.</p> Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br>
</div> Protection des civils en détresse et aide aux infrastructures endommagées.</br>
<div class="center-div-menu-flex txt-bold"> Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles.
<div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> MORE INFORMATIONS <</a></div> </p>
</div> </div>
</div> </div>
</div>
<div class="black-filter"></div>
<div class="center-page-bops"></div>
<!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true"> <div class="center-div-menu">
<iframe <div class="padding50">
id="react-video"
src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1" <p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p>
title="Présentation R.E.A.C.T — YouTube" <!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> -->
loading="lazy" <p class="txt-justify padding5">Founded to combat extreme criminality across the Verse. R.E.A.C.T. is an independent operational label, uniting dedicated pilots from all horizons to neutralize threats where the law is failing.</p>
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" <p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— R.E.A.C.T. is not an organization, but a tactical standard ——————</p>
allowfullscreen <p class="txt-justify padding5">It is a cross-org initiative designed for players who share the same vision: protecting citizens and NPCs from hostile entities. Wearing the R.E.A.C.T. badge doesnt mean leaving your organization; it means joining a rapid response network.</p>
referrerpolicy="strict-origin-when-cross-origin"> </div>
</iframe> <div class="center-div-menu-flex txt-bold">
</div> <div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> MORE INFORMATIONS <</a></div>
</div>
<div class="md-overlay"></div><!-- the overlay element --> </div>
<!-- classie.js by @desandro: https://github.com/desandro/classie --> <div class="black-filter"></div>
<script src="js/classie.js"></script>
<script src="js/modalEffects.js"></script> <!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true">
<!-- for the blur effect --> <iframe
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill --> id="react-video"
<script> src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1"
// this is important for IEs title="Présentation R.E.A.C.T — YouTube"
var polyfilter_scriptpath = '/js/'; loading="lazy"
</script> allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
<script src="js/cssParser.js"></script> allowfullscreen
<script src="js/css-filters-polyfill.js"></script> referrerpolicy="strict-origin-when-cross-origin">
</iframe>
<script> </div>
// On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
(function attachOrigin(){ <div class="md-overlay"></div><!-- the overlay element -->
const iframe = document.getElementById('react-video');
const url = new URL(iframe.src); <!-- classie.js by @desandro: https://github.com/desandro/classie -->
if (!url.searchParams.has('origin')) { <script src="js/classie.js"></script>
url.searchParams.set('origin', window.location.origin); <script src="js/modalEffects.js"></script>
iframe.src = url.toString(); <script src="js/auth.js"></script>
}
})(); <!-- for the blur effect -->
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill -->
// Utilitaires pour parler au lecteur via postMessage (API YouTube) <script>
function ytCommand(func, args = []) { // this is important for IEs
const iframe = document.getElementById('react-video'); var polyfilter_scriptpath = '/js/';
if (!iframe || !iframe.contentWindow) return; </script>
iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*'); <script src="js/cssParser.js"></script>
} <script src="js/css-filters-polyfill.js"></script>
// Au premier geste utilisateur : on remet le son et on met le volume à 50 % <script>
function enableSoundOnce() { // On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
ytCommand('unMute'); (function attachOrigin(){
ytCommand('setVolume', [50]); const iframe = document.getElementById('react-video');
const url = new URL(iframe.src);
// Masquer le bouton if (!url.searchParams.has('origin')) {
const btn = document.getElementById('unmute-btn'); url.searchParams.set('origin', window.location.origin);
if (btn) btn.style.display = 'none'; iframe.src = url.toString();
}
// Retirer les écouteurs pour ne pas répéter laction })();
window.removeEventListener('pointerdown', enableSoundOnce);
window.removeEventListener('keydown', enableSoundOnce); // Utilitaires pour parler au lecteur via postMessage (API YouTube)
window.removeEventListener('touchstart', enableSoundOnce, { passive: true }); function ytCommand(func, args = []) {
} const iframe = document.getElementById('react-video');
if (!iframe || !iframe.contentWindow) return;
// Bouton dédié iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*');
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce); }
// …ou nimporte quel premier geste sur la page // Au premier geste utilisateur : on remet le son et on met le volume à 50 %
//window.addEventListener('pointerdown', enableSoundOnce, { once: true }); function enableSoundOnce() {
//window.addEventListener('keydown', enableSoundOnce, { once: true }); ytCommand('unMute');
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true }); ytCommand('setVolume', [50]);
</script>
<div class="footer txt-s12 txt-center"> // Masquer le bouton
<p> const btn = document.getElementById('unmute-btn');
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a> if (btn) btn.style.display = 'none';
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a> // Retirer les écouteurs pour ne pas répéter laction
</p> window.removeEventListener('pointerdown', enableSoundOnce);
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | All Rights Reserved.</p> window.removeEventListener('keydown', enableSoundOnce);
</div> window.removeEventListener('touchstart', enableSoundOnce, { passive: true });
</body> }
</html> // Bouton dédié
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce);
// …ou nimporte quel premier geste sur la page
//window.addEventListener('pointerdown', enableSoundOnce, { once: true });
//window.addEventListener('keydown', enableSoundOnce, { once: true });
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true });
</script>
<div class="footer txt-s12 txt-center">
<p>
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a>
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a>
</p>
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | All Rights Reserved.</p>
</div>
</body>
</html>

370
index.php
View File

@ -1,176 +1,194 @@
<!DOCTYPE html> <?php
<html lang="fr"> require_once __DIR__ . '/db/auth.php';
<head>
<meta charset="UTF-8"> auth_start_session();
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> auth_bootstrap();
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion."> $session_cl_auth_user = isset($_SESSION['user']) ? (string) $_SESSION['user'] : '';
$session_cl_auth_right = isset($_SESSION['role']) ? (string) $_SESSION['role'] : '';
<title>Rapid Emergency & Action Combat Team / Star Citizen</title> $is_authenticated = $session_cl_auth_user !== '';
?>
<link rel="stylesheet" type="text/css" href="css/styles.css"> <!DOCTYPE html>
<link rel="stylesheet" type="text/css" href="css/default.css" /> <html lang="fr">
<link rel="stylesheet" type="text/css" href="css/component.css" /> <head>
<link rel="stylesheet" type="text/css" href="css/switch.css" /> <meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="css/styles-modal.css" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<script src="js/modernizr.custom.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head> <meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion.">
<body> <title>Rapid Emergency & Action Combat Team / Star Citizen</title>
<div class="assets-div-menu">
<a href="#">Contenu à venir</a> <link rel="stylesheet" type="text/css" href="css/styles.css">
</div> <link rel="stylesheet" type="text/css" href="css/default.css" />
<link rel="stylesheet" type="text/css" href="css/component.css" />
<div class="connexion-div-menu md-trigger" data-modal="modal-Login"><a href="#">Connexion</a></div> <link rel="stylesheet" type="text/css" href="css/switch.css" />
<link rel="stylesheet" type="text/css" href="css/styles-modal.css" />
<div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN"> <script src="js/modernizr.custom.js"></script>
<a class="se-switch__btn is-active" href="index.php" data-site="se1" aria-current="page">🇫🇷</a> </head>
<a class="se-switch__btn" href="index-en.php" data-site="se2">🇬🇧</a>
</div> <body>
<div class="assets-div-menu">
<div class="md-modal md-effect-1" id="modal-Login"> <a href="#">Contenu à venir</a>
<div class="md-content"> </div>
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" /> <div class="connexion-div-menu <?php echo $is_authenticated ? 'is-authenticated' : 'md-trigger'; ?>" data-login-label="Connexion" <?php echo $is_authenticated ? '' : 'data-modal="modal-Login"'; ?> id="accountPanel">
INTERFACE DE CONNEXION <span id="accountLabel"><?php echo htmlspecialchars($is_authenticated ? $session_cl_auth_user : 'Connexion', ENT_QUOTES, 'UTF-8'); ?></span>
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a> <span class="connexion-actions" id="accountActions" <?php echo $is_authenticated ? '' : 'hidden'; ?>>
</h3> <a id="adminLink" href="admin.php" <?php echo $session_cl_auth_right === 'admin' ? '' : 'hidden'; ?>>Admin</a>
<div> <a id="logoutLink" href="logout.php">Déconnexion</a>
<p class="txt-center">Entrer vos identifiants pour acceder à l'espace sécurisé.*</p> </span>
<form> </div>
<div>
<p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="username" placeholder="RID" /></p> <div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN">
<p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="password" placeholder="MOT DE PASSE" /></p> <a class="se-switch__btn is-active" href="index.php" data-site="se1" aria-current="page">🇫🇷</a>
</div> <a class="se-switch__btn" href="index-en.php" data-site="se2">🇬🇧</a>
<div> </div>
<p class="txt-center"><input class="connexion-bouton" type="submit" value="S'authentifier" /></p>
</div> <div class="md-modal md-effect-1" id="modal-Login">
</form> <div class="md-content">
<p class="txt-center txt-s10">* Accès sous haute surveillance. En confirmant vos identifiants, vous acceptez les protocoles de sécurité de l'initiative R.E.A.C.T. Toute infraction aux protocoles est passible de sanctions.</p> <h3>
</div> <img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
</div> INTERFACE DE CONNEXION
</div> <a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div class="md-modal md-effect-1" id="modal-About"> <div>
<div class="md-content"> <p class="txt-center">Entrer vos identifiants pour acceder à l'espace sécurisé.*</p>
<h3> <form class="js-login-form" method="post" action="login.php">
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" /> <div>
EN SAVOIR PLUS <p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="cl_auth_user" placeholder="RID" required /></p>
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a> <p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="cl_auth_pass" placeholder="MOT DE PASSE" required /></p>
</h3> </div>
<div> <div>
<img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" /> <p class="txt-center"><input class="connexion-bouton" type="submit" value="S'authentifier" /></p>
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p> <p class="txt-center login-status" id="loginStatus" aria-live="polite"></p>
<p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p> </div>
<p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p> </form>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p> <p class="txt-center txt-s10">* Accès sous haute surveillance. En confirmant vos identifiants, vous acceptez les protocoles de sécurité de l'initiative R.E.A.C.T. Toute infraction aux protocoles est passible de sanctions.</p>
<p class="ul-style"> </div>
Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br> </div>
Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br> </div>
Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br> <div class="md-modal md-effect-1" id="modal-About">
Protection des civils en détresse et aide aux infrastructures endommagées.</br> <div class="md-content">
Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles. <h3>
</p> <img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
</div> EN SAVOIR PLUS
</div> <a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</div> </h3>
<div>
<div class="center-page-bops"></div> <img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" />
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p>
<div class="center-div-menu"> <p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p>
<div class="padding50"> <p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p>
<p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p> <p class="ul-style">
<!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> --> Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br>
<p class="txt-justify padding5">Fondée pour combattre la criminalité extrême à travers lUnivers, la R.E.A.C.T. est un label opérationnel indépendant. Nous unissons des pilotes dévoués de tous horizons pour neutraliser les menaces la loi fait défaut.</p> Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br>
<p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— La R.E.A.C.T. nest pas une organisation, mais un standard tactique ——————</p> Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
<p class="txt-justify padding5">Cest une initiative inter-organisations conçue pour les joueurs qui partagent la même vision : protéger les citoyens et les PNJs des entités hostiles. Porter linsigne R.E.A.C.T. ne signifie pas quitter votre organisation ; cela signifie rejoindre un réseau d'intervention rapide.</p> Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br>
</div> Protection des civils en détresse et aide aux infrastructures endommagées.</br>
<div class="center-div-menu-flex txt-bold"> Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles.
<div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> EN SAVOIR PLUS <</a></div> </p>
</div> </div>
</div> </div>
</div>
<div class="black-filter"></div>
<div class="center-page-bops"></div>
<!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true"> <div class="center-div-menu">
<iframe <div class="padding50">
id="react-video"
src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1" <p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p>
title="Présentation R.E.A.C.T — YouTube" <!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> -->
loading="lazy" <p class="txt-justify padding5">Fondée pour combattre la criminalité extrême à travers lUnivers, la R.E.A.C.T. est un label opérationnel indépendant. Nous unissons des pilotes dévoués de tous horizons pour neutraliser les menaces la loi fait défaut.</p>
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen" <p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— La R.E.A.C.T. nest pas une organisation, mais un standard tactique ——————</p>
allowfullscreen <p class="txt-justify padding5">Cest une initiative inter-organisations conçue pour les joueurs qui partagent la même vision : protéger les citoyens et les PNJs des entités hostiles. Porter linsigne R.E.A.C.T. ne signifie pas quitter votre organisation ; cela signifie rejoindre un réseau d'intervention rapide.</p>
referrerpolicy="strict-origin-when-cross-origin"> </div>
</iframe> <div class="center-div-menu-flex txt-bold">
</div> <div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> EN SAVOIR PLUS <</a></div>
</div>
<div class="md-overlay"></div><!-- the overlay element --> </div>
<!-- classie.js by @desandro: https://github.com/desandro/classie --> <div class="black-filter"></div>
<script src="js/classie.js"></script>
<script src="js/modalEffects.js"></script> <!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true">
<!-- for the blur effect --> <iframe
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill --> id="react-video"
<script> src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1"
// this is important for IEs title="Présentation R.E.A.C.T — YouTube"
var polyfilter_scriptpath = '/js/'; loading="lazy"
</script> allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
<script src="js/cssParser.js"></script> allowfullscreen
<script src="js/css-filters-polyfill.js"></script> referrerpolicy="strict-origin-when-cross-origin">
</iframe>
<script> </div>
// On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
(function attachOrigin(){ <div class="md-overlay"></div><!-- the overlay element -->
const iframe = document.getElementById('react-video');
const url = new URL(iframe.src); <!-- classie.js by @desandro: https://github.com/desandro/classie -->
if (!url.searchParams.has('origin')) { <script src="js/classie.js"></script>
url.searchParams.set('origin', window.location.origin); <script src="js/modalEffects.js"></script>
iframe.src = url.toString(); <script src="js/auth.js"></script>
}
})(); <!-- for the blur effect -->
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill -->
// Utilitaires pour parler au lecteur via postMessage (API YouTube) <script>
function ytCommand(func, args = []) { // this is important for IEs
const iframe = document.getElementById('react-video'); var polyfilter_scriptpath = '/js/';
if (!iframe || !iframe.contentWindow) return; </script>
iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*'); <script src="js/cssParser.js"></script>
} <script src="js/css-filters-polyfill.js"></script>
// Au premier geste utilisateur : on remet le son et on met le volume à 50 % <script>
function enableSoundOnce() { // On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
ytCommand('unMute'); (function attachOrigin(){
ytCommand('setVolume', [50]); const iframe = document.getElementById('react-video');
const url = new URL(iframe.src);
// Masquer le bouton if (!url.searchParams.has('origin')) {
const btn = document.getElementById('unmute-btn'); url.searchParams.set('origin', window.location.origin);
if (btn) btn.style.display = 'none'; iframe.src = url.toString();
}
// Retirer les écouteurs pour ne pas répéter laction })();
window.removeEventListener('pointerdown', enableSoundOnce);
window.removeEventListener('keydown', enableSoundOnce); // Utilitaires pour parler au lecteur via postMessage (API YouTube)
window.removeEventListener('touchstart', enableSoundOnce, { passive: true }); function ytCommand(func, args = []) {
} const iframe = document.getElementById('react-video');
if (!iframe || !iframe.contentWindow) return;
// Bouton dédié iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*');
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce); }
// …ou nimporte quel premier geste sur la page // Au premier geste utilisateur : on remet le son et on met le volume à 50 %
//window.addEventListener('pointerdown', enableSoundOnce, { once: true }); function enableSoundOnce() {
//window.addEventListener('keydown', enableSoundOnce, { once: true }); ytCommand('unMute');
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true }); ytCommand('setVolume', [50]);
</script>
<div class="footer txt-s12 txt-center"> // Masquer le bouton
<p> const btn = document.getElementById('unmute-btn');
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a> if (btn) btn.style.display = 'none';
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a> // Retirer les écouteurs pour ne pas répéter laction
</p> window.removeEventListener('pointerdown', enableSoundOnce);
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | Tous droits réservés.</p> window.removeEventListener('keydown', enableSoundOnce);
</div> window.removeEventListener('touchstart', enableSoundOnce, { passive: true });
</body> }
</html> // Bouton dédié
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce);
// …ou nimporte quel premier geste sur la page
//window.addEventListener('pointerdown', enableSoundOnce, { once: true });
//window.addEventListener('keydown', enableSoundOnce, { once: true });
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true });
</script>
<div class="footer txt-s12 txt-center">
<p>
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a>
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a>
</p>
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | Tous droits réservés.</p>
</div>
</body>
</html>

122
js/auth.js Normal file
View File

@ -0,0 +1,122 @@
(function () {
var loginForm = document.querySelector('.js-login-form');
var accountPanel = document.getElementById('accountPanel');
var accountLabel = document.getElementById('accountLabel');
var accountActions = document.getElementById('accountActions');
var adminLink = document.getElementById('adminLink');
var logoutLink = document.getElementById('logoutLink');
var loginStatus = document.getElementById('loginStatus');
var loginModal = document.getElementById('modal-Login');
var overlay = document.querySelector('.md-overlay');
if (!loginForm || !accountPanel || !accountLabel || !accountActions || !logoutLink) {
return;
}
function setStatus(message, isError) {
if (!loginStatus) {
return;
}
loginStatus.textContent = message || '';
loginStatus.classList.toggle('is-error', !!isError);
loginStatus.classList.toggle('is-success', !isError && !!message);
}
function closeModal() {
if (loginModal) {
loginModal.classList.remove('md-show');
}
if (document.documentElement) {
document.documentElement.classList.remove('md-perspective');
}
if (overlay) {
overlay.removeEventListener('click', closeModal);
}
}
function renderAuthenticated(user, role, adminUrl, logoutUrl) {
accountPanel.classList.add('is-authenticated');
accountPanel.classList.remove('md-trigger');
accountPanel.removeAttribute('data-modal');
accountLabel.textContent = user;
accountActions.hidden = false;
if (role === 'admin' && adminUrl) {
adminLink.href = adminUrl;
adminLink.hidden = false;
} else {
adminLink.hidden = true;
}
if (logoutUrl) {
logoutLink.href = logoutUrl;
}
}
function renderLoggedOut(defaultLabel) {
accountPanel.classList.remove('is-authenticated');
accountPanel.classList.add('md-trigger');
accountPanel.setAttribute('data-modal', 'modal-Login');
accountLabel.textContent = defaultLabel;
accountActions.hidden = true;
adminLink.hidden = true;
}
loginForm.addEventListener('submit', function (event) {
event.preventDefault();
setStatus('', false);
var formData = new FormData(loginForm);
fetch('login.php', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(function (response) {
return response.json().then(function (payload) {
return {
ok: response.ok,
payload: payload
};
});
})
.then(function (result) {
if (!result.ok || !result.payload.success) {
throw new Error(result.payload.message || 'Connexion impossible.');
}
renderAuthenticated(result.payload.user, result.payload.role, result.payload.adminUrl, result.payload.logoutUrl);
setStatus(result.payload.message || 'Connexion réussie.', false);
loginForm.reset();
window.setTimeout(closeModal, 250);
})
.catch(function (error) {
setStatus(error.message || 'Connexion impossible.', true);
});
});
logoutLink.addEventListener('click', function (event) {
event.preventDefault();
fetch(logoutLink.href, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(function (response) {
return response.json();
})
.then(function () {
renderLoggedOut(accountPanel.dataset.loginLabel || 'Connexion');
setStatus('', false);
})
.catch(function () {
window.location.href = logoutLink.href;
});
});
})();

89
login.php Normal file
View File

@ -0,0 +1,89 @@
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
auth_bootstrap();
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode([
'success' => false,
'message' => 'Méthode non autorisée.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$submitted_cl_auth_user = trim((string) ($_POST['cl_auth_user'] ?? ''));
$submitted_cl_auth_pass = (string) ($_POST['cl_auth_pass'] ?? '');
if ($submitted_cl_auth_user === '' || $submitted_cl_auth_pass === '') {
http_response_code(422);
echo json_encode([
'success' => false,
'message' => 'Identifiants incomplets.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$stmt_tbl_auth = db()->prepare(
'SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right
FROM tbl_auth
WHERE cl_auth_user = :cl_auth_user
LIMIT 1'
);
$stmt_tbl_auth->execute([
'cl_auth_user' => $submitted_cl_auth_user,
]);
$tbl_auth = $stmt_tbl_auth->fetch();
if (!$tbl_auth) {
http_response_code(401);
echo json_encode([
'success' => false,
'message' => 'Identifiants invalides.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($cl_auth_id);
if (!password_verify($submitted_cl_auth_pass, $cl_auth_pass)) {
http_response_code(401);
echo json_encode([
'success' => false,
'message' => 'Identifiants invalides.',
], JSON_UNESCAPED_UNICODE);
exit;
}
if (password_needs_rehash($cl_auth_pass, PASSWORD_DEFAULT)) {
$rehash_cl_auth_pass = password_hash($submitted_cl_auth_pass, PASSWORD_DEFAULT);
$stmt_update_password = db()->prepare(
'UPDATE tbl_auth SET cl_auth_pass = :cl_auth_pass WHERE cl_auth_user = :cl_auth_user'
);
$stmt_update_password->execute([
'cl_auth_pass' => $rehash_cl_auth_pass,
'cl_auth_user' => $cl_auth_user,
]);
}
session_regenerate_id(true);
$_SESSION['user'] = $cl_auth_user;
$_SESSION['role'] = $cl_auth_right;
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
echo json_encode([
'success' => true,
'message' => 'Connexion réussie.',
'user' => $cl_auth_user,
'role' => $cl_auth_right,
'adminUrl' => 'admin.php',
'logoutUrl' => 'logout.php',
], JSON_UNESCAPED_UNICODE);

36
logout.php Normal file
View File

@ -0,0 +1,36 @@
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$session_cookie_params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$session_cookie_params['path'],
$session_cookie_params['domain'],
$session_cookie_params['secure'],
$session_cookie_params['httponly']
);
}
session_destroy();
$is_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
if ($is_ajax) {
header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
'success' => true,
'message' => 'Déconnexion effectuée.',
], JSON_UNESCAPED_UNICODE);
exit;
}
header('Location: index.php');
exit;