diff --git a/app/Services/SearchService.php b/app/Services/SearchService.php index 7524220fc28..ebed9c1a0b1 100644 --- a/app/Services/SearchService.php +++ b/app/Services/SearchService.php @@ -70,6 +70,7 @@ class SearchService protected const MAX_SEARCH_RESULTS = 5000; private TreeService $tree_service; + private $like_operator = 'LIKE'; /** * SearchService constructor. @@ -80,6 +81,11 @@ public function __construct( TreeService $tree_service ) { $this->tree_service = $tree_service; + + // Allows to make insensitive searches in PostgreSQL + if (DB::connection()->getDriverName() === 'pgsql') { + $this->like_operator = 'ILIKE'; + } } /** @@ -492,7 +498,7 @@ public function searchPlaces(Tree $tree, string $search, int $offset = 0, int $l // Filter each level of the hierarchy. foreach (explode(',', $search, 9) as $level => $string) { - $query->where('p' . $level . '.p_place', 'LIKE', '%' . addcslashes($string, '\\%_') . '%'); + $query->where('p' . $level . '.p_place', $this->like_operator, '%' . addcslashes($string, '\\%_') . '%'); } $row_mapper = static function (object $row) use ($tree): Place { @@ -671,10 +677,10 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $query->where('individual_name.n_givn', '=', $field_value); break; case 'BEGINS': - $query->where('individual_name.n_givn', 'LIKE', $field_value . '%'); + $query->where('individual_name.n_givn', $this->like_operator, $field_value . '%'); break; case 'CONTAINS': - $query->where('individual_name.n_givn', 'LIKE', '%' . $field_value . '%'); + $query->where('individual_name.n_givn', $this->like_operator, '%' . $field_value . '%'); break; case 'SDX_STD': $sdx = Soundex::russell($field_value); @@ -682,7 +688,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $this->wherePhonetic($query, 'individual_name.n_soundex_givn_std', $sdx); } else { // No phonetic content? Use a substring match - $query->where('individual_name.n_givn', 'LIKE', '%' . $field_value . '%'); + $query->where('individual_name.n_givn', $this->like_operator, '%' . $field_value . '%'); } break; case 'SDX': // SDX uses DM by default. @@ -692,7 +698,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $this->wherePhonetic($query, 'individual_name.n_soundex_givn_dm', $sdx); } else { // No phonetic content? Use a substring match - $query->where('individual_name.n_givn', 'LIKE', '%' . $field_value . '%'); + $query->where('individual_name.n_givn', $this->like_operator, '%' . $field_value . '%'); } break; } @@ -710,15 +716,15 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo case 'BEGINS': $query->where(function (Builder $query) use ($field_value): void { $query - ->where('individual_name.n_surn', 'LIKE', $field_value . '%') - ->orWhere('individual_name.n_surname', 'LIKE', $field_value . '%'); + ->where('individual_name.n_surn', $this->like_operator, $field_value . '%') + ->orWhere('individual_name.n_surname', $this->like_operator, $field_value . '%'); }); break; case 'CONTAINS': $query->where(function (Builder $query) use ($field_value): void { $query - ->where('individual_name.n_surn', 'LIKE', '%' . $field_value . '%') - ->orWhere('individual_name.n_surname', 'LIKE', '%' . $field_value . '%'); + ->where('individual_name.n_surn', $this->like_operator, '%' . $field_value . '%') + ->orWhere('individual_name.n_surname', $this->like_operator, '%' . $field_value . '%'); }); break; case 'SDX_STD': @@ -729,8 +735,8 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo // No phonetic content? Use a substring match $query->where(function (Builder $query) use ($field_value): void { $query - ->where('individual_name.n_surn', 'LIKE', '%' . $field_value . '%') - ->orWhere('individual_name.n_surname', 'LIKE', '%' . $field_value . '%'); + ->where('individual_name.n_surn', $this->like_operator, '%' . $field_value . '%') + ->orWhere('individual_name.n_surname', $this->like_operator, '%' . $field_value . '%'); }); } break; @@ -743,8 +749,8 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo // No phonetic content? Use a substring match $query->where(function (Builder $query) use ($field_value): void { $query - ->where('individual_name.n_surn', 'LIKE', '%' . $field_value . '%') - ->orWhere('individual_name.n_surname', 'LIKE', '%' . $field_value . '%'); + ->where('individual_name.n_surn', $this->like_operator, '%' . $field_value . '%') + ->orWhere('individual_name.n_surname', $this->like_operator, '%' . $field_value . '%'); }); } break; @@ -756,7 +762,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo case 'INDI:NAME:_HEB': case 'INDI:NAME:_AKA': $like = "%\n1 NAME%\n2 " . $parts[2] . ' %' . preg_quote($field_value, '/') . '%'; - $query->where('individuals.i_gedcom', 'LIKE', $like); + $query->where('individuals.i_gedcom', $this->like_operator, $like); break; } } elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':DATE')) { @@ -781,10 +787,10 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo unset($fields[$field_name]); } elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':PLAC')) { // SQL can only link a place to a person/family, not to an event. - $query->where('individual_places.p_place', 'LIKE', '%' . $field_value . '%'); + $query->where('individual_places.p_place', $this->like_operator, '%' . $field_value . '%'); } elseif (str_starts_with($field_name, 'FAM:') && str_ends_with($field_name, ':PLAC')) { // SQL can only link a place to a person/family, not to an event. - $query->where('family_places.p_place', 'LIKE', '%' . $field_value . '%'); + $query->where('family_places.p_place', $this->like_operator, '%' . $field_value . '%'); } elseif (str_starts_with($field_name, 'MOTHER:NAME:') || str_starts_with($field_name, 'FATHER:NAME:')) { $table = str_starts_with($field_name, 'FATHER:NAME:') ? 'father_name' : 'mother_name'; switch ($parts[2]) { @@ -794,10 +800,10 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $query->where($table . '.n_givn', '=', $field_value); break; case 'BEGINS': - $query->where($table . '.n_givn', 'LIKE', $field_value . '%'); + $query->where($table . '.n_givn', $this->like_operator, $field_value . '%'); break; case 'CONTAINS': - $query->where($table . '.n_givn', 'LIKE', '%' . $field_value . '%'); + $query->where($table . '.n_givn', $this->like_operator, '%' . $field_value . '%'); break; case 'SDX_STD': $sdx = Soundex::russell($field_value); @@ -805,7 +811,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $this->wherePhonetic($query, $table . '.n_soundex_givn_std', $sdx); } else { // No phonetic content? Use a substring match - $query->where($table . '.n_givn', 'LIKE', '%' . $field_value . '%'); + $query->where($table . '.n_givn', $this->like_operator, '%' . $field_value . '%'); } break; case 'SDX': // SDX uses DM by default. @@ -815,7 +821,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $this->wherePhonetic($query, $table . '.n_soundex_givn_dm', $sdx); } else { // No phonetic content? Use a substring match - $query->where($table . '.n_givn', 'LIKE', '%' . $field_value . '%'); + $query->where($table . '.n_givn', $this->like_operator, '%' . $field_value . '%'); } break; } @@ -826,10 +832,10 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $query->where($table . '.n_surn', '=', $field_value); break; case 'BEGINS': - $query->where($table . '.n_surn', 'LIKE', $field_value . '%'); + $query->where($table . '.n_surn', $this->like_operator, $field_value . '%'); break; case 'CONTAINS': - $query->where($table . '.n_surn', 'LIKE', '%' . $field_value . '%'); + $query->where($table . '.n_surn', $this->like_operator, '%' . $field_value . '%'); break; case 'SDX_STD': $sdx = Soundex::russell($field_value); @@ -837,7 +843,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $this->wherePhonetic($query, $table . '.n_soundex_surn_std', $sdx); } else { // No phonetic content? Use a substring match - $query->where($table . '.n_surn', 'LIKE', '%' . $field_value . '%'); + $query->where($table . '.n_surn', $this->like_operator, '%' . $field_value . '%'); } break; case 'SDX': // SDX uses DM by default. @@ -847,7 +853,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo $this->wherePhonetic($query, $table . '.n_soundex_surn_dm', $sdx); } else { // No phonetic content? Use a substring match - $query->where($table . '.n_surn', 'LIKE', '%' . $field_value . '%'); + $query->where($table . '.n_surn', $this->like_operator, '%' . $field_value . '%'); } break; } @@ -857,14 +863,14 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo } elseif (str_starts_with($field_name, 'FAM:')) { // e.g. searches for occupation, religion, note, etc. // Initial matching only. Need PHP to apply filter. - $query->where('spouse_families.f_gedcom', 'LIKE', "%\n1 " . $parts[1] . ' %' . $field_value . '%'); + $query->where('spouse_families.f_gedcom', $this->like_operator, "%\n1 " . $parts[1] . ' %' . $field_value . '%'); } elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':TYPE')) { // Initial matching only. Need PHP to apply filter. - $query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[1] . "%\n2 TYPE %" . $field_value . '%'); + $query->where('individuals.i_gedcom', $this->like_operator, "%\n1 " . $parts[1] . "%\n2 TYPE %" . $field_value . '%'); } elseif (str_starts_with($field_name, 'INDI:')) { // e.g. searches for occupation, religion, note, etc. // Initial matching only. Need PHP to apply filter. - $query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[1] . '%' . $parts[2] . '%' . $field_value . '%'); + $query->where('individuals.i_gedcom', $this->like_operator, "%\n1 " . $parts[1] . '%' . $parts[2] . '%' . $field_value . '%'); } } @@ -1075,7 +1081,7 @@ private function paginateQuery(Builder $query, Closure $row_mapper, Closure $row private function whereSearch(Builder $query, Expression|string $column, array $search_terms): void { foreach ($search_terms as $search_term) { - $query->where($column, 'LIKE', '%' . addcslashes($search_term, '\\%_') . '%'); + $query->where($column, $this->like_operator, '%' . addcslashes($search_term, '\\%_') . '%'); } } @@ -1091,7 +1097,7 @@ private function wherePhonetic(Builder $query, $field, string $soundex): void if ($soundex !== '') { $query->where(static function (Builder $query) use ($soundex, $field): void { foreach (explode(':', $soundex) as $sdx) { - $query->orWhere($field, 'LIKE', '%' . $sdx . '%'); + $query->orWhere($field, $this->like_operator, '%' . $sdx . '%'); } }); }