diff --git a/src/App/Search.php b/src/App/Search.php index 93c49a0..321ecac 100644 --- a/src/App/Search.php +++ b/src/App/Search.php @@ -19,6 +19,9 @@ final class Search if (!$tokens) { $tokens = [$q]; } + // Nur Tokens ab 3 Zeichen für fuzzy/LIKE berücksichtigen + $tokens = array_values(array_filter($tokens, fn($t) => mb_strlen($t) >= 3)); + if (!$tokens && !$hasGeo) return []; $conditions = []; $bindTokens = []; @@ -104,6 +107,34 @@ final class Search try { $stmt->execute($bind); $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; + // Fuzzy filter: allow slight typos (Levenshtein <= 1 or 2) + if ($tokens) { + $rows = array_values(array_filter($rows, function ($row) use ($tokens) { + $haystack = strtolower( + ($row['title'] ?? '') . ' ' . + ($row['teaser_public'] ?? '') . ' ' . + ($row['description'] ?? '') . ' ' . + ($row['city'] ?? '') . ' ' . + ($row['region'] ?? '') + ); + $words = preg_split('/[^a-z0-9äöüß]+/i', $haystack) ?: []; + foreach ($tokens as $tok) { + $t = strtolower($tok); + if ($t === '') continue; + if (str_contains($haystack, $t)) { + return true; + } + foreach ($words as $w) { + if ($w === '') continue; + $dist = levenshtein($t, $w); + if ($dist <= 1 || ($dist <= 2 && max(strlen($t), strlen($w)) > 4)) { + return true; + } + } + } + return false; + })); + } if (defined('APP_ENV') && APP_ENV === 'staging') { $logOk = [ 'status' => 'ok',