ModuleThemeTrait   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Importance

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

21 Methods

Rating   Name   Duplication   Size   Complexity  
A menuMyPage() 0 3 1
A menuMyIndividualRecord() 0 9 2
A menuControlPanel() 0 11 3
A individualBoxMenuCharts() 0 16 3
A userMenu() 0 9 1
A stylesheets() 0 3 1
A genealogyMenuContent() 0 3 1
A description() 0 3 1
A individualBoxMenuFamilyLinks() 0 18 6
A menuLogin() 0 22 3
A individualBoxMenu() 0 5 1
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 menuLanguages() 0 17 4
A menuMyPages() 0 18 3
A menuPendingChanges() 0 14 4
C individualBoxFacts() 0 45 12

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) 2025 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;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Auth was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use Fisharebest\Webtrees\Contracts\UserInterface;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Contracts\UserInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\I18N was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
40
use Fisharebest\Webtrees\Individual;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Individual was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
41
use Fisharebest\Webtrees\Menu;
42
use Fisharebest\Webtrees\Registry;
43
use Fisharebest\Webtrees\Services\ModuleService;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Services\ModuleService was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
    abstract public function title(): string;
59
60
    public function description(): string
61
    {
62
        return I18N::translate('Theme') . ' — ' . $this->title();
63
    }
64
65
    /**
66
     * Generate the facts, for display in charts.
67
     *
68
     * @param Individual $individual
69
     *
70
     * @return string
71
     */
72
    public function individualBoxFacts(Individual $individual): string
73
    {
74
        $html = '';
75
76
        $opt_tags = preg_split('/\W/', $individual->tree()->getPreference('CHART_BOX_TAGS'), 0, PREG_SPLIT_NO_EMPTY);
77
        // Show BIRT or equivalent event
78
        foreach (Gedcom::BIRTH_EVENTS as $birttag) {
79
            if (!in_array($birttag, $opt_tags, true)) {
80
                $event = $individual->facts([$birttag])->first();
81
                if ($event instanceof Fact) {
82
                    $html .= $event->summary();
83
                    break;
84
                }
85
            }
86
        }
87
        // Show optional events (before death)
88
        foreach ($opt_tags as $key => $tag) {
89
            if (!in_array($tag, Gedcom::DEATH_EVENTS, true)) {
90
                $event = $individual->facts([$tag])->first();
91
                if ($event instanceof Fact) {
92
                    $html .= $event->summary();
93
                    unset($opt_tags[$key]);
94
                }
95
            }
96
        }
97
        // Show DEAT or equivalent event
98
        foreach (Gedcom::DEATH_EVENTS as $deattag) {
99
            $event = $individual->facts([$deattag])->first();
100
            if ($event instanceof Fact) {
101
                $html .= $event->summary();
102
                if (in_array($deattag, $opt_tags, true)) {
103
                    unset($opt_tags[array_search($deattag, $opt_tags, true)]);
104
                }
105
                break;
106
            }
107
        }
108
        // Show remaining optional events (after death)
109
        foreach ($opt_tags as $tag) {
110
            $event = $individual->facts([$tag])->first();
111
            if ($event instanceof Fact) {
112
                $html .= $event->summary();
113
            }
114
        }
115
116
        return $html;
117
    }
118
119
    /**
120
     * Links, to show in chart boxes;
121
     *
122
     * @param Individual $individual
123
     *
124
     * @return array<Menu>
125
     */
126
    public function individualBoxMenu(Individual $individual): array
127
    {
128
        return array_merge(
129
            $this->individualBoxMenuCharts($individual),
130
            $this->individualBoxMenuFamilyLinks($individual)
131
        );
132
    }
133
134
    /**
135
     * Chart links, to show in chart boxes;
136
     *
137
     * @param Individual $individual
138
     *
139
     * @return array<Menu>
140
     */
141
    public function individualBoxMenuCharts(Individual $individual): array
