PHP WebShell
Текущая директория: /var/www/bitcardoApp/user/security
Просмотр файла: notifications.php
<?php
// user/security/notifications.php
require_once __DIR__ . '/../../config/bootstrap.php';
if (empty($_SESSION['user_id'])) {
header("Location: /login.php");
exit();
}
$userId = (int)$_SESSION['user_id'];
$errors = [];
$success = '';
function table_exists(mysqli $conn, string $table): bool {
$sql = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1";
if ($st = $conn->prepare($sql)) {
$st->bind_param('s', $table);
$st->execute();
$st->bind_result($c);
$st->fetch();
$st->close();
return ((int)$c) > 0;
}
return false;
}
function prefs_load(mysqli $conn, int $userId, array $keys): array {
$out = [];
foreach ($keys as $k) $out[$k] = null;
if (empty($keys)) return $out;
$placeholders = implode(',', array_fill(0, count($keys), '?'));
$sql = "SELECT preference_key, preference__value
FROM user_preferences
WHERE user_id=? AND preference_key IN ($placeholders)";
if (!($st = $conn->prepare($sql))) return $out;
$types = 'i' . str_repeat('s', count($keys));
$vals = array_merge([$userId], $keys);
$bind = [];
$bind[] = $types;
foreach ($vals as $i => $v) $bind[] = &$vals[$i];
call_user_func_array([$st, 'bind_param'], $bind);
$st->execute();
$res = $st->get_result();
if ($res) {
while ($row = $res->fetch_assoc()) {
$out[$row['preference_key']] = $row['preference__value'];
}
}
$st->close();
return $out;
}
function prefs_set(mysqli $conn, int $userId, string $key, string $value): bool {
$sql = "INSERT INTO user_preferences (user_id, preference_key, preference__value)
VALUES (?,?,?)
ON DUPLICATE KEY UPDATE preference__value=VALUES(preference__value)";
if ($st = $conn->prepare($sql)) {
$st->bind_param('iss', $userId, $key, $value);
$ok = $st->execute();
$st->close();
return (bool)$ok;
}
return false;
}
$canSave = table_exists($conn, 'user_preferences');
/**
* Define rows (alert types) and columns (channels).
* Keys stored as: <alert_key>__<channel>
*/
$alerts = [
'security_alerts' => ['label' => 'Security alerts', 'desc' => 'Login alerts, password changes, and critical account events.'],
'product_updates' => ['label' => 'Product updates', 'desc' => 'New features, maintenance notices, and service updates.'],
'price_alerts' => ['label' => 'Price alerts', 'desc' => 'Updates when tracked asset prices move significantly.'],
'offer_alerts' => ['label' => 'Offer alerts', 'desc' => 'Promotions, bonuses, and limited-time offers.'],
'marketing_messages' => ['label' => 'Marketing messages', 'desc' => 'Newsletters and promotional messages.'],
];
$channels = [
'email' => 'Email',
'sms' => 'SMS',
'app' => 'App',
];
// Defaults per cell (you can tune)
$defaults = [
// security alerts default on for all
'security_alerts__email' => 1,
'security_alerts__sms' => 1,
'security_alerts__app' => 1,
// product updates default on
'product_updates__email' => 1,
'product_updates__sms' => 1,
'product_updates__app' => 1,
// price alerts default on
'price_alerts__email' => 1,
'price_alerts__sms' => 1,
'price_alerts__app' => 1,
// offer alerts default off
'offer_alerts__email' => 0,
'offer_alerts__sms' => 0,
'offer_alerts__app' => 0,
// marketing default off
'marketing_messages__email' => 0,
'marketing_messages__sms' => 0,
'marketing_messages__app' => 0,
];
// Build all keys
$keys = [];
foreach ($alerts as $aKey => $aMeta) {
foreach ($channels as $cKey => $cLabel) {
$keys[] = "{$aKey}__{$cKey}";
}
}
// Load
$prefs = [];
if ($canSave) $prefs = prefs_load($conn, $userId, $keys);
// Normalize current state to ints 0/1
$current = [];
foreach ($keys as $k) {
$v = $prefs[$k] ?? null;
if ($v === null || $v === '') $current[$k] = (int)($defaults[$k] ?? 0);
else $current[$k] = ((string)$v === '1') ? 1 : 0;
}
// Save
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$canSave) {
$errors[] = "Preferences storage is not enabled yet (missing user_preferences table).";
} else {
$new = [];
// Read all posted checkboxes (unchecked means 0)
foreach ($alerts as $aKey => $aMeta) {
foreach ($channels as $cKey => $cLabel) {
$cellKey = "{$aKey}__{$cKey}";
$new[$cellKey] = isset($_POST[$cellKey]) ? 1 : 0;
}
// Rule: each alert type must have at least one channel enabled
// EXCEPT offer_alerts and marketing_messages (allowed to be off everywhere)
$sum = 0;
foreach ($channels as $cKey => $cLabel) {
$sum += $new["{$aKey}__{$cKey}"];
}
$allowAllOff = in_array($aKey, ['offer_alerts', 'marketing_messages'], true);
if ($sum === 0 && !$allowAllOff) {
$errors[] = $aMeta['label'] . " must be enabled for at least one channel (Email, SMS, or App).";
}
}
if (empty($errors)) {
$allOk = true;
foreach ($new as $k => $v) {
if (!prefs_set($conn, $userId, $k, $v ? '1' : '0')) $allOk = false;
}
if ($allOk) {
$success = "Notification preferences saved.";
$current = $new;
} else {
$errors[] = "Unable to save one or more preferences. Please try again.";
}
}
}
}
?>
<? include '../common/header.php'; ?>
<style>
.pref-grid th, .pref-grid td { vertical-align: middle; }
/* Column sizing */
.pref-grid .type-col { width: 55%; }
.pref-grid .chan-col { width: 15%; text-align: center; }
/* Ensure the Alert type header and cells are LEFT aligned */
.pref-grid th.type-col,
.pref-grid td.type-col {
text-align: left !important;
}
/* Make the Alert type content sit vertically centered and not push row height oddly */
.type-cell {
display: flex;
align-items: center; /* vertical center within the row */
gap: 10px;
min-height: 44px; /* keeps row consistent with switch height */
}
.type-text {
line-height: 1.2;
}
.pref-note {
color: rgba(255,255,255,.65);
font-size: .9rem;
margin-top: 4px;
}
/* Keep switch visually centered */
.pref-grid .form-check.form-switch {
display: inline-flex;
justify-content: center;
align-items: center;
margin: 0;
}
</style>
<div class="container mt-3">
<div class="row">
<? include '../common/nav.php'; ?>
<main class="col-md-9 col-lg-10 px-md-5 mb-5">
<? include '../common/page-header.php'; ?>
<div class="container my-5 px-md-5 ms-md-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<div>
<h5 class="mb-0">Notifications</h5>
<div class="text-muted small">Enable each alert type per channel.</div>
</div>
<a href="/user/security/security_privacy.php" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back
</a>
</div>
<?php if ($success): ?>
<div class="alert alert-success"><?= htmlspecialchars($success) ?></div>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<strong>Please fix the following:</strong>
<ul class="mb-0">
<?php foreach ($errors as $e): ?><li><?= htmlspecialchars($e) ?></li><?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<h6 class="section-title">Notification channels</h6>
<div class="card-soft">
<form method="post" action="">
<div class="table-responsive">
<table class="table align-middle pref-grid mb-0">
<thead>
<tr>
<th class="type-col">Alert type</th>
<th class="chan-col">Email</th>
<th class="chan-col">SMS</th>
<th class="chan-col">App</th>
</tr>
</thead>
<tbody>
<?php foreach ($alerts as $aKey => $aMeta): ?>
<tr>
<td class="type-col">
<div class="type-cell">
<div class="type-text">
<div class="fw-bold"><?= htmlspecialchars($aMeta['label']) ?></div>
<div class="pref-note text-muted"><?= htmlspecialchars($aMeta['desc']) ?></div>
</div>
</div>
</td>
<?php foreach ($channels as $cKey => $cLabel): ?>
<?php $cellKey = $aKey . '__' . $cKey; ?>
<td class="chan-col">
<div class="form-check form-switch d-inline-flex m-0">
<input
class="form-check-input"
type="checkbox"
name="<?= htmlspecialchars($cellKey) ?>"
id="<?= htmlspecialchars($cellKey) ?>"
<?= !empty($current[$cellKey]) ? 'checked' : '' ?>
<?= $canSave ? '' : 'disabled' ?>
>
</div>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-dark" <?= $canSave ? '' : 'disabled' ?>>
<i class="bi bi-save2"></i> Save changes
</button>
</div>
</form>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js"></script>
<? include '../common/footer.php'; ?>
Выполнить команду
Для локальной разработки. Не используйте в интернете!