1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Module; 21 22use Fisharebest\Webtrees\Auth; 23use Fisharebest\Webtrees\Contracts\UserInterface; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\Individual; 26use Fisharebest\Webtrees\Module\InteractiveTree\TreeView; 27use Fisharebest\Webtrees\Registry; 28use Fisharebest\Webtrees\Services\ModuleService; 29use Fisharebest\Webtrees\Tree; 30use Fisharebest\Webtrees\Validator; 31use Illuminate\Support\Str; 32use Psr\Http\Message\ServerRequestInterface; 33 34use function extract; 35use function uasort; 36use function view; 37 38use const EXTR_OVERWRITE; 39 40/** 41 * Class ChartsBlockModule 42 */ 43class ChartsBlockModule extends AbstractModule implements ModuleBlockInterface 44{ 45 use ModuleBlockTrait; 46 47 private ModuleService $module_service; 48 49 /** 50 * @param ModuleService $module_service 51 */ 52 public function __construct(ModuleService $module_service) 53 { 54 $this->module_service = $module_service; 55 } 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/block */ 65 return I18N::translate('Charts'); 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 “Charts” module */ 76 return I18N::translate('An alternative way to display charts.'); 77 } 78 79 /** 80 * Generate the HTML content of this block. 81 * 82 * @param Tree $tree 83 * @param int $block_id 84 * @param string $context 85 * @param array<string,string> $config 86 * 87 * @return string 88 */ 89 public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): string 90 { 91 $PEDIGREE_ROOT_ID = $tree->getPreference('PEDIGREE_ROOT_ID'); 92 $gedcomid = $tree->getUserPreference(Auth::user(), UserInterface::PREF_TREE_ACCOUNT_XREF); 93 $default_xref = $gedcomid ?: $PEDIGREE_ROOT_ID; 94 95 $type = $this->getBlockSetting($block_id, 'type', 'pedigree'); 96 $xref = $this->getBlockSetting($block_id, 'pid', $default_xref); 97 98 extract($config, EXTR_OVERWRITE); 99 100 $individual = Registry::individualFactory()->make($xref, $tree); 101 102 $title = $this->title(); 103 104 if ($individual instanceof Individual) { 105 switch ($type) { 106 default: 107 case 'pedigree': 108 $module = $this->module_service->findByInterface(PedigreeChartModule::class)->first(); 109 if ($module instanceof PedigreeChartModule) { 110 $title = $module->chartTitle($individual); 111 $chart_url = $module->chartUrl($individual, [ 112 'ajax' => true, 113 'generations' => 3, 114 'layout' => PedigreeChartModule::STYLE_RIGHT, 115 ]); 116 $content = view('modules/charts/chart', [ 117 'block_id' => $block_id, 118 'chart_url' => $chart_url, 119 ]); 120 } else { 121 $title = I18N::translate('Pedigree'); 122 $content = I18N::translate('The module “%s” has been disabled.', $title); 123 } 124 break; 125 126 case 'descendants': 127 $module = $this->module_service->findByInterface(DescendancyChartModule::class)->first(); 128 129 if ($module instanceof DescendancyChartModule) { 130 $title = $module->chartTitle($individual); 131 $chart_url = $module->chartUrl($individual, [ 132 'ajax' => true, 133 'generations' => 2, 134 'chart_style' => DescendancyChartModule::CHART_STYLE_TREE, 135 ]); 136 $content = view('modules/charts/chart', [ 137 'block_id' => $block_id, 138 'chart_url' => $chart_url, 139 ]); 140 } else { 141 $title = I18N::translate('Descendants'); 142 $content = I18N::translate('The module “%s” has been disabled.', $title); 143 } 144 145 break; 146 147 case 'hourglass': 148 $module = $this->module_service->findByInterface(HourglassChartModule::class)->first(); 149 150 if ($module instanceof HourglassChartModule) { 151 $title = $module->chartTitle($individual); 152 $chart_url = $module->chartUrl($individual, [ 153 'ajax' => true, 154 'generations' => 2, 155 ]); 156 $content = view('modules/charts/chart', [ 157 'block_id' => $block_id, 158 'chart_url' => $chart_url, 159 ]); 160 } else { 161 $title = I18N::translate('Hourglass chart'); 162 $content = I18N::translate('The module “%s” has been disabled.', $title); 163 } 164 break; 165 166 case 'treenav': 167 $module = $this->module_service->findByInterface(InteractiveTreeModule::class)->first(); 168 169 if ($module instanceof InteractiveTreeModule) { 170 $title = I18N::translate('Interactive tree of %s', $individual->fullName()); 171 $tv = new TreeView(); 172 [$html, $js] = $tv->drawViewport($individual, 2); 173 $content = $html . '<script>' . $js . '</script>'; 174 } else { 175 $title = I18N::translate('Interactive tree'); 176 $content = I18N::translate('The module “%s” has been disabled.', $title); 177 } 178 179 break; 180 } 181 } else { 182 $content = I18N::translate('You must select an individual and a chart type in the block preferences'); 183 } 184 185 if ($context !== self::CONTEXT_EMBED) { 186 return view('modules/block-template', [ 187 'block' => Str::kebab($this->name()), 188 'id' => $block_id, 189 'config_url' => $this->configUrl($tree, $context, $block_id), 190 'title' => $title, 191 'content' => $content, 192 ]); 193 } 194 195 return $content; 196 } 197 198 /** 199 * Should this block load asynchronously using AJAX? 200 * 201 * Simple blocks are faster in-line, more complex ones can be loaded later. 202 * 203 * @return bool 204 */ 205 public function loadAjax(): bool 206 { 207 return true; 208 } 209 210 /** 211 * Can this block be shown on the tree’s home page? 212 * 213 * @return bool 214 */ 215 public function isTreeBlock(): bool 216 { 217 return true; 218 } 219 220 /** 221 * Update the configuration for a block. 222 * 223 * @param ServerRequestInterface $request 224 * @param int $block_id 225 * 226 * @return void 227 */ 228 public function saveBlockConfiguration(ServerRequestInterface $request, int $block_id): void 229 { 230 $type = Validator::parsedBody($request)->string('type'); 231 $xref = Validator::parsedBody($request)->isXref()->string('xref'); 232 233 $this->setBlockSetting($block_id, 'type', $type); 234 $this->setBlockSetting($block_id, 'pid', $xref); 235 } 236 237 /** 238 * An HTML form to edit block settings 239 * 240 * @param Tree $tree 241 * @param int $block_id 242 * 243 * @return string 244 */ 245 public function editBlockConfiguration(Tree $tree, int $block_id): string 246 { 247 $PEDIGREE_ROOT_ID = $tree->getPreference('PEDIGREE_ROOT_ID'); 248 $gedcomid = $tree->getUserPreference(Auth::user(), UserInterface::PREF_TREE_ACCOUNT_XREF); 249 $default_xref = $gedcomid ?: $PEDIGREE_ROOT_ID; 250 251 $type = $this->getBlockSetting($block_id, 'type', 'pedigree'); 252 $xref = $this->getBlockSetting($block_id, 'pid', $default_xref); 253 254 $charts = [ 255 'pedigree' => I18N::translate('Pedigree'), 256 'descendants' => I18N::translate('Descendants'), 257 'hourglass' => I18N::translate('Hourglass chart'), 258 'treenav' => I18N::translate('Interactive tree'), 259 ]; 260 uasort($charts, I18N::comparator()); 261 262 $individual = Registry::individualFactory()->make($xref, $tree); 263 264 return view('modules/charts/config', [ 265 'charts' => $charts, 266 'individual' => $individual, 267 'tree' => $tree, 268 'type' => $type, 269 ]); 270 } 271} 272