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 * We use some pseudo-GEDCOM tags for some of our fields. 2146f922bb8SGreg Roach * 2156f922bb8SGreg Roach * @return array<string,string> 2166f922bb8SGreg Roach */ 217c8fd9348SGreg Roach private function fieldLabels(): array 2186f922bb8SGreg Roach { 219c8fd9348SGreg Roach $return = []; 220c8fd9348SGreg Roach 221c8fd9348SGreg Roach foreach (array_merge(self::OTHER_ADVANCED_FIELDS, self::DEFAULT_ADVANCED_FIELDS) as $field) { 222c8fd9348SGreg Roach $tmp = strtr($field, ['MOTHER:' => 'INDI:', 'FATHER:' => 'INDI:']); 223c8fd9348SGreg Roach $return[$field] = Registry::elementFactory()->make($tmp)->label(); 224c8fd9348SGreg Roach } 225c8fd9348SGreg Roach 226c8fd9348SGreg Roach return $return; 2276f922bb8SGreg Roach } 2286f922bb8SGreg Roach 229f5402f3dSGreg Roach /** 230f5402f3dSGreg Roach * For the advanced search 231f5402f3dSGreg Roach * 23224f2a3afSGreg Roach * @return array<string> 233f5402f3dSGreg Roach */ 234f5402f3dSGreg Roach private function dateOptions(): array 235f5402f3dSGreg Roach { 236f5402f3dSGreg Roach return [ 237f5402f3dSGreg Roach 0 => I18N::translate('Exact date'), 238c127524bSGreg Roach 1 => I18N::plural('±%s year', '±%s years', 1, I18N::number(1)), 239f5402f3dSGreg Roach 2 => I18N::plural('±%s year', '±%s years', 2, I18N::number(2)), 240f5402f3dSGreg Roach 5 => I18N::plural('±%s year', '±%s years', 5, I18N::number(5)), 241f5402f3dSGreg Roach 10 => I18N::plural('±%s year', '±%s years', 10, I18N::number(10)), 242c127524bSGreg Roach 20 => I18N::plural('±%s year', '±%s years', 20, I18N::number(20)), 243f5402f3dSGreg Roach ]; 244f5402f3dSGreg Roach } 245f5402f3dSGreg Roach 246f5402f3dSGreg Roach /** 247f5402f3dSGreg Roach * For the advanced search 248f5402f3dSGreg Roach * 24924f2a3afSGreg Roach * @return array<string> 250f5402f3dSGreg Roach */ 251f5402f3dSGreg Roach private function nameOptions(): array 252f5402f3dSGreg Roach { 253f5402f3dSGreg Roach return [ 254f5402f3dSGreg Roach 'EXACT' => I18N::translate('Exact'), 255f5402f3dSGreg Roach 'BEGINS' => I18N::translate('Begins with'), 256f5402f3dSGreg Roach 'CONTAINS' => I18N::translate('Contains'), 257f5402f3dSGreg Roach 'SDX' => I18N::translate('Sounds like'), 258f5402f3dSGreg Roach ]; 259f5402f3dSGreg Roach } 260f5402f3dSGreg Roach} 261