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