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