Passed
Push — master ( ad1699...9c2a55 )
by Greg
10:16
created

IndividualFactsTabModule   F

Complexity

Total Complexity 102

Size/Duplication

Total Lines 917
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 507
c 1
b 0
f 0
dl 0
loc 917
rs 2
wmc 102

16 Methods

Rating   Name   Duplication   Size   Complexity  
A includeFact() 0 5 3
B getTabContent() 0 64 6
A title() 0 4 1
A defaultTabOrder() 0 3 1
A hasTabContent() 0 3 1
A __construct() 0 4 1
A description() 0 4 1
A canLoadAjax() 0 3 1
A isGrayedOut() 0 3 1
A historicalFacts() 0 8 1
A supportedFacts() 0 5 1
B associateFacts() 0 36 8
A convertEvent() 0 6 1
A spouseFacts() 0 33 4
F childFacts() 0 422 43
F parentFacts() 0 157 28

How to fix   Complexity   

Complex Class

Complex classes like IndividualFactsTabModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IndividualFactsTabModule, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 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 <http://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\Gedcom;
27
use Fisharebest\Webtrees\I18N;
28
use Fisharebest\Webtrees\Individual;
29
use Fisharebest\Webtrees\Services\ClipboardService;
30
use Fisharebest\Webtrees\Services\ModuleService;
31
use Illuminate\Support\Collection;
32
33
/**
34
 * Class IndividualFactsTabModule
35
 */
36
class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterface
37
{
38
    use ModuleTabTrait;
39
40
    /** @var ModuleService */
41
    private $module_service;
42
43
    /** @var ClipboardService */
44
    private $clipboard_service;
45
46
    /**
47
     * UserWelcomeModule constructor.
48
     *
49
     * @param ModuleService    $module_service
50
     * @param ClipboardService $clipboard_service
51
     */
52
    public function __construct(ModuleService $module_service, ClipboardService $clipboard_service)
53
    {
54
        $this->module_service    = $module_service;
55
        $this->clipboard_service = $clipboard_service;
56
    }
57
58
    /**
59
     * How should this module be identified in the control panel, etc.?
60
     *
61
     * @return string
62
     */
63
    public function title(): string
64
    {
65
        /* I18N: Name of a module/tab on the individual page. */
66
        return I18N::translate('Facts and events');
67
    }
68
69
    /**
70
     * A sentence describing what this module does.
71
     *
72
     * @return string
73
     */
74
    public function description(): string
75
    {
76
        /* I18N: Description of the “Facts and events” module */
77
        return I18N::translate('A tab showing the facts and events of an individual.');
78
    }
79
80
    /**
81
     * The default position for this tab.  It can be changed in the control panel.
82
     *
83
     * @return int
84
     */
85
    public function defaultTabOrder(): int
86
    {
87
        return 1;
88
    }
89
90
    /**
91
     * A greyed out tab has no actual content, but may perhaps have
92
     * options to create content.
93
     *
94
     * @param Individual $individual
95
     *
96
     * @return bool
97
     */
98
    public function isGrayedOut(Individual $individual): bool
99
    {
100
        return false;
101
    }
102
103
    /**
104
     * Generate the HTML content of this tab.
105
     *
106
     * @param Individual $individual
107
     *
108
     * @return string
109
     */
110
    public function getTabContent(Individual $individual): string
111
    {
112
        // Only include events of close relatives that are between birth and death
113
        $min_date = $individual->getEstimatedBirthDate();
114
        $max_date = $individual->getEstimatedDeathDate();
115
116
        // Which facts and events are handled by other modules?
117
        $sidebar_facts = $this->module_service
118
            ->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user())
119
            ->map(static function (ModuleSidebarInterface $sidebar): Collection {
120
                return $sidebar->supportedFacts();
121
            });
122
123
        $tab_facts = $this->module_service
124
            ->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user())
125
            ->map(static function (ModuleTabInterface $sidebar): Collection {
126
                return $sidebar->supportedFacts();
127
            });
128
129
        $exclude_facts = $sidebar_facts->merge($tab_facts)->flatten();
130
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());
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, $exclude_facts),
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      $type
220
     *
221
     * @return Fact
222
     */
