xref: /webtrees/app/Module/IndividualFactsTabModule.php (revision 0e7a2d236900b65ce66b14620acab98473f496e5)
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    {
145        $fact_date = $fact->getDate();
146
147        return $fact_date->isOK() && Date::compare($min_date, $fact_date) <= 0 && Date::compare($fact_date, $max_date) <= 0;
148    }
149
150    /** {@inheritdoc} */
151    public function hasTabContent(Individual $individual): bool
152    {
153        return true;
154    }
155
156    /** {@inheritdoc} */
157    public function canLoadAjax(): bool
158    {
159        return false;
160    }
161
162    /**
163     * Spouse facts that are shown on an individual’s page.
164     *
165     * @param Individual $individual Show events that occured during the lifetime of this individual
166     * @param Individual $spouse     Show events of this individual
167     * @param Date       $min_date
168     * @param Date       $max_date
169     *
170     * @return Fact[]
171     */
172    private static function spouseFacts(Individual $individual, Individual $spouse, Date $min_date, Date $max_date): array
173    {
174        $SHOW_RELATIVES_EVENTS = $individual->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
175
176        $facts = [];
177        if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) {
178            foreach ($spouse->getFacts(WT_EVENTS_DEAT) as $fact) {
179                if (self::includeFact($fact, $min_date, $max_date)) {
180                    // Convert the event to a close relatives event.
181                    $rela_fact = clone($fact);
182                    $rela_fact->setTag('_' . $fact->getTag() . '_SPOU');
183                    $facts[] = $rela_fact;
184                }
185            }
186        }
187
188        return $facts;
189    }
190
191    /**
192     * Get the events of children and grandchildren.
193     *
194     * @param Individual $person
195     * @param Family     $family
196     * @param string     $option
197     * @param string     $relation
198     * @param Date       $min_date
199     * @param Date       $max_date
200     *
201     * @return Fact[]
202     */
203    private static function childFacts(Individual $person, Family $family, $option, $relation, Date $min_date, Date $max_date): array
204    {
205        $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
206
207        $facts = [];
208
209        // Deal with recursion.
210        switch ($option) {
211            case '_CHIL':
212                // Add grandchildren
213                foreach ($family->getChildren() as $child) {
214                    foreach ($child->getSpouseFamilies() as $cfamily) {
215                        switch ($child->getSex()) {
216                            case 'M':
217                                foreach (self::childFacts($person, $cfamily, '_GCHI', 'son', $min_date, $max_date) as $fact) {
218                                    $facts[] = $fact;
219                                }
220                                break;
221                            case 'F':
222                                foreach (self::childFacts($person, $cfamily, '_GCHI', 'dau', $min_date, $max_date) as $fact) {
223                                    $facts[] = $fact;
224                                }
225                                break;
226                            default:
227                                foreach (self::childFacts($person, $cfamily, '_GCHI', 'chi', $min_date, $max_date) as $fact) {
228                                    $facts[] = $fact;
229                                }
230                                break;
231                        }
232                    }
233                }
234                break;
235        }
236
237        // For each child in the family
238        foreach ($family->getChildren() as $child) {
239            if ($child->getXref() == $person->getXref()) {
240                // We are not our own sibling!
241                continue;
242            }
243            // add child’s birth
244            if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
245                foreach ($child->getFacts(WT_EVENTS_BIRT) as $fact) {
246                    // Always show _BIRT_CHIL, even if the dates are not known
247                    if ($option == '_CHIL' || self::includeFact($fact, $min_date, $max_date)) {
248                        if ($option == '_GCHI' && $relation == 'dau') {
249                            // Convert the event to a close relatives event.
250                            $rela_fact = clone($fact);
251                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
252                            $facts[] = $rela_fact;
253                        } elseif ($option == '_GCHI' && $relation == 'son') {
254                            // Convert the event to a close relatives event.
255                            $rela_fact = clone($fact);
256                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
257                            $facts[] = $rela_fact;
258                        } else {
259                            // Convert the event to a close relatives event.
260                            $rela_fact = clone($fact);
261                            $rela_fact->setTag('_' . $fact->getTag() . $option);
262                            $facts[] = $rela_fact;
263                        }
264                    }
265                }
266            }
267            // add child’s death
268            if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
269                foreach ($child->getFacts(WT_EVENTS_DEAT) as $fact) {
270                    if (self::includeFact($fact, $min_date, $max_date)) {
271                        if ($option == '_GCHI' && $relation == 'dau') {
272                            // Convert the event to a close relatives event.
273                            $rela_fact = clone($fact);
274                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
275                            $facts[] = $rela_fact;
276                        } elseif ($option == '_GCHI' && $relation == 'son') {
277                            // Convert the event to a close relatives event.
278                            $rela_fact = clone($fact);
279                            $rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
280                            $facts[] = $rela_fact;
281                        } else {
282                            // Convert the event to a close relatives event.
283                            $rela_fact = clone($fact);
284                            $rela_fact->setTag('_' . $fact->getTag() . $option);
285                            $facts[] = $rela_fact;
286                        }
287                    }
288                }
289            }
290            // add child’s marriage
291            if (strstr($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) {
292                foreach ($child->getSpouseFamilies() as $sfamily) {
293                    foreach ($sfamily->getFacts('MARR') as $fact) {
294                        if (self::includeFact($fact, $min_date, $max_date)) {
295                            if ($option == '_GCHI' && $relation == 'dau') {
296                                // Convert the event to a close relatives event.
297                                $rela_fact = clone($fact);
298                                $rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
299                                $facts[] = $rela_fact;
300                            } elseif ($option == '_GCHI' && $relation == 'son') {
301                                // Convert the event to a close relatives event.
302                                $rela_fact = clone($fact);
303                                $rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
304                                $facts[] = $rela_fact;
305                            } else {
306                                // Convert the event to a close relatives event.
307                                $rela_fact = clone($fact);
308                                $rela_fact->setTag('_' . $fact->getTag() . $option);
309                                $facts[] = $rela_fact;
310                            }
311                        }
312                    }
313                }
314            }
315        }
316
317        return $facts;
318    }
319
320    /**
321     * Get the events of parents and grandparents.
322     *
323     * @param Individual $person
324     * @param int        $sosa
325     * @param Date       $min_date
326     * @param Date       $max_date
327     *
328     * @return Fact[]
329     */
330    private static function parentFacts(Individual $person, $sosa, Date $min_date, Date $max_date): array
331    {
332        $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
333
334        $facts = [];
335
336        if ($sosa == 1) {
337            foreach ($person->getChildFamilies() as $family) {
338                // Add siblings
339                foreach (self::childFacts($person, $family, '_SIBL', '', $min_date, $max_date) as $fact) {
340                    $facts[] = $fact;
341                }
342                foreach ($family->getSpouses() as $spouse) {
343                    foreach ($spouse->getSpouseFamilies() as $sfamily) {
344                        if ($family !== $sfamily) {
345                            // Add half-siblings
346                            foreach (self::childFacts($person, $sfamily, '_HSIB', '', $min_date, $max_date) as $fact) {
347                                $facts[] = $fact;
348                            }
349                        }
350                    }
351                    // Add grandparents
352                    foreach (self::parentFacts($spouse, $spouse->getSex() == 'F' ? 3 : 2, $min_date, $max_date) as $fact) {
353                        $facts[] = $fact;
354                    }
355                }
356            }
357
358            if (strstr($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) {
359                // add father/mother marriages
360                foreach ($person->getChildFamilies() as $sfamily) {
361                    foreach ($sfamily->getFacts('MARR') as $fact) {
362                        if (self::includeFact($fact, $min_date, $max_date)) {
363                            // marriage of parents (to each other)
364                            $rela_fact = clone($fact);
365                            $rela_fact->setTag('_' . $fact->getTag() . '_FAMC');
366                            $facts[] = $rela_fact;
367                        }
368                    }
369                }
370                foreach ($person->getChildStepFamilies() as $sfamily) {
371                    foreach ($sfamily->getFacts('MARR') as $fact) {
372                        if (self::includeFact($fact, $min_date, $max_date)) {
373                            // marriage of a parent (to another spouse)
374                            // Convert the event to a close relatives event
375                            $rela_fact = clone($fact);
376                            $rela_fact->setTag('_' . $fact->getTag() . '_PARE');
377                            $facts[] = $rela_fact;
378                        }
379                    }
380                }
381            }
382        }
383
384        foreach ($person->getChildFamilies() as $family) {
385            foreach ($family->getSpouses() as $parent) {
386                if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa == 1 ? '_PARE' : '_GPAR'))) {
387                    foreach ($parent->getFacts(WT_EVENTS_DEAT) as $fact) {
388                        if (self::includeFact($fact, $min_date, $max_date)) {
389                            switch ($sosa) {
390                                case 1:
391                                    // Convert the event to a close relatives event.
392                                    $rela_fact = clone($fact);
393                                    $rela_fact->setTag('_' . $fact->getTag() . '_PARE');
394                                    $facts[] = $rela_fact;
395                                    break;
396                                case 2:
397                                    // Convert the event to a close relatives event
398                                    $rela_fact = clone($fact);
399                                    $rela_fact->setTag('_' . $fact->getTag() . '_GPA1');
400                                    $facts[] = $rela_fact;
401                                    break;
402                                case 3:
403                                    // Convert the event to a close relatives event
404                                    $rela_fact = clone($fact);
405                                    $rela_fact->setTag('_' . $fact->getTag() . '_GPA2');
406                                    $facts[] = $rela_fact;
407                                    break;
408                            }
409                        }
410                    }
411                }
412            }
413        }
414
415        return $facts;
416    }
417
418    /**
419     * Get any historical events.
420     *
421     * @param Individual $person
422     * @param Date       $min_date
423     * @param Date       $max_date
424     *
425     * @return Fact[]
426     */
427    private static function historicalFacts(Individual $person, Date $min_date, Date $max_date): array
428    {
429        $SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
430
431        $facts = [];
432
433        if ($SHOW_RELATIVES_EVENTS) {
434            if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) {
435                $histo = [];
436                require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php';
437                foreach ($histo as $hist) {
438                    $fact  = new Fact($hist, $person, 'histo');
439
440                    if (self::includeFact($fact, $min_date, $max_date)) {
441                        $facts[] = $fact;
442                    }
443                }
444            }
445        }
446
447        return $facts;
448    }
449
450    /**
451     * Get the events of associates.
452     *
453     * @param Individual $person
454     *
455     * @return Fact[]
456     */
457    private static function associateFacts(Individual $person): array
458    {
459        $facts = [];
460
461        /** @var Individual[] $associates */
462        $associates = array_merge(
463            $person->linkedIndividuals('ASSO'),
464            $person->linkedIndividuals('_ASSO'),
465            $person->linkedFamilies('ASSO'),
466            $person->linkedFamilies('_ASSO')
467        );
468        foreach ($associates as $associate) {
469            foreach ($associate->getFacts() as $fact) {
470                $arec = $fact->getAttribute('_ASSO');
471                if (!$arec) {
472                    $arec = $fact->getAttribute('ASSO');
473                }
474                if ($arec && trim($arec, '@') === $person->getXref()) {
475                    // Extract the important details from the fact
476                    $factrec = '1 ' . $fact->getTag();
477                    if (preg_match('/\n2 DATE .*/', $fact->getGedcom(), $match)) {
478                        $factrec .= $match[0];
479                    }
480                    if (preg_match('/\n2 PLAC .*/', $fact->getGedcom(), $match)) {
481                        $factrec .= $match[0];
482                    }
483                    if ($associate instanceof Family) {
484                        foreach ($associate->getSpouses() as $spouse) {
485                            $factrec .= "\n2 _ASSO @" . $spouse->getXref() . '@';
486                        }
487                    } else {
488                        $factrec .= "\n2 _ASSO @" . $associate->getXref() . '@';
489                    }
490                    $facts[] = new Fact($factrec, $associate, 'asso');
491                }
492            }
493        }
494
495        return $facts;
496    }
497}
498