39514-vm/scwebhook.php
Flatlogic Bot 37e7f940e8 V1.4.4
2026-04-29 07:11:28 +00:00

642 lines
24 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once __DIR__ . '/db/auth.php';
require_once __DIR__ . '/db/scdiscord.php';
auth_start_session();
auth_bootstrap();
auth_handle_page_access_post('scwebhook.php', 'WEBHOOK');
auth_require_page_access('scwebhook.php', 'WEBHOOK');
scdiscord_bootstrap();
$db = db();
$csrf_token = auth_csrf_token();
$flash = auth_flash_get();
$flash_type = $flash['type'] ?? '';
$flash_message = $flash['message'] ?? '';
$current_session_user = $_SESSION['user'] ?? '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$submitted_csrf = (string) ($_POST['csrf_token'] ?? '');
if (!auth_validate_csrf($submitted_csrf)) {
auth_flash_set('error', 'Jeton CSRF invalide.');
header('Location: scwebhook.php');
exit;
}
$action = (string) ($_POST['action'] ?? '');
if ($action === 'add_webhook' || $action === 'update_webhook') {
$webhook_id = (int) ($_POST['webhook_id'] ?? 0);
$cl_scwebhook_name = trim((string) ($_POST['cl_scwebhook_name'] ?? ''));
$cl_scwebhook_url = trim((string) ($_POST['cl_scwebhook_url'] ?? ''));
$cl_scwebhook_image_url = trim((string) ($_POST['cl_scwebhook_image_url'] ?? ''));
$cl_scwebhook_border_color = scdiscord_normalize_hex_color((string) ($_POST['cl_scwebhook_border_color'] ?? ''));
$cl_scwebhook_is_forum = 0;
if ($cl_scwebhook_name === '' || $cl_scwebhook_url === '' || $cl_scwebhook_image_url === '') {
auth_flash_set('error', 'Le nom, lURL du webhook et limage sont obligatoires.');
header('Location: scwebhook.php');
exit;
}
if (!filter_var($cl_scwebhook_url, FILTER_VALIDATE_URL)) {
auth_flash_set('error', 'LURL du webhook Discord est invalide.');
header('Location: scwebhook.php');
exit;
}
if (!filter_var($cl_scwebhook_image_url, FILTER_VALIDATE_URL)) {
auth_flash_set('error', 'LURL de limage Discord est invalide.');
header('Location: scwebhook.php');
exit;
}
try {
if ($action === 'add_webhook') {
$stmt = $db->prepare('INSERT INTO tbl_scwebhooks (cl_scwebhook_name, cl_scwebhook_url, cl_scwebhook_image_url, cl_scwebhook_border_color, cl_scwebhook_is_forum) VALUES (:name, :url, :image_url, :border_color, :is_forum)');
$stmt->execute([
'name' => $cl_scwebhook_name,
'url' => $cl_scwebhook_url,
'image_url' => $cl_scwebhook_image_url,
'border_color' => $cl_scwebhook_border_color,
'is_forum' => $cl_scwebhook_is_forum,
]);
auth_flash_set('success', 'Canal Discord ajouté avec succès.');
} else {
if ($webhook_id <= 0) {
throw new RuntimeException('ID de webhook invalide.');
}
$stmt = $db->prepare('UPDATE tbl_scwebhooks SET cl_scwebhook_name = :name, cl_scwebhook_url = :url, cl_scwebhook_image_url = :image_url, cl_scwebhook_border_color = :border_color, cl_scwebhook_is_forum = :is_forum WHERE cl_scwebhook_id = :id');
$stmt->execute([
'name' => $cl_scwebhook_name,
'url' => $cl_scwebhook_url,
'image_url' => $cl_scwebhook_image_url,
'border_color' => $cl_scwebhook_border_color,
'is_forum' => $cl_scwebhook_is_forum,
'id' => $webhook_id,
]);
auth_flash_set('success', 'Canal Discord mis à jour.');
}
} catch (Throwable $e) {
auth_flash_set('error', 'Erreur webhook : ' . $e->getMessage());
}
header('Location: scwebhook.php');
exit;
}
if ($action === 'delete_webhook') {
$webhook_id = (int) ($_POST['webhook_id'] ?? 0);
if ($webhook_id > 0) {
try {
$stmt_usage = $db->prepare('SELECT COUNT(*) FROM tbl_scnotifications WHERE cl_scnotification_webhook_id = :id');
$stmt_usage->execute(['id' => $webhook_id]);
$usage_total = (int) $stmt_usage->fetchColumn();
if ($usage_total > 0) {
auth_flash_set('error', 'Suppression refusée : ce webhook est déjà relié à lhistorique des notifications.');
} else {
$stmt = $db->prepare('DELETE FROM tbl_scwebhooks WHERE cl_scwebhook_id = :id');
$stmt->execute(['id' => $webhook_id]);
auth_flash_set('success', 'Canal Discord supprimé.');
}
} catch (Throwable $e) {
auth_flash_set('error', 'Erreur suppression webhook : ' . $e->getMessage());
}
}
header('Location: scwebhook.php');
exit;
}
}
$stmt_webhooks = $db->query('SELECT * FROM tbl_scwebhooks ORDER BY cl_scwebhook_name ASC');
$webhooks = $stmt_webhooks->fetchAll();
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SC Discord | R.E.A.C.T. Admin</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);
--card-bg-secondary: rgba(255, 255, 255, 0.03);
--border-glow: rgba(162, 155, 120, 0.25);
--danger: #ff4d4d;
--success: #00ff88;
--muted: #aaaaaa;
}
@font-face {
font-family: 'Electrolize';
src: url('fonts/Electrolize-Regular.ttf') format('truetype');
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: radial-gradient(circle at top right, #1a1f2e, var(--bg-dark));
background-attachment: fixed;
color: #e0e0e0;
font-family: 'Electrolize', sans-serif;
overflow-x: hidden;
}
.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,
.glass-card {
background: var(--card-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--border-glow);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.admin-topbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 1.5rem 2rem;
margin-bottom: 2rem;
}
.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;
flex-wrap: wrap;
align-items: center;
}
.session-user {
opacity: 0.85;
}
.btn-modern {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
padding: 0.6rem 1.2rem;
border-radius: 4px;
border: 1px solid var(--primary);
background: transparent;
color: #fff;
text-decoration: none;
text-transform: uppercase;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-family: 'Electrolize', sans-serif;
}
.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);
}
.btn-modern.secondary {
border-color: rgba(255, 255, 255, 0.2);
color: #d6d6d6;
}
.btn-modern.secondary:hover {
background: rgba(255, 255, 255, 0.12);
color: #fff;
box-shadow: none;
}
.btn-mini {
padding: 0.45rem 0.8rem;
font-size: 0.72rem;
}
.nav-tabs {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-glow);
}
.nav-tabs a {
text-decoration: none;
color: #888;
text-transform: uppercase;
font-size: 0.9rem;
transition: color 0.3s;
}
.nav-tabs a:hover,
.nav-tabs a.active {
color: var(--primary);
}
.flash {
margin-bottom: 1.5rem;
padding: 1rem 1.2rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
}
.flash.success { border-color: rgba(0, 255, 136, 0.25); color: #9ff3c8; }
.flash.error { border-color: rgba(255, 77, 77, 0.25); color: #ff9d9d; }
.page-grid {
display: grid;
grid-template-columns: 420px 1fr;
gap: 2rem;
}
.stack {
display: grid;
gap: 1.5rem;
}
.glass-card {
padding: 1.5rem;
}
.glass-card h2 {
margin: 0 0 1.5rem;
padding-bottom: 0.75rem;
font-size: 1.25rem;
color: var(--primary);
border-bottom: 1px solid var(--border-glow);
}
.section-note {
margin: -0.5rem 0 1.2rem;
color: var(--muted);
font-size: 0.82rem;
line-height: 1.5;
}
.form-grid {
display: grid;
gap: 1rem;
}
.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, background 0.3s;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
background: rgba(0, 0, 0, 0.5);
}
.form-control[type="color"] {
min-height: 48px;
padding: 0.35rem;
}
.checkbox-row {
display: flex;
align-items: center;
gap: 0.7rem;
color: #e7e7e7;
font-size: 0.9rem;
}
.list-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
}
th {
text-align: left;
padding: 1rem 0.75rem;
font-size: 0.8rem;
text-transform: uppercase;
color: var(--primary);
opacity: 0.7;
}
td {
padding: 1rem 0.75rem;
background: var(--card-bg-secondary);
border-top: 1px solid rgba(255, 255, 255, 0.05);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
vertical-align: top;
text-align: left;
font-size: 0.88rem;
}
td:first-child {
border-left: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 8px 0 0 8px;
}
td:last-child {
border-right: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 0 8px 8px 0;
}
tr:hover td {
background: rgba(162, 155, 120, 0.08);
}
.muted {
color: var(--muted);
font-size: 0.8rem;
}
.pill {
display: inline-flex;
align-items: center;
padding: 0.2rem 0.55rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.07);
font-size: 0.72rem;
color: #fff;
}
.pill.forum {
background: rgba(162, 155, 120, 0.18);
color: #e5dcb7;
}
.banner-preview {
width: 120px;
max-height: 42px;
object-fit: cover;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.08);
display: block;
background: #17191e;
}
.color-chip {
display: inline-flex;
align-items: center;
gap: 0.45rem;
}
.color-chip::before {
content: '';
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--chip-color, var(--primary));
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.08);
}
.actions-inline {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
justify-content: flex-end;
}
.empty-state {
padding: 2rem;
border: 1px dashed rgba(255, 255, 255, 0.12);
border-radius: 10px;
color: #9a9a9a;
text-align: center;
}
@media (max-width: 1100px) {
.page-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<?php echo auth_render_page_access_widget('scwebhook.php', 'WEBHOOK'); ?>
<div class="admin-layout">
<header class="admin-topbar">
<div class="topbar-info">
<h1>R.E.A.C.T. SC Webhook</h1>
<p>Niveau d'accès : <strong>Administrateur</strong></p>
</div>
<div class="topbar-actions">
<span class="session-user">Connecté : <strong><?php echo htmlspecialchars($current_session_user, ENT_QUOTES, 'UTF-8'); ?></strong></span>
<a href="index.php" class="btn-modern">Retour au site</a>
<a href="logout.php" class="btn-modern danger">Déconnexion</a>
</div>
</header>
<?php echo auth_render_app_nav('scwebhook.php'); ?>
<?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; ?>
<div class="page-grid">
<aside class="stack">
<section class="glass-card">
<h2 id="webhookFormTitle">Nouveau canal Discord</h2>
<p class="section-note">Configure ici toute la logique centralisée du canal Discord : nomination, URL du webhook, image de prévisualisation et couleur de bordure.</p>
<form method="post" id="webhookForm" class="form-grid">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" id="webhookAction" value="add_webhook">
<input type="hidden" name="webhook_id" id="webhookId" value="">
<div class="form-group">
<label for="cl_scwebhook_name">Nomination du canal</label>
<input type="text" id="cl_scwebhook_name" name="cl_scwebhook_name" class="form-control" required placeholder="Ex : Code 4">
</div>
<div class="form-group">
<label for="cl_scwebhook_url">Lien du webhook</label>
<input type="url" id="cl_scwebhook_url" name="cl_scwebhook_url" class="form-control" required placeholder="https://discord.com/api/webhooks/...">
</div>
<div class="form-group">
<label for="cl_scwebhook_image_url">Lien image / prévisualisation</label>
<input type="url" id="cl_scwebhook_image_url" name="cl_scwebhook_image_url" class="form-control" required placeholder="https://.../banniere.png">
</div>
<div class="form-group">
<label for="cl_scwebhook_border_color">Couleur de bordure</label>
<input type="color" id="cl_scwebhook_border_color" name="cl_scwebhook_border_color" class="form-control" value="#ffae00">
</div>
<button type="submit" class="btn-modern" id="webhookSubmitBtn">Ajouter le canal</button>
<button type="button" class="btn-modern secondary" id="webhookCancelBtn" style="display:none;" onclick="resetWebhookForm()">Annuler lédition</button>
</form>
</section>
</aside>
<main class="list-grid">
<section class="glass-card">
<h2>Canaux Discord enregistrés</h2>
<?php if (empty($webhooks)): ?>
<div class="empty-state">Aucun canal Discord configuré pour le moment.</div>
<?php else: ?>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Nomination</th>
<th>Webhook</th>
<th>Prévisualisation</th>
<th>Couleur</th>
<th style="text-align:right;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($webhooks as $webhook): ?>
<tr>
<td>
<strong><?php echo htmlspecialchars($webhook['cl_scwebhook_name'], ENT_QUOTES, 'UTF-8'); ?></strong>
</td>
<td>
<span class="muted"><?php echo htmlspecialchars(scdiscord_mask_webhook_url((string) $webhook['cl_scwebhook_url']), ENT_QUOTES, 'UTF-8'); ?></span>
</td>
<td>
<img class="banner-preview" src="<?php echo htmlspecialchars((string) ($webhook['cl_scwebhook_image_url'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>" alt="Prévisualisation webhook">
</td>
<td>
<?php $webhookBorderColor = scdiscord_normalize_hex_color((string) ($webhook['cl_scwebhook_border_color'] ?? '#ffae00')); ?>
<span class="color-chip" style="--chip-color: <?php echo htmlspecialchars($webhookBorderColor, ENT_QUOTES, 'UTF-8'); ?>;">
<?php echo htmlspecialchars($webhookBorderColor, ENT_QUOTES, 'UTF-8'); ?>
</span>
</td>
<td>
<div class="actions-inline">
<button
type="button"
class="btn-modern btn-mini"
onclick='editWebhook(<?php echo json_encode([
'id' => (int) $webhook['cl_scwebhook_id'],
'name' => (string) $webhook['cl_scwebhook_name'],
'url' => (string) $webhook['cl_scwebhook_url'],
'image_url' => (string) ($webhook['cl_scwebhook_image_url'] ?? ''),
'border_color' => (string) ($webhook['cl_scwebhook_border_color'] ?? '#ffae00'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>)'
>Éditer</button>
<form method="post" onsubmit="return confirm('Supprimer ce canal Discord ?');">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" value="delete_webhook">
<input type="hidden" name="webhook_id" value="<?php echo (int) $webhook['cl_scwebhook_id']; ?>">
<button type="submit" class="btn-modern btn-mini danger">Supprimer</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
</main>
</div>
</div>
<script>
function editWebhook(webhook) {
document.getElementById('webhookFormTitle').textContent = 'Modifier le canal Discord';
document.getElementById('webhookAction').value = 'update_webhook';
document.getElementById('webhookId').value = webhook.id || '';
document.getElementById('cl_scwebhook_name').value = webhook.name || '';
document.getElementById('cl_scwebhook_url').value = webhook.url || '';
document.getElementById('cl_scwebhook_image_url').value = webhook.image_url || '';
document.getElementById('cl_scwebhook_border_color').value = webhook.border_color || '#ffae00';
document.getElementById('webhookSubmitBtn').textContent = 'Mettre à jour le canal';
document.getElementById('webhookCancelBtn').style.display = 'inline-flex';
}
function resetWebhookForm() {
document.getElementById('webhookFormTitle').textContent = 'Nouveau canal Discord';
document.getElementById('webhookAction').value = 'add_webhook';
document.getElementById('webhookId').value = '';
document.getElementById('cl_scwebhook_name').value = '';
document.getElementById('cl_scwebhook_url').value = '';
document.getElementById('cl_scwebhook_image_url').value = '';
document.getElementById('cl_scwebhook_border_color').value = '#ffae00';
document.getElementById('webhookSubmitBtn').textContent = 'Ajouter le canal';
document.getElementById('webhookCancelBtn').style.display = 'none';
}
</script>
</body>
</html>