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