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