149a243cbSGreg Roach<?php 249a243cbSGreg Roach/** 349a243cbSGreg Roach * webtrees: online genealogy 449a243cbSGreg Roach * Copyright (C) 2019 webtrees development team 549a243cbSGreg Roach * This program is free software: you can redistribute it and/or modify 649a243cbSGreg Roach * it under the terms of the GNU General Public License as published by 749a243cbSGreg Roach * the Free Software Foundation, either version 3 of the License, or 849a243cbSGreg Roach * (at your option) any later version. 949a243cbSGreg Roach * This program is distributed in the hope that it will be useful, 1049a243cbSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 1149a243cbSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1249a243cbSGreg Roach * GNU General Public License for more details. 1349a243cbSGreg Roach * You should have received a copy of the GNU General Public License 1449a243cbSGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 1549a243cbSGreg Roach */ 1649a243cbSGreg Roachdeclare(strict_types=1); 1749a243cbSGreg Roach 1849a243cbSGreg Roachnamespace Fisharebest\Webtrees\Module; 1949a243cbSGreg Roach 20ade503dfSGreg Roachuse Fisharebest\Webtrees\Auth; 21ade503dfSGreg Roachuse Fisharebest\Webtrees\Fact; 22ade503dfSGreg Roachuse Fisharebest\Webtrees\Gedcom; 23ade503dfSGreg Roachuse Fisharebest\Webtrees\GedcomTag; 24ade503dfSGreg Roachuse Fisharebest\Webtrees\I18N; 25ade503dfSGreg Roachuse Fisharebest\Webtrees\Individual; 26ade503dfSGreg Roachuse Fisharebest\Webtrees\Menu; 27ade503dfSGreg Roachuse Fisharebest\Webtrees\Module; 28ade503dfSGreg Roachuse Fisharebest\Webtrees\Site; 29ade503dfSGreg Roachuse Fisharebest\Webtrees\Theme; 30ade503dfSGreg Roachuse Fisharebest\Webtrees\Tree; 31ade503dfSGreg Roachuse Symfony\Component\HttpFoundation\Request; 32ade503dfSGreg Roach 3349a243cbSGreg Roach/** 3449a243cbSGreg Roach * Trait ModuleThemeTrait - default implementation of ModuleThemeInterface 3549a243cbSGreg Roach */ 3649a243cbSGreg Roachtrait ModuleThemeTrait 3749a243cbSGreg Roach{ 38ade503dfSGreg Roach /** @var Request */ 39ade503dfSGreg Roach protected $request; 40ade503dfSGreg Roach 41ade503dfSGreg Roach /** @var Tree|null */ 42ade503dfSGreg Roach protected $tree; 43ade503dfSGreg Roach 44ade503dfSGreg Roach /** 45ade503dfSGreg Roach * @param Request $request 46ade503dfSGreg Roach * @param Tree|null $tree The current tree (if there is one). 47ade503dfSGreg Roach */ 48ade503dfSGreg Roach public function __construct(Request $request, ?Tree $tree) 49ade503dfSGreg Roach { 50ade503dfSGreg Roach $this->request = $request; 51ade503dfSGreg Roach $this->tree = $tree; 52ade503dfSGreg Roach } 53ade503dfSGreg Roach 54ade503dfSGreg Roach /** 55ade503dfSGreg Roach * Where are our CSS, JS and other assets? 56ade503dfSGreg Roach * 57ade503dfSGreg Roach * @deprecated - use the constant directly 58ade503dfSGreg Roach * @return string A relative path, such as "themes/foo/" 59ade503dfSGreg Roach */ 60ade503dfSGreg Roach public function assetUrl(): string 61ade503dfSGreg Roach { 62ade503dfSGreg Roach return self::ASSET_DIR; 63ade503dfSGreg Roach } 64ade503dfSGreg Roach 65ade503dfSGreg Roach /** 66ade503dfSGreg Roach * Add markup to the secondary menu. 67ade503dfSGreg Roach * 68ade503dfSGreg Roach * @return string 69ade503dfSGreg Roach */ 70ade503dfSGreg Roach public function formatSecondaryMenu(): string 71ade503dfSGreg Roach { 72ade503dfSGreg Roach return 73ade503dfSGreg Roach '<ul class="nav wt-secondary-menu">' . 74ade503dfSGreg Roach implode('', array_map(function (Menu $menu): string { 75ade503dfSGreg Roach return $this->formatSecondaryMenuItem($menu); 76ade503dfSGreg Roach }, $this->secondaryMenu())) . 77ade503dfSGreg Roach '</ul>'; 78ade503dfSGreg Roach } 79ade503dfSGreg Roach 80ade503dfSGreg Roach /** 81ade503dfSGreg Roach * Add markup to an item in the secondary menu. 82ade503dfSGreg Roach * 83ade503dfSGreg Roach * @param Menu $menu 84ade503dfSGreg Roach * 85ade503dfSGreg Roach * @return string 86ade503dfSGreg Roach */ 87ade503dfSGreg Roach public function formatSecondaryMenuItem(Menu $menu): string 88ade503dfSGreg Roach { 89ade503dfSGreg Roach return $menu->bootstrap4(); 90ade503dfSGreg Roach } 91ade503dfSGreg Roach 92ade503dfSGreg Roach /** 93ade503dfSGreg Roach * Display an icon for this fact. 94ade503dfSGreg Roach * 95ade503dfSGreg Roach * @param Fact $fact 96ade503dfSGreg Roach * 97ade503dfSGreg Roach * @return string 98ade503dfSGreg Roach */ 99ade503dfSGreg Roach public function icon(Fact $fact): string 100ade503dfSGreg Roach { 101ade503dfSGreg Roach $icon = 'images/facts/' . $fact->getTag() . '.png'; 102ade503dfSGreg Roach if (file_exists(self::ASSET_DIR . $icon)) { 103ade503dfSGreg Roach return '<img src="' . self::ASSET_DIR . $icon . '" title="' . GedcomTag::getLabel($fact->getTag()) . '">'; 104ade503dfSGreg Roach } 105ade503dfSGreg Roach 106ade503dfSGreg Roach if (file_exists(self::ASSET_DIR . 'images/facts/NULL.png')) { 107ade503dfSGreg Roach // Spacer image - for alignment - until we move to a sprite. 108ade503dfSGreg Roach return '<img src="' . Theme::theme()->assetUrl() . 'images/facts/NULL.png">'; 109ade503dfSGreg Roach } 110ade503dfSGreg Roach 111ade503dfSGreg Roach return ''; 112ade503dfSGreg Roach } 113ade503dfSGreg Roach 114ade503dfSGreg Roach /** 115ade503dfSGreg Roach * Display an individual in a box - for charts, etc. 116ade503dfSGreg Roach * 117ade503dfSGreg Roach * @param Individual $individual 118ade503dfSGreg Roach * 119ade503dfSGreg Roach * @return string 120ade503dfSGreg Roach */ 121ade503dfSGreg Roach public function individualBox(Individual $individual): string 122ade503dfSGreg Roach { 123ade503dfSGreg Roach $person_box_class = self::PERSON_BOX_CLASSES[$individual->getSex()]; 124ade503dfSGreg Roach 125ade503dfSGreg Roach if ($individual->canShow() && $individual->tree()->getPreference('SHOW_HIGHLIGHT_IMAGES')) { 126ade503dfSGreg Roach $thumbnail = $individual->displayImage(40, 50, 'crop', []); 127ade503dfSGreg Roach } else { 128ade503dfSGreg Roach $thumbnail = ''; 129ade503dfSGreg Roach } 130ade503dfSGreg Roach 131ade503dfSGreg Roach $content = '<span class="namedef name1">' . $individual->getFullName() . '</span>'; 132ade503dfSGreg Roach $icons = ''; 133ade503dfSGreg Roach if ($individual->canShow()) { 134ade503dfSGreg Roach $content = '<a href="' . e($individual->url()) . '">' . $content . '</a>' . 135ade503dfSGreg Roach '<div class="namedef name1">' . $individual->getAddName() . '</div>'; 136ade503dfSGreg Roach $icons = '<div class="icons">' . 137ade503dfSGreg Roach '<span class="iconz icon-zoomin" title="' . I18N::translate('Zoom in/out on this box.') . '"></span>' . 138ade503dfSGreg Roach '<div class="itr"><i class="icon-pedigree"></i><div class="popup">' . 139ade503dfSGreg Roach '<ul class="' . $person_box_class . '">' . implode('', array_map(function (Menu $menu): string { 140ade503dfSGreg Roach return $menu->bootstrap4(); 141ade503dfSGreg Roach }, $this->individualBoxMenu($individual))) . '</ul>' . 142ade503dfSGreg Roach '</div>' . 143ade503dfSGreg Roach '</div>' . 144ade503dfSGreg Roach '</div>'; 145ade503dfSGreg Roach } 146ade503dfSGreg Roach 147ade503dfSGreg Roach return 148ade503dfSGreg Roach '<div data-xref="' . e($individual->xref()) . '" data-tree="' . e($individual->tree()->name()) . '" class="person_box_template ' . $person_box_class . ' box-style1" style="width: ' . $this->parameter('chart-box-x') . 'px; height: ' . $this->parameter('chart-box-y') . 'px">' . 149ade503dfSGreg Roach $icons . 150ade503dfSGreg Roach '<div class="chart_textbox" style="max-height:' . $this->parameter('chart-box-y') . 'px;">' . 151ade503dfSGreg Roach $thumbnail . 152ade503dfSGreg Roach $content . 153ade503dfSGreg Roach '<div class="inout2 details1">' . $this->individualBoxFacts($individual) . '</div>' . 154ade503dfSGreg Roach '</div>' . 155ade503dfSGreg Roach '<div class="inout"></div>' . 156ade503dfSGreg Roach '</div>'; 157ade503dfSGreg Roach } 158ade503dfSGreg Roach 159ade503dfSGreg Roach /** 160ade503dfSGreg Roach * Display an empty box - for a missing individual in a chart. 161ade503dfSGreg Roach * 162ade503dfSGreg Roach * @return string 163ade503dfSGreg Roach */ 164ade503dfSGreg Roach public function individualBoxEmpty(): string 165ade503dfSGreg Roach { 166ade503dfSGreg Roach return '<div class="person_box_template person_boxNN box-style1" style="width: ' . $this->parameter('chart-box-x') . 'px; min-height: ' . $this->parameter('chart-box-y') . 'px"></div>'; 167ade503dfSGreg Roach } 168ade503dfSGreg Roach 169ade503dfSGreg Roach /** 170ade503dfSGreg Roach * Display an individual in a box - for charts, etc. 171ade503dfSGreg Roach * 172ade503dfSGreg Roach * @param Individual $individual 173ade503dfSGreg Roach * 174ade503dfSGreg Roach * @return string 175ade503dfSGreg Roach */ 176ade503dfSGreg Roach public function individualBoxLarge(Individual $individual): string 177ade503dfSGreg Roach { 178ade503dfSGreg Roach $person_box_class = self::PERSON_BOX_CLASSES[$individual->getSex()]; 179ade503dfSGreg Roach 180ade503dfSGreg Roach if ($individual->tree()->getPreference('SHOW_HIGHLIGHT_IMAGES')) { 181ade503dfSGreg Roach $thumbnail = $individual->displayImage(40, 50, 'crop', []); 182ade503dfSGreg Roach } else { 183ade503dfSGreg Roach $thumbnail = ''; 184ade503dfSGreg Roach } 185ade503dfSGreg Roach 186ade503dfSGreg Roach $content = '<span class="namedef name1">' . $individual->getFullName() . '</span>'; 187ade503dfSGreg Roach $icons = ''; 188ade503dfSGreg Roach if ($individual->canShow()) { 189ade503dfSGreg Roach $content = '<a href="' . e($individual->url()) . '">' . $content . '</a>' . 190ade503dfSGreg Roach '<div class="namedef name2">' . $individual->getAddName() . '</div>'; 191ade503dfSGreg Roach $icons = '<div class="icons">' . 192ade503dfSGreg Roach '<span class="iconz icon-zoomin" title="' . I18N::translate('Zoom in/out on this box.') . '"></span>' . 193ade503dfSGreg Roach '<div class="itr"><i class="icon-pedigree"></i><div class="popup">' . 194ade503dfSGreg Roach '<ul class="' . $person_box_class . '">' . implode('', array_map(function (Menu $menu): string { 195ade503dfSGreg Roach return $menu->bootstrap4(); 196ade503dfSGreg Roach }, $this->individualBoxMenu($individual))) . '</ul>' . 197ade503dfSGreg Roach '</div>' . 198ade503dfSGreg Roach '</div>' . 199ade503dfSGreg Roach '</div>'; 200ade503dfSGreg Roach } 201ade503dfSGreg Roach 202ade503dfSGreg Roach return 203ade503dfSGreg Roach '<div data-xref="' . e($individual->xref()) . '" data-tree="' . e($individual->tree()->name()) . '" class="person_box_template ' . $person_box_class . ' box-style2">' . 204ade503dfSGreg Roach $icons . 205ade503dfSGreg Roach '<div class="chart_textbox" style="max-height:' . $this->parameter('chart-box-y') . 'px;">' . 206ade503dfSGreg Roach $thumbnail . 207ade503dfSGreg Roach $content . 208ade503dfSGreg Roach '<div class="inout2 details2">' . $this->individualBoxFacts($individual) . '</div>' . 209ade503dfSGreg Roach '</div>' . 210ade503dfSGreg Roach '<div class="inout"></div>' . 211ade503dfSGreg Roach '</div>'; 212ade503dfSGreg Roach } 213ade503dfSGreg Roach 214ade503dfSGreg Roach /** 215ade503dfSGreg Roach * Display an individual in a box - for charts, etc. 216ade503dfSGreg Roach * 217ade503dfSGreg Roach * @param Individual $individual 218ade503dfSGreg Roach * 219ade503dfSGreg Roach * @return string 220ade503dfSGreg Roach */ 221ade503dfSGreg Roach public function individualBoxSmall(Individual $individual): string 222ade503dfSGreg Roach { 223ade503dfSGreg Roach $person_box_class = self::PERSON_BOX_CLASSES[$individual->getSex()]; 224ade503dfSGreg Roach 225ade503dfSGreg Roach if ($individual->tree()->getPreference('SHOW_HIGHLIGHT_IMAGES')) { 226ade503dfSGreg Roach $thumbnail = $individual->displayImage(40, 50, 'crop', []); 227ade503dfSGreg Roach } else { 228ade503dfSGreg Roach $thumbnail = ''; 229ade503dfSGreg Roach } 230ade503dfSGreg Roach 231ade503dfSGreg Roach return 232ade503dfSGreg Roach '<div data-xref="' . $individual->xref() . '" class="person_box_template ' . $person_box_class . ' iconz box-style0" style="width: ' . $this->parameter('compact-chart-box-x') . 'px; min-height: ' . $this->parameter('compact-chart-box-y') . 'px">' . 233ade503dfSGreg Roach '<div class="compact_view">' . 234ade503dfSGreg Roach $thumbnail . 235ade503dfSGreg Roach '<a href="' . e($individual->url()) . '">' . 236ade503dfSGreg Roach '<span class="namedef name0">' . $individual->getFullName() . '</span>' . 237ade503dfSGreg Roach '</a>' . 238ade503dfSGreg Roach '<div class="inout2 details0">' . $individual->getLifeSpan() . '</div>' . 239ade503dfSGreg Roach '</div>' . 240ade503dfSGreg Roach '<div class="inout"></div>' . 241ade503dfSGreg Roach '</div>'; 242ade503dfSGreg Roach } 243ade503dfSGreg Roach 244ade503dfSGreg Roach /** 245ade503dfSGreg Roach * Display an individual in a box - for charts, etc. 246ade503dfSGreg Roach * 247ade503dfSGreg Roach * @return string 248ade503dfSGreg Roach */ 249ade503dfSGreg Roach public function individualBoxSmallEmpty(): string 250ade503dfSGreg Roach { 251ade503dfSGreg Roach return '<div class="person_box_template person_boxNN box-style1" style="width: ' . $this->parameter('compact-chart-box-x') . 'px; min-height: ' . $this->parameter('compact-chart-box-y') . 'px"></div>'; 252ade503dfSGreg Roach } 253ade503dfSGreg Roach 254ade503dfSGreg Roach /** 255ade503dfSGreg Roach * Generate the facts, for display in charts. 256ade503dfSGreg Roach * 257ade503dfSGreg Roach * @param Individual $individual 258ade503dfSGreg Roach * 259ade503dfSGreg Roach * @return string 260ade503dfSGreg Roach */ 261ade503dfSGreg Roach public function individualBoxFacts(Individual $individual): string 262ade503dfSGreg Roach { 263ade503dfSGreg Roach $html = ''; 264ade503dfSGreg Roach 265ade503dfSGreg Roach $opt_tags = preg_split('/\W/', $individual->tree()->getPreference('CHART_BOX_TAGS'), 0, PREG_SPLIT_NO_EMPTY); 266ade503dfSGreg Roach // Show BIRT or equivalent event 267ade503dfSGreg Roach foreach (Gedcom::BIRTH_EVENTS as $birttag) { 268ade503dfSGreg Roach if (!in_array($birttag, $opt_tags)) { 269ade503dfSGreg Roach $event = $individual->getFirstFact($birttag); 270ade503dfSGreg Roach if ($event) { 271ade503dfSGreg Roach $html .= $event->summary(); 272ade503dfSGreg Roach break; 273ade503dfSGreg Roach } 274ade503dfSGreg Roach } 275ade503dfSGreg Roach } 276ade503dfSGreg Roach // Show optional events (before death) 277ade503dfSGreg Roach foreach ($opt_tags as $key => $tag) { 278ade503dfSGreg Roach if (!in_array($tag, Gedcom::DEATH_EVENTS)) { 279ade503dfSGreg Roach $event = $individual->getFirstFact($tag); 280ade503dfSGreg Roach if ($event !== null) { 281ade503dfSGreg Roach $html .= $event->summary(); 282ade503dfSGreg Roach unset($opt_tags[$key]); 283ade503dfSGreg Roach } 284ade503dfSGreg Roach } 285ade503dfSGreg Roach } 286ade503dfSGreg Roach // Show DEAT or equivalent event 287ade503dfSGreg Roach foreach (Gedcom::DEATH_EVENTS as $deattag) { 288ade503dfSGreg Roach $event = $individual->getFirstFact($deattag); 289ade503dfSGreg Roach if ($event) { 290ade503dfSGreg Roach $html .= $event->summary(); 291ade503dfSGreg Roach if (in_array($deattag, $opt_tags)) { 292ade503dfSGreg Roach unset($opt_tags[array_search($deattag, $opt_tags)]); 293ade503dfSGreg Roach } 294ade503dfSGreg Roach break; 295ade503dfSGreg Roach } 296ade503dfSGreg Roach } 297ade503dfSGreg Roach // Show remaining optional events (after death) 298ade503dfSGreg Roach foreach ($opt_tags as $tag) { 299ade503dfSGreg Roach $event = $individual->getFirstFact($tag); 300ade503dfSGreg Roach if ($event) { 301ade503dfSGreg Roach $html .= $event->summary(); 302ade503dfSGreg Roach } 303ade503dfSGreg Roach } 304ade503dfSGreg Roach 305ade503dfSGreg Roach return $html; 306ade503dfSGreg Roach } 307ade503dfSGreg Roach 308ade503dfSGreg Roach /** 309ade503dfSGreg Roach * Links, to show in chart boxes; 310ade503dfSGreg Roach * 311ade503dfSGreg Roach * @param Individual $individual 312ade503dfSGreg Roach * 313ade503dfSGreg Roach * @return Menu[] 314ade503dfSGreg Roach */ 315ade503dfSGreg Roach public function individualBoxMenu(Individual $individual): array 316ade503dfSGreg Roach { 317ade503dfSGreg Roach $menus = array_merge( 318ade503dfSGreg Roach $this->individualBoxMenuCharts($individual), 319ade503dfSGreg Roach $this->individualBoxMenuFamilyLinks($individual) 320ade503dfSGreg Roach ); 321ade503dfSGreg Roach 322ade503dfSGreg Roach return $menus; 323ade503dfSGreg Roach } 324ade503dfSGreg Roach 325ade503dfSGreg Roach /** 326ade503dfSGreg Roach * Chart links, to show in chart boxes; 327ade503dfSGreg Roach * 328ade503dfSGreg Roach * @param Individual $individual 329ade503dfSGreg Roach * 330ade503dfSGreg Roach * @return Menu[] 331ade503dfSGreg Roach */ 332ade503dfSGreg Roach public function individualBoxMenuCharts(Individual $individual): array 333ade503dfSGreg Roach { 334ade503dfSGreg Roach $menus = []; 335ade503dfSGreg Roach foreach (Module::findByComponent('chart', $this->tree, Auth::user()) as $chart) { 336ade503dfSGreg Roach $menu = $chart->chartBoxMenu($individual); 337ade503dfSGreg Roach if ($menu) { 338ade503dfSGreg Roach $menus[] = $menu; 339ade503dfSGreg Roach } 340ade503dfSGreg Roach } 341ade503dfSGreg Roach 342ade503dfSGreg Roach usort($menus, function (Menu $x, Menu $y) { 343ade503dfSGreg Roach return I18N::strcasecmp($x->getLabel(), $y->getLabel()); 344ade503dfSGreg Roach }); 345ade503dfSGreg Roach 346ade503dfSGreg Roach return $menus; 347ade503dfSGreg Roach } 348ade503dfSGreg Roach 349ade503dfSGreg Roach /** 350ade503dfSGreg Roach * Family links, to show in chart boxes. 351ade503dfSGreg Roach * 352ade503dfSGreg Roach * @param Individual $individual 353ade503dfSGreg Roach * 354ade503dfSGreg Roach * @return Menu[] 355ade503dfSGreg Roach */ 356ade503dfSGreg Roach public function individualBoxMenuFamilyLinks(Individual $individual): array 357ade503dfSGreg Roach { 358ade503dfSGreg Roach $menus = []; 359ade503dfSGreg Roach 360ade503dfSGreg Roach foreach ($individual->getSpouseFamilies() as $family) { 361ade503dfSGreg Roach $menus[] = new Menu('<strong>' . I18N::translate('Family with spouse') . '</strong>', $family->url()); 362ade503dfSGreg Roach $spouse = $family->getSpouse($individual); 363ade503dfSGreg Roach if ($spouse && $spouse->canShowName()) { 364ade503dfSGreg Roach $menus[] = new Menu($spouse->getFullName(), $spouse->url()); 365ade503dfSGreg Roach } 366ade503dfSGreg Roach foreach ($family->getChildren() as $child) { 367ade503dfSGreg Roach if ($child->canShowName()) { 368ade503dfSGreg Roach $menus[] = new Menu($child->getFullName(), $child->url()); 369ade503dfSGreg Roach } 370ade503dfSGreg Roach } 371ade503dfSGreg Roach } 372ade503dfSGreg Roach 373ade503dfSGreg Roach return $menus; 374ade503dfSGreg Roach } 375ade503dfSGreg Roach 376ade503dfSGreg Roach /** 377ade503dfSGreg Roach * Generate a menu item to change the blocks on the current (index.php) page. 378ade503dfSGreg Roach * 379ade503dfSGreg Roach * @return Menu|null 380ade503dfSGreg Roach */ 381ade503dfSGreg Roach public function menuChangeBlocks() 382ade503dfSGreg Roach { 383ade503dfSGreg Roach if (Auth::check() && $this->request->get('route') === 'user-page') { 384ade503dfSGreg Roach return new Menu(I18N::translate('Customize this page'), route('user-page-edit', ['ged' => $this->tree->name()]), 'menu-change-blocks'); 385ade503dfSGreg Roach } 386ade503dfSGreg Roach 387ade503dfSGreg Roach if (Auth::isManager($this->tree) && $this->request->get('route') === 'tree-page') { 388ade503dfSGreg Roach return new Menu(I18N::translate('Customize this page'), route('tree-page-edit', ['ged' => $this->tree->name()]), 'menu-change-blocks'); 389ade503dfSGreg Roach } 390ade503dfSGreg Roach 391ade503dfSGreg Roach return null; 392ade503dfSGreg Roach } 393ade503dfSGreg Roach 394ade503dfSGreg Roach /** 395ade503dfSGreg Roach * Generate a menu item for the control panel. 396ade503dfSGreg Roach * 397ade503dfSGreg Roach * @return Menu|null 398ade503dfSGreg Roach */ 399ade503dfSGreg Roach public function menuControlPanel() 400ade503dfSGreg Roach { 401ade503dfSGreg Roach if (Auth::isAdmin()) { 402ade503dfSGreg Roach return new Menu(I18N::translate('Control panel'), route('admin-control-panel'), 'menu-admin'); 403ade503dfSGreg Roach } 404ade503dfSGreg Roach 405ade503dfSGreg Roach if (Auth::isManager($this->tree)) { 406ade503dfSGreg Roach return new Menu(I18N::translate('Control panel'), route('admin-control-panel-manager'), 'menu-admin'); 407ade503dfSGreg Roach } 408ade503dfSGreg Roach 409ade503dfSGreg Roach return null; 410ade503dfSGreg Roach } 411ade503dfSGreg Roach 412ade503dfSGreg Roach /** 413ade503dfSGreg Roach * A menu to show a list of available languages. 414ade503dfSGreg Roach * 415ade503dfSGreg Roach * @return Menu|null 416ade503dfSGreg Roach */ 417ade503dfSGreg Roach public function menuLanguages() 418ade503dfSGreg Roach { 419ade503dfSGreg Roach $menu = new Menu(I18N::translate('Language'), '#', 'menu-language'); 420ade503dfSGreg Roach 421ade503dfSGreg Roach foreach (I18N::activeLocales() as $locale) { 422ade503dfSGreg Roach $language_tag = $locale->languageTag(); 423ade503dfSGreg Roach $class = 'menu-language-' . $language_tag . (WT_LOCALE === $language_tag ? ' active' : ''); 424ade503dfSGreg Roach $menu->addSubmenu(new Menu($locale->endonym(), '#', $class, [ 425ade503dfSGreg Roach 'onclick' => 'return false;', 426ade503dfSGreg Roach 'data-language' => $language_tag, 427ade503dfSGreg Roach ])); 428ade503dfSGreg Roach } 429ade503dfSGreg Roach 430ade503dfSGreg Roach if (count($menu->getSubmenus()) > 1) { 431ade503dfSGreg Roach return $menu; 432ade503dfSGreg Roach } 433ade503dfSGreg Roach 434ade503dfSGreg Roach return null; 435ade503dfSGreg Roach } 436ade503dfSGreg Roach 437ade503dfSGreg Roach /** 438ade503dfSGreg Roach * A login menu option (or null if we are already logged in). 439ade503dfSGreg Roach * 440ade503dfSGreg Roach * @return Menu|null 441ade503dfSGreg Roach */ 442ade503dfSGreg Roach public function menuLogin() 443ade503dfSGreg Roach { 444ade503dfSGreg Roach if (Auth::check()) { 445ade503dfSGreg Roach return null; 446ade503dfSGreg Roach } 447ade503dfSGreg Roach 448ade503dfSGreg Roach // Return to this page after login... 449ade503dfSGreg Roach $url = $this->request->getRequestUri(); 450ade503dfSGreg Roach 451ade503dfSGreg Roach // ...but switch from the tree-page to the user-page 452ade503dfSGreg Roach $url = str_replace('route=tree-page', 'route=user-page', $url); 453ade503dfSGreg Roach 454ade503dfSGreg Roach return new Menu(I18N::translate('Sign in'), route('login', ['url' => $url]), 'menu-login', ['rel' => 'nofollow']); 455ade503dfSGreg Roach } 456ade503dfSGreg Roach 457ade503dfSGreg Roach /** 458ade503dfSGreg Roach * A logout menu option (or null if we are already logged out). 459ade503dfSGreg Roach * 460ade503dfSGreg Roach * @return Menu|null 461ade503dfSGreg Roach */ 462ade503dfSGreg Roach public function menuLogout() 463ade503dfSGreg Roach { 464ade503dfSGreg Roach if (Auth::check()) { 465ade503dfSGreg Roach return new Menu(I18N::translate('Sign out'), route('logout'), 'menu-logout'); 466ade503dfSGreg Roach } 467ade503dfSGreg Roach 468ade503dfSGreg Roach return null; 469ade503dfSGreg Roach } 470ade503dfSGreg Roach 471ade503dfSGreg Roach /** 472ade503dfSGreg Roach * A link to allow users to edit their account settings. 473ade503dfSGreg Roach * 474ade503dfSGreg Roach * @return Menu|null 475ade503dfSGreg Roach */ 476ade503dfSGreg Roach public function menuMyAccount() 477ade503dfSGreg Roach { 478ade503dfSGreg Roach if (Auth::check()) { 479ade503dfSGreg Roach return new Menu(I18N::translate('My account'), route('my-account')); 480ade503dfSGreg Roach } 481ade503dfSGreg Roach 482ade503dfSGreg Roach return null; 483ade503dfSGreg Roach } 484ade503dfSGreg Roach 485ade503dfSGreg Roach /** 486ade503dfSGreg Roach * A link to the user's individual record (individual.php). 487ade503dfSGreg Roach * 488ade503dfSGreg Roach * @return Menu|null 489ade503dfSGreg Roach */ 490ade503dfSGreg Roach public function menuMyIndividualRecord() 491ade503dfSGreg Roach { 492ade503dfSGreg Roach $record = Individual::getInstance($this->tree->getUserPreference(Auth::user(), 'gedcomid'), $this->tree); 493ade503dfSGreg Roach 494ade503dfSGreg Roach if ($record) { 495ade503dfSGreg Roach return new Menu(I18N::translate('My individual record'), $record->url(), 'menu-myrecord'); 496ade503dfSGreg Roach } 497ade503dfSGreg Roach 498ade503dfSGreg Roach return null; 499ade503dfSGreg Roach } 500ade503dfSGreg Roach 501ade503dfSGreg Roach /** 502ade503dfSGreg Roach * A link to the user's personal home page. 503ade503dfSGreg Roach * 504ade503dfSGreg Roach * @return Menu 505ade503dfSGreg Roach */ 506ade503dfSGreg Roach public function menuMyPage(): Menu 507ade503dfSGreg Roach { 508ade503dfSGreg Roach return new Menu(I18N::translate('My page'), route('user-page', ['ged' => $this->tree->name()]), 'menu-mypage'); 509ade503dfSGreg Roach } 510ade503dfSGreg Roach 511ade503dfSGreg Roach /** 512ade503dfSGreg Roach * A menu for the user's personal pages. 513ade503dfSGreg Roach * 514ade503dfSGreg Roach * @return Menu|null 515ade503dfSGreg Roach */ 516ade503dfSGreg Roach public function menuMyPages() 517ade503dfSGreg Roach { 518ade503dfSGreg Roach if (Auth::id() && $this->tree !== null) { 519ade503dfSGreg Roach return new Menu(I18N::translate('My pages'), '#', 'menu-mymenu', [], array_filter([ 520ade503dfSGreg Roach $this->menuMyPage(), 521ade503dfSGreg Roach $this->menuMyIndividualRecord(), 522ade503dfSGreg Roach $this->menuMyPedigree(), 523ade503dfSGreg Roach $this->menuMyAccount(), 524ade503dfSGreg Roach $this->menuControlPanel(), 525ade503dfSGreg Roach $this->menuChangeBlocks(), 526ade503dfSGreg Roach ])); 527ade503dfSGreg Roach } 528ade503dfSGreg Roach 529ade503dfSGreg Roach return null; 530ade503dfSGreg Roach } 531ade503dfSGreg Roach 532ade503dfSGreg Roach /** 533ade503dfSGreg Roach * A link to the user's individual record. 534ade503dfSGreg Roach * 535ade503dfSGreg Roach * @return Menu|null 536ade503dfSGreg Roach */ 537ade503dfSGreg Roach public function menuMyPedigree() 538ade503dfSGreg Roach { 539ade503dfSGreg Roach $gedcomid = $this->tree->getUserPreference(Auth::user(), 'gedcomid'); 540ade503dfSGreg Roach 541ade503dfSGreg Roach $pedigree_chart = Module::findByComponent('chart', $this->tree, Auth::user()) 542ade503dfSGreg Roach ->filter(function (ModuleInterface $module): bool { 543ade503dfSGreg Roach return $module instanceof PedigreeChartModule; 544ade503dfSGreg Roach }); 545ade503dfSGreg Roach 546ade503dfSGreg Roach if ($gedcomid !== '' && $pedigree_chart instanceof PedigreeChartModule) { 547ade503dfSGreg Roach return new Menu( 548ade503dfSGreg Roach I18N::translate('My pedigree'), 549ade503dfSGreg Roach route('pedigree', [ 550ade503dfSGreg Roach 'xref' => $gedcomid, 551ade503dfSGreg Roach 'ged' => $this->tree->name(), 552ade503dfSGreg Roach ]), 553ade503dfSGreg Roach 'menu-mypedigree' 554ade503dfSGreg Roach ); 555ade503dfSGreg Roach } 556ade503dfSGreg Roach 557ade503dfSGreg Roach return null; 558ade503dfSGreg Roach } 559ade503dfSGreg Roach 560ade503dfSGreg Roach /** 561ade503dfSGreg Roach * Create a pending changes menu. 562ade503dfSGreg Roach * 563ade503dfSGreg Roach * @return Menu|null 564ade503dfSGreg Roach */ 565ade503dfSGreg Roach public function menuPendingChanges() 566ade503dfSGreg Roach { 567ade503dfSGreg Roach if ($this->pendingChangesExist()) { 568ade503dfSGreg Roach $url = route('show-pending', [ 569ade503dfSGreg Roach 'ged' => $this->tree ? $this->tree->name() : '', 570ade503dfSGreg Roach 'url' => $this->request->getRequestUri(), 571ade503dfSGreg Roach ]); 572ade503dfSGreg Roach 573ade503dfSGreg Roach return new Menu(I18N::translate('Pending changes'), $url, 'menu-pending'); 574ade503dfSGreg Roach } 575ade503dfSGreg Roach 576ade503dfSGreg Roach return null; 577ade503dfSGreg Roach } 578ade503dfSGreg Roach 579ade503dfSGreg Roach /** 580ade503dfSGreg Roach * Themes menu. 581ade503dfSGreg Roach * 582ade503dfSGreg Roach * @return Menu|null 583ade503dfSGreg Roach */ 584ade503dfSGreg Roach public function menuThemes() 585ade503dfSGreg Roach { 586ade503dfSGreg Roach if ($this->tree !== null && Site::getPreference('ALLOW_USER_THEMES') === '1' && $this->tree->getPreference('ALLOW_THEME_DROPDOWN') === '1') { 587*df8baf00SGreg Roach $themes = Module::findByInterface(ModuleThemeInterface::class); 588ade503dfSGreg Roach $submenus = []; 589*df8baf00SGreg Roach 590*df8baf00SGreg Roach foreach ($themes as $theme) { 591ade503dfSGreg Roach $class = 'menu-theme-' . $theme->name() . ($theme === $this ? ' active' : ''); 592ade503dfSGreg Roach $submenus[] = new Menu($theme->title(), '#', $class, [ 593ade503dfSGreg Roach 'onclick' => 'return false;', 594ade503dfSGreg Roach 'data-theme' => $theme->name(), 595ade503dfSGreg Roach ]); 596ade503dfSGreg Roach } 597ade503dfSGreg Roach 598ade503dfSGreg Roach usort($submenus, function (Menu $x, Menu $y): int { 599ade503dfSGreg Roach return I18N::strcasecmp($x->getLabel(), $y->getLabel()); 600ade503dfSGreg Roach }); 601ade503dfSGreg Roach 602ade503dfSGreg Roach $menu = new Menu(I18N::translate('Theme'), '#', 'menu-theme', [], $submenus); 603ade503dfSGreg Roach 604ade503dfSGreg Roach return $menu; 605ade503dfSGreg Roach } 606ade503dfSGreg Roach 607ade503dfSGreg Roach return null; 608ade503dfSGreg Roach } 609ade503dfSGreg Roach 610ade503dfSGreg Roach /** 611ade503dfSGreg Roach * Misecellaneous dimensions, fonts, styles, etc. 612ade503dfSGreg Roach * 613ade503dfSGreg Roach * @param string $parameter_name 614ade503dfSGreg Roach * 615ade503dfSGreg Roach * @return string|int|float 616ade503dfSGreg Roach */ 617ade503dfSGreg Roach public function parameter($parameter_name) 618ade503dfSGreg Roach { 619ade503dfSGreg Roach return ''; 620ade503dfSGreg Roach } 621ade503dfSGreg Roach 622ade503dfSGreg Roach /** 623ade503dfSGreg Roach * Are there any pending changes for us to approve? 624ade503dfSGreg Roach * 625ade503dfSGreg Roach * @return bool 626ade503dfSGreg Roach */ 627ade503dfSGreg Roach public function pendingChangesExist(): bool 628ade503dfSGreg Roach { 629ade503dfSGreg Roach return $this->tree && $this->tree->hasPendingEdit() && Auth::isModerator($this->tree); 630ade503dfSGreg Roach } 631ade503dfSGreg Roach 632ade503dfSGreg Roach /** 633ade503dfSGreg Roach * Generate a list of items for the main menu. 634ade503dfSGreg Roach * 635ade503dfSGreg Roach * @return Menu[] 636ade503dfSGreg Roach */ 637ade503dfSGreg Roach public function primaryMenu(): array 638ade503dfSGreg Roach { 639ade503dfSGreg Roach return Module::findByComponent('menu', $this->tree, Auth::user()) 640ade503dfSGreg Roach ->map(function (ModuleMenuInterface $menu): ?Menu { 641ade503dfSGreg Roach return $menu->getMenu($this->tree); 642ade503dfSGreg Roach }) 643ade503dfSGreg Roach ->filter() 644ade503dfSGreg Roach ->all(); 645ade503dfSGreg Roach } 646ade503dfSGreg Roach 647ade503dfSGreg Roach /** 648ade503dfSGreg Roach * Create the primary menu. 649ade503dfSGreg Roach * 650ade503dfSGreg Roach * @param Menu[] $menus 651ade503dfSGreg Roach * 652ade503dfSGreg Roach * @return string 653ade503dfSGreg Roach */ 654ade503dfSGreg Roach public function primaryMenuContent(array $menus): string 655ade503dfSGreg Roach { 656ade503dfSGreg Roach return implode('', array_map(function (Menu $menu): string { 657ade503dfSGreg Roach return $menu->bootstrap4(); 658ade503dfSGreg Roach }, $menus)); 659ade503dfSGreg Roach } 660ade503dfSGreg Roach 661ade503dfSGreg Roach /** 662ade503dfSGreg Roach * Generate a list of items for the user menu. 663ade503dfSGreg Roach * 664ade503dfSGreg Roach * @return Menu[] 665ade503dfSGreg Roach */ 666ade503dfSGreg Roach public function secondaryMenu(): array 667ade503dfSGreg Roach { 668ade503dfSGreg Roach return array_filter([ 669ade503dfSGreg Roach $this->menuPendingChanges(), 670ade503dfSGreg Roach $this->menuMyPages(), 671ade503dfSGreg Roach $this->menuThemes(), 672ade503dfSGreg Roach $this->menuLanguages(), 673ade503dfSGreg Roach $this->menuLogin(), 674ade503dfSGreg Roach $this->menuLogout(), 675ade503dfSGreg Roach ]); 676ade503dfSGreg Roach } 677ade503dfSGreg Roach 678ade503dfSGreg Roach /** 679ade503dfSGreg Roach * A list of CSS files to include for this page. 680ade503dfSGreg Roach * 681ade503dfSGreg Roach * @return string[] 682ade503dfSGreg Roach */ 683ade503dfSGreg Roach public function stylesheets(): array 684ade503dfSGreg Roach { 685ade503dfSGreg Roach return []; 686ade503dfSGreg Roach } 68749a243cbSGreg Roach} 688