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