Issues (165)

app/Services/IndividualFactsService.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2023 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\Services;
21
22
use Fisharebest\Webtrees\Date;
23
use Fisharebest\Webtrees\Fact;
24
use Fisharebest\Webtrees\Family;
25
use Fisharebest\Webtrees\I18N;
26
use Fisharebest\Webtrees\Individual;
27
use Fisharebest\Webtrees\Module\ModuleHistoricEventsInterface;
28
use Illuminate\Support\Collection;
29
30
use function explode;
31
use function preg_match;
32
use function preg_replace;
33
use function str_replace;
34
35
/**
36
 * Provide lists of facts for IndividualFactsTabModule.
37
 */
38
class IndividualFactsService
39
{
40
    private LinkedRecordService $linked_record_service;
41
42
    private ModuleService $module_service;
43
44
    /**
45
     * @param LinkedRecordService $linked_record_service
46
     * @param ModuleService       $module_service
47
     */
48
    public function __construct(
49
        LinkedRecordService $linked_record_service,
50
        ModuleService $module_service
51
    ) {
52
        $this->linked_record_service = $linked_record_service;
53
        $this->module_service        = $module_service;
54
    }
55
56
    /**
57
     * The individuals own facts, such as birth and death.
58
     *
59
     * @param Individual             $individual
60
     * @param Collection<int,string> $exclude_facts
61
     *
62
     * @return Collection<int,Fact>
63
     */
64
    public function individualFacts(Individual $individual, Collection $exclude_facts): Collection
65
    {
66
        return $individual->facts()
67
            ->filter(fn (Fact $fact): bool => !$exclude_facts->contains($fact->tag()));
68
    }
69
70
    /**
71
     * The individuals own family facts, such as marriage and divorce.
72
     *
73
     * @param Individual             $individual
74
     * @param Collection<int,string> $exclude_facts
75
     *
76
     * @return Collection<int,Fact>
77
     */
78
    public function familyFacts(Individual $individual, Collection $exclude_facts): Collection
79
    {
80
        return $individual->spouseFamilies()
81
            ->map(fn (Family $family): Collection => $family->facts())
82
            ->flatten()
83
            ->filter(fn (Fact $fact): bool => !$exclude_facts->contains($fact->tag()));
84
    }
85
86
    /**
87
     * Get the events of associates.
88
     *
89
     * @param Individual $individual
90
     *
91
     * @return Collection<int,Fact>
92
     */
93
    public function associateFacts(Individual $individual): Collection
94
    {
95
        $facts = [];
96
97
        $asso1 = $this->linked_record_service->linkedIndividuals($individual, 'ASSO');
98
        $asso2 = $this->linked_record_service->linkedIndividuals($individual, '_ASSO');
99
        $asso3 = $this->linked_record_service->linkedFamilies($individual, 'ASSO');
100
        $asso4 = $this->linked_record_service->linkedFamilies($individual, '_ASSO');
101
102
        $associates = $asso1->merge($asso2)->merge($asso3)->merge($asso4);
103
104
        foreach ($associates as $associate) {
105
            foreach ($associate->facts() as $fact) {
106
                if (preg_match('/\n\d _?ASSO @' . $individual->xref() . '@/', $fact->gedcom())) {
107
                    // Extract the important details from the fact
108
                    $factrec = explode("\n", $fact->gedcom(), 2)[0];
109
                    if (preg_match('/\n2 DATE .*/', $fact->gedcom(), $match)) {
110
                        $factrec .= $match[0];
111
                    }
112
                    if (preg_match('/\n2 PLAC .*/', $fact->gedcom(), $match)) {
113
                        $factrec .= $match[0];
114
                    }
115
                    if ($associate instanceof Family) {
116
                        foreach ($associate->spouses() as $spouse) {
117
                            $factrec .= "\n2 _ASSO @" . $spouse->xref() . '@';
118
                        }
119
                    } else {
120
                        $factrec .= "\n2 _ASSO @" . $associate->xref() . '@';
121
                    }
122
                    $facts[] = new Fact($factrec, $associate, 'asso');
123
                }
124
            }
125
        }
126
127
        return new Collection($facts);
128
    }
129
130
    /**
131
     * Get the events of close relatives.
132
     *
133
     * @param Individual $individual
134
     *
135
     * @return Collection<int,Fact>
136
     */
137
    public function relativeFacts(Individual $individual): Collection
138
    {
139
        // Only include events of close relatives that are between birth and death
140
        $min_date = $individual->getEstimatedBirthDate();
141
        $max_date = $individual->getEstimatedDeathDate();
142
143
        $parent_facts = $this->parentFacts($individual, 1, $min_date, $max_date);
144
145
        $spouse_facts = $individual->spouseFamilies()
146
            ->filter(fn (Family $family): bool => $family->spouse($individual) instanceof Individual)
147
            ->map(fn (Family $family): Collection => $this->spouseFacts($individual, $family->spouse($individual), $min_date, $max_date))
0 ignored issues
show
It seems like $family->spouse($individual) can also be of type null; however, parameter $spouse of Fisharebest\Webtrees\Ser...sService::spouseFacts() does only seem to accept Fisharebest\Webtrees\Individual, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
            ->map(fn (Family $family): Collection => $this->spouseFacts($individual, /** @scrutinizer ignore-type */ $family->spouse($individual), $min_date, $max_date))
Loading history...
148
            ->flatten();
149
150
        $child_facts = $individual->spouseFamilies()
151
            ->map(fn (Family $family): Collection => $this->childFacts($individual, $family, '_CHIL', '', $min_date, $max_date))
152
            ->flatten();
153
154
        return $parent_facts
155
            ->merge($child_facts)
156
            ->merge($spouse_facts)
157
            ->unique();
158
    }
159
160
    /**
161
     * Get any historical events.
162
     *
163
     * @param Individual $individual
164
     *
165
     * @return Collection<int,Fact>
166
     */
167
    public function historicFacts(Individual $individual): Collection
168
    {
169
        return $this->module_service->findByInterface(ModuleHistoricEventsInterface::class)
170
            ->map(static fn (ModuleHistoricEventsInterface $module): Collection => $module->historicEventsForIndividual($individual))
171
            ->flatten();
172
    }
173
174
    /**
175
     * Get the events of children and grandchildren.
176
     *
177
     * @param Individual $person
178
     * @param Family     $family
179
     * @param string     $option
180
     * @param string     $relation
181
     * @param Date       $min_date
182
     * @param Date       $max_date
183
     *
184
     * @return Collection<int,Fact>
185
     */
186
    private function childFacts(Individual $person, Family $family, string $option, string $relation, Date $min_date, Date $max_date): Collection
187
    {
188
        $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS');
189
190
        $birth_of_a_child = [
191
            'INDI:BIRT' => [
192
                'M' => I18N::translate('Birth of a son'),
193
                'F' => I18N::translate('Birth of a daughter'),
194
                'U' => I18N::translate('Birth of a child'),
195
            ],
196
            'INDI:CHR'  => [
197
                'M' => I18N::translate('Christening of a son'),
198
                'F' => I18N::translate('Christening of a daughter'),
199
                'U' => I18N::translate('Christening of a child'),
200
            ],
201
            'INDI:BAPM' => [
202
                'M' => I18N::translate('Baptism of a son'),
203
                'F' => I18N::translate('Baptism of a daughter'),
204
                'U' => I18N::translate('Baptism of a child'),
205
            ],
206
            'INDI:ADOP' => [
207
                'M' => I18N::translate('Adoption of a son'),
208
                'F' => I18N::translate('Adoption of a daughter'),
209
                'U' => I18N::translate('Adoption of a child'),
210
            ],
211
        ];
212
213
        $birth_of_a_sibling = [
214
            'INDI:BIRT' => [
215
                'M' => I18N::translate('Birth of a brother'),
216
                'F' => I18N::translate('Birth of a sister'),
217
                'U' => I18N::translate('Birth of a sibling'),
218
            ],
219
            'INDI:CHR'  => [
220
                'M' => I18N::translate('Christening of a brother'),
221
                'F' => I18N::translate('Christening of a sister'),
222
                'U' => I18N::translate('Christening of a sibling'),
223
            ],
224
            'INDI:BAPM' => [
225
                'M' => I18N::translate('Baptism of a brother'),
226
                'F' => I18N::translate('Baptism of a sister'),
227
                'U' => I18N::translate('Baptism of a sibling'),
228
            ],
229
            'INDI:ADOP' => [
230
                'M' => I18N::translate('Adoption of a brother'),
231
                'F' => I18N::translate('Adoption of a sister'),
232
                'U' => I18N::translate('Adoption of a sibling'),
233
            ],
234
        ];
235
236
        $birth_of_a_half_sibling = [
237
            'INDI:BIRT' => [
238
                'M' => I18N::translate('Birth of a half-brother'),
239
                'F' => I18N::translate('Birth of a half-sister'),
240
                'U' => I18N::translate('Birth of a half-sibling'),
241
            ],
242
            'INDI:CHR'  => [
243
                'M' => I18N::translate('Christening of a half-brother'),
244
                'F' => I18N::translate('Christening of a half-sister'),
245
                'U' => I18N::translate('Christening of a half-sibling'),
246
            ],
247
            'INDI:BAPM' => [
248
                'M' => I18N::translate('Baptism of a half-brother'),
249
                'F' => I18N::translate('Baptism of a half-sister'),
250
                'U' => I18N::translate('Baptism of a half-sibling'),
251
            ],
252
            'INDI:ADOP' => [
253
                'M' => I18N::translate('Adoption of a half-brother'),
254
                'F' => I18N::translate('Adoption of a half-sister'),
255
                'U' => I18N::translate('Adoption of a half-sibling'),
256
            ],
257
        ];
258
259
        $birth_of_a_grandchild = [
260
            'INDI:BIRT' => [
261
                'M' => I18N::translate('Birth of a grandson'),
262
                'F' => I18N::translate('Birth of a granddaughter'),
263
                'U' => I18N::translate('Birth of a grandchild'),
264
            ],
265
            'INDI:CHR'  => [
266
                'M' => I18N::translate('Christening of a grandson'),
267
                'F' => I18N::translate('Christening of a granddaughter'),
268
                'U' => I18N::translate('Christening of a grandchild'),
269
            ],
270
            'INDI:BAPM' => [
271
                'M' => I18N::translate('Baptism of a grandson'),
272
                'F' => I18N::translate('Baptism of a granddaughter'),
273
                'U' => I18N::translate('Baptism of a grandchild'),
274
            ],
275
            'INDI:ADOP' => [
276
                'M' => I18N::translate('Adoption of a grandson'),
277
                'F' => I18N::translate('Adoption of a granddaughter'),
278
                'U' => I18N::translate('Adoption of a grandchild'),
279
            ],
280
        ];
281
282
        $birth_of_a_grandchild1 = [
283
            'INDI:BIRT' => [
284
                'M' => I18N::translateContext('daughter’s son', 'Birth of a grandson'),
285
                'F' => I18N::translateContext('daughter’s daughter', 'Birth of a granddaughter'),
286
                'U' => I18N::translate('Birth of a grandchild'),
287
            ],
288
            'INDI:CHR'  => [
289
                'M' => I18N::translateContext('daughter’s son', 'Christening of a grandson'),
290
                'F' => I18N::translateContext('daughter’s daughter', 'Christening of a granddaughter'),
291
                'U' => I18N::translate('Christening of a grandchild'),
292
            ],
293
            'INDI:BAPM' => [
294
                'M' => I18N::translateContext('daughter’s son', 'Baptism of a grandson'),
295
                'F' => I18N::translateContext('daughter’s daughter', 'Baptism of a granddaughter'),
296
                'U' => I18N::translate('Baptism of a grandchild'),
297
            ],
298
            'INDI:ADOP' => [
299
                'M' => I18N::translateContext('daughter’s son', 'Adoption of a grandson'),
300
                'F' => I18N::translateContext('daughter’s daughter', 'Adoption of a granddaughter'),
301
                'U' => I18N::translate('Adoption of a grandchild'),
302
            ],
303
        ];
304
305
        $birth_of_a_grandchild2 = [
306
            'INDI:BIRT' => [
307
                'M' => I18N::translateContext('son’s son', 'Birth of a grandson'),
308
                'F' => I18N::translateContext('son’s daughter', 'Birth of a granddaughter'),
309
                'U' => I18N::translate('Birth of a grandchild'),
310
            ],
311
            'INDI:CHR'  => [
312
                'M' => I18N::translateContext('son’s son', 'Christening of a grandson'),
313
                'F' => I18N::translateContext('son’s daughter', 'Christening of a granddaughter'),
314
                'U' => I18N::translate('Christening of a grandchild'),
315
            ],
316
            'INDI:BAPM' => [
317
                'M' => I18N::translateContext('son’s son', 'Baptism of a grandson'),
318
                'F' => I18N::translateContext('son’s daughter', 'Baptism of a granddaughter'),
319
                'U' => I18N::translate('Baptism of a grandchild'),
320
            ],
321
            'INDI:ADOP' => [
322
                'M' => I18N::translateContext('son’s son', 'Adoption of a grandson'),
323
                'F' => I18N::translateContext('son’s daughter', 'Adoption of a granddaughter'),
324
                'U' => I18N::translate('Adoption of a grandchild'),
325
            ],
326
        ];
327
328
        $death_of_a_child = [
329
            'INDI:DEAT' => [
330
                'M' => I18N::translate('Death of a son'),
331
                'F' => I18N::translate('Death of a daughter'),
332
                'U' => I18N::translate('Death of a child'),
333
            ],
334
            'INDI:BURI' => [
335
                'M' => I18N::translate('Burial of a son'),
336
                'F' => I18N::translate('Burial of a daughter'),
337
                'U' => I18N::translate('Burial of a child'),
338
            ],
339
            'INDI:CREM' => [
340
                'M' => I18N::translate('Cremation of a son'),
341
                'F' => I18N::translate('Cremation of a daughter'),
342
                'U' => I18N::translate('Cremation of a child'),
343
            ],
344
        ];
345
346
        $death_of_a_sibling = [
347
            'INDI:DEAT' => [
348
                'M' => I18N::translate('Death of a brother'),
349
                'F' => I18N::translate('Death of a sister'),
350
                'U' => I18N::translate('Death of a sibling'),
351
            ],
352
            'INDI:BURI' => [
353
                'M' => I18N::translate('Burial of a brother'),
354
                'F' => I18N::translate('Burial of a sister'),
355
                'U' => I18N::translate('Burial of a sibling'),
356
            ],
357
            'INDI:CREM' => [
358
                'M' => I18N::translate('Cremation of a brother'),
359
                'F' => I18N::translate('Cremation of a sister'),
360
                'U' => I18N::translate('Cremation of a sibling'),
361
            ],
362
        ];
363
364
        $death_of_a_half_sibling = [
365
            'INDI:DEAT' => [
366
                'M' => I18N::translate('Death of a half-brother'),
367
                'F' => I18N::translate('Death of a half-sister'),
368
                'U' => I18N::translate('Death of a half-sibling'),
369
            ],
370
            'INDI:BURI' => [
371
                'M' => I18N::translate('Burial of a half-brother'),
372
                'F' => I18N::translate('Burial of a half-sister'),
373
                'U' => I18N::translate('Burial of a half-sibling'),
374
            ],
375
            'INDI:CREM' => [
376
                'M' => I18N::translate('Cremation of a half-brother'),
377
                'F' => I18N::translate('Cremation of a half-sister'),
378
                'U' => I18N::translate('Cremation of a half-sibling'),
379
            ],
380
        ];
381
382
        $death_of_a_grandchild = [
383
            'INDI:DEAT' => [
384
                'M' => I18N::translate('Death of a grandson'),
385
                'F' => I18N::translate('Death of a granddaughter'),
386
                'U' => I18N::translate('Death of a grandchild'),
387
            ],
388
            'INDI:BURI' => [
389
                'M' => I18N::translate('Burial of a grandson'),
390
                'F' => I18N::translate('Burial of a granddaughter'),
391
                'U' => I18N::translate('Burial of a grandchild'),
392
            ],
393
            'INDI:CREM' => [
394
                'M' => I18N::translate('Cremation of a grandson'),
395
                'F' => I18N::translate('Cremation of a granddaughter'),
396
                'U' => I18N::translate('Baptism of a grandchild'),
397
            ],
398
        ];
399
400
        $death_of_a_grandchild1 = [
401
            'INDI:DEAT' => [
402
                'M' => I18N::translateContext('daughter’s son', 'Death of a grandson'),
403
                'F' => I18N::translateContext('daughter’s daughter', 'Death of a granddaughter'),
404
                'U' => I18N::translate('Death of a grandchild'),
405
            ],
406
            'INDI:BURI' => [
407
                'M' => I18N::translateContext('daughter’s son', 'Burial of a grandson'),
408
                'F' => I18N::translateContext('daughter’s daughter', 'Burial of a granddaughter'),
409
                'U' => I18N::translate('Burial of a grandchild'),
410
            ],
411
            'INDI:CREM' => [
412
                'M' => I18N::translateContext('daughter’s son', 'Cremation of a grandson'),
413
                'F' => I18N::translateContext('daughter’s daughter', 'Cremation of a granddaughter'),
414
                'U' => I18N::translate('Baptism of a grandchild'),
415
            ],
416
        ];
417
418
        $death_of_a_grandchild2 = [
419
            'INDI:DEAT' => [
420
                'M' => I18N::translateContext('son’s son', 'Death of a grandson'),
421
                'F' => I18N::translateContext('son’s daughter', 'Death of a granddaughter'),
422
                'U' => I18N::translate('Death of a grandchild'),
423
            ],
424
            'INDI:BURI' => [
425
                'M' => I18N::translateContext('son’s son', 'Burial of a grandson'),
426
                'F' => I18N::translateContext('son’s daughter', 'Burial of a granddaughter'),
427
                'U' => I18N::translate('Burial of a grandchild'),
428
            ],
429
            'INDI:CREM' => [
430
                'M' => I18N::translateContext('son’s son', 'Cremation of a grandson'),
431
                'F' => I18N::translateContext('son’s daughter', 'Cremation of a granddaughter'),
432
                'U' => I18N::translate('Cremation of a grandchild'),
433
            ],
434
        ];
435
436
        $marriage_of_a_child = [
437
            'M' => I18N::translate('Marriage of a son'),
438
            'F' => I18N::translate('Marriage of a daughter'),
439
            'U' => I18N::translate('Marriage of a child'),
440
        ];
441
442
        $marriage_of_a_grandchild = [
443
            'M' => I18N::translate('Marriage of a grandson'),
444
            'F' => I18N::translate('Marriage of a granddaughter'),
445
            'U' => I18N::translate('Marriage of a grandchild'),
446
        ];
447
448
        $marriage_of_a_grandchild1 = [
449
            'M' => I18N::translateContext('daughter’s son', 'Marriage of a grandson'),
450
            'F' => I18N::translateContext('daughter’s daughter', 'Marriage of a granddaughter'),
451
            'U' => I18N::translate('Marriage of a grandchild'),
452
        ];
453
454
        $marriage_of_a_grandchild2 = [
455
            'M' => I18N::translateContext('son’s son', 'Marriage of a grandson'),
456
            'F' => I18N::translateContext('son’s daughter', 'Marriage of a granddaughter'),
457
            'U' => I18N::translate('Marriage of a grandchild'),
458
        ];
459
460
        $marriage_of_a_sibling = [
461
            'M' => I18N::translate('Marriage of a brother'),
462
            'F' => I18N::translate('Marriage of a sister'),
463
            'U' => I18N::translate('Marriage of a sibling'),
464
        ];
465
466
        $marriage_of_a_half_sibling = [
467
            'M' => I18N::translate('Marriage of a half-brother'),
468
            'F' => I18N::translate('Marriage of a half-sister'),
469
            'U' => I18N::translate('Marriage of a half-sibling'),
470
        ];
471
472
        $facts = new Collection();
473
474
        // Deal with recursion.
475
        if ($option === '_CHIL') {
476
            // Add grandchildren
477
            foreach ($family->children() as $child) {
478
                foreach ($child->spouseFamilies() as $cfamily) {
479
                    switch ($child->sex()) {
480
                        case 'M':
481
                            foreach ($this->childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) {
482
                                $facts[] = $fact;
483
                            }
484
                            break;
485
                        case 'F':
486
                            foreach ($this->childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) {
487
                                $facts[] = $fact;
488
                            }
489
                            break;
490
                        default:
491
                            foreach ($this->childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) {
492
                                $facts[] = $fact;
493
                            }
494
                            break;
495
                    }
496
                }
497
            }
498
        }
499
500
        // For each child in the family
501
        foreach ($family->children() as $child) {
502
            if ($child->xref() === $person->xref()) {
503
                // We are not our own sibling!
504
                continue;
505
            }
506
            // add child’s birth
507
            if (str_contains($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option))) {
508
                foreach ($child->facts(['BIRT', 'CHR', 'BAPM', 'ADOP']) as $fact) {
509
                    // Always show _BIRT_CHIL, even if the dates are not known
510
                    if ($option === '_CHIL' || $this->includeFact($fact, $min_date, $max_date)) {
511
                        switch ($option) {
512
                            case '_GCHI':
513
                                switch ($relation) {
514
                                    case 'dau':
515
                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild1[$fact->tag()], $fact->record()->sex());
516
                                        break;
517
                                    case 'son':
518
                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild2[$fact->tag()], $fact->record()->sex());
519
                                        break;
520
                                    case 'chil':
521
                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild[$fact->tag()], $fact->record()->sex());
522
                                        break;
523
                                }
524
                                break;
525
                            case '_SIBL':
526
                                $facts[] = $this->convertEvent($fact, $birth_of_a_sibling[$fact->tag()], $fact->record()->sex());
527
                                break;
528
                            case '_HSIB':
529
                                $facts[] = $this->convertEvent($fact, $birth_of_a_half_sibling[$fact->tag()], $fact->record()->sex());
530
                                break;
531
                            case '_CHIL':
532
                                $facts[] = $this->convertEvent($fact, $birth_of_a_child[$fact->tag()], $fact->record()->sex());
533
                                break;
534
                        }
535
                    }
536
                }
537
            }
538
            // add child’s death
539
            if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option))) {
540
                foreach ($child->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
541
                    if ($this->includeFact($fact, $min_date, $max_date)) {
542
                        switch ($option) {
543
                            case '_GCHI':
544
                                switch ($relation) {
545
                                    case 'dau':
546
                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild1[$fact->tag()], $fact->record()->sex());
547
                                        break;
548
                                    case 'son':
549
                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild2[$fact->tag()], $fact->record()->sex());
550
                                        break;
551
                                    case 'chi':
552
                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild[$fact->tag()], $fact->record()->sex());
553
                                        break;
554
                                }
555
                                break;
556
                            case '_SIBL':
557
                                $facts[] = $this->convertEvent($fact, $death_of_a_sibling[$fact->tag()], $fact->record()->sex());
558
                                break;
559
                            case '_HSIB':
560
                                $facts[] = $this->convertEvent($fact, $death_of_a_half_sibling[$fact->tag()], $fact->record()->sex());
561
                                break;
562
                            case '_CHIL':
563
                                $facts[] = $this->convertEvent($fact, $death_of_a_child[$fact->tag()], $fact->record()->sex());
564
                                break;
565
                        }
566
                    }
567
                }
568
            }
569
570
            // add child’s marriage
571
            if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) {
572
                foreach ($child->spouseFamilies() as $sfamily) {
573
                    foreach ($sfamily->facts(['MARR']) as $fact) {
574
                        if ($this->includeFact($fact, $min_date, $max_date)) {
575
                            switch ($option) {
576
                                case '_GCHI':
577
                                    switch ($relation) {
578
                                        case 'dau':
579
                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild1, $child->sex());
580
                                            break;
581
                                        case 'son':
582
                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild2, $child->sex());
583
                                            break;
584
                                        case 'chi':
585
                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild, $child->sex());
586
                                            break;
587
                                    }
588
                                    break;
589
                                case '_SIBL':
590
                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_sibling, $child->sex());
591
                                    break;
592
                                case '_HSIB':
593
                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_half_sibling, $child->sex());
594
                                    break;
595
                                case '_CHIL':
596
                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_child, $child->sex());
597
                                    break;
598
                            }
599
                        }
600
                    }
601
                }
602
            }
603
        }
604
605
        return $facts;
606
    }
607
608
    /**
609
     * Get the events of parents and grandparents.
610
     *
611
     * @param Individual $person
612
     * @param int        $sosa
613
     * @param Date       $min_date
614
     * @param Date       $max_date
615
     *
616
     * @return Collection<int,Fact>
617
     */
618
    private function parentFacts(Individual $person, int $sosa, Date $min_date, Date $max_date): Collection
619
    {
620
        $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS');
621
622
        $death_of_a_parent = [
623
            'INDI:DEAT' => [
624
                'M' => I18N::translate('Death of a father'),
625
                'F' => I18N::translate('Death of a mother'),
626
                'U' => I18N::translate('Death of a parent'),
627
            ],
628
            'INDI:BURI' => [
629
                'M' => I18N::translate('Burial of a father'),
630
                'F' => I18N::translate('Burial of a mother'),
631
                'U' => I18N::translate('Burial of a parent'),
632
            ],
633
            'INDI:CREM' => [
634
                'M' => I18N::translate('Cremation of a father'),
635
                'F' => I18N::translate('Cremation of a mother'),
636
                'U' => I18N::translate('Cremation of a parent'),
637
            ],
638
        ];
639
640
        $death_of_a_grandparent = [
641
            'INDI:DEAT' => [
642
                'M' => I18N::translate('Death of a grandfather'),
643
                'F' => I18N::translate('Death of a grandmother'),
644
                'U' => I18N::translate('Death of a grandparent'),
645
            ],
646
            'INDI:BURI' => [
647
                'M' => I18N::translate('Burial of a grandfather'),
648
                'F' => I18N::translate('Burial of a grandmother'),
649
                'U' => I18N::translate('Burial of a grandparent'),
650
            ],
651
            'INDI:CREM' => [
652
                'M' => I18N::translate('Cremation of a grandfather'),
653
                'F' => I18N::translate('Cremation of a grandmother'),
654
                'U' => I18N::translate('Cremation of a grandparent'),
655
            ],
656
        ];
657
658
        $death_of_a_maternal_grandparent = [
659
            'INDI:DEAT' => [
660
                'M' => I18N::translate('Death of a maternal grandfather'),
661
                'F' => I18N::translate('Death of a maternal grandmother'),
662
                'U' => I18N::translate('Death of a grandparent'),
663
            ],
664
            'INDI:BURI' => [
665
                'M' => I18N::translate('Burial of a maternal grandfather'),
666
                'F' => I18N::translate('Burial of a maternal grandmother'),
667
                'U' => I18N::translate('Burial of a grandparent'),
668
            ],
669
            'INDI:CREM' => [
670
                'M' => I18N::translate('Cremation of a maternal grandfather'),
671
                'F' => I18N::translate('Cremation of a maternal grandmother'),
672
                'U' => I18N::translate('Cremation of a grandparent'),
673
            ],
674
        ];
675
676
        $death_of_a_paternal_grandparent = [
677
            'INDI:DEAT' => [
678
                'M' => I18N::translate('Death of a paternal grandfather'),
679
                'F' => I18N::translate('Death of a paternal grandmother'),
680
                'U' => I18N::translate('Death of a grandparent'),
681
            ],
682
            'INDI:BURI' => [
683
                'M' => I18N::translate('Burial of a paternal grandfather'),
684
                'F' => I18N::translate('Burial of a paternal grandmother'),
685
                'U' => I18N::translate('Burial of a grandparent'),
686
            ],
687
            'INDI:CREM' => [
688
                'M' => I18N::translate('Cremation of a paternal grandfather'),
689
                'F' => I18N::translate('Cremation of a paternal grandmother'),
690
                'U' => I18N::translate('Cremation of a grandparent'),
691
            ],
692
        ];
693
694
        $marriage_of_a_parent = [
695
            'M' => I18N::translate('Marriage of a father'),
696
            'F' => I18N::translate('Marriage of a mother'),
697
            'U' => I18N::translate('Marriage of a parent'),
698
        ];
699
700
        $facts = new Collection();
701
702
        if ($sosa === 1) {
703
            foreach ($person->childFamilies() as $family) {
704
                // Add siblings
705
                foreach ($this->childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) {
706
                    $facts[] = $fact;
707
                }
708
                foreach ($family->spouses() as $spouse) {
709
                    foreach ($spouse->spouseFamilies() as $sfamily) {
710
                        if ($family !== $sfamily) {
711
                            // Add half-siblings
712
                            foreach ($this->childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) {
713
                                $facts[] = $fact;
714
                            }
715
                        }
716
                    }
717
                    // Add grandparents
718
                    foreach ($this->parentFacts($spouse, $spouse->sex() === 'F' ? 3 : 2, $min_date, $max_date) as $fact) {
719
                        $facts[] = $fact;
720
                    }
721
                }
722
            }
723
724
            if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) {
725
                // add father/mother marriages
726
                foreach ($person->childFamilies() as $sfamily) {
727
                    foreach ($sfamily->facts(['MARR']) as $fact) {
728
                        if ($this->includeFact($fact, $min_date, $max_date)) {
729
                            // marriage of parents (to each other)
730
                            $facts[] = $this->convertEvent($fact, ['U' => I18N::translate('Marriage of parents')], 'U');
731
                        }
732
                    }
733
                }
734
                foreach ($person->childStepFamilies() as $sfamily) {
735
                    foreach ($sfamily->facts(['MARR']) as $fact) {
736
                        if ($this->includeFact($fact, $min_date, $max_date)) {
737
                            // marriage of a parent (to another spouse)
738
                            $facts[] = $this->convertEvent($fact, $marriage_of_a_parent, 'U');
739
                        }
740
                    }
741
                }
742
            }
743
        }
744
745
        foreach ($person->childFamilies() as $family) {
746
            foreach ($family->spouses() as $parent) {
747
                if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa === 1 ? '_PARE' : '_GPAR'))) {
748
                    foreach ($parent->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
749
                        // Show death of parent when it happened prior to birth
750
                        if ($sosa === 1 && Date::compare($fact->date(), $min_date) < 0 || $this->includeFact($fact, $min_date, $max_date)) {
751
                            switch ($sosa) {
752
                                case 1:
753
                                    $facts[] = $this->convertEvent($fact, $death_of_a_parent[$fact->tag()], $fact->record()->sex());
754
                                    break;
755
                                case 2:
756
                                case 3:
757
                                    switch ($person->sex()) {
758
                                        case 'M':
759
                                            $facts[] = $this->convertEvent($fact, $death_of_a_paternal_grandparent[$fact->tag()], $fact->record()->sex());
760
                                            break;
761
                                        case 'F':
762
                                            $facts[] = $this->convertEvent($fact, $death_of_a_maternal_grandparent[$fact->tag()], $fact->record()->sex());
763
                                            break;
764
                                        default:
765
                                            $facts[] = $this->convertEvent($fact, $death_of_a_grandparent[$fact->tag()], $fact->record()->sex());
766
                                            break;
767
                                    }
768
                            }
769
                        }
770
                    }
771
                }
772
            }
773
        }
774
775
        return $facts;
776
    }
777
778
    /**
779
     * Spouse facts that are shown on an individual’s page.
780
     *
781
     * @param Individual $individual Show events that occurred during the lifetime of this individual
782
     * @param Individual $spouse     Show events of this individual
783
     * @param Date       $min_date
784
     * @param Date       $max_date
785
     *
786
     * @return Collection<int,Fact>
787
     */
788
    private function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): Collection
789
    {
790
        $SHOW_RELATIVES_EVENTS = $individual->tree()->getPreference('SHOW_RELATIVES_EVENTS');
791
792
        $death_of_a_spouse = [
793
            'INDI:DEAT' => [
794
                'M' => I18N::translate('Death of a husband'),
795
                'F' => I18N::translate('Death of a wife'),
796
                'U' => I18N::translate('Death of a spouse'),
797
            ],
798
            'INDI:BURI' => [
799
                'M' => I18N::translate('Burial of a husband'),
800
                'F' => I18N::translate('Burial of a wife'),
801
                'U' => I18N::translate('Burial of a spouse'),
802
            ],
803
            'INDI:CREM' => [
804
                'M' => I18N::translate('Cremation of a husband'),
805
                'F' => I18N::translate('Cremation of a wife'),
806
                'U' => I18N::translate('Cremation of a spouse'),
807
            ],
808
        ];
809
810
        $facts = new Collection();
811
812
        if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) {
813
            foreach ($spouse->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
814
                if ($this->includeFact($fact, $min_date, $max_date)) {
815
                    $facts[] = $this->convertEvent($fact, $death_of_a_spouse[$fact->tag()], $fact->record()->sex());
816
                }
817
            }
818
        }
819
820
        return $facts;
821
    }
822
823
    /**
824
     * Does a relative event occur within a date range (i.e. the individual's lifetime)?
825
     *
826
     * @param Fact $fact
827
     * @param Date $min_date
828
     * @param Date $max_date
829
     *
830
     * @return bool
831
     */
832
    private function includeFact(Fact $fact, Date $min_date, Date $max_date): bool
833
    {
834
        $fact_date = $fact->date();
835
836
        return $fact_date->isOK() && Date::compare($min_date, $fact_date) <= 0 && Date::compare($fact_date, $max_date) <= 0;
837
    }
838
839
    /**
840
     * Convert an event into a special "event of a close relative".
841
     *
842
     * @param Fact          $fact
843
     * @param array<string> $types
844
     * @param string        $sex
845
     *
846
     * @return Fact
847
     */
848
    private function convertEvent(Fact $fact, array $types, string $sex): Fact
849
    {
850
        $type = $types[$sex] ?? $types['U'];
851
852
        $gedcom = $fact->gedcom();
853
        $gedcom = preg_replace('/\n2 TYPE .*/', '', $gedcom);
854
        $gedcom = preg_replace('/^1 .*/', "1 EVEN CLOSE_RELATIVE\n2 TYPE " . $type, $gedcom);
855
856
        $converted = new Fact($gedcom, $fact->record(), $fact->id());
857
858
        if ($fact->isPendingAddition()) {
859
            $converted->setPendingAddition();
860
        }
861
862
        if ($fact->isPendingDeletion()) {
863
            $converted->setPendingDeletion();
864
        }
865
866
        return $converted;
867
    }
868
}
869