1*32cd2800SGreg Roach<?php 2*32cd2800SGreg Roach/** 3*32cd2800SGreg Roach * webtrees: online genealogy 4*32cd2800SGreg Roach * Copyright (C) 2019 webtrees development team 5*32cd2800SGreg Roach * This program is free software: you can redistribute it and/or modify 6*32cd2800SGreg Roach * it under the terms of the GNU General Public License as published by 7*32cd2800SGreg Roach * the Free Software Foundation, either version 3 of the License, or 8*32cd2800SGreg Roach * (at your option) any later version. 9*32cd2800SGreg Roach * This program is distributed in the hope that it will be useful, 10*32cd2800SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 11*32cd2800SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12*32cd2800SGreg Roach * GNU General Public License for more details. 13*32cd2800SGreg Roach * You should have received a copy of the GNU General Public License 14*32cd2800SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 15*32cd2800SGreg Roach */ 16*32cd2800SGreg Roachdeclare(strict_types=1); 17*32cd2800SGreg Roach 18*32cd2800SGreg Roachnamespace Fisharebest\Webtrees\Services; 19*32cd2800SGreg Roach 20*32cd2800SGreg Roachuse Closure; 21*32cd2800SGreg Roachuse Fisharebest\Webtrees\Family; 22*32cd2800SGreg Roachuse Fisharebest\Webtrees\GedcomRecord; 23*32cd2800SGreg Roachuse Fisharebest\Webtrees\Individual; 24*32cd2800SGreg Roachuse Fisharebest\Webtrees\Media; 25*32cd2800SGreg Roachuse Fisharebest\Webtrees\Note; 26*32cd2800SGreg Roachuse Fisharebest\Webtrees\Repository; 27*32cd2800SGreg Roachuse Fisharebest\Webtrees\Source; 28*32cd2800SGreg Roachuse Fisharebest\Webtrees\Tree; 29*32cd2800SGreg Roachuse Illuminate\Database\Capsule\Manager as DB; 30*32cd2800SGreg Roachuse Illuminate\Database\Query\Builder; 31*32cd2800SGreg Roachuse Illuminate\Database\Query\JoinClause; 32*32cd2800SGreg Roachuse Illuminate\Support\Collection; 33*32cd2800SGreg Roachuse stdClass; 34*32cd2800SGreg Roach 35*32cd2800SGreg Roach/** 36*32cd2800SGreg Roach * Search trees for genealogy records. 37*32cd2800SGreg Roach */ 38*32cd2800SGreg Roachclass SearchService 39*32cd2800SGreg Roach{ 40*32cd2800SGreg Roach /** 41*32cd2800SGreg Roach * Search for families by name. 42*32cd2800SGreg Roach * 43*32cd2800SGreg Roach * @param Tree $tree 44*32cd2800SGreg Roach * @param string $search 45*32cd2800SGreg Roach * @param int $offset 46*32cd2800SGreg Roach * @param int $limit 47*32cd2800SGreg Roach * 48*32cd2800SGreg Roach * @return Collection|Family[] 49*32cd2800SGreg Roach */ 50*32cd2800SGreg Roach public function searchFamiliesByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 51*32cd2800SGreg Roach { 52*32cd2800SGreg Roach $prefix = DB::connection()->getTablePrefix(); 53*32cd2800SGreg Roach 54*32cd2800SGreg Roach $query = DB::table('families') 55*32cd2800SGreg Roach ->where('f_file', '=', $tree->id()) 56*32cd2800SGreg Roach ->join('name AS husb_name', function (JoinClause $join) use ($search): void { 57*32cd2800SGreg Roach $join 58*32cd2800SGreg Roach ->on('husb_name.n_file', '=', 'families.f_file') 59*32cd2800SGreg Roach ->on('husb_name.n_id', '=', 'families.f_husb') 60*32cd2800SGreg Roach ->where('husb_name.n_type', '<>', '_MARNM'); 61*32cd2800SGreg Roach }) 62*32cd2800SGreg Roach ->join('name AS wife_name', function (JoinClause $join) use ($search): void { 63*32cd2800SGreg Roach $join 64*32cd2800SGreg Roach ->on('wife_name.n_file', '=', 'families.f_file') 65*32cd2800SGreg Roach ->on('wife_name.n_id', '=', 'families.f_wife') 66*32cd2800SGreg Roach ->where('wife_name.n_type', '<>', '_MARNM'); 67*32cd2800SGreg Roach }) 68*32cd2800SGreg Roach ->whereContains(DB::raw("CONCAT(" . $prefix . "husb_name.n_full, ' ', " . $prefix . "wife_name.n_full)"), $search) 69*32cd2800SGreg Roach ->orderBy('husb_name.n_sort') 70*32cd2800SGreg Roach ->orderBy('wife_name.n_sort') 71*32cd2800SGreg Roach ->select(['families.f_id', 'families.f_gedcom', 'husb_name.n_sort', 'wife_name.n_sort']) 72*32cd2800SGreg Roach ->distinct(); 73*32cd2800SGreg Roach 74*32cd2800SGreg Roach $row_mapper = $this->familyRowMapper($tree); 75*32cd2800SGreg Roach 76*32cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 77*32cd2800SGreg Roach } 78*32cd2800SGreg Roach 79*32cd2800SGreg Roach /** 80*32cd2800SGreg Roach * Search for individuals by name. 81*32cd2800SGreg Roach * 82*32cd2800SGreg Roach * @param Tree $tree 83*32cd2800SGreg Roach * @param string $search 84*32cd2800SGreg Roach * @param int $offset 85*32cd2800SGreg Roach * @param int $limit 86*32cd2800SGreg Roach * 87*32cd2800SGreg Roach * @return Collection|Individual[] 88*32cd2800SGreg Roach */ 89*32cd2800SGreg Roach public function searchIndividualsByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 90*32cd2800SGreg Roach { 91*32cd2800SGreg Roach $query = DB::table('individuals') 92*32cd2800SGreg Roach ->where('i_file', '=', $tree->id()) 93*32cd2800SGreg Roach ->join('name', function (JoinClause $join) use ($search): void { 94*32cd2800SGreg Roach $join 95*32cd2800SGreg Roach ->on('name.n_file', '=', 'individuals.i_file') 96*32cd2800SGreg Roach ->on('name.n_id', '=', 'individuals.i_id') 97*32cd2800SGreg Roach ->whereContains('n_full', $search); 98*32cd2800SGreg Roach }) 99*32cd2800SGreg Roach ->select(['individuals.i_id', 'individuals.i_gedcom', 'n_sort']) 100*32cd2800SGreg Roach ->distinct(); 101*32cd2800SGreg Roach 102*32cd2800SGreg Roach $row_mapper = $this->individualRowMapper($tree); 103*32cd2800SGreg Roach 104*32cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 105*32cd2800SGreg Roach } 106*32cd2800SGreg Roach 107*32cd2800SGreg Roach /** 108*32cd2800SGreg Roach * Search for media objects. 109*32cd2800SGreg Roach * 110*32cd2800SGreg Roach * @param Tree $tree 111*32cd2800SGreg Roach * @param string $search 112*32cd2800SGreg Roach * @param int $offset 113*32cd2800SGreg Roach * @param int $limit 114*32cd2800SGreg Roach * 115*32cd2800SGreg Roach * @return Collection|Media[] 116*32cd2800SGreg Roach */ 117*32cd2800SGreg Roach public function searchMedia(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 118*32cd2800SGreg Roach { 119*32cd2800SGreg Roach $query = DB::table('media') 120*32cd2800SGreg Roach ->where('media.m_file', '=', $tree->id()) 121*32cd2800SGreg Roach ->join('media_file', function (JoinClause $join) use ($search): void { 122*32cd2800SGreg Roach $join 123*32cd2800SGreg Roach ->on('media_file.m_file', '=', 'media.m_file') 124*32cd2800SGreg Roach ->on('media_file.m_id', '=', 'media.m_id'); 125*32cd2800SGreg Roach }) 126*32cd2800SGreg Roach ->where(function (Builder $query) use ($search): void { 127*32cd2800SGreg Roach $query 128*32cd2800SGreg Roach ->whereContains('multimedia_file_refn', $search) 129*32cd2800SGreg Roach ->whereContains('descriptive_title', $search, 'or'); 130*32cd2800SGreg Roach }) 131*32cd2800SGreg Roach ->select(['media.m_id', 'media.m_gedcom']) 132*32cd2800SGreg Roach ->distinct(); 133*32cd2800SGreg Roach 134*32cd2800SGreg Roach $row_mapper = $this->mediaRowMapper($tree); 135*32cd2800SGreg Roach 136*32cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 137*32cd2800SGreg Roach } 138*32cd2800SGreg Roach 139*32cd2800SGreg Roach /** 140*32cd2800SGreg Roach * Search for notes. 141*32cd2800SGreg Roach * 142*32cd2800SGreg Roach * @param Tree $tree 143*32cd2800SGreg Roach * @param string $search 144*32cd2800SGreg Roach * @param int $offset 145*32cd2800SGreg Roach * @param int $limit 146*32cd2800SGreg Roach * 147*32cd2800SGreg Roach * @return Collection|Note[] 148*32cd2800SGreg Roach */ 149*32cd2800SGreg Roach public function searchNotes(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 150*32cd2800SGreg Roach { 151*32cd2800SGreg Roach $query = DB::table('other') 152*32cd2800SGreg Roach ->where('o_file', '=', $tree->id()) 153*32cd2800SGreg Roach ->where('o_type', '=', 'NOTE') 154*32cd2800SGreg Roach ->whereContains('o_gedcom', $search) 155*32cd2800SGreg Roach ->orderBy('o_id') 156*32cd2800SGreg Roach ->select(['o_id', 'o_gedcom']); 157*32cd2800SGreg Roach 158*32cd2800SGreg Roach $row_mapper = $this->noteRowMapper($tree); 159*32cd2800SGreg Roach 160*32cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 161*32cd2800SGreg Roach } 162*32cd2800SGreg Roach 163*32cd2800SGreg Roach /** 164*32cd2800SGreg Roach * Search for repositories. 165*32cd2800SGreg Roach * 166*32cd2800SGreg Roach * @param Tree $tree 167*32cd2800SGreg Roach * @param string $search 168*32cd2800SGreg Roach * @param int $offset 169*32cd2800SGreg Roach * @param int $limit 170*32cd2800SGreg Roach * 171*32cd2800SGreg Roach * @return Collection|Repository[] 172*32cd2800SGreg Roach */ 173*32cd2800SGreg Roach public function searchRepositories(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 174*32cd2800SGreg Roach { 175*32cd2800SGreg Roach $query = DB::table('other') 176*32cd2800SGreg Roach ->where('o_file', '=', $tree->id()) 177*32cd2800SGreg Roach ->where('o_type', '=', 'REPO') 178*32cd2800SGreg Roach ->whereContains('o_gedcom', $search) 179*32cd2800SGreg Roach ->orderBy('o_id') 180*32cd2800SGreg Roach ->select(['o_id', 'o_gedcom']); 181*32cd2800SGreg Roach 182*32cd2800SGreg Roach $row_mapper = $this->repositoryRowMapper($tree); 183*32cd2800SGreg Roach 184*32cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 185*32cd2800SGreg Roach } 186*32cd2800SGreg Roach 187*32cd2800SGreg Roach /** 188*32cd2800SGreg Roach * Search for sources by name. 189*32cd2800SGreg Roach * 190*32cd2800SGreg Roach * @param Tree $tree 191*32cd2800SGreg Roach * @param string $search 192*32cd2800SGreg Roach * @param int $offset 193*32cd2800SGreg Roach * @param int $limit 194*32cd2800SGreg Roach * 195*32cd2800SGreg Roach * @return Collection|Source[] 196*32cd2800SGreg Roach */ 197*32cd2800SGreg Roach public function searchSourcesByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 198*32cd2800SGreg Roach { 199*32cd2800SGreg Roach $query = DB::table('sources') 200*32cd2800SGreg Roach ->where('s_file', '=', $tree->id()) 201*32cd2800SGreg Roach ->whereContains('s_name', $search) 202*32cd2800SGreg Roach ->orderBy('s_name') 203*32cd2800SGreg Roach ->select(['s_id', 's_gedcom']); 204*32cd2800SGreg Roach 205*32cd2800SGreg Roach $row_mapper = $this->sourceRowMapper($tree); 206*32cd2800SGreg Roach 207*32cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 208*32cd2800SGreg Roach } 209*32cd2800SGreg Roach 210*32cd2800SGreg Roach /** 211*32cd2800SGreg Roach * Search for submitters. 212*32cd2800SGreg Roach * 213*32cd2800SGreg Roach * @param Tree $tree 214*32cd2800SGreg Roach * @param string $search 215*32cd2800SGreg Roach * @param int $offset 216*32cd2800SGreg Roach * @param int $limit 217*32cd2800SGreg Roach * 218*32cd2800SGreg Roach * @return Collection|GedcomRecord[] 219*32cd2800SGreg Roach */ 220*32cd2800SGreg Roach public function searchSubmitters(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection 221*32cd2800SGreg Roach { 222*32cd2800SGreg Roach $query = DB::table('other') 223*32cd2800SGreg Roach ->where('o_file', '=', $tree->id()) 224*32cd2800SGreg Roach ->where('o_type', '=', 'SUBM') 225*32cd2800SGreg Roach ->whereContains('o_gedcom', $search) 226*32cd2800SGreg Roach ->orderBy('o_id') 227*32cd2800SGreg Roach ->select(['o_id', 'o_gedcom']); 228*32cd2800SGreg Roach 229*32cd2800SGreg Roach $row_mapper = $this->submitterRowMapper($tree); 230*32cd2800SGreg Roach 231*32cd2800SGreg Roach return $this->paginateQuery($query, $row_mapper, $offset, $limit); 232*32cd2800SGreg Roach } 233*32cd2800SGreg Roach 234*32cd2800SGreg Roach /** 235*32cd2800SGreg Roach * Paginate a search query. 236*32cd2800SGreg Roach * 237*32cd2800SGreg Roach * @param Builder $query Searches the database for the desired records. 238*32cd2800SGreg Roach * @param Closure $row_mapper Converts a row from the query into a record. 239*32cd2800SGreg Roach * @param int $offset Skip this many rows. 240*32cd2800SGreg Roach * @param int $limit Take this many rows. 241*32cd2800SGreg Roach * 242*32cd2800SGreg Roach * @return Collection 243*32cd2800SGreg Roach */ 244*32cd2800SGreg Roach private function paginateQuery(Builder $query, Closure $row_mapper, int $offset, int $limit): Collection 245*32cd2800SGreg Roach { 246*32cd2800SGreg Roach $collection = new Collection(); 247*32cd2800SGreg Roach 248*32cd2800SGreg Roach foreach ($query->cursor() as $row) { 249*32cd2800SGreg Roach $record = $row_mapper($row); 250*32cd2800SGreg Roach 251*32cd2800SGreg Roach if ($record->canShow()) { 252*32cd2800SGreg Roach if ($offset > 0) { 253*32cd2800SGreg Roach $offset--; 254*32cd2800SGreg Roach } else { 255*32cd2800SGreg Roach if ($limit > 0) { 256*32cd2800SGreg Roach $collection->push($record); 257*32cd2800SGreg Roach } 258*32cd2800SGreg Roach 259*32cd2800SGreg Roach $limit--; 260*32cd2800SGreg Roach 261*32cd2800SGreg Roach if ($limit === 0) { 262*32cd2800SGreg Roach break; 263*32cd2800SGreg Roach } 264*32cd2800SGreg Roach } 265*32cd2800SGreg Roach } 266*32cd2800SGreg Roach } 267*32cd2800SGreg Roach 268*32cd2800SGreg Roach return $collection; 269*32cd2800SGreg Roach } 270*32cd2800SGreg Roach 271*32cd2800SGreg Roach /** 272*32cd2800SGreg Roach * A closure to map a database row to a family. 273*32cd2800SGreg Roach * 274*32cd2800SGreg Roach * @param Tree $tree 275*32cd2800SGreg Roach * 276*32cd2800SGreg Roach * @return Closure 277*32cd2800SGreg Roach */ 278*32cd2800SGreg Roach private function familyRowMapper(Tree $tree): Closure 279*32cd2800SGreg Roach { 280*32cd2800SGreg Roach return function (stdClass $row) use ($tree): Family { 281*32cd2800SGreg Roach return Family::getInstance($row->f_id, $tree, $row->f_gedcom); 282*32cd2800SGreg Roach }; 283*32cd2800SGreg Roach } 284*32cd2800SGreg Roach 285*32cd2800SGreg Roach /** 286*32cd2800SGreg Roach * A closure to map a database row to an individual. 287*32cd2800SGreg Roach * 288*32cd2800SGreg Roach * @param Tree $tree 289*32cd2800SGreg Roach * 290*32cd2800SGreg Roach * @return Closure 291*32cd2800SGreg Roach */ 292*32cd2800SGreg Roach private function individualRowMapper(Tree $tree): Closure 293*32cd2800SGreg Roach { 294*32cd2800SGreg Roach return function (stdClass $row) use ($tree): Individual { 295*32cd2800SGreg Roach return Individual::getInstance($row->i_id, $tree, $row->i_gedcom); 296*32cd2800SGreg Roach }; 297*32cd2800SGreg Roach } 298*32cd2800SGreg Roach 299*32cd2800SGreg Roach /** 300*32cd2800SGreg Roach * A closure to map a database row to a media object. 301*32cd2800SGreg Roach * 302*32cd2800SGreg Roach * @param Tree $tree 303*32cd2800SGreg Roach * 304*32cd2800SGreg Roach * @return Closure 305*32cd2800SGreg Roach */ 306*32cd2800SGreg Roach private function mediaRowMapper(Tree $tree): Closure 307*32cd2800SGreg Roach { 308*32cd2800SGreg Roach return function (stdClass $row) use ($tree): Media { 309*32cd2800SGreg Roach return Media::getInstance($row->m_id, $tree, $row->m_gedcom); 310*32cd2800SGreg Roach }; 311*32cd2800SGreg Roach } 312*32cd2800SGreg Roach 313*32cd2800SGreg Roach /** 314*32cd2800SGreg Roach * A closure to map a database row to a note. 315*32cd2800SGreg Roach * 316*32cd2800SGreg Roach * @param Tree $tree 317*32cd2800SGreg Roach * 318*32cd2800SGreg Roach * @return Closure 319*32cd2800SGreg Roach */ 320*32cd2800SGreg Roach private function noteRowMapper(Tree $tree): Closure 321*32cd2800SGreg Roach { 322*32cd2800SGreg Roach return function (stdClass $row) use ($tree): GedcomRecord { 323*32cd2800SGreg Roach return Note::getInstance($row->o_id, $tree, $row->o_gedcom); 324*32cd2800SGreg Roach }; 325*32cd2800SGreg Roach } 326*32cd2800SGreg Roach 327*32cd2800SGreg Roach /** 328*32cd2800SGreg Roach * A closure to map a database row to a repository. 329*32cd2800SGreg Roach * 330*32cd2800SGreg Roach * @param Tree $tree 331*32cd2800SGreg Roach * 332*32cd2800SGreg Roach * @return Closure 333*32cd2800SGreg Roach */ 334*32cd2800SGreg Roach private function repositoryRowMapper(Tree $tree): Closure 335*32cd2800SGreg Roach { 336*32cd2800SGreg Roach return function (stdClass $row) use ($tree): GedcomRecord { 337*32cd2800SGreg Roach return Repository::getInstance($row->o_id, $tree, $row->o_gedcom); 338*32cd2800SGreg Roach }; 339*32cd2800SGreg Roach } 340*32cd2800SGreg Roach 341*32cd2800SGreg Roach /** 342*32cd2800SGreg Roach * A closure to map a database row to an source. 343*32cd2800SGreg Roach * 344*32cd2800SGreg Roach * @param Tree $tree 345*32cd2800SGreg Roach * 346*32cd2800SGreg Roach * @return Closure 347*32cd2800SGreg Roach */ 348*32cd2800SGreg Roach private function sourceRowMapper(Tree $tree): Closure 349*32cd2800SGreg Roach { 350*32cd2800SGreg Roach return function (stdClass $row) use ($tree): Source { 351*32cd2800SGreg Roach return Source::getInstance($row->s_id, $tree, $row->s_gedcom); 352*32cd2800SGreg Roach }; 353*32cd2800SGreg Roach } 354*32cd2800SGreg Roach 355*32cd2800SGreg Roach /** 356*32cd2800SGreg Roach * A closure to map a database row to a submitter. 357*32cd2800SGreg Roach * 358*32cd2800SGreg Roach * @param Tree $tree 359*32cd2800SGreg Roach * 360*32cd2800SGreg Roach * @return Closure 361*32cd2800SGreg Roach */ 362*32cd2800SGreg Roach private function submitterRowMapper(Tree $tree): Closure 363*32cd2800SGreg Roach { 364*32cd2800SGreg Roach return function (stdClass $row) use ($tree): GedcomRecord { 365*32cd2800SGreg Roach return GedcomRecord::getInstance($row->o_id, $tree, $row->o_gedcom); 366*32cd2800SGreg Roach }; 367*32cd2800SGreg Roach } 368*32cd2800SGreg Roach} 369