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; 21a7a24840SGreg Roachuse Fisharebest\Localization\Locale\LocaleInterface; 2232cd2800SGreg Roachuse Fisharebest\Webtrees\Family; 23a7a24840SGreg 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; 34a7a24840SGreg Roachuse Illuminate\Database\Query\Expression; 3532cd2800SGreg Roachuse Illuminate\Database\Query\JoinClause; 3632cd2800SGreg Roachuse Illuminate\Support\Collection; 3732cd2800SGreg Roachuse stdClass; 38a7a24840SGreg Roachuse function mb_stripos; 3932cd2800SGreg Roach 4032cd2800SGreg Roach/** 4132cd2800SGreg Roach * Search trees for genealogy records. 4232cd2800SGreg Roach */ 4332cd2800SGreg Roachclass SearchService 4432cd2800SGreg Roach{ 45a7a24840SGreg Roach /** @var LocaleInterface */ 46a7a24840SGreg Roach private $locale; 47a7a24840SGreg Roach 48a7a24840SGreg Roach /** 49a7a24840SGreg Roach * SearchService constructor. 50a7a24840SGreg Roach * 51a7a24840SGreg Roach * @param LocaleInterface $locale 52a7a24840SGreg Roach */ 53a7a24840SGreg Roach public function __construct(LocaleInterface $locale) 54a7a24840SGreg Roach { 55a7a24840SGreg Roach $this->locale = $locale; 56a7a24840SGreg Roach } 57a7a24840SGreg Roach 58a7a24840SGreg Roach /** 59a7a24840SGreg Roach * @param Tree[] $trees 60a7a24840SGreg Roach * @param string[] $search 61a7a24840SGreg Roach * 62a7a24840SGreg Roach * @return Collection|Family[] 63a7a24840SGreg Roach */ 64a7a24840SGreg Roach public function searchFamilies(array $trees, array $search): Collection 65a7a24840SGreg Roach { 66a7a24840SGreg Roach $query = DB::table('families'); 67a7a24840SGreg Roach 68a7a24840SGreg Roach $this->whereTrees($query, 'f_file', $trees); 69a7a24840SGreg Roach $this->whereSearch($query, 'f_gedcom', $search); 70a7a24840SGreg Roach 71a7a24840SGreg Roach return $query 72a7a24840SGreg Roach ->get() 73a7a24840SGreg Roach ->map(Family::rowMapper()) 74a7a24840SGreg Roach ->filter(GedcomRecord::accessFilter()) 75*7f5fa3c2SGreg Roach ->filter($this->rawGedcomFilter($search)); 76a7a24840SGreg Roach } 77a7a24840SGreg Roach 7832cd2800SGreg Roach /** 7932cd2800SGreg Roach * Search for families by name. 8032cd2800SGreg Roach * 81a7a24840SGreg Roach * @param Tree[] $trees 82a7a24840SGreg Roach * @param string[] $search 8332cd2800SGreg Roach * @param int $offset 8432cd2800SGreg Roach * @param int $limit 8532cd2800SGreg Roach * 8632cd2800SGreg Roach * @return Collection|Family[] 8732cd2800SGreg Roach */ 88a7a24840SGreg 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') 91a7a24840SGreg Roach ->join('name AS husb_name', function (JoinClause $join): void { 9232cd2800SGreg Roach $join 9332cd2800SGreg Roach ->on('husb_name.n_file', '=', 'families.f_file') 94a7a24840SGreg Roach ->on('husb_name.n_id', '=', 'families.f_husb'); 9532cd2800SGreg Roach }) 96a7a24840SGreg Roach ->join('name AS wife_name', function (JoinClause $join): void { 9732cd2800SGreg Roach $join 9832cd2800SGreg Roach ->on('wife_name.n_file', '=', 'families.f_file') 99a7a24840SGreg Roach ->on('wife_name.n_id', '=', 'families.f_wife'); 10032cd2800SGreg Roach }) 101a7a24840SGreg Roach ->where('wife_name.n_type', '<>', '_MARNM') 102a7a24840SGreg Roach ->where('husb_name.n_type', '<>', '_MARNM'); 103a7a24840SGreg Roach 104a7a24840SGreg Roach $prefix = DB::connection()->getTablePrefix(); 105a7a24840SGreg Roach $field = DB::raw($prefix . 'husb_name.n_full || ' . $prefix . 'wife_name.n_full'); 106a7a24840SGreg Roach 107a7a24840SGreg Roach $this->whereTrees($query, 'f_file', $trees); 108a7a24840SGreg Roach $this->whereSearch($query, $field, $search); 109a7a24840SGreg Roach 110a7a24840SGreg 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 116a7a24840SGreg Roach return $this->paginateQuery($query, Family::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 117a7a24840SGreg Roach } 118a7a24840SGreg Roach 119a7a24840SGreg Roach /** 120a7a24840SGreg Roach * @param Tree[] $trees 121a7a24840SGreg Roach * @param string[] $search 122a7a24840SGreg Roach * 123a7a24840SGreg Roach * @return Collection|Individual[] 124a7a24840SGreg Roach */ 125a7a24840SGreg Roach public function searchIndividuals(array $trees, array $search): Collection 126a7a24840SGreg Roach { 127a7a24840SGreg Roach $query = DB::table('individuals'); 128a7a24840SGreg Roach 129a7a24840SGreg Roach $this->whereTrees($query, 'i_file', $trees); 130a7a24840SGreg Roach $this->whereSearch($query, 'i_gedcom', $search); 131a7a24840SGreg Roach 132a7a24840SGreg Roach return $query 133a7a24840SGreg Roach ->get() 134a7a24840SGreg Roach ->map(Individual::rowMapper()) 135a7a24840SGreg Roach ->filter(GedcomRecord::accessFilter()) 136*7f5fa3c2SGreg Roach ->filter($this->rawGedcomFilter($search)); 13732cd2800SGreg Roach } 13832cd2800SGreg Roach 13932cd2800SGreg Roach /** 14032cd2800SGreg Roach * Search for individuals by name. 14132cd2800SGreg Roach * 142a7a24840SGreg Roach * @param Tree[] $trees 143a7a24840SGreg Roach * @param string[] $search 14432cd2800SGreg Roach * @param int $offset 14532cd2800SGreg Roach * @param int $limit 14632cd2800SGreg Roach * 14732cd2800SGreg Roach * @return Collection|Individual[] 14832cd2800SGreg Roach */ 149a7a24840SGreg 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') 152a7a24840SGreg Roach ->join('name', function (JoinClause $join): void { 15332cd2800SGreg Roach $join 15432cd2800SGreg Roach ->on('name.n_file', '=', 'individuals.i_file') 155a7a24840SGreg 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 161a7a24840SGreg Roach $this->whereTrees($query, 'i_file', $trees); 162a7a24840SGreg Roach $this->whereSearch($query, 'n_full', $search); 163a7a24840SGreg Roach 164a7a24840SGreg 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 * 170a7a24840SGreg Roach * @param Tree[] $trees 171a7a24840SGreg Roach * @param string[] $search 17232cd2800SGreg Roach * @param int $offset 17332cd2800SGreg Roach * @param int $limit 17432cd2800SGreg Roach * 17532cd2800SGreg Roach * @return Collection|Media[] 17632cd2800SGreg Roach */ 177a7a24840SGreg Roach public function searchMedia(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 17832cd2800SGreg Roach { 179a7a24840SGreg Roach $query = DB::table('media'); 18032cd2800SGreg Roach 181a7a24840SGreg Roach $this->whereTrees($query, 'media.m_file', $trees); 182a7a24840SGreg Roach $this->whereSearch($query, 'm_gedcom', $search); 183a7a24840SGreg Roach 184a7a24840SGreg Roach return $this->paginateQuery($query, Media::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 18532cd2800SGreg Roach } 18632cd2800SGreg Roach 18732cd2800SGreg Roach /** 18832cd2800SGreg Roach * Search for notes. 18932cd2800SGreg Roach * 190a7a24840SGreg Roach * @param Tree[] $trees 191a7a24840SGreg Roach * @param string[] $search 19232cd2800SGreg Roach * @param int $offset 19332cd2800SGreg Roach * @param int $limit 19432cd2800SGreg Roach * 19532cd2800SGreg Roach * @return Collection|Note[] 19632cd2800SGreg Roach */ 197a7a24840SGreg 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') 200a7a24840SGreg Roach ->where('o_type', '=', 'NOTE'); 20132cd2800SGreg Roach 202a7a24840SGreg Roach $this->whereTrees($query, 'o_file', $trees); 203a7a24840SGreg Roach $this->whereSearch($query, 'o_gedcom', $search); 204a7a24840SGreg Roach 205a7a24840SGreg Roach return $this->paginateQuery($query, Note::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 20632cd2800SGreg Roach } 20732cd2800SGreg Roach 20832cd2800SGreg Roach /** 20932cd2800SGreg Roach * Search for repositories. 21032cd2800SGreg Roach * 211a7a24840SGreg Roach * @param Tree[] $trees 212a7a24840SGreg Roach * @param string[] $search 21332cd2800SGreg Roach * @param int $offset 21432cd2800SGreg Roach * @param int $limit 21532cd2800SGreg Roach * 21632cd2800SGreg Roach * @return Collection|Repository[] 21732cd2800SGreg Roach */ 218a7a24840SGreg 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') 221a7a24840SGreg Roach ->where('o_type', '=', 'REPO'); 22232cd2800SGreg Roach 223a7a24840SGreg Roach $this->whereTrees($query, 'o_file', $trees); 224a7a24840SGreg Roach $this->whereSearch($query, 'o_gedcom', $search); 225a7a24840SGreg Roach 226a7a24840SGreg Roach return $this->paginateQuery($query, Repository::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 22732cd2800SGreg Roach } 22832cd2800SGreg Roach 22932cd2800SGreg Roach /** 230a7a24840SGreg Roach * Search for sources. 23132cd2800SGreg Roach * 232a7a24840SGreg Roach * @param Tree[] $trees 233a7a24840SGreg Roach * @param string[] $search 23432cd2800SGreg Roach * @param int $offset 23532cd2800SGreg Roach * @param int $limit 23632cd2800SGreg Roach * 23732cd2800SGreg Roach * @return Collection|Source[] 23832cd2800SGreg Roach */ 239a7a24840SGreg Roach public function searchSources(array $trees, array $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 240a7a24840SGreg Roach { 241a7a24840SGreg Roach $query = DB::table('sources'); 242a7a24840SGreg Roach 243a7a24840SGreg Roach $this->whereTrees($query, 's_file', $trees); 244a7a24840SGreg Roach $this->whereSearch($query, 's_gedcom', $search); 245a7a24840SGreg Roach 246a7a24840SGreg Roach return $this->paginateQuery($query, Source::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 247a7a24840SGreg Roach } 248a7a24840SGreg Roach 249a7a24840SGreg Roach /** 250a7a24840SGreg Roach * Search for sources by name. 251a7a24840SGreg Roach * 252a7a24840SGreg Roach * @param Tree[] $trees 253a7a24840SGreg Roach * @param string[] $search 254a7a24840SGreg Roach * @param int $offset 255a7a24840SGreg Roach * @param int $limit 256a7a24840SGreg Roach * 257a7a24840SGreg Roach * @return Collection|Source[] 258a7a24840SGreg Roach */ 259a7a24840SGreg 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 264a7a24840SGreg Roach $this->whereTrees($query, 's_file', $trees); 265a7a24840SGreg Roach $this->whereSearch($query, 's_name', $search); 266a7a24840SGreg Roach 267a7a24840SGreg Roach return $this->paginateQuery($query, Source::rowMapper(), GedcomRecord::accessFilter(), $offset, $limit); 26832cd2800SGreg Roach } 26932cd2800SGreg Roach 27032cd2800SGreg Roach /** 27132cd2800SGreg Roach * Search for submitters. 27232cd2800SGreg Roach * 273a7a24840SGreg Roach * @param Tree[] $trees 274a7a24840SGreg Roach * @param string[] $search 27532cd2800SGreg Roach * @param int $offset 27632cd2800SGreg Roach * @param int $limit 27732cd2800SGreg Roach * 27832cd2800SGreg Roach * @return Collection|GedcomRecord[] 27932cd2800SGreg Roach */ 280a7a24840SGreg 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') 283a7a24840SGreg Roach ->where('o_type', '=', 'SUBM'); 28432cd2800SGreg Roach 285a7a24840SGreg Roach $this->whereTrees($query, 'o_file', $trees); 286a7a24840SGreg Roach $this->whereSearch($query, 'o_gedcom', $search); 287a7a24840SGreg Roach 288a7a24840SGreg 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 345a7a24840SGreg Roach $filter = function (): bool { 346a7a24840SGreg Roach return true; 347a7a24840SGreg Roach }; 348b68caec6SGreg Roach 349a7a24840SGreg Roach return $this->paginateQuery($query, $row_mapper, $filter, $offset, $limit); 350a7a24840SGreg 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. 357a7a24840SGreg 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 */ 363a7a24840SGreg 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. 370a7a24840SGreg 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 } 389a7a24840SGreg Roach 390a7a24840SGreg Roach /** 391a7a24840SGreg Roach * Apply search filters to a SQL query column. Apply collation rules to MySQL. 392a7a24840SGreg Roach * 393a7a24840SGreg Roach * @param Builder $query 394a7a24840SGreg Roach * @param Expression|string $field 395a7a24840SGreg Roach * @param string[] $search_terms 396a7a24840SGreg Roach */ 397a7a24840SGreg Roach private function whereSearch(Builder $query, $field, array $search_terms): void 398a7a24840SGreg Roach { 399a7a24840SGreg Roach if ($field instanceof Expression) { 400a7a24840SGreg Roach $field = $field->getValue(); 401a7a24840SGreg Roach } 402a7a24840SGreg Roach 403a7a24840SGreg Roach $field = DB::raw($field . ' /*! COLLATE ' . 'utf8_' . $this->locale->collation() . ' */'); 404a7a24840SGreg Roach 405a7a24840SGreg Roach foreach ($search_terms as $search_term) { 406a7a24840SGreg Roach $query->whereContains($field, $search_term); 407a7a24840SGreg Roach } 408a7a24840SGreg Roach } 409a7a24840SGreg Roach 410a7a24840SGreg Roach /** 411a7a24840SGreg Roach * @param Builder $query 412a7a24840SGreg Roach * @param string $tree_id_field 413a7a24840SGreg Roach * @param Tree[] $trees 414a7a24840SGreg Roach */ 415a7a24840SGreg Roach private function whereTrees(Builder $query, string $tree_id_field, array $trees): void 416a7a24840SGreg Roach { 417a7a24840SGreg Roach $tree_ids = array_map(function (Tree $tree) { 418a7a24840SGreg Roach return $tree->id(); 419a7a24840SGreg Roach }, $trees); 420a7a24840SGreg Roach 421a7a24840SGreg Roach $query->whereIn($tree_id_field, $tree_ids); 422a7a24840SGreg Roach } 423a7a24840SGreg Roach 424a7a24840SGreg Roach /** 425a7a24840SGreg Roach * A closure to filter records by privacy-filtered GEDCOM data. 426a7a24840SGreg Roach * 427a7a24840SGreg Roach * @param array $search_terms 428a7a24840SGreg Roach * 429a7a24840SGreg Roach * @return Closure 430a7a24840SGreg Roach */ 431a7a24840SGreg Roach private function rawGedcomFilter(array $search_terms): Closure 432a7a24840SGreg Roach { 433a7a24840SGreg Roach return function (GedcomRecord $record) use ($search_terms): bool { 434a7a24840SGreg Roach // Ignore non-genealogy fields 435a7a24840SGreg Roach $gedcom = preg_replace('/\n\d (?:_UID) .*/', '', $record->gedcom()); 436a7a24840SGreg Roach 437a7a24840SGreg Roach // Ignore matches in links 438a7a24840SGreg Roach $gedcom = preg_replace('/\n\d ' . Gedcom::REGEX_TAG . '( @' . Gedcom::REGEX_XREF . '@)?/', '', $gedcom); 439a7a24840SGreg Roach 440a7a24840SGreg Roach // Re-apply the filtering 441a7a24840SGreg Roach foreach ($search_terms as $search_term) { 442a7a24840SGreg Roach if (mb_stripos($gedcom, $search_term) === false) { 443a7a24840SGreg Roach return false; 444a7a24840SGreg Roach } 445a7a24840SGreg Roach } 446a7a24840SGreg Roach 447a7a24840SGreg Roach return true; 448a7a24840SGreg Roach }; 449a7a24840SGreg Roach } 45032cd2800SGreg Roach} 451