142
    {
143
        $menus = [];
144
145
        $module_service = Registry::container()->get(ModuleService::class);
146
147
        foreach ($module_service->findByComponent(ModuleChartInterface::class, $individual->tree(), Auth::user()) as $chart) {
148
            $menu = $chart->chartBoxMenu($individual);
149
            if ($menu) {
150
                $menus[] = $menu;
151
            }
152
        }
153
154
        usort($menus, static fn (Menu $x, Menu $y): int => I18N::comparator()($x->getLabel(), $y->getLabel()));
155
156
        return $menus;
157
    }
158
159
    /**
160
     * Family links, to show in chart boxes.
161
     *
162
     * @param Individual $individual
163
     *
164
     * @return array<Menu>
165
     */
166
    public function individualBoxMenuFamilyLinks(Individual $individual): array
167
    {
168
        $menus = [];
169
170
        foreach ($individual->spouseFamilies() as $family) {
171
            $menus[] = new Menu('<strong>' . I18N::translate('Family with spouse') . '</strong>', $family->url());
172
            $spouse  = $family->spouse($individual);
173
            if ($spouse && $spouse->canShowName()) {
174
                $menus[] = new Menu($spouse->fullName(), $spouse->url());
175
            }
176
            foreach ($family->children() as $child) {
177
                if ($child->canShowName()) {
178
                    $menus[] = new Menu($child->fullName(), $child->url());
179
                }
180
            }
181
        }
182
183
        return $menus;
184
    }
185
186
    /**
187
     * Generate a menu item to change the blocks on the current tree/user page.
188
     *
189
     * @param Tree $tree
190
     *
191
     * @return Menu|null
192
     */
193
    public function menuChangeBlocks(Tree $tree): Menu|null
194
    {
195
        $request = Registry::container()->get(ServerRequestInterface::class);
196
        $route   = Validator::attributes($request)->route();
197
198
        if (Auth::check() && $route->name === UserPage::class) {
199
            return new Menu(I18N::translate('Customize this page'), route(UserPageEdit::class, ['tree' => $tree->name()]), 'menu-change-blocks');
200
        }
201
202
        if (Auth::isManager($tree) && $route->name === TreePage::class) {
203
            return new Menu(I18N::translate('Customize this page'), route(TreePageEdit::class, ['tree' => $tree->name()]), 'menu-change-blocks');
204
        }
205
206
        return null;
207
    }
208
209
    /**
210
     * Generate a menu item for the control panel.
211
     *
212
     * @param Tree $tree
213
     *
214
     * @return Menu|null
215
     */
216
    public function menuControlPanel(Tree $tree): Menu|null
217
    {
218
        if (Auth::isAdmin()) {
219
            return new Menu(I18N::translate('Control panel'), route(ControlPanel::class), 'menu-admin');
220
        }
221
222
        if (Auth::isManager($tree)) {
223
            return new Menu(I18N::translate('Control panel'), route(ManageTrees::class, ['tree' => $tree->name()]), 'menu-admin');
224
        }
225
226
        return null;
227
    }
228
229
    /**
230
     * A menu to show a list of available languages.
231
     *
232
     * @return Menu|null
233
     */
234
    public function menuLanguages(): Menu|null
235
    {
236
        $menu = new Menu(I18N::translate('Language'), '#', 'menu-language');
237
238
        foreach (I18N::activeLocales() as $active_locale) {
239
            $language_tag = $active_locale->languageTag();
240
            $class        = 'menu-language-' . $language_tag . (I18N::languageTag() === $language_tag ? ' active' : '');
241
            $menu->addSubmenu(new Menu($active_locale->endonym(), '#', $class, [
242
                'data-wt-post-url' => route(SelectLanguage::class, ['language' => $language_tag]),
243
            ]));
244
        }
245
246
        if (count($menu->getSubmenus()) > 1) {
247
            return $menu;
248
        }
249
250
        return null;
251
    }
252
253
    /**
254
     * A login menu option (or null if we are already logged in).
255
     *
256
     * @return Menu|null
257
     */
258
    public function menuLogin(): Menu|null