223
    private function convertEvent(Fact $fact, $type): Fact {
224
        $gedcom = $fact->gedcom();
225
        $gedcom = preg_replace('/\n2 TYPE .*/', '', $gedcom);
226
        $gedcom = preg_replace('/^1 .*/', "1 EVEN CLOSE_RELATIVE\n2 TYPE " . $type, $gedcom);
227
228
        return new Fact($gedcom, $fact->record(), $fact->id());
229
    }
230
231
    /**
232
     * Spouse facts that are shown on an individual’s page.
233
     *
234
     * @param Individual $individual Show events that occured during the lifetime of this individual
235
     * @param Individual $spouse     Show events of this individual
236
     * @param Date       $min_date
237
     * @param Date       $max_date
238
     *
239
     * @return Fact[]
240
     */
241
    private function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): array
242
    {
243
        $SHOW_RELATIVES_EVENTS = $individual->tree()->getPreference('SHOW_RELATIVES_EVENTS');
244
245
        $death_of_a_spouse = [
246
            'DEAT' => [
247
                'M' => I18N::translate('Death of a husband'),
248
                'F' => I18N::translate('Death of a wife'),
249
                'U' => I18N::translate('Death of a spouse'),
250
            ],
251
            'BURI' => [
252
                'M' => I18N::translate('Burial of a husband'),
253
                'F' => I18N::translate('Burial of a wife'),
254
                'U' => I18N::translate('Burial of a spouse'),
255
            ],
256
            'CREM' => [
257
                'M' => I18N::translate('Cremation of a husband'),
258
                'F' => I18N::translate('Cremation of a wife'),
259
                'U' => I18N::translate('Cremation of a spouse'),
260
            ],
261
        ];
262
263
        $facts = [];
264
265
        if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU') !== false) {
266
            foreach ($spouse->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
267
                if ($this->includeFact($fact, $min_date, $max_date)) {
268
                    $facts[] = $this->convertEvent($fact, $death_of_a_spouse[$fact->getTag()][$fact->record()->sex()]);
269
                }
270
            }
271
        }
272
273
        return $facts;
274
    }
275
276
    /**
277
     * Get the events of children and grandchildren.
278
     *
279
     * @param Individual $person
280
     * @param Family     $family
281
     * @param string     $option
282
     * @param string     $relation
283
     * @param Date       $min_date
284
     * @param Date       $max_date
285
     *
286
     * @return Fact[]
287
     */
288
    private function childFacts(Individual $person, Family $family, $option, $relation, Date $min_date, Date $max_date): array
