camera - (real) remote control, brandschutz - configurable course, dashboard - vbb licensed API (a real vbb license, how cool)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/initialise.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/projects/brandschutz/util/brandschutz_helpers.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/projects/brandschutz/util/brandschutz_course.php';
|
||||
|
||||
if ((int) ($groups['Admin'] ?? 0) !== 1) {
|
||||
header("Location: {$g_httpHomePath}");
|
||||
@@ -25,6 +26,8 @@ function getProfileImageUrl_bs(array $row, string $g_profilePicturePath): string
|
||||
return '/intranet_repo/general_files/profile_pictures/default.jpg';
|
||||
}
|
||||
|
||||
$editorError = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
brandschutz_verify_csrf($_POST['csrf_token'] ?? null);
|
||||
@@ -33,59 +36,93 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
exit($exception->getMessage());
|
||||
}
|
||||
|
||||
if (($_POST['action'] ?? '') !== 'update_training_date') {
|
||||
$action = (string) ($_POST['action'] ?? '');
|
||||
if (in_array($action, ['upload_image', 'delete_image'], true)) {
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
try {
|
||||
$courseState = brandschutz_course_load($pdo);
|
||||
if ($action === 'upload_image') {
|
||||
$image = brandschutz_media_upload($_FILES['image'] ?? []);
|
||||
echo json_encode(['ok' => true, 'image' => $image], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||
} else {
|
||||
brandschutz_media_delete((string) ($_POST['file'] ?? ''), $courseState['config']);
|
||||
echo json_encode(['ok' => true], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => $exception->getMessage()], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($action === 'save_course') {
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
try {
|
||||
$postedConfig = json_decode((string) ($_POST['course_config'] ?? ''), true, 512, JSON_THROW_ON_ERROR);
|
||||
$expectedRevision = filter_var($_POST['course_revision'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
|
||||
if ($expectedRevision === false) throw new InvalidArgumentException('Ungültiger Bearbeitungsstand.');
|
||||
$newRevision = brandschutz_course_save($pdo, $postedConfig, (int) $expectedRevision, (int) $_SESSION['user_id']);
|
||||
echo json_encode(['ok' => true, 'revision' => $newRevision], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
||||
} catch (Throwable $exception) {
|
||||
http_response_code($exception instanceof RuntimeException ? 409 : 400);
|
||||
$message = $exception instanceof JsonException ? 'Die Editor-Daten konnten nicht verarbeitet werden.' : $exception->getMessage();
|
||||
echo json_encode(['ok' => false, 'error' => $message], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
exit();
|
||||
} elseif ($action === 'update_training_date') {
|
||||
$editUserId = filter_var($_POST['bs_user_id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
|
||||
$newDate = trim((string) ($_POST['bs_new_date'] ?? ''));
|
||||
if ($editUserId === false || !brandschutz_is_valid_date($newDate)) {
|
||||
http_response_code(400);
|
||||
exit('Ungültige Eingaben.');
|
||||
}
|
||||
$stmtUser = $pdo->prepare(
|
||||
'SELECT ugi.first_name, ugi.last_name, ugi.mail FROM user_general_information ugi '
|
||||
. 'INNER JOIN user_permissions up ON up.user_id = ugi.user_id '
|
||||
. 'WHERE ugi.user_id = ? AND up."Brandschutz" = 1'
|
||||
);
|
||||
$stmtUser->execute([(int) $editUserId]);
|
||||
$editedUser = $stmtUser->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$editedUser) {
|
||||
http_response_code(404);
|
||||
exit('Brandschutz-Nutzer nicht gefunden.');
|
||||
}
|
||||
$newStatus = brandschutz_training_status($newDate);
|
||||
$resetCounters = (int) $newStatus['days_until_due'] >= 0;
|
||||
$adminName = brandschutz_display_name(is_array($user) ? $user : [], 'Ein Administrator');
|
||||
$editedName = brandschutz_display_name($editedUser);
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
brandschutz_store_training_date($pdo, (int) $editUserId, $newDate, $resetCounters);
|
||||
brandschutz_log_admin_message(
|
||||
$pdo,
|
||||
$adminName,
|
||||
(string) ($editedUser['mail'] ?? ''),
|
||||
'Brandschutz-Schulungsdatum angepasst: ' . $editedName,
|
||||
$adminName . ' hat das Schulungsdatum von ' . $editedName . ' manuell auf den ' . (new DateTimeImmutable($newDate))->format('d.m.Y') . ' gesetzt.'
|
||||
);
|
||||
$pdo->commit();
|
||||
} catch (Throwable $exception) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
throw $exception;
|
||||
}
|
||||
header('Location: admin_brandschutz.php?message=5', true, 303);
|
||||
exit();
|
||||
} elseif ($action !== 'save_course') {
|
||||
http_response_code(400);
|
||||
exit('Ungültige Aktion.');
|
||||
}
|
||||
|
||||
$editUserId = filter_var($_POST['bs_user_id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
|
||||
$newDate = trim((string) ($_POST['bs_new_date'] ?? ''));
|
||||
if ($editUserId === false || !brandschutz_is_valid_date($newDate)) {
|
||||
http_response_code(400);
|
||||
exit('Ungültige Eingaben.');
|
||||
}
|
||||
|
||||
$stmtUser = $pdo->prepare(
|
||||
'SELECT ugi.first_name, ugi.last_name, ugi.mail '
|
||||
. 'FROM user_general_information ugi '
|
||||
. 'INNER JOIN user_permissions up ON up.user_id = ugi.user_id '
|
||||
. 'WHERE ugi.user_id = ? AND up."Brandschutz" = 1'
|
||||
);
|
||||
$stmtUser->execute([(int) $editUserId]);
|
||||
$editedUser = $stmtUser->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$editedUser) {
|
||||
http_response_code(404);
|
||||
exit('Brandschutz-Nutzer nicht gefunden.');
|
||||
}
|
||||
|
||||
$newStatus = brandschutz_training_status($newDate);
|
||||
$resetCounters = (int) $newStatus['days_until_due'] >= 0;
|
||||
$adminName = brandschutz_display_name(is_array($user) ? $user : [], 'Ein Administrator');
|
||||
$editedName = brandschutz_display_name($editedUser);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
brandschutz_store_training_date($pdo, (int) $editUserId, $newDate, $resetCounters);
|
||||
brandschutz_log_admin_message(
|
||||
$pdo,
|
||||
$adminName,
|
||||
(string) ($editedUser['mail'] ?? ''),
|
||||
'Brandschutz-Schulungsdatum angepasst: ' . $editedName,
|
||||
$adminName . ' hat das Schulungsdatum von ' . $editedName . ' manuell auf den '
|
||||
. (new DateTimeImmutable($newDate))->format('d.m.Y') . ' gesetzt.'
|
||||
);
|
||||
$pdo->commit();
|
||||
} catch (Throwable $exception) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
header('Location: admin_brandschutz.php?message=5', true, 303);
|
||||
exit();
|
||||
}
|
||||
|
||||
$courseState = ['config' => ['title' => 'Brandschutzunterweisung', 'settings' => [], 'chapters' => []], 'revision' => 1, 'updated_at' => null];
|
||||
try {
|
||||
$courseState = brandschutz_course_load($pdo);
|
||||
} catch (Throwable $exception) {
|
||||
$editorError = $editorError ?? ('Die Schulungskonfiguration konnte nicht geladen werden: ' . $exception->getMessage());
|
||||
}
|
||||
$courseConfig = $courseState['config'];
|
||||
$courseImages = brandschutz_media_list();
|
||||
|
||||
$sql = 'SELECT ugi.user_id, ugi.first_name, ugi.last_name, ugi.mail, '
|
||||
. 'bt.last_training_date, bt.reminder_count, bt.admin_reminder_count, '
|
||||
. 'CASE WHEN ul.user_id IS NOT NULL THEN 1 ELSE 0 END AS IstRegistriert '
|
||||
@@ -110,6 +147,12 @@ unset($bsUser);
|
||||
<head>
|
||||
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
|
||||
<link rel="stylesheet" href="/intranet_repo/projects/_central/layouts/admin_brandschutz.css">
|
||||
<script id="bsEditorData" type="application/json"><?= json_encode([
|
||||
'config' => $courseConfig,
|
||||
'revision' => (int) $courseState['revision'],
|
||||
'csrfToken' => $csrfToken,
|
||||
'images' => $courseImages,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR) ?></script>
|
||||
<script src="/intranet_repo/projects/_central/scripts/admin_brandschutz.js" defer></script>
|
||||
|
||||
</head>
|
||||
@@ -126,6 +169,51 @@ unset($bsUser);
|
||||
<div class="content" id="fullsize">
|
||||
<?php $adminActiveTab = 'brandschutz'; require __DIR__.'/_tabs.php'; ?>
|
||||
|
||||
<div class="standard_overlay bs-course-editor-overlay" id="bsCourseEditorOverlay" aria-hidden="true">
|
||||
<section class="bs-editor-shell" id="bs-course-editor" role="dialog" aria-modal="true" aria-labelledby="bsCourseEditorTitle">
|
||||
<div class="bs-editor-header">
|
||||
<div>
|
||||
<h1 id="bsCourseEditorTitle">Schulungseditor</h1>
|
||||
<p>Kapitel, Seiten, Inhalte und Prüfungsfragen zentral verwalten.</p>
|
||||
</div>
|
||||
<div class="bs-editor-header-actions">
|
||||
<button type="button" class="bs-editor-button" id="bsEditSettings">Einstellungen</button>
|
||||
<button type="button" class="bs-course-editor-close" id="bsCloseCourseEditor" aria-label="Schulungseditor schließen">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($editorError !== null): ?><div class="bs-editor-notice bs-editor-notice--error"><?= htmlspecialchars($editorError, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
|
||||
<div class="bs-editor-notice" id="bsEditorStatus" role="status" aria-live="polite" hidden></div>
|
||||
|
||||
<div class="bs-editor-grid">
|
||||
<aside class="bs-editor-panel">
|
||||
<div class="bs-editor-panel-title"><span>Kapitel</span><button type="button" id="bsAddChapter" aria-label="Kapitel hinzufügen">+ Hinzufügen</button></div>
|
||||
<div class="bs-editor-list" id="bsChapterList"></div>
|
||||
</aside>
|
||||
<aside class="bs-editor-panel">
|
||||
<div class="bs-editor-panel-title"><span>Seiten</span><button type="button" id="bsAddPage" aria-label="Seite hinzufügen">+ Hinzufügen</button></div>
|
||||
<div class="bs-editor-list" id="bsPageList"></div>
|
||||
</aside>
|
||||
<main class="bs-editor-panel bs-elements-panel">
|
||||
<div class="bs-editor-panel-title"><span id="bsElementPanelTitle">Elemente</span><button type="button" id="bsAddElement">+ Baustein</button></div>
|
||||
<div class="bs-element-list" id="bsElementList"></div>
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="standard_overlay bs-editor-overlay" id="bsEditorModal" aria-hidden="true">
|
||||
<div class="bs-editor-modal" role="dialog" aria-modal="true" aria-labelledby="bsEditorModalTitle">
|
||||
<button type="button" class="bs-editor-modal-close" id="bsEditorModalClose" aria-label="Schließen">×</button>
|
||||
<h2 id="bsEditorModalTitle">Baustein bearbeiten</h2>
|
||||
<div id="bsEditorModalBody"></div>
|
||||
<div class="bs-editor-modal-actions">
|
||||
<button type="button" class="bs-editor-button" id="bsEditorModalCancel">Abbrechen</button>
|
||||
<button type="button" class="bs-editor-button bs-editor-button--primary" id="bsEditorModalSave">Übernehmen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-users-shell">
|
||||
<div class="admin-table-card">
|
||||
<div class="admin-list-header">
|
||||
@@ -133,6 +221,7 @@ unset($bsUser);
|
||||
<span class="admin-list-title">Brandschutz-Schulungen</span>
|
||||
<div class="admin-list-hint"><?= count($bsUsers) ?> Nutzer mit Brandschutz-Rolle</div>
|
||||
</div>
|
||||
<button type="button" class="bs-editor-button bs-editor-button--primary" id="bsOpenCourseEditor">Schulungseditor öffnen</button>
|
||||
</div>
|
||||
|
||||
<table class="admin-user-table" id="bsTable">
|
||||
|
||||
Reference in New Issue
Block a user