Files
WebAPP/app/Services/GeocodingService.php
Rhino 2e24a40d68 Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill
- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:30:37 +01:00

84 lines
2.6 KiB
PHP
Executable File

<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class GeocodingService
{
/** Erlaubte Geocoding-Hosts (SSRF-Schutz) */
private const ALLOWED_HOSTS = [
'nominatim.openstreetmap.org',
'photon.komoot.io',
];
public function search(string $query): array
{
$baseUrl = config('nominatim.base_url');
// SSRF-Schutz: Nur erlaubte Hosts und HTTPS
$parsedHost = parse_url($baseUrl, PHP_URL_HOST);
if (!$parsedHost || !in_array($parsedHost, self::ALLOWED_HOSTS)) {
return [];
}
if (parse_url($baseUrl, PHP_URL_SCHEME) !== 'https') {
return [];
}
$cacheKey = 'geocode:' . hash('sha256', mb_strtolower(trim($query)));
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
$response = Http::withHeaders([
'User-Agent' => config('nominatim.user_agent'),
])->timeout(5)->get($baseUrl . '/search', [
'q' => $query,
'format' => 'json',
'addressdetails' => 1,
'namedetails' => 1,
'limit' => 5,
'countrycodes' => 'de,at,ch',
'accept-language' => 'de',
]);
// Fehlerhafte Responses nicht cachen (V20)
if ($response->failed()) {
return [];
}
$results = collect($response->json())->map(function ($item) {
$addr = $item['address'] ?? [];
// Structured address from components
$street = trim(($addr['road'] ?? '') . ' ' . ($addr['house_number'] ?? ''));
$postcode = $addr['postcode'] ?? '';
$city = $addr['city'] ?? $addr['town'] ?? $addr['village'] ?? $addr['municipality'] ?? '';
$name = $item['namedetails']['name'] ?? '';
// Build formatted address line
$parts = array_filter([$street, implode(' ', array_filter([$postcode, $city]))]);
$formatted = implode(', ', $parts);
return [
'display_name' => $item['display_name'],
'formatted_address' => $formatted ?: $item['display_name'],
'name' => $name,
'street' => $street,
'postcode' => $postcode,
'city' => $city,
'lat' => $item['lat'],
'lon' => $item['lon'],
'type' => $item['type'] ?? '',
];
})->toArray();
Cache::put($cacheKey, $results, 86400);
return $results;
}
}