xref: /webtrees/app/Module/IndividualFactsTabModule.php (revision 4eb71cfaf9604652cd62536a0b3b56c6dfd715ad)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2016 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\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 '<colgroup>';
125		echo '<col class="width20">';
126		echo '<col class="width80">';
127		echo '</colgroup>';
128		echo '<tbody>';
129		echo '<tr><td colspan="2" class="descriptionbox rela">';
130		echo '<form action="?"><input id="checkbox_rela_facts" type="checkbox" ';
131		echo $controller->record->getTree()->getPreference('EXPAND_RELATIVES_EVENTS') ? 'checked' : '';
132		echo ' onclick="jQuery(\'tr.rela\').toggle();"><label for="checkbox_rela_facts">', I18N::translate('Events of close relatives'), '</label>';
133		if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) {
134			echo ' <input id="checkbox_histo" type="checkbox" ';
135			echo $EXPAND_HISTO_EVENTS ? 'checked' : '';
136			echo ' onclick="jQuery(\'tr.histo\').toggle();"><label for="checkbox_histo">', I18N::translate('Historical facts'), '</label>';
137		}
138		echo '</form>';
139		echo '</td></tr>';
140
141		if (!$indifacts) {
142			echo '<tr><td colspan="2" class="facts_value">', I18N::translate('There are no facts for this individual.'), '</td></tr>';
143		}
144
145		foreach ($indifacts as $fact) {
146			FunctionsPrintFacts::printFact($fact, $controller->record);
147		}
148
149		//-- new fact link
150		if ($controller->record->canEdit()) {
151			FunctionsPrint::printAddNewFact($controller->record->getXref(), $indifacts, 'INDI');
152		}
153		echo '</tbody>';
154		echo '</table>';
155
156		if (!$controller->record->getTree()->getPreference('EXPAND_RELATIVES_EVENTS')) {
157			echo '<script>jQuery("tr.rela").toggle();</script>';
158		}
159		if (!$EXPAND_HISTO_EVENTS) {
160			echo '<script>jQuery("tr.histo").toggle();</script>';
161		}
162
163		return '<div id="' . $this->getName() . '_content">' . ob_get_clean() . '</div>';
164	}
165
166	/** {@inheritdoc} */
167	public function hasTabContent() {
168		return true;
169	}
170
171	/** {@inheritdoc} */
172	public function canLoadAjax() {
173		return !Auth::isSearchEngine(); // Search engines cannot use AJAX
174	}
175
176	/** {@inheritdoc} */
177	public function getPreLoadContent() {
178		return '';
179	}
180
181	/**
182	 * Spouse facts that are shown on an individual’s page.
183	 *
184	 * @param Individual $individual Show events that occured during the lifetime of this individual
185	 * @param Individual $spouse     Show events of this individual
186	 *
187	 * @return Fact[]
188	 */
189	private static function spouseFacts(Individual $individual, Individual $spouse) {
190		$SHOW_RELATIVES_EVENTS = $individual->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
191
192		$facts = array();
193		if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) {
194			// Only include events between birth and death
195			$birt_date = $individual->getEstimatedBirthDate();
196			$deat_date = $individual->getEstimatedDeathDate();
197
198			foreach ($spouse->getFacts(WT_EVENTS_DEAT) as $fact) {
199
200				$fact_date = $fact->getDate();
201				if ($fact_date->isOK() && Date::compare($birt_date, $fact_date) <= 0 && Date::compare($fact_date, $deat_date) <= 0) {
202					// Convert the event to a close relatives event.
203					$rela_fact = clone($fact);
204					$rela_fact->setTag('_' . $fact->getTag() . '_SPOU');
205					$facts[] = $rela_fact;
206				}
207			}
208		}
209
210		return $facts;
211	}
212
213	/**
214	 * Get the events of children and grandchildren.
215	 *
216	 * @param Individual $person
217	 * @param Family     $family
218	 * @param string     $option
219	 * @param string     $relation
220	 *
221	 * @return Fact[]
222	 */
223	private static function childFacts(Individual $person, Family $family, $option, $relation) {
224		global $controller;
225
226		$SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
227
228		$facts = array();
229
230		// Only include events between birth and death
231		$birt_date = $controller->record->getEstimatedBirthDate();
232		$deat_date = $controller->record->getEstimatedDeathDate();
233
234		// Deal with recursion.
235		switch ($option) {
236		case '_CHIL':
237			// Add grandchildren
238			foreach ($family->getChildren() as $child) {
239				foreach ($child->getSpouseFamilies() as $cfamily) {
240					switch ($child->getSex()) {
241					case 'M':
242						foreach (self::childFacts($person, $cfamily, '_GCHI', 'son') as $fact) {
243							$facts[] = $fact;
244						}
245						break;
246					case 'F':
247						foreach (self::childFacts($person, $cfamily, '_GCHI', 'dau') as $fact) {
248							$facts[] = $fact;
249						}
250						break;
251					default:
252						foreach (self::childFacts($person, $cfamily, '_GCHI', 'chi') as $fact) {
253							$facts[] = $fact;
254						}
255						break;
256					}
257				}
258			}
259			break;
260		}
261
262		// For each child in the family
263		foreach ($family->getChildren() as $child) {
264			if ($child->getXref() == $person->getXref()) {
265				// We are not our own sibling!
266				continue;
267			}
268			// add child’s birth
269			if (strpos($SHOW_RELATIVES_EVENTS, '_BIRT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
270				foreach ($child->getFacts(WT_EVENTS_BIRT) as $fact) {
271					$sgdate = $fact->getDate();
272					// Always show _BIRT_CHIL, even if the dates are not known
273					if ($option == '_CHIL' || $sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) {
274						if ($option == '_GCHI' && $relation == 'dau') {
275							// Convert the event to a close relatives event.
276							$rela_fact = clone($fact);
277							$rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
278							$facts[] = $rela_fact;
279						} elseif ($option == '_GCHI' && $relation == 'son') {
280							// Convert the event to a close relatives event.
281							$rela_fact = clone($fact);
282							$rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
283							$facts[] = $rela_fact;
284						} else {
285							// Convert the event to a close relatives event.
286							$rela_fact = clone($fact);
287							$rela_fact->setTag('_' . $fact->getTag() . $option);
288							$facts[] = $rela_fact;
289						}
290					}
291				}
292			}
293			// add child’s death
294			if (strpos($SHOW_RELATIVES_EVENTS, '_DEAT' . str_replace('_HSIB', '_SIBL', $option)) !== false) {
295				foreach ($child->getFacts(WT_EVENTS_DEAT) as $fact) {
296					$sgdate = $fact->getDate();
297					if ($sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) {
298						if ($option == '_GCHI' && $relation == 'dau') {
299							// Convert the event to a close relatives event.
300							$rela_fact = clone($fact);
301							$rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
302							$facts[] = $rela_fact;
303						} elseif ($option == '_GCHI' && $relation == 'son') {
304							// Convert the event to a close relatives event.
305							$rela_fact = clone($fact);
306							$rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
307							$facts[] = $rela_fact;
308						} else {
309							// Convert the event to a close relatives event.
310							$rela_fact = clone($fact);
311							$rela_fact->setTag('_' . $fact->getTag() . $option);
312							$facts[] = $rela_fact;
313						}
314					}
315				}
316			}
317			// add child’s marriage
318			if (strstr($SHOW_RELATIVES_EVENTS, '_MARR' . str_replace('_HSIB', '_SIBL', $option))) {
319				foreach ($child->getSpouseFamilies() as $sfamily) {
320					foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) {
321						$sgdate = $fact->getDate();
322						if ($sgdate->isOK() && Date::compare($birt_date, $sgdate) <= 0 && Date::compare($sgdate, $deat_date) <= 0) {
323							if ($option == '_GCHI' && $relation == 'dau') {
324								// Convert the event to a close relatives event.
325								$rela_fact = clone($fact);
326								$rela_fact->setTag('_' . $fact->getTag() . '_GCH1');
327								$facts[] = $rela_fact;
328							} elseif ($option == '_GCHI' && $relation == 'son') {
329								// Convert the event to a close relatives event.
330								$rela_fact = clone($fact);
331								$rela_fact->setTag('_' . $fact->getTag() . '_GCH2');
332								$facts[] = $rela_fact;
333							} else {
334								// Convert the event to a close relatives event.
335								$rela_fact = clone($fact);
336								$rela_fact->setTag('_' . $fact->getTag() . $option);
337								$facts[] = $rela_fact;
338							}
339						}
340					}
341				}
342			}
343		}
344
345		return $facts;
346	}
347
348	/**
349	 * Get the events of parents and grandparents.
350	 *
351	 * @param Individual $person
352	 * @param int        $sosa
353	 *
354	 * @return Fact[]
355	 */
356	private static function parentFacts(Individual $person, $sosa) {
357		global $controller;
358
359		$SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
360
361		$facts = array();
362
363		// Only include events between birth and death
364		$birt_date = $controller->record->getEstimatedBirthDate();
365		$deat_date = $controller->record->getEstimatedDeathDate();
366
367		if ($sosa == 1) {
368			foreach ($person->getChildFamilies() as $family) {
369				// Add siblings
370				foreach (self::childFacts($person, $family, '_SIBL', '') as $fact) {
371					$facts[] = $fact;
372				}
373				foreach ($family->getSpouses() as $spouse) {
374					foreach ($spouse->getSpouseFamilies() as $sfamily) {
375						if ($family !== $sfamily) {
376							// Add half-siblings
377							foreach (self::childFacts($person, $sfamily, '_HSIB', '') as $fact) {
378								$facts[] = $fact;
379							}
380						}
381					}
382					// Add grandparents
383					foreach (self::parentFacts($spouse, $spouse->getSex() == 'F' ? 3 : 2) as $fact) {
384						$facts[] = $fact;
385					}
386				}
387			}
388
389			if (strstr($SHOW_RELATIVES_EVENTS, '_MARR_PARE')) {
390				// add father/mother marriages
391				foreach ($person->getChildFamilies() as $sfamily) {
392					foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) {
393						if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) {
394							// marriage of parents (to each other)
395							$rela_fact = clone($fact);
396							$rela_fact->setTag('_' . $fact->getTag() . '_FAMC');
397							$facts[] = $rela_fact;
398						}
399					}
400				}
401				foreach ($person->getChildStepFamilies() as $sfamily) {
402					foreach ($sfamily->getFacts(WT_EVENTS_MARR) as $fact) {
403						if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) {
404							// marriage of a parent (to another spouse)
405							// Convert the event to a close relatives event
406							$rela_fact = clone($fact);
407							$rela_fact->setTag('_' . $fact->getTag() . '_PARE');
408							$facts[] = $rela_fact;
409						}
410					}
411				}
412			}
413		}
414
415		foreach ($person->getChildFamilies() as $family) {
416			foreach ($family->getSpouses() as $parent) {
417				if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT' . ($sosa == 1 ? '_PARE' : '_GPAR'))) {
418					foreach ($parent->getFacts(WT_EVENTS_DEAT) as $fact) {
419						if ($fact->getDate()->isOK() && Date::compare($birt_date, $fact->getDate()) <= 0 && Date::compare($fact->getDate(), $deat_date) <= 0) {
420							switch ($sosa) {
421							case 1:
422								// Convert the event to a close relatives event.
423								$rela_fact = clone($fact);
424								$rela_fact->setTag('_' . $fact->getTag() . '_PARE');
425								$facts[] = $rela_fact;
426								break;
427							case 2:
428								// Convert the event to a close relatives event
429								$rela_fact = clone($fact);
430								$rela_fact->setTag('_' . $fact->getTag() . '_GPA1');
431								$facts[] = $rela_fact;
432								break;
433							case 3:
434								// Convert the event to a close relatives event
435								$rela_fact = clone($fact);
436								$rela_fact->setTag('_' . $fact->getTag() . '_GPA2');
437								$facts[] = $rela_fact;
438								break;
439							}
440						}
441					}
442				}
443			}
444		}
445
446		return $facts;
447	}
448
449	/**
450	 * Get any historical events.
451	 *
452	 * @param Individual $person
453	 *
454	 * @return Fact[]
455	 */
456	private static function historicalFacts(Individual $person) {
457		$SHOW_RELATIVES_EVENTS = $person->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
458
459		$facts = array();
460
461		if ($SHOW_RELATIVES_EVENTS) {
462			// Only include events between birth and death
463			$birt_date = $person->getEstimatedBirthDate();
464			$deat_date = $person->getEstimatedDeathDate();
465
466			if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) {
467				$histo = array();
468				require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php';
469				foreach ($histo as $hist) {
470					// Earlier versions of the WIKI encouraged people to use HTML entities,
471					// rather than UTF8 encoding.
472					$hist = html_entity_decode($hist, ENT_QUOTES, 'UTF-8');
473
474					$fact  = new Fact($hist, $person, 'histo');
475					$sdate = $fact->getDate();
476					if ($sdate->isOK() && Date::compare($birt_date, $sdate) <= 0 && Date::compare($sdate, $deat_date) <= 0) {
477						$facts[] = $fact;
478					}
479				}
480			}
481		}
482
483		return $facts;
484	}
485
486	/**
487	 * Get the events of associates.
488	 *
489	 * @param Individual $person
490	 *
491	 * @return Fact[]
492	 */
493	private static function associateFacts(Individual $person) {
494		$facts = array();
495
496		$associates = array_merge(
497			$person->linkedIndividuals('ASSO'),
498			$person->linkedIndividuals('_ASSO'),
499			$person->linkedFamilies('ASSO'),
500			$person->linkedFamilies('_ASSO')
501		);
502		foreach ($associates as $associate) {
503			foreach ($associate->getFacts() as $fact) {
504				$arec = $fact->getAttribute('_ASSO');
505				if (!$arec) {
506					$arec = $fact->getAttribute('ASSO');
507				}
508				if ($arec && trim($arec, '@') === $person->getXref()) {
509					// Extract the important details from the fact
510					$factrec = '1 ' . $fact->getTag();
511					if (preg_match('/\n2 DATE .*/', $fact->getGedcom(), $match)) {
512						$factrec .= $match[0];
513					}
514					if (preg_match('/\n2 PLAC .*/', $fact->getGedcom(), $match)) {
515						$factrec .= $match[0];
516					}
517					if ($associate instanceof Family) {
518						foreach ($associate->getSpouses() as $spouse) {
519							$factrec .= "\n2 _ASSO @" . $spouse->getXref() . '@';
520						}
521					} else {
522						$factrec .= "\n2 _ASSO @" . $associate->getXref() . '@';
523					}
524					$facts[] = new Fact($factrec, $associate, 'asso');
525				}
526			}
527		}
528
529		return $facts;
530	}
531}
532