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