259
    {
260
        if (Auth::check()) {
261
            return null;
262
        }
263
264
        $request = Registry::container()->get(ServerRequestInterface::class);
265
266
        // Return to this page after login...
267
        $redirect = Validator::queryParams($request)->string('url', (string) $request->getUri());
268
        $tree     = Validator::attributes($request)->treeOptional();
269
        $route    = Validator::attributes($request)->route();
270
271
        // ...but switch from the tree-page to the user-page
272
        if ($route->name === TreePage::class) {
273
            $redirect = route(UserPage::class, ['tree' => $tree?->name()]);
274
        }
275
276
        // Stay on the same tree page
277
        $url = route(LoginPage::class, ['tree' => $tree?->name(), 'url' => $redirect]);
278
279
        return new Menu(I18N::translate('Sign in'), $url, 'menu-login', ['rel' => 'nofollow']);
280
    }
281
282
    /**
283
     * A logout menu option (or null if we are already logged out).
284
     *
285
     * @return Menu|null
286
     */
287
    public function menuLogout(): Menu|null
288
    {
289
        if (Auth::check()) {
290
            $parameters = [
291
                'data-wt-post-url'   => route(Logout::class),
292
                'data-wt-reload-url' => route(HomePage::class)
293
            ];
294
295
            return new Menu(I18N::translate('Sign out'), '#', 'menu-logout', $parameters);
296
        }
297
298
        return null;
299
    }
300
301
    /**
302
     * A link to allow users to edit their account settings.
303
     *
304
     * @param Tree|null $tree
305
     *
306
     * @return Menu
307
     */
308
    public function menuMyAccount(Tree|null $tree): Menu
309
    {
310
        $url = route(AccountEdit::class, ['tree' => $tree?->name()]);
311
312
        return new Menu(I18N::translate('My account'), $url, 'menu-myaccount');
313
    }
314
315
    /**
316
     * A link to the user's individual record (individual.php).
317
     *
318
     * @param Tree $tree
319
     *
320
     * @return Menu|null
321
     */
322
    public function menuMyIndividualRecord(Tree $tree): Menu|null
323
    {
324
        $record = Registry::individualFactory()->make($tree->getUserPreference(Auth::user(), UserInterface::PREF_TREE_ACCOUNT_XREF), $tree);
325
326
        if ($record instanceof Individual) {
327
            return new Menu(I18N::translate('My individual record'), $record->url(), 'menu-myrecord');
328
        }
329
330
        return null;
331
    }
332
333
    /**
334
     * A link to the user's personal home page.
335
     *
336
     * @param Tree $tree
337
     *
338
     * @return Menu
339
     */
340
    public function menuMyPage(Tree $tree): Menu
341
    {
342
        return new Menu(I18N::translate('My page'), route(UserPage::class, ['tree' => $tree->name()]), 'menu-mypage');
343
    }
344
345
    /**
346
     * A menu for the user's personal pages.
347
     *
348
     * @param Tree|null $tree
349
     *
350
     * @return Menu|null
351
     */
352
    public function menuMyPages(Tree|null $tree): Menu|null
353
    {
354
        if (Auth::check()) {
355
            if ($tree instanceof Tree) {
356
                return new Menu(I18N::translate('My pages'), '#', 'menu-mymenu', [], array_filter([
357
                    $this->menuMyPage($tree),
358
                    $this->menuMyIndividualRecord($tree),
359
                    $this->menuMyPedigree($tree),
360
                    $this->menuMyAccount($tree),
361
                    $this->menuControlPanel($tree),
362
                    $this->menuChangeBlocks($tree),
363
                ]));
364
            }
365
366
            return $this->menuMyAccount($tree);
367
        }
368
369
        return null;
370
    }
371
372
    /**
373
     * A link to the user's individual record.
374
     *
375
     * @param Tree $tree
376
     *
377
     * @return Menu|null
378
     */
379
    public function menuMyPedigree(Tree $tree): Menu|null
