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*b68caec6SGreg Roachuse Fisharebest\Webtrees\Database; 2232cd2800SGreg Roachuse Fisharebest\Webtrees\Family; 2332cd2800SGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 24*b68caec6SGreg Roachuse Fisharebest\Webtrees\I18N; 2532cd2800SGreg Roachuse Fisharebest\Webtrees\Individual; 2632cd2800SGreg Roachuse Fisharebest\Webtrees\Media; 2732cd2800SGreg Roachuse Fisharebest\Webtrees\Note; 28*b68caec6SGreg 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; 3432cd2800SGreg Roachuse Illuminate\Database\Query\JoinClause; 3532cd2800SGreg Roachuse Illuminate\Support\Collection; 36*b68caec6SGreg Roachuse function method_exists; 3732cd2800SGreg Roachuse stdClass; 3832cd2800SGreg Roach 3932cd2800SGreg Roach/** 4032cd2800SGreg Roach * Search trees for genealogy records. 4132cd2800SGreg Roach */ 4232cd2800SGreg Roachclass SearchService 4332cd2800SGreg Roach{ 4432cd2800SGreg Roach /** 4532cd2800SGreg Roach * Search for families by name. 4632cd2800SGreg Roach * 4732cd2800SGreg Roach * @param Tree $tree 4832cd2800SGreg Roach * @param string $search 4932cd2800SGreg Roach * @param int $offset 5032cd2800SGreg Roach * @param int $limit 5132cd2800SGreg Roach * 5232cd2800SGreg Roach * @return Collection|Family[] 5332cd2800SGreg Roach */ 5432cd2800SGreg Roach public function searchFamiliesByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 5532cd2800SGreg Roach { 5632cd2800SGreg Roach $prefix = DB::connection()->getTablePrefix(); 5732cd2800SGreg Roach 5832cd2800SGreg Roach $query = DB::table('families') 5932cd2800SGreg Roach ->where('f_file', '=', $tree->id()) 6032cd2800SGreg Roach ->join('name AS husb_name', function (JoinClause $join) use ($search): void { 6132cd2800SGreg Roach $join 6232cd2800SGreg Roach ->on('husb_name.n_file', '=', 'families.f_file') 6332cd2800SGreg Roach ->on('husb_name.n_id', '=', 'families.f_husb') 6432cd2800SGreg Roach ->where('husb_name.n_type', '<>', '_MARNM'); 6532cd2800SGreg Roach }) 6632cd2800SGreg Roach ->join('name AS wife_name', function (JoinClause $join) use ($search): void { 6732cd2800SGreg Roach $join 6832cd2800SGreg Roach ->on('wife_name.n_file', '=', 'families.f_file') 6932cd2800SGreg Roach ->on('wife_name.n_id', '=', 'families.f_wife') 7032cd2800SGreg Roach ->where('wife_name.n_type', '<>', '_MARNM'); 7132cd2800SGreg Roach }) 7232cd2800SGreg Roach ->whereContains(DB::raw("CONCAT(" . $prefix . "husb_name.n_full, ' ', " . $prefix . "wife_name.n_full)"), $search) 7332cd2800SGreg Roach ->orderBy('husb_name.n_sort') 7432cd2800SGreg Roach ->orderBy('wife_name.n_sort') 7532cd2800SGreg Roach ->select(['families.f_id', 'families.f_gedcom', 'husb_name.n_sort', 'wife_name.n_sort']) 7632cd2800SGreg Roach ->distinct(); 7732cd2800SGreg Roach 78886b77daSGreg Roach $row_mapper = Family::rowMapper($tree); 7932cd2800SGreg Roach 8032cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 8132cd2800SGreg Roach } 8232cd2800SGreg Roach 8332cd2800SGreg Roach /** 8432cd2800SGreg Roach * Search for individuals by name. 8532cd2800SGreg Roach * 8632cd2800SGreg Roach * @param Tree $tree 8732cd2800SGreg Roach * @param string $search 8832cd2800SGreg Roach * @param int $offset 8932cd2800SGreg Roach * @param int $limit 9032cd2800SGreg Roach * 9132cd2800SGreg Roach * @return Collection|Individual[] 9232cd2800SGreg Roach */ 9332cd2800SGreg Roach public function searchIndividualsByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 9432cd2800SGreg Roach { 9532cd2800SGreg Roach $query = DB::table('individuals') 9632cd2800SGreg Roach ->where('i_file', '=', $tree->id()) 9732cd2800SGreg Roach ->join('name', function (JoinClause $join) use ($search): void { 9832cd2800SGreg Roach $join 9932cd2800SGreg Roach ->on('name.n_file', '=', 'individuals.i_file') 10032cd2800SGreg Roach ->on('name.n_id', '=', 'individuals.i_id') 10132cd2800SGreg Roach ->whereContains('n_full', $search); 10232cd2800SGreg Roach }) 10332cd2800SGreg Roach ->select(['individuals.i_id', 'individuals.i_gedcom', 'n_sort']) 10432cd2800SGreg Roach ->distinct(); 10532cd2800SGreg Roach 106886b77daSGreg Roach $row_mapper = Individual::rowMapper($tree); 10732cd2800SGreg Roach 10832cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 10932cd2800SGreg Roach } 11032cd2800SGreg Roach 11132cd2800SGreg Roach /** 11232cd2800SGreg Roach * Search for media objects. 11332cd2800SGreg Roach * 11432cd2800SGreg Roach * @param Tree $tree 11532cd2800SGreg Roach * @param string $search 11632cd2800SGreg Roach * @param int $offset 11732cd2800SGreg Roach * @param int $limit 11832cd2800SGreg Roach * 11932cd2800SGreg Roach * @return Collection|Media[] 12032cd2800SGreg Roach */ 12132cd2800SGreg Roach public function searchMedia(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 12232cd2800SGreg Roach { 12332cd2800SGreg Roach $query = DB::table('media') 12432cd2800SGreg Roach ->where('media.m_file', '=', $tree->id()) 12532cd2800SGreg Roach ->join('media_file', function (JoinClause $join) use ($search): void { 12632cd2800SGreg Roach $join 12732cd2800SGreg Roach ->on('media_file.m_file', '=', 'media.m_file') 12832cd2800SGreg Roach ->on('media_file.m_id', '=', 'media.m_id'); 12932cd2800SGreg Roach }) 13032cd2800SGreg Roach ->where(function (Builder $query) use ($search): void { 13132cd2800SGreg Roach $query 13232cd2800SGreg Roach ->whereContains('multimedia_file_refn', $search) 13332cd2800SGreg Roach ->whereContains('descriptive_title', $search, 'or'); 13432cd2800SGreg Roach }) 13532cd2800SGreg Roach ->select(['media.m_id', 'media.m_gedcom']) 13632cd2800SGreg Roach ->distinct(); 13732cd2800SGreg Roach 138886b77daSGreg Roach $row_mapper = Media::rowMapper($tree); 13932cd2800SGreg Roach 14032cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 14132cd2800SGreg Roach } 14232cd2800SGreg Roach 14332cd2800SGreg Roach /** 14432cd2800SGreg Roach * Search for notes. 14532cd2800SGreg Roach * 14632cd2800SGreg Roach * @param Tree $tree 14732cd2800SGreg Roach * @param string $search 14832cd2800SGreg Roach * @param int $offset 14932cd2800SGreg Roach * @param int $limit 15032cd2800SGreg Roach * 15132cd2800SGreg Roach * @return Collection|Note[] 15232cd2800SGreg Roach */ 15332cd2800SGreg Roach public function searchNotes(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 15432cd2800SGreg Roach { 15532cd2800SGreg Roach $query = DB::table('other') 15632cd2800SGreg Roach ->where('o_file', '=', $tree->id()) 15732cd2800SGreg Roach ->where('o_type', '=', 'NOTE') 15832cd2800SGreg Roach ->whereContains('o_gedcom', $search) 15932cd2800SGreg Roach ->orderBy('o_id') 16032cd2800SGreg Roach ->select(['o_id', 'o_gedcom']); 16132cd2800SGreg Roach 162886b77daSGreg Roach $row_mapper = Note::rowMapper($tree); 16332cd2800SGreg Roach 16432cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 16532cd2800SGreg Roach } 16632cd2800SGreg Roach 16732cd2800SGreg Roach /** 16832cd2800SGreg Roach * Search for repositories. 16932cd2800SGreg Roach * 17032cd2800SGreg Roach * @param Tree $tree 17132cd2800SGreg Roach * @param string $search 17232cd2800SGreg Roach * @param int $offset 17332cd2800SGreg Roach * @param int $limit 17432cd2800SGreg Roach * 17532cd2800SGreg Roach * @return Collection|Repository[] 17632cd2800SGreg Roach */ 17732cd2800SGreg Roach public function searchRepositories(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 17832cd2800SGreg Roach { 17932cd2800SGreg Roach $query = DB::table('other') 18032cd2800SGreg Roach ->where('o_file', '=', $tree->id()) 18132cd2800SGreg Roach ->where('o_type', '=', 'REPO') 18232cd2800SGreg Roach ->whereContains('o_gedcom', $search) 18332cd2800SGreg Roach ->orderBy('o_id') 18432cd2800SGreg Roach ->select(['o_id', 'o_gedcom']); 18532cd2800SGreg Roach 186886b77daSGreg Roach $row_mapper = Repository::rowMapper($tree); 18732cd2800SGreg Roach 18832cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 18932cd2800SGreg Roach } 19032cd2800SGreg Roach 19132cd2800SGreg Roach /** 19232cd2800SGreg Roach * Search for sources by name. 19332cd2800SGreg Roach * 19432cd2800SGreg Roach * @param Tree $tree 19532cd2800SGreg Roach * @param string $search 19632cd2800SGreg Roach * @param int $offset 19732cd2800SGreg Roach * @param int $limit 19832cd2800SGreg Roach * 19932cd2800SGreg Roach * @return Collection|Source[] 20032cd2800SGreg Roach */ 20132cd2800SGreg Roach public function searchSourcesByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 20232cd2800SGreg Roach { 20332cd2800SGreg Roach $query = DB::table('sources') 20432cd2800SGreg Roach ->where('s_file', '=', $tree->id()) 20532cd2800SGreg Roach ->whereContains('s_name', $search) 20632cd2800SGreg Roach ->orderBy('s_name') 20732cd2800SGreg Roach ->select(['s_id', 's_gedcom']); 20832cd2800SGreg Roach 209886b77daSGreg Roach $row_mapper = Source::rowMapper($tree); 21032cd2800SGreg Roach 21132cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 21232cd2800SGreg Roach } 21332cd2800SGreg Roach 21432cd2800SGreg Roach /** 21532cd2800SGreg Roach * Search for submitters. 21632cd2800SGreg Roach * 21732cd2800SGreg Roach * @param Tree $tree 21832cd2800SGreg Roach * @param string $search 21932cd2800SGreg Roach * @param int $offset 22032cd2800SGreg Roach * @param int $limit 22132cd2800SGreg Roach * 22232cd2800SGreg Roach * @return Collection|GedcomRecord[] 22332cd2800SGreg Roach */ 22432cd2800SGreg Roach public function searchSubmitters(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 22532cd2800SGreg Roach { 22632cd2800SGreg Roach $query = DB::table('other') 22732cd2800SGreg Roach ->where('o_file', '=', $tree->id()) 22832cd2800SGreg Roach ->where('o_type', '=', 'SUBM') 22932cd2800SGreg Roach ->whereContains('o_gedcom', $search) 23032cd2800SGreg Roach ->orderBy('o_id') 23132cd2800SGreg Roach ->select(['o_id', 'o_gedcom']); 23232cd2800SGreg Roach 233886b77daSGreg Roach $row_mapper = GedcomRecord::rowMapper($tree); 23432cd2800SGreg Roach 23532cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 23632cd2800SGreg Roach } 23732cd2800SGreg Roach 23832cd2800SGreg Roach /** 239*b68caec6SGreg Roach * Search for places. 240*b68caec6SGreg Roach * 241*b68caec6SGreg Roach * @param Tree $tree 242*b68caec6SGreg Roach * @param string $search 243*b68caec6SGreg Roach * @param int $offset 244*b68caec6SGreg Roach * @param int $limit 245*b68caec6SGreg Roach * 246*b68caec6SGreg Roach * @return Collection|Place[] 247*b68caec6SGreg Roach */ 248*b68caec6SGreg Roach public function searchPlaces(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 249*b68caec6SGreg Roach { 250*b68caec6SGreg Roach $searches = explode(',', $search); 251*b68caec6SGreg Roach 252*b68caec6SGreg Roach $query = DB::table('places AS p0') 253*b68caec6SGreg Roach ->where('p0.p_file', '=', $tree->id()) 254*b68caec6SGreg Roach ->leftJoin('places AS p1', 'p1.p_id', '=', 'p0.p_parent_id') 255*b68caec6SGreg Roach ->leftJoin('places AS p2', 'p2.p_id', '=', 'p1.p_parent_id') 256*b68caec6SGreg Roach ->leftJoin('places AS p3', 'p3.p_id', '=', 'p2.p_parent_id') 257*b68caec6SGreg Roach ->leftJoin('places AS p4', 'p4.p_id', '=', 'p3.p_parent_id') 258*b68caec6SGreg Roach ->leftJoin('places AS p5', 'p5.p_id', '=', 'p4.p_parent_id') 259*b68caec6SGreg Roach ->leftJoin('places AS p6', 'p6.p_id', '=', 'p5.p_parent_id') 260*b68caec6SGreg Roach ->leftJoin('places AS p7', 'p7.p_id', '=', 'p6.p_parent_id') 261*b68caec6SGreg Roach ->leftJoin('places AS p8', 'p8.p_id', '=', 'p7.p_parent_id') 262*b68caec6SGreg Roach ->orderBy('p0.p_place') 263*b68caec6SGreg Roach ->orderBy('p1.p_place') 264*b68caec6SGreg Roach ->orderBy('p2.p_place') 265*b68caec6SGreg Roach ->orderBy('p3.p_place') 266*b68caec6SGreg Roach ->orderBy('p4.p_place') 267*b68caec6SGreg Roach ->orderBy('p5.p_place') 268*b68caec6SGreg Roach ->orderBy('p6.p_place') 269*b68caec6SGreg Roach ->orderBy('p7.p_place') 270*b68caec6SGreg Roach ->orderBy('p8.p_place') 271*b68caec6SGreg Roach ->select([ 272*b68caec6SGreg Roach 'p0.p_place AS place0', 273*b68caec6SGreg Roach 'p1.p_place AS place1', 274*b68caec6SGreg Roach 'p2.p_place AS place2', 275*b68caec6SGreg Roach 'p3.p_place AS place3', 276*b68caec6SGreg Roach 'p4.p_place AS place4', 277*b68caec6SGreg Roach 'p5.p_place AS place5', 278*b68caec6SGreg Roach 'p6.p_place AS place6', 279*b68caec6SGreg Roach 'p7.p_place AS place7', 280*b68caec6SGreg Roach 'p8.p_place AS place8', 281*b68caec6SGreg Roach ]); 282*b68caec6SGreg Roach 283*b68caec6SGreg Roach // Filter each level of the hierarchy. 284*b68caec6SGreg Roach foreach (explode(',', $search, 9) as $level => $string) { 285*b68caec6SGreg Roach $query->whereContains('p' . $level . '.p_place', $string); 286*b68caec6SGreg Roach } 287*b68caec6SGreg Roach 288*b68caec6SGreg Roach $row_mapper = function (stdClass $row) use ($tree): Place { 289*b68caec6SGreg Roach $place = implode(', ', array_filter((array) $row)); 290*b68caec6SGreg Roach 291*b68caec6SGreg Roach return new Place($place, $tree); 292*b68caec6SGreg Roach }; 293*b68caec6SGreg Roach 294*b68caec6SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 295*b68caec6SGreg Roach } 296*b68caec6SGreg Roach 297*b68caec6SGreg Roach 298*b68caec6SGreg Roach /** 29932cd2800SGreg Roach * Paginate a search query. 30032cd2800SGreg Roach * 30132cd2800SGreg Roach * @param Builder $query Searches the database for the desired records. 30232cd2800SGreg Roach * @param Closure $row_mapper Converts a row from the query into a record. 30332cd2800SGreg Roach * @param int $offset Skip this many rows. 30432cd2800SGreg Roach * @param int $limit Take this many rows. 30532cd2800SGreg Roach * 30632cd2800SGreg Roach * @return Collection 30732cd2800SGreg Roach */ 30832cd2800SGreg Roach private function paginateQuery(Builder $query, Closure $row_mapper, int $offset, int $limit): Collection 30932cd2800SGreg Roach { 31032cd2800SGreg Roach $collection = new Collection(); 31132cd2800SGreg Roach 31232cd2800SGreg Roach foreach ($query->cursor() as $row) { 31332cd2800SGreg Roach $record = $row_mapper($row); 31432cd2800SGreg Roach 315*b68caec6SGreg Roach // If the object has a method "canShow()", then use it to filter for privacy. 316*b68caec6SGreg Roach if (!method_exists($record, 'canShow') || $record->canShow()) { 31732cd2800SGreg Roach if ($offset > 0) { 31832cd2800SGreg Roach $offset--; 31932cd2800SGreg Roach } else { 32032cd2800SGreg Roach if ($limit > 0) { 32132cd2800SGreg Roach $collection->push($record); 32232cd2800SGreg Roach } 32332cd2800SGreg Roach 32432cd2800SGreg Roach $limit--; 32532cd2800SGreg Roach 32632cd2800SGreg Roach if ($limit === 0) { 32732cd2800SGreg Roach break; 32832cd2800SGreg Roach } 32932cd2800SGreg Roach } 33032cd2800SGreg Roach } 33132cd2800SGreg Roach } 33232cd2800SGreg Roach 33332cd2800SGreg Roach return $collection; 33432cd2800SGreg Roach } 33532cd2800SGreg Roach} 336