288 lines
16 KiB
PHP
288 lines
16 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/admin_bootstrap.php';
|
|
require_once __DIR__ . '/../includes/smtp_helper.php';
|
|
|
|
$admin = admin_require_auth();
|
|
$adminId = $admin['user_id'];
|
|
$adminUsername = $admin['username'];
|
|
|
|
$supportEmail = 'wspolpraca@togethere.cloud';
|
|
$supportUrl = 'https://togethere.cloud/bok/';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
admin_json_error('Metoda niedozwolona', 405);
|
|
}
|
|
|
|
$pdo = admin_get_pdo();
|
|
|
|
// Ensure required columns exist.
|
|
$columnsToAdd = [
|
|
'suspension_reason' => "ALTER TABLE users ADD COLUMN suspension_reason VARCHAR(500) NULL AFTER account_suspended",
|
|
'suspended_until' => "ALTER TABLE users ADD COLUMN suspended_until DATETIME NULL AFTER suspension_reason",
|
|
'suspended_by' => "ALTER TABLE users ADD COLUMN suspended_by INT UNSIGNED NULL AFTER suspended_until",
|
|
];
|
|
$db = (string)$pdo->query('SELECT DATABASE()')->fetchColumn();
|
|
foreach ($columnsToAdd as $col => $sql) {
|
|
$check = $pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?');
|
|
$check->execute([$db, 'users', $col]);
|
|
if ((int)$check->fetchColumn() === 0) {
|
|
try {
|
|
$pdo->exec($sql);
|
|
} catch (Throwable $e) {
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure history table exists.
|
|
try {
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS user_account_history (
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
user_id INT UNSIGNED NOT NULL,
|
|
action ENUM('suspend', 'unsuspend') NOT NULL,
|
|
reason TEXT NULL,
|
|
suspended_until DATETIME NULL,
|
|
performed_by INT UNSIGNED NULL,
|
|
performed_by_username VARCHAR(50) NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_uah_user_id (user_id),
|
|
KEY idx_uah_created_at (created_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
|
} catch (Throwable $e) {
|
|
}
|
|
|
|
$body = admin_read_json_body();
|
|
$action = isset($body['action']) ? (string)$body['action'] : '';
|
|
$userId = isset($body['user_id']) ? (int)$body['user_id'] : 0;
|
|
|
|
if ($userId <= 0) {
|
|
admin_json_error('Nieprawidłowy user_id');
|
|
}
|
|
if (!in_array($action, ['suspend', 'unsuspend'], true)) {
|
|
admin_json_error('Nieprawidłowa akcja');
|
|
}
|
|
|
|
// Fetch target user
|
|
$stmtUser = $pdo->prepare("SELECT id, username, email, role, account_suspended FROM users WHERE id = ? LIMIT 1");
|
|
$stmtUser->execute([$userId]);
|
|
$targetUser = $stmtUser->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$targetUser) {
|
|
admin_json_error('Użytkownik nie istnieje', 404);
|
|
}
|
|
|
|
$targetRole = strtolower((string)($targetUser['role'] ?? 'user'));
|
|
if ((int)$targetUser['id'] === (int)$adminId) {
|
|
admin_json_error('Nie możesz zarządzać swoim kontem w tym widoku.', 403);
|
|
}
|
|
if ($targetRole === 'admin') {
|
|
admin_json_error('Nie można zarządzać kontami administratorów w tym widoku.', 403);
|
|
}
|
|
|
|
$targetEmail = trim((string)($targetUser['email'] ?? ''));
|
|
if ($action === 'suspend') {
|
|
$reason = trim((string)($body['reason'] ?? ''));
|
|
if ($reason === '') {
|
|
admin_json_error('Powód zawieszenia jest wymagany');
|
|
}
|
|
|
|
$suspendedUntilRaw = (string)($body['suspended_until'] ?? 'permanent');
|
|
$suspendedUntil = null;
|
|
$suspendedUntilDisplay = 'bezterminowo';
|
|
if ($suspendedUntilRaw !== 'permanent') {
|
|
$ts = strtotime($suspendedUntilRaw);
|
|
if ($ts === false || $ts <= time()) {
|
|
admin_json_error('Nieprawidłowa data zawieszenia - musi być w przyszłości');
|
|
}
|
|
$suspendedUntil = date('Y-m-d H:i:s', $ts);
|
|
$suspendedUntilDisplay = date('d.m.Y H:i', $ts);
|
|
}
|
|
|
|
try {
|
|
$pdo->prepare("UPDATE users SET account_suspended = 1, suspension_reason = ?, suspended_until = ?, suspended_by = ?, wallet_status = 'suspended' WHERE id = ?")
|
|
->execute([$reason, $suspendedUntil, $adminId, $userId]);
|
|
} catch (Throwable $e) {
|
|
try {
|
|
$pdo->prepare("UPDATE users SET account_suspended = 1 WHERE id = ?")->execute([$userId]);
|
|
} catch (Throwable $e2) {
|
|
admin_json_error('Błąd aktualizacji bazy danych: ' . $e2->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$pdo->prepare("INSERT INTO user_account_history (user_id, action, reason, suspended_until, performed_by, performed_by_username) VALUES (?, 'suspend', ?, ?, ?, ?)")
|
|
->execute([$userId, $reason, $suspendedUntil, $adminId, $adminUsername]);
|
|
} catch (Throwable $e) {
|
|
}
|
|
|
|
$emailSent = false;
|
|
$emailError = '';
|
|
try {
|
|
$untilHtml = $suspendedUntil ? '<strong>' . htmlspecialchars($suspendedUntilDisplay, ENT_QUOTES, 'UTF-8') . '</strong>' : '<strong>bezterminowo</strong>';
|
|
$safeUsernameSuspend = htmlspecialchars((string)$targetUser['username'], ENT_QUOTES, 'UTF-8');
|
|
$safeReasonSuspend = nl2br(htmlspecialchars($reason, ENT_QUOTES, 'UTF-8'));
|
|
$safeSupportEmailSuspend = htmlspecialchars($supportEmail, ENT_QUOTES, 'UTF-8');
|
|
$safeSupportUrlSuspend = htmlspecialchars($supportUrl, ENT_QUOTES, 'UTF-8');
|
|
$emailBody = '<!DOCTYPE html><html lang="pl"><head><meta charset="UTF-8"><title>Konto zawieszone</title></head><body style="margin:0;padding:0;background:#f4f4f4;font-family:Arial,sans-serif;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f4;padding:30px 0;"><tr><td align="center">
|
|
<table width="600" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.1);">
|
|
<tr><td style="background:linear-gradient(135deg,#dc3545,#c82333);padding:30px 40px;text-align:center;">
|
|
<h1 style="color:#fff;margin:0;font-size:26px;letter-spacing:1px;">⚠ Konto zawieszone</h1>
|
|
<p style="color:#ffc9ce;margin:8px 0 0;font-size:14px;">Powiadomienie dotyczące Twojego konta TOGETHERE</p>
|
|
</td></tr>
|
|
<tr><td style="padding:35px 40px;">
|
|
<p style="font-size:16px;color:#333;margin-top:0;">Cześć <strong>' . $safeUsernameSuspend . '</strong>,</p>
|
|
<p style="font-size:15px;color:#555;">Informujemy, że Twoje konto w serwisie <strong>TOGETHERE GAMES</strong> zostało <strong>zawieszone</strong> przez administrację serwisu.</p>
|
|
|
|
<table width="100%" cellpadding="14" cellspacing="0" style="background:#fff5f5;border:1px solid #f5c6cb;border-radius:6px;margin:24px 0;">
|
|
<tr><td style="padding-bottom:10px;"><span style="font-size:13px;text-transform:uppercase;color:#999;letter-spacing:0.5px;">Powód zawieszenia</span><br><span style="font-size:15px;color:#333;margin-top:4px;display:block;">' . $safeReasonSuspend . '</span></td></tr>
|
|
<tr><td style="border-top:1px solid #f5c6cb;padding-top:10px;"><span style="font-size:13px;text-transform:uppercase;color:#999;letter-spacing:0.5px;">Zawieszone do</span><br><span style="font-size:15px;color:#333;margin-top:4px;display:block;">' . $untilHtml . '</span></td></tr>
|
|
</table>
|
|
|
|
<p style="font-size:15px;color:#333;font-weight:bold;">Co to oznacza?</p>
|
|
<ul style="font-size:14px;color:#555;line-height:1.8;padding-left:20px;">
|
|
<li>Nie możesz się zalogować na swoje konto.</li>
|
|
<li>Dostęp do funkcji serwisu jest zablokowany do czasu odwieszenia.</li>
|
|
<li>Twoje dane i historia są bezpiecznie przechowywane.</li>
|
|
' . ($suspendedUntil ? '<li>Po upłynięciu terminu (<strong>' . htmlspecialchars($suspendedUntilDisplay, ENT_QUOTES, 'UTF-8') . '</strong>) konto zostanie automatycznie odwieszone.</li>' : '') . '
|
|
</ul>
|
|
|
|
<p style="font-size:15px;color:#333;font-weight:bold;">Chcesz złożyć odwołanie?</p>
|
|
<p style="font-size:14px;color:#555;">Jeżeli uważasz, że decyzja została podjęta błędnie, możesz skontaktować się z naszym Biurem Obsługi Klienta:</p>
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;">
|
|
<tr>
|
|
<td style="padding:6px 0;">📧 E-mail: <a href="mailto:' . $safeSupportEmailSuspend . '" style="color:#dc3545;text-decoration:none;font-weight:bold;">' . $safeSupportEmailSuspend . '</a></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding:6px 0;">🌐 BOK online: <a href="' . $safeSupportUrlSuspend . '" style="color:#dc3545;text-decoration:none;font-weight:bold;">' . $safeSupportUrlSuspend . '</a></td>
|
|
</tr>
|
|
</table>
|
|
<p style="font-size:13px;color:#888;margin-top:8px;">W wiadomości podaj swój login oraz opisz sytuację — postaramy się odpowiedzieć możliwie szybko.</p>
|
|
|
|
<hr style="border:none;border-top:1px solid #eee;margin:28px 0;">
|
|
<p style="font-size:13px;color:#aaa;text-align:center;margin:0;">Wiadomość wygenerowana automatycznie — prosimy nie odpowiadać bezpośrednio na ten e-mail.<br>TOGETHERE GAMES • <a href="https://togethere.cloud" style="color:#aaa;">togethere.cloud</a></p>
|
|
</td></tr></table>
|
|
</td></tr></table>
|
|
</body></html>';
|
|
$emailSent = (bool)sendEmailSMTP($targetEmail, 'Twoje konto zostało zawieszone - Wspólnie', $emailBody);
|
|
if (!$emailSent) {
|
|
// Fallback przez mail() jeśli SMTP nie zadziałał
|
|
$mHeaders = "MIME-Version: 1.0\r\n";
|
|
$mHeaders .= "Content-Type: text/html; charset=UTF-8\r\n";
|
|
$mHeaders .= "From: TOGETHERE GAMES <noreply@togethere.cloud>\r\n";
|
|
$mHeaders .= "Reply-To: " . $supportEmail . "\r\n";
|
|
$emailSent = @mail($targetEmail, '=?UTF-8?B?' . base64_encode('Twoje konto zostało zawieszone - Wspólnie') . '?=', $emailBody, $mHeaders);
|
|
if (!$emailSent) {
|
|
$emailError = 'Wysyłka nie powiodła się (SMTP + mail). Sprawdź smtp_debug.log.';
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
$emailError = $e->getMessage();
|
|
}
|
|
|
|
$msg = 'Konto użytkownika zostało zawieszone.';
|
|
if (!$emailSent) {
|
|
$msg .= ' ⚠️ Email NIE został wysłany: ' . $emailError;
|
|
}
|
|
admin_json_response(['success' => true, 'message' => $msg, 'email_sent' => $emailSent]);
|
|
}
|
|
|
|
// unsuspend
|
|
$unsuspendReason = trim((string)($body['reason'] ?? ''));
|
|
if ($unsuspendReason === '') {
|
|
$unsuspendReason = 'Decyzja administracyjna po weryfikacji sytuacji.';
|
|
}
|
|
|
|
try {
|
|
$pdo->prepare("UPDATE users SET account_suspended = 0, suspension_reason = NULL, suspended_until = NULL, suspended_by = NULL, wallet_status = 'active' WHERE id = ?")
|
|
->execute([$userId]);
|
|
} catch (Throwable $e) {
|
|
try {
|
|
$pdo->prepare("UPDATE users SET account_suspended = 0 WHERE id = ?")->execute([$userId]);
|
|
} catch (Throwable $e2) {
|
|
admin_json_error('Błąd aktualizacji bazy danych: ' . $e2->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$pdo->prepare("INSERT INTO user_account_history (user_id, action, reason, suspended_until, performed_by, performed_by_username) VALUES (?, 'unsuspend', ?, NULL, ?, ?)")
|
|
->execute([$userId, $unsuspendReason, $adminId, $adminUsername]);
|
|
} catch (Throwable $e) {
|
|
}
|
|
|
|
$emailSent = false;
|
|
$emailError = '';
|
|
try {
|
|
$safeUsername = htmlspecialchars((string)$targetUser['username'], ENT_QUOTES, 'UTF-8');
|
|
$safeReason = nl2br(htmlspecialchars($unsuspendReason, ENT_QUOTES, 'UTF-8'));
|
|
$safeSupportEmail = htmlspecialchars($supportEmail, ENT_QUOTES, 'UTF-8');
|
|
$safeSupportUrl = htmlspecialchars($supportUrl, ENT_QUOTES, 'UTF-8');
|
|
|
|
$emailBody = '<!DOCTYPE html><html lang="pl"><head><meta charset="UTF-8"><title>Konto aktywne</title></head><body style="margin:0;padding:0;background:#f4f4f4;font-family:Arial,sans-serif;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f4;padding:30px 0;"><tr><td align="center">
|
|
<table width="600" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.1);">
|
|
<tr><td style="background:linear-gradient(135deg,#28a745,#1e7e34);padding:30px 40px;text-align:center;">
|
|
<h1 style="color:#fff;margin:0;font-size:26px;letter-spacing:1px;">✓ Konto odwieszone</h1>
|
|
<p style="color:#c3f0cc;margin:8px 0 0;font-size:14px;">Twoje konto TOGETHERE jest ponownie aktywne</p>
|
|
</td></tr>
|
|
<tr><td style="padding:35px 40px;">
|
|
<p style="font-size:16px;color:#333;margin-top:0;">Cześć <strong>' . $safeUsername . '</strong>,</p>
|
|
<p style="font-size:15px;color:#555;">Mamy dla Ciebie dobrą wiadomość — Twoje konto w serwisie <strong>TOGETHERE GAMES</strong> zostało <strong>odwieszone</strong> i jest w pełni aktywne.</p>
|
|
|
|
<table width="100%" cellpadding="14" cellspacing="0" style="background:#f0fff4;border:1px solid #b2dfdb;border-radius:6px;margin:24px 0;">
|
|
<tr><td><span style="font-size:13px;text-transform:uppercase;color:#999;letter-spacing:0.5px;">Powód odwieszenia</span><br><span style="font-size:15px;color:#333;margin-top:4px;display:block;">' . $safeReason . '</span></td></tr>
|
|
<tr><td style="border-top:1px solid #b2dfdb;"><span style="font-size:13px;text-transform:uppercase;color:#999;letter-spacing:0.5px;">Status konta</span><br><span style="font-size:15px;font-weight:bold;color:#28a745;margin-top:4px;display:block;">● Aktywne — dostęp w pełni przywrócony</span></td></tr>
|
|
</table>
|
|
|
|
<p style="font-size:15px;color:#333;font-weight:bold;">Co zostało przywrócone?</p>
|
|
<ul style="font-size:14px;color:#555;line-height:1.8;padding-left:20px;">
|
|
<li>Możliwość logowania na konto.</li>
|
|
<li>Pełny dostęp do funkcji serwisu (turnieje, mecze, profil).</li>
|
|
<li>Historia i dane konta zostały zachowane bez zmian.</li>
|
|
</ul>
|
|
|
|
<p style="font-size:15px;color:#333;font-weight:bold;">Pamiętaj o zasadach</p>
|
|
<p style="font-size:14px;color:#555;">Prosimy o zapoznanie się z regulaminem serwisu i przestrzeganie zasad społeczności TOGETHERE GAMES. Kolejne naruszenie może skutkować stałym zablokowaniem konta.</p>
|
|
|
|
<p style="font-size:14px;color:#555;">Jeżeli masz pytania lub potrzebujesz pomocy:</p>
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin:12px 0 20px;">
|
|
<tr>
|
|
<td style="padding:6px 0;">📧 E-mail: <a href="mailto:' . $safeSupportEmail . '" style="color:#28a745;text-decoration:none;font-weight:bold;">' . $safeSupportEmail . '</a></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding:6px 0;">🌐 BOK online: <a href="' . $safeSupportUrl . '" style="color:#28a745;text-decoration:none;font-weight:bold;">' . $safeSupportUrl . '</a></td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p style="font-size:15px;color:#333;">Witamy ponownie w serwisie i życzymy udanej gry! 🏆</p>
|
|
|
|
<hr style="border:none;border-top:1px solid #eee;margin:28px 0;">
|
|
<p style="font-size:13px;color:#aaa;text-align:center;margin:0;">Wiadomość wygenerowana automatycznie — prosimy nie odpowiadać bezpośrednio na ten e-mail.<br>TOGETHERE GAMES • <a href="https://togethere.cloud" style="color:#aaa;">togethere.cloud</a></p>
|
|
</td></tr></table>
|
|
</td></tr></table>
|
|
</body></html>';
|
|
|
|
$emailSent = (bool)sendEmailSMTP($targetEmail, 'Status konta - Wspolnie', $emailBody);
|
|
if (!$emailSent) {
|
|
// Awaryjnie wyślij przez mail() niezależnie od helpera SMTP.
|
|
$headers = "MIME-Version: 1.0\r\n";
|
|
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
|
|
$headers .= "From: TOGETHERE GAMES <noreply@togethere.cloud>\r\n";
|
|
$headers .= "Reply-To: " . $supportEmail . "\r\n";
|
|
|
|
$emailSent = @mail($targetEmail, 'Status konta - Wspolnie', $emailBody, $headers);
|
|
if (!$emailSent) {
|
|
$emailError = 'Wysyłka nie powiodła się (SMTP + mail). Sprawdź smtp_debug.log i konfigurację serwera poczty.';
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
$emailError = $e->getMessage();
|
|
}
|
|
|
|
$msg = 'Konto użytkownika zostało odwieszone.';
|
|
if (!$emailSent) {
|
|
$msg .= ' ⚠️ Email NIE został wysłany: ' . $emailError;
|
|
}
|
|
admin_json_response(['success' => true, 'message' => $msg, 'email_sent' => $emailSent]);
|