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\Functions\FunctionsCharts; 23use Fisharebest\Webtrees\Functions\FunctionsPrint; 24use Fisharebest\Webtrees\Gedcom; 25use Fisharebest\Webtrees\I18N; 26use Fisharebest\Webtrees\Individual; 27use Fisharebest\Webtrees\Menu; 28use Fisharebest\Webtrees\Services\ChartService; 29use Fisharebest\Webtrees\Tree; 30use Illuminate\Support\Collection; 31use Psr\Http\Message\ResponseInterface; 32use Psr\Http\Message\ServerRequestInterface; 33use function view; 34 35/** 36 * Class AncestorsChartModule 37 */ 38class AncestorsChartModule extends AbstractModule implements ModuleChartInterface 39{ 40 use ModuleChartTrait; 41 42 // Chart styles 43 protected const CHART_STYLE_LIST = 'list'; 44 protected const CHART_STYLE_BOOKLET = 'booklet'; 45 protected const CHART_STYLE_INDIVIDUALS = 'individuals'; 46 protected const CHART_STYLE_FAMILIES = 'families'; 47 48 // Defaults 49 protected const DEFAULT_COUSINS = false; 50 protected const DEFAULT_STYLE = self::CHART_STYLE_LIST; 51 protected const DEFAULT_GENERATIONS = '4'; 52 53 // Limits 54 protected const MINIMUM_GENERATIONS = 2; 55 protected const MAXIMUM_GENERATIONS = 10; 56 57 /** 58 * How should this module be identified in the control panel, etc.? 59 * 60 * @return string 61 */ 62 public function title(): string 63 { 64 /* I18N: Name of a module/chart */ 65 return I18N::translate('Ancestors'); 66 } 67 68 /** 69 * A sentence describing what this module does. 70 * 71 * @return string 72 */ 73 public function description(): string 74 { 75 /* I18N: Description of the “AncestorsChart” module */ 76 return I18N::translate('A chart of an individual’s ancestors.'); 77 } 78 79 /** 80 * CSS class for the URL. 81 * 82 * @return string 83 */ 84 public function chartMenuClass(): string 85 { 86 return 'menu-chart-ancestry'; 87 } 88 89 /** 90 * Return a menu item for this chart - for use in individual boxes. 91 * 92 * @param Individual $individual 93 * 94 * @return Menu|null 95 */ 96 public function chartBoxMenu(Individual $individual): ?Menu 97 { 98 return $this->chartMenu($individual); 99 } 100 101 /** 102 * The title for a specific instance of this chart. 103 * 104 * @param Individual $individual 105 * 106 * @return string 107 */ 108 public function chartTitle(Individual $individual): string 109 { 110 /* I18N: %s is an individual’s name */ 111 return I18N::translate('Ancestors of %s', $individual->fullName()); 112 } 113 114 /** 115 * A form to request the chart parameters. 116 * 117 * @param ServerRequestInterface $request 118 * @param Tree $tree 119 * @param UserInterface $user 120 * @param ChartService $chart_service 121 * 122 * @return ResponseInterface 123 */ 124 public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user, ChartService $chart_service): ResponseInterface 125 { 126 $ajax = (bool) $request->get('ajax'); 127 $xref = $request->get('xref', ''); 128 $individual = Individual::getInstance($xref, $tree); 129 130 Auth::checkIndividualAccess($individual); 131 Auth::checkComponentAccess($this, 'chart', $tree, $user); 132 133 $show_cousins = (bool) $request->get('show_cousins', self::DEFAULT_COUSINS); 134 $chart_style = $request->get('chart_style', self::DEFAULT_STYLE); 135 $generations = (int) $request->get('generations', self::DEFAULT_GENERATIONS); 136 137 $generations = min($generations, self::MAXIMUM_GENERATIONS); 138 $generations = max($generations, self::MINIMUM_GENERATIONS); 139 140 if ($ajax) { 141 $ancestors = $chart_service->sosaStradonitzAncestors($individual, $generations); 142 143 switch ($chart_style) { 144 default: 145 case self::CHART_STYLE_LIST: 146 return response(view('modules/ancestors-chart/list', ['individual' => $individual, 'parents' => $individual->primaryChildFamily(), 'generations' => $generations, 'sosa' => 1])); 147 148 case self::CHART_STYLE_BOOKLET: 149 return $this->ancestorsBooklet($ancestors, $show_cousins); 150 151 case self::CHART_STYLE_INDIVIDUALS: 152 return $this->ancestorsIndividuals($tree, $ancestors); 153 154 case self::CHART_STYLE_FAMILIES: 155 return $this->ancestorsFamilies($tree, $ancestors); 156 } 157 } 158 159 $ajax_url = $this->chartUrl($individual, [ 160 'generations' => $generations, 161 'chart_style' => $chart_style, 162 'show_cousins' => $show_cousins, 163 'ajax' => true, 164 ]); 165 166 return $this->viewResponse('modules/ancestors-chart/page', [ 167 'ajax_url' => $ajax_url, 168 'chart_style' => $chart_style, 169 'chart_styles' => $this->chartStyles(), 170 'default_generations' => self::DEFAULT_GENERATIONS, 171 'generations' => $generations, 172 'individual' => $individual, 173 'maximum_generations' => self::MAXIMUM_GENERATIONS, 174 'minimum_generations' => self::MINIMUM_GENERATIONS, 175 'module_name' => $this->name(), 176 'show_cousins' => $show_cousins, 177 'title' => $this->chartTitle($individual), 178 ]); 179 } 180 181 /** 182 * Show a tabular list of individual ancestors. 183 * 184 * @param Tree $tree 185 * @param Collection $ancestors 186 * 187 * @return ResponseInterface 188 */ 189 protected function ancestorsIndividuals(Tree $tree, Collection $ancestors): ResponseInterface 190 { 191 $this->layout = 'layouts/ajax'; 192 193 return $this->viewResponse('lists/individuals-table', [ 194 'individuals' => $ancestors, 195 'sosa' => true, 196 'tree' => $tree, 197 ]); 198 } 199 200 /** 201 * Show a tabular list of individual ancestors. 202 * 203 * @param Tree $tree 204 * @param Collection $ancestors 205 * 206 * @return ResponseInterface 207 */ 208 protected function ancestorsFamilies(Tree $tree, Collection $ancestors): ResponseInterface 209 { 210 $this->layout = 'layouts/ajax'; 211 212 $families = []; 213 foreach ($ancestors as $individual) { 214 foreach ($individual->childFamilies() as $family) { 215 $families[$family->xref()] = $family; 216 } 217 } 218 219 return $this->viewResponse('lists/families-table', [ 220 'families' => $families, 221 'tree' => $tree, 222 ]); 223 } 224 225 /** 226 * Show a booklet view of ancestors 227 * 228 * @TODO replace ob_start() with views. 229 * 230 * @param Collection $ancestors 231 * @param bool $show_cousins 232 * 233 * @return ResponseInterface 234 */ 235 protected function ancestorsBooklet(Collection $ancestors, bool $show_cousins): ResponseInterface 236 { 237 ob_start(); 238 239 echo view('chart-box', ['individual' => $ancestors[1]]); 240 foreach ($ancestors as $sosa => $individual) { 241 foreach ($individual->childFamilies() as $family) { 242 FunctionsCharts::printSosaFamily($family, $individual->xref(), $sosa, '', '', '', $show_cousins); 243 } 244 } 245 246 $html = ob_get_clean(); 247 248 return response($html); 249 } 250 251 /** 252 * This chart can display its output in a number of styles 253 * 254 * @return array 255 */ 256 protected function chartStyles(): array 257 { 258 return [ 259 self::CHART_STYLE_LIST => I18N::translate('List'), 260 self::CHART_STYLE_BOOKLET => I18N::translate('Booklet'), 261 self::CHART_STYLE_INDIVIDUALS => I18N::translate('Individuals'), 262 self::CHART_STYLE_FAMILIES => I18N::translate('Families'), 263 ]; 264 } 265} 266