289
    {
290
        $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS');
291
292
        $birth_of_a_child = [
293
            'BIRT' => [
294
                'M' => I18N::translate('Birth of a son'),
295
                'F' => I18N::translate('Birth of a daughter'),
296
                'U' => I18N::translate('Birth of a child'),
297
            ],
298
            'CHR' => [
299
                'M' => I18N::translate('Christening of a son'),
300
                'F' => I18N::translate('Christening of a daughter'),
301
                'U' => I18N::translate('Christening of a child'),
302
            ],
303
            'BAPM' => [
304
                'M' => I18N::translate('Baptism of a son'),
305
                'F' => I18N::translate('Baptism of a daughter'),
306
                'U' => I18N::translate('Baptism of a child'),
307
            ],
308
            'ADOP' => [
309
                'M' => I18N::translate('Adoption of a son'),
310
                'F' => I18N::translate('Adoption of a daughter'),
311
                'U' => I18N::translate('Adoption of a child'),
312
            ],
313
        ];
314
315
        $birth_of_a_sibling = [
316
            'BIRT' => [
317
                'M' => I18N::translate('Birth of a brother'),
318
                'F' => I18N::translate('Birth of a sister'),
319
                'U' => I18N::translate('Birth of a sibling'),
320
            ],
321
            'CHR' => [
322
                'M' => I18N::translate('Christening of a brother'),
323
                'F' => I18N::translate('Christening of a sister'),
324
                'U' => I18N::translate('Christening of a sibling'),
325
            ],
326
            'BAPM' => [
327
                'M' => I18N::translate('Baptism of a brother'),
328
                'F' => I18N::translate('Baptism of a sister'),
329
                'U' => I18N::translate('Baptism of a sibling'),
330
            ],
331
            'ADOP' => [
332
                'M' => I18N::translate('Adoption of a brother'),
333
                'F' => I18N::translate('Adoption of a sister'),
334
                'U' => I18N::translate('Adoption of a sibling'),
335
            ],
336
        ];
337
338
        $birth_of_a_half_sibling = [
339
            'BIRT' => [
340
                'M' => I18N::translate('Birth of a half-brother'),
341
                'F' => I18N::translate('Birth of a half-sister'),
342
                'U' => I18N::translate('Birth of a half-sibling'),
343
            ],
344
            'CHR' => [
345
                'M' => I18N::translate('Christening of a half-brother'),
346
                'F' => I18N::translate('Christening of a half-sister'),
347
                'U' => I18N::translate('Christening of a half-sibling'),
348
            ],
349
            'BAPM' => [
350
                'M' => I18N::translate('Baptism of a half-brother'),
351
                'F' => I18N::translate('Baptism of a half-sister'),
352
                'U' => I18N::translate('Baptism of a half-sibling'),
353
            ],
354
            'ADOP' => [
355
                'M' => I18N::translate('Adoption of a half-brother'),
356
                'F' => I18N::translate('Adoption of a half-sister'),
357
                'U' => I18N::translate('Adoption of a half-sibling'),
358
            ],
359
        ];
360
361
        $birth_of_a_grandchild = [
362
            'BIRT' => [
363
                'M' => I18N::translate('Birth of a grandson'),
364
                'F' => I18N::translate('Birth of a granddaughter'),
365
                'U' => I18N::translate('Birth of a grandchild'),
366
            ],
367
            'CHR' => [
368
                'M' => I18N::translate('Christening of a grandson'),
369
                'F' => I18N::translate('Christening of a granddaughter'),
370
                'U' => I18N::translate('Christening of a grandchild'),
371
            ],
372
            'BAPM' => [
373
                'M' => I18N::translate('Baptism of a grandson'),
374
                'F' => I18N::translate('Baptism of a granddaughter'),
375
                'U' => I18N::translate('Baptism of a grandchild'),
376
            ],
377
            'ADOP' => [
378
                'M' => I18N::translate('Adoption of a grandson'),
379
                'F' => I18N::translate('Adoption of a granddaughter'),
380
                'U' => I18N::translate('Adoption of a grandchild'),
381
            ],
382
        ];
383
384
        $birth_of_a_grandchild1 = [
385
            'BIRT' => [
386
                'M' => I18N::translateContext('daughter’s son', 'Birth of a grandson'),
387
                'F' => I18N::translateContext('daughter’s daughter', 'Birth of a granddaughter'),
388
                'U' => I18N::translate('Birth of a grandchild'),
389
            ],
390
            'CHR' => [
391
                'M' => I18N::translateContext('daughter’s son', 'Christening of a grandson'),
392
                'F' => I18N::translateContext('daughter’s daughter', 'Christening of a granddaughter'),
393
                'U' => I18N::translate('Christening of a grandchild'),
394
            ],
395
            'BAPM' => [
396
                'M' => I18N::translateContext('daughter’s son', 'Baptism of a grandson'),
397
                'F' => I18N::translateContext('daughter’s daughter', 'Baptism of a granddaughter'),
398
                'U' => I18N::translate('Baptism of a grandchild'),
399
            ],
400
            'ADOP' => [
401
                'M' => I18N::translateContext('daughter’s son', 'Adoption of a grandson'),
402
                'F' => I18N::translateContext('daughter’s daughter', 'Adoption of a granddaughter'),
403
                'U' => I18N::translate('Adoption of a grandchild'),
404
            ],
405
        ];
406
407
        $birth_of_a_grandchild2 = [
408
            'BIRT' => [
409
                'M' => I18N::translateContext('son‘s son', 'Birth of a grandson'),
410
                'F' => I18N::translateContext('son‘s daughter', 'Birth of a granddaughter'),
411
                'U' => I18N::translate('Birth of a grandchild'),
412
            ],
413
            'CHR' => [
414
                'M' => I18N::translateContext('son‘s son', 'Christening of a grandson'),
415
                'F' => I18N::translateContext('son‘s daughter', 'Christening of a granddaughter'),
416
                'U' => I18N::translate('Christening of a grandchild'),
417
            ],
418
            'BAPM' => [
419
                'M' => I18N::translateContext('son‘s son', 'Baptism of a grandson'),
420
                'F' => I18N::translateContext('son‘s daughter', 'Baptism of a granddaughter'),
421
                'U' => I18N::translate('Baptism of a grandchild'),
422
            ],
423
            'ADOP' => [
424
                'M' => I18N::translateContext('son‘s son', 'Adoption of a grandson'),
425
                'F' => I18N::translateContext('son‘s daughter', 'Adoption of a granddaughter'),
426
                'U' => I18N::translate('Adoption of a grandchild'),
427
            ],
428
        ];
429
430
        $death_of_a_child = [
431
            'DEAT' => [
432
                'M' => I18N::translate('Death of a son'),
433
                'F' => I18N::translate('Death of a daughter'),
434
                'U' => I18N::translate('Death of a child'),
435
            ],
436
            'BURI' => [
437
                'M' => I18N::translate('Burial of a son'),
438
                'F' => I18N::translate('Burial of a daughter'),
439
                'U' => I18N::translate('Burial of a child'),
440
            ],
441
            'CREM' => [
442
                'M' => I18N::translate('Cremation of a son'),
443
                'F' => I18N::translate('Cremation of a daughter'),
444
                'U' => I18N::translate('Cremation of a child'),
445
            ],
446
        ];
447
448
        $death_of_a_sibling = [
449
            'DEAT' => [
450
                'M' => I18N::translate('Death of a brother'),
451
                'F' => I18N::translate('Death of a sister'),
452
                'U' => I18N::translate('Death of a sibling'),
453
            ],
454
            'BURI' => [
455
                'M' => I18N::translate('Burial of a brother'),
456
                'F' => I18N::translate('Burial of a sister'),
457
                'U' => I18N::translate('Burial of a sibling'),
458
            ],
459
            'CREM' => [
460
                'M' => I18N::translate('Cremation of a brother'),
461
                'F' => I18N::translate('Cremation of a sister'),
462
                'U' => I18N::translate('Cremation of a sibling'),
463
            ],
464
        ];
465
466
        $death_of_a_half_sibling = [
467
            'DEAT' => [
468
                'M' => I18N::translate('Death of a half-brother'),
469
                'F' => I18N::translate('Death of a half-sister'),
470
                'U' => I18N::translate('Death of a half-sibling'),
471
            ],
472
            'BURI' => [
473
                'M' => I18N::translate('Burial of a half-brother'),
474
                'F' => I18N::translate('Burial of a half-sister'),
475
                'U' => I18N::translate('Burial of a half-sibling'),
476
            ],
477
            'CREM' => [
478
                'M' => I18N::translate('Cremation of a half-brother'),
479
                'F' => I18N::translate('Cremation of a half-sister'),
480
                'U' => I18N::translate('Cremation of a half-sibling'),
481
            ],
482
        ];
483
484
        $death_of_a_grandchild = [
485
            'DEAT' => [
486
                'M' => I18N::translate('Death of a grandson'),
487
                'F' => I18N::translate('Death of a granddaughter'),
488
                'U' => I18N::translate('Death of a grandchild'),
489
            ],
490
            'BURI' => [
491
                'M' => I18N::translate('Burial of a grandson'),
492
                'F' => I18N::translate('Burial of a granddaughter'),
493
                'U' => I18N::translate('Burial of a grandchild'),
494
            ],
495
            'CREM' => [
496
                'M' => I18N::translate('Cremation of a grandson'),
497
                'F' => I18N::translate('Cremation of a granddaughter'),
498
                'U' => I18N::translate('Baptism of a grandchild'),
499
            ],
500
        ];
501
502
        $death_of_a_grandchild1 = [
503
            'DEAT' => [
504
                'M' => I18N::translateContext('daughter’s son', 'Death of a grandson'),
505
                'F' => I18N::translateContext('daughter’s daughter', 'Death of a granddaughter'),
506
                'U' => I18N::translate('Death of a grandchild'),
507
            ],
508
            'BURI' => [
509
                'M' => I18N::translateContext('daughter’s son', 'Burial of a grandson'),
510
                'F' => I18N::translateContext('daughter’s daughter', 'Burial of a granddaughter'),
511
                'U' => I18N::translate('Burial of a grandchild'),
512
            ],
513
            'CREM' => [
514
                'M' => I18N::translateContext('daughter’s son', 'Cremation of a grandson'),
515
                'F' => I18N::translateContext('daughter’s daughter', 'Cremation of a granddaughter'),
516
                'U' => I18N::translate('Baptism of a grandchild'),
517
            ],
518
        ];
519
520
        $death_of_a_grandchild2 = [
521
            'DEAT' => [
522
                'M' => I18N::translateContext('son‘s son', 'Death of a grandson'),
523
                'F' => I18N::translateContext('son‘s daughter', 'Death of a granddaughter'),
524
                'U' => I18N::translate('Death of a grandchild'),
525
            ],
526
            'BURI' => [
527
                'M' => I18N::translateContext('son‘s son', 'Burial of a grandson'),
528
                'F' => I18N::translateContext('son‘s daughter', 'Burial of a granddaughter'),
529
                'U' => I18N::translate('Burial of a grandchild'),
530
            ],
531
            'CREM' => [
532
                'M' => I18N::translateContext('son‘s son', 'Cremation of a grandson'),
533
                'F' => I18N::translateContext('son‘s daughter', 'Cremation of a granddaughter'),
534
                'U' => I18N::translate('Cremation of a grandchild'),
535
            ],
536
        ];
537
538
        $marriage_of_a_child = [
539
            'M' => I18N::translate('Marriage of a son'),
540
            'F' => I18N::translate('Marriage of a daughter'),
541
            'U' => I18N::translate('Marriage of a child'),
542
        ];
543
544
        $marriage_of_a_grandchild = [
545
            'M' => I18N::translate('Marriage of a grandson'),
546
            'F' => I18N::translate('Marriage of a granddaughter'),
547
            'U' => I18N::translate('Marriage of a grandchild'),
548
        ];
549
550
        $marriage_of_a_grandchild1 = [
551
            'M' => I18N::translateContext('daughter’s son', 'Marriage of a grandson'),
552
            'F' => I18N::translateContext('daughter’s daughter', 'Marriage of a granddaughter'),
553
            'U' => I18N::translate('Marriage of a grandchild'),
554
        ];
555
556
        $marriage_of_a_grandchild2 = [
557
            'M' => I18N::translateContext('son‘s son', 'Marriage of a grandson'),
558
            'F' => I18N::translateContext('son‘s daughter', 'Marriage of a granddaughter'),
559
            'U' => I18N::translate('Marriage of a grandchild'),
560
        ];
561
562
        $marriage_of_a_sibling = [
563
            'M' => I18N::translate('Marriage of a brother'),
564
            'F' => I18N::translate('Marriage of a sister'),
565
            'U' => I18N::translate('Marriage of a sibling'),
566
        ];
567
568
        $marriage_of_a_half_sibling = [
569
            'M' => I18N::translate('Marriage of a half-brother'),
570
            'F' => I18N::translate('Marriage of a half-sister'),
571
            'U' => I18N::translate('Marriage of a half-sibling'),
572
        ];
573
574
        $facts = [];
575
576
        // Deal with recursion.
577
        switch ($option) {
578
            case '_CHIL':
579
                // Add grandchildren
580
                foreach ($family->children() as $child) {
581
                    foreach ($child->spouseFamilies() as $cfamily) {
582
                        switch ($child->sex()) {
583
                            case 'M':
584
                                foreach ($this->childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) {
585
                                    $facts[] = $fact;
586
                                }
587
                                break;
588
                            case 'F':
589
                                foreach ($this->childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) {
590
                                    $facts[] = $fact;
591
                                }
592
                                break;
593
                            default:
594
                                foreach ($this->childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) {
595
                                    $facts[] = $fact;
596
                                }
597
                                break;
598
                        }
599
                    }
600
                }
601
                break;
602
        }
603
604
        // For each child in the family
605
        foreach ($family->children() as $child) {
606
            if ($child->xref() === $person->xref()) {
607
                // We are not our own sibling!
608
                continue;
609
            }
610
            // add child’s birth
611
            if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
612
                foreach ($child->facts(['BIRT', 'CHR', 'BAPM', 'ADOP']) as $fact) {
613
                    // Always show _BIRT_CHIL, even if the dates are not known
614
                    if ($option === '_CHIL' || $this->includeFact($fact, $min_date, $max_date)) {
615
                        switch ($option) {
616
                            case '_GCHI':
617
                                switch ($relation) {
618
                                    case 'dau':
619
                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild1[$fact->getTag()][$fact->record()->sex()]);
620
                                        break;
621
                                    case 'son':
622
                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild2[$fact->getTag()][$fact->record()->sex()]);
623
                                        break;
624
                                    case 'chil':
625
                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild[$fact->getTag()][$fact->record()->sex()]);
626
                                        break;
627
                                }
628
                                break;
629
                            case '_SIBL':
630
                                $facts[] = $this->convertEvent($fact, $birth_of_a_sibling[$fact->getTag()][$fact->record()->sex()]);
631
                                break;
632
                            case '_HSIB':
633
                                $facts[] = $this->convertEvent($fact, $birth_of_a_half_sibling[$fact->getTag()][$fact->record()->sex()]);
634
                                break;
635
                            case '_CHIL':
636
                                $facts[] = $this->convertEvent($fact, $birth_of_a_child[$fact->getTag()][$fact->record()->sex()]);
637
                                break;
638
                        }
639
                    }
