1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 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 Illuminate\Support\Str; 31use Psr\Http\Message\ServerRequestInterface; 32 33use function extract; 34use function uasort; 35use function view; 36 37use const EXTR_OVERWRITE; 38 39/** 40 * Class ChartsBlockModule 41 */ 42class ChartsBlockModule extends AbstractModule implements ModuleBlockInterface 43{ 44 use ModuleBlockTrait; 45 46 private ModuleService $module_service; 47 48 /** 49 * ChartsBlockModule constructor. 50 * 51 * @param ModuleService $module_service 52 */ 53 public function __construct(ModuleService $module_service) 54 { 55 $this->module_service = $module_service; 56 } 57 58 /** 59 * How should this module be identified in the control panel, etc.? 60 * 61 * @return string 62 */ 63 public function title(): string 64 { 65 /* I18N: Name of a module/block */ 66 return I18N::translate('Charts'); 67 } 68 69 /** 70 * A sentence describing what this module does. 71 * 72 * @return string 73 */ 74 public function description(): string 75 { 76 /* I18N: Description of the “Charts” module */ 77 return I18N::translate('An alternative way to display charts.'); 78 } 79 80 /** 81 * Generate the HTML content of this block. 82 * 83 * @param Tree $tree 84 * @param int $block_id 85 * @param string $context 86 * @param array<string> $config 87 * 88 * @return string 89 */ 90 public function getBlock(Tree $tree, int $block_id, string $context, array $config = []): string 91 { 92 $PEDIGREE_ROOT_ID = $tree->getPreference('PEDIGREE_ROOT_ID'); 93 $gedcomid = $tree->getUserPreference(Auth::user(), UserInterface::PREF_TREE_ACCOUNT_XREF); 94 $default_xref = $gedcomid ?: $PEDIGREE_ROOT_ID; 95 96 $type = $this->getBlockSetting($block_id, 'type', 'pedigree'); 97 $xref = $this->getBlockSetting($block_id, 'pid', $default_xref); 98 99 extract($config, EXTR_OVERWRITE); 100 101 $individual = Registry::individualFactory()->make($xref, $tree); 102 103 $title = $this->title(); 104 105 if ($individual instanceof Individual) { 106 switch ($type) { 107 default: 108 case 'pedigree': 109 $module = $this->module_service->findByInterface(PedigreeChartModule::class)->first(); 110 if ($module instanceof PedigreeChartModule) { 111 $title = $module->chartTitle($individual); 112 $chart_url = $module->chartUrl($individual, [ 113 'ajax' => true, 114 'generations' => 3, 115 'layout' => PedigreeChartModule::STYLE_RIGHT, 116 ]); 117 $content = view('modules/charts/chart', [ 118 'block_id' => $block_id, 119 'chart_url' => $chart_url, 120 ]); 121 } else { 122 $title = I18N::translate('Pedigree'); 123 $content = I18N::translate('The module “%s” has been disabled.', $title); 124 } 125 break; 126 127 case 'descendants': 128 $module = $this->module_service->findByInterface(DescendancyChartModule::class)->first(); 129 130 if ($module instanceof DescendancyChartModule) { 131 $title = $module->chartTitle($individual); 132 $chart_url = $module->chartUrl($individual, [ 133 'ajax' => true, 134 'generations' => 2, 135 'chart_style' => DescendancyChartModule::CHART_STYLE_TREE, 136 ]); 137 $content = view('modules/charts/chart', [ 138 'block_id' => $block_id, 139 'chart_url' => $chart_url, 140 ]); 141 } else { 142 $title = I18N::translate('Descendants'); 143 $content = I18N::translate('The module “%s” has been disabled.', $title); 144 } 145 146 break; 147 148 case 'hourglass': 149 $module = $this->module_service->findByInterface(HourglassChartModule::class)->first(); 150 151 if ($module instanceof HourglassChartModule) { 152 $title = $module->chartTitle($individual); 153 $chart_url = $module->chartUrl($individual, [ 154 'ajax' => true, 155 'generations' => 2, 156 ]); 157 $content = view('modules/charts/chart', [ 158 'block_id' => $block_id, 159 'chart_url' => $chart_url, 160 ]); 161 } else { 162 $title = I18N::translate('Hourglass chart'); 163 $content = I18N::translate('The module “%s” has been disabled.', $title); 164 } 165 break; 166 167 case 'treenav': 168 $module = $this->module_service->findByInterface(InteractiveTreeModule::class)->first(); 169 170 if ($module instanceof InteractiveTreeModule) { 171 $title = I18N::translate('Interactive tree of %s', $individual->fullName()); 172 $tv = new TreeView(); 173 [$html, $js] = $tv->drawViewport($individual, 2); 174 $content = $html . '<script>' . $js . '</script>'; 175 } else { 176 $title = I18N::translate('Interactive tree'); 177 $content = I18N::translate('The module “%s” has been disabled.', $title); 178 } 179 180 break; 181 } 182 } else { 183 $content = I18N::translate('You must select an individual and a chart type in the block preferences'); 184 } 185 186 if ($context !== self::CONTEXT_EMBED) { 187 return view('modules/block-template', [ 188 'block' => Str::kebab($this->name()), 189 'id' => $block_id, 190 'config_url' => $this->configUrl($tree, $context, $block_id), 191 'title' => $title, 192 'content' => $content, 193 ]); 194 } 195 196 return $content; 197 } 198 199 /** 200 * Should this block load asynchronously using AJAX? 201 * 202 * Simple blocks are faster in-line, more complex ones can be loaded later. 203 * 204 * @return bool 205 */ 206 public function loadAjax(): bool 207 { 208 return true; 209 } 210 211 /** 212 * Can this block be shown on the tree’s home page? 213 * 214 * @return bool 215 */ 216 public function isTreeBlock(): bool 217 { 218 return true; 219 } 220 221 /** 222 * Update the configuration for a block. 223 * 224 * @param ServerRequestInterface $request 225 * @param int $block_id 226 * 227 * @return void 228 */ 229 public function saveBlockConfiguration(ServerRequestInterface $request, int $block_id): void 230 { 231 $params = (array) $request->getParsedBody(); 232 233 $this->setBlockSetting($block_id, 'type', $params['type'] ?? 'pedigree'); 234 $this->setBlockSetting($block_id, 'pid', $params['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