592 lines
23 KiB
PHP
592 lines
23 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../../includes/auth.php';
|
|
require_once __DIR__ . '/../../includes/config.php';
|
|
require_once __DIR__ . '/../../includes/header.php';
|
|
require_once __DIR__ . '/../../includes/sidebar.php';
|
|
|
|
try {
|
|
$pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS blocked_usernames (
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(20) NOT NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by INT NULL,
|
|
UNIQUE KEY unique_blocked_username (name),
|
|
KEY idx_blocked_created_by (created_by)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
|
|
);
|
|
} catch (Throwable $e) {
|
|
}
|
|
|
|
$blockedRows = [];
|
|
try {
|
|
$stmt = $pdo->prepare(
|
|
"SELECT b.id, b.name, b.created_at, b.created_by, u.username AS created_by_username
|
|
FROM blocked_usernames b
|
|
LEFT JOIN users u ON u.id = b.created_by
|
|
ORDER BY b.created_at DESC, b.id DESC
|
|
LIMIT 100"
|
|
);
|
|
$stmt->execute();
|
|
$blockedRows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
|
} catch (Throwable $e) {
|
|
$blockedRows = [];
|
|
}
|
|
|
|
$suspendedRows = [];
|
|
try {
|
|
$stmt2 = $pdo->prepare(
|
|
"SELECT u.id, u.username, u.email,
|
|
COALESCE(u.suspension_reason, '') AS suspension_reason,
|
|
u.suspended_until,
|
|
a.username AS suspended_by_username
|
|
FROM users u
|
|
LEFT JOIN users a ON a.id = u.suspended_by
|
|
WHERE u.account_suspended = 1
|
|
ORDER BY u.id DESC
|
|
LIMIT 100"
|
|
);
|
|
$stmt2->execute();
|
|
$suspendedRows = $stmt2->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
|
} catch (Throwable $e) {
|
|
// Columns may not exist yet
|
|
try {
|
|
$stmt2b = $pdo->prepare(
|
|
"SELECT id, username, email, '' AS suspension_reason, NULL AS suspended_until, NULL AS suspended_by_username
|
|
FROM users WHERE account_suspended = 1 ORDER BY id DESC LIMIT 100"
|
|
);
|
|
$stmt2b->execute();
|
|
$suspendedRows = $stmt2b->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
|
} catch (Throwable $e2) {
|
|
$suspendedRows = [];
|
|
}
|
|
}
|
|
?>
|
|
|
|
<div class="admin-content">
|
|
<style>
|
|
.admin-page-title {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: #23282d;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 10px;
|
|
border-bottom: 2px solid #0073aa;
|
|
}
|
|
|
|
.admin-content-box {
|
|
background: #fff;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 20px;
|
|
margin-top: 20px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.blocked-form {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto;
|
|
gap: 10px;
|
|
margin-bottom: 14px;
|
|
align-items: end;
|
|
}
|
|
|
|
.blocked-form label {
|
|
display: block;
|
|
margin-bottom: 6px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #444;
|
|
}
|
|
|
|
.blocked-form input[type="text"] {
|
|
width: 100%;
|
|
min-height: 38px;
|
|
border: 1px solid #d0d7de;
|
|
border-radius: 6px;
|
|
padding: 8px 12px;
|
|
font-size: 14px;
|
|
outline: none;
|
|
}
|
|
|
|
.blocked-form input[type="text"]:focus {
|
|
border-color: #0073aa;
|
|
box-shadow: 0 0 0 3px rgba(0,115,170,0.12);
|
|
}
|
|
|
|
.blocked-btn {
|
|
min-height: 38px;
|
|
border: 0;
|
|
border-radius: 6px;
|
|
padding: 9px 14px;
|
|
background: #0073aa;
|
|
color: #fff;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.blocked-btn:hover {
|
|
background: #005f8d;
|
|
}
|
|
|
|
.blocked-status {
|
|
min-height: 20px;
|
|
font-size: 13px;
|
|
margin-bottom: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
.blocked-status.success {
|
|
color: #1e7e34;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.blocked-status.error {
|
|
color: #b32d2e;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.blocked-help {
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-bottom: 14px;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.blocked-table-wrap {
|
|
overflow-x: auto;
|
|
border: 1px solid #e6e6e6;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.blocked-table {
|
|
width: 100%;
|
|
min-width: 560px;
|
|
border-collapse: collapse;
|
|
background: #fff;
|
|
}
|
|
|
|
.blocked-table th,
|
|
.blocked-table td {
|
|
padding: 10px 12px;
|
|
border-bottom: 1px solid #eee;
|
|
text-align: left;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.blocked-table th {
|
|
background: #f8f9fa;
|
|
color: #23282d;
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.blocked-table tbody tr:hover {
|
|
background: #fafcff;
|
|
}
|
|
|
|
.blocked-empty {
|
|
padding: 16px;
|
|
text-align: center;
|
|
font-size: 13px;
|
|
color: #666;
|
|
}
|
|
|
|
@media (max-width: 720px) {
|
|
.blocked-form {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* Custom Modal */
|
|
.su-modal-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0,0,0,0.55);
|
|
z-index: 10000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
animation: suFadeIn 0.2s ease-out;
|
|
}
|
|
.su-modal-overlay.active { display: flex; }
|
|
.su-modal-box {
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
|
max-width: 520px;
|
|
width: 90%;
|
|
animation: suSlideUp 0.25s ease-out;
|
|
}
|
|
.su-modal-head {
|
|
padding: 18px 22px;
|
|
border-radius: 8px 8px 0 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
color: #fff;
|
|
}
|
|
.su-modal-head.green { background: linear-gradient(135deg,#28a745,#1e7e34); }
|
|
.su-modal-head.blue { background: linear-gradient(135deg,#0073aa,#005a87); }
|
|
.su-modal-head-icon { font-size: 24px; }
|
|
.su-modal-head-title { font-size: 17px; font-weight: 700; margin: 0; }
|
|
.su-modal-body {
|
|
padding: 22px;
|
|
font-size: 14px;
|
|
color: #444;
|
|
line-height: 1.6;
|
|
}
|
|
.su-modal-body textarea {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
resize: vertical;
|
|
box-sizing: border-box;
|
|
margin-top: 8px;
|
|
}
|
|
.su-modal-body textarea:focus {
|
|
outline: none;
|
|
border-color: #0073aa;
|
|
box-shadow: 0 0 0 3px rgba(0,115,170,0.1);
|
|
}
|
|
.su-modal-foot {
|
|
padding: 14px 22px;
|
|
border-top: 1px solid #eee;
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: flex-end;
|
|
}
|
|
.su-modal-btn {
|
|
padding: 9px 20px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.su-modal-btn-primary { background: #28a745; color: #fff; }
|
|
.su-modal-btn-primary:hover { background: #1e7e34; transform: translateY(-1px); }
|
|
.su-modal-btn-secondary { background: #f1f1f1; color: #444; border: 1px solid #ddd; }
|
|
.su-modal-btn-secondary:hover { background: #e2e2e2; }
|
|
@keyframes suFadeIn { from { opacity:0 } to { opacity:1 } }
|
|
@keyframes suSlideUp { from { transform:translateY(40px);opacity:0 } to { transform:translateY(0);opacity:1 } }
|
|
</style>
|
|
|
|
<h1 class="admin-page-title">⚙️ Użytkownicy - Ustawienia</h1>
|
|
|
|
<div class="admin-content-box">
|
|
<h3 style="margin:0 0 12px 0; color:#23282d;">Blokowane nazwy użytkowników</h3>
|
|
|
|
<div class="blocked-help">
|
|
Endpoint: <strong>/admin/user/settings/blocked-names</strong> • metody: <strong>POST, DELETE</strong> • format nazwy: <strong>[A-Za-z0-9_&!]{1,20}</strong>
|
|
</div>
|
|
|
|
<form id="blockedNameForm" class="blocked-form">
|
|
<div>
|
|
<label for="blockedNameInput">Nazwa użytkownika do zablokowania</label>
|
|
<input type="text" id="blockedNameInput" maxlength="20" placeholder="np. admin" required>
|
|
</div>
|
|
<button class="blocked-btn" type="submit">Zablokuj nazwę</button>
|
|
</form>
|
|
|
|
<div id="blockedStatus" class="blocked-status"></div>
|
|
|
|
<div class="blocked-table-wrap">
|
|
<table class="blocked-table" aria-label="Zablokowane nazwy użytkowników">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:70px;">ID</th>
|
|
<th>Nazwa</th>
|
|
<th style="width:190px;">Data dodania</th>
|
|
<th style="width:220px;">Dodane przez</th>
|
|
<th style="width:120px;">Akcja</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="blockedTableBody">
|
|
<?php if (empty($blockedRows)): ?>
|
|
<tr>
|
|
<td colspan="5" class="blocked-empty">Brak zablokowanych nazw</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($blockedRows as $row): ?>
|
|
<tr id="blocked-row-<?php echo (int)($row['id'] ?? 0); ?>">
|
|
<td><?php echo (int)($row['id'] ?? 0); ?></td>
|
|
<td><strong><?php echo htmlspecialchars((string)($row['name'] ?? ''), ENT_QUOTES, 'UTF-8'); ?></strong></td>
|
|
<td><?php echo htmlspecialchars((string)($row['created_at'] ?? ''), ENT_QUOTES, 'UTF-8'); ?></td>
|
|
<td>
|
|
<?php
|
|
$createdByUsername = (string)($row['created_by_username'] ?? '');
|
|
$createdById = (int)($row['created_by'] ?? 0);
|
|
if ($createdByUsername !== '') {
|
|
echo htmlspecialchars($createdByUsername, ENT_QUOTES, 'UTF-8') . ' (#' . $createdById . ')';
|
|
} elseif ($createdById > 0) {
|
|
echo '#' . $createdById;
|
|
} else {
|
|
echo '-';
|
|
}
|
|
?>
|
|
</td>
|
|
<td>
|
|
<button
|
|
type="button"
|
|
class="blocked-btn"
|
|
style="background:#dc3545;font-size:12px;padding:6px 10px;"
|
|
onclick="deleteBlockedName(<?php echo (int)($row['id'] ?? 0); ?>, '<?php echo htmlspecialchars((string)($row['name'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>')"
|
|
>
|
|
Usuń
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-content-box" style="margin-top:20px;">
|
|
<h3 style="margin:0 0 12px 0; color:#23282d;">🚫 Zawieszeni gracze</h3>
|
|
|
|
<div id="suspendStatus" class="blocked-status"></div>
|
|
|
|
<div class="blocked-table-wrap">
|
|
<table class="blocked-table" aria-label="Zawieszeni gracze">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:60px;">ID</th>
|
|
<th>Username</th>
|
|
<th>Email</th>
|
|
<th>Powód zawieszenia</th>
|
|
<th style="width:160px;">Zawieszony do</th>
|
|
<th style="width:100px;">Akcja</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($suspendedRows)): ?>
|
|
<tr>
|
|
<td colspan="6" class="blocked-empty">Brak zawieszonych graczy</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($suspendedRows as $sr): ?>
|
|
<tr id="suspend-row-<?php echo (int)$sr['id']; ?>">
|
|
<td><?php echo (int)$sr['id']; ?></td>
|
|
<td><strong><?php echo htmlspecialchars((string)($sr['username'] ?? ''), ENT_QUOTES, 'UTF-8'); ?></strong></td>
|
|
<td style="font-size:12px;"><?php echo htmlspecialchars((string)($sr['email'] ?? ''), ENT_QUOTES, 'UTF-8'); ?></td>
|
|
<td style="font-size:12px;"><?php echo htmlspecialchars((string)($sr['suspension_reason'] ?? '-'), ENT_QUOTES, 'UTF-8'); ?></td>
|
|
<td style="font-size:12px;"><?php echo $sr['suspended_until'] ? htmlspecialchars((string)$sr['suspended_until'], ENT_QUOTES, 'UTF-8') : 'bezterminowo'; ?></td>
|
|
<td>
|
|
<button class="blocked-btn" style="background:#28a745;font-size:12px;padding:6px 10px;"
|
|
onclick="unsuspendUser(<?php echo (int)$sr['id']; ?>)">
|
|
Odwieś
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Custom Modal -->
|
|
<div id="suModal" class="su-modal-overlay">
|
|
<div class="su-modal-box">
|
|
<div id="suModalHead" class="su-modal-head green">
|
|
<span id="suModalIcon" class="su-modal-head-icon"></span>
|
|
<h3 id="suModalTitle" class="su-modal-head-title"></h3>
|
|
</div>
|
|
<div id="suModalBody" class="su-modal-body"></div>
|
|
<div id="suModalFoot" class="su-modal-foot"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function suShowPrompt(title, message, defaultVal, icon, colorClass) {
|
|
return new Promise((resolve) => {
|
|
const modal = document.getElementById('suModal');
|
|
document.getElementById('suModalHead').className = 'su-modal-head ' + (colorClass || 'blue');
|
|
document.getElementById('suModalIcon').textContent = icon || '✏️';
|
|
document.getElementById('suModalTitle').textContent = title;
|
|
document.getElementById('suModalBody').innerHTML =
|
|
'<p style="margin:0 0 6px;">' + message + '</p>' +
|
|
'<textarea id="suPromptVal" rows="3">' + (defaultVal || '') + '</textarea>';
|
|
document.getElementById('suModalFoot').innerHTML =
|
|
'<button class="su-modal-btn su-modal-btn-secondary" id="suBtnCancel">Anuluj</button>' +
|
|
'<button class="su-modal-btn su-modal-btn-primary" id="suBtnOk">✅ Potwierdź</button>';
|
|
modal.classList.add('active');
|
|
setTimeout(() => { const t = document.getElementById('suPromptVal'); if (t) { t.focus(); t.select(); } }, 50);
|
|
document.getElementById('suBtnOk').onclick = () => {
|
|
const v = document.getElementById('suPromptVal').value;
|
|
modal.classList.remove('active');
|
|
resolve(v);
|
|
};
|
|
document.getElementById('suBtnCancel').onclick = () => {
|
|
modal.classList.remove('active');
|
|
resolve(null);
|
|
};
|
|
modal.onclick = (e) => { if (e.target === modal) { modal.classList.remove('active'); resolve(null); } };
|
|
});
|
|
}
|
|
|
|
async function unsuspendUser(userId) {
|
|
const reasonInput = await suShowPrompt(
|
|
'✅ Odwieszenie konta #' + userId,
|
|
'Podaj powód odwieszenia (opcjonalnie):',
|
|
'Decyzja administracyjna po weryfikacji sytuacji.',
|
|
'✅', 'green'
|
|
);
|
|
if (reasonInput === null) return;
|
|
const reason = (reasonInput || '').trim();
|
|
const statusEl = document.getElementById('suspendStatus');
|
|
statusEl.textContent = 'Przetwarzanie...';
|
|
statusEl.className = 'blocked-status';
|
|
try {
|
|
const resp = await fetch('../../../api/suspendUser.php', {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ action: 'unsuspend', user_id: userId, reason })
|
|
});
|
|
const text = await resp.text();
|
|
let json = null;
|
|
try {
|
|
json = JSON.parse(text);
|
|
} catch (_) {
|
|
throw new Error('API zwróciło nieprawidłową odpowiedź (oczekiwano JSON).');
|
|
}
|
|
|
|
if (!resp.ok || !json.success) {
|
|
throw new Error(json.error || 'Błąd odwieszenia');
|
|
}
|
|
|
|
statusEl.textContent = json.message || 'Konto zostało odwieszone.';
|
|
statusEl.className = 'blocked-status success';
|
|
const row = document.getElementById('suspend-row-' + userId);
|
|
if (row) row.remove();
|
|
} catch (err) {
|
|
statusEl.textContent = err.message || 'Błąd podczas odwieszania.';
|
|
statusEl.className = 'blocked-status error';
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
(function () {
|
|
const form = document.getElementById('blockedNameForm');
|
|
const input = document.getElementById('blockedNameInput');
|
|
const statusEl = document.getElementById('blockedStatus');
|
|
const blockedTableBody = document.getElementById('blockedTableBody');
|
|
const endpoint = '/admin/user/settings/blocked-names/index.php';
|
|
|
|
function setStatus(message, type) {
|
|
statusEl.textContent = message || '';
|
|
statusEl.classList.remove('success', 'error');
|
|
if (type) statusEl.classList.add(type);
|
|
}
|
|
|
|
async function parseApiResponse(response) {
|
|
const rawText = await response.text();
|
|
if (!rawText) {
|
|
throw new Error('API zwróciło pustą odpowiedź.');
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(rawText);
|
|
} catch (_) {
|
|
throw new Error('API zwróciło nieprawidłową odpowiedź (oczekiwano JSON).');
|
|
}
|
|
}
|
|
|
|
function renderEmptyBlockedTable() {
|
|
if (!blockedTableBody) return;
|
|
|
|
const stillRows = blockedTableBody.querySelectorAll('tr[id^="blocked-row-"]');
|
|
if (stillRows.length > 0) return;
|
|
|
|
blockedTableBody.innerHTML = '<tr><td colspan="5" class="blocked-empty">Brak zablokowanych nazw</td></tr>';
|
|
}
|
|
|
|
window.deleteBlockedName = async function (blockedId, blockedName) {
|
|
if (!blockedId) {
|
|
setStatus('Nieprawidłowy identyfikator blokady.', 'error');
|
|
return;
|
|
}
|
|
|
|
const accepted = window.confirm('Czy na pewno chcesz usunąć blokadę nazwy "' + blockedName + '"?');
|
|
if (!accepted) return;
|
|
|
|
setStatus('Usuwanie blokady...', null);
|
|
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: 'DELETE',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ id: blockedId })
|
|
});
|
|
|
|
const json = await parseApiResponse(response);
|
|
|
|
if (!response.ok) {
|
|
throw new Error((json && json.message) ? json.message : 'Nie udało się usunąć blokady nazwy.');
|
|
}
|
|
|
|
const row = document.getElementById('blocked-row-' + blockedId);
|
|
if (row) row.remove();
|
|
renderEmptyBlockedTable();
|
|
|
|
setStatus(json.message || 'Nazwa została usunięta z listy zablokowanych.', 'success');
|
|
} catch (error) {
|
|
setStatus(error && error.message ? error.message : 'Błąd podczas usuwania blokady.', 'error');
|
|
}
|
|
};
|
|
|
|
form.addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
const value = (input.value || '').trim();
|
|
if (value === '') {
|
|
setStatus('Nazwa użytkownika nie może być pusta.', 'error');
|
|
return;
|
|
}
|
|
|
|
setStatus('Zapisywanie...', null);
|
|
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ name: value })
|
|
});
|
|
|
|
const json = await parseApiResponse(response);
|
|
|
|
if (!response.ok) {
|
|
throw new Error((json && json.message) ? json.message : 'Nie udało się zablokować nazwy.');
|
|
}
|
|
|
|
setStatus(json.message || 'Nazwa użytkownika została zablokowana pomyślnie.', 'success');
|
|
input.value = '';
|
|
setTimeout(function () {
|
|
window.location.reload();
|
|
}, 500);
|
|
} catch (error) {
|
|
setStatus(error && error.message ? error.message : 'Błąd podczas zapisu.', 'error');
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
</div>
|
|
|
|
<?php
|
|
require_once __DIR__ . '/../../includes/footer.php';
|
|
?>
|