640
                }
641
            }
642
            // add child’s death
643
            if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
644
                foreach ($child->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
645
                    if ($this->includeFact($fact, $min_date, $max_date)) {
646
                        switch ($option) {
647
                            case '_GCHI':
648
                                switch ($relation) {
649
                                    case 'dau':
650
                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild1[$fact->getTag()][$fact->record()->sex()]);
651
                                        break;
652
                                    case 'son':
653
                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild2[$fact->getTag()][$fact->record()->sex()]);
654
                                        break;
655
                                    case 'chi':
656
                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild[$fact->getTag()][$fact->record()->sex()]);
657
                                        break;
658
                                }
659
                                break;
660
                            case '_SIBL':
661
                                $facts[] = $this->convertEvent($fact, $death_of_a_sibling[$fact->getTag()][$fact->record()->sex()]);
662
                                break;
663
                            case '_HSIB':
664
                                $facts[] = $this->convertEvent($fact, $death_of_a_half_sibling[$fact->getTag()][$fact->record()->sex()]);
665
                                break;
666
                            case 'CHIL':
667
                                $facts[] = $this->convertEvent($fact, $death_of_a_child[$fact->getTag()][$fact->record()->sex()]);
668
                                break;
669
                        }
670
                    }
671
                }
672
            }
