Completed
Push — develop ( 778c00...2ca406 )
by Greg
24:21 queued 15:55
created

IndividualFactsTabModule::historicalFacts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2021 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
    private ModuleService $module_service;
42
43
    private ClipboardService $clipboard_service;
44
45
    /**
46
     * IndividualFactsTabModule constructor.
47
     *
48
     * @param ModuleService    $module_service
49
     * @param ClipboardService $clipboard_service
50
     */
51
    public function __construct(ModuleService $module_service, ClipboardService $clipboard_service)
52
    {
53
        $this->module_service    = $module_service;
54
        $this->clipboard_service = $clipboard_service;
55
    }
56
57
    /**
58
     * How should this module be identified in the control panel, etc.?
59
     *
60
     * @return string
61
     */
62
    public function title(): string
63
    {
64
        /* I18N: Name of a module/tab on the individual page. */
65
        return I18N::translate('Facts and events');
66
    }
67
68
    /**
69
     * A sentence describing what this module does.
70
     *
71
     * @return string
72
     */
73
    public function description(): string
74
    {
75
        /* I18N: Description of the “Facts and events” module */
76
        return I18N::translate('A tab showing the facts and events of an individual.');
77
    }
78
79
    /**
80
     * The default position for this tab.  It can be changed in the control panel.
81
     *
82
     * @return int
83
     */
84
    public function defaultTabOrder(): int
85
    {
86
        return 1;
87
    }
88
89
    /**
90
     * A greyed out tab has no actual content, but may perhaps have
91
     * options to create content.
92
     *
93
     * @param Individual $individual
94
     *
95
     * @return bool
96
     */
97
    public function isGrayedOut(Individual $individual): bool
98
    {
99
        return false;
100
    }
101
102
    /**
103
     * Generate the HTML content of this tab.
104
     *
105
     * @param Individual $individual
106
     *
107
     * @return string
108
     */
109
    public function getTabContent(Individual $individual): string
110
    {
111
        // Only include events of close relatives that are between birth and death
112
        $min_date = $individual->getEstimatedBirthDate();
113
        $max_date = $individual->getEstimatedDeathDate();
114
115
        // Which facts and events are handled by other modules?
116
        $sidebar_facts = $this->module_service
117
            ->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user())
118
            ->map(static function (ModuleSidebarInterface $sidebar): Collection {
119
                return $sidebar->supportedFacts();
120
            });
121
122
        $tab_facts = $this->module_service
123
            ->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user())
124
            ->map(static function (ModuleTabInterface $sidebar): Collection {
125
                return $sidebar->supportedFacts();
126
            });
127
128
        $exclude_facts = $sidebar_facts->merge($tab_facts)->flatten();
129
130
        // The individual’s own facts
131
        $individual_facts = $individual->facts()
132
            ->filter(static function (Fact $fact) use ($exclude_facts): bool {
133
                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

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