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