STandort jetzt in Favs
This commit is contained in:
@@ -54,3 +54,154 @@
|
|||||||
[Sat Apr 4 18:24:44 2026] 127.0.0.1:56614 Accepted
|
[Sat Apr 4 18:24:44 2026] 127.0.0.1:56614 Accepted
|
||||||
[Sat Apr 4 18:25:51 2026] 127.0.0.1:56614 Closed without sending a request; it was probably just an unused speculative preconnection
|
[Sat Apr 4 18:25:51 2026] 127.0.0.1:56614 Closed without sending a request; it was probably just an unused speculative preconnection
|
||||||
[Sat Apr 4 18:25:51 2026] 127.0.0.1:56614 Closing
|
[Sat Apr 4 18:25:51 2026] 127.0.0.1:56614 Closing
|
||||||
|
[Sat Apr 4 18:38:50 2026] 127.0.0.1:62616 Accepted
|
||||||
|
[Sat Apr 4 18:39:01 2026] PHP Warning: Undefined variable $monthStartLocal in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1928
|
||||||
|
[Sat Apr 4 18:39:01 2026] PHP Warning: Undefined variable $monthEndLocal in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1928
|
||||||
|
[Sat Apr 4 18:39:01 2026] PHP Fatal error: Uncaught TypeError: findMonthForecastSolarLongitudeCrossing(): Argument #1 ($rangeStartLocal) must be of type DateTimeImmutable, null given, called in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1927 and defined in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php:488
|
||||||
|
Stack trace:
|
||||||
|
#0 C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php(1927): findMonthForecastSolarLongitudeCrossing(NULL, NULL, 0.0, Object(DateTimeZone))
|
||||||
|
#1 {main}
|
||||||
|
thrown in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 488
|
||||||
|
[Sat Apr 4 18:39:01 2026] 127.0.0.1:62616 [200]: GET /wochenvorhersage.php?year=2026&week=14 - Uncaught TypeError: findMonthForecastSolarLongitudeCrossing(): Argument #1 ($rangeStartLocal) must be of type DateTimeImmutable, null given, called in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1927 and defined in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php:488
|
||||||
|
Stack trace:
|
||||||
|
#0 C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php(1927): findMonthForecastSolarLongitudeCrossing(NULL, NULL, 0.0, Object(DateTimeZone))
|
||||||
|
#1 {main}
|
||||||
|
thrown in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 488
|
||||||
|
[Sat Apr 4 18:39:01 2026] 127.0.0.1:62616 Closing
|
||||||
|
[Sat Apr 4 18:39:55 2026] 127.0.0.1:52595 Accepted
|
||||||
|
[Sat Apr 4 18:40:06 2026] PHP Warning: Undefined variable $monthStartLocal in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1928
|
||||||
|
[Sat Apr 4 18:40:06 2026] PHP Warning: Undefined variable $monthEndLocal in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1928
|
||||||
|
[Sat Apr 4 18:40:06 2026] PHP Fatal error: Uncaught TypeError: findMonthForecastSolarLongitudeCrossing(): Argument #1 ($rangeStartLocal) must be of type DateTimeImmutable, null given, called in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1927 and defined in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php:488
|
||||||
|
Stack trace:
|
||||||
|
#0 C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php(1927): findMonthForecastSolarLongitudeCrossing(NULL, NULL, 0.0, Object(DateTimeZone))
|
||||||
|
#1 {main}
|
||||||
|
thrown in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 488
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:52595 [200]: GET /wochenvorhersage.php?year=2026&week=14 - Uncaught TypeError: findMonthForecastSolarLongitudeCrossing(): Argument #1 ($rangeStartLocal) must be of type DateTimeImmutable, null given, called in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 1927 and defined in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php:488
|
||||||
|
Stack trace:
|
||||||
|
#0 C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php(1927): findMonthForecastSolarLongitudeCrossing(NULL, NULL, 0.0, Object(DateTimeZone))
|
||||||
|
#1 {main}
|
||||||
|
thrown in C:\Users\ich\source\repos\skyview.astronomiemuseum.de\public\wochenvorhersage.php on line 488
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:52595 Closing
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:57969 Accepted
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:57969 [200]: GET /css/style.css
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:57969 Closing
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:53036 Accepted
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:53036 [200]: GET /images/star16x16.png
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:49569 Accepted
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:49569 [200]: GET /images/star16x16_rays.png
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:53036 Closing
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:49569 Closing
|
||||||
|
[Sat Apr 4 18:40:06 2026] 127.0.0.1:49178 Accepted
|
||||||
|
[Sat Apr 4 18:40:07 2026] 127.0.0.1:49178 [200]: GET /favicon.ico
|
||||||
|
[Sat Apr 4 18:40:07 2026] 127.0.0.1:49178 Closing
|
||||||
|
[Sat Apr 4 18:40:59 2026] 127.0.0.1:49611 Accepted
|
||||||
|
[Sat Apr 4 18:41:10 2026] 127.0.0.1:49611 [200]: GET /wochenvorhersage.php?year=2026&week=14
|
||||||
|
[Sat Apr 4 18:41:10 2026] 127.0.0.1:49611 Closing
|
||||||
|
[Sat Apr 4 18:41:10 2026] 127.0.0.1:51173 Accepted
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:51173 [200]: GET /wochenvorhersage.php?year=2026&week=14
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:51173 Closing
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:58633 Accepted
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:51479 Accepted
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:58633 [200]: GET /css/style.css
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:51479 [200]: GET /js/astronomy.browser.min.js
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:58633 Closing
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:51479 Closing
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:61973 Accepted
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:61973 [200]: GET /images/star16x16.png
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:55305 Accepted
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:55305 [200]: GET /images/star16x16_rays.png
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:61973 Closing
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:55305 Closing
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:54170 Accepted
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:54170 [200]: GET /images/moons/moon4geocron.png
|
||||||
|
[Sat Apr 4 18:41:21 2026] 127.0.0.1:54170 Closing
|
||||||
|
[Sat Apr 4 18:41:22 2026] 127.0.0.1:58395 Accepted
|
||||||
|
[Sat Apr 4 18:41:22 2026] 127.0.0.1:58395 [200]: GET /favicon.ico
|
||||||
|
[Sat Apr 4 18:41:22 2026] 127.0.0.1:58395 Closing
|
||||||
|
[Sat Apr 4 19:19:57 2026] 127.0.0.1:49351 Accepted
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:49351 [200]: GET /wochenvorhersage.php?year=2026&week=53
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:49351 Closing
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:50066 Accepted
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:62275 Accepted
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:50066 [200]: GET /css/style.css
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:62275 [200]: GET /js/astronomy.browser.min.js
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:50066 Closing
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:62275 Closing
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:64582 Accepted
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:50907 Accepted
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:64582 [200]: GET /images/star16x16.png
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:50907 [200]: GET /images/star16x16_rays.png
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:64582 Closing
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:50907 Closing
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:49787 Accepted
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:49787 [200]: GET /images/moons/moon4geocron.png
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:49787 Closing
|
||||||
|
[Sat Apr 4 19:20:08 2026] 127.0.0.1:63843 Accepted
|
||||||
|
[Sat Apr 4 19:20:09 2026] 127.0.0.1:63843 [200]: GET /favicon.ico
|
||||||
|
[Sat Apr 4 19:20:09 2026] 127.0.0.1:63843 Closing
|
||||||
|
[Sat Apr 4 19:21:05 2026] 127.0.0.1:53592 Accepted
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:53592 [200]: GET /wochenvorhersage.php?year=2026&week=12
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:53592 Closing
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:55418 Accepted
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:64078 Accepted
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:55418 [200]: GET /css/style.css
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:64078 [200]: GET /js/astronomy.browser.min.js
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:55418 Closing
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:64078 Closing
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:50402 Accepted
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:50402 [200]: GET /images/star16x16.png
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:65488 Accepted
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:65488 [200]: GET /images/star16x16_rays.png
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:50402 Closing
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:65488 Closing
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:59610 Accepted
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:59610 [200]: GET /images/moons/moon4geocron.png
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:59610 Closing
|
||||||
|
[Sat Apr 4 19:21:20 2026] 127.0.0.1:58943 Accepted
|
||||||
|
[Sat Apr 4 19:21:21 2026] 127.0.0.1:58943 [200]: GET /favicon.ico
|
||||||
|
[Sat Apr 4 19:21:21 2026] 127.0.0.1:58943 Closing
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:61751 Accepted
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:61751 [200]: GET /settings.php
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:61751 Closing
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:51920 Accepted
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:51920 [200]: GET /css/style.css
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:51920 Closing
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:60986 Accepted
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:52887 Accepted
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:60986 [200]: GET /images/star16x16.png
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:52887 [200]: GET /images/star16x16_rays.png
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:60986 Closing
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:52887 Closing
|
||||||
|
[Sat Apr 4 19:22:27 2026] 127.0.0.1:61244 Accepted
|
||||||
|
[Sat Apr 4 19:22:28 2026] 127.0.0.1:61244 [200]: GET /favicon.ico
|
||||||
|
[Sat Apr 4 19:22:28 2026] 127.0.0.1:61244 Closing
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:55136 Accepted
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:55136 [200]: GET /settings.php
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:55136 Closing
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:58715 Accepted
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:58715 [200]: GET /css/style.css
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:58715 Closing
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:59506 Accepted
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:59506 [200]: GET /images/star16x16.png
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:49656 Accepted
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:49656 [200]: GET /images/star16x16_rays.png
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:59506 Closing
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:49656 Closing
|
||||||
|
[Sat Apr 4 19:33:35 2026] 127.0.0.1:53331 Accepted
|
||||||
|
[Sat Apr 4 19:33:36 2026] 127.0.0.1:53331 [200]: GET /favicon.ico
|
||||||
|
[Sat Apr 4 19:33:36 2026] 127.0.0.1:53331 Closing
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:60359 Accepted
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:60359 [200]: GET /my_favorites.php
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:60359 Closing
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:57196 Accepted
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:57196 [200]: GET /css/style.css
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:57196 Closing
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:57348 Accepted
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:52349 Accepted
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:57348 [200]: GET /images/star16x16.png
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:52349 [200]: GET /images/star16x16_rays.png
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:57348 Closing
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:52349 Closing
|
||||||
|
[Sat Apr 4 19:33:39 2026] 127.0.0.1:51368 Accepted
|
||||||
|
[Sat Apr 4 19:33:40 2026] 127.0.0.1:51368 [200]: GET /favicon.ico
|
||||||
|
[Sat Apr 4 19:33:40 2026] 127.0.0.1:51368 Closing
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ function h(?string $value): string
|
|||||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validTimezone(string $timezone): bool
|
||||||
|
{
|
||||||
|
return in_array($timezone, timezone_identifiers_list(), true);
|
||||||
|
}
|
||||||
|
|
||||||
function setExclusiveFavorite(PDO $pdo, string $tableName, string $idColumn, int $userId, int $entityId): bool
|
function setExclusiveFavorite(PDO $pdo, string $tableName, string $idColumn, int $userId, int $entityId): bool
|
||||||
{
|
{
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
@@ -278,8 +283,222 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 fuer den Standort eingeben.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($latitude === '' || !is_numeric($latitude)) {
|
||||||
|
$errors[] = 'Bitte einen gueltigen 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 gueltigen Laengengrad eingeben.';
|
||||||
|
} else {
|
||||||
|
$lon = (float) $longitude;
|
||||||
|
if ($lon < -180 || $lon > 180) {
|
||||||
|
$errors[] = 'Der Laengengrad muss zwischen -180 und 180 liegen.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($elevation !== '' && !is_numeric($elevation)) {
|
||||||
|
$errors[] = 'Die Hoehe muss numerisch sein.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validTimezone($timezone)) {
|
||||||
|
$errors[] = 'Bitte eine gueltige IANA-Zeitzone angeben, z. B. Europe/Berlin.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($errors)) {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($isDefault === 1) {
|
||||||
|
$stmtReset = $pdo->prepare("
|
||||||
|
UPDATE `app_user_locations`
|
||||||
|
SET `is_default` = 0
|
||||||
|
WHERE `user_id` = :user_id
|
||||||
|
");
|
||||||
|
$stmtReset->execute([
|
||||||
|
':user_id' => $userId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtInsertLocation = $pdo->prepare("
|
||||||
|
INSERT INTO `app_user_locations` (
|
||||||
|
`user_id`,
|
||||||
|
`name`,
|
||||||
|
`latitude`,
|
||||||
|
`longitude`,
|
||||||
|
`elevation`,
|
||||||
|
`timezone`,
|
||||||
|
`is_default`,
|
||||||
|
`created_at`,
|
||||||
|
`updated_at`
|
||||||
|
) VALUES (
|
||||||
|
:user_id,
|
||||||
|
:name,
|
||||||
|
:latitude,
|
||||||
|
:longitude,
|
||||||
|
:elevation,
|
||||||
|
:timezone,
|
||||||
|
:is_default,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$stmtInsertLocation->execute([
|
||||||
|
':user_id' => $userId,
|
||||||
|
':name' => $name,
|
||||||
|
':latitude' => (float) $latitude,
|
||||||
|
':longitude' => (float) $longitude,
|
||||||
|
':elevation' => $elevation !== '' ? (float) $elevation : null,
|
||||||
|
':timezone' => $timezone,
|
||||||
|
':is_default' => $isDefault,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
$successMessage = 'Der Standort wurde gespeichert.';
|
||||||
|
$_POST = [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
$errors[] = 'Der Standort konnte nicht gespeichert werden.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'set_default_location') {
|
||||||
|
$locationId = (int) ($_POST['location_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($locationId <= 0) {
|
||||||
|
$errors[] = 'Ungueltiger 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[] = 'Ungueltiger Standort.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($errors)) {
|
||||||
|
$stmtDelete = $pdo->prepare("
|
||||||
|
DELETE FROM `app_user_locations`
|
||||||
|
WHERE `id` = :id
|
||||||
|
AND `user_id` = :user_id
|
||||||
|
");
|
||||||
|
$stmtDelete->execute([
|
||||||
|
':id' => $locationId,
|
||||||
|
':user_id' => $userId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$successMessage = 'Der Standort wurde geloescht.';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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 ($satelliteSearchQuery !== '') {
|
||||||
if (ctype_digit($satelliteSearchQuery)) {
|
if (ctype_digit($satelliteSearchQuery)) {
|
||||||
$stmtSatelliteSearch = $pdo->prepare("
|
$stmtSatelliteSearch = $pdo->prepare("
|
||||||
@@ -424,6 +643,13 @@ $loggedIn = true;
|
|||||||
?>
|
?>
|
||||||
<?php require __DIR__ . '/header.php'; ?>
|
<?php require __DIR__ . '/header.php'; ?>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||||
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||||
|
crossorigin=""
|
||||||
|
>
|
||||||
|
|
||||||
<?php if ($successMessage !== ''): ?>
|
<?php if ($successMessage !== ''): ?>
|
||||||
<p class="msg-success"><?= h($successMessage) ?></p>
|
<p class="msg-success"><?= h($successMessage) ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -436,6 +662,101 @@ $loggedIn = true;
|
|||||||
</ul>
|
</ul>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h2>Standorte</h2>
|
||||||
|
<?php if (empty($locations)): ?>
|
||||||
|
<p class="hint">Es sind noch keine Standorte gespeichert.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Breitengrad</th>
|
||||||
|
<th>Laengengrad</th>
|
||||||
|
<th>Hoehe</th>
|
||||||
|
<th>Zeitzone</th>
|
||||||
|
<th>Standard</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($locations as $location): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= h($location['name']) ?></td>
|
||||||
|
<td><?= h((string) $location['latitude']) ?></td>
|
||||||
|
<td><?= h((string) $location['longitude']) ?></td>
|
||||||
|
<td><?= h($location['elevation'] !== null ? (string) $location['elevation'] . ' m' : '-') ?></td>
|
||||||
|
<td><?= h($location['timezone']) ?></td>
|
||||||
|
<td><?= (int) $location['is_default'] === 1 ? '*' : '-' ?></td>
|
||||||
|
<td class="action-cell">
|
||||||
|
<?php if ((int) $location['is_default'] !== 1): ?>
|
||||||
|
<form method="post" action="" class="favorites-loading-form" data-loading-message="Standard-Standort wird gesetzt...">
|
||||||
|
<input type="hidden" name="action" value="set_default_location">
|
||||||
|
<input type="hidden" name="location_id" value="<?= (int) $location['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-secondary">Als Standard</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
<form method="post" action="" class="favorites-loading-form" data-loading-message="Standort wird entfernt..." 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>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Standort hinzufuegen</h2>
|
||||||
|
<p class="hint mb-md">Suche einen Ort, nutze den Browser-Standort oder klicke direkt in die Karte.</p>
|
||||||
|
|
||||||
|
<div class="input-row mb-sm">
|
||||||
|
<input type="text" id="place-search" placeholder="Ort oder Adresse, z. B. Berlin, Wien">
|
||||||
|
<button type="button" id="search-place" class="btn btn-primary">Suchen</button>
|
||||||
|
<button type="button" id="lookup-elevation" class="btn btn-secondary">Hoehe ermitteln</button>
|
||||||
|
<button type="button" id="detect-location" class="btn btn-secondary">Browser-Standort</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
|
||||||
|
<form method="post" action="" class="form-stack mt-xl favorites-loading-form" data-loading-message="Standort wird gespeichert...">
|
||||||
|
<input type="hidden" name="action" value="add_location">
|
||||||
|
<div class="grid-2 gap-sm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="name" required value="<?= h($_POST['name'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="timezone">Zeitzone (IANA)</label>
|
||||||
|
<input type="text" id="timezone" name="timezone" required value="<?= h($_POST['timezone'] ?? ($defaultLocation['timezone'] ?? 'Europe/Berlin')) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="latitude">Breitengrad</label>
|
||||||
|
<input type="text" id="latitude" name="latitude" required value="<?= h($_POST['latitude'] ?? ($defaultLocation['latitude'] ?? '')) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="longitude">Laengengrad</label>
|
||||||
|
<input type="text" id="longitude" name="longitude" required value="<?= h($_POST['longitude'] ?? ($defaultLocation['longitude'] ?? '')) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="elevation">Hoehe (optional, Meter)</label>
|
||||||
|
<input type="text" id="elevation" name="elevation" value="<?= h($_POST['elevation'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label class="form-checkbox">
|
||||||
|
<input type="checkbox" name="is_default" value="1" <?= isset($_POST['is_default']) ? 'checked' : '' ?>>
|
||||||
|
Als Standard-Standort speichern
|
||||||
|
</label>
|
||||||
|
<button type="submit" class="btn btn-primary">Standort speichern</button>
|
||||||
|
<p class="favorites-loading-hint" hidden>Standort wird gespeichert...</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid-2">
|
<div class="grid-2">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Satelliten suchen</h2>
|
<h2>Satelliten suchen</h2>
|
||||||
@@ -636,7 +957,188 @@ $loggedIn = true;
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||||
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||||
|
crossorigin=""
|
||||||
|
></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
(function () {
|
||||||
|
const latInput = document.getElementById('latitude');
|
||||||
|
const lonInput = document.getElementById('longitude');
|
||||||
|
const elevationInput = document.getElementById('elevation');
|
||||||
|
const timezoneInput = document.getElementById('timezone');
|
||||||
|
const detectButton = document.getElementById('detect-location');
|
||||||
|
const searchInput = document.getElementById('place-search');
|
||||||
|
const searchButton = document.getElementById('search-place');
|
||||||
|
const elevationButton = document.getElementById('lookup-elevation');
|
||||||
|
const nameInput = document.getElementById('name');
|
||||||
|
const mapEl = document.getElementById('map');
|
||||||
|
|
||||||
|
if (!latInput || !lonInput || !elevationInput || !timezoneInput || !detectButton || !searchInput || !searchButton || !elevationButton || !nameInput || !mapEl || typeof L === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialLat = parseFloat(<?= json_encode($mapLat) ?>);
|
||||||
|
const initialLon = parseFloat(<?= json_encode($mapLon) ?>);
|
||||||
|
const initialZoom = parseInt(<?= json_encode($mapZoom) ?>, 10);
|
||||||
|
const map = L.map('map').setView([initialLat, initialLon], initialZoom);
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© 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 gueltigen Standort waehlen.');
|
||||||
|
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) => {
|
document.querySelectorAll('.favorites-loading-form').forEach((form) => {
|
||||||
form.addEventListener('submit', () => {
|
form.addEventListener('submit', () => {
|
||||||
const message = form.dataset.loadingMessage || 'Bitte warten...';
|
const message = form.dataset.loadingMessage || 'Bitte warten...';
|
||||||
|
|||||||
+11
-552
@@ -29,25 +29,15 @@ $pdo = new PDO(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$userId = (int)$_SESSION['user_id'];
|
$userId = (int) $_SESSION['user_id'];
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$successMessage = '';
|
$successMessage = '';
|
||||||
|
|
||||||
function h(?string $value): string
|
function h(?string $value): string
|
||||||
{
|
{
|
||||||
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
function validTimezone(string $timezone): bool
|
|
||||||
{
|
|
||||||
return in_array($timezone, timezone_identifiers_list(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Benutzer laden
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
$stmtUser = $pdo->prepare("
|
$stmtUser = $pdo->prepare("
|
||||||
SELECT
|
SELECT
|
||||||
`id`,
|
`id`,
|
||||||
@@ -70,11 +60,6 @@ if (!$user) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Aktionen verarbeiten
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$action = $_POST['action'] ?? '';
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
@@ -83,7 +68,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$email = trim($_POST['email'] ?? '');
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
|
||||||
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
$errors[] = 'Bitte eine gültige E-Mail-Adresse eingeben.';
|
$errors[] = 'Bitte eine gueltige E-Mail-Adresse eingeben.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($displayName !== '' && mb_strlen($displayName) > 100) {
|
if ($displayName !== '' && mb_strlen($displayName) > 100) {
|
||||||
@@ -100,7 +85,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
");
|
");
|
||||||
$stmtCheck->execute([
|
$stmtCheck->execute([
|
||||||
':email' => $email,
|
':email' => $email,
|
||||||
':id' => $userId,
|
':id' => $userId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($stmtCheck->fetch()) {
|
if ($stmtCheck->fetch()) {
|
||||||
@@ -119,8 +104,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
");
|
");
|
||||||
$stmtUpdate->execute([
|
$stmtUpdate->execute([
|
||||||
':display_name' => $displayName !== '' ? $displayName : null,
|
':display_name' => $displayName !== '' ? $displayName : null,
|
||||||
':email' => $email,
|
':email' => $email,
|
||||||
':id' => $userId,
|
':id' => $userId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$_SESSION['display_name'] = $displayName !== '' ? $displayName : null;
|
$_SESSION['display_name'] = $displayName !== '' ? $displayName : null;
|
||||||
@@ -153,7 +138,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($newPassword !== $newPasswordRepeat) {
|
if ($newPassword !== $newPasswordRepeat) {
|
||||||
$errors[] = 'Die neuen Passwörter stimmen nicht überein.';
|
$errors[] = 'Die neuen Passwoerter stimmen nicht ueberein.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($errors)) {
|
if (empty($errors)) {
|
||||||
@@ -168,186 +153,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
");
|
");
|
||||||
$stmtUpdatePassword->execute([
|
$stmtUpdatePassword->execute([
|
||||||
':password_hash' => $newHash,
|
':password_hash' => $newHash,
|
||||||
':id' => $userId,
|
':id' => $userId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$successMessage = 'Das Passwort wurde geändert.';
|
$successMessage = 'Das Passwort wurde geaendert.';
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Höhe 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) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
$errors[] = 'Der Standort konnte nicht gespeichert werden.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($action === 'set_default_location') {
|
|
||||||
$locationId = (int)($_POST['location_id'] ?? 0);
|
|
||||||
|
|
||||||
if ($locationId <= 0) {
|
|
||||||
$errors[] = 'Ungültiger Standort.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($errors)) {
|
|
||||||
$stmtCheckLocation = $pdo->prepare("
|
|
||||||
SELECT `id`
|
|
||||||
FROM `app_user_locations`
|
|
||||||
WHERE `id` = :id
|
|
||||||
AND `user_id` = :user_id
|
|
||||||
LIMIT 1
|
|
||||||
");
|
|
||||||
$stmtCheckLocation->execute([
|
|
||||||
':id' => $locationId,
|
|
||||||
':user_id' => $userId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!$stmtCheckLocation->fetch()) {
|
|
||||||
$errors[] = 'Der Standort wurde nicht gefunden.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($errors)) {
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmtReset = $pdo->prepare("
|
|
||||||
UPDATE `app_user_locations`
|
|
||||||
SET `is_default` = 0
|
|
||||||
WHERE `user_id` = :user_id
|
|
||||||
");
|
|
||||||
$stmtReset->execute([
|
|
||||||
':user_id' => $userId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$stmtSet = $pdo->prepare("
|
|
||||||
UPDATE `app_user_locations`
|
|
||||||
SET `is_default` = 1
|
|
||||||
WHERE `id` = :id
|
|
||||||
AND `user_id` = :user_id
|
|
||||||
");
|
|
||||||
$stmtSet->execute([
|
|
||||||
':id' => $locationId,
|
|
||||||
':user_id' => $userId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
$successMessage = 'Der Standard-Standort wurde gesetzt.';
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
$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)) {
|
|
||||||
$stmtDelete = $pdo->prepare("
|
|
||||||
DELETE FROM `app_user_locations`
|
|
||||||
WHERE `id` = :id
|
|
||||||
AND `user_id` = :user_id
|
|
||||||
");
|
|
||||||
$stmtDelete->execute([
|
|
||||||
':id' => $locationId,
|
|
||||||
':user_id' => $userId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$successMessage = 'Der Standort wurde gelöscht.';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,57 +164,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$user = $stmtUser->fetch();
|
$user = $stmtUser->fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Standorte laden
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
$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;
|
|
||||||
$loggedIn = true;
|
$loggedIn = true;
|
||||||
?>
|
?>
|
||||||
<?php require __DIR__ . '/header.php'; ?>
|
<?php require __DIR__ . '/header.php'; ?>
|
||||||
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
|
||||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
|
||||||
crossorigin=""
|
|
||||||
>
|
|
||||||
|
|
||||||
<?php if ($successMessage !== ''): ?>
|
<?php if ($successMessage !== ''): ?>
|
||||||
<p class="msg-success"><?= h($successMessage) ?></p>
|
<p class="msg-success"><?= h($successMessage) ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -419,7 +181,6 @@ $loggedIn = true;
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="grid-2">
|
<div class="grid-2">
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Kontodaten</h2>
|
<h2>Kontodaten</h2>
|
||||||
<table class="data-table mb-lg">
|
<table class="data-table mb-lg">
|
||||||
@@ -454,7 +215,7 @@ $loggedIn = true;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Passwort ändern</h2>
|
<h2>Passwort aendern</h2>
|
||||||
<form method="post" action="" class="form-stack">
|
<form method="post" action="" class="form-stack">
|
||||||
<input type="hidden" name="action" value="change_password">
|
<input type="hidden" name="action" value="change_password">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -469,311 +230,9 @@ $loggedIn = true;
|
|||||||
<label for="new_password_repeat">Neues Passwort wiederholen</label>
|
<label for="new_password_repeat">Neues Passwort wiederholen</label>
|
||||||
<input type="password" id="new_password_repeat" name="new_password_repeat" required>
|
<input type="password" id="new_password_repeat" name="new_password_repeat" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-full">Passwort ändern</button>
|
<button type="submit" class="btn btn-primary btn-full">Passwort aendern</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2>Standorte</h2>
|
|
||||||
<?php if (empty($locations)): ?>
|
|
||||||
<p class="hint">Es sind noch keine Standorte gespeichert.</p>
|
|
||||||
<?php else: ?>
|
|
||||||
<table class="data-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Breitengrad</th>
|
|
||||||
<th>Längengrad</th>
|
|
||||||
<th>Höhe</th>
|
|
||||||
<th>Zeitzone</th>
|
|
||||||
<th>Standard</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($locations as $location): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= h($location['name']) ?></td>
|
|
||||||
<td><?= h((string)$location['latitude']) ?></td>
|
|
||||||
<td><?= h((string)$location['longitude']) ?></td>
|
|
||||||
<td><?= h($location['elevation'] !== null ? (string)$location['elevation'] . ' m' : '—') ?></td>
|
|
||||||
<td><?= h($location['timezone']) ?></td>
|
|
||||||
<td><?= (int)$location['is_default'] === 1 ? '★' : '—' ?></td>
|
|
||||||
<td class="action-cell">
|
|
||||||
<?php if ((int)$location['is_default'] !== 1): ?>
|
|
||||||
<form method="post" action="">
|
|
||||||
<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; ?>
|
|
||||||
<form method="post" action="" 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>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2>Neuen Standort hinzufügen</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">Höhe ermitteln</button>
|
|
||||||
<button type="button" id="detect-location" class="btn btn-secondary">Browser-Standort</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="map" class="map"></div>
|
|
||||||
|
|
||||||
<form method="post" action="" class="form-stack mt-xl">
|
|
||||||
<input type="hidden" name="action" value="add_location">
|
|
||||||
<div class="grid-2 gap-sm">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input type="text" id="name" name="name" required value="<?= h($_POST['name'] ?? '') ?>">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="timezone">Zeitzone (IANA)</label>
|
|
||||||
<input type="text" id="timezone" name="timezone" required value="<?= h($_POST['timezone'] ?? ($defaultLocation['timezone'] ?? 'Europe/Berlin')) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="latitude">Breitengrad</label>
|
|
||||||
<input type="text" id="latitude" name="latitude" required value="<?= h($_POST['latitude'] ?? ($defaultLocation['latitude'] ?? '')) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="longitude">Längengrad</label>
|
|
||||||
<input type="text" id="longitude" name="longitude" required value="<?= h($_POST['longitude'] ?? ($defaultLocation['longitude'] ?? '')) ?>">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="elevation">Höhe (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>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script
|
|
||||||
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
|
||||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
|
||||||
crossorigin=""
|
|
||||||
></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
const latInput = document.getElementById('latitude');
|
|
||||||
const lonInput = document.getElementById('longitude');
|
|
||||||
const elevationInput = document.getElementById('elevation');
|
|
||||||
const timezoneInput = document.getElementById('timezone');
|
|
||||||
const detectButton = document.getElementById('detect-location');
|
|
||||||
const searchInput = document.getElementById('place-search');
|
|
||||||
const searchButton = document.getElementById('search-place');
|
|
||||||
const elevationButton = document.getElementById('lookup-elevation');
|
|
||||||
const nameInput = document.getElementById('name');
|
|
||||||
|
|
||||||
const 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 ||
|
|
||||||
!data.results ||
|
|
||||||
!Array.isArray(data.results) ||
|
|
||||||
data.results.length === 0 ||
|
|
||||||
typeof data.results[0].elevation === 'undefined'
|
|
||||||
) {
|
|
||||||
throw new Error('Keine Höhendaten erhalten.');
|
|
||||||
}
|
|
||||||
|
|
||||||
elevationInput.value = data.results[0].elevation;
|
|
||||||
} catch (error) {
|
|
||||||
alert('Die Höhe 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 unterstützt 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);
|
|
||||||
|
|
||||||
if (parts.length > 0) {
|
|
||||||
nameInput.value = parts.join(', ');
|
|
||||||
} else {
|
|
||||||
nameInput.value = 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();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<?php require __DIR__ . '/footer.php'; ?>
|
<?php require __DIR__ . '/footer.php'; ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user