149a243cbSGreg Roach<?php 23976b470SGreg Roach 349a243cbSGreg Roach/** 449a243cbSGreg Roach * webtrees: online genealogy 5*5bfc6897SGreg Roach * Copyright (C) 2022 webtrees development team 649a243cbSGreg Roach * This program is free software: you can redistribute it and/or modify 749a243cbSGreg Roach * it under the terms of the GNU General Public License as published by 849a243cbSGreg Roach * the Free Software Foundation, either version 3 of the License, or 949a243cbSGreg Roach * (at your option) any later version. 1049a243cbSGreg Roach * This program is distributed in the hope that it will be useful, 1149a243cbSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 1249a243cbSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1349a243cbSGreg Roach * GNU General Public License for more details. 1449a243cbSGreg Roach * You should have received a copy of the GNU General Public License 1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 1649a243cbSGreg Roach */ 17fcfa147eSGreg Roach 1849a243cbSGreg Roachdeclare(strict_types=1); 1949a243cbSGreg Roach 2049a243cbSGreg Roachnamespace Fisharebest\Webtrees\Module; 2149a243cbSGreg Roach 22de2aa325SGreg Roachuse Aura\Router\Route; 23ade503dfSGreg Roachuse Fisharebest\Webtrees\Auth; 241fe542e9SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 25ade503dfSGreg Roachuse Fisharebest\Webtrees\Fact; 26ade503dfSGreg Roachuse Fisharebest\Webtrees\Gedcom; 27a49d0e3fSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\AccountEdit; 280c0910bfSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel; 29ea101122SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\HomePage; 3056f9a9c1SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\LoginPage; 3156f9a9c1SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\Logout; 326fd01894SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\ManageTrees; 3322e73debSGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\PendingChanges; 347adfb8e5SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\SelectLanguage; 357adfb8e5SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\SelectTheme; 368e0e1b25SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\TreePage; 378e0e1b25SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\TreePageEdit; 388e0e1b25SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\UserPage; 398e0e1b25SGreg Roachuse Fisharebest\Webtrees\Http\RequestHandlers\UserPageEdit; 40ade503dfSGreg Roachuse Fisharebest\Webtrees\I18N; 41ade503dfSGreg Roachuse Fisharebest\Webtrees\Individual; 42ade503dfSGreg Roachuse Fisharebest\Webtrees\Menu; 43c72a9801SGreg Roachuse Fisharebest\Webtrees\Registry; 444ca7e03cSGreg Roachuse Fisharebest\Webtrees\Services\ModuleService; 45ade503dfSGreg Roachuse Fisharebest\Webtrees\Tree; 46b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Validator; 47b55cbc6bSGreg Roachuse PhpParser\Node\Expr\AssignOp\Mod; 486ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 493976b470SGreg Roach 506ccdf4f0SGreg Roachuse function app; 51de2aa325SGreg Roachuse function assert; 527adfb8e5SGreg Roachuse function route; 53f78837dcSGreg Roachuse function view; 54ade503dfSGreg Roach 5549a243cbSGreg Roach/** 5649a243cbSGreg Roach * Trait ModuleThemeTrait - default implementation of ModuleThemeInterface 5749a243cbSGreg Roach */ 5849a243cbSGreg Roachtrait ModuleThemeTrait 5949a243cbSGreg Roach{ 60ade503dfSGreg Roach /** 61bfed30e4SGreg Roach * How should this module be identified in the control panel, etc.? 62bfed30e4SGreg Roach * 63bfed30e4SGreg Roach * @return string 64bfed30e4SGreg Roach */ 65bfed30e4SGreg Roach abstract public function title(): string; 66bfed30e4SGreg Roach 67bfed30e4SGreg Roach /** 683d8b2a8eSGreg Roach * A sentence describing what this module does. 693d8b2a8eSGreg Roach * 703d8b2a8eSGreg Roach * @return string 713d8b2a8eSGreg Roach */ 723d8b2a8eSGreg Roach public function description(): string 733d8b2a8eSGreg Roach { 743d8b2a8eSGreg Roach return I18N::translate('Theme') . ' — ' . $this->title(); 753d8b2a8eSGreg Roach } 763d8b2a8eSGreg Roach 773d8b2a8eSGreg Roach /** 78ade503dfSGreg Roach * Generate the facts, for display in charts. 79ade503dfSGreg Roach * 80ade503dfSGreg Roach * @param Individual $individual 81ade503dfSGreg Roach * 82ade503dfSGreg Roach * @return string 83ade503dfSGreg Roach */ 84ade503dfSGreg Roach public function individualBoxFacts(Individual $individual): string 85ade503dfSGreg Roach { 86ade503dfSGreg Roach $html = ''; 87ade503dfSGreg Roach 88ade503dfSGreg Roach $opt_tags = preg_split('/\W/', $individual->tree()->getPreference('CHART_BOX_TAGS'), 0, PREG_SPLIT_NO_EMPTY); 89ade503dfSGreg Roach // Show BIRT or equivalent event 90ade503dfSGreg Roach foreach (Gedcom::BIRTH_EVENTS as $birttag) { 9122d65e5aSGreg Roach if (!in_array($birttag, $opt_tags, true)) { 92820b62dfSGreg Roach $event = $individual->facts([$birttag])->first(); 93820b62dfSGreg Roach if ($event instanceof Fact) { 94ade503dfSGreg Roach $html .= $event->summary(); 95ade503dfSGreg Roach break; 96ade503dfSGreg Roach } 97ade503dfSGreg Roach } 98ade503dfSGreg Roach } 99ade503dfSGreg Roach // Show optional events (before death) 100ade503dfSGreg Roach foreach ($opt_tags as $key => $tag) { 10122d65e5aSGreg Roach if (!in_array($tag, Gedcom::DEATH_EVENTS, true)) { 102820b62dfSGreg Roach $event = $individual->facts([$tag])->first(); 103820b62dfSGreg Roach if ($event instanceof Fact) { 104ade503dfSGreg Roach $html .= $event->summary(); 105ade503dfSGreg Roach unset($opt_tags[$key]); 106ade503dfSGreg Roach } 107ade503dfSGreg Roach } 108ade503dfSGreg Roach } 109ade503dfSGreg Roach // Show DEAT or equivalent event 110ade503dfSGreg Roach foreach (Gedcom::DEATH_EVENTS as $deattag) { 111820b62dfSGreg Roach $event = $individual->facts([$deattag])->first(); 112820b62dfSGreg Roach if ($event instanceof Fact) { 113ade503dfSGreg Roach $html .= $event->summary(); 11422d65e5aSGreg Roach if (in_array($deattag, $opt_tags, true)) { 11522d65e5aSGreg Roach unset($opt_tags[array_search($deattag, $opt_tags, true)]); 116ade503dfSGreg Roach } 117ade503dfSGreg Roach break; 118ade503dfSGreg Roach } 119ade503dfSGreg Roach } 120ade503dfSGreg Roach // Show remaining optional events (after death) 121ade503dfSGreg Roach foreach ($opt_tags as $tag) { 122820b62dfSGreg Roach $event = $individual->facts([$tag])->first(); 123820b62dfSGreg Roach if ($event instanceof Fact) { 124ade503dfSGreg Roach $html .= $event->summary(); 125ade503dfSGreg Roach } 126ade503dfSGreg Roach } 127ade503dfSGreg Roach 128ade503dfSGreg Roach return $html; 129ade503dfSGreg Roach } 130ade503dfSGreg Roach 131ade503dfSGreg Roach /** 132ade503dfSGreg Roach * Links, to show in chart boxes; 133ade503dfSGreg Roach * 134ade503dfSGreg Roach * @param Individual $individual 135ade503dfSGreg Roach * 13609482a55SGreg Roach * @return array<Menu> 137ade503dfSGreg Roach */ 138ade503dfSGreg Roach public function individualBoxMenu(Individual $individual): array 139ade503dfSGreg Roach { 140aa6311c7SGreg Roach return array_merge( 141ade503dfSGreg Roach $this->individualBoxMenuCharts($individual), 142ade503dfSGreg Roach $this->individualBoxMenuFamilyLinks($individual) 143ade503dfSGreg Roach ); 144ade503dfSGreg Roach } 145ade503dfSGreg Roach 146ade503dfSGreg Roach /** 147ade503dfSGreg Roach * Chart links, to show in chart boxes; 148ade503dfSGreg Roach * 149ade503dfSGreg Roach * @param Individual $individual 150ade503dfSGreg Roach * 15109482a55SGreg Roach * @return array<Menu> 152ade503dfSGreg Roach */ 153ade503dfSGreg Roach public function individualBoxMenuCharts(Individual $individual): array 154ade503dfSGreg Roach { 155ade503dfSGreg Roach $menus = []; 156b55cbc6bSGreg Roach 157b55cbc6bSGreg Roach $module_service = app(ModuleService::class); 158b55cbc6bSGreg Roach assert($module_service instanceof ModuleService); 159b55cbc6bSGreg Roach 160b55cbc6bSGreg Roach foreach ($module_service->findByComponent(ModuleChartInterface::class, $individual->tree(), Auth::user()) as $chart) { 161ade503dfSGreg Roach $menu = $chart->chartBoxMenu($individual); 162ade503dfSGreg Roach if ($menu) { 163ade503dfSGreg Roach $menus[] = $menu; 164ade503dfSGreg Roach } 165ade503dfSGreg Roach } 166ade503dfSGreg Roach 1670b93976aSGreg Roach usort($menus, static function (Menu $x, Menu $y): int { 16837646143SGreg Roach return I18N::comparator()($x->getLabel(), $y->getLabel()); 169ade503dfSGreg Roach }); 170ade503dfSGreg Roach 171ade503dfSGreg Roach return $menus; 172ade503dfSGreg Roach } 173ade503dfSGreg Roach 174ade503dfSGreg Roach /** 175ade503dfSGreg Roach * Family links, to show in chart boxes. 176ade503dfSGreg Roach * 177ade503dfSGreg Roach * @param Individual $individual 178ade503dfSGreg Roach * 17909482a55SGreg Roach * @return array<Menu> 180ade503dfSGreg Roach */ 181ade503dfSGreg Roach public function individualBoxMenuFamilyLinks(Individual $individual): array 182ade503dfSGreg Roach { 183ade503dfSGreg Roach $menus = []; 184ade503dfSGreg Roach 18539ca88baSGreg Roach foreach ($individual->spouseFamilies() as $family) { 186ade503dfSGreg Roach $menus[] = new Menu('<strong>' . I18N::translate('Family with spouse') . '</strong>', $family->url()); 18739ca88baSGreg Roach $spouse = $family->spouse($individual); 188ade503dfSGreg Roach if ($spouse && $spouse->canShowName()) { 18939ca88baSGreg Roach $menus[] = new Menu($spouse->fullName(), $spouse->url()); 190ade503dfSGreg Roach } 19139ca88baSGreg Roach foreach ($family->children() as $child) { 192ade503dfSGreg Roach if ($child->canShowName()) { 19339ca88baSGreg Roach $menus[] = new Menu($child->fullName(), $child->url()); 194ade503dfSGreg Roach } 195ade503dfSGreg Roach } 196ade503dfSGreg Roach } 197ade503dfSGreg Roach 198ade503dfSGreg Roach return $menus; 199ade503dfSGreg Roach } 200ade503dfSGreg Roach 201ade503dfSGreg Roach /** 202f567c3d8SGreg Roach * Generate a menu item to change the blocks on the current tree/user page. 203ade503dfSGreg Roach * 2040c8c69d4SGreg Roach * @param Tree $tree 2050c8c69d4SGreg Roach * 206ade503dfSGreg Roach * @return Menu|null 207ade503dfSGreg Roach */ 208e364afe4SGreg Roach public function menuChangeBlocks(Tree $tree): ?Menu 209ade503dfSGreg Roach { 2106ccdf4f0SGreg Roach $request = app(ServerRequestInterface::class); 211b55cbc6bSGreg Roach assert($request instanceof ServerRequestInterface); 212e6bcfa02SGreg Roach 213b55cbc6bSGreg Roach $route = Validator::attributes($request)->route(); 214eb235819SGreg Roach 215de2aa325SGreg Roach if (Auth::check() && $route->name === UserPage::class) { 2168e0e1b25SGreg Roach return new Menu(I18N::translate('Customize this page'), route(UserPageEdit::class, ['tree' => $tree->name()]), 'menu-change-blocks'); 217ade503dfSGreg Roach } 218ade503dfSGreg Roach 219de2aa325SGreg Roach if (Auth::isManager($tree) && $route->name === TreePage::class) { 2208e0e1b25SGreg Roach return new Menu(I18N::translate('Customize this page'), route(TreePageEdit::class, ['tree' => $tree->name()]), 'menu-change-blocks'); 221ade503dfSGreg Roach } 222ade503dfSGreg Roach 223ade503dfSGreg Roach return null; 224ade503dfSGreg Roach } 225ade503dfSGreg Roach 226ade503dfSGreg Roach /** 227ade503dfSGreg Roach * Generate a menu item for the control panel. 228ade503dfSGreg Roach * 2290c8c69d4SGreg Roach * @param Tree $tree 2300c8c69d4SGreg Roach * 231ade503dfSGreg Roach * @return Menu|null 232ade503dfSGreg Roach */ 233e364afe4SGreg Roach public function menuControlPanel(Tree $tree): ?Menu 234ade503dfSGreg Roach { 235ade503dfSGreg Roach if (Auth::isAdmin()) { 2360c0910bfSGreg Roach return new Menu(I18N::translate('Control panel'), route(ControlPanel::class), 'menu-admin'); 237ade503dfSGreg Roach } 238ade503dfSGreg Roach 2390c8c69d4SGreg Roach if (Auth::isManager($tree)) { 2406fd01894SGreg Roach return new Menu(I18N::translate('Control panel'), route(ManageTrees::class, ['tree' => $tree->name()]), 'menu-admin'); 241ade503dfSGreg Roach } 242ade503dfSGreg Roach 243ade503dfSGreg Roach return null; 244ade503dfSGreg Roach } 245ade503dfSGreg Roach 246ade503dfSGreg Roach /** 247ade503dfSGreg Roach * A menu to show a list of available languages. 248ade503dfSGreg Roach * 249ade503dfSGreg Roach * @return Menu|null 250ade503dfSGreg Roach */ 251e364afe4SGreg Roach public function menuLanguages(): ?Menu 252ade503dfSGreg Roach { 253ade503dfSGreg Roach $menu = new Menu(I18N::translate('Language'), '#', 'menu-language'); 254ade503dfSGreg Roach 25590a2f718SGreg Roach foreach (I18N::activeLocales() as $active_locale) { 25690a2f718SGreg Roach $language_tag = $active_locale->languageTag(); 25765cf5706SGreg Roach $class = 'menu-language-' . $language_tag . (I18N::languageTag() === $language_tag ? ' active' : ''); 25890a2f718SGreg Roach $menu->addSubmenu(new Menu($active_locale->endonym(), '#', $class, [ 259d4786c66SGreg Roach 'data-wt-post-url' => route(SelectLanguage::class, ['language' => $language_tag]), 260ade503dfSGreg Roach ])); 261ade503dfSGreg Roach } 262ade503dfSGreg Roach 263ade503dfSGreg Roach if (count($menu->getSubmenus()) > 1) { 264ade503dfSGreg Roach return $menu; 265ade503dfSGreg Roach } 266ade503dfSGreg Roach 267ade503dfSGreg Roach return null; 268ade503dfSGreg Roach } 269ade503dfSGreg Roach 270ade503dfSGreg Roach /** 271ade503dfSGreg Roach * A login menu option (or null if we are already logged in). 272ade503dfSGreg Roach * 273ade503dfSGreg Roach * @return Menu|null 274ade503dfSGreg Roach */ 275e364afe4SGreg Roach public function menuLogin(): ?Menu 276ade503dfSGreg Roach { 277ade503dfSGreg Roach if (Auth::check()) { 278ade503dfSGreg Roach return null; 279ade503dfSGreg Roach } 280ade503dfSGreg Roach 28186661454SGreg Roach $request = app(ServerRequestInterface::class); 282b55cbc6bSGreg Roach assert($request instanceof ServerRequestInterface); 28386661454SGreg Roach 284ade503dfSGreg Roach // Return to this page after login... 285b6c0b825SGreg Roach $redirect = $request->getQueryParams()['url'] ?? (string) $request->getUri(); 286ade503dfSGreg Roach 287b55cbc6bSGreg Roach $tree = Validator::attributes($request)->treeOptional(); 288b55cbc6bSGreg Roach $route = Validator::attributes($request)->route(); 289b45ccc4fSGreg Roach 290ade503dfSGreg Roach // ...but switch from the tree-page to the user-page 291de2aa325SGreg Roach if ($route->name === TreePage::class) { 2928e0e1b25SGreg Roach $redirect = route(UserPage::class, ['tree' => $tree instanceof Tree ? $tree->name() : null]); 29391c514e5SGreg Roach } 294ade503dfSGreg Roach 29586661454SGreg Roach // Stay on the same tree page 29691c514e5SGreg Roach $url = route(LoginPage::class, ['tree' => $tree instanceof Tree ? $tree->name() : null, 'url' => $redirect]); 29786661454SGreg Roach 29886661454SGreg Roach return new Menu(I18N::translate('Sign in'), $url, 'menu-login', ['rel' => 'nofollow']); 299ade503dfSGreg Roach } 300ade503dfSGreg Roach 301ade503dfSGreg Roach /** 302ade503dfSGreg Roach * A logout menu option (or null if we are already logged out). 303ade503dfSGreg Roach * 304ade503dfSGreg Roach * @return Menu|null 305ade503dfSGreg Roach */ 306e364afe4SGreg Roach public function menuLogout(): ?Menu 307ade503dfSGreg Roach { 308ade503dfSGreg Roach if (Auth::check()) { 3099b541e4aSGreg Roach $parameters = [ 310d4786c66SGreg Roach 'data-wt-post-url' => route(Logout::class), 311d4786c66SGreg Roach 'data-wt-reload-url' => route(HomePage::class) 3129b541e4aSGreg Roach ]; 3139b541e4aSGreg Roach 3149b541e4aSGreg Roach return new Menu(I18N::translate('Sign out'), '#', 'menu-logout', $parameters); 315ade503dfSGreg Roach } 316ade503dfSGreg Roach 317ade503dfSGreg Roach return null; 318ade503dfSGreg Roach } 319ade503dfSGreg Roach 320ade503dfSGreg Roach /** 321ade503dfSGreg Roach * A link to allow users to edit their account settings. 322ade503dfSGreg Roach * 32365aec466SGreg Roach * @param Tree|null $tree 324a49d0e3fSGreg Roach * 325a49d0e3fSGreg Roach * @return Menu 326ade503dfSGreg Roach */ 327a49d0e3fSGreg Roach public function menuMyAccount(?Tree $tree): Menu 328ade503dfSGreg Roach { 329a49d0e3fSGreg Roach $url = route(AccountEdit::class, ['tree' => $tree instanceof Tree ? $tree->name() : null]); 330ade503dfSGreg Roach 331df314983SGreg Roach return new Menu(I18N::translate('My account'), $url, 'menu-myaccount'); 332ade503dfSGreg Roach } 333ade503dfSGreg Roach 334ade503dfSGreg Roach /** 335ade503dfSGreg Roach * A link to the user's individual record (individual.php). 336ade503dfSGreg Roach * 3370c8c69d4SGreg Roach * @param Tree $tree 3380c8c69d4SGreg Roach * 339ade503dfSGreg Roach * @return Menu|null 340ade503dfSGreg Roach */ 341e364afe4SGreg Roach public function menuMyIndividualRecord(Tree $tree): ?Menu 342ade503dfSGreg Roach { 3431fe542e9SGreg Roach $record = Registry::individualFactory()->make($tree->getUserPreference(Auth::user(), UserInterface::PREF_TREE_ACCOUNT_XREF), $tree); 344ade503dfSGreg Roach 345ade503dfSGreg Roach if ($record) { 346ade503dfSGreg Roach return new Menu(I18N::translate('My individual record'), $record->url(), 'menu-myrecord'); 347ade503dfSGreg Roach } 348ade503dfSGreg Roach 349ade503dfSGreg Roach return null; 350ade503dfSGreg Roach } 351ade503dfSGreg Roach 352ade503dfSGreg Roach /** 353ade503dfSGreg Roach * A link to the user's personal home page. 354ade503dfSGreg Roach * 3550c8c69d4SGreg Roach * @param Tree $tree 3560c8c69d4SGreg Roach * 357ade503dfSGreg Roach * @return Menu 358ade503dfSGreg Roach */ 3590c8c69d4SGreg Roach public function menuMyPage(Tree $tree): Menu 360ade503dfSGreg Roach { 3618e0e1b25SGreg Roach return new Menu(I18N::translate('My page'), route(UserPage::class, ['tree' => $tree->name()]), 'menu-mypage'); 362ade503dfSGreg Roach } 363ade503dfSGreg Roach 364ade503dfSGreg Roach /** 365ade503dfSGreg Roach * A menu for the user's personal pages. 366ade503dfSGreg Roach * 3670c8c69d4SGreg Roach * @param Tree|null $tree 3680c8c69d4SGreg Roach * 369ade503dfSGreg Roach * @return Menu|null 370ade503dfSGreg Roach */ 371e364afe4SGreg Roach public function menuMyPages(?Tree $tree): ?Menu 372ade503dfSGreg Roach { 373047fd705SGreg Roach if (Auth::check()) { 374a49d0e3fSGreg Roach if ($tree instanceof Tree) { 375ade503dfSGreg Roach return new Menu(I18N::translate('My pages'), '#', 'menu-mymenu', [], array_filter([ 3760c8c69d4SGreg Roach $this->menuMyPage($tree), 3770c8c69d4SGreg Roach $this->menuMyIndividualRecord($tree), 3780c8c69d4SGreg Roach $this->menuMyPedigree($tree), 379a49d0e3fSGreg Roach $this->menuMyAccount($tree), 3800c8c69d4SGreg Roach $this->menuControlPanel($tree), 3810c8c69d4SGreg Roach $this->menuChangeBlocks($tree), 382ade503dfSGreg Roach ])); 383ade503dfSGreg Roach } 384ade503dfSGreg Roach 385a49d0e3fSGreg Roach return $this->menuMyAccount($tree); 386a49d0e3fSGreg Roach } 387a49d0e3fSGreg Roach 388ade503dfSGreg Roach return null; 389ade503dfSGreg Roach } 390ade503dfSGreg Roach 391ade503dfSGreg Roach /** 392ade503dfSGreg Roach * A link to the user's individual record. 393ade503dfSGreg Roach * 3940c8c69d4SGreg Roach * @param Tree $tree 3950c8c69d4SGreg Roach * 396ade503dfSGreg Roach * @return Menu|null 397ade503dfSGreg Roach */ 398e364afe4SGreg Roach public function menuMyPedigree(Tree $tree): ?Menu 399ade503dfSGreg Roach { 400c72a9801SGreg Roach $my_xref = $tree->getUserPreference(Auth::user(), UserInterface::PREF_TREE_ACCOUNT_XREF); 401ade503dfSGreg Roach 402b55cbc6bSGreg Roach $module_service = app(ModuleService::class); 403b55cbc6bSGreg Roach assert($module_service instanceof ModuleService); 404b55cbc6bSGreg Roach 405b55cbc6bSGreg Roach $pedigree_chart = $module_service 406b55cbc6bSGreg Roach ->findByComponent(ModuleChartInterface::class, $tree, Auth::user()) 407b55cbc6bSGreg Roach ->first(static fn (ModuleInterface $module): bool => $module instanceof PedigreeChartModule); 408ade503dfSGreg Roach 409c72a9801SGreg Roach if ($my_xref !== '' && $pedigree_chart instanceof PedigreeChartModule) { 410c72a9801SGreg Roach $individual = Registry::individualFactory()->make($my_xref, $tree); 411c72a9801SGreg Roach 412c72a9801SGreg Roach if ($individual instanceof Individual) { 413ade503dfSGreg Roach return new Menu( 414ade503dfSGreg Roach I18N::translate('My pedigree'), 415c72a9801SGreg Roach $pedigree_chart->chartUrl($individual), 416ade503dfSGreg Roach 'menu-mypedigree' 417ade503dfSGreg Roach ); 418ade503dfSGreg Roach } 419c72a9801SGreg Roach } 420ade503dfSGreg Roach 421ade503dfSGreg Roach return null; 422ade503dfSGreg Roach } 423ade503dfSGreg Roach 424ade503dfSGreg Roach /** 425ade503dfSGreg Roach * Create a pending changes menu. 426ade503dfSGreg Roach * 4270c8c69d4SGreg Roach * @param Tree|null $tree 4280c8c69d4SGreg Roach * 429ade503dfSGreg Roach * @return Menu|null 430ade503dfSGreg Roach */ 431e364afe4SGreg Roach public function menuPendingChanges(?Tree $tree): ?Menu 432ade503dfSGreg Roach { 4330c8c69d4SGreg Roach if ($tree instanceof Tree && $tree->hasPendingEdit() && Auth::isModerator($tree)) { 434b55cbc6bSGreg Roach $request = app(ServerRequestInterface::class); 435b55cbc6bSGreg Roach assert($request instanceof ServerRequestInterface); 436b55cbc6bSGreg Roach 43722e73debSGreg Roach $url = route(PendingChanges::class, [ 4389022ab66SGreg Roach 'tree' => $tree->name(), 439b55cbc6bSGreg Roach 'url' => (string) $request->getUri(), 440ade503dfSGreg Roach ]); 441ade503dfSGreg Roach 442ade503dfSGreg Roach return new Menu(I18N::translate('Pending changes'), $url, 'menu-pending'); 443ade503dfSGreg Roach } 444ade503dfSGreg Roach 445ade503dfSGreg Roach return null; 446ade503dfSGreg Roach } 447ade503dfSGreg Roach 448ade503dfSGreg Roach /** 449ade503dfSGreg Roach * Themes menu. 450ade503dfSGreg Roach * 451ade503dfSGreg Roach * @return Menu|null 452ade503dfSGreg Roach */ 453e364afe4SGreg Roach public function menuThemes(): ?Menu 454ade503dfSGreg Roach { 455b55cbc6bSGreg Roach $module_service = app(ModuleService::class); 456b55cbc6bSGreg Roach assert($module_service instanceof ModuleService); 457b55cbc6bSGreg Roach 458b55cbc6bSGreg Roach $themes = $module_service->findByInterface(ModuleThemeInterface::class, false, true); 459df8baf00SGreg Roach 460cab242e7SGreg Roach $current_theme = app(ModuleThemeInterface::class); 4618136679eSGreg Roach 4628136679eSGreg Roach if ($themes->count() > 1) { 4630b5fd0a6SGreg Roach $submenus = $themes->map(static function (ModuleThemeInterface $theme) use ($current_theme): Menu { 4648136679eSGreg Roach $active = $theme->name() === $current_theme->name(); 4658136679eSGreg Roach $class = 'menu-theme-' . $theme->name() . ($active ? ' active' : ''); 4668136679eSGreg Roach 4678136679eSGreg Roach return new Menu($theme->title(), '#', $class, [ 468d4786c66SGreg Roach 'data-wt-post-url' => route(SelectTheme::class, ['theme' => $theme->name()]), 469ade503dfSGreg Roach ]); 470ade503dfSGreg Roach }); 471ade503dfSGreg Roach 4728136679eSGreg Roach return new Menu(I18N::translate('Theme'), '#', 'menu-theme', [], $submenus->all()); 473ade503dfSGreg Roach } 474ade503dfSGreg Roach 475ade503dfSGreg Roach return null; 476ade503dfSGreg Roach } 477ade503dfSGreg Roach 478ade503dfSGreg Roach /** 479ade503dfSGreg Roach * Generate a list of items for the main menu. 480ade503dfSGreg Roach * 4810c8c69d4SGreg Roach * @param Tree|null $tree 4820c8c69d4SGreg Roach * 48309482a55SGreg Roach * @return array<Menu> 484ade503dfSGreg Roach */ 4850c8c69d4SGreg Roach public function genealogyMenu(?Tree $tree): array 486ade503dfSGreg Roach { 4870c8c69d4SGreg Roach if ($tree === null) { 4880c8c69d4SGreg Roach return []; 4890c8c69d4SGreg Roach } 4900c8c69d4SGreg Roach 491b55cbc6bSGreg Roach $module_service = app(ModuleService::class); 492b55cbc6bSGreg Roach assert($module_service instanceof ModuleService); 493b55cbc6bSGreg Roach 494b55cbc6bSGreg Roach return $module_service 495b55cbc6bSGreg Roach ->findByComponent(ModuleMenuInterface::class, $tree, Auth::user()) 496b55cbc6bSGreg Roach ->map(static fn (ModuleMenuInterface $menu): ?Menu => $menu->getMenu($tree)) 497ade503dfSGreg Roach ->filter() 498ade503dfSGreg Roach ->all(); 499ade503dfSGreg Roach } 500ade503dfSGreg Roach 501ade503dfSGreg Roach /** 5020c8c69d4SGreg Roach * Create the genealogy menu. 503ade503dfSGreg Roach * 50409482a55SGreg Roach * @param array<Menu> $menus 505ade503dfSGreg Roach * 506ade503dfSGreg Roach * @return string 507ade503dfSGreg Roach */ 5080c8c69d4SGreg Roach public function genealogyMenuContent(array $menus): string 509ade503dfSGreg Roach { 5100b5fd0a6SGreg Roach return implode('', array_map(static function (Menu $menu): string { 511f78837dcSGreg Roach return view('components/menu-item', ['menu' => $menu]); 512ade503dfSGreg Roach }, $menus)); 513ade503dfSGreg Roach } 514ade503dfSGreg Roach 515ade503dfSGreg Roach /** 516ade503dfSGreg Roach * Generate a list of items for the user menu. 517ade503dfSGreg Roach * 5180c8c69d4SGreg Roach * @param Tree|null $tree 5190c8c69d4SGreg Roach * 52009482a55SGreg Roach * @return array<Menu> 521ade503dfSGreg Roach */ 5220c8c69d4SGreg Roach public function userMenu(?Tree $tree): array 523ade503dfSGreg Roach { 524ade503dfSGreg Roach return array_filter([ 5250c8c69d4SGreg Roach $this->menuPendingChanges($tree), 5260c8c69d4SGreg Roach $this->menuMyPages($tree), 527ade503dfSGreg Roach $this->menuThemes(), 528ade503dfSGreg Roach $this->menuLanguages(), 529ade503dfSGreg Roach $this->menuLogin(), 530ade503dfSGreg Roach $this->menuLogout(), 531ade503dfSGreg Roach ]); 532ade503dfSGreg Roach } 533ade503dfSGreg Roach 534ade503dfSGreg Roach /** 535ade503dfSGreg Roach * A list of CSS files to include for this page. 536ade503dfSGreg Roach * 53724f2a3afSGreg Roach * @return array<string> 538ade503dfSGreg Roach */ 539ade503dfSGreg Roach public function stylesheets(): array 540ade503dfSGreg Roach { 541ade503dfSGreg Roach return []; 542ade503dfSGreg Roach } 54349a243cbSGreg Roach} 544