| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/clients/client2/arni-solutions.de/web/slots/ |
Upload File : |
<?php
require_once __DIR__ . '/includes/bootstrap.php';
$admin = require_admin();
$config = app_config();
$games = all_games();
// ---------------- POST-Aktionen (CSRF) ----------------
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!csrf_verify($_POST['csrf'] ?? null)) {
$_SESSION['admin_flash'] = ['err', 'Sicherheitstoken ungültig.'];
header('Location: admin.php'); exit;
}
$action = (string)($_POST['action'] ?? '');
$redir = 'admin.php';
try {
if ($action === 'set_balance') {
$id = (int)$_POST['player_id'];
admin_set_balance($id, (float)str_replace(',', '.', (string)$_POST['balance']));
$_SESSION['admin_flash'] = ['ok', "Guthaben von Spieler #$id gesetzt."];
} elseif ($action === 'adjust_balance') {
$id = (int)$_POST['player_id'];
admin_adjust_balance($id, (float)str_replace(',', '.', (string)$_POST['delta']));
$_SESSION['admin_flash'] = ['ok', "Guthaben von Spieler #$id gebucht."];
} elseif ($action === 'set_free_spins') {
$id = (int)$_POST['player_id'];
admin_set_free_spins($id, (int)$_POST['free_spins']);
$_SESSION['admin_flash'] = ['ok', "Freispiele von Spieler #$id gesetzt."];
} elseif ($action === 'set_player_factor') {
$id = (int)$_POST['player_id'];
admin_set_player_factor($id, (float)str_replace(',', '.', (string)$_POST['factor']));
$_SESSION['admin_flash'] = ['ok', "Gewinnfaktor von Spieler #$id gesetzt."];
} elseif ($action === 'toggle_ban') {
$id = (int)$_POST['player_id'];
$ban = (int)$_POST['banned'] === 1;
if ($id === (int)$admin['id'] && $ban) {
$_SESSION['admin_flash'] = ['err', 'Du kannst dich nicht selbst sperren.'];
} else {
admin_set_banned($id, $ban);
$_SESSION['admin_flash'] = ['ok', ($ban ? 'Gesperrt' : 'Entsperrt') . ": Spieler #$id."];
}
} elseif ($action === 'save_rtp') {
$global = admin_clamp_factor((float)str_replace(',', '.', (string)($_POST['rtp_global'] ?? '1')));
admin_set_setting('rtp_factor:global', (string)$global);
$fsGlobal = admin_clamp_fs_factor((float)str_replace(',', '.', (string)($_POST['fs_global'] ?? '1')));
admin_set_setting('fs_factor:global', (string)$fsGlobal);
foreach ($games as $id => $g) {
$val = admin_clamp_factor((float)str_replace(',', '.', (string)($_POST['rtp_' . $id] ?? '1')));
admin_set_setting('rtp_factor:' . $id, (string)$val);
$fsv = admin_clamp_fs_factor((float)str_replace(',', '.', (string)($_POST['fs_' . $id] ?? '1')));
admin_set_setting('fs_factor:' . $id, (string)$fsv);
}
admin_set_setting('gamble_enabled', isset($_POST['gamble_enabled']) ? '1' : '0');
$_SESSION['admin_flash'] = ['ok', 'Spielwahrscheinlichkeiten gespeichert.'];
} elseif ($action === 'sym_icon') {
$g = preg_replace('/[^a-z_]/', '', (string)($_POST['game'] ?? ''));
$code = preg_replace('/[^A-Za-z0-9_]/', '', (string)($_POST['code'] ?? ''));
admin_set_symbol_override($g, $code, ['icon' => trim((string)($_POST['icon'] ?? '')), 'img' => '']);
$_SESSION['admin_flash'] = ['ok', "Symbol $code geändert."];
$redir = 'admin.php?symgame=' . $g;
} elseif ($action === 'sym_upload') {
$g = preg_replace('/[^a-z_]/', '', (string)($_POST['game'] ?? ''));
$code = preg_replace('/[^A-Za-z0-9_]/', '', (string)($_POST['code'] ?? ''));
$path = admin_save_symbol_image($g, $code, $_FILES['symfile'] ?? []);
admin_set_symbol_override($g, $code, ['img' => $path]);
$_SESSION['admin_flash'] = ['ok', "Bild für Symbol $code hochgeladen."];
$redir = 'admin.php?symgame=' . $g;
} elseif ($action === 'sym_reset') {
$g = preg_replace('/[^a-z_]/', '', (string)($_POST['game'] ?? ''));
$code = preg_replace('/[^A-Za-z0-9_]/', '', (string)($_POST['code'] ?? ''));
admin_reset_symbol_override($g, $code);
$_SESSION['admin_flash'] = ['ok', "Symbol $code auf Standard zurückgesetzt."];
$redir = 'admin.php?symgame=' . $g;
}
} catch (Throwable $e) {
$_SESSION['admin_flash'] = ['err', 'Fehler: ' . $e->getMessage()];
if (!empty($_POST['game'])) $redir = 'admin.php?symgame=' . preg_replace('/[^a-z_]/', '', (string)$_POST['game']);
}
header('Location: ' . $redir); exit;
}
$players = admin_all_players();
$settings = admin_settings();
$flash = $_SESSION['admin_flash'] ?? null;
unset($_SESSION['admin_flash']);
$csrf = csrf_token();
$gf = isset($settings['rtp_factor:global']) ? (float)$settings['rtp_factor:global'] : 1.0;
// Symbol-Editor: gewähltes Spiel
$symGameId = preg_replace('/[^a-z_]/', '', (string)($_GET['symgame'] ?? ''));
if (!isset($games[$symGameId])) { $symGameId = array_key_first($games); }
$symGame = game_client_config($games[$symGameId]);
$symOverrides = game_symbol_overrides($symGameId);
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title><?= e($config['app']['name']) ?> · Administration</title>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@600;800&family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<link href="css/core.css" rel="stylesheet">
<style>
.adm{max-width:1080px;margin:0 auto;padding:18px clamp(10px,3vw,24px) 60px}
.adm-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px;flex-wrap:wrap}
.adm-head h1{font-size:1.6rem;color:var(--rr-gold)}
.adm-card{background:var(--rr-panel);border:1px solid var(--rr-line);border-radius:18px;padding:18px;margin-bottom:18px;box-shadow:var(--rr-shadow)}
.adm-card h2{font-size:1.1rem;color:var(--rr-acc);margin:0 0 14px}
.adm-flash{padding:12px 16px;border-radius:12px;margin-bottom:16px;font-weight:600}
.adm-flash.ok{background:rgba(74,217,145,.14);border:1px solid rgba(74,217,145,.4);color:#bdf5d6}
.adm-flash.err{background:rgba(255,93,108,.14);border:1px solid rgba(255,93,108,.4);color:#ffc2c8}
.adm-table{width:100%;border-collapse:collapse;font-size:.86rem}
.adm-table th{text-align:left;font-size:.66rem;text-transform:uppercase;letter-spacing:.08em;color:var(--rr-soft);padding:8px;border-bottom:1px solid var(--rr-line)}
.adm-table td{padding:10px 8px;border-bottom:1px solid rgba(255,255,255,.05);vertical-align:top}
.adm-player{font-weight:700} .adm-player small{display:block;color:var(--rr-soft);font-weight:400;font-size:.72rem}
.adm-row-forms{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.adm-row-forms form{display:flex;gap:5px;align-items:center}
.adm input[type=number]{width:120px;padding:8px 10px;border-radius:9px;border:1px solid var(--rr-line-2);background:rgba(0,0,0,.35);color:#fff;font-size:.85rem}
.adm input.small{width:78px}
.adm button{padding:8px 12px;border-radius:9px;border:1px solid var(--rr-line-2);background:rgba(255,255,255,.07);color:#fff;font-weight:700;cursor:pointer;font-size:.8rem;transition:.14s}
.adm button:hover{background:rgba(255,255,255,.16)}
.adm .btn-gold{background:linear-gradient(180deg,var(--rr-gold),var(--rr-gold-2));color:#221504;border:0}
.adm .btn-ban{background:rgba(255,93,108,.18);border-color:rgba(255,93,108,.45);color:#ffc2c8}
.adm .btn-unban{background:rgba(74,217,145,.16);border-color:rgba(74,217,145,.4);color:#bdf5d6}
.adm-badge{display:inline-block;padding:2px 9px;border-radius:999px;font-size:.66rem;font-weight:800}
.adm-badge.active{background:rgba(74,217,145,.18);color:#bdf5d6} .adm-badge.banned{background:rgba(255,93,108,.2);color:#ffc2c8}
.adm-rtp-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-bottom:14px}
.adm-rtp-item{background:rgba(255,255,255,.04);border:1px solid var(--rr-line);border-radius:12px;padding:12px}
.adm-rtp-item label{display:block;font-size:.8rem;margin-bottom:6px}
.adm-rtp-item .eff{font-size:.72rem;color:var(--rr-soft);margin-top:6px}
.adm-range{width:100%}
.adm-note{font-size:.78rem;color:var(--rr-soft);margin-top:8px}
.adm-symselect{padding:10px 14px;border-radius:10px;border:1px solid var(--rr-line-2);background:rgba(0,0,0,.35);color:#fff;font-size:.95rem;min-width:220px}
.adm-sym-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px}
.adm-sym{background:rgba(255,255,255,.04);border:1px solid var(--rr-line);border-radius:12px;padding:12px;display:flex;flex-direction:column;gap:8px}
.adm-sym-prev{height:66px;display:grid;place-items:center;font-size:2.4rem;background:rgba(0,0,0,.28);border-radius:10px}
.adm-sym-prev img{width:58px;height:58px;object-fit:cover;border-radius:10px;border:1px solid var(--rr-line-2)}
.adm-sym-name{font-size:.84rem;font-weight:700}
.adm-sym-name small{display:block;color:var(--rr-soft);font-weight:400;font-size:.68rem}
.adm-ov{display:inline-block;margin-top:3px;font-size:.6rem;font-weight:800;padding:2px 7px;border-radius:999px;background:rgba(255,206,83,.2);color:var(--rr-acc)}
.adm-sym-form{display:flex;gap:5px;align-items:center}
.adm-sym-form input[type=text]{flex:1;min-width:0;padding:7px 9px;border-radius:8px;border:1px solid var(--rr-line-2);background:rgba(0,0,0,.35);color:#fff;font-size:.85rem}
.adm-sym-form input[type=file]{flex:1;min-width:0;font-size:.68rem;color:var(--rr-soft)}
@media (max-width:760px){ .adm-table thead{display:none} .adm-table td{display:block;border:0;padding:4px 8px}
.adm-table tr{display:block;border:1px solid var(--rr-line);border-radius:12px;padding:8px;margin-bottom:12px} }
</style>
</head>
<body class="rr-body">
<div class="adm">
<div class="adm-head">
<h1>⚙ Administration</h1>
<div style="display:flex;gap:10px">
<span class="adm-badge active">Admin: <?= e($admin['username']) ?></span>
<a class="rr-btn rr-btn-ghost" href="index.php">‹ Zum Casino</a>
</div>
</div>
<?php if ($flash): ?><div class="adm-flash <?= $flash[0] === 'ok' ? 'ok' : 'err' ?>"><?= e($flash[1]) ?></div><?php endif; ?>
<!-- ===== Spielwahrscheinlichkeiten ===== -->
<?php
$fsGf = isset($settings['fs_factor:global']) ? (float)$settings['fs_factor:global'] : 1.0;
$nf = function ($v) { return rtrim(rtrim(number_format($v, 2), '0'), '.'); };
?>
<div class="adm-card">
<h2>🎚 Spielwahrscheinlichkeiten</h2>
<form method="post">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="save_rtp">
<div class="adm-rtp-grid" style="margin-bottom:14px">
<div class="adm-rtp-item">
<label><strong>Globaler Auszahlungsfaktor</strong> — <span id="gfval"><?= $nf($gf) ?></span>×</label>
<input class="adm-range" type="range" min="0.5" max="5" step="0.05" name="rtp_global" value="<?= e((string)$gf) ?>"
oninput="document.getElementById('gfval').textContent=(+this.value).toFixed(2)">
</div>
<div class="adm-rtp-item">
<label><strong>Globale Freispiel-Häufigkeit</strong> — <span id="fsgval"><?= $nf($fsGf) ?></span>×</label>
<input class="adm-range" type="range" min="1" max="8" step="0.1" name="fs_global" value="<?= e((string)$fsGf) ?>"
oninput="document.getElementById('fsgval').textContent=(+this.value).toFixed(1)">
</div>
</div>
<div class="adm-rtp-grid">
<?php foreach ($games as $id => $g):
$gv = isset($settings['rtp_factor:' . $id]) ? (float)$settings['rtp_factor:' . $id] : 1.0;
$fsv = isset($settings['fs_factor:' . $id]) ? (float)$settings['fs_factor:' . $id] : 1.0;
$baseRtp = (float)($g['rtp'] ?? 0.96);
?>
<div class="adm-rtp-item">
<label><strong><?= e($g['title']) ?></strong></label>
<label style="font-size:.72rem">Auszahlung: <span class="rtpf"><?= $nf($gv) ?></span>×</label>
<input class="adm-range" type="range" min="0.5" max="5" step="0.05" name="rtp_<?= e($id) ?>" value="<?= e((string)$gv) ?>"
data-base="<?= $baseRtp ?>" oninput="rtpEff(this)">
<label style="font-size:.72rem;margin-top:6px">Freispiele: <span class="fsf"><?= $nf($fsv) ?></span>×</label>
<input class="adm-range" type="range" min="1" max="8" step="0.1" name="fs_<?= e($id) ?>" value="<?= e((string)$fsv) ?>"
oninput="this.parentNode.querySelector('.fsf').textContent=(+this.value).toFixed(1)">
<div class="eff" data-eff>Effektive RTP ≈ <?= round($baseRtp * $gf * $gv * 100) ?>%</div>
</div>
<?php endforeach; ?>
</div>
<label style="display:flex;align-items:center;gap:8px;margin:14px 0;font-size:.9rem">
<input type="checkbox" name="gamble_enabled" value="1" <?= gamble_enabled() ? 'checked' : '' ?> style="width:18px;height:18px">
<strong>Verdopplungsspiel (Rot/Schwarz-Gewinnleiter) aktiv</strong>
</label>
<button class="btn-gold" type="submit">Wahrscheinlichkeiten speichern</button>
<div class="adm-note">Auszahlungsfaktor 0,50–5,00 (höher = mehr Gewinn). Freispiel-Häufigkeit 1,0–8,0 (höher = öfter Freispiele). Pro-Spieler-Gewinnfaktor multipliziert zusätzlich. Roulette bleibt fair bei 97,3%.</div>
</form>
</div>
<!-- ===== Symbole austauschen ===== -->
<div class="adm-card">
<h2>🖼 Symbole austauschen</h2>
<form method="get" style="margin-bottom:14px">
<select name="symgame" class="adm-symselect" onchange="this.form.submit()">
<?php foreach ($games as $id => $g): ?>
<option value="<?= e($id) ?>" <?= $id === $symGameId ? 'selected' : '' ?>><?= e($g['title']) ?></option>
<?php endforeach; ?>
</select>
<noscript><button class="btn-gold">Anzeigen</button></noscript>
</form>
<div class="adm-sym-grid">
<?php foreach ($symGame['symbols'] as $code => $sym): $hasOv = isset($symOverrides[$code]); ?>
<div class="adm-sym">
<div class="adm-sym-prev">
<?php if (!empty($sym['img'])): ?>
<img src="<?= e($sym['img']) ?>" alt="">
<?php else: ?>
<span><?= e($sym['icon']) ?></span>
<?php endif; ?>
</div>
<div class="adm-sym-name"><?= e($sym['name']) ?>
<small><?= e($code) ?> · <?= e($sym['type']) ?></small>
<?php if ($hasOv): ?><span class="adm-ov">geändert</span><?php endif; ?>
</div>
<form method="post" class="adm-sym-form">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="sym_icon">
<input type="hidden" name="game" value="<?= e($symGameId) ?>">
<input type="hidden" name="code" value="<?= e($code) ?>">
<input type="text" name="icon" placeholder="Emoji/Text" maxlength="8">
<button type="submit">Setzen</button>
</form>
<form method="post" enctype="multipart/form-data" class="adm-sym-form">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="sym_upload">
<input type="hidden" name="game" value="<?= e($symGameId) ?>">
<input type="hidden" name="code" value="<?= e($code) ?>">
<input type="file" name="symfile" accept="image/*">
<button type="submit">⬆ Bild</button>
</form>
<?php if ($hasOv): ?>
<form method="post" class="adm-sym-form">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="sym_reset">
<input type="hidden" name="game" value="<?= e($symGameId) ?>">
<input type="hidden" name="code" value="<?= e($code) ?>">
<button type="submit" class="btn-ban">↺ Standard</button>
</form>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<div class="adm-note">Eigenes Emoji/Text setzen <em>oder</em> ein Bild hochladen (JPG/PNG/WebP/GIF, max. 3 MB, am besten quadratisch). „↺ Standard" stellt das Originalsymbol wieder her.</div>
</div>
<!-- ===== Spielerverwaltung ===== -->
<div class="adm-card">
<h2>👤 Spielerverwaltung (<?= count($players) ?>)</h2>
<div class="adm-note" style="margin-bottom:12px">Der <strong>Gewinnfaktor</strong> stellt die Gewinnwahrscheinlichkeit <em>einzelner</em> Spieler ein (Standard 1,00 für alle). Wirkt auf Slots <em>und</em> Roulette; multipliziert mit den globalen Spielwahrscheinlichkeiten. Bereich 0,20–2,00.</div>
<table class="adm-table">
<thead><tr><th>Spieler</th><th>Guthaben / Freispiele</th><th>Status</th><th>Aktionen</th></tr></thead>
<tbody>
<?php foreach ($players as $p): $isBanned = (int)$p['banned'] === 1; ?>
<tr>
<td class="adm-player">#<?= (int)$p['id'] ?> <?= e($p['username']) ?>
<small>Zuletzt: <?= e((string)($p['last_seen_at'] ?? '–')) ?></small></td>
<td>
<div><strong style="color:var(--rr-gold)"><?= money_fmt((float)$p['balance']) ?></strong> <?= e($config['app']['currency']) ?></div>
<div style="color:var(--rr-soft);font-size:.78rem">Freispiele: <?= (int)$p['free_spins'] ?> · Gewinnfaktor: <strong style="color:<?= (float)$p['rtp_factor'] == 1.0 ? 'var(--rr-soft)' : 'var(--rr-acc)' ?>"><?= rtrim(rtrim(number_format((float)$p['rtp_factor'], 2), '0'), '.') ?>×</strong></div>
</td>
<td><span class="adm-badge <?= $isBanned ? 'banned' : 'active' ?>"><?= $isBanned ? 'GESPERRT' : 'Aktiv' ?></span></td>
<td>
<div class="adm-row-forms">
<form method="post" title="Guthaben absolut setzen">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="set_balance">
<input type="hidden" name="player_id" value="<?= (int)$p['id'] ?>">
<input type="number" step="0.01" name="balance" value="<?= e((string)round((float)$p['balance'], 2)) ?>">
<button class="btn-gold" type="submit">Setzen</button>
</form>
<form method="post" title="Betrag gutschreiben/abziehen">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="adjust_balance">
<input type="hidden" name="player_id" value="<?= (int)$p['id'] ?>">
<input class="small" type="number" step="0.01" name="delta" placeholder="±">
<button type="submit">Buchen</button>
</form>
<form method="post" title="Freispiele setzen">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="set_free_spins">
<input type="hidden" name="player_id" value="<?= (int)$p['id'] ?>">
<input class="small" type="number" min="0" name="free_spins" value="<?= (int)$p['free_spins'] ?>">
<button type="submit">FS</button>
</form>
<form method="post" title="Gewinnwahrscheinlichkeit (1,00 = Standard; <1 weniger, >1 mehr Gewinn)">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="set_player_factor">
<input type="hidden" name="player_id" value="<?= (int)$p['id'] ?>">
<input class="small" type="number" step="0.05" min="0.2" max="10" name="factor" value="<?= number_format((float)$p['rtp_factor'], 2, '.', '') ?>">
<button type="submit">Faktor</button>
</form>
<form method="post" onsubmit="return <?= $isBanned ? 'true' : "confirm('Spieler wirklich sperren?')" ?>">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<input type="hidden" name="action" value="toggle_ban">
<input type="hidden" name="player_id" value="<?= (int)$p['id'] ?>">
<input type="hidden" name="banned" value="<?= $isBanned ? 0 : 1 ?>">
<button class="<?= $isBanned ? 'btn-unban' : 'btn-ban' ?>" type="submit"><?= $isBanned ? 'Entsperren' : 'Sperren' ?></button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<script>
function rtpEff(el){
const base=parseFloat(el.dataset.base), gf=parseFloat(document.querySelector('[name=rtp_global]').value);
const eff=Math.round(base*gf*parseFloat(el.value)*100);
const item=el.parentNode;
const rtpf=item.querySelector('.rtpf'); if(rtpf) rtpf.textContent=(+el.value).toFixed(2);
item.querySelector('[data-eff]').textContent='Effektive RTP ≈ '+eff+'%';
}
</script>
</body>
</html>