xref: /webtrees/app/Statistics/Repository/EventRepository.php (revision 21bbc77713ed863881cf331fd2cd1edbe3ddfd82)
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 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Statistics\Repository;
21
22use Fisharebest\Webtrees\Date;
23use Fisharebest\Webtrees\Fact;
24use Fisharebest\Webtrees\Functions\FunctionsPrint;
25use Fisharebest\Webtrees\Gedcom;
26use Fisharebest\Webtrees\GedcomRecord;
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     * @inheritDoc
112     */
113    public function totalEvents(array $events = []): string
114    {
115        return I18N::number(
116            $this->getEventCount($events)
117        );
118    }
119
120    /**
121     * @inheritDoc
122     */
123    public function totalEventsBirth(): string
124    {
125        return $this->totalEvents(Gedcom::BIRTH_EVENTS);
126    }
127
128    /**
129     * @inheritDoc
130     */
131    public function totalBirths(): string
132    {
133        return $this->totalEvents([self::EVENT_BIRTH]);
134    }
135
136    /**
137     * @inheritDoc
138     */
139    public function totalEventsDeath(): string
140    {
141        return $this->totalEvents(Gedcom::DEATH_EVENTS);
142    }
143
144    /**
145     * @inheritDoc
146     */
147    public function totalDeaths(): string
148    {
149        return $this->totalEvents([self::EVENT_DEATH]);
150    }
151
152    /**
153     * @inheritDoc
154     */
155    public function totalEventsMarriage(): string
156    {
157        return $this->totalEvents(Gedcom::MARRIAGE_EVENTS);
158    }
159
160    /**
161     * @inheritDoc
162     */
163    public function totalMarriages(): string
164    {
165        return $this->totalEvents([self::EVENT_MARRIAGE]);
166    }
167
168    /**
169     * @inheritDoc
170     */
171    public function totalEventsDivorce(): string
172    {
173        return $this->totalEvents(Gedcom::DIVORCE_EVENTS);
174    }
175
176    /**
177     * @inheritDoc
178     */
179    public function totalDivorces(): string
180    {
181        return $this->totalEvents([self::EVENT_DIVORCE]);
182    }
183
184    /**
185     * Retursn the list of common facts used query the data.
186     *
187     * @return array
188     */
189    private function getCommonFacts(): array
190    {
191        // The list of facts used to limit the query result
192        return array_merge(
193            Gedcom::BIRTH_EVENTS,
194            Gedcom::MARRIAGE_EVENTS,
195            Gedcom::DIVORCE_EVENTS,
196            Gedcom::DEATH_EVENTS
197        );
198    }
199
200    /**
201     * @inheritDoc
202     */
203    public function totalEventsOther(): string
204    {
205        $no_facts = array_map(
206            static function (string $fact): string {
207                return '!' . $fact;
208            },
209            $this->getCommonFacts()
210        );
211
212        return $this->totalEvents($no_facts);
213    }
214
215    /**
216     * Returns the first/last event record from the given list of event facts.
217     *
218     * @param string $direction The sorting direction of the query (To return first or last record)
219     *
220     * @return Model|Builder|object|null
221     */
222    private function eventQuery(string $direction)
223    {
224        return DB::table('dates')
225            ->select(['d_gid as id', 'd_year as year', 'd_fact AS fact', 'd_type AS type'])
226            ->where('d_file', '=', $this->tree->id())
227            ->where('d_gid', '<>', Header::RECORD_TYPE)
228            ->whereIn('d_fact', $this->getCommonFacts())
229            ->where('d_julianday1', '<>', 0)
230            ->orderBy('d_julianday1', $direction)
231            ->orderBy('d_type')
232            ->first();
233    }
234
235    /**
236     * Returns the formatted first/last occuring event.
237     *
238     * @param string $direction The sorting direction
239     *
240     * @return string
241     */
242    private function getFirstLastEvent(string $direction): string
243    {
244        $row    = $this->eventQuery($direction);
245        $result = I18N::translate('This information is not available.');
246
247        if ($row) {
248            $record = GedcomRecord::getInstance($row->id, $this->tree);
249
250            if ($record && $record->canShow()) {
251                $result = $record->formatList();
252            } else {
253                $result = I18N::translate('This information is private and cannot be shown.');
254            }
255        }
256
257        return $result;
258    }
259
260    /**
261     * @inheritDoc
262     */
263    public function firstEvent(): string
264    {
265        return $this->getFirstLastEvent(self::SORT_ASC);
266    }
267
268    /**
269     * @inheritDoc
270     */
271    public function lastEvent(): string
272    {
273        return $this->getFirstLastEvent(self::SORT_DESC);
274    }
275
276    /**
277     * Returns the formatted year of the first/last occuring event.
278     *
279     * @param string $direction The sorting direction
280     *
281     * @return string
282     */
283    private function getFirstLastEventYear(string $direction): string
284    {
285        $row = $this->eventQuery($direction);
286
287        if (!$row) {
288            return '';
289        }
290
291        return (new Date($row->type . ' ' . $row->year))
292            ->display();
293    }
294
295    /**
296     * @inheritDoc
297     */
298    public function firstEventYear(): string
299    {
300        return $this->getFirstLastEventYear(self::SORT_ASC);
301    }
302
303    /**
304     * @inheritDoc
305     */
306    public function lastEventYear(): string
307    {
308        return $this->getFirstLastEventYear(self::SORT_DESC);
309    }
310
311    /**
312     * Returns the formatted type of the first/last occuring event.
313     *
314     * @param string $direction The sorting direction
315     *
316     * @return string
317     */
318    private function getFirstLastEventType(string $direction): string
319    {
320        $row = $this->eventQuery($direction);
321
322        if ($row) {
323            $event_types = [
324                self::EVENT_BIRTH    => I18N::translate('birth'),
325                self::EVENT_DEATH    => I18N::translate('death'),
326                self::EVENT_MARRIAGE => I18N::translate('marriage'),
327                self::EVENT_ADOPTION => I18N::translate('adoption'),
328                self::EVENT_BURIAL   => I18N::translate('burial'),
329                self::EVENT_CENSUS   => I18N::translate('census added'),
330            ];
331
332            return $event_types[$row->fact] ?? GedcomTag::getLabel($row->fact);
333        }
334
335        return '';
336    }
337
338    /**
339     * @inheritDoc
340     */
341    public function firstEventType(): string
342    {
343        return $this->getFirstLastEventType(self::SORT_ASC);
344    }
345
346    /**
347     * @inheritDoc
348     */
349    public function lastEventType(): string
350    {
351        return $this->getFirstLastEventType(self::SORT_DESC);
352    }
353
354    /**
355     * Returns the formatted name of the first/last occuring event.
356     *
357     * @param string $direction The sorting direction
358     *
359     * @return string
360     */
361    private function getFirstLastEventName(string $direction): string
362    {
363        $row = $this->eventQuery($direction);
364
365        if ($row) {
366            $record = GedcomRecord::getInstance($row->id, $this->tree);
367
368            if ($record) {
369                return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>';
370            }
371        }
372
373        return '';
374    }
375
376    /**
377     * @inheritDoc
378     */
379    public function firstEventName(): string
380    {
381        return $this->getFirstLastEventName(self::SORT_ASC);
382    }
383
384    /**
385     * @inheritDoc
386     */
387    public function lastEventName(): string
388    {
389        return $this->getFirstLastEventName(self::SORT_DESC);
390    }
391
392    /**
393     * Returns the formatted place of the first/last occuring event.
394     *
395     * @param string $direction The sorting direction
396     *
397     * @return string
398     */
399    private function getFirstLastEventPlace(string $direction): string
400    {
401        $row = $this->eventQuery($direction);
402
403        if ($row) {
404            $record = GedcomRecord::getInstance($row->id, $this->tree);
405            $fact   = null;
406
407            if ($record) {
408                $fact = $record->facts([$row->fact])->first();
409            }
410
411            if ($fact instanceof Fact) {
412                return FunctionsPrint::formatFactPlace($fact, true, true, true);
413            }
414        }
415
416        return I18N::translate('Private');
417    }
418
419    /**
420     * @inheritDoc
421     */
422    public function firstEventPlace(): string
423    {
424        return $this->getFirstLastEventPlace(self::SORT_ASC);
425    }
426
427    /**
428     * @inheritDoc
429     */
430    public function lastEventPlace(): string
431    {
432        return $this->getFirstLastEventPlace(self::SORT_DESC);
433    }
434}
435