1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2019 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17declare(strict_types=1); 18 19namespace Fisharebest\Webtrees\Module; 20 21use Fisharebest\Webtrees\Family; 22use Fisharebest\Webtrees\I18N; 23use Fisharebest\Webtrees\Individual; 24use Fisharebest\Webtrees\Services\SearchService; 25use Psr\Http\Message\ResponseInterface; 26use Psr\Http\Message\ServerRequestInterface; 27 28use function view; 29 30/** 31 * Class DescendancyModule 32 */ 33class DescendancyModule extends AbstractModule implements ModuleSidebarInterface 34{ 35 use ModuleSidebarTrait; 36 37 /** @var SearchService */ 38 private $search_service; 39 40 /** 41 * DescendancyModule constructor. 42 * 43 * @param SearchService $search_service 44 */ 45 public function __construct(SearchService $search_service) 46 { 47 $this->search_service = $search_service; 48 } 49 50 /** 51 * How should this module be identified in the control panel, etc.? 52 * 53 * @return string 54 */ 55 public function title(): string 56 { 57 /* I18N: Name of a module/sidebar */ 58 return I18N::translate('Descendants'); 59 } 60 61 /** 62 * A sentence describing what this module does. 63 * 64 * @return string 65 */ 66 public function description(): string 67 { 68 /* I18N: Description of the “Descendants” module */ 69 return I18N::translate('A sidebar showing the descendants of an individual.'); 70 } 71 72 /** 73 * The default position for this sidebar. It can be changed in the control panel. 74 * 75 * @return int 76 */ 77 public function defaultSidebarOrder(): int 78 { 79 return 3; 80 } 81 82 /** 83 * @param ServerRequestInterface $request 84 * 85 * @return ResponseInterface 86 */ 87 public function getSearchAction(ServerRequestInterface $request): ResponseInterface 88 { 89 $tree = $request->getAttribute('tree'); 90 $search = $request->getQueryParams()['search']; 91 92 $html = ''; 93 94 if (strlen($search) >= 2) { 95 $html = $this->search_service 96 ->searchIndividualNames([$tree], [$search]) 97 ->map(function (Individual $individual): string { 98 return $this->getPersonLi($individual); 99 }) 100 ->implode(''); 101 } 102 103 if ($html !== '') { 104 $html = '<ul>' . $html . '</ul>'; 105 } 106 107 return response($html); 108 } 109 110 /** 111 * @param ServerRequestInterface $request 112 * 113 * @return ResponseInterface 114 */ 115 public function getDescendantsAction(ServerRequestInterface $request): ResponseInterface 116 { 117 $tree = $request->getAttribute('tree'); 118 $xref = $request->getQueryParams()['xref']; 119 120 $individual = Individual::getInstance($xref, $tree); 121 122 if ($individual !== null && $individual->canShow()) { 123 $html = $this->loadSpouses($individual, 1); 124 } else { 125 $html = ''; 126 } 127 128 return response($html); 129 } 130 131 /** {@inheritdoc} */ 132 public function hasSidebarContent(Individual $individual): bool 133 { 134 return true; 135 } 136 137 /** 138 * Load this sidebar synchronously. 139 * 140 * @param Individual $individual 141 * 142 * @return string 143 */ 144 public function getSidebarContent(Individual $individual): string 145 { 146 return view('modules/descendancy/sidebar', [ 147 'individual_list' => $this->getPersonLi($individual, 1), 148 ]); 149 } 150 151 /** 152 * Format an individual in a list. 153 * 154 * @param Individual $person 155 * @param int $generations 156 * 157 * @return string 158 */ 159 public function getPersonLi(Individual $person, $generations = 0): string 160 { 161 $icon = $generations > 0 ? 'icon-minus' : 'icon-plus'; 162 $lifespan = $person->canShow() ? '(' . $person->getLifeSpan() . ')' : ''; 163 $spouses = $generations > 0 ? $this->loadSpouses($person, 0) : ''; 164 165 return 166 '<li class="sb_desc_indi_li">' . 167 '<a class="sb_desc_indi" href="' . e(route('module', [ 168 'module' => $this->name(), 169 'action' => 'Descendants', 170 'tree' => $person->tree()->name(), 171 'xref' => $person->xref(), 172 ])) . '">' . 173 '<i class="plusminus ' . $icon . '"></i>' . 174 '<small>' . view('icons/sex-' . $person->sex()) . '</small>' . $person->fullName() . $lifespan . 175 '</a>' . 176 '<a href="' . e($person->url()) . '" title="' . strip_tags($person->fullName()) . '">' . view('icons/individual') . '</a>' . 177 '<div>' . $spouses . '</div>' . 178 '</li>'; 179 } 180 181 /** 182 * Format a family in a list. 183 * 184 * @param Family $family 185 * @param Individual $person 186 * @param int $generations 187 * 188 * @return string 189 */ 190 public function getFamilyLi(Family $family, Individual $person, $generations = 0): string 191 { 192 $spouse = $family->spouse($person); 193 if ($spouse) { 194 $spouse_name = '<small>' . view('icons/sex-' . $spouse->sex()) . '</small>' . $spouse->fullName(); 195 $spouse_link = '<a href="' . e($person->url()) . '" title="' . strip_tags($person->fullName()) . '">' . view('icons/individual') . '</a>'; 196 } else { 197 $spouse_name = ''; 198 $spouse_link = ''; 199 } 200 201 $family_link = '<a href="' . e($family->url()) . '" title="' . strip_tags($family->fullName()) . '">' . view('icons/family') . '</a>'; 202 203 $marryear = $family->getMarriageYear(); 204 $marr = $marryear ? '<i class="icon-rings"></i>' . $marryear : ''; 205 206 return 207 '<li class="sb_desc_indi_li">' . 208 '<a class="sb_desc_indi" href="#"><i class="plusminus icon-minus"></i>' . 209 $spouse_name . 210 $marr . 211 '</a>' . 212 $spouse_link . 213 $family_link . 214 '<div>' . $this->loadChildren($family, $generations) . '</div>' . 215 '</li>'; 216 } 217 218 /** 219 * Display spouses. 220 * 221 * @param Individual $individual 222 * @param int $generations 223 * 224 * @return string 225 */ 226 public function loadSpouses(Individual $individual, $generations): string 227 { 228 $out = ''; 229 if ($individual->canShow()) { 230 foreach ($individual->spouseFamilies() as $family) { 231 $out .= $this->getFamilyLi($family, $individual, $generations - 1); 232 } 233 } 234 if ($out) { 235 return '<ul>' . $out . '</ul>'; 236 } 237 238 return ''; 239 } 240 241 /** 242 * Display descendants. 243 * 244 * @param Family $family 245 * @param int $generations 246 * 247 * @return string 248 */ 249 public function loadChildren(Family $family, $generations): string 250 { 251 $out = ''; 252 if ($family->canShow()) { 253 $children = $family->children(); 254 255 if ($children->isNotEmpty()) { 256 foreach ($children as $child) { 257 $out .= $this->getPersonLi($child, $generations - 1); 258 } 259 } else { 260 $out .= '<li class="sb_desc_none">' . I18N::translate('No children') . '</li>'; 261 } 262 } 263 if ($out) { 264 return '<ul>' . $out . '</ul>'; 265 } 266 267 return ''; 268 } 269} 270