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