Your IP : 216.73.217.78


Current Path : /home/seto/testhive.pm/admin/
Upload File :
Current File : /home/seto/testhive.pm/admin/campaign-edit.php

<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/redis.php';
require_once __DIR__ . '/_layout.php';

function tip(string $html): string {
    $style = 'display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;'
           . 'border-radius:50%;background:#6c757d;color:#fff;font-size:11px;font-weight:600;'
           . 'cursor:default;margin-left:6px;vertical-align:middle;user-select:none';
    return '<span tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-html="true"'
         . ' data-bs-placement="right" data-bs-content="<div class=\'small\'>' . $html . '</div>"'
         . ' style="' . $style . '">?</span>';
}

$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$campaign = null;
$existingTexts = '';

if ($id) {
    $stmt = db()->prepare("SELECT * FROM campaigns WHERE id=?");
    $stmt->execute([$id]);
    $campaign = $stmt->fetch();
    if (!$campaign) { header('Location: /admin/campaigns.php'); exit; }

    $links = db()->prepare("SELECT weight, text FROM links WHERE campaign_id=? ORDER BY id");
    $links->execute([$id]);
    $lines = [];
    foreach ($links->fetchAll() as $lnk) {
        $w = (float)$lnk['weight'];
        $lines[] = ($w != 1 ? $w . '|' : '') . $lnk['text'];
    }
    $existingTexts = implode("\n", $lines);
}

$errors = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name           = trim($_POST['name']            ?? '');
    $targetUrl      = trim($_POST['target_url']      ?? '');
    $weight         = (($_POST['weight']         ?? '') !== '') ? (float)$_POST['weight']         : 1.0;
    $coveragePct    = (($_POST['coverage_percent'] ?? '') !== '') ? (float)$_POST['coverage_percent'] : 50.0;
    $rotationDays   = (($_POST['rotation_days']   ?? '') !== '') ? (int)$_POST['rotation_days']   : 14;
    $status         = in_array($_POST['status'] ?? '', ['active','paused']) ? $_POST['status'] : 'active';
    $textsRaw       = trim($_POST['texts'] ?? '');

    if ($name === '')      $errors[] = 'Укажите название';
    if ($targetUrl === '') $errors[] = 'Укажите target URL';

    // Parse lines: [weight|]#a#...#/a#
    $lines = array_filter(array_map('trim', explode("\n", $textsRaw)));
    $validLinks = [];
    foreach ($lines as $line) {
        $lineWeight = 1.0;
        $text = $line;
        // Optional weight prefix: "2|..." or "1.5|..."
        if (preg_match('/^(\d+(?:\.\d+)?)\|(.+)$/u', $line, $m)) {
            $lineWeight = max(0.01, (float)$m[1]);
            $text = trim($m[2]);
        }
        if (preg_match('/#a#.+?#\/a#/u', $text)) {
            $validLinks[] = ['weight' => $lineWeight, 'text' => $text];
        } else {
            $errors[] = 'Строка без разметки #a#: ' . htmlspecialchars(mb_substr($line, 0, 60));
        }
    }
    if (empty($validLinks)) $errors[] = 'Добавьте хотя бы одну строку с #a#анкор#/a#';

    if (empty($errors)) {
        try {
            db()->beginTransaction();
            if ($id) {
                db()->prepare(
                    "UPDATE campaigns SET name=?, target_url=?, weight=?, coverage_percent=?,
                     rotation_days=?, status=? WHERE id=?"
                )->execute([$name, $targetUrl, $weight, $coveragePct, $rotationDays, $status, $id]);
                db()->prepare("DELETE FROM impressions_daily WHERE link_id IN (SELECT id FROM links WHERE campaign_id=?)")->execute([$id]);
                db()->prepare("DELETE FROM page_assignments WHERE campaign_id=?")->execute([$id]);
                db()->prepare("DELETE FROM links WHERE campaign_id=?")->execute([$id]);
            } else {
                db()->prepare(
                    "INSERT INTO campaigns (name, target_url, weight, coverage_percent, rotation_days, status, created_at)
                     VALUES (?,?,?,?,?,?,?)"
                )->execute([$name, $targetUrl, $weight, $coveragePct, $rotationDays, $status, time()]);
                $id = (int)db()->lastInsertId();
            }
            $ins = db()->prepare("INSERT INTO links (campaign_id, text, weight) VALUES (?,?,?)");
            foreach ($validLinks as $lnk) {
                $ins->execute([$id, $lnk['text'], $lnk['weight']]);
            }
            db()->commit();
            cache_del('campaigns:active');
            header('Location: /admin/campaigns.php'); exit;
        } catch (Exception $e) {
            db()->rollBack();
            $errors[] = 'Ошибка БД: ' . $e->getMessage();
        }
    }

    $campaign = [
        'name'             => $name,
        'target_url'       => $targetUrl,
        'weight'           => $weight,
        'coverage_percent' => $coveragePct,
        'rotation_days'    => $rotationDays,
        'status'           => $status,
    ];
    $existingTexts = $textsRaw;
}

