xref: /webtrees/app/Statistics/Repository/FamilyDatesRepository.php (revision 5bfc689774bb9a6401271c4ed15a6d50652c991b)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Statistics\Repository;
21
22use Fisharebest\Webtrees\Date;
23use Fisharebest\Webtrees\Fact;
24use Fisharebest\Webtrees\Registry;
25use Fisharebest\Webtrees\I18N;
26use Fisharebest\Webtrees\Statistics\Repository\Interfaces\FamilyDatesRepositoryInterface;
27use Fisharebest\Webtrees\Tree;
28use Illuminate\Database\Capsule\Manager as DB;
29use Illuminate\Database\Query\Builder;
30
31use function abs;
32use function e;
33
34/**
35 * A repository providing methods for family dates related statistics (birth, death, marriage, divorce).
36 */
37class FamilyDatesRepository implements FamilyDatesRepositoryInterface
38{
39    /**
40     * Sorting directions.
41     */
42    private const SORT_MIN = 'MIN';
43    private const SORT_MAX = 'MAX';
44
45    /**
46     * Event facts.
47     */
48    private const EVENT_BIRTH    = 'BIRT';
49    private const EVENT_DEATH    = 'DEAT';
50    private const EVENT_MARRIAGE = 'MARR';
51    private const EVENT_DIVORCE  = 'DIV';
52
53    private Tree $tree;
54
55    /**
56     * @param Tree $tree
57     */
58    public function __construct(Tree $tree)
59    {
60        $this->tree = $tree;
61    }
62
63    /**
64     * Returns the first/last event record for the given event fact.
65     *
66     * @param string $fact
67     * @param string $operation
68     *
69     * @return object|null
70     */
71    private function eventQuery(string $fact, string $operation): ?object
72    {
73        return DB::table('dates')
74            ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type'])
75            ->where('d_file', '=', $this->tree->id())
76            ->where('d_fact', '=', $fact)
77            ->where('d_julianday1', '=', function (Builder $query) use ($operation, $fact): void {
78                $query->selectRaw($operation . '(d_julianday1)')
79                    ->from('dates')
80                    ->where('d_file', '=', $this->tree->id())
81                    ->where('d_fact', '=', $fact)
82                    ->where('d_julianday1', '<>', 0);
83            })
84            ->first();
85    }
86
87    /**
88     * Returns the formatted year of the first/last occuring event.
89     *
90     * @param string $type      The fact to query
91     * @param string $operation The sorting operation
92     *
93     * @return string
94     */
95    private function getFirstLastEvent(string $type, string $operation): string
96    {
97        $row    = $this->eventQuery($type, $operation);
98        $result = I18N::translate('This information is not available.');
99
100        if ($row) {
101            $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree);
102
103            if ($record && $record->canShow()) {
104                $result = $record->formatList();
105            } else {
106                $result = I18N::translate('This information is private and cannot be shown.');
107            }
108        }
109
110        return $result;
111    }
112
113    /**
114     * @return string
115     */
116    public function firstBirth(): string
117    {
118        return $this->getFirstLastEvent(self::EVENT_BIRTH, self::SORT_MIN);
119    }
120
121    /**
122     * @return string
123     */
124    public function lastBirth(): string
125    {
126        return $this->getFirstLastEvent(self::EVENT_BIRTH, self::SORT_MAX);
127    }
128
129    /**
130     * @return string
131     */
132    public function firstDeath(): string
133    {
134        return $this->getFirstLastEvent(self::EVENT_DEATH, self::SORT_MIN);
135    }
136
137    /**
138     * @return string
139     */
140    public function lastDeath(): string
141    {
142        return $this->getFirstLastEvent(self::EVENT_DEATH, self::SORT_MAX);
143    }
144
145    /**
146     * @return string
147     */
148    public function firstMarriage(): string
149    {
150        return $this->getFirstLastEvent(self::EVENT_MARRIAGE, self::SORT_MIN);
151    }
152
153    /**
154     * @return string
155     */
156    public function lastMarriage(): string
157    {
158        return $this->getFirstLastEvent(self::EVENT_MARRIAGE, self::SORT_MAX);
159    }
160
161    /**
162     * @return string
163     */
164    public function firstDivorce(): string
165    {
166        return $this->getFirstLastEvent(self::EVENT_DIVORCE, self::SORT_MIN);
167    }
168
169    /**
170     * @return string
171     */
172    public function lastDivorce(): string
173    {
174        return $this->getFirstLastEvent(self::EVENT_DIVORCE, self::SORT_MAX);
175    }
176
177    /**
178     * Returns the formatted year of the first/last occuring event.
179     *
180     * @param string $type      The fact to query
181     * @param string $operation The sorting operation
182     *
183     * @return string
184     */
185    private function getFirstLastEventYear(string $type, string $operation): string
186    {
187        $row = $this->eventQuery($type, $operation);
188
189        if (!$row) {
190            return '';
191        }
192
193        if ($row->year < 0) {
194            $row->year = abs($row->year) . ' B.C.';
195        }
196
197        return (new Date($row->type . ' ' . $row->year))
198            ->display();
199    }
200
201    /**
202     * @return string
203     */
204    public function firstBirthYear(): string
205    {
206        return $this->getFirstLastEventYear(self::EVENT_BIRTH, self::SORT_MIN);
207    }
208
209    /**
210     * @return string
211     */
212    public function lastBirthYear(): string
213    {
214        return $this->getFirstLastEventYear(self::EVENT_BIRTH, self::SORT_MAX);
215    }
216
217    /**
218     * @return string
219     */
220    public function firstDeathYear(): string
221    {
222        return $this->getFirstLastEventYear(self::EVENT_DEATH, self::SORT_MIN);
223    }
224
225    /**
226     * @return string
227     */
228    public function lastDeathYear(): string
229    {
230        return $this->getFirstLastEventYear(self::EVENT_DEATH, self::SORT_MAX);
231    }
232
233    /**
234     * @return string
235     */
236    public function firstMarriageYear(): string
237    {
238        return $this->getFirstLastEventYear(self::EVENT_MARRIAGE, self::SORT_MIN);
239    }
240
241    /**
242     * @return string
243     */
244    public function lastMarriageYear(): string
245    {
246        return $this->getFirstLastEventYear(self::EVENT_MARRIAGE, self::SORT_MAX);
247    }
248
249    /**
250     * @return string
251     */
252    public function firstDivorceYear(): string
253    {
254        return $this->getFirstLastEventYear(self::EVENT_DIVORCE, self::SORT_MIN);
255    }
256
257    /**
258     * @return string
259     */
260    public function lastDivorceYear(): string
261    {
262        return $this->getFirstLastEventYear(self::EVENT_DIVORCE, self::SORT_MAX);
263    }
264
265    /**
266     * Returns the formatted name of the first/last occuring event.
267     *
268     * @param string $type      The fact to query
269     * @param string $operation The sorting operation
270     *
271     * @return string
272     */
273    private function getFirstLastEventName(string $type, string $operation): string
274    {
275        $row = $this->eventQuery($type, $operation);
276
277        if ($row) {
278            $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree);
279
280            if ($record) {
281                return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>';
282            }
283        }
284
285        return '';
286    }
287
288    /**
289     * @return string
290     */
291    public function firstBirthName(): string
292    {
293        return $this->getFirstLastEventName(self::EVENT_BIRTH, self::SORT_MIN);
294    }
295
296    /**
297     * @return string
298     */
299    public function lastBirthName(): string
300    {
301        return $this->getFirstLastEventName(self::EVENT_BIRTH, self::SORT_MAX);
302    }
303
304    /**
305     * @return string
306     */
307    public function firstDeathName(): string
308    {
309        return $this->getFirstLastEventName(self::EVENT_DEATH, self::SORT_MIN);
310    }
311
312    /**
313     * @return string
314     */
315    public function lastDeathName(): string
316    {
317        return $this->getFirstLastEventName(self::EVENT_DEATH, self::SORT_MAX);
318    }
319
320    /**
321     * @return string
322     */
323    public function firstMarriageName(): string
324    {
325        return $this->getFirstLastEventName(self::EVENT_MARRIAGE, self::SORT_MIN);
326    }
327
328    /**
329     * @return string
330     */
331    public function lastMarriageName(): string
332    {
333        return $this->getFirstLastEventName(self::EVENT_MARRIAGE, self::SORT_MAX);
334    }
335
336    /**
337     * @return string
338     */
339    public function firstDivorceName(): string
340    {
341        return $this->getFirstLastEventName(self::EVENT_DIVORCE, self::SORT_MIN);
342    }
343
344    /**
345     * @return string
346     */
347    public function lastDivorceName(): string
348    {
349        return $this->getFirstLastEventName(self::EVENT_DIVORCE, self::SORT_MAX);
350    }
351
352    /**
353     * Returns the formatted place of the first/last occuring event.
354     *
355     * @param string $type      The fact to query
356     * @param string $operation The sorting operation
357     *
358     * @return string
359     */
360    private function getFirstLastEventPlace(string $type, string $operation): string
361    {
362        $row = $this->eventQuery($type, $operation);
363
364        if ($row) {
365            $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree);
366            $fact   = null;
367
368            if ($record) {
369                $fact = $record->facts([$row->fact])->first();
370            }
371
372            if ($fact instanceof Fact) {
373                return $fact->place()->shortName();
374            }
375        }
376
377        return I18N::translate('This information is private and cannot be shown.');
378    }
379
380    /**
381     * @return string
382     */
383    public function firstBirthPlace(): string
384    {
385        return $this->getFirstLastEventPlace(self::EVENT_BIRTH, self::SORT_MIN);
386    }
387
388    /**
389     * @return string
390     */
391    public function lastBirthPlace(): string
392    {
393        return $this->getFirstLastEventPlace(self::EVENT_BIRTH, self::SORT_MAX);
394    }
395
396    /**
397     * @return string
398     */
399    public function firstDeathPlace(): string
400    {
401        return $this->getFirstLastEventPlace(self::EVENT_DEATH, self::SORT_MIN);
402    }
403
404    /**
405     * @return string
406     */
407    public function lastDeathPlace(): string
408    {
409        return $this->getFirstLastEventPlace(self::EVENT_DEATH, self::SORT_MAX);
410    }
411
412    /**
413     * @return string
414     */
415    public function firstMarriagePlace(): string
416    {
417        return $this->getFirstLastEventPlace(self::EVENT_MARRIAGE, self::SORT_MIN);
418    }
419
420    /**
421     * @return string
422     */
423    public function lastMarriagePlace(): string
424    {
425        return $this->getFirstLastEventPlace(self::EVENT_MARRIAGE, self::SORT_MAX);
426    }
427
428    /**
429     * @return string
430     */
431    public function firstDivorcePlace(): string
432    {
433        return $this->getFirstLastEventPlace(self::EVENT_DIVORCE, self::SORT_MIN);
434    }
435
436    /**
437     * @return string
438     */
439    public function lastDivorcePlace(): string
440    {
441        return $this->getFirstLastEventPlace(self::EVENT_DIVORCE, self::SORT_MAX);
442    }
443}
444