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