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