xref: /webtrees/app/Services/SearchService.php (revision 886b77daa151baff5435671d4ed5ba22ff96b9d6)
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;
2632cd2800SGreg Roachuse Fisharebest\Webtrees\Repository;
2732cd2800SGreg Roachuse Fisharebest\Webtrees\Source;
2832cd2800SGreg Roachuse Fisharebest\Webtrees\Tree;
2932cd2800SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
3032cd2800SGreg Roachuse Illuminate\Database\Query\Builder;
3132cd2800SGreg Roachuse Illuminate\Database\Query\JoinClause;
3232cd2800SGreg Roachuse Illuminate\Support\Collection;
3332cd2800SGreg Roachuse stdClass;
3432cd2800SGreg Roach
3532cd2800SGreg Roach/**
3632cd2800SGreg Roach * Search trees for genealogy records.
3732cd2800SGreg Roach */
3832cd2800SGreg Roachclass SearchService
3932cd2800SGreg Roach{
4032cd2800SGreg Roach    /**
4132cd2800SGreg Roach     * Search for families by name.
4232cd2800SGreg Roach     *
4332cd2800SGreg Roach     * @param Tree   $tree
4432cd2800SGreg Roach     * @param string $search
4532cd2800SGreg Roach     * @param int    $offset
4632cd2800SGreg Roach     * @param int    $limit
4732cd2800SGreg Roach     *
4832cd2800SGreg Roach     * @return Collection|Family[]
4932cd2800SGreg Roach     */
5032cd2800SGreg Roach    public function searchFamiliesByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection
5132cd2800SGreg Roach    {
5232cd2800SGreg Roach        $prefix = DB::connection()->getTablePrefix();
5332cd2800SGreg Roach
5432cd2800SGreg Roach        $query = DB::table('families')
5532cd2800SGreg Roach            ->where('f_file', '=', $tree->id())
5632cd2800SGreg Roach            ->join('name AS husb_name', function (JoinClause $join) use ($search): void {
5732cd2800SGreg Roach                $join
5832cd2800SGreg Roach                    ->on('husb_name.n_file', '=', 'families.f_file')
5932cd2800SGreg Roach                    ->on('husb_name.n_id', '=', 'families.f_husb')
6032cd2800SGreg Roach                    ->where('husb_name.n_type', '<>', '_MARNM');
6132cd2800SGreg Roach            })
6232cd2800SGreg Roach            ->join('name AS wife_name', function (JoinClause $join) use ($search): void {
6332cd2800SGreg Roach                $join
6432cd2800SGreg Roach                    ->on('wife_name.n_file', '=', 'families.f_file')
6532cd2800SGreg Roach                    ->on('wife_name.n_id', '=', 'families.f_wife')
6632cd2800SGreg Roach                    ->where('wife_name.n_type', '<>', '_MARNM');
6732cd2800SGreg Roach            })
6832cd2800SGreg Roach            ->whereContains(DB::raw("CONCAT(" . $prefix . "husb_name.n_full, ' ', " . $prefix . "wife_name.n_full)"), $search)
6932cd2800SGreg Roach            ->orderBy('husb_name.n_sort')
7032cd2800SGreg Roach            ->orderBy('wife_name.n_sort')
7132cd2800SGreg Roach            ->select(['families.f_id', 'families.f_gedcom', 'husb_name.n_sort', 'wife_name.n_sort'])
7232cd2800SGreg Roach            ->distinct();
7332cd2800SGreg Roach
74*886b77daSGreg Roach        $row_mapper = Family::rowMapper($tree);
7532cd2800SGreg Roach
7632cd2800SGreg Roach        return $this->paginateQuery($query, $row_mapper, $offset, $limit);
7732cd2800SGreg Roach    }
7832cd2800SGreg Roach
7932cd2800SGreg Roach    /**
8032cd2800SGreg Roach     * Search for individuals by name.
8132cd2800SGreg Roach     *
8232cd2800SGreg Roach     * @param Tree   $tree
8332cd2800SGreg Roach     * @param string $search
8432cd2800SGreg Roach     * @param int    $offset
8532cd2800SGreg Roach     * @param int    $limit
8632cd2800SGreg Roach     *
8732cd2800SGreg Roach     * @return Collection|Individual[]
8832cd2800SGreg Roach     */
8932cd2800SGreg Roach    public function searchIndividualsByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection
9032cd2800SGreg Roach    {
9132cd2800SGreg Roach        $query = DB::table('individuals')
9232cd2800SGreg Roach            ->where('i_file', '=', $tree->id())
9332cd2800SGreg Roach            ->join('name', function (JoinClause $join) use ($search): void {
9432cd2800SGreg Roach                $join
9532cd2800SGreg Roach                    ->on('name.n_file', '=', 'individuals.i_file')
9632cd2800SGreg Roach                    ->on('name.n_id', '=', 'individuals.i_id')
9732cd2800SGreg Roach                    ->whereContains('n_full', $search);
9832cd2800SGreg Roach            })
9932cd2800SGreg Roach            ->select(['individuals.i_id', 'individuals.i_gedcom', 'n_sort'])
10032cd2800SGreg Roach            ->distinct();
10132cd2800SGreg Roach
102*886b77daSGreg Roach        $row_mapper = Individual::rowMapper($tree);
10332cd2800SGreg Roach
10432cd2800SGreg Roach        return $this->paginateQuery($query, $row_mapper, $offset, $limit);
10532cd2800SGreg Roach    }
10632cd2800SGreg Roach
10732cd2800SGreg Roach    /**
10832cd2800SGreg Roach     * Search for media objects.
10932cd2800SGreg Roach     *
11032cd2800SGreg Roach     * @param Tree   $tree
11132cd2800SGreg Roach     * @param string $search
11232cd2800SGreg Roach     * @param int    $offset
11332cd2800SGreg Roach     * @param int    $limit
11432cd2800SGreg Roach     *
11532cd2800SGreg Roach     * @return Collection|Media[]
11632cd2800SGreg Roach     */
11732cd2800SGreg Roach    public function searchMedia(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection
11832cd2800SGreg Roach    {
11932cd2800SGreg Roach        $query = DB::table('media')
12032cd2800SGreg Roach            ->where('media.m_file', '=', $tree->id())
12132cd2800SGreg Roach            ->join('media_file', function (JoinClause $join) use ($search): void {
12232cd2800SGreg Roach                $join
12332cd2800SGreg Roach                    ->on('media_file.m_file', '=', 'media.m_file')
12432cd2800SGreg Roach                    ->on('media_file.m_id', '=', 'media.m_id');
12532cd2800SGreg Roach            })
12632cd2800SGreg Roach            ->where(function (Builder $query) use ($search): void {
12732cd2800SGreg Roach                $query
12832cd2800SGreg Roach                    ->whereContains('multimedia_file_refn', $search)
12932cd2800SGreg Roach                    ->whereContains('descriptive_title', $search, 'or');
13032cd2800SGreg Roach            })
13132cd2800SGreg Roach            ->select(['media.m_id', 'media.m_gedcom'])
13232cd2800SGreg Roach            ->distinct();
13332cd2800SGreg Roach
134*886b77daSGreg Roach        $row_mapper = Media::rowMapper($tree);
13532cd2800SGreg Roach
13632cd2800SGreg Roach        return $this->paginateQuery($query, $row_mapper, $offset, $limit);
13732cd2800SGreg Roach    }
13832cd2800SGreg Roach
13932cd2800SGreg Roach    /**
14032cd2800SGreg Roach     * Search for notes.
14132cd2800SGreg Roach     *
14232cd2800SGreg Roach     * @param Tree   $tree
14332cd2800SGreg Roach     * @param string $search
14432cd2800SGreg Roach     * @param int    $offset
14532cd2800SGreg Roach     * @param int    $limit
14632cd2800SGreg Roach     *
14732cd2800SGreg Roach     * @return Collection|Note[]
14832cd2800SGreg Roach     */
14932cd2800SGreg Roach    public function searchNotes(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection
15032cd2800SGreg Roach    {
15132cd2800SGreg Roach        $query = DB::table('other')
15232cd2800SGreg Roach            ->where('o_file', '=', $tree->id())
15332cd2800SGreg Roach            ->where('o_type', '=', 'NOTE')
15432cd2800SGreg Roach            ->whereContains('o_gedcom', $search)
15532cd2800SGreg Roach            ->orderBy('o_id')
15632cd2800SGreg Roach            ->select(['o_id', 'o_gedcom']);
15732cd2800SGreg Roach
158*886b77daSGreg Roach        $row_mapper = Note::rowMapper($tree);
15932cd2800SGreg Roach
16032cd2800SGreg Roach        return $this->paginateQuery($query, $row_mapper, $offset, $limit);
16132cd2800SGreg Roach    }
16232cd2800SGreg Roach
16332cd2800SGreg Roach    /**
16432cd2800SGreg Roach     * Search for repositories.
16532cd2800SGreg Roach     *
16632cd2800SGreg Roach     * @param Tree   $tree
16732cd2800SGreg Roach     * @param string $search
16832cd2800SGreg Roach     * @param int    $offset
16932cd2800SGreg Roach     * @param int    $limit
17032cd2800SGreg Roach     *
17132cd2800SGreg Roach     * @return Collection|Repository[]
17232cd2800SGreg Roach     */
17332cd2800SGreg Roach    public function searchRepositories(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection
17432cd2800SGreg Roach    {
17532cd2800SGreg Roach        $query = DB::table('other')
17632cd2800SGreg Roach            ->where('o_file', '=', $tree->id())
17732cd2800SGreg Roach            ->where('o_type', '=', 'REPO')
17832cd2800SGreg Roach            ->whereContains('o_gedcom', $search)
17932cd2800SGreg Roach            ->orderBy('o_id')
18032cd2800SGreg Roach            ->select(['o_id', 'o_gedcom']);
18132cd2800SGreg Roach
182*886b77daSGreg Roach        $row_mapper = Repository::rowMapper($tree);
18332cd2800SGreg Roach
18432cd2800SGreg Roach        return $this->paginateQuery($query, $row_mapper, $offset, $limit);
18532cd2800SGreg Roach    }
18632cd2800SGreg Roach
18732cd2800SGreg Roach    /**
18832cd2800SGreg Roach    * Search for sources by name.
18932cd2800SGreg Roach    *
19032cd2800SGreg Roach    * @param Tree   $tree
19132cd2800SGreg Roach    * @param string $search
19232cd2800SGreg Roach    * @param int    $offset
19332cd2800SGreg Roach    * @param int    $limit
19432cd2800SGreg Roach    *
19532cd2800SGreg Roach    * @return Collection|Source[]
19632cd2800SGreg Roach    */
19732cd2800SGreg Roach    public function searchSourcesByName(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection
19832cd2800SGreg Roach    {
19932cd2800SGreg Roach        $query = DB::table('sources')
20032cd2800SGreg Roach            ->where('s_file', '=', $tree->id())
20132cd2800SGreg Roach            ->whereContains('s_name', $search)
20232cd2800SGreg Roach            ->orderBy('s_name')
20332cd2800SGreg Roach            ->select(['s_id', 's_gedcom']);
20432cd2800SGreg Roach
205*886b77daSGreg Roach        $row_mapper = Source::rowMapper($tree);
20632cd2800SGreg Roach
20732cd2800SGreg Roach        return $this->paginateQuery($query, $row_mapper, $offset, $limit);
20832cd2800SGreg Roach    }
20932cd2800SGreg Roach
21032cd2800SGreg Roach    /**
21132cd2800SGreg Roach     * Search for submitters.
21232cd2800SGreg Roach     *
21332cd2800SGreg Roach     * @param Tree   $tree
21432cd2800SGreg Roach     * @param string $search
21532cd2800SGreg Roach     * @param int    $offset
21632cd2800SGreg Roach     * @param int    $limit
21732cd2800SGreg Roach     *
21832cd2800SGreg Roach     * @return Collection|GedcomRecord[]
21932cd2800SGreg Roach     */
22032cd2800SGreg Roach    public function searchSubmitters(Tree $tree, string $search, int $offset = 0, int $limit = PHP_INT_MAX): Collection
22132cd2800SGreg Roach    {
22232cd2800SGreg Roach        $query = DB::table('other')
22332cd2800SGreg Roach            ->where('o_file', '=', $tree->id())
22432cd2800SGreg Roach            ->where('o_type', '=', 'SUBM')
22532cd2800SGreg Roach            ->whereContains('o_gedcom', $search)
22632cd2800SGreg Roach            ->orderBy('o_id')
22732cd2800SGreg Roach            ->select(['o_id', 'o_gedcom']);
22832cd2800SGreg Roach
229*886b77daSGreg Roach        $row_mapper = GedcomRecord::rowMapper($tree);
23032cd2800SGreg Roach
23132cd2800SGreg Roach        return $this->paginateQuery($query, $row_mapper, $offset, $limit);
23232cd2800SGreg Roach    }
23332cd2800SGreg Roach
23432cd2800SGreg Roach    /**
23532cd2800SGreg Roach     * Paginate a search query.
23632cd2800SGreg Roach     *
23732cd2800SGreg Roach     * @param Builder $query      Searches the database for the desired records.
23832cd2800SGreg Roach     * @param Closure $row_mapper Converts a row from the query into a record.
23932cd2800SGreg Roach     * @param int     $offset     Skip this many rows.
24032cd2800SGreg Roach     * @param int     $limit      Take this many rows.
24132cd2800SGreg Roach     *
24232cd2800SGreg Roach     * @return Collection
24332cd2800SGreg Roach     */
24432cd2800SGreg Roach    private function paginateQuery(Builder $query, Closure $row_mapper, int $offset, int $limit): Collection
24532cd2800SGreg Roach    {
24632cd2800SGreg Roach        $collection = new Collection();
24732cd2800SGreg Roach
24832cd2800SGreg Roach        foreach ($query->cursor() as $row) {
24932cd2800SGreg Roach            $record = $row_mapper($row);
25032cd2800SGreg Roach
25132cd2800SGreg Roach            if ($record->canShow()) {
25232cd2800SGreg Roach                if ($offset > 0) {
25332cd2800SGreg Roach                    $offset--;
25432cd2800SGreg Roach                } else {
25532cd2800SGreg Roach                    if ($limit > 0) {
25632cd2800SGreg Roach                        $collection->push($record);
25732cd2800SGreg Roach                    }
25832cd2800SGreg Roach
25932cd2800SGreg Roach                    $limit--;
26032cd2800SGreg Roach
26132cd2800SGreg Roach                    if ($limit === 0) {
26232cd2800SGreg Roach                        break;
26332cd2800SGreg Roach                    }
26432cd2800SGreg Roach                }
26532cd2800SGreg Roach            }
26632cd2800SGreg Roach        }
26732cd2800SGreg Roach
26832cd2800SGreg Roach        return $collection;
26932cd2800SGreg Roach    }
27032cd2800SGreg Roach}
271