- 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>
84 lines
2.6 KiB
PHP
Executable File
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;
|
|
}
|
|
}
|