673
674
            // add child’s marriage
675
            if (strpos($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
676
                foreach ($child->spouseFamilies() as $sfamily) {
677
                    foreach ($sfamily->facts(['MARR']) as $fact) {
678
                        if ($this->includeFact($fact, $min_date, $max_date)) {
679
                            switch ($option) {
680
                                case '_GCHI':
681
                                    switch ($relation) {
682
                                        case 'dau':
683
                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild1['F']);
684
                                            break;
685
                                        case 'son':
686
                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild2['M']);
687
                                            break;
688
                                        case 'chi':
689
                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild['U']);
690
                                            break;
691
                                    }
692
                                    break;
693
                                case '_SIBL':
694
                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_sibling['U']);
695
                                    break;
696
                                case '_HSIB':
697
                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_half_sibling['U']);
698
                                    break;
699
                                case '_CHIL':
700
                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_child['U']);
701
                                    break;
702
                            }
703
                        }
704
                    }
705
                }
706
            }
707
        }
708
709
        return $facts;
710
    }
711
712
    /**
713
     * Get the events of parents and grandparents.
714
     *
715
     * @param Individual $person
716
     * @param int        $sosa
717
     * @param Date       $min_date
718
     * @param Date       $max_date
719
     *
720
     * @return Fact[]
721
     */
