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