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