722
    private function parentFacts(Individual $person, $sosa, Date $min_date, Date $max_date): array
723
    {
724
        $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS');
725
726
        $death_of_a_parent = [
727
            'DEAT' => [
728
                'M' => I18N::translate('Death of a father'),
729
                'F' => I18N::translate('Death of a mother'),
730
                'U' => I18N::translate('Death of a parent'),
731
            ],
732
            'BURI' => [
733
                'M' => I18N::translate('Burial of a father'),
734
                'F' => I18N::translate('Burial of a mother'),
735
                'U' => I18N::translate('Burial of a parent'),
736
            ],
737
            'CREM' => [
738
                'M' => I18N::translate('Cremation of a father'),
739
                'F' => I18N::translate('Cremation of a mother'),
740
                'U' => I18N::translate('Cremation of a parent'),
741
            ],
742
        ];
743
744
        $death_of_a_grandparent = [
745
            'DEAT' => [
746
                'M' => I18N::translate('Death of a grandfather'),
747
                'F' => I18N::translate('Death of a grandmother'),
748
                'U' => I18N::translate('Death of a grandparent'),
749
            ],
750
            'BURI' => [
751
                'M' => I18N::translate('Burial of a grandfather'),
752
                'F' => I18N::translate('Burial of a grandmother'),
753
                'U' => I18N::translate('Burial of a grandparent'),
754
            ],
755
            'CREM' => [
756
                'M' => I18N::translate('Cremation of a grandfather'),
757
                'F' => I18N::translate('Cremation of a grandmother'),
758
                'U' => I18N::translate('Baptism of a grandparent'),
759
            ],
760
        ];
761
762
        $death_of_a_maternal_grandparent = [
763
            'DEAT' => [
764
                'M' => I18N::translateContext('mother’s father', 'Death of a grandfather'),
765
                'F' => I18N::translateContext('mother’s mother', 'Death of a grandmother'),
766
                'U' => I18N::translate('Death of a grandparent'),
767
            ],
768
            'BURI' => [
769
                'M' => I18N::translateContext('mother’s father', 'Burial of a grandfather'),
770
                'F' => I18N::translateContext('mother’s mother', 'Burial of a grandmother'),
771
                'U' => I18N::translate('Burial of a grandparent'),
772
            ],
773
            'CREM' => [
774
                'M' => I18N::translateContext('mother’s father', 'Cremation of a grandfather'),
775
                'F' => I18N::translateContext('mother’s mother', 'Cremation of a grandmother'),
776
                'U' => I18N::translate('Baptism of a grandparent'),
777
            ],
778
        ];
779
780
        $death_of_a_paternal_grandparent = [
781
            'DEAT' => [
782
                'M' => I18N::translateContext('father‘s father', 'Death of a grandfather'),
783
                'F' => I18N::translateContext('father‘s mother', 'Death of a grandmother'),
784
                'U' => I18N::translate('Death of a grandparent'),
785
            ],
786
            'BURI' => [
787
                'M' => I18N::translateContext('father‘s father', 'Burial of a grandfather'),
788
                'F' => I18N::translateContext('father‘s mother', 'Burial of a grandmother'),
789
                'U' => I18N::translate('Burial of a grandparent'),
790
            ],
791
            'CREM' => [
792
                'M' => I18N::translateContext('father‘s father', 'Cremation of a grandfather'),
793
                'F' => I18N::translateContext('father‘s mother', 'Cremation of a grandmother'),
794
                'U' => I18N::translate('Cremation of a grandparent'),
795
            ],
796
        ];
797
798
        $marriage_of_a_parent = [
799
            'M' => I18N::translate('Marriage of a father'),
800
            'F' => I18N::translate('Marriage of a mother'),
801
            'U' => I18N::translate('Marriage of a parent'),
802
        ];
803
804
        $facts = [];
805
806
        if ($sosa == 1) {
807
            foreach ($person->childFamilies() as $family) {
808
                // Add siblings
809
                foreach ($this->childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) {
810
                    $facts[] = $fact;
811
                }
812
                foreach ($family->spouses() as $spouse) {
813
                    foreach ($spouse->spouseFamilies() as $sfamily) {
814
                        if ($family !== $sfamily) {
815
                            // Add half-siblings
816
                            foreach ($this->childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) {
817
                                $facts[] = $fact;
818
                            }
819
                        }
820
                    }
821
                    // Add grandparents
822
                    foreach ($this->parentFacts($spouse, $spouse->sex() === 'F' ? 3 : 2, $min_date, $max_date) as $fact) {
823
                        $facts[] = $fact;
824
                    }
825
                }
826
            }
827
828
            if (strpos($SHOW_RELATIVES_EVENTS, '_MARR_PARE') !== false) {
829
                // add father/mother marriages
830
                foreach ($person->childFamilies() as $sfamily) {
831
                    foreach ($sfamily->facts(['MARR']) as $fact) {
832
                        if ($this->includeFact($fact, $min_date, $max_date)) {
833
                            // marriage of parents (to each other)
834
                            $facts[] = $this->convertEvent($fact, I18N::translate('Marriage of parents'));
835
                        }
836
                    }
837
                }
838
                foreach ($person->childStepFamilies() as $sfamily) {
839
                    foreach ($sfamily->facts(['MARR']) as $fact) {
840
                        if ($this->includeFact($fact, $min_date, $max_date)) {
841
                            // marriage of a parent (to another spouse)
842
                            $facts[] = $this->convertEvent($fact, $marriage_of_a_parent['U']);
843
                        }
844
                    }
845
                }
846
            }
847
        }
848
849
        foreach ($person->childFamilies() as $family) {
850
            foreach ($family->spouses() as $parent) {
851
                if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa === 1 ? '_PARE' : '_GPAR')) !== false) {
852
                    foreach ($parent->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
853
                        if ($this->includeFact($fact, $min_date, $max_date)) {
854
                            switch ($sosa) {
855
                                case 1:
856
                                    $facts[] = $this->convertEvent($fact, $death_of_a_parent[$fact->getTag()][$fact->record()->sex()]);
857
                                    break;
858
                                case 2:
859
                                case 3:
860
                                    switch ($parent->sex()) {
861
                                        case 'M':
862
                                            $facts[] = $this->convertEvent($fact, $death_of_a_paternal_grandparent[$fact->getTag()][$fact->record()->sex()]);
863
                                            break;
864
                                        case 'F':
865
                                            $facts[] = $this->convertEvent($fact, $death_of_a_maternal_grandparent[$fact->getTag()][$fact->record()->sex()]);
866
                                            break;
867
                                        default:
868
                                            $facts[] = $this->convertEvent($fact, $death_of_a_grandparent[$fact->getTag()][$fact->record()->sex()]);
869
                                            break;
870
                                    }
871
                            }
872
                        }
873
                    }
874
                }
