This commit is contained in:
Ty Clifford
2026-06-24 09:19:08 -04:00
commit 5f7c62f8a7
151 changed files with 18705 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
<?php
/**
* admanager/save.php — Authenticated ad CRUD API
* ─────────────────────────────────────────────────────────────────────────────
* All requests require the X-Ad-Key header matching ADS_ADMIN_KEY in config.php.
*
* POST save.php body: ad object → create
* PUT save.php?id=<id> body: partial ad fields → update
* DELETE save.php?id=<id> → delete
* POST save.php?action=reorder body: {ids:[…]} → reorder
* POST save.php?action=toggle body: {id,active} → toggle active
*/
require_once __DIR__ . '/config.php';
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
function ad_ok(mixed $data, int $code = 200): never {
http_response_code($code);
echo json_encode(['ok' => true, 'data' => $data], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
exit;
}
function ad_err(string $msg, int $code = 400): never {
http_response_code($code);
echo json_encode(['ok' => false, 'error' => $msg]);
exit;
}
// ── Auth ──────────────────────────────────────────────────────────────────────
$supplied = trim($_SERVER['HTTP_X_AD_KEY'] ?? $_SERVER['HTTP_X_Ad_Key'] ?? ($_GET['ad_key'] ?? ''));
if ($supplied === '') ad_err('Missing X-Ad-Key header.', 401);
if (!hash_equals(ADS_ADMIN_KEY, $supplied)) ad_err('Invalid key.', 403);
// ── Data helpers ──────────────────────────────────────────────────────────────
function load_ads(): array {
if (!file_exists(ADS_JSON_PATH)) return [];
$raw = json_decode(file_get_contents(ADS_JSON_PATH), true);
return is_array($raw) ? $raw : [];
}
function save_ads(array $ads): void {
$json = json_encode(array_values($ads), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
$tmp = ADS_JSON_PATH . '.tmp.' . getmypid();
if (file_put_contents($tmp, $json, LOCK_EX) === false) ad_err('Write failed.', 500);
if (!rename($tmp, ADS_JSON_PATH)) { @unlink($tmp); ad_err('Rename failed.', 500); }
}
function find_ad(array $ads, string $id): int|false {
foreach ($ads as $i => $a) { if (($a['id'] ?? '') === $id) return $i; }
return false;
}
function new_id(array $ads): string {
$existing = array_column($ads, 'id');
do { $id = 'ad_' . strtolower(bin2hex(random_bytes(4))); } while (in_array($id, $existing));
return $id;
}
function read_body(): array {
$raw = file_get_contents('php://input');
if (!$raw) return [];
$d = json_decode($raw, true);
if (!is_array($d)) ad_err('Body must be valid JSON.');
return $d;
}
function sanitise_ad(array $in, bool $require_fields = true): array {
$errors = [];
if ($require_fields) {
if (trim($in['title'] ?? '') === '') $errors[] = '"title" is required.';
if (trim($in['placement'] ?? '') === '') $errors[] = '"placement" is required.';
$valid_placements = array_keys(unserialize(ADS_PLACEMENTS));
if (!in_array($in['placement'] ?? '', $valid_placements)) $errors[] = 'Invalid placement.';
}
if ($errors) ad_err(implode(' ', $errors));
$out = [];
if (isset($in['title'])) $out['title'] = trim((string)$in['title']);
if (isset($in['placement'])) $out['placement'] = trim((string)$in['placement']);
if (isset($in['click_url'])) $out['click_url'] = trim((string)$in['click_url']);
if (isset($in['active'])) $out['active'] = (bool)$in['active'];
if (isset($in['note'])) $out['note'] = trim((string)$in['note']);
// sizes: array of {size_key, media_url, media_type, custom_w, custom_h}
if (isset($in['sizes']) && is_array($in['sizes'])) {
$clean_sizes = [];
foreach ($in['sizes'] as $s) {
if (empty($s['size_key']) || empty($s['media_url'])) continue;
$clean_sizes[] = [
'size_key' => trim((string)$s['size_key']),
'media_url' => trim((string)$s['media_url']),
'media_type' => in_array($s['media_type'] ?? '', ['image','video']) ? $s['media_type'] : 'image',
'custom_w' => isset($s['custom_w']) ? (int)$s['custom_w'] : null,
'custom_h' => isset($s['custom_h']) ? (int)$s['custom_h'] : null,
];
}
$out['sizes'] = $clean_sizes;
}
return $out;
}
// ── Routing ───────────────────────────────────────────────────────────────────
$method = $_SERVER['REQUEST_METHOD'];
$id = trim($_GET['id'] ?? '');
$action = trim($_GET['action'] ?? '');
// Toggle active
if ($method === 'POST' && $action === 'toggle') {
$body = read_body();
$tid = trim((string)($body['id'] ?? ''));
$ads = load_ads();
$idx = find_ad($ads, $tid);
if ($idx === false) ad_err("Ad \"$tid\" not found.", 404);
$ads[$idx]['active'] = (bool)($body['active'] ?? !$ads[$idx]['active']);
save_ads($ads);
ad_ok($ads[$idx]);
}
// Reorder
if ($method === 'POST' && $action === 'reorder') {
$body = read_body();
if (!isset($body['ids']) || !is_array($body['ids'])) ad_err('"ids" required.');
$ads = load_ads();
$indexed = [];
foreach ($ads as $a) $indexed[$a['id']] = $a;
$reordered = [];
foreach ($body['ids'] as $rid) {
if (!isset($indexed[$rid])) ad_err("Unknown id \"$rid\".");
$reordered[] = $indexed[$rid];
unset($indexed[$rid]);
}
foreach ($indexed as $a) $reordered[] = $a;
save_ads($reordered);
ad_ok(['reordered' => count($reordered)]);
}
// Create
if ($method === 'POST') {
$body = read_body();
$clean = sanitise_ad($body, true);
$ads = load_ads();
$ad = array_merge([
'id' => new_id($ads),
'title' => '',
'placement' => 'above',
'click_url' => '',
'active' => true,
'note' => '',
'sizes' => [],
'created' => (new DateTime('now', new DateTimeZone('UTC')))->format(DateTime::ATOM),
], $clean);
array_unshift($ads, $ad);
save_ads($ads);
ad_ok($ad, 201);
}
// Update
if ($method === 'PUT') {
if ($id === '') ad_err('?id= required for PUT.');
$ads = load_ads();
$idx = find_ad($ads, $id);
if ($idx === false) ad_err("Ad \"$id\" not found.", 404);
$body = read_body();
$clean = sanitise_ad($body, false);
$ads[$idx] = array_merge($ads[$idx], $clean);
$ads[$idx]['id'] = $id;
save_ads($ads);
ad_ok($ads[$idx]);
}
// Delete
if ($method === 'DELETE') {
if ($id === '') ad_err('?id= required for DELETE.');
$ads = load_ads();
$idx = find_ad($ads, $id);
if ($idx === false) ad_err("Ad \"$id\" not found.", 404);
$deleted = $ads[$idx];
array_splice($ads, $idx, 1);
save_ads($ads);
ad_ok($deleted);
}
ad_err('Method not allowed.', 405);