.
*/
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\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()) {
return new Menu(I18N::translate('Sign out'), '#', 'menu-logout', ['data-post-url' => route(Logout::class)]);
}
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 [];
}
}