xref: /webtrees/app/Module/FamilyNavigatorModule.php (revision d53324c994737e50e57b9e0bc691b4f742b400b2)
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\Family;
19use Fisharebest\Webtrees\Functions\Functions;
20use Fisharebest\Webtrees\I18N;
21use Fisharebest\Webtrees\Individual;
22use Fisharebest\Webtrees\Menu;
23
24/**
25 * Class FamilyNavigatorModule
26 */
27class FamilyNavigatorModule extends AbstractModule implements ModuleSidebarInterface {
28	const TTL = "<div class='flyout2'>%s</div>";
29	const LNK = "<div class='flyout3' data-href='%s'>%s</div>";
30	const MSG = "<div class='flyout4'>(%s)</div>"; // class flyout4 not used in standard themes
31
32	/** {@inheritdoc} */
33	public function getTitle() {
34		return /* I18N: Name of a module/sidebar */ I18N::translate('Family navigator');
35	}
36
37	/** {@inheritdoc} */
38	public function getDescription() {
39		return /* I18N: Description of the “Family navigator” module */ I18N::translate('A sidebar showing an individual’s close families and relatives.');
40	}
41
42	/** {@inheritdoc} */
43	public function defaultSidebarOrder() {
44		return 20;
45	}
46
47	/** {@inheritdoc} */
48	public function hasSidebarContent() {
49		return true;
50	}
51
52	/** {@inheritdoc} */
53	public function getSidebarAjaxContent() {
54		return '';
55	}
56
57	/**
58	 * Load this sidebar synchronously.
59	 *
60	 * @return string
61	 */
62	public function getSidebarContent() {
63		global $controller;
64
65		$controller->addInlineJavascript('
66			$("#sb_family_nav_content")
67				.on("click", ".flyout a", function() {
68					return false;
69				})
70				.on("click", ".flyout3", function() {
71					window.location.href = $(this).data("href");
72					return false;
73				});
74		');
75
76		ob_start();
77
78		?>
79		<div id="sb_family_nav_content">
80			<div class="nav_content">
81
82		<?php
83		//-- parent families -------------------------------------------------------------
84		foreach ($controller->record->getChildFamilies() as $family) {
85			$this->drawFamily($family, $controller->record->getChildFamilyLabel($family));
86		}
87		//-- step parents ----------------------------------------------------------------
88		foreach ($controller->record->getChildStepFamilies() as $family) {
89			$this->drawFamily($family, $controller->record->getStepFamilyLabel($family));
90		}
91		//-- spouse and children --------------------------------------------------
92		foreach ($controller->record->getSpouseFamilies() as $family) {
93			$this->drawFamily($family, $controller->getSpouseFamilyLabel($family, $controller->record));
94		}
95		//-- step children ----------------------------------------------------------------
96		foreach ($controller->record->getSpouseStepFamilies() as $family) {
97			$this->drawFamily($family, $family->getFullName());
98		}
99		?>
100			</div>
101		</div>
102		<?php
103
104		return ob_get_clean();
105	}
106
107	/**
108	 * Format a family.
109	 *
110	 * @param Family $family
111	 * @param string $title
112	 */
113	private function drawFamily(Family $family, $title) {
114		global $controller;
115
116		?>
117		<table class="table table-sm wt-facts-table">
118		<caption class="text-center">
119			<a class="famnav_title" href="<?= e($family->url()) ?>">
120				<?= $title ?>
121			</a>
122		</caption>
123		<tbody>
124			<?php
125				foreach ($family->getSpouses() as $spouse) {
126					$icon = $controller->record === $spouse ? '<i class="icon-selected"></i>' : '';
127					$menu = new Menu($icon . Functions::getCloseRelationshipName($controller->record, $spouse));
128					$menu->addSubmenu(new Menu($this->getParents($spouse)));
129					?>
130					<tr class="text-center wt-parent wt-gender-<?= $spouse->getSex() ?>">
131						<th scope="row">
132							<ul class="nav">
133								<?= $menu->bootstrap4() ?>
134							</ul>
135						</th>
136						<td>
137							<?php if ($spouse->canShow()): ?>
138							<a class="famnav_link" href="<?= e($spouse->url()) ?>">
139								<?= $spouse->getFullName() ?>
140							</a>
141							<div class="small">
142								<?= $spouse->getLifeSpan() ?>
143							</div>
144							<?php else: ?>
145								<?= $spouse->getFullName() ?>
146							<?php endif ?>
147						</td>
148					</tr>
149				<?php
150				}
151
152				foreach ($family->getChildren() as $child) {
153					$icon = $controller->record === $child ? '<i class="icon-selected"></i>' : '';
154					$menu = new Menu($icon . Functions::getCloseRelationshipName($controller->record, $child));
155					$menu->addSubmenu(new Menu($this->getFamily($child)));
156					?>
157					<tr class="text-center wt-child wt-gender-<?= $child->getSex() ?>">
158						<th scope="row">
159							<ul class="nav">
160								<?= $menu->bootstrap4() ?>
161							</ul>
162						</th>
163						<td>
164							<?php if ($child->canShow()): ?>
165							<a class="famnav_link" href="<?= e($child->url()) ?>">
166								<?= $child->getFullName() ?>
167							</a>
168							<div class="small">
169								<?= $child->getLifeSpan() ?>
170							</div>
171							<?php else: ?>
172								<?= $child->getFullName() ?>
173							<?php endif ?>
174						</td>
175					</tr>
176				<?php
177				}
178				?>
179			</tbody>
180		</table>
181		<?php
182	}
183
184	/**
185	 * Format an individual.
186	 *
187	 * @param      $person
188	 * @param bool $showUnknown
189	 *
190	 * @return string
191	 */
192	private function getHTML($person, $showUnknown = false) {
193		if ($person instanceof Individual) {
194			return sprintf(self::LNK, e($person->url()), $person->getFullName());
195		} elseif ($showUnknown) {
196			return sprintf(self::MSG, I18N::translate('unknown'));
197		} else {
198			return '';
199		}
200	}
201
202	/**
203	 * Forat the parents of an individual.
204	 *
205	 * @param Individual $person
206	 *
207	 * @return string
208	 */
209	private function getParents(Individual $person) {
210		$father = null;
211		$mother = null;
212		$html   = sprintf(self::TTL, I18N::translate('Parents'));
213		$family = $person->getPrimaryChildFamily();
214		if ($person->canShowName() && $family !== null) {
215			$father = $family->getHusband();
216			$mother = $family->getWife();
217			$html .= $this->getHTML($father) .
218					 $this->getHTML($mother);
219
220			// Can only have a step parent if one & only one parent found at this point
221			if ($father instanceof Individual xor $mother instanceof Individual) {
222				$stepParents = '';
223				foreach ($person->getChildStepFamilies() as $family) {
224					if (!$father instanceof Individual) {
225						$stepParents .= $this->getHTML($family->getHusband());
226					} else {
227						$stepParents .= $this->getHTML($family->getWife());
228					}
229				}
230				if ($stepParents) {
231					$relationship = $father instanceof Individual ?
232						I18N::translateContext('father’s wife', 'step-mother') : I18N::translateContext('mother’s husband', 'step-father');
233					$html .= sprintf(self::TTL, $relationship) . $stepParents;
234				}
235			}
236		}
237		if (!($father instanceof Individual || $mother instanceof Individual)) {
238			$html .= sprintf(self::MSG, I18N::translateContext('unknown family', 'unknown'));
239		}
240
241		return $html;
242	}
243
244	/**
245	 * Format a family.
246	 *
247	 * @param Individual $person
248	 *
249	 * @return string
250	 */
251	private function getFamily(Individual $person) {
252		$html = '';
253		if ($person->canShowName()) {
254			foreach ($person->getSpouseFamilies() as $family) {
255				$spouse = $family->getSpouse($person);
256				$html .= $this->getHTML($spouse, true);
257				$children = $family->getChildren();
258				if (count($children) > 0) {
259					$html .= "<ul class='clist'>";
260					foreach ($children as $child) {
261						$html .= '<li>' . $this->getHTML($child) . '</li>';
262					}
263					$html .= '</ul>';
264				}
265			}
266		}
267		if (!$html) {
268			$html = sprintf(self::MSG, I18N::translate('none'));
269		}
270
271		return sprintf(self::TTL, I18N::translate('Family')) . $html;
272	}
273}
274