ModuleThemeTrait   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 468
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 151
dl 0
loc 468
rs 3.36
c 0
b 0
f 0
wmc 63

21 Methods

Rating   Name   Duplication   Size   Complexity  
C individualBoxFacts() 0 45 12
A userMenu() 0 9 1
A stylesheets() 0 3 1
A description() 0 3 1
A individualBoxMenu() 0 5 1
A individualBoxMenuFamilyLinks() 0 18 6
A menuControlPanel() 0 11 3
A menuLanguages() 0 17 4
A menuMyPage() 0 3 1
A individualBoxMenuCharts() 0 16 3
A genealogyMenuContent() 0 3 1
A menuLogin() 0 22 3
A menuMyAccount() 0 5 1
A menuThemes() 0 20 3
A genealogyMenu() 0 13 2
A menuChangeBlocks() 0 14 5
A menuMyPedigree() 0 22 4
A menuLogout() 0 12 2
A menuMyPages() 0 18 3
A menuMyIndividualRecord() 0 9 2
A menuPendingChanges() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like ModuleThemeTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ModuleThemeTrait, and based on these observations, apply Extract Interface, too.

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