xref: /webtrees/app/Module/IndividualFactsTabModule.php (revision 5bfc689774bb9a6401271c4ed15a6d50652c991b)
13763c3f2SGreg Roach<?php
23976b470SGreg Roach
33763c3f2SGreg Roach/**
43763c3f2SGreg Roach * webtrees: online genealogy
5*5bfc6897SGreg Roach * Copyright (C) 2022 webtrees development team
63763c3f2SGreg Roach * This program is free software: you can redistribute it and/or modify
73763c3f2SGreg Roach * it under the terms of the GNU General Public License as published by
83763c3f2SGreg Roach * the Free Software Foundation, either version 3 of the License, or
93763c3f2SGreg Roach * (at your option) any later version.
103763c3f2SGreg Roach * This program is distributed in the hope that it will be useful,
113763c3f2SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
123763c3f2SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
133763c3f2SGreg Roach * GNU General Public License for more details.
143763c3f2SGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
163763c3f2SGreg Roach */
17fcfa147eSGreg Roach
18e7f56f2aSGreg Roachdeclare(strict_types=1);
19e7f56f2aSGreg Roach
2076692c8bSGreg Roachnamespace Fisharebest\Webtrees\Module;
2176692c8bSGreg Roach
2263276d8fSGreg Roachuse Fisharebest\Webtrees\Auth;
230e62c4b8SGreg Roachuse Fisharebest\Webtrees\Date;
240e62c4b8SGreg Roachuse Fisharebest\Webtrees\Fact;
250e62c4b8SGreg Roachuse Fisharebest\Webtrees\Family;
260e62c4b8SGreg Roachuse Fisharebest\Webtrees\I18N;
270e62c4b8SGreg Roachuse Fisharebest\Webtrees\Individual;
282adcbd9aSGreg Roachuse Fisharebest\Webtrees\Services\ClipboardService;
294991f205SGreg Roachuse Fisharebest\Webtrees\Services\LinkedRecordService;
304ca7e03cSGreg Roachuse Fisharebest\Webtrees\Services\ModuleService;
3117c50b57SGreg Roachuse Illuminate\Support\Collection;
323763c3f2SGreg Roach
33d0889c63SGreg Roachuse function explode;
34d0889c63SGreg Roachuse function preg_match;
35d0889c63SGreg Roachuse function preg_replace;
36dec352c1SGreg Roachuse function str_contains;
37d0889c63SGreg Roachuse function str_replace;
38d0889c63SGreg Roachuse function view;
39dec352c1SGreg Roach
403763c3f2SGreg Roach/**
413763c3f2SGreg Roach * Class IndividualFactsTabModule
423763c3f2SGreg Roach */
4337eb8894SGreg Roachclass IndividualFactsTabModule extends AbstractModule implements ModuleTabInterface
44c1010edaSGreg Roach{
4549a243cbSGreg Roach    use ModuleTabTrait;
4649a243cbSGreg Roach
4791a257a4SGreg Roach    private ClipboardService $clipboard_service;
482adcbd9aSGreg Roach
494991f205SGreg Roach    private LinkedRecordService $linked_record_service;
504991f205SGreg Roach
514991f205SGreg Roach    private ModuleService $module_service;
524991f205SGreg Roach
534ca7e03cSGreg Roach    /**
540874af26SRichard Cissée     * IndividualFactsTabModule constructor.
554ca7e03cSGreg Roach     *
564ca7e03cSGreg Roach     * @param ModuleService       $module_service
574991f205SGreg Roach     * @param LinkedRecordService $linked_record_service
582adcbd9aSGreg Roach     * @param ClipboardService    $clipboard_service
594ca7e03cSGreg Roach     */
604991f205SGreg Roach    public function __construct(ModuleService $module_service, LinkedRecordService $linked_record_service, ClipboardService $clipboard_service)
615bdbe281SGreg Roach    {
622adcbd9aSGreg Roach        $this->clipboard_service     = $clipboard_service;
634991f205SGreg Roach        $this->linked_record_service = $linked_record_service;
644991f205SGreg Roach        $this->module_service        = $module_service;
654ca7e03cSGreg Roach    }
664ca7e03cSGreg Roach
674ca7e03cSGreg Roach    /**
680cfd6963SGreg Roach     * How should this module be identified in the control panel, etc.?
69961ec755SGreg Roach     *
70961ec755SGreg Roach     * @return string
71961ec755SGreg Roach     */
7249a243cbSGreg Roach    public function title(): string
73c1010edaSGreg Roach    {
74bbb76c12SGreg Roach        /* I18N: Name of a module/tab on the individual page. */
75bbb76c12SGreg Roach        return I18N::translate('Facts and events');
763763c3f2SGreg Roach    }
773763c3f2SGreg Roach
78961ec755SGreg Roach    /**
79961ec755SGreg Roach     * A sentence describing what this module does.
80961ec755SGreg Roach     *
81961ec755SGreg Roach     * @return string
82961ec755SGreg Roach     */
8349a243cbSGreg Roach    public function description(): string
84c1010edaSGreg Roach    {
85bbb76c12SGreg Roach        /* I18N: Description of the “Facts and events” module */
86bbb76c12SGreg Roach        return I18N::translate('A tab showing the facts and events of an individual.');
873763c3f2SGreg Roach    }
883763c3f2SGreg Roach
8949a243cbSGreg Roach    /**
9049a243cbSGreg Roach     * The default position for this tab.  It can be changed in the control panel.
9149a243cbSGreg Roach     *
9249a243cbSGreg Roach     * @return int
9349a243cbSGreg Roach     */
94cbf4b7faSGreg Roach    public function defaultTabOrder(): int
95cbf4b7faSGreg Roach    {
96fb7a0427SGreg Roach        return 1;
973763c3f2SGreg Roach    }
983763c3f2SGreg Roach
993caaa4d2SGreg Roach    /**
1003caaa4d2SGreg Roach     * A greyed out tab has no actual content, but may perhaps have
1013caaa4d2SGreg Roach     * options to create content.
1023caaa4d2SGreg Roach     *
1033caaa4d2SGreg Roach     * @param Individual $individual
1043caaa4d2SGreg Roach     *
1053caaa4d2SGreg Roach     * @return bool
1063caaa4d2SGreg Roach     */
1078f53f488SRico Sonntag    public function isGrayedOut(Individual $individual): bool
108c1010edaSGreg Roach    {
1093763c3f2SGreg Roach        return false;
1103763c3f2SGreg Roach    }
1113763c3f2SGreg Roach
1123caaa4d2SGreg Roach    /**
1133caaa4d2SGreg Roach     * Generate the HTML content of this tab.
1143caaa4d2SGreg Roach     *
1153caaa4d2SGreg Roach     * @param Individual $individual
1163caaa4d2SGreg Roach     *
1173caaa4d2SGreg Roach     * @return string
1183caaa4d2SGreg Roach     */
1199b34404bSGreg Roach    public function getTabContent(Individual $individual): string
120c1010edaSGreg Roach    {
121ee727175SGreg Roach        // Only include events of close relatives that are between birth and death
122ee727175SGreg Roach        $min_date = $individual->getEstimatedBirthDate();
123ee727175SGreg Roach        $max_date = $individual->getEstimatedDeathDate();
124ee727175SGreg Roach
1258eaf8709SGreg Roach        // Which facts and events are handled by other modules?
1268eaf8709SGreg Roach        $sidebar_facts = $this->module_service
12787cca37cSGreg Roach            ->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user())
128d0889c63SGreg Roach            ->map(fn (ModuleSidebarInterface $sidebar): Collection => $sidebar->supportedFacts());
1298eaf8709SGreg Roach
1308eaf8709SGreg Roach        $tab_facts = $this->module_service
13187cca37cSGreg Roach            ->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user())
132d0889c63SGreg Roach            ->map(fn (ModuleTabInterface $tab): Collection => $tab->supportedFacts());
1338eaf8709SGreg Roach
1348eaf8709SGreg Roach        $exclude_facts = $sidebar_facts->merge($tab_facts)->flatten();
1358eaf8709SGreg Roach
1363763c3f2SGreg Roach        // The individual’s own facts
13791a257a4SGreg Roach        $individual_facts = $individual->facts()
138d0889c63SGreg Roach            ->filter(fn (Fact $fact): bool => !$exclude_facts->contains($fact->tag()));
1393763c3f2SGreg Roach
14091a257a4SGreg Roach        $relative_facts = new Collection();
14191a257a4SGreg Roach
1423763c3f2SGreg Roach        // Add spouse-family facts
14339ca88baSGreg Roach        foreach ($individual->spouseFamilies() as $family) {
14430158ae7SGreg Roach            foreach ($family->facts() as $fact) {
145d0889c63SGreg Roach                if (!$exclude_facts->contains($fact->tag()) && $fact->tag() !== 'FAM:CHAN') {
14691a257a4SGreg Roach                    $relative_facts->push($fact);
1473763c3f2SGreg Roach                }
1483763c3f2SGreg Roach            }
149ee727175SGreg Roach
15039ca88baSGreg Roach            $spouse = $family->spouse($individual);
151ee727175SGreg Roach
152ee727175SGreg Roach            if ($spouse instanceof Individual) {
1538b9cfadbSGreg Roach                $spouse_facts   = $this->spouseFacts($individual, $spouse, $min_date, $max_date);
15491a257a4SGreg Roach                $relative_facts = $relative_facts->merge($spouse_facts);
1553763c3f2SGreg Roach            }
1563763c3f2SGreg Roach
1578b9cfadbSGreg Roach            $child_facts    = $this->childFacts($individual, $family, '_CHIL', '', $min_date, $max_date);
15891a257a4SGreg Roach            $relative_facts = $relative_facts->merge($child_facts);
1593763c3f2SGreg Roach        }
160225e381fSGreg Roach
1618b9cfadbSGreg Roach        $parent_facts    = $this->parentFacts($individual, 1, $min_date, $max_date);
16291a257a4SGreg Roach        $relative_facts  = $relative_facts->merge($parent_facts);
1638b9cfadbSGreg Roach        $associate_facts = $this->associateFacts($individual);
16491a257a4SGreg Roach        $historic_facts  = $this->historicFacts($individual);
165225e381fSGreg Roach
16691a257a4SGreg Roach        $individual_facts = $individual_facts
16739ca88baSGreg Roach            ->merge($associate_facts)
16891a257a4SGreg Roach            ->merge($historic_facts)
16991a257a4SGreg Roach            ->merge($relative_facts);
1703763c3f2SGreg Roach
17191a257a4SGreg Roach        $individual_facts = Fact::sortFacts($individual_facts);
1723763c3f2SGreg Roach
173a8cd57e1SGreg Roach        return view('modules/personal_facts/tab', [
174225e381fSGreg Roach            'can_edit'            => $individual->canEdit(),
17569cdf014SGreg Roach            'clipboard_facts'     => $this->clipboard_service->pastableFacts($individual),
17691a257a4SGreg Roach            'has_associate_facts' => $associate_facts->isNotEmpty(),
17791a257a4SGreg Roach            'has_historic_facts'  => $historic_facts->isNotEmpty(),
17891a257a4SGreg Roach            'has_relative_facts'  => $relative_facts->isNotEmpty(),
179225e381fSGreg Roach            'individual'          => $individual,
18091a257a4SGreg Roach            'facts'               => $individual_facts,
181225e381fSGreg Roach        ]);
1823763c3f2SGreg Roach    }
1833763c3f2SGreg Roach
184ee727175SGreg Roach    /**
18591a257a4SGreg Roach     * Spouse facts that are shown on an individual’s page.
18691a257a4SGreg Roach     *
18791a257a4SGreg Roach     * @param Individual $individual Show events that occured during the lifetime of this individual
18891a257a4SGreg Roach     * @param Individual $spouse     Show events of this individual
18991a257a4SGreg Roach     * @param Date       $min_date
19091a257a4SGreg Roach     * @param Date       $max_date
19191a257a4SGreg Roach     *
19236779af1SGreg Roach     * @return Collection<int,Fact>
19391a257a4SGreg Roach     */
19491a257a4SGreg Roach    private function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): Collection
19591a257a4SGreg Roach    {
19691a257a4SGreg Roach        $SHOW_RELATIVES_EVENTS = $individual->tree()->getPreference('SHOW_RELATIVES_EVENTS');
19791a257a4SGreg Roach
19891a257a4SGreg Roach        $death_of_a_spouse = [
199f7f3cc73SGreg Roach            'INDI:DEAT' => [
20091a257a4SGreg Roach                'M' => I18N::translate('Death of a husband'),
20191a257a4SGreg Roach                'F' => I18N::translate('Death of a wife'),
20291a257a4SGreg Roach                'U' => I18N::translate('Death of a spouse'),
20391a257a4SGreg Roach            ],
204f7f3cc73SGreg Roach            'INDI:BURI' => [
20591a257a4SGreg Roach                'M' => I18N::translate('Burial of a husband'),
20691a257a4SGreg Roach                'F' => I18N::translate('Burial of a wife'),
20791a257a4SGreg Roach                'U' => I18N::translate('Burial of a spouse'),
20891a257a4SGreg Roach            ],
209f7f3cc73SGreg Roach            'INDI:CREM' => [
21091a257a4SGreg Roach                'M' => I18N::translate('Cremation of a husband'),
21191a257a4SGreg Roach                'F' => I18N::translate('Cremation of a wife'),
21291a257a4SGreg Roach                'U' => I18N::translate('Cremation of a spouse'),
21391a257a4SGreg Roach            ],
21491a257a4SGreg Roach        ];
21591a257a4SGreg Roach
21691a257a4SGreg Roach        $facts = new Collection();
21791a257a4SGreg Roach
21891a257a4SGreg Roach        if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) {
21991a257a4SGreg Roach            foreach ($spouse->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
22091a257a4SGreg Roach                if ($this->includeFact($fact, $min_date, $max_date)) {
221d0889c63SGreg Roach                    $facts[] = $this->convertEvent($fact, $death_of_a_spouse[$fact->tag()][$fact->record()->sex()]);
22291a257a4SGreg Roach                }
22391a257a4SGreg Roach            }
22491a257a4SGreg Roach        }
22591a257a4SGreg Roach
22691a257a4SGreg Roach        return $facts;
22791a257a4SGreg Roach    }
22891a257a4SGreg Roach
22991a257a4SGreg Roach    /**
230ee727175SGreg Roach     * Does a relative event occur within a date range (i.e. the individual's lifetime)?
231ee727175SGreg Roach     *
232ee727175SGreg Roach     * @param Fact $fact
233ee727175SGreg Roach     * @param Date $min_date
234ee727175SGreg Roach     * @param Date $max_date
235ee727175SGreg Roach     *
236ee727175SGreg Roach     * @return bool
237ee727175SGreg Roach     */
2388b9cfadbSGreg Roach    private function includeFact(Fact $fact, Date $min_date, Date $max_date): bool
239b3b1d905SGreg Roach    {
2402decada7SGreg Roach        $fact_date = $fact->date();
241ee727175SGreg Roach
242ee727175SGreg Roach        return $fact_date->isOK() && Date::compare($min_date, $fact_date) <= 0 && Date::compare($fact_date, $max_date) <= 0;
243ee727175SGreg Roach    }
244ee727175SGreg Roach
2453caaa4d2SGreg Roach    /**
2467bf6ca81SGreg Roach     * Convert an event into a special "event of a close relative".
2477bf6ca81SGreg Roach     *
2487bf6ca81SGreg Roach     * @param Fact   $fact
2499e65d053SGreg Roach     * @param string $type
2507bf6ca81SGreg Roach     *
2517bf6ca81SGreg Roach     * @return Fact
2527bf6ca81SGreg Roach     */
2539e65d053SGreg Roach    private function convertEvent(Fact $fact, string $type): Fact
25499ed8541SGreg Roach    {
2557bf6ca81SGreg Roach        $gedcom = $fact->gedcom();
2567bf6ca81SGreg Roach        $gedcom = preg_replace('/\n2 TYPE .*/', '', $gedcom);
2577bf6ca81SGreg Roach        $gedcom = preg_replace('/^1 .*/', "1 EVEN CLOSE_RELATIVE\n2 TYPE " . $type, $gedcom);
2587bf6ca81SGreg Roach
2599cda3358SGreg Roach        $converted = new Fact($gedcom, $fact->record(), $fact->id());
2609cda3358SGreg Roach
2619cda3358SGreg Roach        if ($fact->isPendingAddition()) {
2629cda3358SGreg Roach            $converted->setPendingAddition();
2639cda3358SGreg Roach        }
2649cda3358SGreg Roach
2659cda3358SGreg Roach        if ($fact->isPendingDeletion()) {
2669cda3358SGreg Roach            $converted->setPendingDeletion();
2679cda3358SGreg Roach        }
2689cda3358SGreg Roach
2699cda3358SGreg Roach        return $converted;
2707bf6ca81SGreg Roach    }
2717bf6ca81SGreg Roach
2727bf6ca81SGreg Roach    /**
2733763c3f2SGreg Roach     * Get the events of children and grandchildren.
2743763c3f2SGreg Roach     *
2753763c3f2SGreg Roach     * @param Individual $person
2763763c3f2SGreg Roach     * @param Family     $family
2773763c3f2SGreg Roach     * @param string     $option
2783763c3f2SGreg Roach     * @param string     $relation
279ee727175SGreg Roach     * @param Date       $min_date
280ee727175SGreg Roach     * @param Date       $max_date
2813763c3f2SGreg Roach     *
28236779af1SGreg Roach     * @return Collection<int,Fact>
2833763c3f2SGreg Roach     */
28491a257a4SGreg Roach    private function childFacts(Individual $person, Family $family, string $option, string $relation, Date $min_date, Date $max_date): Collection
285c1010edaSGreg Roach    {
286f4afa648SGreg Roach        $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS');
2873763c3f2SGreg Roach
2887bf6ca81SGreg Roach        $birth_of_a_child = [
289d0889c63SGreg Roach            'INDI:BIRT' => [
2907bf6ca81SGreg Roach                'M' => I18N::translate('Birth of a son'),
2917bf6ca81SGreg Roach                'F' => I18N::translate('Birth of a daughter'),
2927bf6ca81SGreg Roach                'U' => I18N::translate('Birth of a child'),
2937bf6ca81SGreg Roach            ],
294d0889c63SGreg Roach            'INDI:CHR'  => [
2957bf6ca81SGreg Roach                'M' => I18N::translate('Christening of a son'),
2967bf6ca81SGreg Roach                'F' => I18N::translate('Christening of a daughter'),
2977bf6ca81SGreg Roach                'U' => I18N::translate('Christening of a child'),
2987bf6ca81SGreg Roach            ],
299d0889c63SGreg Roach            'INDI:BAPM' => [
3007bf6ca81SGreg Roach                'M' => I18N::translate('Baptism of a son'),
3017bf6ca81SGreg Roach                'F' => I18N::translate('Baptism of a daughter'),
3027bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a child'),
3037bf6ca81SGreg Roach            ],
304d0889c63SGreg Roach            'INDI:ADOP' => [
3057bf6ca81SGreg Roach                'M' => I18N::translate('Adoption of a son'),
3067bf6ca81SGreg Roach                'F' => I18N::translate('Adoption of a daughter'),
3077bf6ca81SGreg Roach                'U' => I18N::translate('Adoption of a child'),
3087bf6ca81SGreg Roach            ],
3097bf6ca81SGreg Roach        ];
3107bf6ca81SGreg Roach
3117bf6ca81SGreg Roach        $birth_of_a_sibling = [
312d0889c63SGreg Roach            'INDI:BIRT' => [
3137bf6ca81SGreg Roach                'M' => I18N::translate('Birth of a brother'),
3147bf6ca81SGreg Roach                'F' => I18N::translate('Birth of a sister'),
3157bf6ca81SGreg Roach                'U' => I18N::translate('Birth of a sibling'),
3167bf6ca81SGreg Roach            ],
317d0889c63SGreg Roach            'INDI:CHR'  => [
3187bf6ca81SGreg Roach                'M' => I18N::translate('Christening of a brother'),
3197bf6ca81SGreg Roach                'F' => I18N::translate('Christening of a sister'),
3207bf6ca81SGreg Roach                'U' => I18N::translate('Christening of a sibling'),
3217bf6ca81SGreg Roach            ],
322d0889c63SGreg Roach            'INDI:BAPM' => [
3237bf6ca81SGreg Roach                'M' => I18N::translate('Baptism of a brother'),
3247bf6ca81SGreg Roach                'F' => I18N::translate('Baptism of a sister'),
3257bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a sibling'),
3267bf6ca81SGreg Roach            ],
327d0889c63SGreg Roach            'INDI:ADOP' => [
3287bf6ca81SGreg Roach                'M' => I18N::translate('Adoption of a brother'),
3297bf6ca81SGreg Roach                'F' => I18N::translate('Adoption of a sister'),
3307bf6ca81SGreg Roach                'U' => I18N::translate('Adoption of a sibling'),
3317bf6ca81SGreg Roach            ],
3327bf6ca81SGreg Roach        ];
3337bf6ca81SGreg Roach
3347bf6ca81SGreg Roach        $birth_of_a_half_sibling = [
335d0889c63SGreg Roach            'INDI:BIRT' => [
3367bf6ca81SGreg Roach                'M' => I18N::translate('Birth of a half-brother'),
3377bf6ca81SGreg Roach                'F' => I18N::translate('Birth of a half-sister'),
3387bf6ca81SGreg Roach                'U' => I18N::translate('Birth of a half-sibling'),
3397bf6ca81SGreg Roach            ],
340d0889c63SGreg Roach            'INDI:CHR'  => [
3417bf6ca81SGreg Roach                'M' => I18N::translate('Christening of a half-brother'),
3427bf6ca81SGreg Roach                'F' => I18N::translate('Christening of a half-sister'),
3437bf6ca81SGreg Roach                'U' => I18N::translate('Christening of a half-sibling'),
3447bf6ca81SGreg Roach            ],
345d0889c63SGreg Roach            'INDI:BAPM' => [
3467bf6ca81SGreg Roach                'M' => I18N::translate('Baptism of a half-brother'),
3477bf6ca81SGreg Roach                'F' => I18N::translate('Baptism of a half-sister'),
3487bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a half-sibling'),
3497bf6ca81SGreg Roach            ],
350d0889c63SGreg Roach            'INDI:ADOP' => [
3517bf6ca81SGreg Roach                'M' => I18N::translate('Adoption of a half-brother'),
3527bf6ca81SGreg Roach                'F' => I18N::translate('Adoption of a half-sister'),
3537bf6ca81SGreg Roach                'U' => I18N::translate('Adoption of a half-sibling'),
3547bf6ca81SGreg Roach            ],
3557bf6ca81SGreg Roach        ];
3567bf6ca81SGreg Roach
3577bf6ca81SGreg Roach        $birth_of_a_grandchild = [
358d0889c63SGreg Roach            'INDI:BIRT' => [
3597bf6ca81SGreg Roach                'M' => I18N::translate('Birth of a grandson'),
3607bf6ca81SGreg Roach                'F' => I18N::translate('Birth of a granddaughter'),
3617bf6ca81SGreg Roach                'U' => I18N::translate('Birth of a grandchild'),
3627bf6ca81SGreg Roach            ],
363d0889c63SGreg Roach            'INDI:CHR'  => [
3647bf6ca81SGreg Roach                'M' => I18N::translate('Christening of a grandson'),
3657bf6ca81SGreg Roach                'F' => I18N::translate('Christening of a granddaughter'),
3667bf6ca81SGreg Roach                'U' => I18N::translate('Christening of a grandchild'),
3677bf6ca81SGreg Roach            ],
368d0889c63SGreg Roach            'INDI:BAPM' => [
3697bf6ca81SGreg Roach                'M' => I18N::translate('Baptism of a grandson'),
3707bf6ca81SGreg Roach                'F' => I18N::translate('Baptism of a granddaughter'),
3717bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a grandchild'),
3727bf6ca81SGreg Roach            ],
373d0889c63SGreg Roach            'INDI:ADOP' => [
3747bf6ca81SGreg Roach                'M' => I18N::translate('Adoption of a grandson'),
3757bf6ca81SGreg Roach                'F' => I18N::translate('Adoption of a granddaughter'),
3767bf6ca81SGreg Roach                'U' => I18N::translate('Adoption of a grandchild'),
3777bf6ca81SGreg Roach            ],
3787bf6ca81SGreg Roach        ];
3797bf6ca81SGreg Roach
3807bf6ca81SGreg Roach        $birth_of_a_grandchild1 = [
381d0889c63SGreg Roach            'INDI:BIRT' => [
3827bf6ca81SGreg Roach                'M' => I18N::translateContext('daughter’s son', 'Birth of a grandson'),
3837bf6ca81SGreg Roach                'F' => I18N::translateContext('daughter’s daughter', 'Birth of a granddaughter'),
3847bf6ca81SGreg Roach                'U' => I18N::translate('Birth of a grandchild'),
3857bf6ca81SGreg Roach            ],
386d0889c63SGreg Roach            'INDI:CHR'  => [
3877bf6ca81SGreg Roach                'M' => I18N::translateContext('daughter’s son', 'Christening of a grandson'),
3887bf6ca81SGreg Roach                'F' => I18N::translateContext('daughter’s daughter', 'Christening of a granddaughter'),
3897bf6ca81SGreg Roach                'U' => I18N::translate('Christening of a grandchild'),
3907bf6ca81SGreg Roach            ],
391d0889c63SGreg Roach            'INDI:BAPM' => [
3927bf6ca81SGreg Roach                'M' => I18N::translateContext('daughter’s son', 'Baptism of a grandson'),
3937bf6ca81SGreg Roach                'F' => I18N::translateContext('daughter’s daughter', 'Baptism of a granddaughter'),
3947bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a grandchild'),
3957bf6ca81SGreg Roach            ],
396d0889c63SGreg Roach            'INDI:ADOP' => [
3977bf6ca81SGreg Roach                'M' => I18N::translateContext('daughter’s son', 'Adoption of a grandson'),
3987bf6ca81SGreg Roach                'F' => I18N::translateContext('daughter’s daughter', 'Adoption of a granddaughter'),
3997bf6ca81SGreg Roach                'U' => I18N::translate('Adoption of a grandchild'),
4007bf6ca81SGreg Roach            ],
4017bf6ca81SGreg Roach        ];
4027bf6ca81SGreg Roach
4037bf6ca81SGreg Roach        $birth_of_a_grandchild2 = [
404d0889c63SGreg Roach            'INDI:BIRT' => [
4053d394ce7SGreg Roach                'M' => I18N::translateContext('son’s son', 'Birth of a grandson'),
4063d394ce7SGreg Roach                'F' => I18N::translateContext('son’s daughter', 'Birth of a granddaughter'),
4077bf6ca81SGreg Roach                'U' => I18N::translate('Birth of a grandchild'),
4087bf6ca81SGreg Roach            ],
409d0889c63SGreg Roach            'INDI:CHR'  => [
4103d394ce7SGreg Roach                'M' => I18N::translateContext('son’s son', 'Christening of a grandson'),
4113d394ce7SGreg Roach                'F' => I18N::translateContext('son’s daughter', 'Christening of a granddaughter'),
4127bf6ca81SGreg Roach                'U' => I18N::translate('Christening of a grandchild'),
4137bf6ca81SGreg Roach            ],
414d0889c63SGreg Roach            'INDI:BAPM' => [
4153d394ce7SGreg Roach                'M' => I18N::translateContext('son’s son', 'Baptism of a grandson'),
4163d394ce7SGreg Roach                'F' => I18N::translateContext('son’s daughter', 'Baptism of a granddaughter'),
4177bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a grandchild'),
4187bf6ca81SGreg Roach            ],
419d0889c63SGreg Roach            'INDI:ADOP' => [
4203d394ce7SGreg Roach                'M' => I18N::translateContext('son’s son', 'Adoption of a grandson'),
4213d394ce7SGreg Roach                'F' => I18N::translateContext('son’s daughter', 'Adoption of a granddaughter'),
4227bf6ca81SGreg Roach                'U' => I18N::translate('Adoption of a grandchild'),
4237bf6ca81SGreg Roach            ],
4247bf6ca81SGreg Roach        ];
4257bf6ca81SGreg Roach
4267bf6ca81SGreg Roach        $death_of_a_child = [
427d0889c63SGreg Roach            'INDI:DEAT' => [
4287bf6ca81SGreg Roach                'M' => I18N::translate('Death of a son'),
4297bf6ca81SGreg Roach                'F' => I18N::translate('Death of a daughter'),
4307bf6ca81SGreg Roach                'U' => I18N::translate('Death of a child'),
4317bf6ca81SGreg Roach            ],
432d0889c63SGreg Roach            'INDI:BURI' => [
4337bf6ca81SGreg Roach                'M' => I18N::translate('Burial of a son'),
4347bf6ca81SGreg Roach                'F' => I18N::translate('Burial of a daughter'),
4357bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a child'),
4367bf6ca81SGreg Roach            ],
437d0889c63SGreg Roach            'INDI:CREM' => [
4387bf6ca81SGreg Roach                'M' => I18N::translate('Cremation of a son'),
4397bf6ca81SGreg Roach                'F' => I18N::translate('Cremation of a daughter'),
4407bf6ca81SGreg Roach                'U' => I18N::translate('Cremation of a child'),
4417bf6ca81SGreg Roach            ],
4427bf6ca81SGreg Roach        ];
4437bf6ca81SGreg Roach
4447bf6ca81SGreg Roach        $death_of_a_sibling = [
445d0889c63SGreg Roach            'INDI:DEAT' => [
4467bf6ca81SGreg Roach                'M' => I18N::translate('Death of a brother'),
4477bf6ca81SGreg Roach                'F' => I18N::translate('Death of a sister'),
4487bf6ca81SGreg Roach                'U' => I18N::translate('Death of a sibling'),
4497bf6ca81SGreg Roach            ],
450d0889c63SGreg Roach            'INDI:BURI' => [
4517bf6ca81SGreg Roach                'M' => I18N::translate('Burial of a brother'),
4527bf6ca81SGreg Roach                'F' => I18N::translate('Burial of a sister'),
4537bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a sibling'),
4547bf6ca81SGreg Roach            ],
455d0889c63SGreg Roach            'INDI:CREM' => [
4567bf6ca81SGreg Roach                'M' => I18N::translate('Cremation of a brother'),
4577bf6ca81SGreg Roach                'F' => I18N::translate('Cremation of a sister'),
4587bf6ca81SGreg Roach                'U' => I18N::translate('Cremation of a sibling'),
4597bf6ca81SGreg Roach            ],
4607bf6ca81SGreg Roach        ];
4617bf6ca81SGreg Roach
4627bf6ca81SGreg Roach        $death_of_a_half_sibling = [
463d0889c63SGreg Roach            'INDI:DEAT' => [
4647bf6ca81SGreg Roach                'M' => I18N::translate('Death of a half-brother'),
4657bf6ca81SGreg Roach                'F' => I18N::translate('Death of a half-sister'),
4667bf6ca81SGreg Roach                'U' => I18N::translate('Death of a half-sibling'),
4677bf6ca81SGreg Roach            ],
468d0889c63SGreg Roach            'INDI:BURI' => [
4697bf6ca81SGreg Roach                'M' => I18N::translate('Burial of a half-brother'),
4707bf6ca81SGreg Roach                'F' => I18N::translate('Burial of a half-sister'),
4717bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a half-sibling'),
4727bf6ca81SGreg Roach            ],
473d0889c63SGreg Roach            'INDI:CREM' => [
4747bf6ca81SGreg Roach                'M' => I18N::translate('Cremation of a half-brother'),
4757bf6ca81SGreg Roach                'F' => I18N::translate('Cremation of a half-sister'),
4767bf6ca81SGreg Roach                'U' => I18N::translate('Cremation of a half-sibling'),
4777bf6ca81SGreg Roach            ],
4787bf6ca81SGreg Roach        ];
4797bf6ca81SGreg Roach
4807bf6ca81SGreg Roach        $death_of_a_grandchild = [
481d0889c63SGreg Roach            'INDI:DEAT' => [
4827bf6ca81SGreg Roach                'M' => I18N::translate('Death of a grandson'),
4837bf6ca81SGreg Roach                'F' => I18N::translate('Death of a granddaughter'),
4847bf6ca81SGreg Roach                'U' => I18N::translate('Death of a grandchild'),
4857bf6ca81SGreg Roach            ],
486d0889c63SGreg Roach            'INDI:BURI' => [
4877bf6ca81SGreg Roach                'M' => I18N::translate('Burial of a grandson'),
4887bf6ca81SGreg Roach                'F' => I18N::translate('Burial of a granddaughter'),
4897bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a grandchild'),
4907bf6ca81SGreg Roach            ],
491d0889c63SGreg Roach            'INDI:CREM' => [
4927bf6ca81SGreg Roach                'M' => I18N::translate('Cremation of a grandson'),
4937bf6ca81SGreg Roach                'F' => I18N::translate('Cremation of a granddaughter'),
4947bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a grandchild'),
4957bf6ca81SGreg Roach            ],
4967bf6ca81SGreg Roach        ];
4977bf6ca81SGreg Roach
4987bf6ca81SGreg Roach        $death_of_a_grandchild1 = [
499d0889c63SGreg Roach            'INDI:DEAT' => [
5007bf6ca81SGreg Roach                'M' => I18N::translateContext('daughter’s son', 'Death of a grandson'),
5017bf6ca81SGreg Roach                'F' => I18N::translateContext('daughter’s daughter', 'Death of a granddaughter'),
5027bf6ca81SGreg Roach                'U' => I18N::translate('Death of a grandchild'),
5037bf6ca81SGreg Roach            ],
504d0889c63SGreg Roach            'INDI:BURI' => [
5057bf6ca81SGreg Roach                'M' => I18N::translateContext('daughter’s son', 'Burial of a grandson'),
5067bf6ca81SGreg Roach                'F' => I18N::translateContext('daughter’s daughter', 'Burial of a granddaughter'),
5077bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a grandchild'),
5087bf6ca81SGreg Roach            ],
509d0889c63SGreg Roach            'INDI:CREM' => [
5107bf6ca81SGreg Roach                'M' => I18N::translateContext('daughter’s son', 'Cremation of a grandson'),
5117bf6ca81SGreg Roach                'F' => I18N::translateContext('daughter’s daughter', 'Cremation of a granddaughter'),
5127bf6ca81SGreg Roach                'U' => I18N::translate('Baptism of a grandchild'),
5137bf6ca81SGreg Roach            ],
5147bf6ca81SGreg Roach        ];
5157bf6ca81SGreg Roach
5167bf6ca81SGreg Roach        $death_of_a_grandchild2 = [
517d0889c63SGreg Roach            'INDI:DEAT' => [
5183d394ce7SGreg Roach                'M' => I18N::translateContext('son’s son', 'Death of a grandson'),
5193d394ce7SGreg Roach                'F' => I18N::translateContext('son’s daughter', 'Death of a granddaughter'),
5207bf6ca81SGreg Roach                'U' => I18N::translate('Death of a grandchild'),
5217bf6ca81SGreg Roach            ],
522d0889c63SGreg Roach            'INDI:BURI' => [
5233d394ce7SGreg Roach                'M' => I18N::translateContext('son’s son', 'Burial of a grandson'),
5243d394ce7SGreg Roach                'F' => I18N::translateContext('son’s daughter', 'Burial of a granddaughter'),
5257bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a grandchild'),
5267bf6ca81SGreg Roach            ],
527d0889c63SGreg Roach            'INDI:CREM' => [
5283d394ce7SGreg Roach                'M' => I18N::translateContext('son’s son', 'Cremation of a grandson'),
5293d394ce7SGreg Roach                'F' => I18N::translateContext('son’s daughter', 'Cremation of a granddaughter'),
5307bf6ca81SGreg Roach                'U' => I18N::translate('Cremation of a grandchild'),
5317bf6ca81SGreg Roach            ],
5327bf6ca81SGreg Roach        ];
5337bf6ca81SGreg Roach
5347bf6ca81SGreg Roach        $marriage_of_a_child = [
5357bf6ca81SGreg Roach            'M' => I18N::translate('Marriage of a son'),
5367bf6ca81SGreg Roach            'F' => I18N::translate('Marriage of a daughter'),
5377bf6ca81SGreg Roach            'U' => I18N::translate('Marriage of a child'),
5387bf6ca81SGreg Roach        ];
5397bf6ca81SGreg Roach
5407bf6ca81SGreg Roach        $marriage_of_a_grandchild = [
5417bf6ca81SGreg Roach            'M' => I18N::translate('Marriage of a grandson'),
5427bf6ca81SGreg Roach            'F' => I18N::translate('Marriage of a granddaughter'),
5437bf6ca81SGreg Roach            'U' => I18N::translate('Marriage of a grandchild'),
5447bf6ca81SGreg Roach        ];
5457bf6ca81SGreg Roach
5467bf6ca81SGreg Roach        $marriage_of_a_grandchild1 = [
5477bf6ca81SGreg Roach            'M' => I18N::translateContext('daughter’s son', 'Marriage of a grandson'),
5487bf6ca81SGreg Roach            'F' => I18N::translateContext('daughter’s daughter', 'Marriage of a granddaughter'),
5497bf6ca81SGreg Roach            'U' => I18N::translate('Marriage of a grandchild'),
5507bf6ca81SGreg Roach        ];
5517bf6ca81SGreg Roach
5527bf6ca81SGreg Roach        $marriage_of_a_grandchild2 = [
5533d394ce7SGreg Roach            'M' => I18N::translateContext('son’s son', 'Marriage of a grandson'),
5543d394ce7SGreg Roach            'F' => I18N::translateContext('son’s daughter', 'Marriage of a granddaughter'),
5557bf6ca81SGreg Roach            'U' => I18N::translate('Marriage of a grandchild'),
5567bf6ca81SGreg Roach        ];
5577bf6ca81SGreg Roach
5587bf6ca81SGreg Roach        $marriage_of_a_sibling = [
5597bf6ca81SGreg Roach            'M' => I18N::translate('Marriage of a brother'),
5607bf6ca81SGreg Roach            'F' => I18N::translate('Marriage of a sister'),
5617bf6ca81SGreg Roach            'U' => I18N::translate('Marriage of a sibling'),
5627bf6ca81SGreg Roach        ];
5637bf6ca81SGreg Roach
5647bf6ca81SGreg Roach        $marriage_of_a_half_sibling = [
5657bf6ca81SGreg Roach            'M' => I18N::translate('Marriage of a half-brother'),
5667bf6ca81SGreg Roach            'F' => I18N::translate('Marriage of a half-sister'),
5677bf6ca81SGreg Roach            'U' => I18N::translate('Marriage of a half-sibling'),
5687bf6ca81SGreg Roach        ];
5697bf6ca81SGreg Roach
57091a257a4SGreg Roach        $facts = new Collection();
5713763c3f2SGreg Roach
5723763c3f2SGreg Roach        // Deal with recursion.
57305babb96SGreg Roach        if ($option === '_CHIL') {
5743763c3f2SGreg Roach            // Add grandchildren
57539ca88baSGreg Roach            foreach ($family->children() as $child) {
57639ca88baSGreg Roach                foreach ($child->spouseFamilies() as $cfamily) {
57739ca88baSGreg Roach                    switch ($child->sex()) {
5783763c3f2SGreg Roach                        case 'M':
5798b9cfadbSGreg Roach                            foreach ($this->childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) {
5803763c3f2SGreg Roach                                $facts[] = $fact;
5813763c3f2SGreg Roach                            }
5823763c3f2SGreg Roach                            break;
5833763c3f2SGreg Roach                        case 'F':
5848b9cfadbSGreg Roach                            foreach ($this->childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) {
5853763c3f2SGreg Roach                                $facts[] = $fact;
5863763c3f2SGreg Roach                            }
5873763c3f2SGreg Roach                            break;
5883763c3f2SGreg Roach                        default:
5898b9cfadbSGreg Roach                            foreach ($this->childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) {
5903763c3f2SGreg Roach                                $facts[] = $fact;
5913763c3f2SGreg Roach                            }
5923763c3f2SGreg Roach                            break;
5933763c3f2SGreg Roach                    }
5943763c3f2SGreg Roach                }
5953763c3f2SGreg Roach            }
5963763c3f2SGreg Roach        }
5973763c3f2SGreg Roach
5983763c3f2SGreg Roach        // For each child in the family
59939ca88baSGreg Roach        foreach ($family->children() as $child) {
60022d65e5aSGreg Roach            if ($child->xref() === $person->xref()) {
6013763c3f2SGreg Roach                // We are not our own sibling!
6023763c3f2SGreg Roach                continue;
6033763c3f2SGreg Roach            }
6043763c3f2SGreg Roach            // add child’s birth
605dec352c1SGreg Roach            if (str_contains($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option))) {
6067bf6ca81SGreg Roach                foreach ($child->facts(['BIRT', 'CHR', 'BAPM', 'ADOP']) as $fact) {
6073763c3f2SGreg Roach                    // Always show _BIRT_CHIL, even if the dates are not known
60822d65e5aSGreg Roach                    if ($option === '_CHIL' || $this->includeFact($fact, $min_date, $max_date)) {
6097bf6ca81SGreg Roach                        switch ($option) {
6107bf6ca81SGreg Roach                            case '_GCHI':
6117bf6ca81SGreg Roach                                switch ($relation) {
6127bf6ca81SGreg Roach                                    case 'dau':
613d0889c63SGreg Roach                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild1[$fact->tag()][$fact->record()->sex()]);
6147bf6ca81SGreg Roach                                        break;
6157bf6ca81SGreg Roach                                    case 'son':
616d0889c63SGreg Roach                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild2[$fact->tag()][$fact->record()->sex()]);
6177bf6ca81SGreg Roach                                        break;
6187bf6ca81SGreg Roach                                    case 'chil':
619d0889c63SGreg Roach                                        $facts[] = $this->convertEvent($fact, $birth_of_a_grandchild[$fact->tag()][$fact->record()->sex()]);
6207bf6ca81SGreg Roach                                        break;
6217bf6ca81SGreg Roach                                }
6227bf6ca81SGreg Roach                                break;
6237bf6ca81SGreg Roach                            case '_SIBL':
624d0889c63SGreg Roach                                $facts[] = $this->convertEvent($fact, $birth_of_a_sibling[$fact->tag()][$fact->record()->sex()]);
6257bf6ca81SGreg Roach                                break;
6267bf6ca81SGreg Roach                            case '_HSIB':
627d0889c63SGreg Roach                                $facts[] = $this->convertEvent($fact, $birth_of_a_half_sibling[$fact->tag()][$fact->record()->sex()]);
6287bf6ca81SGreg Roach                                break;
6297bf6ca81SGreg Roach                            case '_CHIL':
630d0889c63SGreg Roach                                $facts[] = $this->convertEvent($fact, $birth_of_a_child[$fact->tag()][$fact->record()->sex()]);
6317bf6ca81SGreg Roach                                break;
6323763c3f2SGreg Roach                        }
6333763c3f2SGreg Roach                    }
6343763c3f2SGreg Roach                }
6353763c3f2SGreg Roach            }
6363763c3f2SGreg Roach            // add child’s death
637dec352c1SGreg Roach            if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option))) {
6387bf6ca81SGreg Roach                foreach ($child->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
6398b9cfadbSGreg Roach                    if ($this->includeFact($fact, $min_date, $max_date)) {
6407bf6ca81SGreg Roach                        switch ($option) {
6417bf6ca81SGreg Roach                            case '_GCHI':
6427bf6ca81SGreg Roach                                switch ($relation) {
6437bf6ca81SGreg Roach                                    case 'dau':
644d0889c63SGreg Roach                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild1[$fact->tag()][$fact->record()->sex()]);
6457bf6ca81SGreg Roach                                        break;
6467bf6ca81SGreg Roach                                    case 'son':
647d0889c63SGreg Roach                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild2[$fact->tag()][$fact->record()->sex()]);
6487bf6ca81SGreg Roach                                        break;
6497bf6ca81SGreg Roach                                    case 'chi':
650d0889c63SGreg Roach                                        $facts[] = $this->convertEvent($fact, $death_of_a_grandchild[$fact->tag()][$fact->record()->sex()]);
6517bf6ca81SGreg Roach                                        break;
6527bf6ca81SGreg Roach                                }
6537bf6ca81SGreg Roach                                break;
6547bf6ca81SGreg Roach                            case '_SIBL':
655d0889c63SGreg Roach                                $facts[] = $this->convertEvent($fact, $death_of_a_sibling[$fact->tag()][$fact->record()->sex()]);
6567bf6ca81SGreg Roach                                break;
6577bf6ca81SGreg Roach                            case '_HSIB':
658d0889c63SGreg Roach                                $facts[] = $this->convertEvent($fact, $death_of_a_half_sibling[$fact->tag()][$fact->record()->sex()]);
6597bf6ca81SGreg Roach                                break;
660718b3bc2SGreg Roach                            case '_CHIL':
661d0889c63SGreg Roach                                $facts[] = $this->convertEvent($fact, $death_of_a_child[$fact->tag()][$fact->record()->sex()]);
6627bf6ca81SGreg Roach                                break;
6633763c3f2SGreg Roach                        }
6643763c3f2SGreg Roach                    }
6653763c3f2SGreg Roach                }
6663763c3f2SGreg Roach            }
6677bf6ca81SGreg Roach
6683763c3f2SGreg Roach            // add child’s marriage
669dec352c1SGreg Roach            if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) {
67039ca88baSGreg Roach                foreach ($child->spouseFamilies() as $sfamily) {
6718d0ebef0SGreg Roach                    foreach ($sfamily->facts(['MARR']) as $fact) {
6728b9cfadbSGreg Roach                        if ($this->includeFact($fact, $min_date, $max_date)) {
6737bf6ca81SGreg Roach                            switch ($option) {
6747bf6ca81SGreg Roach                                case '_GCHI':
6757bf6ca81SGreg Roach                                    switch ($relation) {
6767bf6ca81SGreg Roach                                        case 'dau':
6775d15adbbSJonathan Jaubart                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild1[$child->sex()]);
6787bf6ca81SGreg Roach                                            break;
6797bf6ca81SGreg Roach                                        case 'son':
6805d15adbbSJonathan Jaubart                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild2[$child->sex()]);
6817bf6ca81SGreg Roach                                            break;
6827bf6ca81SGreg Roach                                        case 'chi':
6835d15adbbSJonathan Jaubart                                            $facts[] = $this->convertEvent($fact, $marriage_of_a_grandchild[$child->sex()]);
6847bf6ca81SGreg Roach                                            break;
6857bf6ca81SGreg Roach                                    }
6867bf6ca81SGreg Roach                                    break;
6877bf6ca81SGreg Roach                                case '_SIBL':
6885d15adbbSJonathan Jaubart                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_sibling[$child->sex()]);
6897bf6ca81SGreg Roach                                    break;
6907bf6ca81SGreg Roach                                case '_HSIB':
6915d15adbbSJonathan Jaubart                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_half_sibling[$child->sex()]);
6927bf6ca81SGreg Roach                                    break;
6937bf6ca81SGreg Roach                                case '_CHIL':
6945d15adbbSJonathan Jaubart                                    $facts[] = $this->convertEvent($fact, $marriage_of_a_child[$child->sex()]);
6957bf6ca81SGreg Roach                                    break;
6963763c3f2SGreg Roach                            }
6973763c3f2SGreg Roach                        }
6983763c3f2SGreg Roach                    }
6993763c3f2SGreg Roach                }
7003763c3f2SGreg Roach            }
7013763c3f2SGreg Roach        }
7023763c3f2SGreg Roach
7033763c3f2SGreg Roach        return $facts;
7043763c3f2SGreg Roach    }
7053763c3f2SGreg Roach
7063763c3f2SGreg Roach    /**
7073763c3f2SGreg Roach     * Get the events of parents and grandparents.
7083763c3f2SGreg Roach     *
7093763c3f2SGreg Roach     * @param Individual $person
710cbc1590aSGreg Roach     * @param int        $sosa
711ee727175SGreg Roach     * @param Date       $min_date
712ee727175SGreg Roach     * @param Date       $max_date
7133763c3f2SGreg Roach     *
71436779af1SGreg Roach     * @return Collection<int,Fact>
7153763c3f2SGreg Roach     */
71691a257a4SGreg Roach    private function parentFacts(Individual $person, int $sosa, Date $min_date, Date $max_date): Collection
717c1010edaSGreg Roach    {
718f4afa648SGreg Roach        $SHOW_RELATIVES_EVENTS = $person->tree()->getPreference('SHOW_RELATIVES_EVENTS');
7193763c3f2SGreg Roach
7207bf6ca81SGreg Roach        $death_of_a_parent = [
721d0889c63SGreg Roach            'INDI:DEAT' => [
7227bf6ca81SGreg Roach                'M' => I18N::translate('Death of a father'),
7237bf6ca81SGreg Roach                'F' => I18N::translate('Death of a mother'),
7247bf6ca81SGreg Roach                'U' => I18N::translate('Death of a parent'),
7257bf6ca81SGreg Roach            ],
726d0889c63SGreg Roach            'INDI:BURI' => [
7277bf6ca81SGreg Roach                'M' => I18N::translate('Burial of a father'),
7287bf6ca81SGreg Roach                'F' => I18N::translate('Burial of a mother'),
7297bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a parent'),
7307bf6ca81SGreg Roach            ],
731d0889c63SGreg Roach            'INDI:CREM' => [
7327bf6ca81SGreg Roach                'M' => I18N::translate('Cremation of a father'),
7337bf6ca81SGreg Roach                'F' => I18N::translate('Cremation of a mother'),
7347bf6ca81SGreg Roach                'U' => I18N::translate('Cremation of a parent'),
7357bf6ca81SGreg Roach            ],
7367bf6ca81SGreg Roach        ];
7377bf6ca81SGreg Roach
7387bf6ca81SGreg Roach        $death_of_a_grandparent = [
739d0889c63SGreg Roach            'INDI:DEAT' => [
7407bf6ca81SGreg Roach                'M' => I18N::translate('Death of a grandfather'),
7417bf6ca81SGreg Roach                'F' => I18N::translate('Death of a grandmother'),
7427bf6ca81SGreg Roach                'U' => I18N::translate('Death of a grandparent'),
7437bf6ca81SGreg Roach            ],
744d0889c63SGreg Roach            'INDI:BURI' => [
7457bf6ca81SGreg Roach                'M' => I18N::translate('Burial of a grandfather'),
7467bf6ca81SGreg Roach                'F' => I18N::translate('Burial of a grandmother'),
7477bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a grandparent'),
7487bf6ca81SGreg Roach            ],
749d0889c63SGreg Roach            'INDI:CREM' => [
7507bf6ca81SGreg Roach                'M' => I18N::translate('Cremation of a grandfather'),
7517bf6ca81SGreg Roach                'F' => I18N::translate('Cremation of a grandmother'),
7520b73ecfcSGreg Roach                'U' => I18N::translate('Cremation of a grandparent'),
7537bf6ca81SGreg Roach            ],
7547bf6ca81SGreg Roach        ];
7557bf6ca81SGreg Roach
7567bf6ca81SGreg Roach        $death_of_a_maternal_grandparent = [
757d0889c63SGreg Roach            'INDI:DEAT' => [
7580b73ecfcSGreg Roach                'M' => I18N::translate('Death of a maternal grandfather'),
7590b73ecfcSGreg Roach                'F' => I18N::translate('Death of a maternal grandmother'),
7607bf6ca81SGreg Roach                'U' => I18N::translate('Death of a grandparent'),
7617bf6ca81SGreg Roach            ],
762d0889c63SGreg Roach            'INDI:BURI' => [
7630b73ecfcSGreg Roach                'M' => I18N::translate('Burial of a maternal grandfather'),
7640b73ecfcSGreg Roach                'F' => I18N::translate('Burial of a maternal grandmother'),
7657bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a grandparent'),
7667bf6ca81SGreg Roach            ],
767d0889c63SGreg Roach            'INDI:CREM' => [
7680b73ecfcSGreg Roach                'M' => I18N::translate('Cremation of a maternal grandfather'),
7690b73ecfcSGreg Roach                'F' => I18N::translate('Cremation of a maternal grandmother'),
7700b73ecfcSGreg Roach                'U' => I18N::translate('Cremation of a grandparent'),
7717bf6ca81SGreg Roach            ],
7727bf6ca81SGreg Roach        ];
7737bf6ca81SGreg Roach
7747bf6ca81SGreg Roach        $death_of_a_paternal_grandparent = [
775d0889c63SGreg Roach            'INDI:DEAT' => [
7760b73ecfcSGreg Roach                'M' => I18N::translate('Death of a paternal grandfather'),
7770b73ecfcSGreg Roach                'F' => I18N::translate('Death of a paternal grandmother'),
7787bf6ca81SGreg Roach                'U' => I18N::translate('Death of a grandparent'),
7797bf6ca81SGreg Roach            ],
780d0889c63SGreg Roach            'INDI:BURI' => [
7810b73ecfcSGreg Roach                'M' => I18N::translate('Burial of a paternal grandfather'),
7820b73ecfcSGreg Roach                'F' => I18N::translate('Burial of a paternal grandmother'),
7837bf6ca81SGreg Roach                'U' => I18N::translate('Burial of a grandparent'),
7847bf6ca81SGreg Roach            ],
785d0889c63SGreg Roach            'INDI:CREM' => [
7860b73ecfcSGreg Roach                'M' => I18N::translate('Cremation of a paternal grandfather'),
7870b73ecfcSGreg Roach                'F' => I18N::translate('Cremation of a paternal grandmother'),
7887bf6ca81SGreg Roach                'U' => I18N::translate('Cremation of a grandparent'),
7897bf6ca81SGreg Roach            ],
7907bf6ca81SGreg Roach        ];
7917bf6ca81SGreg Roach
7927bf6ca81SGreg Roach        $marriage_of_a_parent = [
7937bf6ca81SGreg Roach            'M' => I18N::translate('Marriage of a father'),
7947bf6ca81SGreg Roach            'F' => I18N::translate('Marriage of a mother'),
7957bf6ca81SGreg Roach            'U' => I18N::translate('Marriage of a parent'),
7967bf6ca81SGreg Roach        ];
7977bf6ca81SGreg Roach
79891a257a4SGreg Roach        $facts = new Collection();
7993763c3f2SGreg Roach
8000b73ecfcSGreg Roach        if ($sosa === 1) {
80139ca88baSGreg Roach            foreach ($person->childFamilies() as $family) {
8023763c3f2SGreg Roach                // Add siblings
8038b9cfadbSGreg Roach                foreach ($this->childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) {
8043763c3f2SGreg Roach                    $facts[] = $fact;
8053763c3f2SGreg Roach                }
80639ca88baSGreg Roach                foreach ($family->spouses() as $spouse) {
80739ca88baSGreg Roach                    foreach ($spouse->spouseFamilies() as $sfamily) {
8083763c3f2SGreg Roach                        if ($family !== $sfamily) {
8093763c3f2SGreg Roach                            // Add half-siblings
8108b9cfadbSGreg Roach                            foreach ($this->childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) {
8113763c3f2SGreg Roach                                $facts[] = $fact;
8123763c3f2SGreg Roach                            }
8133763c3f2SGreg Roach                        }
8143763c3f2SGreg Roach                    }
8153763c3f2SGreg Roach                    // Add grandparents
81622d65e5aSGreg Roach                    foreach ($this->parentFacts($spouse, $spouse->sex() === 'F' ? 3 : 2, $min_date, $max_date) as $fact) {
8173763c3f2SGreg Roach                        $facts[] = $fact;
8183763c3f2SGreg Roach                    }
8193763c3f2SGreg Roach                }
8203763c3f2SGreg Roach            }
8213763c3f2SGreg Roach
822dec352c1SGreg Roach            if (str_contains($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) {
8233763c3f2SGreg Roach                // add father/mother marriages
82439ca88baSGreg Roach                foreach ($person->childFamilies() as $sfamily) {
8258d0ebef0SGreg Roach                    foreach ($sfamily->facts(['MARR']) as $fact) {
8268b9cfadbSGreg Roach                        if ($this->includeFact($fact, $min_date, $max_date)) {
8273763c3f2SGreg Roach                            // marriage of parents (to each other)
8287bf6ca81SGreg Roach                            $facts[] = $this->convertEvent($fact, I18N::translate('Marriage of parents'));
8293763c3f2SGreg Roach                        }
8303763c3f2SGreg Roach                    }
8313763c3f2SGreg Roach                }
83239ca88baSGreg Roach                foreach ($person->childStepFamilies() as $sfamily) {
8338d0ebef0SGreg Roach                    foreach ($sfamily->facts(['MARR']) as $fact) {
8348b9cfadbSGreg Roach                        if ($this->includeFact($fact, $min_date, $max_date)) {
8353763c3f2SGreg Roach                            // marriage of a parent (to another spouse)
8367bf6ca81SGreg Roach                            $facts[] = $this->convertEvent($fact, $marriage_of_a_parent['U']);
8373763c3f2SGreg Roach                        }
8383763c3f2SGreg Roach                    }
8393763c3f2SGreg Roach                }
8403763c3f2SGreg Roach            }
8413763c3f2SGreg Roach        }
8423763c3f2SGreg Roach
84339ca88baSGreg Roach        foreach ($person->childFamilies() as $family) {
84439ca88baSGreg Roach            foreach ($family->spouses() as $parent) {
845dec352c1SGreg Roach                if (str_contains($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa === 1 ? '_PARE' : '_GPAR'))) {
8467bf6ca81SGreg Roach                    foreach ($parent->facts(['DEAT', 'BURI', 'CREM']) as $fact) {
847e4cf93e3SGreg Roach                        // Show death of parent when it happened prior to birth
848e4cf93e3SGreg Roach                        if ($sosa === 1 && Date::compare($fact->date(), $min_date) < 0 || $this->includeFact($fact, $min_date, $max_date)) {
8493763c3f2SGreg Roach                            switch ($sosa) {
8503763c3f2SGreg Roach                                case 1:
851d0889c63SGreg Roach                                    $facts[] = $this->convertEvent($fact, $death_of_a_parent[$fact->tag()][$fact->record()->sex()]);
8523763c3f2SGreg Roach                                    break;
8533763c3f2SGreg Roach                                case 2:
8543763c3f2SGreg Roach                                case 3:
85569c89463SGreg Roach                                    switch ($person->sex()) {
8567bf6ca81SGreg Roach                                        case 'M':
857d0889c63SGreg Roach                                            $facts[] = $this->convertEvent($fact, $death_of_a_paternal_grandparent[$fact->tag()][$fact->record()->sex()]);
8583763c3f2SGreg Roach                                            break;
8597bf6ca81SGreg Roach                                        case 'F':
860d0889c63SGreg Roach                                            $facts[] = $this->convertEvent($fact, $death_of_a_maternal_grandparent[$fact->tag()][$fact->record()->sex()]);
8617bf6ca81SGreg Roach                                            break;
8627bf6ca81SGreg Roach                                        default:
863d0889c63SGreg Roach                                            $facts[] = $this->convertEvent($fact, $death_of_a_grandparent[$fact->tag()][$fact->record()->sex()]);
8647bf6ca81SGreg Roach                                            break;
8657bf6ca81SGreg Roach                                    }
8663763c3f2SGreg Roach                            }
8673763c3f2SGreg Roach                        }
8683763c3f2SGreg Roach                    }
8693763c3f2SGreg Roach                }
8703763c3f2SGreg Roach            }
8713763c3f2SGreg Roach        }
8723763c3f2SGreg Roach
8733763c3f2SGreg Roach        return $facts;
8743763c3f2SGreg Roach    }
8753763c3f2SGreg Roach
8763763c3f2SGreg Roach    /**
8773763c3f2SGreg Roach     * Get the events of associates.
8783763c3f2SGreg Roach     *
8793763c3f2SGreg Roach     * @param Individual $person
8803763c3f2SGreg Roach     *
88136779af1SGreg Roach     * @return Collection<int,Fact>
8823763c3f2SGreg Roach     */
88391a257a4SGreg Roach    private function associateFacts(Individual $person): Collection
884c1010edaSGreg Roach    {
88513abd6f3SGreg Roach        $facts = [];
8863763c3f2SGreg Roach
8874991f205SGreg Roach        $asso1 = $this->linked_record_service->linkedIndividuals($person, 'ASSO');
8884991f205SGreg Roach        $asso2 = $this->linked_record_service->linkedIndividuals($person, '_ASSO');
8894991f205SGreg Roach        $asso3 = $this->linked_record_service->linkedFamilies($person, 'ASSO');
8904991f205SGreg Roach        $asso4 = $this->linked_record_service->linkedFamilies($person, '_ASSO');
891907c1109SGreg Roach
892907c1109SGreg Roach        $associates = $asso1->merge($asso2)->merge($asso3)->merge($asso4);
893907c1109SGreg Roach
8943763c3f2SGreg Roach        foreach ($associates as $associate) {
89530158ae7SGreg Roach            foreach ($associate->facts() as $fact) {
896907c1109SGreg Roach                if (preg_match('/\n\d _?ASSO @' . $person->xref() . '@/', $fact->gedcom())) {
8973763c3f2SGreg Roach                    // Extract the important details from the fact
898d0889c63SGreg Roach                    $factrec = explode("\n", $fact->gedcom(), 2)[0];
899138ca96cSGreg Roach                    if (preg_match('/\n2 DATE .*/', $fact->gedcom(), $match)) {
9003763c3f2SGreg Roach                        $factrec .= $match[0];
9013763c3f2SGreg Roach                    }
902138ca96cSGreg Roach                    if (preg_match('/\n2 PLAC .*/', $fact->gedcom(), $match)) {
9033763c3f2SGreg Roach                        $factrec .= $match[0];
9043763c3f2SGreg Roach                    }
9053763c3f2SGreg Roach                    if ($associate instanceof Family) {
90639ca88baSGreg Roach                        foreach ($associate->spouses() as $spouse) {
907c0935879SGreg Roach                            $factrec .= "\n2 _ASSO @" . $spouse->xref() . '@';
9083763c3f2SGreg Roach                        }
9093763c3f2SGreg Roach                    } else {
910c0935879SGreg Roach                        $factrec .= "\n2 _ASSO @" . $associate->xref() . '@';
9113763c3f2SGreg Roach                    }
9123763c3f2SGreg Roach                    $facts[] = new Fact($factrec, $associate, 'asso');
9133763c3f2SGreg Roach                }
9143763c3f2SGreg Roach            }
9153763c3f2SGreg Roach        }
9163763c3f2SGreg Roach
91791a257a4SGreg Roach        return new Collection($facts);
91891a257a4SGreg Roach    }
91991a257a4SGreg Roach
92091a257a4SGreg Roach    /**
92191a257a4SGreg Roach     * Get any historical events.
92291a257a4SGreg Roach     *
92391a257a4SGreg Roach     * @param Individual $individual
92491a257a4SGreg Roach     *
92536779af1SGreg Roach     * @return Collection<int,Fact>
92691a257a4SGreg Roach     */
92791a257a4SGreg Roach    private function historicFacts(Individual $individual): Collection
92891a257a4SGreg Roach    {
92991a257a4SGreg Roach        return $this->module_service->findByInterface(ModuleHistoricEventsInterface::class)
93091a257a4SGreg Roach            ->map(static function (ModuleHistoricEventsInterface $module) use ($individual): Collection {
93191a257a4SGreg Roach                return $module->historicEventsForIndividual($individual);
93291a257a4SGreg Roach            })
93391a257a4SGreg Roach            ->flatten();
93491a257a4SGreg Roach    }
93591a257a4SGreg Roach
93691a257a4SGreg Roach    /**
93791a257a4SGreg Roach     * Is this tab empty? If so, we don't always need to display it.
93891a257a4SGreg Roach     *
93991a257a4SGreg Roach     * @param Individual $individual
94091a257a4SGreg Roach     *
94191a257a4SGreg Roach     * @return bool
94291a257a4SGreg Roach     */
94391a257a4SGreg Roach    public function hasTabContent(Individual $individual): bool
94491a257a4SGreg Roach    {
94591a257a4SGreg Roach        return true;
94691a257a4SGreg Roach    }
94791a257a4SGreg Roach
94891a257a4SGreg Roach    /**
94991a257a4SGreg Roach     * Can this tab load asynchronously?
95091a257a4SGreg Roach     *
95191a257a4SGreg Roach     * @return bool
95291a257a4SGreg Roach     */
95391a257a4SGreg Roach    public function canLoadAjax(): bool
95491a257a4SGreg Roach    {
95591a257a4SGreg Roach        return false;
9563763c3f2SGreg Roach    }
9578eaf8709SGreg Roach
9588eaf8709SGreg Roach    /**
9598eaf8709SGreg Roach     * This module handles the following facts - so don't show them on the "Facts and events" tab.
9608eaf8709SGreg Roach     *
96136779af1SGreg Roach     * @return Collection<int,string>
9628eaf8709SGreg Roach     */
9638eaf8709SGreg Roach    public function supportedFacts(): Collection
9648eaf8709SGreg Roach    {
9658eaf8709SGreg Roach        // We don't actually displaye these facts, but they are displayed
9668eaf8709SGreg Roach        // outside the tabs/sidebar systems. This just forces them to be excluded here.
967d0889c63SGreg Roach        return new Collection(['INDI:NAME', 'INDI:SEX', 'INDI:OBJE']);
9688eaf8709SGreg Roach    }
9693763c3f2SGreg Roach}
970