875
            }
876
        }
877
878
        return $facts;
879
    }
880
881
    /**
882
     * Get any historical events.
883
     *
884
     * @param Individual $individual
885
     *
886
     * @return Fact[]
887
     */
888
    private function historicalFacts(Individual $individual): array
889
    {
890
        return $this->module_service->findByInterface(ModuleHistoricEventsInterface::class)
891
            ->map(static function (ModuleHistoricEventsInterface $module) use ($individual): Collection {
892
                return $module->historicEventsForIndividual($individual);
893
            })
894
            ->flatten()
895
            ->all();
896
    }
897
898
    /**
899
     * Get the events of associates.
900
     *
901
     * @param Individual $person
902
     *
903
     * @return Fact[]
904
     */
905
    private function associateFacts(Individual $person): array
906
    {
907
        $facts = [];
908
909
        /** @var Individual[] $associates */
910
        $asso1 = $person->linkedIndividuals('ASSO');
911
        $asso2 = $person->linkedIndividuals('_ASSO');
912
        $asso3 = $person->linkedFamilies('ASSO');
913
        $asso4 = $person->linkedFamilies('_ASSO');
914
915
        $associates = $asso1->merge($asso2)->merge($asso3)->merge($asso4);
916
917
        foreach ($associates as $associate) {
918
            foreach ($associate->facts() as $fact) {
919
                if (preg_match('/\n\d _?ASSO @' . $person->xref() . '@/', $fact->gedcom())) {
920
                    // Extract the important details from the fact
921
                    $factrec = '1 ' . $fact->getTag();
922
                    if (preg_match('/\n2 DATE .*/', $fact->gedcom(), $match)) {
923
                        $factrec .= $match[0];
924
                    }
925
                    if (preg_match('/\n2 PLAC .*/', $fact->gedcom(), $match)) {
926
                        $factrec .= $match[0];
927
                    }
928
                    if ($associate instanceof Family) {
929
                        foreach ($associate->spouses() as $spouse) {
930
                            $factrec .= "\n2 _ASSO @" . $spouse->xref() . '@';
931
                        }
932
                    } else {
933
                        $factrec .= "\n2 _ASSO @" . $associate->xref() . '@';
934
                    }
935
                    $facts[] = new Fact($factrec, $associate, 'asso');
936
                }
937
            }
938
        }
939
940
        return $facts;
941
    }
942
943
    /**
944
     * This module handles the following facts - so don't show them on the "Facts and events" tab.
945
     *
946
     * @return Collection<string>
947
     */
948
    public function supportedFacts(): Collection
949
    {
950
        // We don't actually displaye these facts, but they are displayed
951
        // outside the tabs/sidebar systems. This just forces them to be excluded here.
952
        return new Collection(['NAME', 'SEX']);
953
    }
954
}
955