This commit is contained in:
2025-12-31 01:00:41 +01:00
parent 3110b48c33
commit cbcd09003e
5 changed files with 232 additions and 180 deletions

View File

@@ -7,12 +7,13 @@ final class Search
{
public function __construct(private ?\PDO $pdo) {}
public function searchEvents(string $query, int $limit = 100): array
public function searchEvents(string $query, int $limit = 100, ?array $geo = null): array
{
if (!$this->pdo) return [];
$q = trim($query);
if ($q === '') return [];
$hasGeo = isset($geo['lat'], $geo['lng']) && is_numeric($geo['lat']) && is_numeric($geo['lng']);
if ($q === '' && !$hasGeo) return [];
$tokens = array_filter(preg_split('/\s+/', $q) ?: [], fn($t) => $t !== '');
if (!$tokens) {
@@ -39,18 +40,56 @@ final class Search
}
$i++;
}
$where = $conditions ? ('AND ' . implode(' AND ', $conditions)) : '';
$whereParts = [
"starts_at >= NOW()",
"status != 'cancelled'",
];
if ($conditions) {
$whereParts[] = implode(' AND ', $conditions);
}
$sql = "SELECT id, title, teaser_public, description, city, region, zip, starts_at, visibility, allow_kids, location_label, lat, lng";
$distanceFiltering = false;
if ($hasGeo) {
$lat = (float)$geo['lat'];
$lng = (float)$geo['lng'];
$radius = isset($geo['radius']) && is_numeric($geo['radius']) ? max(0.1, (float)$geo['radius']) : 5.0;
$sql .= ",
(6371 * ACOS(LEAST(1,
COS(RADIANS(:glat)) * COS(RADIANS(lat)) * COS(RADIANS(lng) - RADIANS(:glng)) +
SIN(RADIANS(:glat)) * SIN(RADIANS(lat))
))) AS distance_km";
$distanceFiltering = true;
$latRange = $radius / 111.0;
$lngRange = $radius / (111.0 * max(0.1, cos($lat * M_PI / 180)));
$whereParts[] = "(lat IS NOT NULL AND lng IS NOT NULL)";
$whereParts[] = "(lat BETWEEN :latMin AND :latMax)";
$whereParts[] = "(lng BETWEEN :lngMin AND :lngMax)";
$params[':glat'] = $lat;
$params[':glng'] = $lng;
$params[':latMin'] = $lat - $latRange;
$params[':latMax'] = $lat + $latRange;
$params[':lngMin'] = $lng - $lngRange;
$params[':lngMax'] = $lng + $lngRange;
$params[':radius'] = $radius;
}
$where = $whereParts ? ('WHERE ' . implode(' AND ', $whereParts)) : '';
$sql .= " FROM events $where";
if ($distanceFiltering) {
$sql .= " HAVING distance_km <= :radius";
$sql .= " ORDER BY distance_km ASC, starts_at ASC";
} else {
$sql .= " ORDER BY starts_at ASC";
}
$sql .= " LIMIT :lim";
$sql = "SELECT id, title, teaser_public, description, city, region, starts_at, visibility, allow_kids, location_label
FROM events
WHERE starts_at >= NOW()
AND status != 'cancelled'
$where
ORDER BY starts_at ASC
LIMIT :lim";
$stmt = $this->pdo->prepare($sql);
foreach ($params as $k => $v) {
$stmt->bindValue($k, $v, \PDO::PARAM_STR);
$type = is_int($v) ? \PDO::PARAM_INT : \PDO::PARAM_STR;
$stmt->bindValue($k, $v, $type);
}
$stmt->bindValue(':lim', $limit, \PDO::PARAM_INT);
$stmt->execute();