xref: /webtrees/app/Module/RelativesTabModule.php (revision 1062a1429914c995339f502856821457aa975a5a)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2018 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\Family;
21use Fisharebest\Webtrees\Functions\Functions;
22use Fisharebest\Webtrees\GedcomTag;
23use Fisharebest\Webtrees\I18N;
24use Fisharebest\Webtrees\Individual;
25use Fisharebest\Webtrees\Theme;
26
27/**
28 * Class RelativesTabModule
29 */
30class RelativesTabModule extends AbstractModule implements ModuleTabInterface {
31	/**
32	 * How should this module be labelled on tabs, menus, etc.?
33	 *
34	 * @return string
35	 */
36	public function getTitle() {
37		return /* I18N: Name of a module */ I18N::translate('Families');
38	}
39
40	/**
41	 * A sentence describing what this module does.
42	 *
43	 * @return string
44	 */
45	public function getDescription() {
46		return /* I18N: Description of the “Families” module */ I18N::translate('A tab showing the close relatives of an individual.');
47	}
48
49	/**
50	 * The user can re-arrange the tab order, but until they do, this
51	 * is the order in which tabs are shown.
52	 *
53	 * @return int
54	 */
55	public function defaultTabOrder() {
56		return 20;
57	}
58
59	/**
60	 * Display the age difference between marriages and the births of children.
61	 *
62	 * @param Date $prev
63	 * @param Date $next
64	 * @param int  $child_number
65	 *
66	 * @return string
67	 */
68	private static function ageDifference(Date $prev, Date $next, $child_number = 0) {
69		if ($prev->isOK() && $next->isOK()) {
70			$days = $next->maximumJulianDay() - $prev->minimumJulianDay();
71			if ($days < 0) {
72				// Show warning triangle if dates in reverse order
73				$diff = '<i class="icon-warning"></i> ';
74			} elseif ($child_number > 1 && $days > 1 && $days < 240) {
75				// Show warning triangle if children born too close together
76				$diff = '<i class="icon-warning"></i> ';
77			} else {
78				$diff = '';
79			}
80
81			$months = round($days * 12 / 365.25); // Approximate - we do not know the calendar
82			if (abs($months) == 12 || abs($months) >= 24) {
83				$diff .= I18N::plural('%s year', '%s years', round($months / 12), I18N::number(round($months / 12)));
84			} elseif ($months != 0) {
85				$diff .= I18N::plural('%s month', '%s months', $months, I18N::number($months));
86			}
87
88			return '<div class="elderdate age">' . $diff . '</div>';
89		} else {
90			return '';
91		}
92	}
93
94	/**
95	 * Print a family group.
96	 *
97	 * @param Family $family
98	 * @param string $type
99	 * @param string $label
100	 */
101	private function printFamily(Family $family, $type, $label) {
102		global $controller;
103
104		if ($family->getTree()->getPreference('SHOW_PRIVATE_RELATIONSHIPS')) {
105			$access_level = Auth::PRIV_HIDE;
106		} else {
107			$access_level = Auth::accessLevel($family->getTree());
108		}
109
110		?>
111		<table>
112			<tr>
113				<td>
114					<i class="icon-cfamily"></i>
115				</td>
116				<td>
117					<span class="subheaders"> <?= $label ?></span>
118					<a href="<?= e($family->url()) ?>"> - <?= I18N::translate('View this family') ?></a>
119				</td>
120			</tr>
121		</table>
122
123		<table class="table table-sm wt-facts-table">
124		<caption></caption>
125		<tbody>
126		<?php
127
128		///// HUSB /////
129		$found = false;
130		foreach ($family->getFacts('HUSB', false, $access_level) as $fact) {
131			$found |= !$fact->isPendingDeletion();
132			$person = $fact->getTarget();
133			if ($person instanceof Individual) {
134				$row_class = 'wt-gender-' . $person->getSex();
135				if ($fact->isPendingAddition()) {
136					$row_class .= ' new';
137				} elseif ($fact->isPendingDeletion()) {
138					$row_class .= ' old';
139				}
140				$icon = $controller->record === $person ? '<i class="icon-selected"></i>' : '';
141				?>
142					<tr class="<?= $row_class ?>">
143						<th scope="row">
144							<?= $icon ?>
145							<?= Functions::getCloseRelationshipName($controller->record, $person) ?>
146						</th>
147						<td class="border-0 p-0">
148							<?= Theme::theme()->individualBoxLarge($person) ?>
149						</td>
150					</tr>
151				<?php
152			}
153		}
154		if (!$found && $family->canEdit()) {
155			?>
156			<tr>
157				<th></th>
158				<td scope="row">
159					<a href="edit_interface.php?action=add_spouse_to_family&amp;ged=<?= $family->getTree()->getNameHtml() ?>&amp;xref=<?= $family->getXref() ?>&amp;famtag=HUSB">
160						<?= I18N::translate('Add a husband to this family') ?>
161					</a>
162					</td>
163			</tr>
164			<?php
165		}
166
167		///// WIFE /////
168		$found = false;
169		foreach ($family->getFacts('WIFE', false, $access_level) as $fact) {
170			$person = $fact->getTarget();
171			if ($person instanceof Individual) {
172				$found |= !$fact->isPendingDeletion();
173				$row_class = 'wt-gender-' . $person->getSex();
174				if ($fact->isPendingAddition()) {
175					$row_class .= ' new';
176				} elseif ($fact->isPendingDeletion()) {
177					$row_class .= ' old';
178				}
179				$icon = $controller->record === $person ? '<i class="icon-selected"></i>' : '';
180				?>
181				<tr class="<?= $row_class ?>">
182					<th scope="row">
183					<?= $icon ?>
184					<?= Functions::getCloseRelationshipName($controller->record, $person) ?>
185					</th>
186					<td class="border-0 p-0">
187						<?= Theme::theme()->individualBoxLarge($person) ?>
188					</td>
189				</tr>
190				<?php
191			}
192		}
193		if (!$found && $family->canEdit()) {
194			?>
195			<tr>
196				<th scope="row"></th>
197				<td>
198					<a href="edit_interface.php?action=add_spouse_to_family&amp;ged=<?= $family->getTree()->getNameHtml() ?>&amp;xref=<?= $family->getXref() ?>&amp;famtag=WIFE">
199						<?= I18N::translate('Add a wife to this family') ?>
200					</a>
201				</td>
202			</tr>
203			<?php
204		}
205
206		///// MARR /////
207		$found = false;
208		$prev  = new Date('');
209		foreach ($family->getFacts(WT_EVENTS_MARR . '|' . WT_EVENTS_DIV, true) as $fact) {
210			$found |= !$fact->isPendingDeletion();
211			if ($fact->isPendingAddition()) {
212				$row_class = 'new';
213			} elseif ($fact->isPendingDeletion()) {
214				$row_class = 'old';
215			} else {
216				$row_class = '';
217			}
218			?>
219			<tr class="<?= $row_class ?>">
220				<th scope="row">
221				</th>
222				<td>
223					<?= GedcomTag::getLabelValue($fact->getTag(), $fact->getDate()->display() . ' — ' . $fact->getPlace()->getFullName()) ?>
224				</td>
225			</tr>
226			<?php
227			if (!$prev->isOK() && $fact->getDate()->isOK()) {
228				$prev = $fact->getDate();
229			}
230		}
231		if (!$found && $family->canShow() && $family->canEdit()) {
232			// Add a new marriage
233			?>
234			<tr>
235				<th scope="row">
236				</th>
237				<td>
238					<a href="edit_interface.php?action=add&amp;ged=<?= $family->getTree()->getNameHtml() ?>&amp;xref=<?= $family->getXref() ?>&amp;fact=MARR">
239						<?= I18N::translate('Add marriage details') ?>
240					</a>
241				</td>
242			</tr>
243			<?php
244		}
245
246		///// CHIL /////
247		$child_number = 0;
248		foreach ($family->getFacts('CHIL', false, $access_level) as $fact) {
249			$person = $fact->getTarget();
250			if ($person instanceof Individual) {
251				$row_class = 'wt-gender-' . $person->getSex();
252				if ($fact->isPendingAddition()) {
253					$child_number++;
254					$row_class .= ' new';
255				} elseif ($fact->isPendingDeletion()) {
256					$row_class .= ' old';
257				} else {
258					$child_number++;
259				}
260				$next = new Date('');
261				foreach ($person->getFacts(WT_EVENTS_BIRT, true) as $bfact) {
262					if ($bfact->getDate()->isOK()) {
263						$next = $bfact->getDate();
264						break;
265					}
266				}
267				$icon = $controller->record === $person ? '<i class="icon-selected"></i>' : '';
268				?>
269				<tr class="<?= $row_class ?>">
270					<th scope="row">
271						<?= $icon ?>
272						<?= self::ageDifference($prev, $next, $child_number) ?>
273						<?= Functions::getCloseRelationshipName($controller->record, $person) ?>
274					</th>
275					<td class="border-0 p-0">
276						<?= Theme::theme()->individualBoxLarge($person) ?>
277					</td>
278				</tr>
279				<?php
280				$prev = $next;
281			}
282		}
283		// Re-order children / add a new child
284		if ($family->canEdit()) {
285			if ($type == 'FAMS') {
286				$add_child_text = I18N::translate('Add a son or daughter');
287			} else {
288				$add_child_text = I18N::translate('Add a brother or sister');
289			}
290			?>
291			<tr>
292				<th scope="row">
293					<?php if (count($family->getChildren()) > 1): ?>
294					<a href="edit_interface.php?action=reorder-children&amp;ged=<?= $family->getTree()->getNameHtml() ?>&amp;xref=<?= $family->getXref() ?>">
295						<i class="icon-media-shuffle"></i> <?= I18N::translate('Re-order children') ?>
296					</a>
297					<?php endif; ?>
298				</th>
299				<td>
300					<a href="edit_interface.php?action=add_child_to_family&amp;ged=<?= $family->getTree()->getNameHtml() ?>&amp;xref=<?= $family->getXref() ?>&amp;gender=U">
301						<?= $add_child_text ?>
302					</a>
303					<span style='white-space:nowrap;'>
304						<a href="edit_interface.php?action=add_child_to_family&amp;ged=<?= $family->getTree()->getNameHtml() ?>&amp;xref=<?= $family->getXref() ?>&amp;gender=M" class="icon-sex_m_15x15"></a>
305						<a href="edit_interface.php?action=add_child_to_family&amp;ged=<?= $family->getTree()->getNameHtml() ?>&amp;xref=<?= $family->getXref() ?>&amp;gender=F" class="icon-sex_f_15x15"></a>
306					</span>
307				</td>
308			</tr>
309			<?php
310		}
311
312		echo '</tbody>';
313		echo '</table>';
314	}
315
316	/** {@inheritdoc} */
317	public function getTabContent() {
318		global $controller;
319
320		ob_start();
321		?>
322		<table class="table table-sm wt-facts-table" role="presentation">
323			<tbody>
324				<tr>
325					<td>
326						<label>
327							<input id="show-date-differences" type="checkbox" checked>
328							<?= I18N::translate('Date differences') ?>
329						</label>
330					</td>
331				</tr>
332			</tbody>
333		</table>
334		<?php
335		$families = $controller->record->getChildFamilies();
336		if (!$families && $controller->record->canEdit()) {
337			?>
338			<table class="table table-sm wt-facts-table">
339				<tbody>
340					<tr>
341						<td>
342							<a href="edit_interface.php?action=add_parent_to_individual&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>&amp;gender=M">
343								<?= I18N::translate('Add a father') ?>
344							</a>
345						</td>
346					</tr>
347					<tr>
348						<td>
349							<a href="edit_interface.php?action=add_parent_to_individual&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>&amp;gender=F">
350								<?= I18N::translate('Add a mother') ?>
351							</a>
352						</td>
353					</tr>
354				</tbody>
355			</table>
356			<?php
357		}
358
359		// parents
360		foreach ($families as $family) {
361			$this->printFamily($family, 'FAMC', $controller->record->getChildFamilyLabel($family));
362		}
363
364		// step-parents
365		foreach ($controller->record->getChildStepFamilies() as $family) {
366			$this->printFamily($family, 'FAMC', $controller->record->getStepFamilyLabel($family));
367		}
368
369		// spouses
370		$families = $controller->record->getSpouseFamilies();
371		foreach ($families as $family) {
372			$this->printFamily($family, 'FAMS', $controller->getSpouseFamilyLabel($family, $controller->record));
373		}
374
375		// step-children
376		foreach ($controller->record->getSpouseStepFamilies() as $family) {
377			$this->printFamily($family, 'FAMS', $family->getFullName());
378		}
379
380		if ($controller->record->canEdit()) {
381		?>
382		<br>
383		<table class="table table-sm wt-facts-table">
384			<tbody>
385				<?php if (count($families) > 1) { ?>
386				<tr>
387					<td>
388						<a href="edit_interface.php?action=reorder-spouses&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>">
389						<?= I18N::translate('Re-order families') ?>
390						</a>
391					</td>
392				</tr>
393			<?php } ?>
394				<tr>
395					<td>
396					<a href="edit_interface.php?action=addfamlink&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>"><?= I18N::translate('Link this individual to an existing family as a child') ?></a>
397					</td>
398				</tr>
399				<?php if ($controller->record->getSex() !== 'F') { ?>
400				<tr>
401					<td>
402					<a href="edit_interface.php?action=add_spouse_to_individual&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>&amp;sex=F"><?= I18N::translate('Add a wife') ?></a>
403					</td>
404				</tr>
405				<tr>
406					<td>
407					<a href="edit_interface.php?action=linkspouse&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>&amp;famtag=WIFE"><?= I18N::translate('Add a wife using an existing individual') ?></a>
408					</td>
409				</tr>
410				<?php } ?>
411				<?php if ($controller->record->getSex() !== 'M') { ?>
412				<tr>
413					<td>
414					<a href="edit_interface.php?action=add_spouse_to_individual&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>&amp;sex=M"><?= I18N::translate('Add a husband') ?></a>
415					</td>
416				</tr>
417				<tr>
418					<td>
419					<a href="edit_interface.php?action=linkspouse&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>&amp;famtag=HUSB"><?= I18N::translate('Add a husband using an existing individual') ?></a>
420					</td>
421				</tr>
422				<?php } ?>
423				<tr>
424					<td>
425						<a href="edit_interface.php?action=add_child_to_individual&amp;ged=<?= $controller->record->getTree()->getNameHtml() ?>&amp;xref=<?= $controller->record->getXref() ?>&amp;gender=U">
426							<?= I18N::translate('Add a child to create a one-parent family') ?>
427						</a>
428					</td>
429				</tr>
430			</tbody>
431		</table>
432		<?php } ?>
433		<br>
434		<script>
435			//persistent_toggle("show-date-differences", ".elderdate");
436		</script>
437		<?php
438
439		return '<div id="' . $this->getName() . '_content">' . ob_get_clean() . '</div>';
440	}
441
442	/** {@inheritdoc} */
443	public function hasTabContent() {
444		return true;
445	}
446	/** {@inheritdoc} */
447	public function isGrayedOut() {
448		return false;
449	}
450	/** {@inheritdoc} */
451	public function canLoadAjax() {
452		return false;
453	}
454
455	/** {@inheritdoc} */
456	public function getPreLoadContent() {
457		return '';
458	}
459}
460