1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Module; 21 22use Aura\Router\Route; 23use Fisharebest\Webtrees\Auth; 24use Fisharebest\Webtrees\Fact; 25use Fisharebest\Webtrees\Factory; 26use Fisharebest\Webtrees\Gedcom; 27use Fisharebest\Webtrees\Http\RequestHandlers\AccountEdit; 28use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel; 29use Fisharebest\Webtrees\Http\RequestHandlers\HomePage; 30use Fisharebest\Webtrees\Http\RequestHandlers\LoginPage; 31use Fisharebest\Webtrees\Http\RequestHandlers\Logout; 32use Fisharebest\Webtrees\Http\RequestHandlers\PendingChanges; 33use Fisharebest\Webtrees\Http\RequestHandlers\SelectLanguage; 34use Fisharebest\Webtrees\Http\RequestHandlers\SelectTheme; 35use Fisharebest\Webtrees\Http\RequestHandlers\TreePage; 36use Fisharebest\Webtrees\Http\RequestHandlers\TreePageEdit; 37use Fisharebest\Webtrees\Http\RequestHandlers\UserPage; 38use Fisharebest\Webtrees\Http\RequestHandlers\UserPageEdit; 39use Fisharebest\Webtrees\I18N; 40use Fisharebest\Webtrees\Individual; 41use Fisharebest\Webtrees\Menu; 42use Fisharebest\Webtrees\Services\ModuleService; 43use Fisharebest\Webtrees\Tree; 44use Fisharebest\Webtrees\User; 45use Psr\Http\Message\ServerRequestInterface; 46 47use function app; 48use function assert; 49use function route; 50use function view; 51 52/** 53 * Trait ModuleThemeTrait - default implementation of ModuleThemeInterface 54 */ 55trait ModuleThemeTrait 56{ 57 /** 58 * A sentence describing what this module does. 59 * 60 * @return string 61 */ 62 public function description(): string 63 { 64 return I18N::translate('Theme') . ' — ' . $this->title(); 65 } 66 67 /** 68 * Generate the facts, for display in charts. 69 * 70 * @param Individual $individual 71 * 72 * @return string 73 */ 74 public function individualBoxFacts(Individual $individual): string 75 { 76 $html = ''; 77 78 $opt_tags = preg_split('/\W/', $individual->tree()->getPreference('CHART_BOX_TAGS'), 0, PREG_SPLIT_NO_EMPTY); 79 // Show BIRT or equivalent event 80 foreach (Gedcom::BIRTH_EVENTS as $birttag) { 81 if (!in_array($birttag, $opt_tags, true)) { 82 $event = $individual->facts([$birttag])->first(); 83 if ($event instanceof Fact) { 84 $html .= $event->summary(); 85 break; 86 } 87 } 88 } 89 // Show optional events (before death) 90 foreach ($opt_tags as $key => $tag) { 91 if (!in_array($tag, Gedcom::DEATH_EVENTS, true)) { 92 $event = $individual->facts([$tag])->first(); 93 if ($event instanceof Fact) { 94 $html .= $event->summary(); 95 unset($opt_tags[$key]); 96 } 97 } 98 } 99 // Show DEAT or equivalent event 100 foreach (Gedcom::DEATH_EVENTS as $deattag) { 101 $event = $individual->facts([$deattag])->first(); 102 if ($event instanceof Fact) { 103 $html .= $event->summary(); 104 if (in_array($deattag, $opt_tags, true)) { 105 unset($opt_tags[array_search($deattag, $opt_tags, true)]); 106 } 107 break; 108 } 109 } 110 // Show remaining optional events (after death) 111 foreach ($opt_tags as $tag) { 112 $event = $individual->facts([$tag])->first(); 113 if ($event instanceof Fact) { 114 $html .= $event->summary(); 115 } 116 } 117 118 return $html; 119 } 120 121 /** 122 * Links, to show in chart boxes; 123 * 124 * @param Individual $individual 125 * 126 * @return Menu[] 127 */ 128 public function individualBoxMenu(Individual $individual): array 129 { 130 return array_merge( 131 $this->individualBoxMenuCharts($individual), 132 $this->individualBoxMenuFamilyLinks($individual) 133 ); 134 } 135 136 /** 137 * Chart links, to show in chart boxes; 138 * 139 * @param Individual $individual 140 * 141 * @return Menu[] 142 */ 143 public function individualBoxMenuCharts(Individual $individual): array 144 { 145 $menus = []; 146 foreach (app(ModuleService::class)->findByComponent(ModuleChartInterface::class, $individual->tree(), Auth::user()) as $chart) { 147 $menu = $chart->chartBoxMenu($individual); 148 if ($menu) { 149 $menus[] = $menu; 150 } 151 } 152 153 usort($menus, static function (Menu $x, Menu $y): int { 154 return I18N::strcasecmp($x->getLabel(), $y->getLabel()); 155 }); 156 157 return $menus; 158 } 159 160 /** 161 * Family links, to show in chart boxes. 162 * 163 * @param Individual $individual 164 * 165 * @return Menu[] 166 */ 167 public function individualBoxMenuFamilyLinks(Individual $individual): array 168 { 169 $menus = []; 170 171 foreach ($individual->spouseFamilies() as $family) { 172 $menus[] = new Menu('<strong>' . I18N::translate('Family with spouse') . '</strong>', $family->url()); 173 $spouse = $family->spouse($individual); 174 if ($spouse && $spouse->canShowName()) { 175 $menus[] = new Menu($spouse->fullName(), $spouse->url()); 176 } 177 foreach ($family->children() as $child) { 178 if ($child->canShowName()) { 179 $menus[] = new Menu($child->fullName(), $child->url()); 180 } 181 } 182 } 183 184 return $menus; 185 } 186 187 /** 188 * Generate a menu item to change the blocks on the current tree/user page. 189 * 190 * @param Tree $tree 191 * 192 * @return Menu|null 193 */ 194 public function menuChangeBlocks(Tree $tree): ?Menu 195 { 196 /** @var ServerRequestInterface $request */ 197 $request = app(ServerRequestInterface::class); 198 199 $route = $request->getAttribute('route'); 200 assert($route instanceof Route); 201 202 if (Auth::check() && $route->name === UserPage::class) { 203 return new Menu(I18N::translate('Customize this page'), route(UserPageEdit::class, ['tree' => $tree->name()]), 'menu-change-blocks'); 204 } 205 206 if (Auth::isManager($tree) && $route->name === TreePage::class) { 207 return new Menu(I18N::translate('Customize this page'), route(TreePageEdit::class, ['tree' => $tree->name()]), 'menu-change-blocks'); 208 } 209 210 return null; 211 } 212 213 /** 214 * Generate a menu item for the control panel. 215 * 216 * @param Tree $tree 217 * 218 * @return Menu|null 219 */ 220 public function menuControlPanel(Tree $tree): ?Menu 221 { 222 if (Auth::isAdmin()) { 223 return new Menu(I18N::translate('Control panel'), route(ControlPanel::class), 'menu-admin'); 224 } 225 226 if (Auth::isManager($tree)) { 227 return new Menu(I18N::translate('Control panel'), route('manage-trees', ['tree' => $tree->name()]), 'menu-admin'); 228 } 229 230 return null; 231 } 232 233 /** 234 * A menu to show a list of available languages. 235 * 236 * @return Menu|null 237 */ 238 public function menuLanguages(): ?Menu 239 { 240 $menu = new Menu(I18N::translate('Language'), '#', 'menu-language'); 241 242 foreach (I18N::activeLocales() as $active_locale) { 243 $language_tag = $active_locale->languageTag(); 244 $class = 'menu-language-' . $language_tag . (I18N::languageTag() === $language_tag ? ' active' : ''); 245 $menu->addSubmenu(new Menu($active_locale->endonym(), '#', $class, [ 246 'data-post-url' => route(SelectLanguage::class, ['language' => $language_tag]), 247 ])); 248 } 249 250 if (count($menu->getSubmenus()) > 1) { 251 return $menu; 252 } 253 254 return null; 255 } 256 257 /** 258 * A login menu option (or null if we are already logged in). 259 * 260 * @return Menu|null 261 */ 262 public function menuLogin(): ?Menu 263 { 264 if (Auth::check()) { 265 return null; 266 } 267 268 $request = app(ServerRequestInterface::class); 269 270 // Return to this page after login... 271 $redirect = $request->getQueryParams()['url'] ?? (string) $request->getUri(); 272 273 $tree = $request->getAttribute('tree'); 274 $route = $request->getAttribute('route'); 275 assert($route instanceof Route); 276 277 // ...but switch from the tree-page to the user-page 278 if ($route->name === TreePage::class) { 279 $redirect = route(UserPage::class, ['tree' => $tree instanceof Tree ? $tree->name() : null]); 280 } 281 282 // Stay on the same tree page 283 $url = route(LoginPage::class, ['tree' => $tree instanceof Tree ? $tree->name() : null, 'url' => $redirect]); 284 285 return new Menu(I18N::translate('Sign in'), $url, 'menu-login', ['rel' => 'nofollow']); 286 } 287 288 /** 289 * A logout menu option (or null if we are already logged out). 290 * 291 * @return Menu|null 292 */ 293 public function menuLogout(): ?Menu 294 { 295 if (Auth::check()) { 296 $parameters = [ 297 'data-post-url' => route(Logout::class), 298 'data-reload-url' => route(HomePage::class) 299 ]; 300 301 return new Menu(I18N::translate('Sign out'), '#', 'menu-logout', $parameters); 302 } 303 304 return null; 305 } 306 307 /** 308 * A link to allow users to edit their account settings. 309 * 310 * @param Tree|null $tree 311 * 312 * @return Menu 313 */ 314 public function menuMyAccount(?Tree $tree): Menu 315 { 316 $url = route(AccountEdit::class, ['tree' => $tree instanceof Tree ? $tree->name() : null]); 317 318 return new Menu(I18N::translate('My account'), $url, 'menu-myaccount'); 319 } 320 321 /** 322 * A link to the user's individual record (individual.php). 323 * 324 * @param Tree $tree 325 * 326 * @return Menu|null 327 */ 328 public function menuMyIndividualRecord(Tree $tree): ?Menu 329 { 330 $record = Factory::individual()->make($tree->getUserPreference(Auth::user(), User::PREF_TREE_ACCOUNT_XREF), $tree); 331 332 if ($record) { 333 return new Menu(I18N::translate('My individual record'), $record->url(), 'menu-myrecord'); 334 } 335 336 return null; 337 } 338 339 /** 340 * A link to the user's personal home page. 341 * 342 * @param Tree $tree 343 * 344 * @return Menu 345 */ 346 public function menuMyPage(Tree $tree): Menu 347 { 348 return new Menu(I18N::translate('My page'), route(UserPage::class, ['tree' => $tree->name()]), 'menu-mypage'); 349 } 350 351 /** 352 * A menu for the user's personal pages. 353 * 354 * @param Tree|null $tree 355 * 356 * @return Menu|null 357 */ 358 public function menuMyPages(?Tree $tree): ?Menu 359 { 360 if (Auth::id()) { 361 if ($tree instanceof Tree) { 362 return new Menu(I18N::translate('My pages'), '#', 'menu-mymenu', [], array_filter([ 363 $this->menuMyPage($tree), 364 $this->menuMyIndividualRecord($tree), 365 $this->menuMyPedigree($tree), 366 $this->menuMyAccount($tree), 367 $this->menuControlPanel($tree), 368 $this->menuChangeBlocks($tree), 369 ])); 370 } 371 372 return $this->menuMyAccount($tree); 373 } 374 375 return null; 376 } 377 378 /** 379 * A link to the user's individual record. 380 * 381 * @param Tree $tree 382 * 383 * @return Menu|null 384 */ 385 public function menuMyPedigree(Tree $tree): ?Menu 386 { 387 $gedcomid = $tree->getUserPreference(Auth::user(), User::PREF_TREE_ACCOUNT_XREF); 388 389 $pedigree_chart = app(ModuleService::class)->findByComponent(ModuleChartInterface::class, $tree, Auth::user()) 390 ->filter(static function (ModuleInterface $module): bool { 391 return $module instanceof PedigreeChartModule; 392 }); 393 394 if ($gedcomid !== '' && $pedigree_chart instanceof PedigreeChartModule) { 395 return new Menu( 396 I18N::translate('My pedigree'), 397 route('pedigree', [ 398 'xref' => $gedcomid, 399 'tree' => $tree->name(), 400 ]), 401 'menu-mypedigree' 402 ); 403 } 404 405 return null; 406 } 407 408 /** 409 * Create a pending changes menu. 410 * 411 * @param Tree|null $tree 412 * 413 * @return Menu|null 414 */ 415 public function menuPendingChanges(?Tree $tree): ?Menu 416 { 417 if ($tree instanceof Tree && $tree->hasPendingEdit() && Auth::isModerator($tree)) { 418 $url = route(PendingChanges::class, [ 419 'tree' => $tree->name(), 420 'url' => (string) app(ServerRequestInterface::class)->getUri(), 421 ]); 422 423 return new Menu(I18N::translate('Pending changes'), $url, 'menu-pending'); 424 } 425 426 return null; 427 } 428 429 /** 430 * Themes menu. 431 * 432 * @return Menu|null 433 */ 434 public function menuThemes(): ?Menu 435 { 436 $themes = app(ModuleService::class)->findByInterface(ModuleThemeInterface::class, false, true); 437 438 $current_theme = app(ModuleThemeInterface::class); 439 440 if ($themes->count() > 1) { 441 $submenus = $themes->map(static function (ModuleThemeInterface $theme) use ($current_theme): Menu { 442 $active = $theme->name() === $current_theme->name(); 443 $class = 'menu-theme-' . $theme->name() . ($active ? ' active' : ''); 444 445 return new Menu($theme->title(), '#', $class, [ 446 'data-post-url' => route(SelectTheme::class, ['theme' => $theme->name()]), 447 ]); 448 }); 449 450 return new Menu(I18N::translate('Theme'), '#', 'menu-theme', [], $submenus->all()); 451 } 452 453 return null; 454 } 455 456 /** 457 * Misecellaneous dimensions, fonts, styles, etc. 458 * 459 * @param string $parameter_name 460 * 461 * @return string|int|float 462 */ 463 public function parameter($parameter_name) 464 { 465 return ''; 466 } 467 468 /** 469 * Generate a list of items for the main menu. 470 * 471 * @param Tree|null $tree 472 * 473 * @return Menu[] 474 */ 475 public function genealogyMenu(?Tree $tree): array 476 { 477 if ($tree === null) { 478 return []; 479 } 480 481 return app(ModuleService::class)->findByComponent(ModuleMenuInterface::class, $tree, Auth::user()) 482 ->map(static function (ModuleMenuInterface $menu) use ($tree): ?Menu { 483 return $menu->getMenu($tree); 484 }) 485 ->filter() 486 ->all(); 487 } 488 489 /** 490 * Create the genealogy menu. 491 * 492 * @param Menu[] $menus 493 * 494 * @return string 495 */ 496 public function genealogyMenuContent(array $menus): string 497 { 498 return implode('', array_map(static function (Menu $menu): string { 499 return view('components/menu-item', ['menu' => $menu]); 500 }, $menus)); 501 } 502 503 /** 504 * Generate a list of items for the user menu. 505 * 506 * @param Tree|null $tree 507 * 508 * @return Menu[] 509 */ 510 public function userMenu(?Tree $tree): array 511 { 512 return array_filter([ 513 $this->menuPendingChanges($tree), 514 $this->menuMyPages($tree), 515 $this->menuThemes(), 516 $this->menuLanguages(), 517 $this->menuLogin(), 518 $this->menuLogout(), 519 ]); 520 } 521 522 /** 523 * A list of CSS files to include for this page. 524 * 525 * @return string[] 526 */ 527 public function stylesheets(): array 528 { 529 return []; 530 } 531} 532