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