IndividualFactsService::childFacts()   F
last analyzed

Complexity

Conditions 43
Paths 100

Size

Total Lines 420
Code Lines 302

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 43
eloc 302
c 0
b 0
f 0
nc 100
nop 6
dl 0
loc 420
rs 3.3333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Services;
21
22
use Fisharebest\Webtrees\Date;
23
use Fisharebest\Webtrees\Fact;
24
use Fisharebest\Webtrees\Family;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Family was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use Fisharebest\Webtrees\I18N;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\I18N was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use Fisharebest\Webtrees\Individual;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Individual was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
    public function __construct(
41
        private readonly LinkedRecordService $linked_record_service,
42
        private readonly ModuleService $module_service,
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Services\ModuleService was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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