| Current Path : /home/seto/testhive.pm/admin/ |
| 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('Приоритет этой кампании относительно других.<br>Чем выше — тем чаще страницы достаются этой кампании при распределении.<hr class='my-2'><b>Пример:</b><br>Кампания A: weight 2<br>Кампания B: weight 1<br>→ 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('Сколько процентов всех страниц-доноров получат ссылку этой кампании.<hr class='my-2'><b>Пример:</b><br>50 → ссылка появится примерно на половине страниц<br>100 → на всех страницах<br>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('Через сколько дней ссылки на страницах обновятся.<hr class='my-2'>После истечения срока страница получит новую ссылку при следующем обращении или при нажатии Redistribute.<br><b>Рекомендуется: 7–30 дней</b>') ?></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 — кампания участвует в распределении.<br>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('<b>Формат строки:</b><br><code>[вес|]#a#анкор#/a# текст</code><hr class='my-2'><b>Примеры:</b><br><code>#a#Купить ролики#/a# по цене</code><br><span class='text-muted'>↑ вес 1 (по умолчанию)</span><br><br><code>2|#a#роликовые коньки#/a# онлайн</code><br><span class='text-muted'>↑ вес 2 — вдвое чаще</span><br><br><code>0.5|#a#www.site.com#/a#</code><br><span class='text-muted'>↑ вес 0.5 — вдвое реже</span>') ?>
</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() ?>