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