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