xref: /webtrees/app/Module/RelativesTabModule.php (revision 6664b4a34cf6b2d1fc123cfb8f05bb5dda4a7f25)
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\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"> <?php echo $label; ?></span>
118					<a class="noprint" href="<?php echo $family->getHtmlUrl(); ?>"> - <?php echo I18N::translate('View the family'); ?></a>
119				</td>
120			</tr>
121		</table>
122		<table class="facts_table">
123		<?php
124
125		///// HUSB /////
126		$found = false;
127		foreach ($family->getFacts('HUSB', false, $access_level) as $fact) {
128			$found |= !$fact->isPendingDeletion();
129			$person = $fact->getTarget();
130			if ($person instanceof Individual) {
131				if ($fact->isPendingAddition()) {
132					$class = 'facts_label new';
133				} elseif ($fact->isPendingDeletion()) {
134					$class = 'facts_label old';
135				} else {
136					$class = 'facts_label';
137				}
138				?>
139					<tr>
140					<td class="<?php echo $class; ?>">
141						<?php echo Functions::getCloseRelationshipName($controller->record, $person); ?>
142					</td>
143					<td class="<?php echo $controller->getPersonStyle($person); ?>">
144						<?php echo Theme::theme()->individualBoxLarge($person); ?>
145					</td>
146					</tr>
147				<?php
148			}
149		}
150		if (!$found && $family->canEdit()) {
151			?>
152			<tr>
153				<td class="facts_label"></td>
154				<td class="facts_value"><a href="#" onclick="return add_spouse_to_family('<?php echo $family->getXref(); ?>', 'HUSB');"><?php echo I18N::translate('Add a husband to this family'); ?></a></td>
155			</tr>
156			<?php
157		}
158
159		///// WIFE /////
160		$found = false;
161		foreach ($family->getFacts('WIFE', false, $access_level) as $fact) {
162			$person = $fact->getTarget();
163			if ($person instanceof Individual) {
164				$found |= !$fact->isPendingDeletion();
165				if ($fact->isPendingAddition()) {
166					$class = 'facts_label new';
167				} elseif ($fact->isPendingDeletion()) {
168					$class = 'facts_label old';
169				} else {
170					$class = 'facts_label';
171				}
172				?>
173				<tr>
174					<td class="<?php echo $class; ?>">
175						<?php echo Functions::getCloseRelationshipName($controller->record, $person); ?>
176					</td>
177					<td class="<?php echo $controller->getPersonStyle($person); ?>">
178						<?php echo Theme::theme()->individualBoxLarge($person); ?>
179					</td>
180				</tr>
181				<?php
182			}
183		}
184		if (!$found && $family->canEdit()) {
185			?>
186			<tr>
187				<td class="facts_label"></td>
188				<td class="facts_value"><a href="#" onclick="return add_spouse_to_family('<?php echo $family->getXref(); ?>', 'WIFE');"><?php echo I18N::translate('Add a wife to this family'); ?></a></td>
189			</tr>
190			<?php
191		}
192
193		///// MARR /////
194		$found = false;
195		$prev  = new Date('');
196		foreach ($family->getFacts(WT_EVENTS_MARR . '|' . WT_EVENTS_DIV) as $fact) {
197			$found |= !$fact->isPendingDeletion();
198			if ($fact->isPendingAddition()) {
199				$class = ' new';
200			} elseif ($fact->isPendingDeletion()) {
201				$class = ' old';
202			} else {
203				$class = '';
204			}
205			?>
206			<tr>
207				<td class="facts_label">
208				</td>
209				<td class="facts_value<?php echo $class; ?>">
210					<?php echo GedcomTag::getLabelValue($fact->getTag(), $fact->getDate()->display() . ' — ' . $fact->getPlace()->getFullName()); ?>
211				</td>
212			</tr>
213			<?php
214			if (!$prev->isOK() && $fact->getDate()->isOK()) {
215				$prev = $fact->getDate();
216			}
217		}
218		if (!$found && $family->canShow() && $family->canEdit()) {
219			// Add a new marriage
220			?>
221			<tr>
222				<td class="facts_label">
223				</td>
224				<td class="facts_value">
225					<a href="#" onclick="return add_new_record('<?php echo $family->getXref(); ?>', 'MARR');">
226						<?php echo I18N::translate('Add marriage details'); ?>
227					</a>
228				</td>
229			</tr>
230			<?php
231		}
232
233		///// CHIL /////
234		$child_number = 0;
235		foreach ($family->getFacts('CHIL', false, $access_level) as $fact) {
236			$person = $fact->getTarget();
237			if ($person instanceof Individual) {
238				if ($fact->isPendingAddition()) {
239					$child_number++;
240					$class = 'facts_label new';
241				} elseif ($fact->isPendingDeletion()) {
242					$class = 'facts_label old';
243				} else {
244					$child_number++;
245					$class = 'facts_label';
246				}
247				$next = new Date('');
248				foreach ($person->getFacts(WT_EVENTS_BIRT, true) as $bfact) {
249					if ($bfact->getDate()->isOK()) {
250						$next = $bfact->getDate();
251						break;
252					}
253				}
254				?>
255				<tr>
256					<td class="<?php echo $class; ?>">
257						<?php echo self::ageDifference($prev, $next, $child_number); ?>
258						<?php echo Functions::getCloseRelationshipName($controller->record, $person); ?>
259					</td>
260					<td class="<?php echo $controller->getPersonStyle($person); ?>">
261						<?php echo Theme::theme()->individualBoxLarge($person); ?>
262					</td>
263				</tr>
264				<?php
265				$prev = $next;
266			}
267		}
268		// Re-order children / add a new child
269		if ($family->canEdit()) {
270			if ($type == 'FAMS') {
271				$add_child_text = I18N::translate('Add a son or daughter');
272			} else {
273				$add_child_text = I18N::translate('Add a brother or sister');
274			}
275			?>
276			<tr class="noprint">
277				<td class="facts_label">
278					<?php if (count($family->getChildren()) > 1) { ?>
279					<a href="#" onclick="reorder_children('<?php echo $family->getXref(); ?>');tabswitch(5);"><i class="icon-media-shuffle"></i> <?php echo I18N::translate('Re-order children'); ?></a>
280					<?php } ?>
281				</td>
282				<td class="facts_value">
283					<a href="#" onclick="return add_child_to_family('<?php echo $family->getXref(); ?>');"><?php echo $add_child_text; ?></a>
284					<span style='white-space:nowrap;'>
285						<a href="#" class="icon-sex_m_15x15" onclick="return add_child_to_family('<?php echo $family->getXref(); ?>','M');"></a>
286						<a href="#" class="icon-sex_f_15x15" onclick="return add_child_to_family('<?php echo $family->getXref(); ?>','F');"></a>
287					</span>
288				</td>
289			</tr>
290			<?php
291		}
292
293		echo '</table>';
294
295		return;
296	}
297
298	/** {@inheritdoc} */
299	public function getTabContent() {
300		global $WT_TREE, $show_full, $controller;
301
302		if (isset($show_full)) {
303			$saved_show_full = $show_full;
304		}
305		// We always want to see full details here
306		$show_full = 1;
307
308		ob_start();
309		?>
310		<table class="facts_table"><tr class="noprint"><td class="descriptionbox rela">
311		<input id="checkbox_elder" type="checkbox" onclick="jQuery('div.elderdate').toggle();" <?php echo $WT_TREE->getPreference('SHOW_AGE_DIFF') ? 'checked' : ''; ?>>
312		<label for="checkbox_elder"><?php echo I18N::translate('Show date differences'); ?></label>
313		</td></tr></table>
314		<?php
315		$families = $controller->record->getChildFamilies();
316		if (!$families && $controller->record->canEdit()) {
317			?>
318			<table class="facts_table">
319				<tr>
320					<td class="facts_value"><a href="#" onclick="return add_parent_to_individual('<?php echo $controller->record->getXref(); ?>', 'M');"><?php echo I18N::translate('Add a father'); ?></td>
321				</tr>
322				<tr>
323					<td class="facts_value"><a href="#" onclick="return add_parent_to_individual('<?php echo $controller->record->getXref(); ?>', 'F');"><?php echo I18N::translate('Add a mother'); ?></a></td>
324				</tr>
325			</table>
326			<?php
327		}
328
329		// parents
330		foreach ($families as $family) {
331			$this->printFamily($family, 'FAMC', $controller->record->getChildFamilyLabel($family));
332		}
333
334		// step-parents
335		foreach ($controller->record->getChildStepFamilies() as $family) {
336			$this->printFamily($family, 'FAMC', $controller->record->getStepFamilyLabel($family));
337		}
338
339		// spouses
340		$families = $controller->record->getSpouseFamilies();
341		foreach ($families as $family) {
342			$this->printFamily($family, 'FAMS', $controller->getSpouseFamilyLabel($family, $controller->record));
343		}
344
345		// step-children
346		foreach ($controller->record->getSpouseStepFamilies() as $family) {
347			$this->printFamily($family, 'FAMS', $family->getFullName());
348		}
349
350		if (!$WT_TREE->getPreference('SHOW_AGE_DIFF')) {
351			echo '<script>jQuery("DIV.elderdate").toggle();</script>';
352		}
353
354		if ($controller->record->canEdit()) {
355		?>
356		<br><table class="facts_table noprint">
357		<?php
358			if (count($families) > 1) { ?>
359			<tr>
360				<td class="facts_value">
361				<a href="#" onclick="return reorder_families('<?php echo $controller->record->getXref(); ?>');"><?php echo I18N::translate('Re-order families'); ?></a>
362				</td>
363			</tr>
364		<?php } ?>
365			<tr>
366				<td class="facts_value">
367				<a href="#" onclick="return add_famc('<?php echo $controller->record->getXref(); ?>');"><?php echo I18N::translate('Link this individual to an existing family as a child'); ?></a>
368				</td>
369			</tr>
370			<?php if ($controller->record->getSex() != "F") { ?>
371			<tr>
372				<td class="facts_value">
373				<a href="#" onclick="return add_spouse_to_individual('<?php echo $controller->record->getXref(); ?>','WIFE');"><?php echo I18N::translate('Add a wife'); ?></a>
374				</td>
375			</tr>
376			<tr>
377				<td class="facts_value">
378				<a href="#" onclick="return linkspouse('<?php echo $controller->record->getXref(); ?>','WIFE');"><?php echo I18N::translate('Add a wife using an existing individual'); ?></a>
379				</td>
380			</tr>
381			<?php }
382			if ($controller->record->getSex() != "M") { ?>
383			<tr>
384				<td class="facts_value">
385				<a href="#" onclick="return add_spouse_to_individual('<?php echo $controller->record->getXref(); ?>','HUSB');"><?php echo I18N::translate('Add a husband'); ?></a>
386				</td>
387			</tr>
388			<tr>
389				<td class="facts_value">
390				<a href="#" onclick="return linkspouse('<?php echo $controller->record->getXref(); ?>','HUSB');"><?php echo I18N::translate('Add a husband using an existing individual'); ?></a>
391				</td>
392			</tr>
393			<?php } ?>
394			<tr>
395				<td class="facts_value">
396				<a href="#" onclick="return add_child_to_individual('<?php echo $controller->record->getXref(); ?>','U');"><?php echo I18N::translate('Add a child to create a one-parent family'); ?></a>
397				</td>
398			</tr>
399		</table>
400		<?php } ?>
401		<br>
402		<?php
403
404		unset($show_full);
405		if (isset($saved_show_full)) {
406			$show_full = $saved_show_full;
407		}
408
409		return '<div id="' . $this->getName() . '_content">' . ob_get_clean() . '</div>';
410	}
411
412	/** {@inheritdoc} */
413	public function hasTabContent() {
414		return true;
415	}
416	/** {@inheritdoc} */
417	public function isGrayedOut() {
418		return false;
419	}
420	/** {@inheritdoc} */
421	public function canLoadAjax() {
422		return !Auth::isSearchEngine(); // Search engines cannot use AJAX
423	}
424
425	/** {@inheritdoc} */
426	public function getPreLoadContent() {
427		return '';
428	}
429}
430