$title = $id ? 'Edit Campaign' : 'New Campaign';
layout_head($title);
?>
<?php if ($errors): ?>
<div class="alert alert-danger">
  <ul class="mb-0"><?php foreach ($errors as $e): ?><li><?= $e ?></li><?php endforeach ?></ul>
</div>
<?php endif ?>
<div class="card shadow-sm" style="max-width:700px">
<div class="card-body">
<form method="post">
  <div class="mb-3">
    <label class="form-label">Название</label>
    <input type="text" name="name" class="form-control"
           value="<?= htmlspecialchars($campaign['name'] ?? '') ?>" required>
  </div>
  <div class="mb-3">
    <label class="form-label">Target URL</label>
    <input type="url" name="target_url" class="form-control"
           value="<?= htmlspecialchars($campaign['target_url'] ?? '') ?>" required>
  </div>

  <div class="mb-3">
    <label class="form-label">Weight <?= tip('Приоритет этой кампании относительно других.&lt;br&gt;Чем выше — тем чаще страницы достаются этой кампании при распределении.&lt;hr class=&apos;my-2&apos;&gt;&lt;b&gt;Пример:&lt;/b&gt;&lt;br&gt;Кампания A: weight&nbsp;2&lt;br&gt;Кампания B: weight&nbsp;1&lt;br&gt;→ A получит ~67% страниц, B ~33%') ?></label>
    <input type="number" name="weight" class="form-control" min="0.01" step="0.01" style="max-width:180px"
           value="<?= htmlspecialchars((string)($campaign['weight'] ?? 1)) ?>">
  </div>
  <div class="mb-3">
    <label class="form-label">Coverage % <?= tip('Сколько процентов всех страниц-доноров получат ссылку этой кампании.&lt;hr class=&apos;my-2&apos;&gt;&lt;b&gt;Пример:&lt;/b&gt;&lt;br&gt;50 → ссылка появится примерно на половине страниц&lt;br&gt;100 → на всех страницах&lt;br&gt;30 → только на каждой третьей') ?></label>
    <input type="number" name="coverage_percent" class="form-control" min="0" max="100" step="1" style="max-width:180px"
           value="<?= htmlspecialchars((string)($campaign['coverage_percent'] ?? 50)) ?>">
  </div>

  <div class="row mb-3">
    <div class="col-auto">
      <label class="form-label">Rotation <?= tip('Через сколько дней ссылки на страницах обновятся.&lt;hr class=&apos;my-2&apos;&gt;После истечения срока страница получит новую ссылку при следующем обращении или при нажатии Redistribute.&lt;br&gt;&lt;b&gt;Рекомендуется: 7–30 дней&lt;/b&gt;') ?></label>
      <input type="number" name="rotation_days" class="form-control" min="1" step="1" style="width:120px"
             value="<?= htmlspecialchars((string)($campaign['rotation_days'] ?? 14)) ?>">
    </div>
    <div class="col-auto">
      <label class="form-label">Status <?= tip('Active — кампания участвует в распределении.&lt;br&gt;Paused — ссылки кампании не показываются, но assignments сохраняются.') ?></label>
      <select name="status" class="form-select" style="width:130px">
        <option value="active" <?= ($campaign['status'] ?? 'active') === 'active' ? 'selected' : '' ?>>Active</option>
        <option value="paused" <?= ($campaign['status'] ?? '') === 'paused' ? 'selected' : '' ?>>Paused</option>
      </select>
    </div>
  </div>

  <div class="mb-1">
    <label class="form-label fw-semibold">
      Тексты со ссылками
      <?= tip('&lt;b&gt;Формат строки:&lt;/b&gt;&lt;br&gt;&lt;code&gt;[вес|]#a#анкор#/a# текст&lt;/code&gt;&lt;hr class=&apos;my-2&apos;&gt;&lt;b&gt;Примеры:&lt;/b&gt;&lt;br&gt;&lt;code&gt;#a#Купить ролики#/a# по цене&lt;/code&gt;&lt;br&gt;&lt;span class=&apos;text-muted&apos;&gt;↑ вес 1 (по умолчанию)&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;code&gt;2|#a#роликовые коньки#/a# онлайн&lt;/code&gt;&lt;br&gt;&lt;span class=&apos;text-muted&apos;&gt;↑ вес 2 — вдвое чаще&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;code&gt;0.5|#a#www.site.com#/a#&lt;/code&gt;&lt;br&gt;&lt;span class=&apos;text-muted&apos;&gt;↑ вес 0.5 — вдвое реже&lt;/span&gt;') ?>
    </label>
    <textarea name="texts" class="form-control font-monospace" rows="10"
              placeholder="#a#анкор#/a# текст"><?= htmlspecialchars($existingTexts) ?></textarea>
  </div>

<script>
document.addEventListener('DOMContentLoaded', function () {
  document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
    new bootstrap.Popover(el, { sanitize: false });
  });
});
</script>

  <div class="mt-3 d-flex gap-2">
    <button class="btn btn-primary">Сохранить</button>
    <a href="/admin/campaigns.php" class="btn btn-outline-secondary">Отмена</a>
  </div>
</form>
</div>
</div>
<?php layout_foot() ?>