Datenschutz & Cookies
-Stand: 26. Juli 2026
+Stand: 31. Juli 2026
Diese Hinweise erklären, welche personenbezogenen Daten beim Besuch und bei der Nutzung von Papa-Kind-Treff @@ -139,8 +139,10 @@ $clientCookie = $config->cookiePrefix() . 'client';
Zusätzlich kannst du im Mitgliederbereich deine Profiladresse über die gleichen externen Adressdienste - lokalisieren lassen, die auch für Event-Adressen genutzt werden. Dabei werden die von dir eingegebenen - Adressbestandteile an den jeweiligen Geocoding-Dienst übermittelt. + lokalisieren lassen, die auch für Event-Adressen genutzt werden. Dabei kannst du entweder nach passenden + Adress-Treffern suchen oder die vom Browser ermittelte Position in eine Profiladresse übernehmen. + In beiden Fällen werden die von dir eingegebenen oder vom Browser bereitgestellten Positionsdaten + an den jeweiligen Geocoding-Dienst übermittelt.
Für Karten- und Adressfunktionen werden derzeit externe Dienste genutzt: diff --git a/src/App/AccountPages.php b/src/App/AccountPages.php index d2d2c75..9950ac2 100755 --- a/src/App/AccountPages.php +++ b/src/App/AccountPages.php @@ -192,6 +192,40 @@ final class AccountPages $languagesEnc = self::encryptOptionalProfileField($crypto, trim((string)$languages)); $aboutEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['about'])); $locationPreference = (string)($_POST['location_tracking_preference'] ?? 'prompt'); + $street = trim((string)($_POST['street'] ?? '')); + $zip = trim((string)($_POST['zip'] ?? '')); + $city = trim((string)($_POST['city'] ?? '')); + $region = trim((string)($_POST['region'] ?? '')); + $lat = isset($_POST['lat']) && $_POST['lat'] !== '' ? (float)$_POST['lat'] : null; + $lng = isset($_POST['lng']) && $_POST['lng'] !== '' ? (float)$_POST['lng'] : null; + + $hasAddress = $street !== '' || $zip !== '' || $city !== '' || $region !== ''; + if ($hasAddress) { + $resolvedAddress = null; + if ($lat !== null && $lng !== null) { + $resolvedAddress = self::reverseGeocodeAddress($lat, $lng); + } + if ($resolvedAddress === null) { + $searchResults = self::geocodeAddressCandidates($street, $zip, $city, $region, 5); + if (!$searchResults) { + throw new \RuntimeException('Die Profiladresse konnte nicht validiert werden. Bitte waehle eine Adresse aus der Suche oder ueber den Browser-Standort.'); + } + $resolvedAddress = $searchResults[0]; + } + + $street = $resolvedAddress['street'] !== '' ? $resolvedAddress['street'] : $street; + $zip = $resolvedAddress['zip'] !== '' ? $resolvedAddress['zip'] : $zip; + $city = $resolvedAddress['city'] !== '' ? $resolvedAddress['city'] : $city; + $region = $resolvedAddress['region'] !== '' ? $resolvedAddress['region'] : $region; + $lat = $resolvedAddress['lat']; + $lng = $resolvedAddress['lng']; + } else { + $lat = null; + $lng = null; + $region = ''; + } + + $streetEnc = self::encryptOptionalProfileField($crypto, $street); if ($profileSettings) { $profileSettings->ensureSchema(); } @@ -201,11 +235,11 @@ final class AccountPages 'fname' => $firstNameEnc, 'lname' => $lastNameEnc, 'street' => $streetEnc, - 'zip' => trim((string)$_POST['zip']), - 'city' => trim((string)$_POST['city']), - 'region' => trim((string)($_POST['region'] ?? '')), - 'lat' => isset($_POST['lat']) && $_POST['lat'] !== '' ? (float)$_POST['lat'] : null, - 'lng' => isset($_POST['lng']) && $_POST['lng'] !== '' ? (float)$_POST['lng'] : null, + 'zip' => $zip, + 'city' => $city, + 'region' => $region, + 'lat' => $lat, + 'lng' => $lng, 'prof' => $professionEnc, 'langs' => $languagesEnc, 'about' => $aboutEnc, @@ -629,6 +663,17 @@ final class AccountPages } private static function geocodeAddress(?string $street, ?string $zip, ?string $city, ?string $region): array + { + $candidates = self::geocodeAddressCandidates($street, $zip, $city, $region, 1); + if (!$candidates) { + return [null, null, null]; + } + + $best = $candidates[0]; + return [$best['lat'], $best['lng'], $best['region'] !== '' ? $best['region'] : null]; + } + + private static function geocodeAddressCandidates(?string $street, ?string $zip, ?string $city, ?string $region, int $limit = 5): array { $parts = array_filter([ $street ?: null, @@ -643,10 +688,75 @@ final class AccountPages $query = implode(', ', $parts); $url = 'https://nominatim.openstreetmap.org/search?' . http_build_query([ 'format' => 'jsonv2', - 'limit' => 1, + 'limit' => max(1, min(10, $limit)), + 'addressdetails' => 1, 'q' => $query, ]); + $resp = self::fetchGeoJson($url); + if ($resp === null) { + return []; + } + $json = json_decode($resp, true); + if (!is_array($json)) { + return []; + } + + $results = []; + foreach ($json as $item) { + if (!is_array($item) || empty($item['lat']) || empty($item['lon'])) { + continue; + } + $results[] = self::normalizeGeoAddressResult($item); + } + + return $results; + } + + private static function reverseGeocodeAddress(float $lat, float $lng): ?array + { + $url = 'https://nominatim.openstreetmap.org/reverse?' . http_build_query([ + 'format' => 'jsonv2', + 'addressdetails' => 1, + 'lat' => $lat, + 'lon' => $lng, + ]); + + $resp = self::fetchGeoJson($url); + if ($resp === null) { + return null; + } + $json = json_decode($resp, true); + if (!is_array($json) || empty($json['lat']) || empty($json['lon'])) { + return null; + } + + return self::normalizeGeoAddressResult($json); + } + + private static function normalizeGeoAddressResult(array $item): array + { + $addr = is_array($item['address'] ?? null) ? $item['address'] : []; + $street = trim(implode(' ', array_filter([ + (string)($addr['road'] ?? ''), + (string)($addr['house_number'] ?? ''), + ]))); + $city = (string)($addr['city'] ?? $addr['town'] ?? $addr['village'] ?? ''); + $region = (string)($addr['city_district'] ?? $addr['suburb'] ?? $addr['state'] ?? $addr['county'] ?? $addr['region'] ?? $addr['state_district'] ?? ''); + + return [ + 'label' => trim((string)($item['display_name'] ?? '')), + 'street' => $street, + 'zip' => (string)($addr['postcode'] ?? ''), + 'city' => $city, + 'region' => $region, + 'lat' => round((float)$item['lat'], 7), + 'lng' => round((float)$item['lon'], 7), + ]; + } + + private static function fetchGeoJson(string $url): ?string + { $ctx = stream_context_create([ 'http' => [ 'method' => 'GET', @@ -656,15 +766,6 @@ final class AccountPages ]); $resp = @file_get_contents($url, false, $ctx); - if ($resp === false) { - return [null, null, null]; - } - $json = json_decode($resp, true); - if (!is_array($json) || empty($json[0]['lat']) || empty($json[0]['lon'])) { - return [null, null, null]; - } - $addr = $json[0]['address'] ?? []; - $regionGuess = $addr['city_district'] ?? $addr['suburb'] ?? $addr['state'] ?? $addr['county'] ?? $addr['region'] ?? $addr['state_district'] ?? null; - return [round((float)$json[0]['lat'], 7), round((float)$json[0]['lon'], 7), $regionGuess]; + return $resp === false ? null : $resp; } }