Raiju/admin/api/events.php
2026-10-25 06:05:42 +03:00

339 lines
11 KiB
PHP

<?php
if (!defined('RAIJU_ADMIN_DIR')) { http_response_code(404); exit; }
// Authenticated dispatcher supplies shared helpers and request context.
if (!function_exists('raijuEventsBackup')) {
/** Copy the current events file into the backup folder and drop the oldest copies. */
function raijuEventsBackup(): void {
if (!is_file(EVENTS_PATH)) {
return;
}
copy(EVENTS_PATH, BACKUP_PATH . 'events_' . date('Y-m-d_His') . '.json');
$backups = glob(BACKUP_PATH . 'events_*.json') ?: [];
usort($backups, function($a, $b) {
return filemtime($b) - filemtime($a);
});
while (count($backups) > MAX_BACKUPS) {
unlink(array_pop($backups));
}
}
/**
* One locked read-modify-write of events.json. The callback edits the data by
* reference and returns null to write, or an error message to abort.
*
* @return true|string|false true = written, string = callback error, false = storage failure
*/
function raijuEventsMutate(callable $change, bool $backup = false) {
$error = null;
$ok = raijuJsonUpdate(EVENTS_PATH, function (array &$data) use ($change, $backup, &$error) {
if (!isset($data['events']) || !is_array($data['events'])) {
$data['events'] = [];
}
$error = $change($data);
if ($error !== null) {
return false;
}
if ($backup) {
raijuEventsBackup();
}
return true;
}, ['events' => []]);
if ($error !== null) {
return $error;
}
return $ok;
}
}
switch ($action) {
case 'load-events':
if (!file_exists(EVENTS_PATH)) {
jsonResponse(true, ['events' => [], 'lastModified' => null]);
}
$content = json_decode(file_get_contents(EVENTS_PATH), true);
jsonResponse(true, $content);
break;
case 'save-event':
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$eventData = $_POST['event'] ?? '';
$event = json_decode($eventData, true);
if (!is_array($event)) {
jsonResponse(false, null, 'Invalid JSON format');
}
// Validate: date required, title in at least one language required
$defaultLang = defined('SITE_LANG_DEFAULT') ? SITE_LANG_DEFAULT : 'en';
$hasTitle = false;
if (!empty($event['title']) && is_array($event['title'])) {
foreach ($event['title'] as $t) {
if (!empty($t)) { $hasTitle = true; break; }
}
}
if (empty($event['date']) || !$hasTitle) {
jsonResponse(false, null, 'Date and title are required');
}
if (empty($event['id'])) {
// Use default language title for ID, fallback to first available
$titleForId = $event['title'][$defaultLang] ?? reset($event['title']);
$event['id'] = $event['date'] . '-' . preg_replace('/[^a-z0-9-]/', '', strtolower(str_replace(' ', '-', $titleForId)));
}
$found = false;
$result = raijuEventsMutate(function (array &$data) use ($event, &$found) {
foreach ($data['events'] as $index => $existing) {
if (($existing['id'] ?? null) === $event['id']) {
$data['events'][$index] = $event;
$found = true;
break;
}
}
if (!$found) {
$data['events'][] = $event;
}
$data['lastModified'] = date('c');
return null;
}, true);
if ($result !== true) {
jsonResponse(false, null, 'Error saving');
}
jsonResponse(true, ['id' => $event['id']], $found ? 'Event updated' : 'Event created');
break;
case 'delete-event':
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$eventId = $_POST['id'] ?? '';
if (empty($eventId)) {
jsonResponse(false, null, 'Event ID missing');
}
if (!file_exists(EVENTS_PATH)) {
jsonResponse(false, null, 'No events found');
}
$result = raijuEventsMutate(function (array &$data) use ($eventId) {
if (!isset($data['trash']) || !is_array($data['trash'])) {
$data['trash'] = [];
}
// Move event to trash array (instead of deleting)
$movedEvent = null;
foreach ($data['events'] as $idx => $existing) {
if (($existing['id'] ?? null) === $eventId) {
$movedEvent = $existing;
array_splice($data['events'], $idx, 1);
break;
}
}
if ($movedEvent === null) {
return 'Event not found';
}
$data['trash'][] = [
'event' => $movedEvent,
'deletedAt' => date('c'),
];
$data['lastModified'] = date('c');
return null;
}, true);
if ($result !== true) {
jsonResponse(false, null, is_string($result) ? $result : 'Error moving event to trash');
}
jsonResponse(true, null, 'Event moved to trash');
break;
case 'list-events-trash':
if (!file_exists(EVENTS_PATH)) {
jsonResponse(true, []);
}
$data = json_decode(file_get_contents(EVENTS_PATH), true);
$trash = $data['trash'] ?? [];
// Sort newest first
usort($trash, function($a, $b) {
return strcmp($b['deletedAt'] ?? '', $a['deletedAt'] ?? '');
});
jsonResponse(true, $trash);
break;
case 'restore-event':
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$eventId = $_POST['id'] ?? '';
if (empty($eventId)) {
jsonResponse(false, null, 'Event ID missing');
}
if (!file_exists(EVENTS_PATH)) {
jsonResponse(false, null, 'No events found');
}
$restored = null;
$result = raijuEventsMutate(function (array &$data) use ($eventId, &$restored) {
if (!isset($data['trash']) || !is_array($data['trash'])) {
return 'Event not in trash';
}
foreach ($data['trash'] as $idx => $item) {
if (($item['event']['id'] ?? null) === $eventId) {
$restored = $item['event'];
array_splice($data['trash'], $idx, 1);
break;
}
}
if ($restored === null) {
return 'Event not found in trash';
}
// Avoid id collision: if restored id already exists, append a suffix
$existingIds = array_map(function($e) { return $e['id'] ?? ''; }, $data['events']);
if (in_array($restored['id'], $existingIds, true)) {
$restored['id'] = $restored['id'] . '-restored-' . time();
}
$data['events'][] = $restored;
$data['lastModified'] = date('c');
return null;
});
if ($result !== true) {
jsonResponse(false, null, is_string($result) ? $result : 'Error restoring event');
}
jsonResponse(true, ['id' => $restored['id']], 'Event restored');
break;
case 'delete-event-permanent':
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$eventId = $_POST['id'] ?? '';
if (empty($eventId)) {
jsonResponse(false, null, 'Event ID missing');
}
if (!file_exists(EVENTS_PATH)) {
jsonResponse(false, null, 'No events found');
}
$result = raijuEventsMutate(function (array &$data) use ($eventId) {
if (!isset($data['trash']) || !is_array($data['trash'])) {
return 'Event not in trash';
}
foreach ($data['trash'] as $idx => $item) {
if (($item['event']['id'] ?? null) === $eventId) {
array_splice($data['trash'], $idx, 1);
return null;
}
}
return 'Event not found in trash';
});
if ($result !== true) {
jsonResponse(false, null, is_string($result) ? $result : 'Error deleting event permanently');
}
jsonResponse(true, null, 'Event permanently deleted');
break;
case 'empty-events-trash':
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
if (!file_exists(EVENTS_PATH)) {
jsonResponse(true, null, 'Trash is empty');
}
$result = raijuEventsMutate(function (array &$data) {
$data['trash'] = [];
return null;
});
if ($result !== true) {
jsonResponse(false, null, 'Error emptying trash');
}
jsonResponse(true, null, 'Events trash emptied');
break;
case 'toggle-event-visibility':
if (!validateCsrfToken()) {
jsonResponse(false, null, 'Invalid CSRF token');
}
$eventId = $_POST['id'] ?? '';
if (empty($eventId)) {
jsonResponse(false, null, 'Event ID missing');
}
if (!file_exists(EVENTS_PATH)) {
jsonResponse(false, null, 'No events found');
}
$nowHidden = false;
$result = raijuEventsMutate(function (array &$data) use ($eventId, &$nowHidden) {
foreach ($data['events'] as $index => $existing) {
if (($existing['id'] ?? null) === $eventId) {
$nowHidden = empty($existing['hidden']);
if ($nowHidden) {
$data['events'][$index]['hidden'] = true;
} else {
unset($data['events'][$index]['hidden']);
}
$data['lastModified'] = date('c');
return null;
}
}
return 'Event not found';
});
if ($result !== true) {
jsonResponse(false, null, is_string($result) ? $result : 'Error saving');
}
jsonResponse(true, ['hidden' => $nowHidden], $nowHidden ? 'Event hidden' : 'Event visible');
break;
case 'load-event':
$eventId = $_GET['id'] ?? '';
if (empty($eventId)) {
jsonResponse(false, null, 'Event ID missing');
}
if (!file_exists(EVENTS_PATH)) {
jsonResponse(false, null, 'No events found');
}
$data = json_decode(file_get_contents(EVENTS_PATH), true);
foreach ($data['events'] as $event) {
if ($event['id'] === $eventId) {
jsonResponse(true, $event);
}
}
jsonResponse(false, null, 'Event not found');
break;
// ============================================================
// IMAGE MANAGEMENT
// ============================================================
}