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