380
    {
381
        $my_xref = $tree->getUserPreference(Auth::user(), UserInterface::PREF_TREE_ACCOUNT_XREF);
382
383
        $module_service = Registry::container()->get(ModuleService::class);
384
        $pedigree_chart = $module_service
385
            ->findByComponent(ModuleChartInterface::class, $tree, Auth::user())
386
            ->first(static fn (ModuleInterface $module): bool => $module instanceof PedigreeChartModule);
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Module\PedigreeChartModule was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
387
388
        if ($my_xref !== '' && $pedigree_chart instanceof PedigreeChartModule) {
389
            $individual = Registry::individualFactory()->make($my_xref, $tree);
390
391
            if ($individual instanceof Individual) {
392
                return new Menu(
393
                    I18N::translate('My pedigree'),
394
                    $pedigree_chart->chartUrl($individual),
395
                    'menu-mypedigree'
396
                );
397
            }
398
        }
399
400
        return null;
401
    }
402
403
    /**
404
     * Create a pending changes menu.
405
     *
406
     * @param Tree|null $tree
407
     *
408
     * @return Menu|null
409
     */
410
    public function menuPendingChanges(Tree|null $tree): Menu|null
411
    {
412
        if ($tree instanceof Tree && $tree->hasPendingEdit() && Auth::isModerator($tree)) {
413
            $request = Registry::container()->get(ServerRequestInterface::class);
414
415
            $url = route(PendingChanges::class, [
416
                'tree' => $tree->name(),
417
                'url' => (string) $request->getUri(),
418
            ]);
419
420
            return new Menu(I18N::translate('Pending changes'), $url, 'menu-pending');
421
        }
422
423
        return null;
424
    }
425
426
    /**
427
     * Themes menu.
428
     *
429
     * @return Menu|null
430
     */
431
    public function menuThemes(): Menu|null
432
    {
433
        $module_service = Registry::container()->get(ModuleService::class);
434
        $themes         = $module_service->findByInterface(ModuleThemeInterface::class, false, true);
435
        $current_theme  = Registry::container()->get(ModuleThemeInterface::class);
436
437
        if ($themes->count() > 1) {
438
            $submenus = $themes->map(static function (ModuleThemeInterface $theme) use ($current_theme): Menu {
439
                $active     = $theme->name() === $current_theme->name();
440
                $class      = 'menu-theme-' . $theme->name() . ($active ? ' active' : '');
441
442
                return new Menu($theme->title(), '#', $class, [
443
                    'data-wt-post-url' => route(SelectTheme::class, ['theme' => $theme->name()]),
444
                ]);
445
            });
446
447
            return new Menu(I18N::translate('Theme'), '#', 'menu-theme', [], $submenus->all());
448
        }
449
450
        return null;
451
    }
452
453
    /**
454
     * Generate a list of items for the main menu.
455
     *
456
     * @param Tree|null $tree
457
     *
458
     * @return array<Menu>
459
     */
460
    public function genealogyMenu(Tree|null $tree): array
461
    {
462
        if ($tree === null) {
463
            return [];
464
        }
465
466
        $module_service = Registry::container()->get(ModuleService::class);
467
468
        return $module_service
469
            ->findByComponent(ModuleMenuInterface::class, $tree, Auth::user())
470
            ->map(static fn (ModuleMenuInterface $menu): Menu|null => $menu->getMenu($tree))
471
            ->filter()
472
            ->all();
473
    }
474
475
    /**
476
     * Create the genealogy menu.
477
     *
478
     * @param array<Menu> $menus
479
     *
480
     * @return string
481
     */
482
    public function genealogyMenuContent(array $menus): string
483
    {
484
        return implode('', array_map(static fn (Menu $menu): string => view('components/menu-item', ['menu' => $menu]), $menus));
485
    }
486
487
    /**
488
     * Generate a list of items for the user menu.
489
     *
490
     * @param Tree|null $tree
491
     *
492
     * @return array<Menu>
493
     */
494
    public function userMenu(Tree|null $tree): array
495
    {
496
        return array_filter([
497
            $this->menuPendingChanges($tree),
498
            $this->menuMyPages($tree),
499
            $this->menuThemes(),
500
            $this->menuLanguages(),
501
            $this->menuLogin(),
502
            $this->menuLogout(),
503
        ]);
504
    }
505
506
    /**
507
     * A list of CSS files to include for this page.
508
     *
509
     * @return array<string>
510
     */
511
    public function stylesheets(): array
512
    {
513
        return [];
514
    }
515
}
516