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