1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 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 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees\Module; 19 20use Fisharebest\Webtrees\Auth; 21use Fisharebest\Webtrees\Contracts\UserInterface; 22use Fisharebest\Webtrees\Family; 23use Fisharebest\Webtrees\Functions\FunctionsCharts; 24use Fisharebest\Webtrees\Functions\FunctionsPrint; 25use Fisharebest\Webtrees\Gedcom; 26use Fisharebest\Webtrees\GedcomTag; 27use Fisharebest\Webtrees\I18N; 28use Fisharebest\Webtrees\Individual; 29use Fisharebest\Webtrees\Menu; 30use Fisharebest\Webtrees\Services\ChartService; 31use Fisharebest\Webtrees\Tree; 32use Illuminate\Support\Collection; 33use Psr\Http\Message\ResponseInterface; 34use Psr\Http\Message\ServerRequestInterface; 35use Ramsey\Uuid\Uuid; 36 37/** 38 * Class DescendancyChartModule 39 */ 40class DescendancyChartModule extends AbstractModule implements ModuleChartInterface 41{ 42 use ModuleChartTrait; 43 44 // Chart styles 45 public const CHART_STYLE_LIST = 0; 46 public const CHART_STYLE_BOOKLET = 1; 47 public const CHART_STYLE_INDIVIDUALS = 2; 48 public const CHART_STYLE_FAMILIES = 3; 49 50 // Defaults 51 public const DEFAULT_STYLE = self::CHART_STYLE_LIST; 52 public const DEFAULT_GENERATIONS = '3'; 53 54 // Limits 55 public const MINIMUM_GENERATIONS = 2; 56 public const MAXIMUM_GENERATIONS = 10; 57 58 /** @var int[] */ 59 protected $dabo_num = []; 60 61 /** @var string[] */ 62 protected $dabo_sex = []; 63 64 /** 65 * How should this module be identified in the control panel, etc.? 66 * 67 * @return string 68 */ 69 public function title(): string 70 { 71 /* I18N: Name of a module/chart */ 72 return I18N::translate('Descendants'); 73 } 74 75 /** 76 * A sentence describing what this module does. 77 * 78 * @return string 79 */ 80 public function description(): string 81 { 82 /* I18N: Description of the “DescendancyChart” module */ 83 return I18N::translate('A chart of an individual’s descendants.'); 84 } 85 86 /** 87 * CSS class for the URL. 88 * 89 * @return string 90 */ 91 public function chartMenuClass(): string 92 { 93 return 'menu-chart-descendants'; 94 } 95 96 /** 97 * Return a menu item for this chart - for use in individual boxes. 98 * 99 * @param Individual $individual 100 * 101 * @return Menu|null 102 */ 103 public function chartBoxMenu(Individual $individual): ?Menu 104 { 105 return $this->chartMenu($individual); 106 } 107 108 /** 109 * The title for a specific instance of this chart. 110 * 111 * @param Individual $individual 112 * 113 * @return string 114 */ 115 public function chartTitle(Individual $individual): string 116 { 117 /* I18N: %s is an individual’s name */ 118 return I18N::translate('Descendants of %s', $individual->fullName()); 119 } 120 121 /** 122 * A form to request the chart parameters. 123 * 124 * @param ServerRequestInterface $request 125 * @param Tree $tree 126 * @param UserInterface $user 127 * @param ChartService $chart_service 128 * 129 * @return ResponseInterface 130 */ 131 public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user, ChartService $chart_service): ResponseInterface 132 { 133 $ajax = (bool) ($request->getQueryParams()['ajax'] ?? false); 134 $xref = $request->getQueryParams()['xref'] ?? ''; 135 $individual = Individual::getInstance($xref, $tree); 136 137 Auth::checkIndividualAccess($individual); 138 Auth::checkComponentAccess($this, 'chart', $tree, $user); 139 140 $chart_style = (int) ($request->getQueryParams()['chart_style'] ?? self::DEFAULT_STYLE); 141 $generations = (int) ($request->getQueryParams()['generations'] ?? self::DEFAULT_GENERATIONS); 142 143 $generations = min($generations, self::MAXIMUM_GENERATIONS); 144 $generations = max($generations, self::MINIMUM_GENERATIONS); 145 146 if ($ajax) { 147 return $this->chart($request, $tree, $chart_service); 148 } 149 150 $ajax_url = $this->chartUrl($individual, [ 151 'chart_style' => $chart_style, 152 'generations' => $generations, 153 'ajax' => true, 154 ]); 155 156 return $this->viewResponse('modules/descendancy_chart/page', [ 157 'ajax_url' => $ajax_url, 158 'chart_style' => $chart_style, 159 'chart_styles' => $this->chartStyles(), 160 'default_generations' => self::DEFAULT_GENERATIONS, 161 'generations' => $generations, 162 'individual' => $individual, 163 'maximum_generations' => self::MAXIMUM_GENERATIONS, 164 'minimum_generations' => self::MINIMUM_GENERATIONS, 165 'module_name' => $this->name(), 166 'title' => $this->chartTitle($individual), 167 ]); 168 } 169 170 /** 171 * @param ServerRequestInterface $request 172 * @param Tree $tree 173 * @param ChartService $chart_service 174 * 175 * @return ResponseInterface 176 */ 177 public function chart(ServerRequestInterface $request, Tree $tree, ChartService $chart_service): ResponseInterface 178 { 179 $this->layout = 'layouts/ajax'; 180 181 $xref = $request->getQueryParams()['xref']; 182 $individual = Individual::getInstance($xref, $tree); 183 184 Auth::checkIndividualAccess($individual); 185 186 $chart_style = (int) $request->getQueryParams()['chart_style']; 187 $generations = (int) $request->getQueryParams()['generations']; 188 189 $generations = min($generations, self::MAXIMUM_GENERATIONS); 190 $generations = max($generations, self::MINIMUM_GENERATIONS); 191 192 switch ($chart_style) { 193 case self::CHART_STYLE_LIST: 194 default: 195 return response(view('modules/descendancy_chart/list', ['individual' => $individual, 'generations' => $generations, 'daboville' => '1'])); 196 197 case self::CHART_STYLE_BOOKLET: 198 return $this->descendantsBooklet($individual, $generations); 199 200 case self::CHART_STYLE_INDIVIDUALS: 201 $individuals = $chart_service->descendants($individual, $generations - 1); 202 203 return $this->descendantsIndividuals($tree, $individuals); 204 205 case self::CHART_STYLE_FAMILIES: 206 $families = $chart_service->descendantFamilies($individual, $generations - 1); 207 208 return $this->descendantsFamilies($tree, $families); 209 } 210 } 211 212 /** 213 * Show a tabular list of individual descendants. 214 * 215 * @param Tree $tree 216 * @param Collection $individuals 217 * 218 * @return ResponseInterface 219 */ 220 private function descendantsIndividuals(Tree $tree, Collection $individuals): ResponseInterface 221 { 222 $this->layout = 'layouts/ajax'; 223 224 return $this->viewResponse('lists/individuals-table', [ 225 'individuals' => $individuals, 226 'sosa' => false, 227 'tree' => $tree, 228 ]); 229 } 230 231 /** 232 * Show a tabular list of individual descendants. 233 * 234 * @param Tree $tree 235 * @param Collection $families 236 * 237 * @return ResponseInterface 238 */ 239 private function descendantsFamilies(Tree $tree, Collection $families): ResponseInterface 240 { 241 $this->layout = 'layouts/ajax'; 242 243 return $this->viewResponse('lists/families-table', [ 244 'families' => $families, 245 'tree' => $tree, 246 ]); 247 } 248 249 /** 250 * Show a booklet view of descendants 251 * 252 * @TODO replace ob_start() with views. 253 * 254 * @param Individual $individual 255 * @param int $generations 256 * 257 * @return ResponseInterface 258 */ 259 private function descendantsBooklet(Individual $individual, int $generations): ResponseInterface 260 { 261 ob_start(); 262 263 $this->printChildFamily($individual, $generations); 264 265 $html = ob_get_clean(); 266 267 return response($html); 268 } 269 270 /** 271 * Print a child family 272 * 273 * @param Individual $individual 274 * @param int $depth - the descendancy depth to show 275 * @param string $daboville - d'Aboville number 276 * @param string $gpid 277 * 278 * @return void 279 */ 280 private function printChildFamily(Individual $individual, $depth, $daboville = '1.', $gpid = ''): void 281 { 282 if ($depth < 2) { 283 return; 284 } 285 286 $i = 1; 287 288 foreach ($individual->spouseFamilies() as $family) { 289 FunctionsCharts::printSosaFamily($family, '', -1, $daboville, $individual->xref(), $gpid, false); 290 foreach ($family->children() as $child) { 291 $this->printChildFamily($child, $depth - 1, $daboville . ($i++) . '.', $individual->xref()); 292 } 293 } 294 } 295 296 /** 297 * This chart can display its output in a number of styles 298 * 299 * @return string[] 300 */ 301 private function chartStyles(): array 302 { 303 return [ 304 self::CHART_STYLE_LIST => I18N::translate('List'), 305 self::CHART_STYLE_BOOKLET => I18N::translate('Booklet'), 306 self::CHART_STYLE_INDIVIDUALS => I18N::translate('Individuals'), 307 self::CHART_STYLE_FAMILIES => I18N::translate('Families'), 308 ]; 309 } 310} 311