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