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