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