xref: /webtrees/app/Module/DescendancyModule.php (revision e2a378d30d9bd3fff591da7a11c7cb5ead502323)
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
19use Zend_Session;
20
21/**
22 * Class DescendancyModule
23 */
24class DescendancyModule extends AbstractModule implements ModuleSidebarInterface {
25	/** {@inheritdoc} */
26	public function getTitle() {
27		return /* I18N: Name of a module/sidebar */
28			I18N::translate('Descendants');
29	}
30
31	/** {@inheritdoc} */
32	public function getDescription() {
33		return /* I18N: Description of the “Descendants” module */
34			I18N::translate('A sidebar showing the descendants of an individual.');
35	}
36
37	/** {@inheritdoc} */
38	public function modAction($modAction) {
39		global $WT_TREE;
40
41		Zend_Session::writeClose();
42		header('Content-Type: text/html; charset=UTF-8');
43
44		switch ($modAction) {
45		case 'search':
46			$search = Filter::get('search');
47			echo $this->search($search);
48			break;
49		case 'descendants':
50			$individual = Individual::getInstance(Filter::get('xref', WT_REGEX_XREF), $WT_TREE);
51			if ($individual) {
52				echo $this->loadSpouses($individual, 1);
53			}
54			break;
55		default:
56			http_response_code(404);
57			break;
58		}
59	}
60
61	/** {@inheritdoc} */
62	public function defaultSidebarOrder() {
63		return 30;
64	}
65
66	/** {@inheritdoc} */
67	public function hasSidebarContent() {
68		return !Auth::isSearchEngine();
69	}
70
71	/** {@inheritdoc} */
72	public function getSidebarAjaxContent() {
73		return '';
74	}
75
76	/** {@inheritdoc} */
77	public function getSidebarContent() {
78		global $controller;
79
80		$controller->addInlineJavascript('
81			function dsearchQ() {
82				var query = jQuery("#sb_desc_name").val();
83				if (query.length>1) {
84					jQuery("#sb_desc_content").load("module.php?mod=' . $this->getName() . '&mod_action=search&search="+query);
85				}
86			}
87
88			jQuery("#sb_desc_name").focus(function(){this.select();});
89			jQuery("#sb_desc_name").blur(function(){if (this.value=="") this.value="' . I18N::translate('Search') . '";});
90			var dtimerid = null;
91			jQuery("#sb_desc_name").keyup(function(e) {
92				if (dtimerid) window.clearTimeout(dtimerid);
93				dtimerid = window.setTimeout("dsearchQ()", 500);
94			});
95
96			jQuery("#sb_desc_content").on("click", ".sb_desc_indi", function() {
97				var self = jQuery(this),
98					state = self.children(".plusminus"),
99					target = self.siblings("div");
100				if(state.hasClass("icon-plus")) {
101					if (jQuery.trim(target.html())) {
102						target.show("fast"); // already got content so just show it
103					} else {
104						target
105							.hide()
106							.load(self.attr("href"), function(response, status, xhr) {
107								if(status == "success" && response !== "") {
108									target.show("fast");
109								}
110							})
111					}
112				} else {
113					target.hide("fast");
114				}
115				state.toggleClass("icon-minus icon-plus");
116				return false;
117			});
118		');
119
120		return
121			'<form method="post" action="module.php?mod=' . $this->getName() . '&amp;mod_action=search" onsubmit="return false;">' .
122			'<input type="search" name="sb_desc_name" id="sb_desc_name" placeholder="' . I18N::translate('Search') . '">' .
123			'</form>' .
124			'<div id="sb_desc_content">' .
125			'<ul>' . $this->getPersonLi($controller->record, 1) . '</ul>' .
126			'</div>';
127	}
128
129	/**
130	 * @param Individual $person
131	 * @param integer       $generations
132	 *
133	 * @return string
134	 */
135	public function getPersonLi(Individual $person, $generations = 0) {
136		$icon = $generations > 0 ? 'icon-minus' : 'icon-plus';
137		$lifespan = $person->canShow() ? '(' . $person->getLifeSpan() . ')' : '';
138		$spouses = $generations > 0 ? $this->loadSpouses($person, 0) : '';
139		return sprintf('<li class="sb_desc_indi_li">
140		                  <a class="sb_desc_indi" href="module.php?mod=%s&amp;mod_action=descendants&amp;xref=%s"><i class="plusminus %s"></i>%s %s %s</a>
141		                  <a class="icon-button_indi" href="%s"></a>
142		                  %s
143		                  <div>%s</div>
144		                </li>', $this->getName(), $person->getXref(), $icon, $person->getSexImage(), $person->getFullName(), $lifespan, $person->getHtmlUrl(), '', $spouses);
145	}
146
147	/**
148	 * @param Family     $family
149	 * @param Individual $person
150	 * @param integer       $generations
151	 *
152	 * @return string
153	 */
154	public function getFamilyLi(Family $family, Individual $person, $generations = 0) {
155		$marryear = $family->getMarriageYear();
156		$marr = $marryear ? '<i class="icon-rings"></i>' . $marryear : '';
157		$fam = '<a href="' . $family->getHtmlUrl() . '" class="icon-button_family"></a>';
158		$kids = $this->loadChildren($family, $generations);
159		return sprintf('<li class="sb_desc_indi_li">
160		                  <a class="sb_desc_indi" href="#"><i class="plusminus icon-minus"></i>%s %s %s</a>
161		                  <a class="icon-button_indi" href="%s"></a>
162		                  %s
163		                  <div>%s</div>
164		                </li>', $person->getSexImage(), $person->getFullName(), $marr, $person->getHtmlUrl(), $fam, $kids);
165	}
166
167	/**
168	 * @param string $query
169	 *
170	 * @return string
171	 */
172	public function search($query) {
173		global $WT_TREE;
174
175		if (strlen($query) < 2) {
176			return '';
177		}
178		$rows = Database::prepare(
179			"SELECT i_id AS xref" .
180			" FROM `##individuals`, `##name`" .
181			" WHERE (i_id LIKE ? OR n_sort LIKE ?)" .
182			" AND i_id=n_id AND i_file=n_file AND i_file=?" .
183			" ORDER BY n_sort"
184		)
185			->execute(array("%{$query}%", "%{$query}%", $WT_TREE->getTreeId()))
186			->fetchAll();
187
188		$out = '';
189		foreach ($rows as $row) {
190			$person = Individual::getInstance($row->xref, $WT_TREE);
191			if ($person->canShowName()) {
192				$out .= $this->getPersonLi($person);
193			}
194		}
195		if ($out) {
196			return '<ul>' . $out . '</ul>';
197		} else {
198			return '';
199		}
200	}
201
202	/**
203	 * @param Individual $person
204	 * @param integer       $generations
205	 *
206	 * @return string
207	 */
208	public function loadSpouses(Individual $person, $generations) {
209		$out = '';
210		if ($person && $person->canShow()) {
211			foreach ($person->getSpouseFamilies() as $family) {
212				$spouse = $family->getSpouse($person);
213				if ($spouse) {
214					$out .= $this->getFamilyLi($family, $spouse, $generations - 1);
215				}
216			}
217			if (!$out) {
218				$out = '<li class="sb_desc_none">' . I18N::translate('No children') . '</li>';
219			}
220		}
221		if ($out) {
222			return '<ul>' . $out . '</ul>';
223		} else {
224			return '';
225		}
226	}
227
228	/**
229	 * @param Family $family
230	 * @param integer   $generations
231	 *
232	 * @return string
233	 */
234	public function loadChildren(Family $family, $generations) {
235		$out = '';
236		if ($family->canShow()) {
237			$children = $family->getChildren();
238			if ($children) {
239				foreach ($children as $child) {
240					$out .= $this->getPersonLi($child, $generations - 1);
241				}
242			} else {
243				$out .= '<li class="sb_desc_none">' . I18N::translate('No children') . '</li>';
244			}
245		}
246		if ($out) {
247			return '<ul>' . $out . '</ul>';
248		} else {
249			return '';
250		}
251	}
252}
253