v3.1.1
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user