xref: /webtrees/app/Module/IndividualFactsTabModule.php (revision 67994fb087e1b24564a780e4ae8aeff801733e35)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2018 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16declare(strict_types=1);
17
18namespace Fisharebest\Webtrees\Module;
19
20use Fisharebest\Webtrees\Date;
21use Fisharebest\Webtrees\Fact;
22use Fisharebest\Webtrees\Family;
23use Fisharebest\Webtrees\Functions\Functions;
24use Fisharebest\Webtrees\I18N;
25use Fisharebest\Webtrees\Individual;
26use Fisharebest\Webtrees\Module;
27use Fisharebest\Webtrees\Site;
28
29/**
30 * Class IndividualFactsTabModule
31 */
32class IndividualFactsTabModule extends AbstractModule implements ModuleTabInterface
33{
34    /** {@inheritdoc} */
35    public function getTitle(): string
36    {
37        /* I18N: Name of a module/tab on the individual page. */
38        return I18N::translate('Facts and events');
39    }
40
41    /** {@inheritdoc} */
42    public function getDescription(): string
43    {
44        /* I18N: Description of the “Facts and events” module */
45        return I18N::translate('A tab showing the facts and events of an individual.');
46    }
47
48    /** {@inheritdoc} */
49    public function defaultTabOrder(): int
50    {
51        return 10;
52    }
53
54    /** {@inheritdoc} */
55    public function isGrayedOut(Individual $individual): bool
56    {
57        return false;
58    }
59
60    /** {@inheritdoc} */
61    public function getTabContent(Individual $individual): string
62    {
63        $indifacts = [];
64        // The individual’s own facts
65        foreach ($individual->getFacts() as $fact) {
66            switch ($fact->getTag()) {
67                case 'SEX':
68                case 'NAME':
69                case 'SOUR':
70                case 'OBJE':
71                case 'NOTE':
72                case 'FAMC':
73                case 'FAMS':
74                    break;
75                default:
76                    if (!array_key_exists('extra_info', Module::getActiveSidebars($individual->getTree())) || !ExtraInformationModule::showFact($fact)) {
77                        $indifacts[] = $fact;
78                    }
79                    break;
80            }
81        }
82
83        // Add spouse-family facts
84        foreach ($individual->getSpouseFamilies() as $family) {
85            foreach ($family->getFacts() as $fact) {
86                switch ($fact->getTag()) {
87                    case 'SOUR':
88                    case 'NOTE':
89                    case 'OBJE':
90                    case 'CHAN':
91                    case '_UID':
92                    case 'RIN':
93                    case 'HUSB':
94                    case 'WIFE':
95                    case 'CHIL':
96                        break;
97                    default:
98                        $indifacts[] = $fact;
99                        break;
100                }
101            }
102            $spouse = $family->getSpouse($individual);
103            if ($spouse) {
104                foreach (self::spouseFacts($individual, $spouse) as $fact) {
105                    $indifacts[] = $fact;
106                }
107            }
108            foreach (self::childFacts($individual, $family, '_CHIL', '') as $fact) {
109                $indifacts[] = $fact;
110            }
111        }
112
113        foreach (self::parentFacts($individual, 1) as $fact) {
114            $indifacts[] = $fact;
115        }
116
117        foreach (self::associateFacts($individual) as $fact) {
118            $indifacts[] = $fact;
119        }
120
121        $has_historical_facts = false;
122        foreach (self::historicalFacts($individual) as $fact) {
123            $has_historical_facts = true;
124            $indifacts[]          = $fact;
125        }
126
127        Functions::sortFacts($indifacts);
128
129        return view('modules/personal_facts/tab', [
130            'can_edit'             => $individual->canEdit(),
131            'has_historical_facts' => $has_historical_facts,
132            'individual'           => $individual,
133            'facts'                => $indifacts,
134        ]);
135    }
136
137    /** {@inheritdoc} */
138    public function hasTabContent(Individual $individual): bool
139    {
140        return true;
141    }
142
143    /** {@inheritdoc} */
144    public function canLoadAjax(): bool
145    {
146        return false;
147    }
148
149    /**
150     * Spouse facts that are shown on an individual’s page.
151     *
152     * @param Individual $individual Show events that occured during the lifetime of this individual
153     * @param Individual $spouse     Show events of this individual
154     *
155     * @return Fact[]
156     */
157    private static function spouseFacts(Individual $individual, Individual $spouse): array
158    {
159        $SHOW_RELATIVES_EVENTS = $individual->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
160
161        $facts = [];
162        if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) {
163            // Only include events between birth and death
164            $birt_date = $individual->getEstimatedBirthDate();
165            $deat_date = $individual->getEstimatedDeathDate();
166
167            foreach ($spouse->getFacts(WT_EVENTS_DEAT) as $fact) {
168                $fact_date = $fact->getDate();
169                if ($fact_date->isOK() && Date::compare($birt_date, $fact_date) <= 0 && Date::compare($fact_date, $deat_date) <= 0) {
170                    // Convert the event to a close relatives event.
171                    $rela_fact = clone($fact);
172                    $rela_fact->setTag('_' . $fact->getTag() . '_SPOU');
173                    $facts[] = $rela_fact;
174                }
175            }
176        }
177
178        return $facts;
179    }
180
181    /**
182     * Get the events of children and grandchildren.
183     *
184     * @param Individual $person
185     * @param Family     $family
186     * @param string     $option
187     * @param string     $relation
188     *
189     * @return Fact[]
190     */
191    private static function childFacts(Individual $person, Family $family, $option, $relation): array
192    {
193        $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
194
195        $facts = [];
196
197        // Only include events between birth and death
198        $birt_date = $person->getEstimatedBirthDate();
199        $deat_date = $person->getEstimatedDeathDate();
200
201        // Deal with recursion.
202        switch ($option) {
203            case '_CHIL':
204                // Add grandchildren
205                foreach ($family->getChildren() as $child) {
206                    foreach ($child->getSpouseFamilies() as $cfamily) {
207                        switch ($child->getSex()) {
208                            case 'M':
209                                foreach (self::childFacts($person, $cfamily, '_GCHI', 'son') as $fact) {
210                                    $facts[] = $fact;
211                                }
212                                break;
213                            case 'F':
214                                foreach (self::childFacts($person, $cfamily, '_GCHI', 'dau') as $fact) {
215                                    $facts[] = $fact;
216                                }
217                                break;
218                            default:
219                                foreach (self::childFacts($person, $cfamily, '_GCHI', 'chi') as $fact) {
220                                    $facts[] = $fact;
221                                }
222                                break;
223                        }
224                    }
225                }
226                break;
227        }
228
229        // For each child in the family
230        foreach ($family->getChildren() as $child) {
231            if ($child->getXref() == $person->getXref()) {
232                // We are not our own sibling!
233                continue;
234            }
235            // add child’s birth
236            if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
237                foreach ($child->getFacts(WT_EVENTS_BIRT) as $fact) {
238                    $sgdate = $fact->getDate();
239                    // Always show _BIRT_CHIL, even if the dates are not known
240                    if ($option == '_CHIL' || $sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) {
241                        if ($option == '_GCHI' && $relation == 'dau') {
242                            // Convert the event to a close relatives event.
243                            $rela_fact = clone($fact);
244                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
245                            $facts[] = $rela_fact;
246                        } elseif ($option == '_GCHI' && $relation == 'son') {
247                            // Convert the event to a close relatives event.
248                            $rela_fact = clone($fact);
249                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
250                            $facts[] = $rela_fact;
251                        } else {
252                            // Convert the event to a close relatives event.
253                            $rela_fact = clone($fact);
254                            $rela_fact->setTag('_' . $fact->getTag() . $option);
255                            $facts[] = $rela_fact;
256                        }
257                    }
258                }
259            }
260            // add child’s death
261            if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
262                foreach ($child->getFacts(WT_EVENTS_DEAT) as $fact) {
263                    $sgdate = $fact->getDate();
264                    if ($sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) {
265                        if ($option == '_GCHI' && $relation == 'dau') {
266                            // Convert the event to a close relatives event.
267                            $rela_fact = clone($fact);
268                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
269                            $facts[] = $rela_fact;
270                        } elseif ($option == '_GCHI' && $relation == 'son') {
271                            // Convert the event to a close relatives event.
272                            $rela_fact = clone($fact);
273                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
274                            $facts[] = $rela_fact;
275                        } else {
276                            // Convert the event to a close relatives event.
277                            $rela_fact = clone($fact);
278                            $rela_fact->setTag('_' . $fact->getTag() . $option);
279                            $facts[] = $rela_fact;
280                        }
281                    }
282                }
283            }
284            // add child’s marriage
285            if (strstr($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) {
286                foreach ($child->getSpouseFamilies() as $sfamily) {
287                    foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) {
288                        $sgdate = $fact->getDate();
289                        if ($sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) {
290                            if ($option == '_GCHI' && $relation == 'dau') {
291                                // Convert the event to a close relatives event.
292                                $rela_fact = clone($fact);
293                                $rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
294                                $facts[] = $rela_fact;
295                            } elseif ($option == '_GCHI' && $relation == 'son') {
296                                // Convert the event to a close relatives event.
297                                $rela_fact = clone($fact);
298                                $rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
299                                $facts[] = $rela_fact;
300                            } else {
301                                // Convert the event to a close relatives event.
302                                $rela_fact = clone($fact);
303                                $rela_fact->setTag('_' . $fact->getTag() . $option);
304                                $facts[] = $rela_fact;
305                            }
306                        }
307                    }
308                }
309            }
310        }
311
312        return $facts;
313    }
314
315    /**
316     * Get the events of parents and grandparents.
317     *
318     * @param Individual $person
319     * @param int        $sosa
320     *
321     * @return Fact[]
322     */
323    private static function parentFacts(Individual $person, $sosa): array
324    {
325        $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
326
327        $facts = [];
328
329        // Only include events between birth and death
330        $birt_date = $person->getEstimatedBirthDate();
331        $deat_date = $person->getEstimatedDeathDate();
332
333        if ($sosa == 1) {
334            foreach ($person->getChildFamilies() as $family) {
335                // Add siblings
336                foreach (self::childFacts($person, $family, '_SIBL', '') as $fact) {
337                    $facts[] = $fact;
338                }
339                foreach ($family->getSpouses() as $spouse) {
340                    foreach ($spouse->getSpouseFamilies() as $sfamily) {
341                        if ($family !== $sfamily) {
342                            // Add half-siblings
343                            foreach (self::childFacts($person, $sfamily, '_HSIB', '') as $fact) {
344                                $facts[] = $fact;
345                            }
346                        }
347                    }
348                    // Add grandparents
349                    foreach (self::parentFacts($spouse, $spouse->getSex() == 'F' ? 3 : 2) as $fact) {
350                        $facts[] = $fact;
351                    }
352                }
353            }
354
355            if (strstr($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) {
356                // add father/mother marriages
357                foreach ($person->getChildFamilies() as $sfamily) {
358                    foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) {
359                        if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) {
360                            // marriage of parents (to each other)
361                            $rela_fact = clone($fact);
362                            $rela_fact->setTag('_' . $fact->getTag() . '_FAMC');
363                            $facts[] = $rela_fact;
364                        }
365                    }
366                }
367                foreach ($person->getChildStepFamilies() as $sfamily) {
368                    foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) {
369                        if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) {
370                            // marriage of a parent (to another spouse)
371                            // Convert the event to a close relatives event
372                            $rela_fact = clone($fact);
373                            $rela_fact->setTag('_' . $fact->getTag() . '_PARE');
374                            $facts[] = $rela_fact;
375                        }
376                    }
377                }
378            }
379        }
380
381        foreach ($person->getChildFamilies() as $family) {
382            foreach ($family->getSpouses() as $parent) {
383                if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa == 1 ? '_PARE' : '_GPAR'))) {
384                    foreach ($parent->getFacts(WT_EVENTS_DEAT) as $fact) {
385                        if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) {
386                            switch ($sosa) {
387                                case 1:
388                                    // Convert the event to a close relatives event.
389                                    $rela_fact = clone($fact);
390                                    $rela_fact->setTag('_' . $fact->getTag() . '_PARE');
391                                    $facts[] = $rela_fact;
392                                    break;
393                                case 2:
394                                    // Convert the event to a close relatives event
395                                    $rela_fact = clone($fact);
396                                    $rela_fact->setTag('_' . $fact->getTag() . '_GPA1');
397                                    $facts[] = $rela_fact;
398                                    break;
399                                case 3:
400                                    // Convert the event to a close relatives event
401                                    $rela_fact = clone($fact);
402                                    $rela_fact->setTag('_' . $fact->getTag() . '_GPA2');
403                                    $facts[] = $rela_fact;
404                                    break;
405                            }
406                        }
407                    }
408                }
409            }
410        }
411
412        return $facts;
413    }
414
415    /**
416     * Get any historical events.
417     *
418     * @param Individual $person
419     *
420     * @return Fact[]
421     */
422    private static function historicalFacts(Individual $person): array
423    {
424        $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
425
426        $facts = [];
427
428        if ($SHOW_RELATIVES_EVENTS) {
429            // Only include events between birth and death
430            $birt_date = $person->getEstimatedBirthDate();
431            $deat_date = $person->getEstimatedDeathDate();
432
433            if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) {
434                $histo = [];
435                require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php';
436                foreach ($histo as $hist) {
437                    $fact  = new Fact($hist, $person, 'histo');
438                    $sdate = $fact->getDate();
439                    if ($sdate->isOK() && Date::compare($birt_date, $sdate) <= 0 && Date::compare($sdate, $deat_date) <= 0) {
440                        $facts[] = $fact;
441                    }
442                }
443            }
444        }
445
446        return $facts;
447    }
448
449    /**
450     * Get the events of associates.
451     *
452     * @param Individual $person
453     *
454     * @return Fact[]
455     */
456    private static function associateFacts(Individual $person): array
457    {
458        $facts = [];
459
460        $associates = array_merge(
461            $person->linkedIndividuals('ASSO'),
462            $person->linkedIndividuals('_ASSO'),
463            $person->linkedFamilies('ASSO'),
464            $person->linkedFamilies('_ASSO')
465        );
466        foreach ($associates as $associate) {
467            foreach ($associate->getFacts() as $fact) {
468                $arec = $fact->getAttribute('_ASSO');
469                if (!$arec) {
470                    $arec = $fact->getAttribute('ASSO');
471                }
472                if ($arec && trim($arec, '@') === $person->getXref()) {
473                    // Extract the important details from the fact
474                    $factrec = '1 ' . $fact->getTag();
475                    if (preg_match('/\n2 DATE .*/', $fact->getGedcom(), $match)) {
476                        $factrec .= $match[0];
477                    }
478                    if (preg_match('/\n2 PLAC .*/', $fact->getGedcom(), $match)) {
479                        $factrec .= $match[0];
480                    }
481                    if ($associate instanceof Family) {
482                        foreach ($associate->getSpouses() as $spouse) {
483                            $factrec .= "\n2 _ASSO @" . $spouse->getXref() . '@';
484                        }
485                    } else {
486                        $factrec .= "\n2 _ASSO @" . $associate->getXref() . '@';
487                    }
488                    $facts[] = new Fact($factrec, $associate, 'asso');
489                }
490            }
491        }
492
493        return $facts;
494    }
495}
496