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'; ?>

Выполнить команду


Для локальной разработки. Не используйте в интернете!