. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Module; use Fisharebest\Webtrees\Auth; use Fisharebest\Webtrees\Fact; use Fisharebest\Webtrees\Gedcom; use Fisharebest\Webtrees\GedcomTag; use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel; use Fisharebest\Webtrees\Http\RequestHandlers\HomePage; use Fisharebest\Webtrees\Http\RequestHandlers\LoginPage; use Fisharebest\Webtrees\Http\RequestHandlers\Logout; use Fisharebest\Webtrees\Http\RequestHandlers\SelectLanguage; use Fisharebest\Webtrees\Http\RequestHandlers\SelectTheme; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; use Fisharebest\Webtrees\Menu; use Fisharebest\Webtrees\Services\ModuleService; use Fisharebest\Webtrees\Tree; use Fisharebest\Webtrees\Webtrees; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; use function app; use function route; /** * Trait ModuleThemeTrait - default implementation of ModuleThemeInterface */ trait ModuleThemeTrait { /** * @return string */ abstract public function name(): string; /** * @return string */ abstract public function title(): string; /** * A sentence describing what this module does. * * @return string */ public function description(): string { return I18N::translate('Theme') . ' — ' . $this->title(); } /** * Display an icon for this fact. * * @TODO use CSS for this * * @param Fact $fact * * @return string */ public function icon(Fact $fact): string { $asset = 'public/css/' . $this->name() . '/images/facts/' . $fact->getTag() . '.png'; if (file_exists(Webtrees::ROOT_DIR . 'public' . $asset)) { return ''; } // Spacer image - for alignment - until we move to a sprite. $asset = 'public/css/' . $this->name() . '/images/facts/NULL.png'; if (file_exists(Webtrees::ROOT_DIR . 'public' . $asset)) { return ''; } return ''; } /** * Generate the facts, for display in charts. * * @param Individual $individual * * @return string */ public function individualBoxFacts(Individual $individual): string { $html = ''; $opt_tags = preg_split('/\W/', $individual->tree()->getPreference('CHART_BOX_TAGS'), 0, PREG_SPLIT_NO_EMPTY); // Show BIRT or equivalent event foreach (Gedcom::BIRTH_EVENTS as $birttag) { if (!in_array($birttag, $opt_tags, true)) { $event = $individual->facts([$birttag])->first(); if ($event instanceof Fact) { $html .= $event->summary(); break; } } } // Show optional events (before death) foreach ($opt_tags as $key => $tag) { if (!in_array($tag, Gedcom::DEATH_EVENTS, true)) { $event = $individual->facts([$tag])->first(); if ($event instanceof Fact) { $html .= $event->summary(); unset($opt_tags[$key]); } } } // Show DEAT or equivalent event foreach (Gedcom::DEATH_EVENTS as $deattag) { $event = $individual->facts([$deattag])->first(); if ($event instanceof Fact) { $html .= $event->summary(); if (in_array($deattag, $opt_tags, true)) { unset($opt_tags[array_search($deattag, $opt_tags, true)]); } break; } } // Show remaining optional events (after death) foreach ($opt_tags as $tag) { $event = $individual->facts([$tag])->first(); if ($event instanceof Fact) { $html .= $event->summary(); } } return $html; } /** * Links, to show in chart boxes; * * @param Individual $individual * * @return Menu[] */ public function individualBoxMenu(Individual $individual): array { $menus = array_merge( $this->individualBoxMenuCharts($individual), $this->individualBoxMenuFamilyLinks($individual) ); return $menus; } /** * Chart links, to show in chart boxes; * * @param Individual $individual * * @return Menu[] */ public function individualBoxMenuCharts(Individual $individual): array { $menus = []; foreach (app(ModuleService::class)->findByComponent(ModuleChartInterface::class, $individual->tree(), Auth::user()) as $chart) { $menu = $chart->chartBoxMenu($individual); if ($menu) { $menus[] = $menu; } } usort($menus, static function (Menu $x, Menu $y): int { return I18N::strcasecmp($x->getLabel(), $y->getLabel()); }); return $menus; } /** * Family links, to show in chart boxes. * * @param Individual $individual * * @return Menu[] */ public function individualBoxMenuFamilyLinks(Individual $individual): array { $menus = []; foreach ($individual->spouseFamilies() as $family) { $menus[] = new Menu('' . I18N::translate('Family with spouse') . '', $family->url()); $spouse = $family->spouse($individual); if ($spouse && $spouse->canShowName()) { $menus[] = new Menu($spouse->fullName(), $spouse->url()); } foreach ($family->children() as $child) { if ($child->canShowName()) { $menus[] = new Menu($child->fullName(), $child->url()); } } } return $menus; } /** * Generate a menu item to change the blocks on the current tree/user page. * * @param Tree $tree * * @return Menu|null */ public function menuChangeBlocks(Tree $tree): ?Menu { /** @var ServerRequestInterface $request */ $request = app(ServerRequestInterface::class); $route = $request->getAttribute('route'); if (Auth::check() && $route === 'user-page') { return new Menu(I18N::translate('Customize this page'), route('user-page-edit', ['tree' => $tree->name()]), 'menu-change-blocks'); } if (Auth::isManager($tree) && $route === 'tree-page') { return new Menu(I18N::translate('Customize this page'), route('tree-page-edit', ['tree' => $tree->name()]), 'menu-change-blocks'); } return null; } /** * Generate a menu item for the control panel. * * @param Tree $tree * * @return Menu|null */ public function menuControlPanel(Tree $tree): ?Menu { if (Auth::isAdmin()) { return new Menu(I18N::translate('Control panel'), route(ControlPanel::class), 'menu-admin'); } if (Auth::isManager($tree)) { return new Menu(I18N::translate('Control panel'), route('manage-trees'), 'menu-admin'); } return null; } /** * A menu to show a list of available languages. * * @return Menu|null */ public function menuLanguages(): ?Menu { $menu = new Menu(I18N::translate('Language'), '#', 'menu-language'); foreach (I18N::activeLocales() as $locale) { $language_tag = $locale->languageTag(); $class = 'menu-language-' . $language_tag . (WT_LOCALE === $language_tag ? ' active' : ''); $menu->addSubmenu(new Menu($locale->endonym(), '#', $class, [ 'data-post-url' => route(SelectLanguage::class, ['language' => $language_tag]), ])); } if (count($menu->getSubmenus()) > 1) { return $menu; } return null; } /** * A login menu option (or null if we are already logged in). * * @return Menu|null */ public function menuLogin(): ?Menu { if (Auth::check()) { return null; } $request = app(ServerRequestInterface::class); // Return to this page after login... $redirect = (string) $request->getUri(); // ...but switch from the tree-page to the user-page if ($request->getAttribute('route') === 'tree-page') { $tree = $request->getAttribute('tree'); assert($tree instanceof Tree, new InvalidArgumentException()); $redirect = route('user-page', ['tree' => $tree->name()]); } // Stay on the same tree page $tree = $request->getAttribute('tree'); $url = route(LoginPage::class, ['tree' => $tree instanceof Tree ? $tree->name() : null, 'url' => $redirect]); return new Menu(I18N::translate('Sign in'), $url, 'menu-login', ['rel' => 'nofollow']); } /** * A logout menu option (or null if we are already logged out). * * @return Menu|null */ public function menuLogout(): ?Menu { if (Auth::check()) { $parameters = [ 'data-post-url' => route(Logout::class), 'data-reload-url' => route(HomePage::class), ]; return new Menu(I18N::translate('Sign out'), '#', 'menu-logout', $parameters); } return null; } /** * A link to allow users to edit their account settings. * * @return Menu|null */ public function menuMyAccount(): ?Menu { if (Auth::check()) { return new Menu(I18N::translate('My account'), route('my-account')); } return null; } /** * A link to the user's individual record (individual.php). * * @param Tree $tree * * @return Menu|null */ public function menuMyIndividualRecord(Tree $tree): ?Menu { $record = Individual::getInstance($tree->getUserPreference(Auth::user(), 'gedcomid'), $tree); if ($record) { return new Menu(I18N::translate('My individual record'), $record->url(), 'menu-myrecord'); } return null; } /** * A link to the user's personal home page. * * @param Tree $tree * * @return Menu */ public function menuMyPage(Tree $tree): Menu { return new Menu(I18N::translate('My page'), route('user-page', ['tree' => $tree->name()]), 'menu-mypage'); } /** * A menu for the user's personal pages. * * @param Tree|null $tree * * @return Menu|null */ public function menuMyPages(?Tree $tree): ?Menu { if ($tree instanceof Tree && Auth::id()) { return new Menu(I18N::translate('My pages'), '#', 'menu-mymenu', [], array_filter([ $this->menuMyPage($tree), $this->menuMyIndividualRecord($tree), $this->menuMyPedigree($tree), $this->menuMyAccount(), $this->menuControlPanel($tree), $this->menuChangeBlocks($tree), ])); } return null; } /** * A link to the user's individual record. * * @param Tree $tree * * @return Menu|null */ public function menuMyPedigree(Tree $tree): ?Menu { $gedcomid = $tree->getUserPreference(Auth::user(), 'gedcomid'); $pedigree_chart = app(ModuleService::class)->findByComponent(ModuleChartInterface::class, $tree, Auth::user()) ->filter(static function (ModuleInterface $module): bool { return $module instanceof PedigreeChartModule; }); if ($gedcomid !== '' && $pedigree_chart instanceof PedigreeChartModule) { return new Menu( I18N::translate('My pedigree'), route('pedigree', [ 'xref' => $gedcomid, 'tree' => $tree->name(), ]), 'menu-mypedigree' ); } return null; } /** * Create a pending changes menu. * * @param Tree|null $tree * * @return Menu|null */ public function menuPendingChanges(?Tree $tree): ?Menu { if ($tree instanceof Tree && $tree->hasPendingEdit() && Auth::isModerator($tree)) { $url = route('show-pending', [ 'tree' => $tree->name(), 'url' => (string) app(ServerRequestInterface::class)->getUri(), ]); return new Menu(I18N::translate('Pending changes'), $url, 'menu-pending'); } return null; } /** * Themes menu. * * @return Menu|null */ public function menuThemes(): ?Menu { $themes = app(ModuleService::class)->findByInterface(ModuleThemeInterface::class, false, true); $current_theme = app(ModuleThemeInterface::class); if ($themes->count() > 1) { $submenus = $themes->map(static function (ModuleThemeInterface $theme) use ($current_theme): Menu { $active = $theme->name() === $current_theme->name(); $class = 'menu-theme-' . $theme->name() . ($active ? ' active' : ''); return new Menu($theme->title(), '#', $class, [ 'data-post-url' => route(SelectTheme::class, ['theme' => $theme->name()]), ]); }); return new Menu(I18N::translate('Theme'), '#', 'menu-theme', [], $submenus->all()); } return null; } /** * Misecellaneous dimensions, fonts, styles, etc. * * @param string $parameter_name * * @return string|int|float */ public function parameter($parameter_name) { return ''; } /** * Generate a list of items for the main menu. * * @param Tree|null $tree * * @return Menu[] */ public function genealogyMenu(?Tree $tree): array { if ($tree === null) { return []; } return app(ModuleService::class)->findByComponent(ModuleMenuInterface::class, $tree, Auth::user()) ->map(static function (ModuleMenuInterface $menu) use ($tree): ?Menu { return $menu->getMenu($tree); }) ->filter() ->all(); } /** * Create the genealogy menu. * * @param Menu[] $menus * * @return string */ public function genealogyMenuContent(array $menus): string { return implode('', array_map(static function (Menu $menu): string { return $menu->bootstrap4(); }, $menus)); } /** * Generate a list of items for the user menu. * * @param Tree|null $tree * * @return Menu[] */ public function userMenu(?Tree $tree): array { return array_filter([ $this->menuPendingChanges($tree), $this->menuMyPages($tree), $this->menuThemes(), $this->menuLanguages(), $this->menuLogin(), $this->menuLogout(), ]); } /** * A list of CSS files to include for this page. * * @return string[] */ public function stylesheets(): array { return []; } }