Files
skyview.astronomiemuseum.de/public/my_favorites.php
T

1367 lines
50 KiB
PHP

<?php
declare(strict_types=1);
ini_set('display_errors', '1');
error_reporting(E_ALL);
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$config = require __DIR__ . '/../config/database.php';
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
$config['host'],
$config['dbname'],
$config['charset'] ?? 'utf8mb4'
);
$pdo = new PDO(
$dsn,
$config['user'],
$config['pass'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
$userId = (int) $_SESSION['user_id'];
$errors = [];
$successMessage = '';
$satelliteSearchResults = [];
$minorplanetSearchResults = [];
$satelliteSearchQuery = trim($_GET['sat_q'] ?? '');
$minorplanetSearchQuery = trim($_GET['mp_q'] ?? '');
$pageTitle = 'Favoriten - AstroTools';
$headerIntroPre = 'Merkliste';
$headerIntroTitle = 'Favoriten';
$headerIntroSub = 'Satelliten und Kleinplaneten zentral verwalten.';
function h(?string $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function validTimezone(string $timezone): bool
{
return in_array($timezone, timezone_identifiers_list(), true);
}
function setExclusiveFavorite(PDO $pdo, string $tableName, string $idColumn, int $userId, int $entityId): bool
{
$pdo->beginTransaction();
try {
$stmtCurrent = $pdo->prepare("
SELECT `is_favorite`
FROM `{$tableName}`
WHERE `user_id` = :user_id
AND `{$idColumn}` = :entity_id
LIMIT 1
");
$stmtCurrent->execute([
':user_id' => $userId,
':entity_id' => $entityId,
]);
$current = $stmtCurrent->fetchColumn();
if ($current === false) {
$pdo->rollBack();
return false;
}
$newValue = (int) $current === 1 ? 0 : 1;
if ($newValue === 1) {
$stmtReset = $pdo->prepare("
UPDATE `{$tableName}`
SET `is_favorite` = 0
WHERE `user_id` = :user_id
");
$stmtReset->execute([
':user_id' => $userId,
]);
}
$stmtSet = $pdo->prepare("
UPDATE `{$tableName}`
SET `is_favorite` = :is_favorite
WHERE `user_id` = :user_id
AND `{$idColumn}` = :entity_id
");
$stmtSet->execute([
':is_favorite' => $newValue,
':user_id' => $userId,
':entity_id' => $entityId,
]);
$pdo->commit();
return true;
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}
}
function countUserRows(PDO $pdo, string $tableName, int $userId): int
{
$stmtCount = $pdo->prepare("
SELECT COUNT(*)
FROM `{$tableName}`
WHERE `user_id` = :user_id
");
$stmtCount->execute([
':user_id' => $userId,
]);
return (int) $stmtCount->fetchColumn();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'add_satellite') {
$satelliteId = (int) ($_POST['satellite_id'] ?? 0);
if ($satelliteId <= 0) {
$errors[] = 'Ungültiger Satellit.';
}
if (empty($errors)) {
$stmtCheck = $pdo->prepare("
SELECT `id`
FROM `sat_satellites`
WHERE `id` = :id
LIMIT 1
");
$stmtCheck->execute([
':id' => $satelliteId,
]);
if (!$stmtCheck->fetch()) {
$errors[] = 'Der Satellit wurde nicht gefunden.';
}
}
if (empty($errors)) {
$stmtInsert = $pdo->prepare("
INSERT INTO `app_user_satellites` (
`user_id`,
`satellite_id`,
`is_favorite`,
`created_at`
) VALUES (
:user_id,
:satellite_id,
0,
NOW()
)
ON DUPLICATE KEY UPDATE
`satellite_id` = VALUES(`satellite_id`)
");
$stmtInsert->execute([
':user_id' => $userId,
':satellite_id' => $satelliteId,
]);
$successMessage = 'Der Satellit wurde hinzugefuegt.';
}
}
if ($action === 'remove_satellite') {
$satelliteId = (int) ($_POST['satellite_id'] ?? 0);
if ($satelliteId <= 0) {
$errors[] = 'Ungültiger Satellit.';
}
if (empty($errors)) {
$stmtSatellite = $pdo->prepare("
SELECT `satellite_id`, `is_favorite`
FROM `app_user_satellites`
WHERE `user_id` = :user_id
AND `satellite_id` = :satellite_id
LIMIT 1
");
$stmtSatellite->execute([
':user_id' => $userId,
':satellite_id' => $satelliteId,
]);
$satelliteRow = $stmtSatellite->fetch();
if (!$satelliteRow) {
$errors[] = 'Der Satellit ist nicht in deiner Liste vorhanden.';
}
}
if (empty($errors)) {
$satelliteCount = countUserRows($pdo, 'app_user_satellites', $userId);
if ($satelliteCount <= 1) {
$errors[] = 'Der letzte Satellit kann nicht gelöscht werden.';
}
}
if (empty($errors)) {
$pdo->beginTransaction();
try {
$stmtDelete = $pdo->prepare("
DELETE FROM `app_user_satellites`
WHERE `user_id` = :user_id
AND `satellite_id` = :satellite_id
");
$stmtDelete->execute([
':user_id' => $userId,
':satellite_id' => $satelliteId,
]);
if ((int) ($satelliteRow['is_favorite'] ?? 0) === 1) {
$stmtNextFavorite = $pdo->prepare("
SELECT `satellite_id`
FROM `app_user_satellites`
WHERE `user_id` = :user_id
ORDER BY `created_at` ASC, `satellite_id` ASC
LIMIT 1
");
$stmtNextFavorite->execute([
':user_id' => $userId,
]);
$nextSatelliteId = $stmtNextFavorite->fetchColumn();
if ($nextSatelliteId !== false) {
$stmtResetFavorites = $pdo->prepare("
UPDATE `app_user_satellites`
SET `is_favorite` = 0
WHERE `user_id` = :user_id
");
$stmtResetFavorites->execute([
':user_id' => $userId,
]);
$stmtSetFavorite = $pdo->prepare("
UPDATE `app_user_satellites`
SET `is_favorite` = 1
WHERE `user_id` = :user_id
AND `satellite_id` = :satellite_id
");
$stmtSetFavorite->execute([
':user_id' => $userId,
':satellite_id' => (int) $nextSatelliteId,
]);
}
}
$pdo->commit();
$successMessage = 'Der Satellit wurde entfernt.';
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$errors[] = 'Der Satellit konnte nicht entfernt werden.';
}
}
}
if ($action === 'toggle_satellite_favorite') {
$satelliteId = (int) ($_POST['satellite_id'] ?? 0);
if ($satelliteId <= 0) {
$errors[] = 'Ungültiger Satellit.';
}
if (empty($errors)) {
$stmtCurrentSatellite = $pdo->prepare("
SELECT `is_favorite`
FROM `app_user_satellites`
WHERE `user_id` = :user_id
AND `satellite_id` = :satellite_id
LIMIT 1
");
$stmtCurrentSatellite->execute([
':user_id' => $userId,
':satellite_id' => $satelliteId,
]);
$currentSatelliteFavorite = $stmtCurrentSatellite->fetchColumn();
if ($currentSatelliteFavorite === false) {
$errors[] = 'Der Satellit ist nicht in deiner Liste vorhanden.';
}
}
if (empty($errors)) {
$satelliteCount = countUserRows($pdo, 'app_user_satellites', $userId);
if ($satelliteCount <= 1 && (int) $currentSatelliteFavorite === 1) {
$errors[] = 'Beim letzten Satelliten kann der Favorit nicht entfernt werden.';
}
}
if (empty($errors)) {
if (!setExclusiveFavorite($pdo, 'app_user_satellites', 'satellite_id', $userId, $satelliteId)) {
$errors[] = 'Der Satellit ist nicht in deiner Liste vorhanden.';
} else {
$successMessage = 'Der Satelliten-Favorit wurde aktualisiert.';
}
}
}
if ($action === 'add_minorplanet') {
$minorplanetId = (int) ($_POST['minorplanet_id'] ?? 0);
if ($minorplanetId <= 0) {
$errors[] = 'Ungültiger Kleinplanet.';
}
if (empty($errors)) {
$stmtCheck = $pdo->prepare("
SELECT `id`
FROM `minorplanets_mpc`
WHERE `id` = :id
LIMIT 1
");
$stmtCheck->execute([
':id' => $minorplanetId,
]);
if (!$stmtCheck->fetch()) {
$errors[] = 'Der Kleinplanet wurde nicht gefunden.';
}
}
if (empty($errors)) {
$stmtInsert = $pdo->prepare("
INSERT INTO `app_user_minorplanets` (
`user_id`,
`minorplanet_id`,
`is_favorite`,
`created_at`
) VALUES (
:user_id,
:minorplanet_id,
0,
NOW()
)
ON DUPLICATE KEY UPDATE
`minorplanet_id` = VALUES(`minorplanet_id`)
");
$stmtInsert->execute([
':user_id' => $userId,
':minorplanet_id' => $minorplanetId,
]);
$successMessage = 'Der Kleinplanet wurde hinzugefuegt.';
}
}
if ($action === 'remove_minorplanet') {
$minorplanetId = (int) ($_POST['minorplanet_id'] ?? 0);
if ($minorplanetId <= 0) {
$errors[] = 'Ungültiger Kleinplanet.';
}
if (empty($errors)) {
$stmtDelete = $pdo->prepare("
DELETE FROM `app_user_minorplanets`
WHERE `user_id` = :user_id
AND `minorplanet_id` = :minorplanet_id
");
$stmtDelete->execute([
':user_id' => $userId,
':minorplanet_id' => $minorplanetId,
]);
$successMessage = 'Der Kleinplanet wurde entfernt.';
}
}
if ($action === 'toggle_minorplanet_favorite') {
$minorplanetId = (int) ($_POST['minorplanet_id'] ?? 0);
if ($minorplanetId <= 0) {
$errors[] = 'Ungültiger Kleinplanet.';
}
if (empty($errors)) {
if (!setExclusiveFavorite($pdo, 'app_user_minorplanets', 'minorplanet_id', $userId, $minorplanetId)) {
$errors[] = 'Der Kleinplanet ist nicht in deiner Liste vorhanden.';
} else {
$successMessage = 'Der Kleinplaneten-Favorit wurde aktualisiert.';
}
}
}
if ($action === 'add_location') {
$name = trim($_POST['name'] ?? '');
$latitude = trim($_POST['latitude'] ?? '');
$longitude = trim($_POST['longitude'] ?? '');
$elevation = trim($_POST['elevation'] ?? '');
$timezone = trim($_POST['timezone'] ?? 'Europe/Berlin');
$isDefault = isset($_POST['is_default']) ? 1 : 0;
if ($name === '') {
$errors[] = 'Bitte einen Namen für den Standort eingeben.';
}
if ($latitude === '' || !is_numeric($latitude)) {
$errors[] = 'Bitte einen gültigen Breitengrad eingeben.';
} else {
$lat = (float) $latitude;
if ($lat < -90 || $lat > 90) {
$errors[] = 'Der Breitengrad muss zwischen -90 und 90 liegen.';
}
}
if ($longitude === '' || !is_numeric($longitude)) {
$errors[] = 'Bitte einen gültigen Längengrad eingeben.';
} else {
$lon = (float) $longitude;
if ($lon < -180 || $lon > 180) {
$errors[] = 'Der Längengrad muss zwischen -180 und 180 liegen.';
}
}
if ($elevation !== '' && !is_numeric($elevation)) {
$errors[] = 'Die Hoehe muss numerisch sein.';
}
if (!validTimezone($timezone)) {
$errors[] = 'Bitte eine gültige IANA-Zeitzone angeben, z. B. Europe/Berlin.';
}
if (empty($errors)) {
$pdo->beginTransaction();
try {
if ($isDefault === 1) {
$stmtReset = $pdo->prepare("
UPDATE `app_user_locations`
SET `is_default` = 0
WHERE `user_id` = :user_id
");
$stmtReset->execute([
':user_id' => $userId,
]);
}
$stmtInsertLocation = $pdo->prepare("
INSERT INTO `app_user_locations` (
`user_id`,
`name`,
`latitude`,
`longitude`,
`elevation`,
`timezone`,
`is_default`,
`created_at`,
`updated_at`
) VALUES (
:user_id,
:name,
:latitude,
:longitude,
:elevation,
:timezone,
:is_default,
NOW(),
NOW()
)
");
$stmtInsertLocation->execute([
':user_id' => $userId,
':name' => $name,
':latitude' => (float) $latitude,
':longitude' => (float) $longitude,
':elevation' => $elevation !== '' ? (float) $elevation : null,
':timezone' => $timezone,
':is_default' => $isDefault,
]);
$pdo->commit();
$successMessage = 'Der Standort wurde gespeichert.';
$_POST = [];
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$errors[] = 'Der Standort konnte nicht gespeichert werden.';
}
}
}
if ($action === 'set_default_location') {
$locationId = (int) ($_POST['location_id'] ?? 0);
if ($locationId <= 0) {
$errors[] = 'Ungültiger Standort.';
}
if (empty($errors)) {
$stmtCheckLocation = $pdo->prepare("
SELECT `id`
FROM `app_user_locations`
WHERE `id` = :id
AND `user_id` = :user_id
LIMIT 1
");
$stmtCheckLocation->execute([
':id' => $locationId,
':user_id' => $userId,
]);
if (!$stmtCheckLocation->fetch()) {
$errors[] = 'Der Standort wurde nicht gefunden.';
}
}
if (empty($errors)) {
$pdo->beginTransaction();
try {
$stmtReset = $pdo->prepare("
UPDATE `app_user_locations`
SET `is_default` = 0
WHERE `user_id` = :user_id
");
$stmtReset->execute([
':user_id' => $userId,
]);
$stmtSet = $pdo->prepare("
UPDATE `app_user_locations`
SET `is_default` = 1
WHERE `id` = :id
AND `user_id` = :user_id
");
$stmtSet->execute([
':id' => $locationId,
':user_id' => $userId,
]);
$pdo->commit();
$successMessage = 'Der Standard-Standort wurde gesetzt.';
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$errors[] = 'Der Standard-Standort konnte nicht gesetzt werden.';
}
}
}
if ($action === 'delete_location') {
$locationId = (int) ($_POST['location_id'] ?? 0);
if ($locationId <= 0) {
$errors[] = 'Ungültiger Standort.';
}
if (empty($errors)) {
$stmtLocation = $pdo->prepare("
SELECT `id`, `is_default`
FROM `app_user_locations`
WHERE `id` = :id
AND `user_id` = :user_id
LIMIT 1
");
$stmtLocation->execute([
':id' => $locationId,
':user_id' => $userId,
]);
$locationRow = $stmtLocation->fetch();
if (!$locationRow) {
$errors[] = 'Der Standort wurde nicht gefunden.';
}
}
if (empty($errors)) {
$locationCount = countUserRows($pdo, 'app_user_locations', $userId);
if ($locationCount <= 1) {
$errors[] = 'Der letzte Standort kann nicht gelöscht werden.';
}
}
if (empty($errors)) {
$pdo->beginTransaction();
try {
$stmtDelete = $pdo->prepare("
DELETE FROM `app_user_locations`
WHERE `id` = :id
AND `user_id` = :user_id
");
$stmtDelete->execute([
':id' => $locationId,
':user_id' => $userId,
]);
if ((int) ($locationRow['is_default'] ?? 0) === 1) {
$stmtNextDefault = $pdo->prepare("
SELECT `id`
FROM `app_user_locations`
WHERE `user_id` = :user_id
ORDER BY `name` ASC, `id` ASC
LIMIT 1
");
$stmtNextDefault->execute([
':user_id' => $userId,
]);
$nextLocationId = $stmtNextDefault->fetchColumn();
if ($nextLocationId !== false) {
$stmtResetDefaults = $pdo->prepare("
UPDATE `app_user_locations`
SET `is_default` = 0
WHERE `user_id` = :user_id
");
$stmtResetDefaults->execute([
':user_id' => $userId,
]);
$stmtSetDefault = $pdo->prepare("
UPDATE `app_user_locations`
SET `is_default` = 1
WHERE `user_id` = :user_id
AND `id` = :id
");
$stmtSetDefault->execute([
':user_id' => $userId,
':id' => (int) $nextLocationId,
]);
}
}
$pdo->commit();
$successMessage = 'Der Standort wurde gelöscht.';
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$errors[] = 'Der Standort konnte nicht gelöscht werden.';
}
}
}
}
$stmtLocations = $pdo->prepare("
SELECT
`id`,
`name`,
`latitude`,
`longitude`,
`elevation`,
`timezone`,
`is_default`
FROM `app_user_locations`
WHERE `user_id` = :user_id
ORDER BY `is_default` DESC, `name` ASC
");
$stmtLocations->execute([
':user_id' => $userId,
]);
$locations = $stmtLocations->fetchAll();
$defaultLocation = null;
foreach ($locations as $location) {
if ((int) $location['is_default'] === 1) {
$defaultLocation = $location;
break;
}
}
$mapLat = isset($_POST['latitude']) && is_numeric($_POST['latitude'])
? (float) $_POST['latitude']
: ($defaultLocation ? (float) $defaultLocation['latitude'] : 51.1657);
$mapLon = isset($_POST['longitude']) && is_numeric($_POST['longitude'])
? (float) $_POST['longitude']
: ($defaultLocation ? (float) $defaultLocation['longitude'] : 10.4515);
$mapZoom = $defaultLocation ? 10 : 6;
if ($satelliteSearchQuery !== '') {
if (ctype_digit($satelliteSearchQuery)) {
$stmtSatelliteSearch = $pdo->prepare("
SELECT
s.`id`,
s.`norad_cat_id`,
s.`object_name`,
CASE
WHEN aus.`satellite_id` IS NULL THEN 0
ELSE 1
END AS `already_added`
FROM `sat_satellites` s
LEFT JOIN `app_user_satellites` aus
ON aus.`satellite_id` = s.`id`
AND aus.`user_id` = :user_id
WHERE s.`norad_cat_id` = :norad_cat_id
ORDER BY s.`object_name` ASC
LIMIT 50
");
$stmtSatelliteSearch->execute([
':user_id' => $userId,
':norad_cat_id' => (int) $satelliteSearchQuery,
]);
} else {
$stmtSatelliteSearch = $pdo->prepare("
SELECT
s.`id`,
s.`norad_cat_id`,
s.`object_name`,
CASE
WHEN aus.`satellite_id` IS NULL THEN 0
ELSE 1
END AS `already_added`
FROM `sat_satellites` s
LEFT JOIN `app_user_satellites` aus
ON aus.`satellite_id` = s.`id`
AND aus.`user_id` = :user_id
WHERE s.`object_name` LIKE :query
ORDER BY s.`object_name` ASC
LIMIT 50
");
$stmtSatelliteSearch->execute([
':user_id' => $userId,
':query' => '%' . $satelliteSearchQuery . '%',
]);
}
$satelliteSearchResults = $stmtSatelliteSearch->fetchAll();
}
if ($minorplanetSearchQuery !== '') {
if (ctype_digit($minorplanetSearchQuery)) {
$stmtMinorplanetSearch = $pdo->prepare("
SELECT
mp.`id`,
mp.`mp_number`,
mp.`designation_text`,
mp.`desig_packed`,
CASE
WHEN aum.`minorplanet_id` IS NULL THEN 0
ELSE 1
END AS `already_added`
FROM `minorplanets_mpc` mp
LEFT JOIN `app_user_minorplanets` aum
ON aum.`minorplanet_id` = mp.`id`
AND aum.`user_id` = :user_id
WHERE mp.`mp_number` = :mp_number
ORDER BY mp.`mp_number` ASC, mp.`designation_text` ASC
LIMIT 50
");
$stmtMinorplanetSearch->execute([
':user_id' => $userId,
':mp_number' => (int) $minorplanetSearchQuery,
]);
} else {
$stmtMinorplanetSearch = $pdo->prepare("
SELECT
mp.`id`,
mp.`mp_number`,
mp.`designation_text`,
mp.`desig_packed`,
CASE
WHEN aum.`minorplanet_id` IS NULL THEN 0
ELSE 1
END AS `already_added`
FROM `minorplanets_mpc` mp
LEFT JOIN `app_user_minorplanets` aum
ON aum.`minorplanet_id` = mp.`id`
AND aum.`user_id` = :user_id
WHERE mp.`designation_text` LIKE :query
OR mp.`desig_packed` LIKE :query
ORDER BY mp.`mp_number` IS NULL ASC, mp.`mp_number` ASC, mp.`designation_text` ASC
LIMIT 50
");
$stmtMinorplanetSearch->execute([
':user_id' => $userId,
':query' => '%' . $minorplanetSearchQuery . '%',
]);
}
$minorplanetSearchResults = $stmtMinorplanetSearch->fetchAll();
}
$stmtTrackedSatellites = $pdo->prepare("
SELECT
s.`id`,
s.`norad_cat_id`,
s.`object_name`,
aus.`is_favorite`,
aus.`created_at`
FROM `app_user_satellites` aus
INNER JOIN `sat_satellites` s
ON s.`id` = aus.`satellite_id`
WHERE aus.`user_id` = :user_id
ORDER BY aus.`is_favorite` DESC, s.`object_name` ASC
");
$stmtTrackedSatellites->execute([
':user_id' => $userId,
]);
$trackedSatellites = $stmtTrackedSatellites->fetchAll();
$stmtTrackedMinorplanets = $pdo->prepare("
SELECT
mp.`id`,
mp.`mp_number`,
mp.`designation_text`,
mp.`desig_packed`,
aum.`is_favorite`,
aum.`created_at`
FROM `app_user_minorplanets` aum
INNER JOIN `minorplanets_mpc` mp
ON mp.`id` = aum.`minorplanet_id`
WHERE aum.`user_id` = :user_id
ORDER BY aum.`is_favorite` DESC, mp.`mp_number` IS NULL ASC, mp.`mp_number` ASC, mp.`designation_text` ASC
");
$stmtTrackedMinorplanets->execute([
':user_id' => $userId,
]);
$trackedMinorplanets = $stmtTrackedMinorplanets->fetchAll();
$loggedIn = true;
?>
<?php require __DIR__ . '/header.php'; ?>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
>
<?php if ($successMessage !== ''): ?>
<p class="msg-success"><?= h($successMessage) ?></p>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<ul class="msg-error">
<?php foreach ($errors as $error): ?>
<li><?= h($error) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<div class="grid-2">
<div class="card">
<h2>Standorte</h2>
<?php if (empty($locations)): ?>
<p class="hint">Es sind noch keine Standorte gespeichert.</p>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>Breitengrad</th>
<th>Längengrad</th>
<th>Hoehe</th>
<th>Zeitzone</th>
<th>Standard</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($locations as $location): ?>
<tr>
<td><?= h($location['name']) ?></td>
<td><?= h((string) $location['latitude']) ?></td>
<td><?= h((string) $location['longitude']) ?></td>
<td><?= h($location['elevation'] !== null ? (string) $location['elevation'] . ' m' : '-') ?></td>
<td><?= h($location['timezone']) ?></td>
<td><?= (int) $location['is_default'] === 1 ? '*' : '-' ?></td>
<td class="action-cell">
<?php if ((int) $location['is_default'] !== 1): ?>
<form method="post" action="" class="favorites-loading-form" data-loading-message="Standard-Standort wird gesetzt...">
<input type="hidden" name="action" value="set_default_location">
<input type="hidden" name="location_id" value="<?= (int) $location['id'] ?>">
<button type="submit" class="btn btn-sm btn-secondary">Als Standard</button>
</form>
<?php endif; ?>
<?php if (count($locations) > 1): ?>
<form method="post" action="" class="favorites-loading-form" data-loading-message="Standort wird entfernt..." onsubmit="return confirm('Standort wirklich löschen?');">
<input type="hidden" name="action" value="delete_location">
<input type="hidden" name="location_id" value="<?= (int) $location['id'] ?>">
<button type="submit" class="btn btn-sm btn-danger">Loeschen</button>
</form>
<?php else: ?>
<span class="hint">Letzter Standort</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="card">
<h2>Standort hinzufuegen</h2>
<p class="hint mb-md">Suche einen Ort, nutze den Browser-Standort oder klicke direkt in die Karte.</p>
<div class="input-row mb-sm">
<input type="text" id="place-search" placeholder="Ort oder Adresse, z. B. Berlin, Wien">
<button type="button" id="search-place" class="btn btn-primary">Suchen</button>
<button type="button" id="lookup-elevation" class="btn btn-secondary">Hoehe ermitteln</button>
<button type="button" id="detect-location" class="btn btn-secondary">Browser-Standort</button>
</div>
<div id="map" class="map"></div>
<form method="post" action="" class="form-stack mt-xl favorites-loading-form" data-loading-message="Standort wird gespeichert...">
<input type="hidden" name="action" value="add_location">
<div class="grid-2 gap-sm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" required value="<?= h($_POST['name'] ?? '') ?>">
</div>
<div class="form-group">
<label for="timezone">Zeitzone (IANA)</label>
<input type="text" id="timezone" name="timezone" required value="<?= h($_POST['timezone'] ?? ($defaultLocation['timezone'] ?? 'Europe/Berlin')) ?>">
</div>
<div class="form-group">
<label for="latitude">Breitengrad</label>
<input type="text" id="latitude" name="latitude" required value="<?= h($_POST['latitude'] ?? ($defaultLocation['latitude'] ?? '')) ?>">
</div>
<div class="form-group">
<label for="longitude">Längengrad</label>
<input type="text" id="longitude" name="longitude" required value="<?= h($_POST['longitude'] ?? ($defaultLocation['longitude'] ?? '')) ?>">
</div>
<div class="form-group">
<label for="elevation">Hoehe (optional, Meter)</label>
<input type="text" id="elevation" name="elevation" value="<?= h($_POST['elevation'] ?? '') ?>">
</div>
</div>
<label class="form-checkbox">
<input type="checkbox" name="is_default" value="1" <?= isset($_POST['is_default']) ? 'checked' : '' ?>>
Als Standard-Standort speichern
</label>
<button type="submit" class="btn btn-primary">Standort speichern</button>
<p class="favorites-loading-hint" hidden>Standort wird gespeichert...</p>
</form>
</div>
</div>
<div class="grid-2">
<div class="card">
<h2>Satelliten suchen</h2>
<form method="get" action="" class="favorites-loading-form" data-loading-message="Satelliten werden gesucht. Das kann kurz dauern...">
<div class="input-row">
<input
type="text"
id="sat_q"
name="sat_q"
value="<?= h($satelliteSearchQuery) ?>"
placeholder="Name oder NORAD-ID, z. B. ISS oder 25544"
>
<button type="submit" class="btn btn-primary">Suchen</button>
</div>
<p class="favorites-loading-hint" hidden>Satelliten werden gesucht. Das kann kurz dauern...</p>
</form>
<?php if ($satelliteSearchQuery !== ''): ?>
<div class="mt-xl">
<h2>Suchergebnisse</h2>
<?php if (empty($satelliteSearchResults)): ?>
<p class="hint">Keine passenden Satelliten gefunden.</p>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>NORAD</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($satelliteSearchResults as $satellite): ?>
<tr>
<td><?= h($satellite['object_name']) ?></td>
<td><?= h((string) $satellite['norad_cat_id']) ?></td>
<td>
<?php if ((int) $satellite['already_added'] === 1): ?>
<span class="hint">Bereits hinzugefuegt</span>
<?php else: ?>
<form method="post" action="" class="favorites-loading-form" data-loading-message="Satellit wird hinzugefuegt...">
<input type="hidden" name="action" value="add_satellite">
<input type="hidden" name="satellite_id" value="<?= (int) $satellite['id'] ?>">
<button type="submit" class="btn btn-sm btn-primary">Hinzufuegen</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="card">
<h2>Kleinplaneten suchen</h2>
<form method="get" action="" class="favorites-loading-form" data-loading-message="Kleinplaneten werden gesucht. Das kann etwas dauern...">
<div class="input-row">
<input
type="text"
id="mp_q"
name="mp_q"
value="<?= h($minorplanetSearchQuery) ?>"
placeholder="Nummer oder Bezeichnung, z. B. 1 oder Ceres"
>
<button type="submit" class="btn btn-primary">Suchen</button>
</div>
<p class="favorites-loading-hint" hidden>Kleinplaneten werden gesucht. Das kann etwas dauern...</p>
</form>
<?php if ($minorplanetSearchQuery !== ''): ?>
<div class="mt-xl">
<h2>Suchergebnisse</h2>
<?php if (empty($minorplanetSearchResults)): ?>
<p class="hint">Keine passenden Kleinplaneten gefunden.</p>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Bezeichnung</th>
<th>Nummer</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($minorplanetSearchResults as $minorplanet): ?>
<tr>
<td><?= h($minorplanet['designation_text'] ?: $minorplanet['desig_packed']) ?></td>
<td><?= h($minorplanet['mp_number'] !== null ? (string) $minorplanet['mp_number'] : '—') ?></td>
<td>
<?php if ((int) $minorplanet['already_added'] === 1): ?>
<span class="hint">Bereits hinzugefuegt</span>
<?php else: ?>
<form method="post" action="" class="favorites-loading-form" data-loading-message="Kleinplanet wird hinzugefuegt...">
<input type="hidden" name="action" value="add_minorplanet">
<input type="hidden" name="minorplanet_id" value="<?= (int) $minorplanet['id'] ?>">
<button type="submit" class="btn btn-sm btn-primary">Hinzufuegen</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
<div class="grid-2">
<div class="card">
<h2>Meine Satelliten</h2>
<?php if (empty($trackedSatellites)): ?>
<p class="hint">Du hast noch keine Satelliten ausgewählt.</p>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>NORAD</th>
<th>Favorit</th>
<th>Hinzugefuegt</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($trackedSatellites as $satellite): ?>
<tr>
<td><?= h($satellite['object_name']) ?></td>
<td><?= h((string) $satellite['norad_cat_id']) ?></td>
<td><?= (int) $satellite['is_favorite'] === 1 ? '*' : '-' ?></td>
<td><?= h($satellite['created_at']) ?></td>
<td class="action-cell">
<?php if (count($trackedSatellites) <= 1 && (int) $satellite['is_favorite'] === 1): ?>
<span class="hint">Favorit bleibt gesetzt</span>
<?php else: ?>
<form method="post" action="" class="favorites-loading-form" data-loading-message="Satelliten-Favorit wird aktualisiert...">
<input type="hidden" name="action" value="toggle_satellite_favorite">
<input type="hidden" name="satellite_id" value="<?= (int) $satellite['id'] ?>">
<button type="submit" class="btn btn-sm btn-secondary">
<?= (int) $satellite['is_favorite'] === 1 ? 'Favorit entfernen' : 'Als Favorit' ?>
</button>
</form>
<?php endif; ?>
<?php if (count($trackedSatellites) > 1): ?>
<form method="post" action="" class="favorites-loading-form" data-loading-message="Satellit wird entfernt..." onsubmit="return confirm('Satellit wirklich entfernen?');">
<input type="hidden" name="action" value="remove_satellite">
<input type="hidden" name="satellite_id" value="<?= (int) $satellite['id'] ?>">
<button type="submit" class="btn btn-sm btn-danger">Entfernen</button>
</form>
<?php else: ?>
<span class="hint">Letzter Satellit</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="card">
<h2>Meine Kleinplaneten</h2>
<?php if (empty($trackedMinorplanets)): ?>
<p class="hint">Du hast noch keine Kleinplaneten ausgewählt.</p>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Bezeichnung</th>
<th>Nummer</th>
<th>Favorit</th>
<th>Hinzugefuegt</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($trackedMinorplanets as $minorplanet): ?>
<tr>
<td><?= h($minorplanet['designation_text'] ?: $minorplanet['desig_packed']) ?></td>
<td><?= h($minorplanet['mp_number'] !== null ? (string) $minorplanet['mp_number'] : '-') ?></td>
<td><?= (int) $minorplanet['is_favorite'] === 1 ? '*' : '-' ?></td>
<td><?= h($minorplanet['created_at']) ?></td>
<td class="action-cell">
<form method="post" action="" class="favorites-loading-form" data-loading-message="Kleinplaneten-Favorit wird aktualisiert...">
<input type="hidden" name="action" value="toggle_minorplanet_favorite">
<input type="hidden" name="minorplanet_id" value="<?= (int) $minorplanet['id'] ?>">
<button type="submit" class="btn btn-sm btn-secondary">
<?= (int) $minorplanet['is_favorite'] === 1 ? 'Favorit entfernen' : 'Als Favorit' ?>
</button>
</form>
<form method="post" action="" class="favorites-loading-form" data-loading-message="Kleinplanet wird entfernt..." onsubmit="return confirm('Kleinplanet wirklich entfernen?');">
<input type="hidden" name="action" value="remove_minorplanet">
<input type="hidden" name="minorplanet_id" value="<?= (int) $minorplanet['id'] ?>">
<button type="submit" class="btn btn-sm btn-danger">Entfernen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""
></script>
<script>
(function () {
const latInput = document.getElementById('latitude');
const lonInput = document.getElementById('longitude');
const elevationInput = document.getElementById('elevation');
const timezoneInput = document.getElementById('timezone');
const detectButton = document.getElementById('detect-location');
const searchInput = document.getElementById('place-search');
const searchButton = document.getElementById('search-place');
const elevationButton = document.getElementById('lookup-elevation');
const nameInput = document.getElementById('name');
const mapEl = document.getElementById('map');
if (!latInput || !lonInput || !elevationInput || !timezoneInput || !detectButton || !searchInput || !searchButton || !elevationButton || !nameInput || !mapEl || typeof L === 'undefined') {
return;
}
const initialLat = parseFloat(<?= json_encode($mapLat) ?>);
const initialLon = parseFloat(<?= json_encode($mapLon) ?>);
const initialZoom = parseInt(<?= json_encode($mapZoom) ?>, 10);
const map = L.map('map').setView([initialLat, initialLon], initialZoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap-Mitwirkende'
}).addTo(map);
let marker = L.marker([initialLat, initialLon], { draggable: true }).addTo(map);
function setInputs(lat, lon) {
latInput.value = Number(lat).toFixed(7);
lonInput.value = Number(lon).toFixed(7);
}
function setBrowserTimezoneIfEmpty() {
if (!timezoneInput.value.trim()) {
try {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (tz) {
timezoneInput.value = tz;
}
} catch (e) {
}
}
}
function moveMarker(lat, lon, zoom = null) {
marker.setLatLng([lat, lon]);
if (zoom !== null) {
map.setView([lat, lon], zoom);
} else {
map.panTo([lat, lon]);
}
setInputs(lat, lon);
setBrowserTimezoneIfEmpty();
}
async function lookupElevation() {
const lat = parseFloat(latInput.value);
const lon = parseFloat(lonInput.value);
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
alert('Bitte zuerst einen gültigen Standort wählen.');
return;
}
try {
const url = 'https://api.open-elevation.com/api/v1/lookup?locations=' + encodeURIComponent(lat + ',' + lon);
const response = await fetch(url, { method: 'GET' });
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const data = await response.json();
if (!data || !Array.isArray(data.results) || data.results.length === 0 || typeof data.results[0].elevation === 'undefined') {
throw new Error('Keine Hoehendaten erhalten.');
}
elevationInput.value = data.results[0].elevation;
} catch (error) {
alert('Die Hoehe konnte nicht automatisch ermittelt werden.');
console.error(error);
}
}
marker.on('dragend', function (event) {
const pos = event.target.getLatLng();
setInputs(pos.lat, pos.lng);
});
map.on('click', function (event) {
moveMarker(event.latlng.lat, event.latlng.lng);
});
detectButton.addEventListener('click', function () {
if (!navigator.geolocation) {
alert('Der Browser unterstuetzt keine Standortbestimmung.');
return;
}
navigator.geolocation.getCurrentPosition(
function (position) {
moveMarker(position.coords.latitude, position.coords.longitude, 13);
setBrowserTimezoneIfEmpty();
},
function (error) {
let message = 'Standort konnte nicht ermittelt werden.';
if (error && error.message) {
message += ' ' + error.message;
}
alert(message);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
});
async function doSearch() {
const query = searchInput.value.trim();
if (!query) {
alert('Bitte einen Ort oder eine Adresse eingeben.');
return;
}
try {
const url = 'https://photon.komoot.io/api/?q=' + encodeURIComponent(query) + '&limit=5';
const response = await fetch(url, { method: 'GET' });
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const data = await response.json();
if (!data.features || data.features.length === 0) {
alert('Kein passender Ort gefunden.');
return;
}
const feature = data.features[0];
const coords = feature.geometry.coordinates;
const lon = parseFloat(coords[0]);
const lat = parseFloat(coords[1]);
moveMarker(lat, lon, 13);
const props = feature.properties || {};
const parts = [];
if (props.name) parts.push(props.name);
if (props.city && props.city !== props.name) parts.push(props.city);
if (props.state) parts.push(props.state);
if (props.country) parts.push(props.country);
nameInput.value = parts.length > 0 ? parts.join(', ') : query;
setBrowserTimezoneIfEmpty();
} catch (error) {
alert('Die Ortssuche ist fehlgeschlagen.');
console.error(error);
}
}
searchButton.addEventListener('click', doSearch);
elevationButton.addEventListener('click', lookupElevation);
searchInput.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
event.preventDefault();
doSearch();
}
});
if (!latInput.value || !lonInput.value) {
setInputs(initialLat, initialLon);
}
setBrowserTimezoneIfEmpty();
})();
document.querySelectorAll('.favorites-loading-form').forEach((form) => {
form.addEventListener('submit', () => {
const message = form.dataset.loadingMessage || 'Bitte warten...';
let hint = form.querySelector('.favorites-loading-hint');
if (!hint) {
hint = document.createElement('p');
hint.className = 'favorites-loading-hint';
form.appendChild(hint);
}
hint.textContent = message;
hint.hidden = false;
form.classList.add('is-loading');
form.querySelectorAll('button, input, select, textarea').forEach((field) => {
if (field.tagName === 'INPUT' && field.type === 'hidden') {
return;
}
field.setAttribute('aria-busy', 'true');
if (field.tagName === 'BUTTON') {
field.disabled = true;
}
});
});
});
</script>
<?php require __DIR__ . '/footer.php'; ?>