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