xref: /webtrees/app/Http/RequestHandlers/SearchAdvancedPage.php (revision ca1e6d47754d1d3667ac160664ddb033e3d5755e)
1f5402f3dSGreg Roach<?php
2f5402f3dSGreg Roach
3f5402f3dSGreg Roach/**
4f5402f3dSGreg Roach * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
6f5402f3dSGreg Roach * This program is free software: you can redistribute it and/or modify
7f5402f3dSGreg Roach * it under the terms of the GNU General Public License as published by
8f5402f3dSGreg Roach * the Free Software Foundation, either version 3 of the License, or
9f5402f3dSGreg Roach * (at your option) any later version.
10f5402f3dSGreg Roach * This program is distributed in the hope that it will be useful,
11f5402f3dSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
12f5402f3dSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13f5402f3dSGreg Roach * GNU General Public License for more details.
14f5402f3dSGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
16f5402f3dSGreg Roach */
17f5402f3dSGreg Roach
18f5402f3dSGreg Roachdeclare(strict_types=1);
19f5402f3dSGreg Roach
20f5402f3dSGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers;
21f5402f3dSGreg Roach
2245ee34f2SGreg Roachuse Fisharebest\Webtrees\Auth;
23f5402f3dSGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait;
24f5402f3dSGreg Roachuse Fisharebest\Webtrees\I18N;
2545ee34f2SGreg Roachuse Fisharebest\Webtrees\Log;
262f86083cSGreg Roachuse Fisharebest\Webtrees\Registry;
27f5402f3dSGreg Roachuse Fisharebest\Webtrees\Services\SearchService;
28b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator;
29075d1a05SGreg Roachuse Illuminate\Support\Collection;
30f5402f3dSGreg Roachuse Psr\Http\Message\ResponseInterface;
31f5402f3dSGreg Roachuse Psr\Http\Message\ServerRequestInterface;
32f5402f3dSGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
33f5402f3dSGreg Roach
3477fedc87SGreg Roachuse function array_fill_keys;
3577fedc87SGreg Roachuse function array_filter;
36d72c9f8aSGreg Roachuse function array_key_exists;
3745ee34f2SGreg Roachuse function array_keys;
3845ee34f2SGreg Roachuse function array_map;
39c8fd9348SGreg Roachuse function array_merge;
4045ee34f2SGreg Roachuse function implode;
41c8fd9348SGreg Roachuse function strtr;
4277fedc87SGreg Roach
43f5402f3dSGreg Roach/**
44f5402f3dSGreg Roach * Search for genealogy data
45f5402f3dSGreg Roach */
46f5402f3dSGreg Roachclass SearchAdvancedPage implements RequestHandlerInterface
47f5402f3dSGreg Roach{
48f5402f3dSGreg Roach    use ViewResponseTrait;
49f5402f3dSGreg Roach
50f5402f3dSGreg Roach    private const DEFAULT_ADVANCED_FIELDS = [
512f86083cSGreg Roach        'INDI:NAME:GIVN',
522f86083cSGreg Roach        'INDI:NAME:SURN',
532f86083cSGreg Roach        'INDI:BIRT:DATE',
542f86083cSGreg Roach        'INDI:BIRT:PLAC',
552f86083cSGreg Roach        'FAM:MARR:DATE',
562f86083cSGreg Roach        'FAM:MARR:PLAC',
572f86083cSGreg Roach        'INDI:DEAT:DATE',
582f86083cSGreg Roach        'INDI:DEAT:PLAC',
592f86083cSGreg Roach        'FATHER:NAME:GIVN',
602f86083cSGreg Roach        'FATHER:NAME:SURN',
612f86083cSGreg Roach        'MOTHER:NAME:GIVN',
622f86083cSGreg Roach        'MOTHER:NAME:SURN',
63f5402f3dSGreg Roach    ];
64f5402f3dSGreg Roach
65f5402f3dSGreg Roach    private const OTHER_ADVANCED_FIELDS = [
662f86083cSGreg Roach        'INDI:ADOP:DATE',
672f86083cSGreg Roach        'INDI:ADOP:PLAC',
682f86083cSGreg Roach        'INDI:AFN',
692f86083cSGreg Roach        'INDI:BAPL:DATE',
702f86083cSGreg Roach        'INDI:BAPL:PLAC',
712f86083cSGreg Roach        'INDI:BAPM:DATE',
722f86083cSGreg Roach        'INDI:BAPM:PLAC',
732f86083cSGreg Roach        'INDI:BARM:DATE',
742f86083cSGreg Roach        'INDI:BARM:PLAC',
752f86083cSGreg Roach        'INDI:BASM:DATE',
762f86083cSGreg Roach        'INDI:BASM:PLAC',
772f86083cSGreg Roach        'INDI:BLES:DATE',
782f86083cSGreg Roach        'INDI:BLES:PLAC',
792f86083cSGreg Roach        'INDI:BURI:DATE',
802f86083cSGreg Roach        'INDI:BURI:PLAC',
812f86083cSGreg Roach        'INDI:CENS:DATE',
822f86083cSGreg Roach        'INDI:CENS:PLAC',
832f86083cSGreg Roach        'INDI:CHAN:DATE',
842f86083cSGreg Roach        'INDI:CHAN:_WT_USER',
852f86083cSGreg Roach        'INDI:CHR:DATE',
862f86083cSGreg Roach        'INDI:CHR:PLAC',
872f86083cSGreg Roach        'INDI:CREM:DATE',
882f86083cSGreg Roach        'INDI:CREM:PLAC',
892f86083cSGreg Roach        'INDI:DSCR',
902f86083cSGreg Roach        'INDI:EMIG:DATE',
912f86083cSGreg Roach        'INDI:EMIG:PLAC',
922f86083cSGreg Roach        'INDI:ENDL:DATE',
932f86083cSGreg Roach        'INDI:ENDL:PLAC',
942f86083cSGreg Roach        'INDI:EVEN',
952f86083cSGreg Roach        'INDI:EVEN:TYPE',
962f86083cSGreg Roach        'INDI:EVEN:DATE',
972f86083cSGreg Roach        'INDI:EVEN:PLAC',
982f86083cSGreg Roach        'INDI:FACT',
992f86083cSGreg Roach        'INDI:FACT:TYPE',
1002f86083cSGreg Roach        'INDI:FCOM:DATE',
1012f86083cSGreg Roach        'INDI:FCOM:PLAC',
1022f86083cSGreg Roach        'INDI:IMMI:DATE',
1032f86083cSGreg Roach        'INDI:IMMI:PLAC',
1042f86083cSGreg Roach        'INDI:NAME:NICK',
1052f86083cSGreg Roach        'INDI:NAME:_MARNM',
1062f86083cSGreg Roach        'INDI:NAME:_HEB',
1072f86083cSGreg Roach        'INDI:NAME:ROMN',
1082f86083cSGreg Roach        'INDI:NATI',
1092f86083cSGreg Roach        'INDI:NATU:DATE',
1102f86083cSGreg Roach        'INDI:NATU:PLAC',
1112f86083cSGreg Roach        'INDI:NOTE',
1122f86083cSGreg Roach        'INDI:OCCU',
1132f86083cSGreg Roach        'INDI:ORDN:DATE',
1142f86083cSGreg Roach        'INDI:ORDN:PLAC',
1152f86083cSGreg Roach        'INDI:REFN',
1162f86083cSGreg Roach        'INDI:RELI',
1172f86083cSGreg Roach        'INDI:RESI:DATE',
1182f86083cSGreg Roach        'INDI:RESI:EMAIL',
1192f86083cSGreg Roach        'INDI:RESI:PLAC',
1202f86083cSGreg Roach        'INDI:SLGC:DATE',
1212f86083cSGreg Roach        'INDI:SLGC:PLAC',
1222f86083cSGreg Roach        'INDI:TITL',
1232f86083cSGreg Roach        'FAM:DIV:DATE',
1242f86083cSGreg Roach        'FAM:SLGS:DATE',
1252f86083cSGreg Roach        'FAM:SLGS:PLAC',
126f5402f3dSGreg Roach    ];
127f5402f3dSGreg Roach
128c4943cffSGreg Roach    private SearchService $search_service;
129f5402f3dSGreg Roach
130f5402f3dSGreg Roach    /**
131f5402f3dSGreg Roach     * @param SearchService $search_service
132f5402f3dSGreg Roach     */
133f5402f3dSGreg Roach    public function __construct(SearchService $search_service)
134f5402f3dSGreg Roach    {
135f5402f3dSGreg Roach        $this->search_service = $search_service;
136f5402f3dSGreg Roach    }
137f5402f3dSGreg Roach
138f5402f3dSGreg Roach    /**
139f5402f3dSGreg Roach     * @param ServerRequestInterface $request
140f5402f3dSGreg Roach     *
141f5402f3dSGreg Roach     * @return ResponseInterface
142f5402f3dSGreg Roach     */
143f5402f3dSGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
144f5402f3dSGreg Roach    {
145b55cbc6bSGreg Roach        $tree           = Validator::attributes($request)->tree();
146f5402f3dSGreg Roach        $default_fields = array_fill_keys(self::DEFAULT_ADVANCED_FIELDS, '');
147748dbe15SGreg Roach        $fields         = Validator::queryParams($request)->array('fields') ?: $default_fields;
148748dbe15SGreg Roach        $modifiers      = Validator::queryParams($request)->array('modifiers');
1492f86083cSGreg Roach        $other_fields   = $this->otherFields($fields);
150f5402f3dSGreg Roach        $date_options   = $this->dateOptions();
151f5402f3dSGreg Roach        $name_options   = $this->nameOptions();
152f5402f3dSGreg Roach
15321adbb99SGreg Roach        $fields = array_map(static fn (string $x): string => preg_replace('/^\s+|\s+$/uD', '', $x), $fields);
15421adbb99SGreg Roach
15545ee34f2SGreg Roach        $search_fields = array_filter($fields, static fn (string $x): bool => $x !== '');
15645ee34f2SGreg Roach
15745ee34f2SGreg Roach        if ($search_fields !== []) {
15845ee34f2SGreg Roach            // Log search requests for visitors
15945ee34f2SGreg Roach            if (Auth::id() === null) {
16045ee34f2SGreg Roach                $fn      = static fn (string $x, string $y): string => $x . '=' . $y;
16145ee34f2SGreg Roach                $message = 'Advanced: ' . implode(', ', array_map($fn, array_keys($search_fields), $search_fields));
16245ee34f2SGreg Roach                Log::addSearchLog($message, [$tree]);
16345ee34f2SGreg Roach            }
164*ca1e6d47SGreg Roach
165*ca1e6d47SGreg Roach            $individuals = $this->search_service->searchIndividualsAdvanced($tree, $search_fields, $modifiers);
166f5402f3dSGreg Roach        } else {
167075d1a05SGreg Roach            $individuals = new Collection();
168f5402f3dSGreg Roach        }
169f5402f3dSGreg Roach
170f5402f3dSGreg Roach        $title = I18N::translate('Advanced search');
171f5402f3dSGreg Roach
172f5402f3dSGreg Roach        return $this->viewResponse('search-advanced-page', [
173f5402f3dSGreg Roach            'date_options' => $date_options,
174f5402f3dSGreg Roach            'fields'       => $fields,
175c8fd9348SGreg Roach            'field_labels' => $this->fieldLabels(),
176f5402f3dSGreg Roach            'individuals'  => $individuals,
177f5402f3dSGreg Roach            'modifiers'    => $modifiers,
178f5402f3dSGreg Roach            'name_options' => $name_options,
179f5402f3dSGreg Roach            'other_fields' => $other_fields,
180f5402f3dSGreg Roach            'title'        => $title,
181ef5d23f1SGreg Roach            'tree'         => $tree,
182f5402f3dSGreg Roach        ]);
183f5402f3dSGreg Roach    }
184f5402f3dSGreg Roach
185f5402f3dSGreg Roach    /**
186f5402f3dSGreg Roach     * Extra search fields to add to the advanced search
187f5402f3dSGreg Roach     *
18809482a55SGreg Roach     * @param array<string> $fields
189f5402f3dSGreg Roach     *
19077fedc87SGreg Roach     * @return array<string,string>
191f5402f3dSGreg Roach     */
1922f86083cSGreg Roach    private function otherFields(array $fields): array
193f5402f3dSGreg Roach    {
19477fedc87SGreg Roach        $default_facts = new Collection(self::OTHER_ADVANCED_FIELDS);
1952f86083cSGreg Roach
1962f86083cSGreg Roach        $comparator = static function (string $x, string $y): int {
1972f86083cSGreg Roach            $element_factory = Registry::elementFactory();
1982f86083cSGreg Roach
1992f86083cSGreg Roach            $label1 = $element_factory->make(strtr($x, [':DATE' => '', ':PLAC' => '', ':TYPE' => '']))->label();
2002f86083cSGreg Roach            $label2 = $element_factory->make(strtr($y, [':DATE' => '', ':PLAC' => '', ':TYPE' => '']))->label();
2012f86083cSGreg Roach
2022f86083cSGreg Roach            return I18N::comparator()($label1, $label2) ?: strcmp($x, $y);
2032f86083cSGreg Roach        };
204f5402f3dSGreg Roach
20577fedc87SGreg Roach        return $default_facts
2062f86083cSGreg Roach            ->reject(fn (string $field): bool => array_key_exists($field, $fields))
2072f86083cSGreg Roach            ->sort($comparator)
208c8fd9348SGreg Roach            ->mapWithKeys(fn (string $fact): array => [$fact => Registry::elementFactory()->make($fact)->label()])
20977fedc87SGreg Roach            ->all();
210f5402f3dSGreg Roach    }
211f5402f3dSGreg Roach
2126f922bb8SGreg Roach
2136f922bb8SGreg Roach    /**
2146f922bb8SGreg Roach     * We use some pseudo-GEDCOM tags for some of our fields.
2156f922bb8SGreg Roach     *
2166f922bb8SGreg Roach     * @return array<string,string>
2176f922bb8SGreg Roach     */
218c8fd9348SGreg Roach    private function fieldLabels(): array
2196f922bb8SGreg Roach    {
220c8fd9348SGreg Roach        $return = [];
221c8fd9348SGreg Roach
222c8fd9348SGreg Roach        foreach (array_merge(self::OTHER_ADVANCED_FIELDS, self::DEFAULT_ADVANCED_FIELDS) as $field) {
223c8fd9348SGreg Roach            $tmp = strtr($field, ['MOTHER:' => 'INDI:', 'FATHER:' => 'INDI:']);
224c8fd9348SGreg Roach            $return[$field] = Registry::elementFactory()->make($tmp)->label();
225c8fd9348SGreg Roach        }
226c8fd9348SGreg Roach
227c8fd9348SGreg Roach
228c8fd9348SGreg Roach        return $return;
2296f922bb8SGreg Roach    }
2306f922bb8SGreg Roach
231f5402f3dSGreg Roach    /**
232f5402f3dSGreg Roach     * For the advanced search
233f5402f3dSGreg Roach     *
23424f2a3afSGreg Roach     * @return array<string>
235f5402f3dSGreg Roach     */
236f5402f3dSGreg Roach    private function dateOptions(): array
237f5402f3dSGreg Roach    {
238f5402f3dSGreg Roach        return [
239f5402f3dSGreg Roach            0  => I18N::translate('Exact date'),
240c127524bSGreg Roach            1  => I18N::plural('±%s year', '±%s years', 1, I18N::number(1)),
241f5402f3dSGreg Roach            2  => I18N::plural('±%s year', '±%s years', 2, I18N::number(2)),
242f5402f3dSGreg Roach            5  => I18N::plural('±%s year', '±%s years', 5, I18N::number(5)),
243f5402f3dSGreg Roach            10 => I18N::plural('±%s year', '±%s years', 10, I18N::number(10)),
244c127524bSGreg Roach            20 => I18N::plural('±%s year', '±%s years', 20, I18N::number(20)),
245f5402f3dSGreg Roach        ];
246f5402f3dSGreg Roach    }
247f5402f3dSGreg Roach
248f5402f3dSGreg Roach    /**
249f5402f3dSGreg Roach     * For the advanced search
250f5402f3dSGreg Roach     *
25124f2a3afSGreg Roach     * @return array<string>
252f5402f3dSGreg Roach     */
253f5402f3dSGreg Roach    private function nameOptions(): array
254f5402f3dSGreg Roach    {
255f5402f3dSGreg Roach        return [
256f5402f3dSGreg Roach            'EXACT'    => I18N::translate('Exact'),
257f5402f3dSGreg Roach            'BEGINS'   => I18N::translate('Begins with'),
258f5402f3dSGreg Roach            'CONTAINS' => I18N::translate('Contains'),
259f5402f3dSGreg Roach            'SDX'      => I18N::translate('Sounds like'),
260f5402f3dSGreg Roach        ];
261f5402f3dSGreg Roach    }
262f5402f3dSGreg Roach}
263