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