Passed
Push — 2.1 ( 26eef6...e47b9b )
by Greg
08:36
created

EventRepository::countEvents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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