xref: /webtrees/app/Module/IndividualFactsTabModule.php (revision b1f1e4ef9803ff38b1a05784d23abe6119b8d697)
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="table wt-facts-table">
123			<tbody>
124				<tr>
125					<td colspan="2">
126						<?php if ($controller->record->getTree()->getPreference('SHOW_RELATIVES_EVENTS')) : ?>
127						<label>
128							<input id="show-relatives-facts" type="checkbox" data-toggle="collapse" data-target=".wt-relation-fact">
129							<?= I18N::translate('Events of close relatives') ?>
130						</label>
131						<?php endif ?>
132						<?php if (file_exists(Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php')) : ?>
133						<label>
134							<input id="show-historical-facts" type="checkbox" data-toggle="collapse" data-target=".wt-historic-fact">
135							<?= I18N::translate('Historical facts') ?>
136						</label>
137						<?php endif ?>
138					</td>
139				</tr>
140				<?php
141
142		if (!$indifacts) {
143			echo '<tr><td colspan="2">', I18N::translate('There are no facts for this individual.'), '</td></tr>';
144		}
145
146		foreach ($indifacts as $fact) {
147			FunctionsPrintFacts::printFact($fact, $controller->record);
148		}
149
150		//-- new fact link
151		if ($controller->record->canEdit()) {
152			FunctionsPrint::printAddNewFact($controller->record->getXref(), $indifacts, 'INDI');
153		}
154
155		?>
156			</tbody>
157		</table>
158		<script>
159			//persistent_toggle("show-relatives-facts", "tr.rela");
160			//persistent_toggle("show-historical-facts", "tr.histo");
161		</script>
162		<?php
163
164		return '<div id="' . $this->getName() . '_content">' . ob_get_clean() . '</div>';
165	}
166
167	/** {@inheritdoc} */
168	public function hasTabContent() {
169		return true;
170	}
171
172	/** {@inheritdoc} */
173	public function canLoadAjax() {
174		return false;
175	}
176
177	/** {@inheritdoc} */
178	public function getPreLoadContent() {
179		return '';
180	}
181
182	/**
183	 * Spouse facts that are shown on an individual’s page.
184	 *
185	 * @param Individual $individual Show events that occured during the lifetime of this individual
186	 * @param Individual $spouse     Show events of this individual
187	 *
188	 * @return Fact[]
189	 */
190	private static function spouseFacts(Individual $individual, Individual $spouse) {
191		$SHOW_RELATIVES_EVENTS = $individual->getTree()->getPreference('SHOW_RELATIVES_EVENTS');
192
193		$facts = [];
194		if (strstr($SHOW_RELATIVES_EVENTS, '_DEAT_SPOU')) {
195			// Only include events between birth and death
196			$birt_date = $individual->getEstimatedBirthDate();
197			$deat_date = $individual->getEstimatedDeathDate();
198
199			foreach ($spouse->getFacts(WT_EVENTS_DEAT) as $fact) {
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 = [];
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 = [];
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 = [];
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 = [];
468				require Site::getPreference('INDEX_DIRECTORY') . 'histo.' . WT_LOCALE . '.php';
469				foreach ($histo as $hist) {
470					$fact  = new Fact($hist, $person, 'histo');
471					$sdate = $fact->getDate();
472					if ($sdate->isOK() && Date::compare($birt_date, $sdate) <= 0 && Date::compare($sdate, $deat_date) <= 0) {
473						$facts[] = $fact;
474					}
475				}
476			}
477		}
478
479		return $facts;
480	}
481
482	/**
483	 * Get the events of associates.
484	 *
485	 * @param Individual $person
486	 *
487	 * @return Fact[]
488	 */
489	private static function associateFacts(Individual $person) {
490		$facts = [];
491
492		$associates = array_merge(
493			$person->linkedIndividuals('ASSO'),
494			$person->linkedIndividuals('_ASSO'),
495			$person->linkedFamilies('ASSO'),
496			$person->linkedFamilies('_ASSO')
497		);
498		foreach ($associates as $associate) {
499			foreach ($associate->getFacts() as $fact) {
500				$arec = $fact->getAttribute('_ASSO');
501				if (!$arec) {
502					$arec = $fact->getAttribute('ASSO');
503				}
504				if ($arec && trim($arec, '@') === $person->getXref()) {
505					// Extract the important details from the fact
506					$factrec = '1 ' . $fact->getTag();
507					if (preg_match('/\n2 DATE .*/', $fact->getGedcom(), $match)) {
508						$factrec .= $match[0];
509					}
510					if (preg_match('/\n2 PLAC .*/', $fact->getGedcom(), $match)) {
511						$factrec .= $match[0];
512					}
513					if ($associate instanceof Family) {
514						foreach ($associate->getSpouses() as $spouse) {
515							$factrec .= "\n2 _ASSO @" . $spouse->getXref() . '@';
516						}
517					} else {
518						$factrec .= "\n2 _ASSO @" . $associate->getXref() . '@';
519					}
520					$facts[] = new Fact($factrec, $associate, 'asso');
521				}
522			}
523		}
524
525		return $facts;
526	}
527}
528