schreiben jetzt per Ajax

This commit is contained in:
2026-05-06 19:51:03 +02:00
parent 3813d0e976
commit bd1f34845e
2 changed files with 230 additions and 114 deletions
+140
View File
@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
session_start();
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode([
'success' => false,
'message' => 'Nur POST ist erlaubt.',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$sessionToken = isset($_SESSION['observation_tip_insert_token']) && is_string($_SESSION['observation_tip_insert_token'])
? $_SESSION['observation_tip_insert_token']
: '';
$submittedToken = isset($_POST['observation_tip_insert_token']) ? (string) $_POST['observation_tip_insert_token'] : '';
if ($sessionToken === '' || $submittedToken === '' || !hash_equals($sessionToken, $submittedToken)) {
http_response_code(403);
echo json_encode([
'success' => false,
'message' => 'Sicherheitspruefung fehlgeschlagen. Bitte die Seite neu laden und erneut versuchen.',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$payloadJson = isset($_POST['observation_tip_payload']) ? (string) $_POST['observation_tip_payload'] : '';
$rows = json_decode($payloadJson, true);
if (!is_array($rows)) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Ungueltige Ereignisdaten empfangen.',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
$normalized = [];
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$moment = trim((string) ($row['moment'] ?? ''));
$tip = trim((string) ($row['tip'] ?? ''));
if ($moment === '' || $tip === '') {
continue;
}
$normalized[] = [
'moment' => $moment,
'tip' => $tip,
];
}
if ($normalized === []) {
echo json_encode([
'success' => false,
'message' => 'Keine gueltigen Ereignisse zum Eintragen vorhanden.',
'inserted' => 0,
'skipped' => 0,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
try {
$dbConfig = require __DIR__ . '/../../config/observationtips_database.php';
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
$dbConfig['host'],
$dbConfig['dbname'],
$dbConfig['charset'] ?? 'utf8mb4'
);
$pdo = new PDO(
$dsn,
$dbConfig['user'],
$dbConfig['pass'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$checkStmt = $pdo->prepare(
'SELECT 1 FROM `astro_sonobs_observationtips` WHERE `moment` = :moment AND `tip` = :tip LIMIT 1'
);
$insertStmt = $pdo->prepare(
'INSERT INTO `astro_sonobs_observationtips` (`moment`, `tip`) VALUES (:moment, :tip)'
);
$inserted = 0;
$skipped = 0;
$pdo->beginTransaction();
try {
foreach ($normalized as $row) {
$checkStmt->execute([
':moment' => $row['moment'],
':tip' => $row['tip'],
]);
if ($checkStmt->fetchColumn()) {
$skipped++;
continue;
}
$insertStmt->execute([
':moment' => $row['moment'],
':tip' => $row['tip'],
]);
$inserted++;
}
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}
echo json_encode([
'success' => true,
'message' => sprintf('%d Ereignisse eingetragen, %d bereits vorhanden.', $inserted, $skipped),
'inserted' => $inserted,
'skipped' => $skipped,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Eintragen in `astro_sonobs_observationtips` fehlgeschlagen: ' . $e->getMessage(),
'inserted' => 0,
'skipped' => 0,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
+90 -114
View File
@@ -257,11 +257,12 @@ $favoriteCometEvents = [];
$seasonChanges = [];
$timeChanges = [];
$monthEventList = [];
$observationTipInsertResult = null;
$starDataError = null;
$monthNarrativeText = '';
$observationTipInsertSql = '';
$observationTipCalendarIcs = '';
$observationTipInsertPayloadJson = '[]';
$observationTipInsertEndpoint = $publicBasePath . 'ajax/insert_observationtips.php';
$chartConfig = null;
$chartConfigJson = 'null';
$starDataJson = '[]';
@@ -635,62 +636,6 @@ if (!function_exists('normalizeObservationTipEvents')) {
}
}
if (!function_exists('insertObservationTipEvents')) {
function insertObservationTipEvents(PDO $pdo, array $events, DateTimeZone $timezone): array
{
$normalized = normalizeObservationTipEvents($events, $timezone);
if ($normalized === []) {
return [
'inserted' => 0,
'skipped' => 0,
];
}
$checkStmt = $pdo->prepare(
'SELECT 1 FROM `astro_sonobs_observationtips` WHERE `moment` = :moment AND `tip` = :tip LIMIT 1'
);
$insertStmt = $pdo->prepare(
'INSERT INTO `astro_sonobs_observationtips` (`moment`, `tip`) VALUES (:moment, :tip)'
);
$inserted = 0;
$skipped = 0;
$pdo->beginTransaction();
try {
foreach ($normalized as $row) {
$checkStmt->execute([
':moment' => $row['moment'],
':tip' => $row['tip'],
]);
if ($checkStmt->fetchColumn()) {
$skipped++;
continue;
}
$insertStmt->execute([
':moment' => $row['moment'],
':tip' => $row['tip'],
]);
$inserted++;
}
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}
return [
'inserted' => $inserted,
'skipped' => $skipped,
];
}
}
if (!function_exists('escapeIcalText')) {
function escapeIcalText(string $value): string
{
@@ -2490,56 +2435,10 @@ $monthNarrativeText = monthForecastBuildNarrativeText($monthEventList, $selected
$observationTipInsertSql = buildObservationTipInsertSql($monthEventList, new DateTimeZone($chartTimezone));
$observationTipCalendarIcs = buildObservationTipCalendarIcs($monthEventList, new DateTimeZone($chartTimezone));
if (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& isset($_POST['insert_observation_tips'])
&& $_POST['insert_observation_tips'] === '1'
) {
$submittedToken = isset($_POST['observation_tip_insert_token']) ? (string) $_POST['observation_tip_insert_token'] : '';
if (!hash_equals($observationTipInsertToken, $submittedToken)) {
$observationTipInsertResult = [
'success' => false,
'message' => 'Sicherheitspruefung fehlgeschlagen. Bitte die Seite neu laden und erneut versuchen.',
];
} else {
try {
$observationTipDbConfig = require __DIR__ . '/../config/observationtips_database.php';
$observationTipDsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
$observationTipDbConfig['host'],
$observationTipDbConfig['dbname'],
$observationTipDbConfig['charset'] ?? 'utf8mb4'
);
$observationTipPdo = new PDO(
$observationTipDsn,
$observationTipDbConfig['user'],
$observationTipDbConfig['pass'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$observationTipInsertResult = insertObservationTipEvents(
$observationTipPdo,
$monthEventList,
new DateTimeZone($chartTimezone)
);
$observationTipInsertResult['success'] = true;
$observationTipInsertResult['message'] = sprintf(
'%d Ereignisse eingetragen, %d bereits vorhanden.',
(int) $observationTipInsertResult['inserted'],
(int) $observationTipInsertResult['skipped']
);
} catch (Throwable $e) {
$observationTipInsertResult = [
'success' => false,
'message' => 'Eintragen in `astro_sonobs_observationtips` fehlgeschlagen: ' . $e->getMessage(),
];
}
}
}
$observationTipInsertPayloadJson = json_encode(
normalizeObservationTipEvents($monthEventList, new DateTimeZone($chartTimezone)),
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
) ?: '[]';
$chartConfig = [
'monthName' => $months[$selectedMonth],
@@ -2768,10 +2667,11 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
</div>
<div class="monatskarte-export">
<?php if ($monthEventList): ?>
<form method="post" class="monatskarte-inline-form">
<form method="post" action="<?= htmlspecialchars($observationTipInsertEndpoint, ENT_QUOTES, 'UTF-8') ?>" class="monatskarte-inline-form" id="observationTipInsertForm">
<input type="hidden" name="insert_observation_tips" value="1">
<input type="hidden" name="observation_tip_insert_token" value="<?= htmlspecialchars($observationTipInsertToken, ENT_QUOTES, 'UTF-8') ?>">
<button type="submit" class="monatskarte-export-button">
<input type="hidden" name="observation_tip_payload" value="<?= htmlspecialchars($observationTipInsertPayloadJson, ENT_QUOTES, 'UTF-8') ?>">
<button type="submit" class="monatskarte-export-button" id="observationTipInsertButton">
Direkt in Datenbank eintragen
</button>
</form>
@@ -2790,11 +2690,7 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
</div>
<div class="monatskarte-footer">
<?php if (is_array($observationTipInsertResult)): ?>
<p class="monatskarte-insert-status <?= !empty($observationTipInsertResult['success']) ? 'is-success' : 'is-error' ?>">
<?= htmlspecialchars((string) ($observationTipInsertResult['message'] ?? ''), ENT_QUOTES, 'UTF-8') ?>
</p>
<?php endif; ?>
<p class="monatskarte-insert-status" id="observationTipInsertStatus" role="status" aria-live="polite" hidden></p>
<?php if ($observationTipInsertSql !== ''): ?>
<div class="monatskarte-sql-export" id="observationTipSqlPanel" hidden>
<label class="monatskarte-sql-label" for="observationTipSqlOutput">SQL für `astro_sonobs_observationtips`</label>
@@ -2869,6 +2765,11 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
border: 1px solid #efc0c0;
}
.monatskarte-export-button.is-success {
background: rgba(36, 104, 62, 0.92);
border-color: rgba(141, 214, 167, 0.82);
}
.monatsvorhersage-shell {
display: grid;
gap: 18px;
@@ -3475,12 +3376,87 @@ function monthChartCometRaDec(comet, date) {
}
(function () {
const insertForm = document.getElementById('observationTipInsertForm');
const insertButton = document.getElementById('observationTipInsertButton');
let insertStatus = document.getElementById('observationTipInsertStatus');
const toggleButton = document.getElementById('toggleObservationTipSql');
const copyButton = document.getElementById('copyObservationTipSql');
const icalButton = document.getElementById('downloadObservationTipIcal');
const panel = document.getElementById('observationTipSqlPanel');
const output = document.getElementById('observationTipSqlOutput');
if (insertForm && insertButton) {
insertForm.addEventListener('submit', async function (event) {
event.preventDefault();
const originalLabel = insertButton.textContent;
insertButton.classList.remove('is-success');
insertButton.disabled = true;
insertButton.textContent = 'Trage ein...';
try {
const response = await fetch(insertForm.action, {
method: 'POST',
body: new FormData(insertForm),
headers: {
'X-Requested-With': 'fetch'
}
});
const payload = await response.json();
if (!insertStatus) {
insertStatus = document.createElement('p');
insertStatus.id = 'observationTipInsertStatus';
insertStatus.setAttribute('role', 'status');
insertStatus.setAttribute('aria-live', 'polite');
insertStatus.className = 'monatskarte-insert-status';
const footer = insertForm.closest('.monatskarte-card')?.querySelector('.monatskarte-footer');
if (footer) {
footer.insertBefore(insertStatus, footer.firstChild);
}
}
if (insertStatus) {
insertStatus.hidden = false;
insertStatus.className = 'monatskarte-insert-status ' + (payload && payload.success ? 'is-success' : 'is-error');
insertStatus.textContent = payload && payload.message ? payload.message : 'Unbekannte Rueckmeldung vom Server.';
}
if (payload && payload.success) {
insertButton.classList.add('is-success');
insertButton.textContent = 'Eingetragen';
window.setTimeout(function () {
insertButton.classList.remove('is-success');
insertButton.textContent = originalLabel;
}, 1800);
return;
}
} catch (error) {
if (!insertStatus) {
insertStatus = document.createElement('p');
insertStatus.id = 'observationTipInsertStatus';
insertStatus.setAttribute('role', 'status');
insertStatus.setAttribute('aria-live', 'polite');
insertStatus.className = 'monatskarte-insert-status is-error';
const footer = insertForm.closest('.monatskarte-card')?.querySelector('.monatskarte-footer');
if (footer) {
footer.insertBefore(insertStatus, footer.firstChild);
}
}
if (insertStatus) {
insertStatus.hidden = false;
insertStatus.className = 'monatskarte-insert-status is-error';
insertStatus.textContent = 'Eintragen fehlgeschlagen. Bitte erneut versuchen.';
}
} finally {
insertButton.disabled = false;
if (!insertButton.classList.contains('is-success')) {
insertButton.textContent = originalLabel;
}
}
});
}
if (toggleButton && panel) {
toggleButton.addEventListener('click', function () {
const isHidden = panel.hasAttribute('hidden');