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