132cd2800SGreg Roach<?php 232cd2800SGreg Roach/** 332cd2800SGreg Roach * webtrees: online genealogy 432cd2800SGreg Roach * Copyright (C) 2019 webtrees development team 532cd2800SGreg Roach * This program is free software: you can redistribute it and/or modify 632cd2800SGreg Roach * it under the terms of the GNU General Public License as published by 732cd2800SGreg Roach * the Free Software Foundation, either version 3 of the License, or 832cd2800SGreg Roach * (at your option) any later version. 932cd2800SGreg Roach * This program is distributed in the hope that it will be useful, 1032cd2800SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 1132cd2800SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1232cd2800SGreg Roach * GNU General Public License for more details. 1332cd2800SGreg Roach * You should have received a copy of the GNU General Public License 1432cd2800SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 1532cd2800SGreg Roach */ 1632cd2800SGreg Roachdeclare(strict_types=1); 1732cd2800SGreg Roach 1832cd2800SGreg Roachnamespace Fisharebest\Webtrees\Services; 1932cd2800SGreg Roach 2032cd2800SGreg Roachuse Closure; 21*a7a24840SGreg Roachuse Fisharebest\Localization\Locale\LocaleInterface; 2232cd2800SGreg Roachuse Fisharebest\Webtrees\Family; 23*a7a24840SGreg Roachuse Fisharebest\Webtrees\Gedcom; 2432cd2800SGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 2532cd2800SGreg Roachuse Fisharebest\Webtrees\Individual; 2632cd2800SGreg Roachuse Fisharebest\Webtrees\Media; 2732cd2800SGreg Roachuse Fisharebest\Webtrees\Note; 28b68caec6SGreg Roachuse Fisharebest\Webtrees\Place; 2932cd2800SGreg Roachuse Fisharebest\Webtrees\Repository; 3032cd2800SGreg Roachuse Fisharebest\Webtrees\Source; 3132cd2800SGreg Roachuse Fisharebest\Webtrees\Tree; 3232cd2800SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 3332cd2800SGreg Roachuse Illuminate\Database\Query\Builder; 34*a7a24840SGreg Roachuse Illuminate\Database\Query\Expression; 3532cd2800SGreg Roachuse Illuminate\Database\Query\JoinClause; 3632cd2800SGreg Roachuse Illuminate\Support\Collection; 3732cd2800SGreg Roachuse stdClass; 38*a7a24840SGreg Roachuse function mb_stripos; 3932cd2800SGreg Roach 4032cd2800SGreg Roach/** 4132cd2800SGreg Roach * Search trees for genealogy records. 4232cd2800SGreg Roach */ 4332cd2800SGreg Roachclass SearchService 4432cd2800SGreg Roach{ 45*a7a24840SGreg Roach /** @var LocaleInterface */ 46*a7a24840SGreg Roach private $locale; 47*a7a24840SGreg Roach 48*a7a24840SGreg Roach /** 49*a7a24840SGreg Roach * SearchService constructor. 50*a7a24840SGreg Roach * 51*a7a24840SGreg Roach * @param LocaleInterface $locale 52*a7a24840SGreg Roach */ 53*a7a24840SGreg Roach public function __construct(LocaleInterface $locale) 54*a7a24840SGreg Roach { 55*a7a24840SGreg Roach $this->locale = $locale; 56*a7a24840SGreg Roach } 57*a7a24840SGreg Roach 58*a7a24840SGreg Roach /** 59*a7a24840SGreg Roach * @param Tree[] $trees 60*a7a24840SGreg Roach * @param string[] $search 61*a7a24840SGreg Roach * 62*a7a24840SGreg Roach * @return Collection|Family[] 63*a7a24840SGreg Roach */ 64*a7a24840SGreg Roach public function searchFamilies(array $trees, array $search): Collection 65*a7a24840SGreg Roach { 66*a7a24840SGreg Roach $query = DB::table('families'); 67*a7a24840SGreg Roach 68*a7a24840SGreg Roach $this->whereTrees($query, 'f_file', $trees); 69*a7a24840SGreg Roach $this->whereSearch($query, 'f_gedcom', $search); 70*a7a24840SGreg Roach 71*a7a24840SGreg Roach return $query 72*a7a24840SGreg Roach ->get() 73*a7a24840SGreg Roach ->map(Family::rowMapper()) 74*a7a24840SGreg Roach ->filter(GedcomRecord::accessFilter()) 75*a7a24840SGreg Roach ->filter(self::rawGedcomFilter($search)); 76*a7a24840SGreg Roach } 77*a7a24840SGreg Roach 7832cd2800SGreg Roach /** 7932cd2800SGreg Roach * Search for families by name. 8032cd2800SGreg Roach * 81*a7a24840SGreg Roach * @param Tree[] $trees 82*a7a24840SGreg Roach * @param string[] $search 8332cd2800SGreg Roach * @param int $offset 8432cd2800SGreg Roach * @param int $limit 8532cd2800SGreg Roach * 8632cd2800SGreg Roach * @return Collection|Family[] 8732cd2800SGreg Roach */ 88*a7a24840SGreg Roach public function searchFamilyNames(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 8932cd2800SGreg Roach { 9032cd2800SGreg Roach $query = DB::table('families') 91*a7a24840SGreg Roach ->join('name AS husb_name', function (JoinClause $join): void { 9232cd2800SGreg Roach $join 9332cd2800SGreg Roach ->on('husb_name.n_file', '=', 'families.f_file') 94*a7a24840SGreg Roach ->on('husb_name.n_id', '=', 'families.f_husb'); 9532cd2800SGreg Roach }) 96*a7a24840SGreg Roach ->join('name AS wife_name', function (JoinClause $join): void { 9732cd2800SGreg Roach $join 9832cd2800SGreg Roach ->on('wife_name.n_file', '=', 'families.f_file') 99*a7a24840SGreg Roach ->on('wife_name.n_id', '=', 'families.f_wife'); 10032cd2800SGreg Roach }) 101*a7a24840SGreg Roach ->where('wife_name.n_type', '<>', '_MARNM') 102*a7a24840SGreg Roach ->where('husb_name.n_type', '<>', '_MARNM'); 103*a7a24840SGreg Roach 104*a7a24840SGreg Roach $prefix = DB::connection()->getTablePrefix(); 105*a7a24840SGreg Roach $field = DB::raw($prefix . 'husb_name.n_full || ' . $prefix . 'wife_name.n_full'); 106*a7a24840SGreg Roach 107*a7a24840SGreg Roach $this->whereTrees($query, 'f_file', $trees); 108*a7a24840SGreg Roach $this->whereSearch($query, $field, $search); 109*a7a24840SGreg Roach 110*a7a24840SGreg Roach $query 11132cd2800SGreg Roach ->orderBy('husb_name.n_sort') 11232cd2800SGreg Roach ->orderBy('wife_name.n_sort') 113c0804649SGreg Roach ->select(['families.*', 'husb_name.n_sort', 'wife_name.n_sort']) 11432cd2800SGreg Roach ->distinct(); 11532cd2800SGreg Roach 116*a7a24840SGreg Roach return $this->paginateQuery($query, Family::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 117*a7a24840SGreg Roach } 118*a7a24840SGreg Roach 119*a7a24840SGreg Roach /** 120*a7a24840SGreg Roach * @param Tree[] $trees 121*a7a24840SGreg Roach * @param string[] $search 122*a7a24840SGreg Roach * 123*a7a24840SGreg Roach * @return Collection|Individual[] 124*a7a24840SGreg Roach */ 125*a7a24840SGreg Roach public function searchIndividuals(array $trees, array $search): Collection 126*a7a24840SGreg Roach { 127*a7a24840SGreg Roach $query = DB::table('individuals'); 128*a7a24840SGreg Roach 129*a7a24840SGreg Roach $this->whereTrees($query, 'i_file', $trees); 130*a7a24840SGreg Roach $this->whereSearch($query, 'i_gedcom', $search); 131*a7a24840SGreg Roach 132*a7a24840SGreg Roach return $query 133*a7a24840SGreg Roach ->get() 134*a7a24840SGreg Roach ->map(Individual::rowMapper()) 135*a7a24840SGreg Roach ->filter(GedcomRecord::accessFilter()) 136*a7a24840SGreg Roach ->filter(self::rawGedcomFilter($search)); 13732cd2800SGreg Roach } 13832cd2800SGreg Roach 13932cd2800SGreg Roach /** 14032cd2800SGreg Roach * Search for individuals by name. 14132cd2800SGreg Roach * 142*a7a24840SGreg Roach * @param Tree[] $trees 143*a7a24840SGreg Roach * @param string[] $search 14432cd2800SGreg Roach * @param int $offset 14532cd2800SGreg Roach * @param int $limit 14632cd2800SGreg Roach * 14732cd2800SGreg Roach * @return Collection|Individual[] 14832cd2800SGreg Roach */ 149*a7a24840SGreg Roach public function searchIndividualNames(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 15032cd2800SGreg Roach { 15132cd2800SGreg Roach $query = DB::table('individuals') 152*a7a24840SGreg Roach ->join('name', function (JoinClause $join): void { 15332cd2800SGreg Roach $join 15432cd2800SGreg Roach ->on('name.n_file', '=', 'individuals.i_file') 155*a7a24840SGreg Roach ->on('name.n_id', '=', 'individuals.i_id'); 15632cd2800SGreg Roach }) 157e84cf2deSGreg Roach ->orderBy('n_sort') 158c0804649SGreg Roach ->select(['individuals.*', 'n_sort', 'n_num']) 15932cd2800SGreg Roach ->distinct(); 16032cd2800SGreg Roach 161*a7a24840SGreg Roach $this->whereTrees($query, 'i_file', $trees); 162*a7a24840SGreg Roach $this->whereSearch($query, 'n_full', $search); 163*a7a24840SGreg Roach 164*a7a24840SGreg Roach return $this->paginateQuery($query, Individual::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 16532cd2800SGreg Roach } 16632cd2800SGreg Roach 16732cd2800SGreg Roach /** 16832cd2800SGreg Roach * Search for media objects. 16932cd2800SGreg Roach * 170*a7a24840SGreg Roach * @param Tree[] $trees 171*a7a24840SGreg Roach * @param string[] $search 17232cd2800SGreg Roach * @param int $offset 17332cd2800SGreg Roach * @param int $limit 17432cd2800SGreg Roach * 17532cd2800SGreg Roach * @return Collection|Media[] 17632cd2800SGreg Roach */ 177*a7a24840SGreg Roach public function searchMedia(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 17832cd2800SGreg Roach { 179*a7a24840SGreg Roach $query = DB::table('media'); 18032cd2800SGreg Roach 181*a7a24840SGreg Roach $this->whereTrees($query, 'media.m_file', $trees); 182*a7a24840SGreg Roach $this->whereSearch($query, 'm_gedcom', $search); 183*a7a24840SGreg Roach 184*a7a24840SGreg Roach return $this->paginateQuery($query, Media::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 18532cd2800SGreg Roach } 18632cd2800SGreg Roach 18732cd2800SGreg Roach /** 18832cd2800SGreg Roach * Search for notes. 18932cd2800SGreg Roach * 190*a7a24840SGreg Roach * @param Tree[] $trees 191*a7a24840SGreg Roach * @param string[] $search 19232cd2800SGreg Roach * @param int $offset 19332cd2800SGreg Roach * @param int $limit 19432cd2800SGreg Roach * 19532cd2800SGreg Roach * @return Collection|Note[] 19632cd2800SGreg Roach */ 197*a7a24840SGreg Roach public function searchNotes(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 19832cd2800SGreg Roach { 19932cd2800SGreg Roach $query = DB::table('other') 200*a7a24840SGreg Roach ->where('o_type', '=', 'NOTE'); 20132cd2800SGreg Roach 202*a7a24840SGreg Roach $this->whereTrees($query, 'o_file', $trees); 203*a7a24840SGreg Roach $this->whereSearch($query, 'o_gedcom', $search); 204*a7a24840SGreg Roach 205*a7a24840SGreg Roach return $this->paginateQuery($query, Note::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 20632cd2800SGreg Roach } 20732cd2800SGreg Roach 20832cd2800SGreg Roach /** 20932cd2800SGreg Roach * Search for repositories. 21032cd2800SGreg Roach * 211*a7a24840SGreg Roach * @param Tree[] $trees 212*a7a24840SGreg Roach * @param string[] $search 21332cd2800SGreg Roach * @param int $offset 21432cd2800SGreg Roach * @param int $limit 21532cd2800SGreg Roach * 21632cd2800SGreg Roach * @return Collection|Repository[] 21732cd2800SGreg Roach */ 218*a7a24840SGreg Roach public function searchRepositories(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 21932cd2800SGreg Roach { 22032cd2800SGreg Roach $query = DB::table('other') 221*a7a24840SGreg Roach ->where('o_type', '=', 'REPO'); 22232cd2800SGreg Roach 223*a7a24840SGreg Roach $this->whereTrees($query, 'o_file', $trees); 224*a7a24840SGreg Roach $this->whereSearch($query, 'o_gedcom', $search); 225*a7a24840SGreg Roach 226*a7a24840SGreg Roach return $this->paginateQuery($query, Repository::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 22732cd2800SGreg Roach } 22832cd2800SGreg Roach 22932cd2800SGreg Roach /** 230*a7a24840SGreg Roach * Search for sources. 23132cd2800SGreg Roach * 232*a7a24840SGreg Roach * @param Tree[] $trees 233*a7a24840SGreg Roach * @param string[] $search 23432cd2800SGreg Roach * @param int $offset 23532cd2800SGreg Roach * @param int $limit 23632cd2800SGreg Roach * 23732cd2800SGreg Roach * @return Collection|Source[] 23832cd2800SGreg Roach */ 239*a7a24840SGreg Roach public function searchSources(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 240*a7a24840SGreg Roach { 241*a7a24840SGreg Roach $query = DB::table('sources'); 242*a7a24840SGreg Roach 243*a7a24840SGreg Roach $this->whereTrees($query, 's_file', $trees); 244*a7a24840SGreg Roach $this->whereSearch($query, 's_gedcom', $search); 245*a7a24840SGreg Roach 246*a7a24840SGreg Roach return $this->paginateQuery($query, Source::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 247*a7a24840SGreg Roach } 248*a7a24840SGreg Roach 249*a7a24840SGreg Roach /** 250*a7a24840SGreg Roach * Search for sources by name. 251*a7a24840SGreg Roach * 252*a7a24840SGreg Roach * @param Tree[] $trees 253*a7a24840SGreg Roach * @param string[] $search 254*a7a24840SGreg Roach * @param int $offset 255*a7a24840SGreg Roach * @param int $limit 256*a7a24840SGreg Roach * 257*a7a24840SGreg Roach * @return Collection|Source[] 258*a7a24840SGreg Roach */ 259*a7a24840SGreg Roach public function searchSourcesByName(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 26032cd2800SGreg Roach { 26132cd2800SGreg Roach $query = DB::table('sources') 262c0804649SGreg Roach ->orderBy('s_name'); 26332cd2800SGreg Roach 264*a7a24840SGreg Roach $this->whereTrees($query, 's_file', $trees); 265*a7a24840SGreg Roach $this->whereSearch($query, 's_name', $search); 266*a7a24840SGreg Roach 267*a7a24840SGreg Roach return $this->paginateQuery($query, Source::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 26832cd2800SGreg Roach } 26932cd2800SGreg Roach 27032cd2800SGreg Roach /** 27132cd2800SGreg Roach * Search for submitters. 27232cd2800SGreg Roach * 273*a7a24840SGreg Roach * @param Tree[] $trees 274*a7a24840SGreg Roach * @param string[] $search 27532cd2800SGreg Roach * @param int $offset 27632cd2800SGreg Roach * @param int $limit 27732cd2800SGreg Roach * 27832cd2800SGreg Roach * @return Collection|GedcomRecord[] 27932cd2800SGreg Roach */ 280*a7a24840SGreg Roach public function searchSubmitters(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 28132cd2800SGreg Roach { 28232cd2800SGreg Roach $query = DB::table('other') 283*a7a24840SGreg Roach ->where('o_type', '=', 'SUBM'); 28432cd2800SGreg Roach 285*a7a24840SGreg Roach $this->whereTrees($query, 'o_file', $trees); 286*a7a24840SGreg Roach $this->whereSearch($query, 'o_gedcom', $search); 287*a7a24840SGreg Roach 288*a7a24840SGreg Roach return $this->paginateQuery($query, GedcomRecord::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 28932cd2800SGreg Roach } 29032cd2800SGreg Roach 29132cd2800SGreg Roach /** 292b68caec6SGreg Roach * Search for places. 293b68caec6SGreg Roach * 294b68caec6SGreg Roach * @param Tree $tree 295b68caec6SGreg Roach * @param string $search 296b68caec6SGreg Roach * @param int $offset 297b68caec6SGreg Roach * @param int $limit 298b68caec6SGreg Roach * 299b68caec6SGreg Roach * @return Collection|Place[] 300b68caec6SGreg Roach */ 301b68caec6SGreg Roach public function searchPlaces(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 302b68caec6SGreg Roach { 303b68caec6SGreg Roach $query = DB::table('places AS p0') 304b68caec6SGreg Roach ->where('p0.p_file', '=', $tree->id()) 305b68caec6SGreg Roach ->leftJoin('places AS p1', 'p1.p_id', '=', 'p0.p_parent_id') 306b68caec6SGreg Roach ->leftJoin('places AS p2', 'p2.p_id', '=', 'p1.p_parent_id') 307b68caec6SGreg Roach ->leftJoin('places AS p3', 'p3.p_id', '=', 'p2.p_parent_id') 308b68caec6SGreg Roach ->leftJoin('places AS p4', 'p4.p_id', '=', 'p3.p_parent_id') 309b68caec6SGreg Roach ->leftJoin('places AS p5', 'p5.p_id', '=', 'p4.p_parent_id') 310b68caec6SGreg Roach ->leftJoin('places AS p6', 'p6.p_id', '=', 'p5.p_parent_id') 311b68caec6SGreg Roach ->leftJoin('places AS p7', 'p7.p_id', '=', 'p6.p_parent_id') 312b68caec6SGreg Roach ->leftJoin('places AS p8', 'p8.p_id', '=', 'p7.p_parent_id') 313b68caec6SGreg Roach ->orderBy('p0.p_place') 314b68caec6SGreg Roach ->orderBy('p1.p_place') 315b68caec6SGreg Roach ->orderBy('p2.p_place') 316b68caec6SGreg Roach ->orderBy('p3.p_place') 317b68caec6SGreg Roach ->orderBy('p4.p_place') 318b68caec6SGreg Roach ->orderBy('p5.p_place') 319b68caec6SGreg Roach ->orderBy('p6.p_place') 320b68caec6SGreg Roach ->orderBy('p7.p_place') 321b68caec6SGreg Roach ->orderBy('p8.p_place') 322b68caec6SGreg Roach ->select([ 323b68caec6SGreg Roach 'p0.p_place AS place0', 324b68caec6SGreg Roach 'p1.p_place AS place1', 325b68caec6SGreg Roach 'p2.p_place AS place2', 326b68caec6SGreg Roach 'p3.p_place AS place3', 327b68caec6SGreg Roach 'p4.p_place AS place4', 328b68caec6SGreg Roach 'p5.p_place AS place5', 329b68caec6SGreg Roach 'p6.p_place AS place6', 330b68caec6SGreg Roach 'p7.p_place AS place7', 331b68caec6SGreg Roach 'p8.p_place AS place8', 332b68caec6SGreg Roach ]); 333b68caec6SGreg Roach 334b68caec6SGreg Roach // Filter each level of the hierarchy. 335b68caec6SGreg Roach foreach (explode(',', $search, 9) as $level => $string) { 336b68caec6SGreg Roach $query->whereContains('p' . $level . '.p_place', $string); 337b68caec6SGreg Roach } 338b68caec6SGreg Roach 339b68caec6SGreg Roach $row_mapper = function (stdClass $row) use ($tree): Place { 340b68caec6SGreg Roach $place = implode(', ', array_filter((array) $row)); 341b68caec6SGreg Roach 342b68caec6SGreg Roach return new Place($place, $tree); 343b68caec6SGreg Roach }; 344b68caec6SGreg Roach 345*a7a24840SGreg Roach $filter = function (): bool { 346*a7a24840SGreg Roach return true; 347*a7a24840SGreg Roach }; 348b68caec6SGreg Roach 349*a7a24840SGreg Roach return $this->paginateQuery($query, $row_mapper, $filter, $offset, $limit); 350*a7a24840SGreg Roach } 351b68caec6SGreg Roach 352b68caec6SGreg Roach /** 35332cd2800SGreg Roach * Paginate a search query. 35432cd2800SGreg Roach * 35532cd2800SGreg Roach * @param Builder $query Searches the database for the desired records. 35632cd2800SGreg Roach * @param Closure $row_mapper Converts a row from the query into a record. 357*a7a24840SGreg Roach * @param Closure $row_filter 35832cd2800SGreg Roach * @param int $offset Skip this many rows. 35932cd2800SGreg Roach * @param int $limit Take this many rows. 36032cd2800SGreg Roach * 36132cd2800SGreg Roach * @return Collection 36232cd2800SGreg Roach */ 363*a7a24840SGreg Roach private function paginateQuery(Builder $query, Closure $row_mapper, Closure $row_filter, int $offset, int $limit): Collection 36432cd2800SGreg Roach { 36532cd2800SGreg Roach $collection = new Collection(); 36632cd2800SGreg Roach 36732cd2800SGreg Roach foreach ($query->cursor() as $row) { 36832cd2800SGreg Roach $record = $row_mapper($row); 369b68caec6SGreg Roach // If the object has a method "canShow()", then use it to filter for privacy. 370*a7a24840SGreg Roach if ($row_filter($record)) { 37132cd2800SGreg Roach if ($offset > 0) { 37232cd2800SGreg Roach $offset--; 37332cd2800SGreg Roach } else { 37432cd2800SGreg Roach if ($limit > 0) { 37532cd2800SGreg Roach $collection->push($record); 37632cd2800SGreg Roach } 37732cd2800SGreg Roach 37832cd2800SGreg Roach $limit--; 37932cd2800SGreg Roach 38032cd2800SGreg Roach if ($limit === 0) { 38132cd2800SGreg Roach break; 38232cd2800SGreg Roach } 38332cd2800SGreg Roach } 38432cd2800SGreg Roach } 38532cd2800SGreg Roach } 38632cd2800SGreg Roach 38732cd2800SGreg Roach return $collection; 38832cd2800SGreg Roach } 389*a7a24840SGreg Roach 390*a7a24840SGreg Roach /** 391*a7a24840SGreg Roach * Apply search filters to a SQL query column. Apply collation rules to MySQL. 392*a7a24840SGreg Roach * 393*a7a24840SGreg Roach * @param Builder $query 394*a7a24840SGreg Roach * @param Expression|string $field 395*a7a24840SGreg Roach * @param string[] $search_terms 396*a7a24840SGreg Roach */ 397*a7a24840SGreg Roach private function whereSearch(Builder $query, $field, array $search_terms): void 398*a7a24840SGreg Roach { 399*a7a24840SGreg Roach if ($field instanceof Expression) { 400*a7a24840SGreg Roach $field = $field->getValue(); 401*a7a24840SGreg Roach } 402*a7a24840SGreg Roach 403*a7a24840SGreg Roach $field = DB::raw($field . ' /*! COLLATE ' . 'utf8_' . $this->locale->collation() . ' */'); 404*a7a24840SGreg Roach 405*a7a24840SGreg Roach foreach ($search_terms as $search_term) { 406*a7a24840SGreg Roach $query->whereContains($field, $search_term); 407*a7a24840SGreg Roach } 408*a7a24840SGreg Roach } 409*a7a24840SGreg Roach 410*a7a24840SGreg Roach /** 411*a7a24840SGreg Roach * @param Builder $query 412*a7a24840SGreg Roach * @param string $tree_id_field 413*a7a24840SGreg Roach * @param Tree[] $trees 414*a7a24840SGreg Roach */ 415*a7a24840SGreg Roach private function whereTrees(Builder $query, string $tree_id_field, array $trees): void 416*a7a24840SGreg Roach { 417*a7a24840SGreg Roach $tree_ids = array_map(function (Tree $tree) { 418*a7a24840SGreg Roach return $tree->id(); 419*a7a24840SGreg Roach }, $trees); 420*a7a24840SGreg Roach 421*a7a24840SGreg Roach $query->whereIn($tree_id_field, $tree_ids); 422*a7a24840SGreg Roach } 423*a7a24840SGreg Roach 424*a7a24840SGreg Roach /** 425*a7a24840SGreg Roach * A closure to filter records by privacy-filtered GEDCOM data. 426*a7a24840SGreg Roach * 427*a7a24840SGreg Roach * @param array $search_terms 428*a7a24840SGreg Roach * 429*a7a24840SGreg Roach * @return Closure 430*a7a24840SGreg Roach */ 431*a7a24840SGreg Roach private function rawGedcomFilter(array $search_terms): Closure 432*a7a24840SGreg Roach { 433*a7a24840SGreg Roach return function (GedcomRecord $record) use ($search_terms): bool { 434*a7a24840SGreg Roach // Ignore non-genealogy fields 435*a7a24840SGreg Roach $gedcom = preg_replace('/\n\d (?:_UID) .*/', '', $record->gedcom()); 436*a7a24840SGreg Roach 437*a7a24840SGreg Roach // Ignore matches in links 438*a7a24840SGreg Roach $gedcom = preg_replace('/\n\d ' . Gedcom::REGEX_TAG . '( @' . Gedcom::REGEX_XREF . '@)?/', '', $gedcom); 439*a7a24840SGreg Roach 440*a7a24840SGreg Roach // Re-apply the filtering 441*a7a24840SGreg Roach foreach ($search_terms as $search_term) { 442*a7a24840SGreg Roach if (mb_stripos($gedcom, $search_term) === false) { 443*a7a24840SGreg Roach return false; 444*a7a24840SGreg Roach } 445*a7a24840SGreg Roach } 446*a7a24840SGreg Roach 447*a7a24840SGreg Roach return true; 448*a7a24840SGreg Roach }; 449*a7a24840SGreg Roach } 45032cd2800SGreg Roach} 451