diff --git a/.gitignore b/.gitignore
index 6ee131a..523cd3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,6 @@
-<<<<<<< HEAD
+.codex-temp/
php-server.log
php-server.err.log
php-server-error.log
-=======
-.codex-temp/
-php-server-error.log
->>>>>>> fc3264cc2fa76249c40e422de2381eb7c6920c28
+__pycache__/
+*.pyc
diff --git a/docs/README.md b/docs/README.md
index 531a1b6..467e0bf 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -10,6 +10,8 @@ Hier liegen die internen Anleitungen und technischen Hilfen für das Projekt.
Lokale Startanleitung für den PHP-Webserver auf den bekannten Arbeitsplätzen
- [`docs/README_RECHTESYSTEM.md`](README_RECHTESYSTEM.md)
Beschreibung des aktuellen Rollen- und Seitenrechtesystems
+- [`docs/README_DATENBANKABFRAGEN.md`](README_DATENBANKABFRAGEN.md)
+ Kurzanleitung fuer lokale Datenbankabfragen mit der vorhandenen Projektkonfiguration
## Hinweis
diff --git a/docs/README_DATENBANKABFRAGEN.md b/docs/README_DATENBANKABFRAGEN.md
new file mode 100644
index 0000000..853089d
--- /dev/null
+++ b/docs/README_DATENBANKABFRAGEN.md
@@ -0,0 +1,83 @@
+# Datenbankabfragen
+
+Diese Notiz beschreibt, wie in diesem Projekt lokale Datenbankabfragen ausgefuehrt werden koennen.
+
+## Zugangsdaten
+
+Die Verbindungsdaten liegen in:
+
+- `config/database.php`
+
+Dort stehen `host`, `dbname`, `user`, `pass` und `charset`.
+
+## Empfohlener Weg
+
+Am einfachsten ist eine kurze PHP-Datei oder ein kurzes PHP-Skript, das die vorhandene Projektkonfiguration verwendet.
+
+Beispiel:
+
+```php
+ PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ ]
+);
+
+$stmt = $pdo->query("SELECT id, mp_number, designation_text FROM minorplanets_mpc WHERE mp_number = 1 LIMIT 1");
+$row = $stmt->fetch();
+
+echo json_encode($row, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+```
+
+## Ausfuehrung
+
+Wenn `php` nicht im Systempfad liegt, kann die lokale `php.exe` direkt verwendet werden.
+
+Beispiel von diesem Rechner:
+
+```powershell
+C:\laragon\bin\php\php-8.4.5-nts-Win32-vs17-x64\php.exe .codex-temp\check_defaults.php
+```
+
+## Praktische Beispielabfragen
+
+`Ceres` pruefen:
+
+```sql
+SELECT id, mp_number, designation_text, desig_packed
+FROM minorplanets_mpc
+WHERE mp_number = 1
+LIMIT 1;
+```
+
+`Halley` pruefen:
+
+```sql
+SELECT id, designation_and_name, orbit_type
+FROM comets_mpc
+WHERE designation_and_name LIKE '%Halley%'
+ORDER BY designation_and_name ASC, id ASC
+LIMIT 10;
+```
+
+## Hinweise
+
+- Temporaere Pruefskripte am besten in `.codex-temp/` anlegen und danach wieder loeschen.
+- Fuer Projektcode immer die vorhandene `config/database.php` verwenden und keine Zugangsdaten hart in Dateien eintragen.
+- Wenn nur geprueft werden soll, reichen `SELECT`-Abfragen ohne schreibende Operationen.
diff --git a/public/my_favorites.php b/public/my_favorites.php
index e66f49a..89a7f29 100644
--- a/public/my_favorites.php
+++ b/public/my_favorites.php
@@ -358,18 +358,26 @@ function renderTrackedMinorplanetsSection(array $trackedMinorplanets, bool $oob
= h($minorplanet['mp_number'] !== null ? (string) $minorplanet['mp_number'] : '-') ?> |
= (int) $minorplanet['is_favorite'] === 1 ? '*' : '-' ?> |
-
-
+
+ Favorit bleibt gesetzt
+
+
+
+ 1): ?>
+
+
+ Letzter Kleinplanet
+
|
@@ -470,18 +478,26 @@ function renderTrackedCometsSection(array $trackedComets, bool $oob = false): st
= h((string) $comet['orbit_type']) ?> |
= (int) $comet['is_favorite'] === 1 ? '*' : '-' ?> |
-
-
+
+ Favorit bleibt gesetzt
+
+
+
+ 1): ?>
+
+
+ Letzter Komet
+
|
@@ -566,6 +582,35 @@ function setExclusiveFavorite(PDO $pdo, string $tableName, string $idColumn, int
':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) {
@@ -590,6 +635,51 @@ function countUserRows(PDO $pdo, string $tableName, int $userId): int
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'] ?? ''));
@@ -879,6 +969,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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`
@@ -901,6 +998,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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.';
@@ -965,6 +1088,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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`
@@ -987,6 +1117,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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.';
@@ -1329,6 +1485,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
+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`,
@@ -1987,18 +2148,26 @@ $loggedIn = true;
= h($minorplanet['mp_number'] !== null ? (string) $minorplanet['mp_number'] : '-') ?> |
= (int) $minorplanet['is_favorite'] === 1 ? '*' : '-' ?> |
-
-
+
+ Favorit bleibt gesetzt
+
+
+
+ 1): ?>
+
+
+ Letzter Kleinplanet
+
|
diff --git a/public/register.php b/public/register.php
index 8d4781b..a8a27e6 100644
--- a/public/register.php
+++ b/public/register.php
@@ -33,6 +33,8 @@ const DEFAULT_LOCATION_LONGITUDE = 11.189;
const DEFAULT_LOCATION_ELEVATION = 640.0;
const DEFAULT_LOCATION_TIMEZONE = 'Europe/Berlin';
const DEFAULT_SATELLITE_ID = 61;
+const DEFAULT_MINORPLANET_NUMBER = 1;
+const DEFAULT_COMET_SEARCH = 'Halley';
function sendRegistrationNotification(string $username, string $email, ?string $displayName): void
{
@@ -77,6 +79,39 @@ function textLength(string $value): int
return strlen($value);
}
+function findDefaultMinorplanetId(PDO $pdo): ?int
+{
+ $stmt = $pdo->prepare("
+ SELECT `id`
+ FROM `minorplanets_mpc`
+ WHERE `mp_number` = :mp_number
+ LIMIT 1
+ ");
+ $stmt->execute([
+ ':mp_number' => DEFAULT_MINORPLANET_NUMBER,
+ ]);
+ $id = $stmt->fetchColumn();
+
+ return $id !== false ? (int) $id : null;
+}
+
+function findDefaultCometId(PDO $pdo): ?int
+{
+ $stmt = $pdo->prepare("
+ SELECT `id`
+ FROM `comets_mpc`
+ WHERE `designation_and_name` LIKE :search
+ ORDER BY `designation_and_name` ASC, `id` ASC
+ LIMIT 1
+ ");
+ $stmt->execute([
+ ':search' => '%' . DEFAULT_COMET_SEARCH . '%',
+ ]);
+ $id = $stmt->fetchColumn();
+
+ return $id !== false ? (int) $id : null;
+}
+
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$email = trim($_POST['email'] ?? '');
@@ -131,6 +166,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($errors)) {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
+ $defaultMinorplanetId = findDefaultMinorplanetId($pdo);
+ $defaultCometId = findDefaultCometId($pdo);
+
+ if ($defaultMinorplanetId === null) {
+ $errors[] = 'Der Standard-Kleinplanet Ceres wurde in der Datenbank nicht gefunden.';
+ }
+
+ if ($defaultCometId === null) {
+ $errors[] = 'Der Standard-Komet Halley wurde in der Datenbank nicht gefunden.';
+ }
+ }
+
+ if (empty($errors)) {
$pdo->beginTransaction();
try {
@@ -215,6 +263,42 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
':satellite_id' => DEFAULT_SATELLITE_ID,
]);
+ $stmtInsertMinorplanet = $pdo->prepare("
+ INSERT INTO `app_user_minorplanets` (
+ `user_id`,
+ `minorplanet_id`,
+ `is_favorite`,
+ `created_at`
+ ) VALUES (
+ :user_id,
+ :minorplanet_id,
+ 1,
+ NOW()
+ )
+ ");
+ $stmtInsertMinorplanet->execute([
+ ':user_id' => $userId,
+ ':minorplanet_id' => $defaultMinorplanetId,
+ ]);
+
+ $stmtInsertComet = $pdo->prepare("
+ INSERT INTO `app_user_comets` (
+ `user_id`,
+ `comet_id`,
+ `is_favorite`,
+ `created_at`
+ ) VALUES (
+ :user_id,
+ :comet_id,
+ 1,
+ NOW()
+ )
+ ");
+ $stmtInsertComet->execute([
+ ':user_id' => $userId,
+ ':comet_id' => $defaultCometId,
+ ]);
+
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {