Passed
Push — develop ( e50e9c...6c809f )
by Greg
20:25 queued 05:22
created

IndividualFactsService::historicFacts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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