xref: /webtrees/app/Http/RequestHandlers/SearchAdvancedPage.php (revision 21bbc77713ed863881cf331fd2cd1edbe3ddfd82)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2019 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Http\RequestHandlers;
21
22use Fisharebest\Webtrees\GedcomTag;
23use Fisharebest\Webtrees\Http\ViewResponseTrait;
24use Fisharebest\Webtrees\I18N;
25use Fisharebest\Webtrees\Services\SearchService;
26use Fisharebest\Webtrees\Tree;
27use Illuminate\Support\Collection;
28use Psr\Http\Message\ResponseInterface;
29use Psr\Http\Message\ServerRequestInterface;
30use Psr\Http\Server\RequestHandlerInterface;
31
32use function array_fill_keys;
33use function array_filter;
34use function array_key_exists;
35use function assert;
36use function explode;
37use function in_array;
38
39/**
40 * Search for genealogy data
41 */
42class SearchAdvancedPage implements RequestHandlerInterface
43{
44    use ViewResponseTrait;
45
46    private const DEFAULT_ADVANCED_FIELDS = [
47        'NAME:GIVN',
48        'NAME:SURN',
49        'BIRT:DATE',
50        'BIRT:PLAC',
51        'FAMS:MARR:DATE',
52        'FAMS:MARR:PLAC',
53        'DEAT:DATE',
54        'DEAT:PLAC',
55        'FAMC:HUSB:NAME:GIVN',
56        'FAMC:HUSB:NAME:SURN',
57        'FAMC:WIFE:NAME:GIVN',
58        'FAMC:WIFE:NAME:SURN',
59    ];
60
61    private const OTHER_ADVANCED_FIELDS = [
62        'ADOP:DATE',
63        'ADOP:PLAC',
64        'AFN',
65        'BAPL:DATE',
66        'BAPL:PLAC',
67        'BAPM:DATE',
68        'BAPM:PLAC',
69        'BARM:DATE',
70        'BARM:PLAC',
71        'BASM:DATE',
72        'BASM:PLAC',
73        'BLES:DATE',
74        'BLES:PLAC',
75        'BURI:DATE',
76        'BURI:PLAC',
77        'CAST',
78        'CENS:DATE',
79        'CENS:PLAC',
80        'CHAN:DATE',
81        'CHAN:_WT_USER',
82        'CHR:DATE',
83        'CHR:PLAC',
84        'CREM:DATE',
85        'CREM:PLAC',
86        'DSCR',
87        'EMAIL',
88        'EMIG:DATE',
89        'EMIG:PLAC',
90        'ENDL:DATE',
91        'ENDL:PLAC',
92        'EVEN',
93        'EVEN:TYPE',
94        'EVEN:DATE',
95        'EVEN:PLAC',
96        'FACT',
97        'FACT:TYPE',
98        'FAMS:CENS:DATE',
99        'FAMS:CENS:PLAC',
100        'FAMS:DIV:DATE',
101        'FAMS:NOTE',
102        'FAMS:SLGS:DATE',
103        'FAMS:SLGS:PLAC',
104        'FAX',
105        'FCOM:DATE',
106        'FCOM:PLAC',
107        'IMMI:DATE',
108        'IMMI:PLAC',
109        'NAME:NICK',
110        'NAME:_MARNM',
111        'NAME:_HEB',
112        'NAME:ROMN',
113        'NATI',
114        'NATU:DATE',
115        'NATU:PLAC',
116        'NOTE',
117        'OCCU',
118        'ORDN:DATE',
119        'ORDN:PLAC',
120        'REFN',
121        'RELI',
122        'RESI',
123        'RESI:DATE',
124        'RESI:PLAC',
125        'SLGC:DATE',
126        'SLGC:PLAC',
127        'TITL',
128    ];
129
130    /** @var SearchService */
131    private $search_service;
132
133    /**
134     * SearchController constructor.
135     *
136     * @param SearchService $search_service
137     */
138    public function __construct(SearchService $search_service)
139    {
140        $this->search_service = $search_service;
141    }
142
143    /**
144     * A structured search.
145     *
146     * @param ServerRequestInterface $request
147     *
148     * @return ResponseInterface
149     */
150    public function handle(ServerRequestInterface $request): ResponseInterface
151    {
152        $tree = $request->getAttribute('tree');
153        assert($tree instanceof Tree);
154
155        $default_fields = array_fill_keys(self::DEFAULT_ADVANCED_FIELDS, '');
156
157        $params = $request->getQueryParams();
158
159        $fields      = $params['fields'] ?? $default_fields;
160        $modifiers   = $params['modifiers'] ?? [];
161
162        $other_fields = $this->otherFields($tree, $fields);
163        $date_options = $this->dateOptions();
164        $name_options = $this->nameOptions();
165
166        if (array_filter($fields) !== []) {
167            $individuals = $this->search_service->searchIndividualsAdvanced([$tree], $fields, $modifiers);
168        } else {
169            $individuals = new Collection();
170        }
171
172        $title = I18N::translate('Advanced search');
173
174        return $this->viewResponse('search-advanced-page', [
175            'date_options' => $date_options,
176            'fields'       => $fields,
177            'individuals'  => $individuals,
178            'modifiers'    => $modifiers,
179            'name_options' => $name_options,
180            'other_fields' => $other_fields,
181            'title'        => $title,
182            'tree'         => $tree,
183        ]);
184    }
185
186    /**
187     * Extra search fields to add to the advanced search
188     *
189     * @param Tree     $tree
190     * @param string[] $fields
191     *
192     * @return array<string,string>
193     */
194    private function otherFields(Tree $tree, array $fields): array
195    {
196        $default_facts     = new Collection(self::OTHER_ADVANCED_FIELDS);
197        $indi_facts_add    = new Collection(explode(',', $tree->getPreference('INDI_FACTS_ADD')));
198        $indi_facts_unique = new Collection(explode(',', $tree->getPreference('INDI_FACTS_UNIQUE')));
199
200        return $default_facts
201            ->merge($indi_facts_add)
202            ->merge($indi_facts_unique)
203            ->unique()
204            ->reject(static function (string $field) use ($fields): bool {
205                return
206                    array_key_exists($field, $fields) ||
207                    array_key_exists($field . ':DATE', $fields) ||
208                    array_key_exists($field . ':PLAC', $fields);
209            })
210            ->mapWithKeys(static function (string $fact): array {
211                return [$fact => GedcomTag::getLabel($fact)];
212            })
213            ->all();
214    }
215
216    /**
217     * For the advanced search
218     *
219     * @return string[]
220     */
221    private function dateOptions(): array
222    {
223        return [
224            0  => I18N::translate('Exact date'),
225            2  => I18N::plural('±%s year', '±%s years', 2, I18N::number(2)),
226            5  => I18N::plural('±%s year', '±%s years', 5, I18N::number(5)),
227            10 => I18N::plural('±%s year', '±%s years', 10, I18N::number(10)),
228        ];
229    }
230
231    /**
232     * For the advanced search
233     *
234     * @return string[]
235     */
236    private function nameOptions(): array
237    {
238        return [
239            'EXACT'    => I18N::translate('Exact'),
240            'BEGINS'   => I18N::translate('Begins with'),
241            'CONTAINS' => I18N::translate('Contains'),
242            'SDX'      => I18N::translate('Sounds like'),
243        ];
244    }
245}
246