1<?php 2/** 3 * webtrees: online genealogy 4 * Copyright (C) 2019 webtrees development team 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16declare(strict_types=1); 17 18namespace Fisharebest\Webtrees\Module; 19 20use Fisharebest\Webtrees\Auth; 21use Fisharebest\Webtrees\Fact; 22use Fisharebest\Webtrees\Gedcom; 23use Fisharebest\Webtrees\GedcomTag; 24use Fisharebest\Webtrees\I18N; 25use Fisharebest\Webtrees\Individual; 26use Fisharebest\Webtrees\Menu; 27use Fisharebest\Webtrees\Services\ModuleService; 28use Fisharebest\Webtrees\Tree; 29use Symfony\Component\HttpFoundation\Request; 30 31/** 32 * Trait ModuleThemeTrait - default implementation of ModuleThemeInterface 33 */ 34trait ModuleThemeTrait 35{ 36 /** 37 * Add markup to the secondary menu. 38 * 39 * @param Tree|null $tree 40 * 41 * @return string 42 */ 43 public function formatUserMenu(?Tree $tree): string 44 { 45 return 46 '<ul class="nav wt-user-menu">' . 47 implode('', array_map(function (Menu $menu): string { 48 return $this->formatUserMenuItem($menu); 49 }, $this->userMenu($tree))) . 50 '</ul>'; 51 } 52 53 /** 54 * Add markup to an item in the secondary menu. 55 * 56 * @param Menu $menu 57 * 58 * @return string 59 */ 60 public function formatUserMenuItem(Menu $menu): string 61 { 62 return $menu->bootstrap4(); 63 } 64 65 /** 66 * Display an icon for this fact. 67 * 68 * @TODO use CSS for this 69 * 70 * @param Fact $fact 71 * 72 * @return string 73 */ 74 public function icon(Fact $fact): string 75 { 76 $asset = 'public/css/' . $this->name() . '/images/facts/' . $fact->getTag() . '.png'; 77 if (file_exists(WT_ROOT . 'public' . $asset)) { 78 return '<img src="' . e(asset($asset)) . '" title="' . GedcomTag::getLabel($fact->getTag()) . '">'; 79 } 80 81 // Spacer image - for alignment - until we move to a sprite. 82 $asset = 'public/css/' . $this->name() . '/images/facts/NULL.png'; 83 if (file_exists(WT_ROOT . 'public' . $asset)) { 84 return '<img src="' . e(asset($asset)) . '">'; 85 } 86 87 return ''; 88 } 89 90 /** 91 * Display an individual in a box - for charts, etc. 92 * 93 * @param Individual $individual 94 * 95 * @return string 96 */ 97 public function individualBox(Individual $individual): string 98 { 99 return view('chart-box', ['individual' => $individual]); 100 } 101 102 /** 103 * Display an empty box - for a missing individual in a chart. 104 * 105 * @return string 106 */ 107 public function individualBoxEmpty(): string 108 { 109 return '<div class="wt-chart-box"></div>'; 110 } 111 112 /** 113 * Display an individual in a box - for charts, etc. 114 * 115 * @param Individual $individual 116 * 117 * @return string 118 */ 119 public function individualBoxLarge(Individual $individual): string 120 { 121 return $this->individualBox($individual); 122 } 123 124 /** 125 * Display an individual in a box - for charts, etc. 126 * 127 * @param Individual $individual 128 * 129 * @return string 130 */ 131 public function individualBoxSmall(Individual $individual): string 132 { 133 return $this->individualBox($individual); 134 } 135 136 /** 137 * Display an individual in a box - for charts, etc. 138 * 139 * @return string 140 */ 141 public function individualBoxSmallEmpty(): string 142 { 143 return '<div class="wt-chart-box"></div>'; 144 } 145 146 /** 147 * Generate the facts, for display in charts. 148 * 149 * @param Individual $individual 150 * 151 * @return string 152 */ 153 public function individualBoxFacts(Individual $individual): string 154 { 155 $html = ''; 156 157 $opt_tags = preg_split('/\W/', $individual->tree()->getPreference('CHART_BOX_TAGS'), 0, PREG_SPLIT_NO_EMPTY); 158 // Show BIRT or equivalent event 159 foreach (Gedcom::BIRTH_EVENTS as $birttag) { 160 if (!in_array($birttag, $opt_tags)) { 161 $event = $individual->facts([$birttag])->first(); 162 if ($event instanceof Fact) { 163 $html .= $event->summary(); 164 break; 165 } 166 } 167 } 168 // Show optional events (before death) 169 foreach ($opt_tags as $key => $tag) { 170 if (!in_array($tag, Gedcom::DEATH_EVENTS)) { 171 $event = $individual->facts([$tag])->first(); 172 if ($event instanceof Fact) { 173 $html .= $event->summary(); 174 unset($opt_tags[$key]); 175 } 176 } 177 } 178 // Show DEAT or equivalent event 179 foreach (Gedcom::DEATH_EVENTS as $deattag) { 180 $event = $individual->facts([$deattag])->first(); 181 if ($event instanceof Fact) { 182 $html .= $event->summary(); 183 if (in_array($deattag, $opt_tags)) { 184 unset($opt_tags[array_search($deattag, $opt_tags)]); 185 } 186 break; 187 } 188 } 189 // Show remaining optional events (after death) 190 foreach ($opt_tags as $tag) { 191 $event = $individual->facts([$tag])->first(); 192 if ($event instanceof Fact) { 193 $html .= $event->summary(); 194 } 195 } 196 197 return $html; 198 } 199 200 /** 201 * Links, to show in chart boxes; 202 * 203 * @param Individual $individual 204 * 205 * @return Menu[] 206 */ 207 public function individualBoxMenu(Individual $individual): array 208 { 209 $menus = array_merge( 210 $this->individualBoxMenuCharts($individual), 211 $this->individualBoxMenuFamilyLinks($individual) 212 ); 213 214 return $menus; 215 } 216 217 /** 218 * Chart links, to show in chart boxes; 219 * 220 * @param Individual $individual 221 * 222 * @return Menu[] 223 */ 224 public function individualBoxMenuCharts(Individual $individual): array 225 { 226 $menus = []; 227 foreach (app(ModuleService::class)->findByComponent(ModuleChartInterface::class, $individual->tree(), Auth::user()) as $chart) { 228 $menu = $chart->chartBoxMenu($individual); 229 if ($menu) { 230 $menus[] = $menu; 231 } 232 } 233 234 usort($menus, function (Menu $x, Menu $y) { 235 return I18N::strcasecmp($x->getLabel(), $y->getLabel()); 236 }); 237 238 return $menus; 239 } 240 241 /** 242 * Family links, to show in chart boxes. 243 * 244 * @param Individual $individual 245 * 246 * @return Menu[] 247 */ 248 public function individualBoxMenuFamilyLinks(Individual $individual): array 249 { 250 $menus = []; 251 252 foreach ($individual->spouseFamilies() as $family) { 253 $menus[] = new Menu('<strong>' . I18N::translate('Family with spouse') . '</strong>', $family->url()); 254 $spouse = $family->spouse($individual); 255 if ($spouse && $spouse->canShowName()) { 256 $menus[] = new Menu($spouse->fullName(), $spouse->url()); 257 } 258 foreach ($family->children() as $child) { 259 if ($child->canShowName()) { 260 $menus[] = new Menu($child->fullName(), $child->url()); 261 } 262 } 263 } 264 265 return $menus; 266 } 267 268 /** 269 * Generate a menu item to change the blocks on the current (index.php) page. 270 * 271 * @param Tree $tree 272 * 273 * @return Menu|null 274 */ 275 public function menuChangeBlocks(Tree $tree): ?Menu 276 { 277 $request = app(Request::class); 278 279 if (Auth::check() && $request->get('route') === 'user-page') { 280 return new Menu(I18N::translate('Customize this page'), route('user-page-edit', ['ged' => $tree->name()]), 'menu-change-blocks'); 281 } 282 283 if (Auth::isManager($tree) && $request->get('route') === 'tree-page') { 284 return new Menu(I18N::translate('Customize this page'), route('tree-page-edit', ['ged' => $tree->name()]), 'menu-change-blocks'); 285 } 286 287 return null; 288 } 289 290 /** 291 * Generate a menu item for the control panel. 292 * 293 * @param Tree $tree 294 * 295 * @return Menu|null 296 */ 297 public function menuControlPanel(Tree $tree): ?Menu 298 { 299 if (Auth::isAdmin()) { 300 return new Menu(I18N::translate('Control panel'), route('admin-control-panel'), 'menu-admin'); 301 } 302 303 if (Auth::isManager($tree)) { 304 return new Menu(I18N::translate('Control panel'), route('admin-control-panel-manager'), 'menu-admin'); 305 } 306 307 return null; 308 } 309 310 /** 311 * A menu to show a list of available languages. 312 * 313 * @return Menu|null 314 */ 315 public function menuLanguages(): ?Menu 316 { 317 $menu = new Menu(I18N::translate('Language'), '#', 'menu-language'); 318 319 foreach (I18N::activeLocales() as $locale) { 320 $language_tag = $locale->languageTag(); 321 $class = 'menu-language-' . $language_tag . (WT_LOCALE === $language_tag ? ' active' : ''); 322 $menu->addSubmenu(new Menu($locale->endonym(), '#', $class, [ 323 'onclick' => 'return false;', 324 'data-language' => $language_tag, 325 ])); 326 } 327 328 if (count($menu->getSubmenus()) > 1) { 329 return $menu; 330 } 331 332 return null; 333 } 334 335 /** 336 * A login menu option (or null if we are already logged in). 337 * 338 * @return Menu|null 339 */ 340 public function menuLogin(): ?Menu 341 { 342 if (Auth::check()) { 343 return null; 344 } 345 346 // Return to this page after login... 347 $url = app(Request::class)->getRequestUri(); 348 349 // ...but switch from the tree-page to the user-page 350 $url = str_replace('route=tree-page', 'route=user-page', $url); 351 352 return new Menu(I18N::translate('Sign in'), route('login', ['url' => $url]), 'menu-login', ['rel' => 'nofollow']); 353 } 354 355 /** 356 * A logout menu option (or null if we are already logged out). 357 * 358 * @return Menu|null 359 */ 360 public function menuLogout(): ?Menu 361 { 362 if (Auth::check()) { 363 return new Menu(I18N::translate('Sign out'), route('logout'), 'menu-logout'); 364 } 365 366 return null; 367 } 368 369 /** 370 * A link to allow users to edit their account settings. 371 * 372 * @return Menu|null 373 */ 374 public function menuMyAccount(): ?Menu 375 { 376 if (Auth::check()) { 377 return new Menu(I18N::translate('My account'), route('my-account')); 378 } 379 380 return null; 381 } 382 383 /** 384 * A link to the user's individual record (individual.php). 385 * 386 * @param Tree $tree 387 * 388 * @return Menu|null 389 */ 390 public function menuMyIndividualRecord(Tree $tree): ?Menu 391 { 392 $record = Individual::getInstance($tree->getUserPreference(Auth::user(), 'gedcomid'), $tree); 393 394 if ($record) { 395 return new Menu(I18N::translate('My individual record'), $record->url(), 'menu-myrecord'); 396 } 397 398 return null; 399 } 400 401 /** 402 * A link to the user's personal home page. 403 * 404 * @param Tree $tree 405 * 406 * @return Menu 407 */ 408 public function menuMyPage(Tree $tree): Menu 409 { 410 return new Menu(I18N::translate('My page'), route('user-page', ['ged' => $tree->name()]), 'menu-mypage'); 411 } 412 413 /** 414 * A menu for the user's personal pages. 415 * 416 * @param Tree|null $tree 417 * 418 * @return Menu|null 419 */ 420 public function menuMyPages(?Tree $tree): ?Menu 421 { 422 if ($tree instanceof Tree && Auth::id()) { 423 return new Menu(I18N::translate('My pages'), '#', 'menu-mymenu', [], array_filter([ 424 $this->menuMyPage($tree), 425 $this->menuMyIndividualRecord($tree), 426 $this->menuMyPedigree($tree), 427 $this->menuMyAccount(), 428 $this->menuControlPanel($tree), 429 $this->menuChangeBlocks($tree), 430 ])); 431 } 432 433 return null; 434 } 435 436 /** 437 * A link to the user's individual record. 438 * 439 * @param Tree $tree 440 * 441 * @return Menu|null 442 */ 443 public function menuMyPedigree(Tree $tree): ?Menu 444 { 445 $gedcomid = $tree->getUserPreference(Auth::user(), 'gedcomid'); 446 447 $pedigree_chart = app(ModuleService::class)->findByComponent(ModuleChartInterface::class, $tree, Auth::user()) 448 ->filter(function (ModuleInterface $module): bool { 449 return $module instanceof PedigreeChartModule; 450 }); 451 452 if ($gedcomid !== '' && $pedigree_chart instanceof PedigreeChartModule) { 453 return new Menu( 454 I18N::translate('My pedigree'), 455 route('pedigree', [ 456 'xref' => $gedcomid, 457 'ged' => $tree->name(), 458 ]), 459 'menu-mypedigree' 460 ); 461 } 462 463 return null; 464 } 465 466 /** 467 * Create a pending changes menu. 468 * 469 * @param Tree|null $tree 470 * 471 * @return Menu|null 472 */ 473 public function menuPendingChanges(?Tree $tree): ?Menu 474 { 475 if ($tree instanceof Tree && $tree->hasPendingEdit() && Auth::isModerator($tree)) { 476 $url = route('show-pending', [ 477 'ged' => $tree->name(), 478 'url' => app(Request::class)->getRequestUri(), 479 ]); 480 481 return new Menu(I18N::translate('Pending changes'), $url, 'menu-pending'); 482 } 483 484 return null; 485 } 486 487 /** 488 * Themes menu. 489 * 490 * @return Menu|null 491 */ 492 public function menuThemes(): ?Menu 493 { 494 $themes = app(ModuleService::class)->findByInterface(ModuleThemeInterface::class, false, true); 495 496 $current_theme = app(ModuleThemeInterface::class); 497 498 if ($themes->count() > 1) { 499 $submenus = $themes->map(function (ModuleThemeInterface $theme) use ($current_theme): Menu { 500 $active = $theme->name() === $current_theme->name(); 501 $class = 'menu-theme-' . $theme->name() . ($active ? ' active' : ''); 502 503 return new Menu($theme->title(), '#', $class, [ 504 'onclick' => 'return false;', 505 'data-theme' => $theme->name(), 506 ]); 507 }); 508 509 return new Menu(I18N::translate('Theme'), '#', 'menu-theme', [], $submenus->all()); 510 } 511 512 return null; 513 } 514 515 /** 516 * Misecellaneous dimensions, fonts, styles, etc. 517 * 518 * @param string $parameter_name 519 * 520 * @return string|int|float 521 */ 522 public function parameter($parameter_name) 523 { 524 return ''; 525 } 526 527 /** 528 * Generate a list of items for the main menu. 529 * 530 * @param Tree|null $tree 531 * 532 * @return Menu[] 533 */ 534 public function genealogyMenu(?Tree $tree): array 535 { 536 if ($tree === null) { 537 return []; 538 } 539 540 return app(ModuleService::class)->findByComponent(ModuleMenuInterface::class, $tree, Auth::user()) 541 ->map(function (ModuleMenuInterface $menu) use ($tree): ?Menu { 542 return $menu->getMenu($tree); 543 }) 544 ->filter() 545 ->all(); 546 } 547 548 /** 549 * Create the genealogy menu. 550 * 551 * @param Menu[] $menus 552 * 553 * @return string 554 */ 555 public function genealogyMenuContent(array $menus): string 556 { 557 return implode('', array_map(function (Menu $menu): string { 558 return $menu->bootstrap4(); 559 }, $menus)); 560 } 561 562 /** 563 * Generate a list of items for the user menu. 564 * 565 * @param Tree|null $tree 566 * 567 * @return Menu[] 568 */ 569 public function userMenu(?Tree $tree): array 570 { 571 return array_filter([ 572 $this->menuPendingChanges($tree), 573 $this->menuMyPages($tree), 574 $this->menuThemes(), 575 $this->menuLanguages(), 576 $this->menuLogin(), 577 $this->menuLogout(), 578 ]); 579 } 580 581 /** 582 * A list of CSS files to include for this page. 583 * 584 * @return string[] 585 */ 586 public function stylesheets(): array 587 { 588 return []; 589 } 590} 591