xref: /webtrees/app/Module/DescendancyModule.php (revision ffd703ea1e658c7dcb5e5f1f9ef137a420f2d167)
1<?php
2namespace Fisharebest\Webtrees;
3
4/**
5 * webtrees: online genealogy
6 * Copyright (C) 2015 webtrees development team
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/**
20 * Class DescendancyModule
21 */
22class DescendancyModule extends AbstractModule implements ModuleSidebarInterface {
23	/** {@inheritdoc} */
24	public function getTitle() {
25		return /* I18N: Name of a module/sidebar */
26			I18N::translate('Descendants');
27	}
28
29	/** {@inheritdoc} */
30	public function getDescription() {
31		return /* I18N: Description of the “Descendants” module */
32			I18N::translate('A sidebar showing the descendants of an individual.');
33	}
34
35	/** {@inheritdoc} */
36	public function modAction($modAction) {
37		global $WT_TREE;
38
39		header('Content-Type: text/html; charset=UTF-8');
40
41		switch ($modAction) {
42		case 'search':
43			$search = Filter::get('search');
44			echo $this->search($search, $WT_TREE);
45			break;
46		case 'descendants':
47			$individual = Individual::getInstance(Filter::get('xref', WT_REGEX_XREF), $WT_TREE);
48			if ($individual) {
49				echo $this->loadSpouses($individual, 1);
50			}
51			break;
52		default:
53			http_response_code(404);
54			break;
55		}
56	}
57
58	/** {@inheritdoc} */
59	public function defaultSidebarOrder() {
60		return 30;
61	}
62
63	/** {@inheritdoc} */
64	public function hasSidebarContent() {
65		return !Auth::isSearchEngine();
66	}
67
68	/** {@inheritdoc} */
69	public function getSidebarAjaxContent() {
70		return '';
71	}
72
73	/** {@inheritdoc} */
74	public function getSidebarContent() {
75		global $controller;
76
77		$controller->addInlineJavascript('
78			function dsearchQ() {
79				var query = jQuery("#sb_desc_name").val();
80				if (query.length>1) {
81					jQuery("#sb_desc_content").load("module.php?mod=' . $this->getName() . '&mod_action=search&search="+query);
82				}
83			}
84
85			jQuery("#sb_desc_name").focus(function(){this.select();});
86			jQuery("#sb_desc_name").blur(function(){if (this.value=="") this.value="' . I18N::translate('Search') . '";});
87			var dtimerid = null;
88			jQuery("#sb_desc_name").keyup(function(e) {
89				if (dtimerid) window.clearTimeout(dtimerid);
90				dtimerid = window.setTimeout("dsearchQ()", 500);
91			});
92
93			jQuery("#sb_desc_content").on("click", ".sb_desc_indi", function() {
94				var self = jQuery(this),
95					state = self.children(".plusminus"),
96					target = self.siblings("div");
97				if(state.hasClass("icon-plus")) {
98					if (jQuery.trim(target.html())) {
99						target.show("fast"); // already got content so just show it
100					} else {
101						target
102							.hide()
103							.load(self.attr("href"), function(response, status, xhr) {
104								if(status == "success" && response !== "") {
105									target.show("fast");
106								}
107							})
108					}
109				} else {
110					target.hide("fast");
111				}
112				state.toggleClass("icon-minus icon-plus");
113				return false;
114			});
115		');
116
117		return
118			'<form method="post" action="module.php?mod=' . $this->getName() . '&amp;mod_action=search" onsubmit="return false;">' .
119			'<input type="search" name="sb_desc_name" id="sb_desc_name" placeholder="' . I18N::translate('Search') . '">' .
120			'</form>' .
121			'<div id="sb_desc_content">' .
122			'<ul>' . $this->getPersonLi($controller->record, 1) . '</ul>' .
123			'</div>';
124	}
125
126	/**
127	 * @param Individual $person
128	 * @param integer    $generations
129	 *
130	 * @return string
131	 */
132	public function getPersonLi(Individual $person, $generations = 0) {
133		$icon     = $generations > 0 ? 'icon-minus' : 'icon-plus';
134		$lifespan = $person->canShow() ? '(' . $person->getLifeSpan() . ')' : '';
135		$spouses  = $generations > 0 ? $this->loadSpouses($person, 0) : '';
136
137		return
138			'<li class="sb_desc_indi_li">' .
139			'<a class="sb_desc_indi" href="module.php?mod=' . $this->getName() . '&amp;mod_action=descendants&amp;xref=' . $person->getXref() . '">' .
140			'<i class="plusminus ' . $icon . '"></i>' .
141			$person->getSexImage() . $person->getFullName() . $lifespan .
142			'</a>' .
143			'<a class="icon-button_indi" href="' . $person->getHtmlUrl() . '"></a>' .
144			'<div>' . $spouses . '</div>' .
145			'</li>';
146	}
147
148	/**
149	 * @param Family     $family
150	 * @param Individual $person
151	 * @param integer    $generations
152	 *
153	 * @return string
154	 */
155	public function getFamilyLi(Family $family, Individual $person, $generations = 0) {
156		$spouse = $family->getSpouse($person);
157		if ($spouse) {
158			$spouse_name = $spouse->getSexImage() . $spouse->getFullName();
159			$spouse_link = '<a class="icon-button_indi" href="' . $spouse->getHtmlUrl() . '"></a>';
160		} else {
161			$spouse_name = '';
162			$spouse_link = '';
163		}
164
165		$marryear = $family->getMarriageYear();
166		$marr = $marryear ? '<i class="icon-rings"></i>' . $marryear : '';
167
168		return
169			'<li class="sb_desc_indi_li">' .
170			'<a class="sb_desc_indi" href="#"><i class="plusminus icon-minus"></i>' . $spouse_name . $marr . '</a>' .
171			$spouse_link .
172			'<a href="' . $family->getHtmlUrl() . '" class="icon-button_family"></a>' .
173		 '<div>' . $this->loadChildren($family, $generations) . '</div>' .
174			'</li>';
175	}
176
177	/**
178	 * @param string $query Search for this term
179	 * @param Tree   $tree  Search in this tree
180	 *
181	 * @return string
182	 */
183	public function search($query, Tree $tree) {
184		if (strlen($query) < 2) {
185			return '';
186		}
187
188		$rows = Database::prepare(
189			"SELECT i_id AS xref" .
190			" FROM `##individuals`" .
191			" JOIN `##name` ON i_id = n_id AND i_file = n_file" .
192			" WHERE n_sort LIKE CONCAT('%', :query, '%') AND i_file = :tree_id" .
193			" ORDER BY n_sort"
194		)->execute(array(
195			'query'   => $query,
196			'tree_id' => $tree->getTreeId(),
197		))->fetchAll();
198
199		$out = '';
200		foreach ($rows as $row) {
201			$person = Individual::getInstance($row->xref, $tree);
202			if ($person && $person->canShowName()) {
203				$out .= $this->getPersonLi($person);
204			}
205		}
206		if ($out) {
207			return '<ul>' . $out . '</ul>';
208		} else {
209			return '';
210		}
211	}
212
213	/**
214	 * @param Individual $person
215	 * @param integer    $generations
216	 *
217	 * @return string
218	 */
219	public function loadSpouses(Individual $person, $generations) {
220		$out = '';
221		if ($person && $person->canShow()) {
222			foreach ($person->getSpouseFamilies() as $family) {
223				$out .= $this->getFamilyLi($family, $person, $generations - 1);
224			}
225		}
226		if ($out) {
227			return '<ul>' . $out . '</ul>';
228		} else {
229			return '';
230		}
231	}
232
233	/**
234	 * @param Family  $family
235	 * @param integer $generations
236	 *
237	 * @return string
238	 */
239	public function loadChildren(Family $family, $generations) {
240		$out = '';
241		if ($family->canShow()) {
242			$children = $family->getChildren();
243			if ($children) {
244				foreach ($children as $child) {
245					$out .= $this->getPersonLi($child, $generations - 1);
246				}
247			} else {
248				$out .= '<li class="sb_desc_none">' . I18N::translate('No children') . '</li>';
249			}
250		}
251		if ($out) {
252			return '<ul>' . $out . '</ul>';
253		} else {
254			return '';
255		}
256	}
257}
258