xref: /webtrees/app/Statistics/Repository/EventRepository.php (revision 24f2a3af38709f9bf0a739b30264240d20ba34e8)
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\Gedcom;
27use Fisharebest\Webtrees\GedcomTag;
28use Fisharebest\Webtrees\Header;
29use Fisharebest\Webtrees\I18N;
30use Fisharebest\Webtrees\Statistics\Repository\Interfaces\EventRepositoryInterface;
31use Fisharebest\Webtrees\Tree;
32use Illuminate\Database\Capsule\Manager as DB;
33use stdClass;
34
35/**
36 * A repository providing methods for event related statistics.
37 */
38class EventRepository implements EventRepositoryInterface
39{
40    /**
41     * Sorting directions.
42     */
43    private const SORT_ASC  = 'ASC';
44    private const SORT_DESC = 'DESC';
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    private const EVENT_ADOPTION = 'ADOP';
54    private const EVENT_BURIAL   = 'BURI';
55    private const EVENT_CENSUS   = 'CENS';
56
57    /**
58     * @var Tree
59     */
60    private $tree;
61
62    /**
63     * Constructor.
64     *
65     * @param Tree $tree
66     */
67    public function __construct(Tree $tree)
68    {
69        $this->tree = $tree;
70    }
71
72    /**
73     * Returns the total number of a given list of events (with dates).
74     *
75     * @param array<string> $events The list of events to count (e.g. BIRT, DEAT, ...)
76     *
77     * @return int
78     */
79    private function getEventCount(array $events): int
80    {
81        $query = DB::table('dates')
82            ->where('d_file', '=', $this->tree->id());
83
84        $no_types = [
85            'HEAD',
86            'CHAN',
87        ];
88
89        if ($events !== []) {
90            $types = [];
91
92            foreach ($events as $type) {
93                if (strncmp($type, '!', 1) === 0) {
94                    $no_types[] = substr($type, 1);
95                } else {
96                    $types[] = $type;
97                }
98            }
99
100            if ($types !== []) {
101                $query->whereIn('d_fact', $types);
102            }
103        }
104
105        return $query->whereNotIn('d_fact', $no_types)
106            ->count();
107    }
108
109    /**
110     * @param string[] $events
111     *
112     * @return string
113     */
114    public function totalEvents(array $events = []): string
115    {
116        return I18N::number(
117            $this->getEventCount($events)
118        );
119    }
120
121    /**
122     * @return string
123     */
124    public function totalEventsBirth(): string
125    {
126        return $this->totalEvents(Gedcom::BIRTH_EVENTS);
127    }
128
129    /**
130     * @return string
131     */
132    public function totalBirths(): string
133    {
134        return $this->totalEvents([self::EVENT_BIRTH]);
135    }
136
137    /**
138     * @return string
139     */
140    public function totalEventsDeath(): string
141    {
142        return $this->totalEvents(Gedcom::DEATH_EVENTS);
143    }
144
145    /**
146     * @return string
147     */
148    public function totalDeaths(): string
149    {
150        return $this->totalEvents([self::EVENT_DEATH]);
151    }
152
153    /**
154     * @return string
155     */
156    public function totalEventsMarriage(): string
157    {
158        return $this->totalEvents(Gedcom::MARRIAGE_EVENTS);
159    }
160
161    /**
162     * @return string
163     */
164    public function totalMarriages(): string
165    {
166        return $this->totalEvents([self::EVENT_MARRIAGE]);
167    }
168
169    /**
170     * @return string
171     */
172    public function totalEventsDivorce(): string
173    {
174        return $this->totalEvents(Gedcom::DIVORCE_EVENTS);
175    }
176
177    /**
178     * @return string
179     */
180    public function totalDivorces(): string
181    {
182        return $this->totalEvents([self::EVENT_DIVORCE]);
183    }
184
185    /**
186     * Retursn the list of common facts used query the data.
187     *
188     * @return array<string>
189     */
190    private function getCommonFacts(): array
191    {
192        // The list of facts used to limit the query result
193        return array_merge(
194            Gedcom::BIRTH_EVENTS,
195            Gedcom::MARRIAGE_EVENTS,
196            Gedcom::DIVORCE_EVENTS,
197            Gedcom::DEATH_EVENTS
198        );
199    }
200
201    /**
202     * @return string
203     */
204    public function totalEventsOther(): string
205    {
206        $no_facts = array_map(
207            static function (string $fact): string {
208                return '!' . $fact;
209            },
210            $this->getCommonFacts()
211        );
212
213        return $this->totalEvents($no_facts);
214    }
215
216    /**
217     * Returns the first/last event record from the given list of event facts.
218     *
219     * @param string $direction The sorting direction of the query (To return first or last record)
220     *
221     * @return stdClass|null
222     */
223    private function eventQuery(string $direction): ?stdClass
224    {
225        return DB::table('dates')
226            ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type'])
227            ->where('d_file', '=', $this->tree->id())
228            ->where('d_gid', '<>', Header::RECORD_TYPE)
229            ->whereIn('d_fact', $this->getCommonFacts())
230            ->where('d_julianday1', '<>', 0)
231            ->orderBy('d_julianday1', $direction)
232            ->orderBy('d_type')
233            ->first();
234    }
235
236    /**
237     * Returns the formatted first/last occuring event.
238     *
239     * @param string $direction The sorting direction
240     *
241     * @return string
242     */
243    private function getFirstLastEvent(string $direction): string
244    {
245        $row    = $this->eventQuery($direction);
246        $result = I18N::translate('This information is not available.');
247
248        if ($row) {
249            $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree);
250
251            if ($record && $record->canShow()) {
252                $result = $record->formatList();
253            } else {
254                $result = I18N::translate('This information is private and cannot be shown.');
255            }
256        }
257
258        return $result;
259    }
260
261    /**
262     * @return string
263     */
264    public function firstEvent(): string
265    {
266        return $this->getFirstLastEvent(self::SORT_ASC);
267    }
268
269    /**
270     * @return string
271     */
272    public function lastEvent(): string
273    {
274        return $this->getFirstLastEvent(self::SORT_DESC);
275    }
276
277    /**
278     * Returns the formatted year of the first/last occuring event.
279     *
280     * @param string $direction The sorting direction
281     *
282     * @return string
283     */
284    private function getFirstLastEventYear(string $direction): string
285    {
286        $row = $this->eventQuery($direction);
287
288        if (!$row) {
289            return '';
290        }
291
292        return (new Date($row->type . ' ' . $row->year))
293            ->display();
294    }
295
296    /**
297     * @return string
298     */
299    public function firstEventYear(): string
300    {
301        return $this->getFirstLastEventYear(self::SORT_ASC);
302    }
303
304    /**
305     * @return string
306     */
307    public function lastEventYear(): string
308    {
309        return $this->getFirstLastEventYear(self::SORT_DESC);
310    }
311
312    /**
313     * Returns the formatted type of the first/last occurring event.
314     *
315     * @param string $direction The sorting direction
316     *
317     * @return string
318     */
319    private function getFirstLastEventType(string $direction): string
320    {
321        $row = $this->eventQuery($direction);
322
323        if ($row) {
324            $event_types = [
325                self::EVENT_BIRTH    => I18N::translate('birth'),
326                self::EVENT_DEATH    => I18N::translate('death'),
327                self::EVENT_MARRIAGE => I18N::translate('marriage'),
328                self::EVENT_ADOPTION => I18N::translate('adoption'),
329                self::EVENT_BURIAL   => I18N::translate('burial'),
330                self::EVENT_CENSUS   => I18N::translate('census added'),
331            ];
332
333            return $event_types[$row->fact] ?? GedcomTag::getLabel($row->fact);
334        }
335
336        return '';
337    }
338
339    /**
340     * @return string
341     */
342    public function firstEventType(): string
343    {
344        return $this->getFirstLastEventType(self::SORT_ASC);
345    }
346
347    /**
348     * @return string
349     */
350    public function lastEventType(): string
351    {
352        return $this->getFirstLastEventType(self::SORT_DESC);
353    }
354
355    /**
356     * Returns the formatted name of the first/last occuring event.
357     *
358     * @param string $direction The sorting direction
359     *
360     * @return string
361     */
362    private function getFirstLastEventName(string $direction): string
363    {
364        $row = $this->eventQuery($direction);
365
366        if ($row) {
367            $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree);
368
369            if ($record) {
370                return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>';
371            }
372        }
373
374        return '';
375    }
376
377    /**
378     * @return string
379     */
380    public function firstEventName(): string
381    {
382        return $this->getFirstLastEventName(self::SORT_ASC);
383    }
384
385    /**
386     * @return string
387     */
388    public function lastEventName(): string
389    {
390        return $this->getFirstLastEventName(self::SORT_DESC);
391    }
392
393    /**
394     * Returns the formatted place of the first/last occuring event.
395     *
396     * @param string $direction The sorting direction
397     *
398     * @return string
399     */
400    private function getFirstLastEventPlace(string $direction): string
401    {
402        $row = $this->eventQuery($direction);
403
404        if ($row) {
405            $record = Registry::gedcomRecordFactory()->make($row->id, $this->tree);
406            $fact   = null;
407
408            if ($record) {
409                $fact = $record->facts([$row->fact])->first();
410            }
411
412            if ($fact instanceof Fact) {
413                return FunctionsPrint::formatFactPlace($fact, true, true, true);
414            }
415        }
416
417        return I18N::translate('Private');
418    }
419
420    /**
421     * @return string
422     */
423    public function firstEventPlace(): string
424    {
425        return $this->getFirstLastEventPlace(self::SORT_ASC);
426    }
427
428    /**
429     * @return string
430     */
431    public function lastEventPlace(): string
432    {
433        return $this->getFirstLastEventPlace(self::SORT_DESC);
434    }
435}
436