2667 lines
108 KiB
PHP
2667 lines
108 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 = [];
|
|
$cometSearchResults = [];
|
|
$satelliteSearchQuery = trim($_GET['sat_q'] ?? '');
|
|
$minorplanetSearchQuery = trim($_GET['mp_q'] ?? '');
|
|
$cometSearchQuery = trim($_GET['com_q'] ?? '');
|
|
$pageTitle = 'Favoriten - AstroTools';
|
|
$headerIntroPre = 'Merkliste';
|
|
$headerIntroTitle = 'Favoriten';
|
|
$headerIntroSub = 'Satelliten und Kleinplaneten zentral verwalten.';
|
|
$htmxTriggers = [];
|
|
|
|
function h(?string $value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function isHtmxRequest(): bool
|
|
{
|
|
return strtolower((string) ($_SERVER['HTTP_HX_REQUEST'] ?? '')) === 'true';
|
|
}
|
|
|
|
function renderMessages(string $successMessage, array $errors, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div id="favorites-messages"<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>>
|
|
<?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>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function renderLocationsSection(array $locations, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div class="card" id="locations-section"<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>>
|
|
<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>Laengengrad</th>
|
|
<th>Hoehe</th>
|
|
<th>Zeitzone</th>
|
|
<th>Standard</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($locations as $location): ?>
|
|
<tr>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h($location['name']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h((string) $location['latitude']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h((string) $location['longitude']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h($location['elevation'] !== null ? (string) $location['elevation'] . ' m' : '-') ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h($location['timezone']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= (int) $location['is_default'] === 1 ? '*' : '-' ?></td>
|
|
<td class="action-cell">
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-primary js-edit-location"
|
|
data-location-id="<?= (int) $location['id'] ?>"
|
|
data-location-name="<?= h((string) $location['name']) ?>"
|
|
data-location-latitude="<?= h((string) $location['latitude']) ?>"
|
|
data-location-longitude="<?= h((string) $location['longitude']) ?>"
|
|
data-location-elevation="<?= h($location['elevation'] !== null ? (string) $location['elevation'] : '') ?>"
|
|
data-location-timezone="<?= h((string) $location['timezone']) ?>"
|
|
data-location-default="<?= (int) $location['is_default'] === 1 ? '1' : '0' ?>"
|
|
>Bearbeiten</button>
|
|
<?php if ((int) $location['is_default'] !== 1): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Standard-Standort wird gesetzt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 favorites-loading-form-htmx" data-loading-message="Standort wird entfernt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML" onsubmit="return confirm('Standort wirklich loeschen?');">
|
|
<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>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function renderSatelliteSearchResults(string $satelliteSearchQuery, array $satelliteSearchResults, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div
|
|
id="satellite-search-results"
|
|
class="favorites-search-results<?= $satelliteSearchQuery !== '' ? ' has-results' : '' ?>"
|
|
aria-live="polite"
|
|
<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>
|
|
>
|
|
<?php if ($satelliteSearchQuery === ''): ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Noch keine Suche gestartet.</p>
|
|
<p class="hint">Suche nach Namen oder NORAD-ID, um passende Satelliten direkt hier einzublenden.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="favorites-search-summary">
|
|
<h2>Suchergebnisse</h2>
|
|
<p class="hint">
|
|
<?= count($satelliteSearchResults) ?> Treffer fuer <strong><?= h($satelliteSearchQuery) ?></strong>
|
|
</p>
|
|
</div>
|
|
|
|
<?php if (empty($satelliteSearchResults)): ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Keine passenden Satelliten gefunden.</p>
|
|
<p class="hint">Probiere einen Namen wie <strong>ISS</strong> oder eine NORAD-ID wie <strong>25544</strong>.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="favorites-search-table-wrap">
|
|
<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): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Satellit wird hinzugefuegt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function renderMinorplanetSearchResults(string $minorplanetSearchQuery, array $minorplanetSearchResults, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div
|
|
id="minorplanet-search-results"
|
|
class="favorites-search-results<?= $minorplanetSearchQuery !== '' ? ' has-results' : '' ?>"
|
|
aria-live="polite"
|
|
<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>
|
|
>
|
|
<?php if ($minorplanetSearchQuery === ''): ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Noch keine Suche gestartet.</p>
|
|
<p class="hint">Suche nach Nummer oder Bezeichnung, um passende Kleinplaneten direkt hier einzublenden.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="favorites-search-summary">
|
|
<h2>Suchergebnisse</h2>
|
|
<p class="hint">
|
|
<?= count($minorplanetSearchResults) ?> Treffer fuer <strong><?= h($minorplanetSearchQuery) ?></strong>
|
|
</p>
|
|
</div>
|
|
|
|
<?php if (empty($minorplanetSearchResults)): ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Keine passenden Kleinplaneten gefunden.</p>
|
|
<p class="hint">Probiere eine Nummer wie <strong>1</strong> oder eine Bezeichnung wie <strong>Ceres</strong>.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="favorites-search-table-wrap">
|
|
<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): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Kleinplanet wird hinzugefuegt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function renderTrackedSatellitesSection(array $trackedSatellites, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div class="card" id="tracked-satellites-section" data-favorites-group="satellites"<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>>
|
|
<h2>Meine Satelliten</h2>
|
|
<?php if (empty($trackedSatellites)): ?>
|
|
<p class="hint">Du hast noch keine Satelliten ausgewaehlt.</p>
|
|
<?php else: ?>
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>NORAD</th>
|
|
<th>Favorit</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($trackedSatellites as $satellite): ?>
|
|
<tr>
|
|
<td class="<?= (int) $satellite['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h($satellite['object_name']) ?></td>
|
|
<td class="<?= (int) $satellite['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h((string) $satellite['norad_cat_id']) ?></td>
|
|
<td class="<?= (int) $satellite['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= (int) $satellite['is_favorite'] === 1 ? '*' : '-' ?></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 favorites-loading-form-htmx" data-loading-message="Satelliten-Favorit wird aktualisiert..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 ? ' favorite-active' : '' ?>">
|
|
<?= (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 favorites-loading-form-htmx" data-loading-message="Satellit wird entfernt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML" 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>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function renderTrackedMinorplanetsSection(array $trackedMinorplanets, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div class="card" id="tracked-minorplanets-section" data-favorites-group="minorplanets"<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>>
|
|
<h2>Meine Kleinplaneten</h2>
|
|
<?php if (empty($trackedMinorplanets)): ?>
|
|
<p class="hint">Du hast noch keine Kleinplaneten ausgewaehlt.</p>
|
|
<?php else: ?>
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Bezeichnung</th>
|
|
<th>Nummer</th>
|
|
<th>Favorit</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($trackedMinorplanets as $minorplanet): ?>
|
|
<tr>
|
|
<td class="<?= (int) $minorplanet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h($minorplanet['designation_text'] ?: $minorplanet['desig_packed']) ?></td>
|
|
<td class="<?= (int) $minorplanet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h($minorplanet['mp_number'] !== null ? (string) $minorplanet['mp_number'] : '-') ?></td>
|
|
<td class="<?= (int) $minorplanet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= (int) $minorplanet['is_favorite'] === 1 ? '*' : '-' ?></td>
|
|
<td class="action-cell">
|
|
<?php if (count($trackedMinorplanets) <= 1 && (int) $minorplanet['is_favorite'] === 1): ?>
|
|
<span class="hint">Favorit bleibt gesetzt</span>
|
|
<?php else: ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Kleinplaneten-Favorit wird aktualisiert..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 ? ' favorite-active' : '' ?>">
|
|
<?= (int) $minorplanet['is_favorite'] === 1 ? 'Favorit entfernen' : 'Als Favorit' ?>
|
|
</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
<?php if (count($trackedMinorplanets) > 1): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Kleinplanet wird entfernt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML" 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>
|
|
<?php else: ?>
|
|
<span class="hint">Letzter Kleinplanet</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function renderCometSearchResults(string $cometSearchQuery, array $cometSearchResults, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div
|
|
id="comet-search-results"
|
|
class="favorites-search-results<?= $cometSearchQuery !== '' ? ' has-results' : '' ?>"
|
|
aria-live="polite"
|
|
<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>
|
|
>
|
|
<?php if ($cometSearchQuery === ''): ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Noch keine Suche gestartet.</p>
|
|
<p class="hint">Suche nach MPC-Bezeichnung oder Kometenname, um passende Kometen direkt hier einzublenden.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="favorites-search-summary">
|
|
<h2>Suchergebnisse</h2>
|
|
<p class="hint">
|
|
<?= count($cometSearchResults) ?> Treffer fuer <strong><?= h($cometSearchQuery) ?></strong>
|
|
</p>
|
|
</div>
|
|
|
|
<?php if (empty($cometSearchResults)): ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Keine passenden Kometen gefunden.</p>
|
|
<p class="hint">Probiere eine Bezeichnung wie <strong>67P</strong> oder einen Namen wie <strong>Halley</strong>.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="favorites-search-table-wrap">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Bezeichnung</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($cometSearchResults as $comet): ?>
|
|
<tr>
|
|
<td><?= h($comet['designation_and_name']) ?></td>
|
|
<td>
|
|
<?php if ((int) $comet['already_added'] !== 1): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Komet wird hinzugefuegt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<input type="hidden" name="action" value="add_comet">
|
|
<input type="hidden" name="comet_id" value="<?= (int) $comet['id'] ?>">
|
|
<button type="submit" class="btn btn-sm btn-primary">Hinzufuegen</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function renderTrackedCometsSection(array $trackedComets, bool $oob = false): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<div class="card" id="tracked-comets-section" data-favorites-group="comets"<?= $oob ? ' hx-swap-oob="outerHTML"' : '' ?>>
|
|
<h2>Meine Kometen</h2>
|
|
<?php if (empty($trackedComets)): ?>
|
|
<p class="hint">Du hast noch keine Kometen ausgewaehlt.</p>
|
|
<?php else: ?>
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Bezeichnung</th>
|
|
<th>Typ</th>
|
|
<th>Favorit</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($trackedComets as $comet): ?>
|
|
<tr>
|
|
<td class="<?= (int) $comet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h($comet['designation_and_name']) ?></td>
|
|
<td class="<?= (int) $comet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h((string) $comet['orbit_type']) ?></td>
|
|
<td class="<?= (int) $comet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= (int) $comet['is_favorite'] === 1 ? '*' : '-' ?></td>
|
|
<td class="action-cell">
|
|
<?php if (count($trackedComets) <= 1 && (int) $comet['is_favorite'] === 1): ?>
|
|
<span class="hint">Favorit bleibt gesetzt</span>
|
|
<?php else: ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Kometen-Favorit wird aktualisiert..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<input type="hidden" name="action" value="toggle_comet_favorite">
|
|
<input type="hidden" name="comet_id" value="<?= (int) $comet['id'] ?>">
|
|
<button type="submit" class="btn btn-sm btn-secondary<?= (int) $comet['is_favorite'] === 1 ? ' favorite-active' : '' ?>">
|
|
<?= (int) $comet['is_favorite'] === 1 ? 'Favorit entfernen' : 'Als Favorit' ?>
|
|
</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
<?php if (count($trackedComets) > 1): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Komet wird entfernt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML" onsubmit="return confirm('Komet wirklich entfernen?');">
|
|
<input type="hidden" name="action" value="remove_comet">
|
|
<input type="hidden" name="comet_id" value="<?= (int) $comet['id'] ?>">
|
|
<button type="submit" class="btn btn-sm btn-danger">Entfernen</button>
|
|
</form>
|
|
<?php else: ?>
|
|
<span class="hint">Letzter Komet</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
|
|
return (string) ob_get_clean();
|
|
}
|
|
|
|
function validTimezone(string $timezone): bool
|
|
{
|
|
return in_array($timezone, timezone_identifiers_list(), true);
|
|
}
|
|
|
|
if (isHtmxRequest()) {
|
|
$hxCurrentUrl = trim((string) ($_SERVER['HTTP_HX_CURRENT_URL'] ?? ''));
|
|
if ($hxCurrentUrl !== '') {
|
|
$queryString = (string) parse_url($hxCurrentUrl, PHP_URL_QUERY);
|
|
if ($queryString !== '') {
|
|
parse_str($queryString, $hxQueryParams);
|
|
if ($satelliteSearchQuery === '' && isset($hxQueryParams['sat_q'])) {
|
|
$satelliteSearchQuery = trim((string) $hxQueryParams['sat_q']);
|
|
}
|
|
if ($minorplanetSearchQuery === '' && isset($hxQueryParams['mp_q'])) {
|
|
$minorplanetSearchQuery = trim((string) $hxQueryParams['mp_q']);
|
|
}
|
|
if ($cometSearchQuery === '' && isset($hxQueryParams['com_q'])) {
|
|
$cometSearchQuery = trim((string) $hxQueryParams['com_q']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
]);
|
|
|
|
if ($newValue === 0) {
|
|
$stmtNextFavorite = $pdo->prepare("
|
|
SELECT `{$idColumn}`
|
|
FROM `{$tableName}`
|
|
WHERE `user_id` = :user_id
|
|
AND `{$idColumn}` <> :entity_id
|
|
ORDER BY `created_at` ASC, `{$idColumn}` ASC
|
|
LIMIT 1
|
|
");
|
|
$stmtNextFavorite->execute([
|
|
':user_id' => $userId,
|
|
':entity_id' => $entityId,
|
|
]);
|
|
$nextEntityId = $stmtNextFavorite->fetchColumn();
|
|
|
|
if ($nextEntityId !== false) {
|
|
$stmtPromote = $pdo->prepare("
|
|
UPDATE `{$tableName}`
|
|
SET `is_favorite` = 1
|
|
WHERE `user_id` = :user_id
|
|
AND `{$idColumn}` = :entity_id
|
|
");
|
|
$stmtPromote->execute([
|
|
':user_id' => $userId,
|
|
':entity_id' => (int) $nextEntityId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
$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();
|
|
}
|
|
|
|
function ensureFavoriteIfNeeded(PDO $pdo, string $tableName, string $idColumn, int $userId): void
|
|
{
|
|
$stmtFavoriteCount = $pdo->prepare("
|
|
SELECT COUNT(*)
|
|
FROM `{$tableName}`
|
|
WHERE `user_id` = :user_id
|
|
AND `is_favorite` = 1
|
|
");
|
|
$stmtFavoriteCount->execute([
|
|
':user_id' => $userId,
|
|
]);
|
|
$favoriteCount = (int) $stmtFavoriteCount->fetchColumn();
|
|
|
|
if ($favoriteCount > 0) {
|
|
return;
|
|
}
|
|
|
|
$stmtNextFavorite = $pdo->prepare("
|
|
SELECT `{$idColumn}`
|
|
FROM `{$tableName}`
|
|
WHERE `user_id` = :user_id
|
|
ORDER BY `created_at` ASC, `{$idColumn}` ASC
|
|
LIMIT 1
|
|
");
|
|
$stmtNextFavorite->execute([
|
|
':user_id' => $userId,
|
|
]);
|
|
$nextEntityId = $stmtNextFavorite->fetchColumn();
|
|
|
|
if ($nextEntityId === false) {
|
|
return;
|
|
}
|
|
|
|
$stmtPromote = $pdo->prepare("
|
|
UPDATE `{$tableName}`
|
|
SET `is_favorite` = 1
|
|
WHERE `user_id` = :user_id
|
|
AND `{$idColumn}` = :entity_id
|
|
");
|
|
$stmtPromote->execute([
|
|
':user_id' => $userId,
|
|
':entity_id' => (int) $nextEntityId,
|
|
]);
|
|
}
|
|
|
|
function validateLocationForm(array $input, array &$errors): array
|
|
{
|
|
$name = trim((string) ($input['name'] ?? ''));
|
|
$latitude = trim((string) ($input['latitude'] ?? ''));
|
|
$longitude = trim((string) ($input['longitude'] ?? ''));
|
|
$elevation = trim((string) ($input['elevation'] ?? ''));
|
|
$timezone = trim((string) ($input['timezone'] ?? 'Europe/Berlin'));
|
|
|
|
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 Höhe muss numerisch sein.';
|
|
}
|
|
|
|
if (!validTimezone($timezone)) {
|
|
$errors[] = 'Bitte eine gültige IANA-Zeitzone angeben, z. B. Europe/Berlin.';
|
|
}
|
|
|
|
return [
|
|
'name' => $name,
|
|
'latitude' => $latitude,
|
|
'longitude' => $longitude,
|
|
'elevation' => $elevation,
|
|
'timezone' => $timezone,
|
|
];
|
|
}
|
|
|
|
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)) {
|
|
$minorplanetCount = countUserRows($pdo, 'app_user_minorplanets', $userId);
|
|
if ($minorplanetCount <= 1) {
|
|
$errors[] = 'Der letzte Kleinplanet kann nicht gelöscht werden.';
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
$stmtCurrentMinorplanet = $pdo->prepare("
|
|
SELECT `is_favorite`
|
|
FROM `app_user_minorplanets`
|
|
WHERE `user_id` = :user_id
|
|
AND `minorplanet_id` = :minorplanet_id
|
|
LIMIT 1
|
|
");
|
|
$stmtCurrentMinorplanet->execute([
|
|
':user_id' => $userId,
|
|
':minorplanet_id' => $minorplanetId,
|
|
]);
|
|
$currentMinorplanetFavorite = $stmtCurrentMinorplanet->fetchColumn();
|
|
|
|
if ($currentMinorplanetFavorite === false) {
|
|
$errors[] = 'Der Kleinplanet ist nicht in deiner Liste vorhanden.';
|
|
}
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$minorplanetCount = countUserRows($pdo, 'app_user_minorplanets', $userId);
|
|
if ($minorplanetCount <= 1 && (int) $currentMinorplanetFavorite === 1) {
|
|
$errors[] = 'Beim letzten Kleinplaneten kann der Favorit nicht entfernt werden.';
|
|
}
|
|
}
|
|
|
|
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_comet') {
|
|
$cometId = (int) ($_POST['comet_id'] ?? 0);
|
|
|
|
if ($cometId <= 0) {
|
|
$errors[] = 'Ungueltiger Komet.';
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$stmtCheck = $pdo->prepare("
|
|
SELECT `id`
|
|
FROM `comets_mpc`
|
|
WHERE `id` = :id
|
|
LIMIT 1
|
|
");
|
|
$stmtCheck->execute([
|
|
':id' => $cometId,
|
|
]);
|
|
|
|
if (!$stmtCheck->fetch()) {
|
|
$errors[] = 'Der Komet wurde nicht gefunden.';
|
|
}
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$stmtInsert = $pdo->prepare("
|
|
INSERT INTO `app_user_comets` (
|
|
`user_id`,
|
|
`comet_id`,
|
|
`is_favorite`,
|
|
`created_at`
|
|
) VALUES (
|
|
:user_id,
|
|
:comet_id,
|
|
0,
|
|
NOW()
|
|
)
|
|
ON DUPLICATE KEY UPDATE
|
|
`comet_id` = VALUES(`comet_id`)
|
|
");
|
|
$stmtInsert->execute([
|
|
':user_id' => $userId,
|
|
':comet_id' => $cometId,
|
|
]);
|
|
|
|
$successMessage = 'Der Komet wurde hinzugefuegt.';
|
|
}
|
|
}
|
|
|
|
if ($action === 'remove_comet') {
|
|
$cometId = (int) ($_POST['comet_id'] ?? 0);
|
|
|
|
if ($cometId <= 0) {
|
|
$errors[] = 'Ungueltiger Komet.';
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$cometCount = countUserRows($pdo, 'app_user_comets', $userId);
|
|
if ($cometCount <= 1) {
|
|
$errors[] = 'Der letzte Komet kann nicht gelöscht werden.';
|
|
}
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$stmtDelete = $pdo->prepare("
|
|
DELETE FROM `app_user_comets`
|
|
WHERE `user_id` = :user_id
|
|
AND `comet_id` = :comet_id
|
|
");
|
|
$stmtDelete->execute([
|
|
':user_id' => $userId,
|
|
':comet_id' => $cometId,
|
|
]);
|
|
|
|
$successMessage = 'Der Komet wurde entfernt.';
|
|
}
|
|
}
|
|
|
|
if ($action === 'toggle_comet_favorite') {
|
|
$cometId = (int) ($_POST['comet_id'] ?? 0);
|
|
|
|
if ($cometId <= 0) {
|
|
$errors[] = 'Ungueltiger Komet.';
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$stmtCurrentComet = $pdo->prepare("
|
|
SELECT `is_favorite`
|
|
FROM `app_user_comets`
|
|
WHERE `user_id` = :user_id
|
|
AND `comet_id` = :comet_id
|
|
LIMIT 1
|
|
");
|
|
$stmtCurrentComet->execute([
|
|
':user_id' => $userId,
|
|
':comet_id' => $cometId,
|
|
]);
|
|
$currentCometFavorite = $stmtCurrentComet->fetchColumn();
|
|
|
|
if ($currentCometFavorite === false) {
|
|
$errors[] = 'Der Komet ist nicht in deiner Liste vorhanden.';
|
|
}
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$cometCount = countUserRows($pdo, 'app_user_comets', $userId);
|
|
if ($cometCount <= 1 && (int) $currentCometFavorite === 1) {
|
|
$errors[] = 'Beim letzten Kometen kann der Favorit nicht entfernt werden.';
|
|
}
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
if (!setExclusiveFavorite($pdo, 'app_user_comets', 'comet_id', $userId, $cometId)) {
|
|
$errors[] = 'Der Komet ist nicht in deiner Liste vorhanden.';
|
|
} else {
|
|
$successMessage = 'Der Kometen-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 === 'update_location') {
|
|
$locationId = (int) ($_POST['location_id'] ?? 0);
|
|
$locationForm = validateLocationForm($_POST, $errors);
|
|
$setDefault = isset($_POST['is_default']) ? 1 : 0;
|
|
|
|
if ($locationId <= 0) {
|
|
$errors[] = 'Ungültiger Standort.';
|
|
}
|
|
|
|
$existingLocation = null;
|
|
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,
|
|
]);
|
|
$existingLocation = $stmtLocation->fetch();
|
|
|
|
if (!$existingLocation) {
|
|
$errors[] = 'Der Standort wurde nicht gefunden.';
|
|
}
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$pdo->beginTransaction();
|
|
|
|
try {
|
|
if ($setDefault === 1) {
|
|
$stmtReset = $pdo->prepare("
|
|
UPDATE `app_user_locations`
|
|
SET `is_default` = 0
|
|
WHERE `user_id` = :user_id
|
|
");
|
|
$stmtReset->execute([
|
|
':user_id' => $userId,
|
|
]);
|
|
}
|
|
|
|
$stmtUpdate = $pdo->prepare("
|
|
UPDATE `app_user_locations`
|
|
SET
|
|
`name` = :name,
|
|
`latitude` = :latitude,
|
|
`longitude` = :longitude,
|
|
`elevation` = :elevation,
|
|
`timezone` = :timezone,
|
|
`is_default` = :is_default,
|
|
`updated_at` = NOW()
|
|
WHERE `id` = :id
|
|
AND `user_id` = :user_id
|
|
");
|
|
$stmtUpdate->execute([
|
|
':id' => $locationId,
|
|
':user_id' => $userId,
|
|
':name' => $locationForm['name'],
|
|
':latitude' => (float) $locationForm['latitude'],
|
|
':longitude' => (float) $locationForm['longitude'],
|
|
':elevation' => $locationForm['elevation'] !== '' ? (float) $locationForm['elevation'] : null,
|
|
':timezone' => $locationForm['timezone'],
|
|
':is_default' => $setDefault === 1 ? 1 : (int) ($existingLocation['is_default'] ?? 0),
|
|
]);
|
|
|
|
$pdo->commit();
|
|
$successMessage = 'Der Standort wurde aktualisiert.';
|
|
$htmxTriggers['favorites:location-updated'] = true;
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
$errors[] = 'Der Standort konnte nicht aktualisiert 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.';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
ensureFavoriteIfNeeded($pdo, 'app_user_minorplanets', 'minorplanet_id', $userId);
|
|
ensureFavoriteIfNeeded($pdo, 'app_user_comets', 'comet_id', $userId);
|
|
}
|
|
|
|
$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();
|
|
}
|
|
|
|
if ($cometSearchQuery !== '') {
|
|
$stmtCometSearch = $pdo->prepare("
|
|
SELECT
|
|
c.`id`,
|
|
c.`designation_and_name`,
|
|
c.`designation_packed`,
|
|
c.`orbit_type`,
|
|
CASE
|
|
WHEN auc.`comet_id` IS NULL THEN 0
|
|
ELSE 1
|
|
END AS `already_added`
|
|
FROM `comets_mpc` c
|
|
LEFT JOIN `app_user_comets` auc
|
|
ON auc.`comet_id` = c.`id`
|
|
AND auc.`user_id` = :user_id
|
|
WHERE c.`designation_and_name` LIKE :query
|
|
OR c.`designation_packed` LIKE :query
|
|
ORDER BY c.`orbit_type` ASC, c.`designation_and_name` ASC
|
|
LIMIT 50
|
|
");
|
|
$stmtCometSearch->execute([
|
|
':user_id' => $userId,
|
|
':query' => '%' . $cometSearchQuery . '%',
|
|
]);
|
|
|
|
$cometSearchResults = $stmtCometSearch->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();
|
|
|
|
$stmtTrackedComets = $pdo->prepare("
|
|
SELECT
|
|
c.`id`,
|
|
c.`designation_and_name`,
|
|
c.`orbit_type`,
|
|
auc.`is_favorite`,
|
|
auc.`created_at`
|
|
FROM `app_user_comets` auc
|
|
INNER JOIN `comets_mpc` c
|
|
ON c.`id` = auc.`comet_id`
|
|
WHERE auc.`user_id` = :user_id
|
|
ORDER BY auc.`is_favorite` DESC, c.`orbit_type` ASC, c.`designation_and_name` ASC
|
|
");
|
|
$stmtTrackedComets->execute([
|
|
':user_id' => $userId,
|
|
]);
|
|
$trackedComets = $stmtTrackedComets->fetchAll();
|
|
|
|
if (isHtmxRequest()) {
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET' && array_key_exists('sat_q', $_GET)) {
|
|
echo renderSatelliteSearchResults($satelliteSearchQuery, $satelliteSearchResults);
|
|
exit;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET' && array_key_exists('mp_q', $_GET)) {
|
|
echo renderMinorplanetSearchResults($minorplanetSearchQuery, $minorplanetSearchResults);
|
|
exit;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET' && array_key_exists('com_q', $_GET)) {
|
|
echo renderCometSearchResults($cometSearchQuery, $cometSearchResults);
|
|
exit;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
if ($htmxTriggers !== []) {
|
|
header('HX-Trigger: ' . (string) json_encode($htmxTriggers));
|
|
}
|
|
|
|
echo renderMessages($successMessage, $errors);
|
|
echo renderLocationsSection($locations, true);
|
|
echo renderSatelliteSearchResults($satelliteSearchQuery, $satelliteSearchResults, true);
|
|
echo renderMinorplanetSearchResults($minorplanetSearchQuery, $minorplanetSearchResults, true);
|
|
echo renderCometSearchResults($cometSearchQuery, $cometSearchResults, true);
|
|
echo renderTrackedSatellitesSection($trackedSatellites, true);
|
|
echo renderTrackedMinorplanetsSection($trackedMinorplanets, true);
|
|
echo renderTrackedCometsSection($trackedComets, true);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
$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=""
|
|
>
|
|
|
|
<style>
|
|
.favorites-tabs {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin: 1.5rem 0 1.75rem;
|
|
border-bottom: 1px solid var(--border);
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.favorites-tab-button {
|
|
border: 1px solid transparent;
|
|
border-bottom: none;
|
|
background: transparent;
|
|
color: var(--text-dim);
|
|
border-radius: var(--radius) var(--radius) 0 0;
|
|
padding: 0.6rem 1.25rem;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
font-size: 0.92rem;
|
|
letter-spacing: 0.03em;
|
|
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
position: relative;
|
|
bottom: -1px;
|
|
}
|
|
|
|
.favorites-tab-button:hover {
|
|
color: var(--gold-pale);
|
|
background: rgba(201, 168, 76, 0.06);
|
|
border-color: var(--border);
|
|
}
|
|
|
|
.favorites-tab-button[aria-selected="true"] {
|
|
color: var(--gold-pale);
|
|
background: var(--bg-card);
|
|
border-color: var(--border);
|
|
border-bottom-color: var(--bg-card);
|
|
}
|
|
|
|
.favorites-tab-panel[hidden] {
|
|
display: none !important;
|
|
}
|
|
|
|
.favorites-tab-panel {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
</style>
|
|
|
|
<?= renderMessages($successMessage, $errors) ?>
|
|
|
|
<div class="favorites-tabs" role="tablist" aria-label="Favoriten-Bereiche">
|
|
<button type="button" class="favorites-tab-button" id="favorites-tab-locations" data-tab-target="favorites-panel-locations" role="tab" aria-selected="true" aria-controls="favorites-panel-locations">Standorte</button>
|
|
<button type="button" class="favorites-tab-button" id="favorites-tab-satellites" data-tab-target="favorites-group-satellites" role="tab" aria-selected="false" aria-controls="favorites-panel-search" tabindex="-1">Satelliten</button>
|
|
<button type="button" class="favorites-tab-button" id="favorites-tab-minorplanets" data-tab-target="favorites-group-minorplanets" role="tab" aria-selected="false" aria-controls="favorites-panel-search" tabindex="-1">Kleinplaneten</button>
|
|
<button type="button" class="favorites-tab-button" id="favorites-tab-comets" data-tab-target="favorites-group-comets" role="tab" aria-selected="false" aria-controls="favorites-panel-search" tabindex="-1">Kometen</button>
|
|
</div>
|
|
|
|
<section id="favorites-panel-locations" class="favorites-tab-panel" role="tabpanel" aria-labelledby="favorites-tab-locations">
|
|
<div class="grid-2">
|
|
<div class="card" id="locations-section">
|
|
<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 class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h($location['name']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h((string) $location['latitude']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h((string) $location['longitude']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h($location['elevation'] !== null ? (string) $location['elevation'] . ' m' : '-') ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= h($location['timezone']) ?></td>
|
|
<td class="<?= (int) $location['is_default'] === 1 ? 'favorite-text' : '' ?>"><?= (int) $location['is_default'] === 1 ? '*' : '-' ?></td>
|
|
<td class="action-cell">
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-primary js-edit-location"
|
|
data-location-id="<?= (int) $location['id'] ?>"
|
|
data-location-name="<?= h((string) $location['name']) ?>"
|
|
data-location-latitude="<?= h((string) $location['latitude']) ?>"
|
|
data-location-longitude="<?= h((string) $location['longitude']) ?>"
|
|
data-location-elevation="<?= h($location['elevation'] !== null ? (string) $location['elevation'] : '') ?>"
|
|
data-location-timezone="<?= h((string) $location['timezone']) ?>"
|
|
data-location-default="<?= (int) $location['is_default'] === 1 ? '1' : '0' ?>"
|
|
>Bearbeiten</button>
|
|
<?php if ((int) $location['is_default'] !== 1): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Standard-Standort wird gesetzt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 favorites-loading-form-htmx" data-loading-message="Standort wird entfernt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML" 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">Löschen</button>
|
|
</form>
|
|
<?php else: ?>
|
|
<span class="hint">Letzter Standort</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="card" data-favorites-group="satellites">
|
|
<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="" id="add-location-form" class="form-stack mt-xl favorites-loading-form favorites-loading-form-htmx" data-loading-message="Standort wird gespeichert..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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
|
|
aria-describedby="add-location-name-error"
|
|
value="<?= h($_POST['name'] ?? '') ?>"
|
|
>
|
|
<p
|
|
id="add-location-name-error"
|
|
class="favorites-loading-hint favorites-loading-hint-error"
|
|
data-validation-hint="name"
|
|
<?= (($_POST['action'] ?? '') === 'add_location' && trim((string) ($_POST['name'] ?? '')) === '') ? '' : 'hidden' ?>
|
|
>Bitte einen Namen für den Standort eingeben.</p>
|
|
</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" data-loading-hint hidden>Standort wird gespeichert...</p>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<dialog class="admin-dialog" id="editLocationDialog">
|
|
<form method="post" action="" class="admin-dialog-card form-stack favorites-loading-form favorites-loading-form-htmx" data-loading-message="Standort wird aktualisiert..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<input type="hidden" name="action" value="update_location">
|
|
<input type="hidden" name="location_id" id="edit-location-id" value="">
|
|
<h3>Standort bearbeiten</h3>
|
|
<div class="grid-2 gap-sm">
|
|
<div class="form-group">
|
|
<label for="edit-location-name">Name</label>
|
|
<input type="text" id="edit-location-name" name="name" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-location-timezone">Zeitzone (IANA)</label>
|
|
<input type="text" id="edit-location-timezone" name="timezone" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-location-latitude">Breitengrad</label>
|
|
<input type="text" id="edit-location-latitude" name="latitude" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-location-longitude">Längengrad</label>
|
|
<input type="text" id="edit-location-longitude" name="longitude" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-location-elevation">Höhe (optional, Meter)</label>
|
|
<input type="text" id="edit-location-elevation" name="elevation">
|
|
</div>
|
|
</div>
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="edit-location-default" name="is_default" value="1">
|
|
Als Standard-Standort speichern
|
|
</label>
|
|
<div class="admin-dialog-actions">
|
|
<button type="button" class="btn btn-secondary" id="editLocationCancel">Abbrechen</button>
|
|
<button type="submit" class="btn btn-primary">Änderungen speichern</button>
|
|
</div>
|
|
<p class="favorites-loading-hint" data-loading-hint hidden>Standort wird aktualisiert...</p>
|
|
</form>
|
|
</dialog>
|
|
|
|
<section id="favorites-panel-search" class="favorites-tab-panel" role="tabpanel" aria-labelledby="favorites-tab-search" hidden>
|
|
<div class="grid-2">
|
|
<div class="card" data-favorites-group="satellites">
|
|
<h2>Satelliten suchen</h2>
|
|
<form method="get" action="" class="favorites-loading-form favorites-loading-form-htmx favorites-search-form" data-loading-message="Satelliten werden gesucht. Das kann kurz dauern..." hx-get="my_favorites.php" hx-target="#satellite-search-results" hx-swap="outerHTML" hx-push-url="false">
|
|
<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" data-loading-hint hidden>Satelliten werden gesucht. Das kann kurz dauern...</p>
|
|
</form>
|
|
|
|
<div id="satellite-search-results" class="favorites-search-results<?= $satelliteSearchQuery !== '' ? ' has-results' : '' ?>" aria-live="polite">
|
|
<?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): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Satellit wird hinzugefuegt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 else: ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Noch keine Suche gestartet.</p>
|
|
<p class="hint">Suche nach Namen oder NORAD-ID, um passende Satelliten direkt hier einzublenden.</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" data-favorites-group="minorplanets">
|
|
<h2>Kleinplaneten suchen</h2>
|
|
<form
|
|
method="get"
|
|
action=""
|
|
class="favorites-loading-form favorites-loading-form-htmx favorites-search-form"
|
|
data-loading-message="Kleinplaneten werden gesucht. Das kann etwas dauern..."
|
|
hx-get="my_favorites.php"
|
|
hx-target="#minorplanet-search-results"
|
|
hx-swap="outerHTML"
|
|
hx-push-url="false"
|
|
>
|
|
<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" data-loading-hint hidden>Kleinplaneten werden gesucht. Das kann etwas dauern...</p>
|
|
</form>
|
|
|
|
<div id="minorplanet-search-results" class="favorites-search-results<?= $minorplanetSearchQuery !== '' ? ' has-results' : '' ?>" aria-live="polite">
|
|
<?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): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Kleinplanet wird hinzugefuegt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 else: ?>
|
|
<div class="favorites-search-empty">
|
|
<p>Noch keine Suche gestartet.</p>
|
|
<p class="hint">Suche nach Nummer oder Bezeichnung, um passende Kleinplaneten direkt hier einzublenden.</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" data-favorites-group="comets">
|
|
<h2>Kometen suchen</h2>
|
|
<form
|
|
method="get"
|
|
action=""
|
|
class="favorites-loading-form favorites-loading-form-htmx favorites-search-form"
|
|
data-loading-message="Kometen werden gesucht. Das kann etwas dauern..."
|
|
hx-get="my_favorites.php"
|
|
hx-target="#comet-search-results"
|
|
hx-swap="outerHTML"
|
|
hx-push-url="false"
|
|
>
|
|
<div class="input-row">
|
|
<input
|
|
type="text"
|
|
id="com_q"
|
|
name="com_q"
|
|
value="<?= h($cometSearchQuery) ?>"
|
|
placeholder="Bezeichnung oder Name, z. B. 67P oder Halley"
|
|
>
|
|
<button type="submit" class="btn btn-primary">Suchen</button>
|
|
</div>
|
|
<p class="favorites-loading-hint" data-loading-hint hidden>Kometen werden gesucht. Das kann etwas dauern...</p>
|
|
</form>
|
|
|
|
<?= renderCometSearchResults($cometSearchQuery, $cometSearchResults) ?>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="favorites-panel-tracked" class="favorites-tab-panel" role="tabpanel" aria-labelledby="favorites-tab-tracked" hidden>
|
|
<div class="grid-2">
|
|
<div class="card" id="tracked-satellites-section" data-favorites-group="satellites">
|
|
<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></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($trackedSatellites as $satellite): ?>
|
|
<tr>
|
|
<td class="<?= (int) $satellite['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h($satellite['object_name']) ?></td>
|
|
<td class="<?= (int) $satellite['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h((string) $satellite['norad_cat_id']) ?></td>
|
|
<td class="<?= (int) $satellite['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= (int) $satellite['is_favorite'] === 1 ? '*' : '-' ?></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 favorites-loading-form-htmx" data-loading-message="Satelliten-Favorit wird aktualisiert..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 ? ' favorite-active' : '' ?>">
|
|
<?= (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 favorites-loading-form-htmx" data-loading-message="Satellit wird entfernt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML" 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" id="tracked-minorplanets-section" data-favorites-group="minorplanets">
|
|
<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></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($trackedMinorplanets as $minorplanet): ?>
|
|
<tr>
|
|
<td class="<?= (int) $minorplanet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h($minorplanet['designation_text'] ?: $minorplanet['desig_packed']) ?></td>
|
|
<td class="<?= (int) $minorplanet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= h($minorplanet['mp_number'] !== null ? (string) $minorplanet['mp_number'] : '-') ?></td>
|
|
<td class="<?= (int) $minorplanet['is_favorite'] === 1 ? 'favorite-text' : '' ?>"><?= (int) $minorplanet['is_favorite'] === 1 ? '*' : '-' ?></td>
|
|
<td class="action-cell">
|
|
<?php if (count($trackedMinorplanets) <= 1 && (int) $minorplanet['is_favorite'] === 1): ?>
|
|
<span class="hint">Favorit bleibt gesetzt</span>
|
|
<?php else: ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Kleinplaneten-Favorit wird aktualisiert..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML">
|
|
<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 ? ' favorite-active' : '' ?>">
|
|
<?= (int) $minorplanet['is_favorite'] === 1 ? 'Favorit entfernen' : 'Als Favorit' ?>
|
|
</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
<?php if (count($trackedMinorplanets) > 1): ?>
|
|
<form method="post" action="" class="favorites-loading-form favorites-loading-form-htmx" data-loading-message="Kleinplanet wird entfernt..." hx-post="my_favorites.php" hx-target="#favorites-messages" hx-swap="outerHTML" 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>
|
|
<?php else: ?>
|
|
<span class="hint">Letzter Kleinplanet</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?= renderTrackedCometsSection($trackedComets) ?>
|
|
</div>
|
|
</section>
|
|
|
|
<script src="js/htmx.min.js"></script>
|
|
<script
|
|
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
|
crossorigin=""
|
|
></script>
|
|
|
|
<script>
|
|
(function () {
|
|
const tabButtons = Array.from(document.querySelectorAll('.favorites-tab-button'));
|
|
const tabPanels = Array.from(document.querySelectorAll('.favorites-tab-panel'));
|
|
const locationsPanel = document.getElementById('favorites-panel-locations');
|
|
const searchPanel = document.getElementById('favorites-panel-search');
|
|
const trackedPanel = document.getElementById('favorites-panel-tracked');
|
|
if (tabButtons.length === 0 || tabPanels.length === 0 || !locationsPanel || !searchPanel || !trackedPanel) {
|
|
return;
|
|
}
|
|
|
|
let currentPanelId = 'favorites-panel-locations';
|
|
|
|
function activateTab(panelId, focusButton = false) {
|
|
currentPanelId = panelId;
|
|
tabButtons.forEach((button) => {
|
|
const isActive = button.dataset.tabTarget === panelId;
|
|
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
button.tabIndex = isActive ? 0 : -1;
|
|
if (isActive && focusButton) {
|
|
button.focus();
|
|
}
|
|
});
|
|
|
|
// Immer frisch abfragen, da HTMX OOB-Swaps die DOM-Elemente ersetzen.
|
|
const groupCards = Array.from(document.querySelectorAll('[data-favorites-group]'));
|
|
|
|
if (panelId === 'favorites-panel-locations') {
|
|
locationsPanel.hidden = false;
|
|
searchPanel.hidden = true;
|
|
trackedPanel.hidden = true;
|
|
groupCards.forEach((card) => {
|
|
card.hidden = false;
|
|
});
|
|
return;
|
|
}
|
|
|
|
const groupName = panelId.replace('favorites-group-', '');
|
|
locationsPanel.hidden = true;
|
|
searchPanel.hidden = false;
|
|
trackedPanel.hidden = false;
|
|
|
|
groupCards.forEach((card) => {
|
|
card.hidden = card.dataset.favoritesGroup !== groupName;
|
|
});
|
|
}
|
|
|
|
tabButtons.forEach((button, index) => {
|
|
button.addEventListener('click', () => {
|
|
activateTab(button.dataset.tabTarget);
|
|
});
|
|
|
|
button.addEventListener('keydown', (event) => {
|
|
if (!['ArrowRight', 'ArrowLeft', 'Home', 'End'].includes(event.key)) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
let nextIndex = index;
|
|
if (event.key === 'ArrowRight') {
|
|
nextIndex = (index + 1) % tabButtons.length;
|
|
} else if (event.key === 'ArrowLeft') {
|
|
nextIndex = (index - 1 + tabButtons.length) % tabButtons.length;
|
|
} else if (event.key === 'Home') {
|
|
nextIndex = 0;
|
|
} else if (event.key === 'End') {
|
|
nextIndex = tabButtons.length - 1;
|
|
}
|
|
|
|
activateTab(tabButtons[nextIndex].dataset.tabTarget, true);
|
|
});
|
|
});
|
|
|
|
// Nach HTMX-OOB-Swap das aktive Tab erneut anwenden,
|
|
// da neu gerenderte Elemente kein hidden-Attribut haben.
|
|
document.body.addEventListener('htmx:afterSettle', function () {
|
|
activateTab(currentPanelId);
|
|
});
|
|
})();
|
|
|
|
(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: '© 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', (event) => {
|
|
if (form.classList.contains('favorites-loading-form-htmx')) {
|
|
return;
|
|
}
|
|
|
|
if (form.id === 'add-location-form') {
|
|
const nameField = form.querySelector('#name');
|
|
const nameError = document.getElementById('add-location-name-error');
|
|
|
|
if (nameField) {
|
|
const trimmedName = nameField.value.trim();
|
|
|
|
if (trimmedName === '') {
|
|
event.preventDefault();
|
|
nameField.value = '';
|
|
nameField.focus();
|
|
|
|
if (nameError) {
|
|
nameError.textContent = 'Bitte einen Namen für den Standort eingeben.';
|
|
nameError.hidden = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (nameError) {
|
|
nameError.hidden = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const message = form.dataset.loadingMessage || 'Bitte warten...';
|
|
let hint = form.querySelector('[data-loading-hint]');
|
|
|
|
if (!hint) {
|
|
hint = document.createElement('p');
|
|
hint.className = 'favorites-loading-hint';
|
|
hint.dataset.loadingHint = 'true';
|
|
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;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
(function () {
|
|
function setLoadingState(form) {
|
|
if (!form) {
|
|
return;
|
|
}
|
|
|
|
const message = form.dataset.loadingMessage || 'Bitte warten...';
|
|
let hint = form.querySelector('[data-loading-hint]');
|
|
|
|
if (!hint) {
|
|
hint = document.createElement('p');
|
|
hint.className = 'favorites-loading-hint';
|
|
hint.dataset.loadingHint = 'true';
|
|
form.appendChild(hint);
|
|
}
|
|
|
|
hint.textContent = message;
|
|
hint.hidden = false;
|
|
form.classList.add('is-loading');
|
|
|
|
form.querySelectorAll('button').forEach((button) => {
|
|
button.disabled = true;
|
|
button.setAttribute('aria-busy', 'true');
|
|
});
|
|
}
|
|
|
|
function clearLoadingState(form) {
|
|
if (!form) {
|
|
return;
|
|
}
|
|
|
|
form.classList.remove('is-loading');
|
|
|
|
const hint = form.querySelector('[data-loading-hint]');
|
|
if (hint) {
|
|
hint.hidden = true;
|
|
}
|
|
|
|
form.querySelectorAll('button').forEach((button) => {
|
|
button.disabled = false;
|
|
button.removeAttribute('aria-busy');
|
|
});
|
|
}
|
|
|
|
document.body.addEventListener('htmx:beforeRequest', (event) => {
|
|
const form = event.target.closest('.favorites-loading-form-htmx');
|
|
setLoadingState(form);
|
|
});
|
|
|
|
document.body.addEventListener('htmx:afterRequest', (event) => {
|
|
const form = event.target.closest('.favorites-loading-form-htmx');
|
|
clearLoadingState(form);
|
|
});
|
|
|
|
document.body.addEventListener('htmx:responseError', (event) => {
|
|
const form = event.target.closest('.favorites-loading-form-htmx');
|
|
clearLoadingState(form);
|
|
});
|
|
})();
|
|
|
|
(function () {
|
|
const dialog = document.getElementById('editLocationDialog');
|
|
const cancelButton = document.getElementById('editLocationCancel');
|
|
|
|
if (!dialog || typeof dialog.showModal !== 'function') {
|
|
return;
|
|
}
|
|
|
|
const idInput = document.getElementById('edit-location-id');
|
|
const nameInput = document.getElementById('edit-location-name');
|
|
const latitudeInput = document.getElementById('edit-location-latitude');
|
|
const longitudeInput = document.getElementById('edit-location-longitude');
|
|
const elevationInput = document.getElementById('edit-location-elevation');
|
|
const timezoneInput = document.getElementById('edit-location-timezone');
|
|
const defaultInput = document.getElementById('edit-location-default');
|
|
|
|
document.addEventListener('click', (event) => {
|
|
const button = event.target.closest('.js-edit-location');
|
|
if (!button) {
|
|
return;
|
|
}
|
|
|
|
idInput.value = button.dataset.locationId || '';
|
|
nameInput.value = button.dataset.locationName || '';
|
|
latitudeInput.value = button.dataset.locationLatitude || '';
|
|
longitudeInput.value = button.dataset.locationLongitude || '';
|
|
elevationInput.value = button.dataset.locationElevation || '';
|
|
timezoneInput.value = button.dataset.locationTimezone || 'Europe/Berlin';
|
|
defaultInput.checked = button.dataset.locationDefault === '1';
|
|
dialog.showModal();
|
|
});
|
|
|
|
cancelButton?.addEventListener('click', () => {
|
|
dialog.close();
|
|
});
|
|
|
|
document.body.addEventListener('favorites:location-updated', () => {
|
|
dialog.close();
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
<?php require __DIR__ . '/footer.php'; ?>
|