IndividualFactsTabModule::childFacts()   F
last analyzed

Complexity

Conditions 43
Paths 100

Size

Total Lines 422
Code Lines 304

Duplication

Lines 0
Ratio 0 %

Importance

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

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) 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\Module;
21
22
use Fisharebest\Webtrees\Auth;
23
use Fisharebest\Webtrees\Date;
24
use Fisharebest\Webtrees\Fact;
25
use Fisharebest\Webtrees\Family;
26
use Fisharebest\Webtrees\I18N;
27
use Fisharebest\Webtrees\Individual;
28
use Fisharebest\Webtrees\Services\ClipboardService;
29
use Fisharebest\Webtrees\Services\ModuleService;
30
use Illuminate\Support\Collection;
31
32
use function str_contains;
33
34
/**
35
 * Class IndividualFactsTabModule
36
 */
37
class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterface
38
{
39
    use ModuleTabTrait;
40
41
    /** @var ModuleService */
42
    private $module_service;
43
44
    /** @var ClipboardService */
45
    private $clipboard_service;
46
47
    /**
48
     * IndividualFactsTabModule constructor.
49
     *
50
     * @param ModuleService    $module_service
51
     * @param ClipboardService $clipboard_service
52
     */
53
    public function __construct(ModuleService $module_service, ClipboardService $clipboard_service)
54
    {
55
        $this->module_service    = $module_service;
56
        $this->clipboard_service = $clipboard_service;
57
    }
58
59
    /**
60
     * How should this module be identified in the control panel, etc.?
61
     *
62
     * @return string
63
     */
64
    public function title(): string
65
    {
66
        /* I18N: Name of a module/tab on the individual page. */
67
        return I18N::translate('Facts and events');
68
    }
69
70
    /**
71
     * A sentence describing what this module does.
72
     *
73
     * @return string
74
     */
75
    public function description(): string
76
    {
77
        /* I18N: Description of the “Facts and events” module */
78
        return I18N::translate('A tab showing the facts and events of an individual.');
79
    }
80
81
    /**
82
     * The default position for this tab.  It can be changed in the control panel.
83
     *
84
     * @return int
85
     */
86
    public function defaultTabOrder(): int
87
    {
88
        return 1;
89
    }
90
91
    /**
92
     * A greyed out tab has no actual content, but may perhaps have
93
     * options to create content.
94
     *
95
     * @param Individual $individual
96
     *
97
     * @return bool
98
     */
99
    public function isGrayedOut(Individual $individual): bool
100
    {
101
        return false;
102
    }
103
104
    /**
105
     * Generate the HTML content of this tab.
106
     *
107
     * @param Individual $individual
108
     *
109
     * @return string
110
     */
111
    public function getTabContent(Individual $individual): string
112
    {
113
        // Only include events of close relatives that are between birth and death
114
        $min_date = $individual->getEstimatedBirthDate();
115
        $max_date = $individual->getEstimatedDeathDate();
116
117
        // Which facts and events are handled by other modules?
118
        $sidebar_facts = $this->module_service
119
            ->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user())
120
            ->map(static function (ModuleSidebarInterface $sidebar): Collection {
121
                return $sidebar->supportedFacts();
122
            });
123
124
        $tab_facts = $this->module_service
125
            ->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user())
126
            ->map(static function (ModuleTabInterface $sidebar): Collection {
127
                return $sidebar->supportedFacts();
128
            });
129
130
        $exclude_facts = $sidebar_facts->merge($tab_facts)->flatten();
131
132
        // The individual’s own facts
133
        $indifacts = $individual->facts()
134
            ->filter(static function (Fact $fact) use ($exclude_facts): bool {
135
                return !$exclude_facts->contains($fact->getTag());
0 ignored issues
show
Deprecated Code introduced by
The function Fisharebest\Webtrees\Fact::getTag() has been deprecated: since 2.0.5. Will be removed in 2.1.0 ( Ignorable by Annotation )

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

135
                return !$exclude_facts->contains(/** @scrutinizer ignore-deprecated */ $fact->getTag());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

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