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