AbstractTheme   F
last analyzed

Complexity

Total Complexity 271

Size/Duplication

Total Lines 2156
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 714
dl 0
loc 2156
rs 1.886
c 0
b 0
f 0
wmc 271

121 Methods

Rating   Name   Duplication   Size   Complexity  
A metaDescription() 0 6 2
A headerContent() 0 8 1
F menuFavorites() 0 51 22
A menuChartCompact() 0 5 1
B contactLinks() 0 15 7
A menuListsIndividuals() 0 3 1
A metaCharset() 0 3 1
A menuMyPages() 0 13 2
A formatTreeTitle() 0 6 2
A primaryMenuContainer() 0 3 1
A menuListsSources() 0 3 1
A metaGenerator() 0 6 2
A menuChangeBlocks() 0 8 6
A pendingChangesLink() 0 6 1
A head() 0 11 1
A footerContainer() 0 3 1
A icon() 0 11 3
A analyticsPiwikTracker() 0 18 3
A metaRobots() 0 6 2
A formQuickSearchFields() 0 5 1
A menuLists() 0 40 5
A bodyHeader() 0 10 1
A menuChartLifespan() 0 5 1
A formatContactLinks() 0 6 2
B individualBoxLdsSummary() 0 17 7
A menuModules() 0 8 2
C individualBoxFacts() 0 45 12
A formatSecondaryMenu() 0 6 1
A formatSecondaryMenuItem() 0 3 1
A contactLinkGenealogy() 0 3 1
A menuMyIndividualRecord() 0 8 2
A menuThemes() 0 21 6
A menuMyPage() 0 3 1
A menuListsBranches() 0 3 1
A secondaryMenuContent() 0 5 1
A primaryMenu() 0 18 2
A menuCalendar() 0 9 1
A menuMyAccount() 0 6 2
A menuSearchGeneral() 0 3 1
A parameter() 0 42 2
A __construct() 0 2 1
A hookHeaderExtraContent() 0 3 1
A individualBoxMenuFamilyLinks() 0 18 6
A title() 0 3 1
A html() 0 3 1
B pageViews() 0 17 10
A formatBlock() 0 7 1
A analytics() 0 22 3
A sendHeaders() 0 3 1
A individualBox() 0 35 4
A headContents() 0 32 5
A menuHomePage() 0 16 5
A pendingChangesExist() 0 3 3
A hookAfterInit() 0 2 1
A flashMessagesContainer() 0 11 3
A bodyHeaderPopupWindow() 0 6 1
A secondaryMenu() 0 10 1
A footerContent() 0 7 1
A menuChartHourglass() 0 5 1
A individualBoxSmallEmpty() 0 3 1
A menuReports() 0 11 3
A menuControlPanel() 0 6 2
A footerContainerPopupWindow() 0 3 1
A formQuickSearch() 0 11 2
A menuChartTimeline() 0 5 1
A individualBoxMenuCharts() 0 15 3
A menuSearchAndReplace() 0 6 2
A analyticsStatcounterTracker() 0 11 3
A secondaryMenuContainer() 0 3 1
A menuChartFamilyBook() 0 5 1
A individualBoxMenu() 0 8 1
A cookieWarning() 0 15 6
A menuChartInteractiveTree() 0 5 1
A logoHeader() 0 3 1
A individualBoxSexSymbol() 0 6 2
A primaryMenuContent() 0 5 1
A menuChartStatistics() 0 5 1
A menuListsFamilies() 0 3 1
A formatPageViews() 0 10 2
A flashMessageContainer() 0 3 1
A menuChartDescendants() 0 5 1
A analyticsGoogleWebmaster() 0 7 3
A metaUaCompatible() 0 3 1
A htmlAlert() 0 15 2
A menuPendingChanges() 0 8 2
A menuChartPedigree() 0 5 1
A menuListsRepositories() 0 3 1
A contactLink() 0 11 3
A menuChart() 0 18 4
A menuSearchAdvanced() 0 3 1
A analyticsBingWebmaster() 0 7 3
A metaViewport() 0 3 1
A individualBoxSmall() 0 20 2
A stylesheets() 0 13 2
A menuChartRelationship() 0 5 1
A individualBoxLarge() 0 35 3
A favicon() 0 6 1
A menuSearchPhonetic() 0 3 1
A menuChartPedigreeMap() 0 5 1
A menuMyPedigree() 0 15 5
A menuListsMedia() 0 3 1
A doctype() 0 3 1
A menuChartAncestors() 0 5 1
A menuLogout() 0 6 2
A menuChartFanChart() 0 5 1
A individualBoxEmpty() 0 3 1
A menuListsPlaces() 0 3 1
A headerSimple() 0 5 1
A menuLogin() 0 6 3
A logoPoweredBy() 0 3 1
A menuListsNotes() 0 3 1
A formatPendingChangesLink() 0 6 2
A menuSearch() 0 7 1
A accessibilityLinks() 0 8 1
A contactLinkTechnical() 0 3 1
A analyticsGoogleTracker() 0 18 4
A pendingChangesLinkText() 0 3 1
A menuLanguages() 0 17 4
A hookFooterExtraJavascript() 0 3 1
A init() 0 6 2
A contactLinkEverything() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractTheme 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 AbstractTheme, and based on these observations, apply Extract Interface, too.

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
 */
16
namespace Fisharebest\Webtrees\Theme;
17
18
use Fisharebest\Webtrees\Auth;
19
use Fisharebest\Webtrees\Controller\PageController;
20
use Fisharebest\Webtrees\Database;
21
use Fisharebest\Webtrees\Fact;
22
use Fisharebest\Webtrees\Filter;
23
use Fisharebest\Webtrees\FlashMessages;
24
use Fisharebest\Webtrees\Functions\Functions;
25
use Fisharebest\Webtrees\GedcomRecord;
26
use Fisharebest\Webtrees\GedcomTag;
27
use Fisharebest\Webtrees\HitCounter;
28
use Fisharebest\Webtrees\I18N;
29
use Fisharebest\Webtrees\Individual;
30
use Fisharebest\Webtrees\Menu;
31
use Fisharebest\Webtrees\Module;
32
use Fisharebest\Webtrees\Module\AncestorsChartModule;
33
use Fisharebest\Webtrees\Module\CompactTreeChartModule;
34
use Fisharebest\Webtrees\Module\DescendancyChartModule;
35
use Fisharebest\Webtrees\Module\FamilyBookChartModule;
36
use Fisharebest\Webtrees\Module\FamilyTreeFavoritesModule;
37
use Fisharebest\Webtrees\Module\FanChartModule;
38
use Fisharebest\Webtrees\Module\GoogleMapsModule;
0 ignored issues
show
Bug introduced by
The type Fisharebest\Webtrees\Module\GoogleMapsModule 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...
39
use Fisharebest\Webtrees\Module\HourglassChartModule;
40
use Fisharebest\Webtrees\Module\InteractiveTreeModule;
41
use Fisharebest\Webtrees\Module\LifespansChartModule;
42
use Fisharebest\Webtrees\Module\PedigreeChartModule;
43
use Fisharebest\Webtrees\Module\RelationshipsChartModule;
44
use Fisharebest\Webtrees\Module\StatisticsChartModule;
45
use Fisharebest\Webtrees\Module\TimelineChartModule;
46
use Fisharebest\Webtrees\Module\UserFavoritesModule;
47
use Fisharebest\Webtrees\Site;
48
use Fisharebest\Webtrees\Theme;
49
use Fisharebest\Webtrees\Tree;
50
use Fisharebest\Webtrees\User;
51
52
/**
53
 * Common functions for all themes.
54
 */
55
abstract class AbstractTheme
56
{
57
    /** @var Tree The current tree */
58
    protected $tree;
59
60
    /** @var string An escaped version of the "ged=XXX" URL parameter */
61
    protected $tree_url;
62
63
    /** @var int The number of times this page has been shown */
64
    protected $page_views;
65
66
    /**
67
     * Custom themes should place their initialization code in the function hookAfterInit(), not in
68
     * the constructor, as all themes get constructed - whether they are used or not.
69
     */
70
    final public function __construct()
71
    {
72
    }
73
74
    /**
75
     * Create accessibility links for the header.
76
     *
77
     * "Skip to content" allows keyboard only users to navigate over the headers without
78
     * pressing TAB many times.
79
     *
80
     * @return string
81
     */
82
    protected function accessibilityLinks()
83
    {
84
        return
85
            '<div class="accessibility-links">' .
86
            '<a class="sr-only sr-only-focusable btn btn-info btn-sm" href="#content">' .
87
            /* I18N: Skip over the headers and menus, to the main content of the page */ I18N::translate('Skip to content') .
88
            '</a>' .
89
            '</div>';
90
    }
91
92
    /**
93
     * Create scripts for analytics and tracking.
94
     *
95
     * @return string
96
     */
97
    protected function analytics()
98
    {
99
        if ($this->themeId() === '_administration' || !empty($_SERVER['HTTP_DNT'])) {
0 ignored issues
show
Bug introduced by
The method themeId() does not exist on Fisharebest\Webtrees\Theme\AbstractTheme. Since it exists in all sub-types, consider adding an abstract or default implementation to Fisharebest\Webtrees\Theme\AbstractTheme. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

99
        if ($this->/** @scrutinizer ignore-call */ themeId() === '_administration' || !empty($_SERVER['HTTP_DNT'])) {
Loading history...
100
            return '';
101
        } else {
102
            return
103
                $this->analyticsBingWebmaster(
104
                    Site::getPreference('BING_WEBMASTER_ID')
105
                ) .
106
                $this->analyticsGoogleWebmaster(
107
                    Site::getPreference('GOOGLE_WEBMASTER_ID')
108
                ) .
109
                $this->analyticsGoogleTracker(
110
                    Site::getPreference('GOOGLE_ANALYTICS_ID')
111
                ) .
112
                $this->analyticsPiwikTracker(
113
                    Site::getPreference('PIWIK_URL'),
114
                    Site::getPreference('PIWIK_SITE_ID')
115
                ) .
116
                $this->analyticsStatcounterTracker(
117
                    Site::getPreference('STATCOUNTER_PROJECT_ID'),
118
                    Site::getPreference('STATCOUNTER_SECURITY_ID')
119
                );
120
        }
121
    }
122
123
    /**
124
     * Create the verification code for Google Webmaster Tools.
125
     *
126
     * @param string $verification_id
127
     *
128
     * @return string
129
     */
130
    protected function analyticsBingWebmaster($verification_id)
131
    {
132
        // Only need to add this to the home page.
133
        if (WT_SCRIPT_NAME === 'index.php' && $verification_id) {
0 ignored issues
show
introduced by
The condition Fisharebest\Webtrees\The...PT_NAME === 'index.php' is always false.
Loading history...
134
            return '<meta name="msvalidate.01" content="' . $verification_id . '">';
135
        } else {
136
            return '';
137
        }
138
    }
139
140
    /**
141
     * Create the verification code for Google Webmaster Tools.
142
     *
143
     * @param string $verification_id
144
     *
145
     * @return string
146
     */
147
    protected function analyticsGoogleWebmaster($verification_id)
148
    {
149
        // Only need to add this to the home page.
150
        if (WT_SCRIPT_NAME === 'index.php' && $verification_id) {
0 ignored issues
show
introduced by
The condition Fisharebest\Webtrees\The...PT_NAME === 'index.php' is always false.
Loading history...
151
            return '<meta name="google-site-verification" content="' . $verification_id . '">';
152
        } else {
153
            return '';
154
        }
155
    }
156
157
    /**
158
     * Create the tracking code for Google Analytics.
159
     *
160
     * See https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced
161
     *
162
     * @param string $analytics_id
163
     *
164
     * @return string
165
     */
166
    protected function analyticsGoogleTracker($analytics_id)
167
    {
168
        if ($analytics_id) {
169
            // Add extra dimensions (i.e. filtering categories)
170
            $dimensions = (object) array(
171
                'dimension1' => $this->tree ? $this->tree->getName() : '-',
172
                'dimension2' => $this->tree ? Auth::accessLevel($this->tree) : '-',
173
            );
174
175
            return
176
                '<script async src="https://www.google-analytics.com/analytics.js"></script>' .
177
                '<script>' .
178
                'window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;' .
179
                'ga("create","' . $analytics_id . '","auto");' .
180
                'ga("send", "pageview", ' . json_encode($dimensions) . ');' .
181
                '</script>';
182
        } else {
183
            return '';
184
        }
185
    }
186
187
    /**
188
     * Create the tracking code for Piwik Analytics.
189
     *
190
     * @param string $url     - The domain/path to Piwik
191
     * @param string $site_id - The Piwik site identifier
192
     *
193
     * @return string
194
     */
195
    protected function analyticsPiwikTracker($url, $site_id)
196
    {
197
        $url = preg_replace(array('/^https?:\/\//', '/\/$/'), '', $url);
198
199
        if ($url && $site_id) {
200
            return
201
                '<script>' .
202
                'var _paq=_paq||[];' .
203
                '(function(){var u=(("https:"==document.location.protocol)?"https://' . $url . '/":"http://' . $url . '/");' .
204
                '_paq.push(["setSiteId",' . $site_id . ']);' .
205
                '_paq.push(["setTrackerUrl",u+"piwik.php"]);' .
206
                '_paq.push(["trackPageView"]);' .
207
                '_paq.push(["enableLinkTracking"]);' .
208
                'var d=document,g=d.createElement("script"),s=d.getElementsByTagName("script")[0];g.defer=true;g.async=true;g.src=u+"piwik.js";' .
209
                's.parentNode.insertBefore(g,s);})();' .
210
                '</script>';
211
        } else {
212
            return '';
213
        }
214
    }
215
216
    /**
217
     * Create the tracking code for Statcounter.
218
     *
219
     * @param string $project_id  - The statcounter project ID
220
     * @param string $security_id - The statcounter security ID
221
     *
222
     * @return string
223
     */
224
    protected function analyticsStatcounterTracker($project_id, $security_id)
225
    {
226
        if ($project_id && $security_id) {
227
            return
228
                '<script>' .
229
                'var sc_project=' . (int) $project_id . ',sc_invisible=1,sc_security="' . $security_id .
230
                '",scJsHost = (("https:"===document.location.protocol)?"https://secure.":"http://www.");' .
231
                'document.write("<sc"+"ript src=\'"+scJsHost+"statcounter.com/counter/counter.js\'></"+"script>");' .
232
                '</script>';
233
        } else {
234
            return '';
235
        }
236
    }
237
238
    /**
239
     * Create the top of the <body>.
240
     *
241
     * @return string
242
     */
243
    public function bodyHeader()
244
    {
245
        return
246
            '<body class="container">' .
247
            '<header>' .
248
            $this->headerContent() .
249
            $this->primaryMenuContainer($this->primaryMenu()) .
250
            '</header>' .
251
            '<main id="content">' .
252
            $this->flashMessagesContainer(FlashMessages::getMessages());
253
    }
254
255
    /**
256
     * Create the top of the <body> (for popup windows).
257
     *
258
     * @return string
259
     */
260
    public function bodyHeaderPopupWindow()
261
    {
262
        return
263
            '<body class="container container-popup">' .
264
            '<main id="content">' .
265
            $this->flashMessagesContainer(FlashMessages::getMessages());
266
    }
267
268
    /**
269
     * Create a contact link for a user.
270
     *
271
     * @param User $user
272
     *
273
     * @return string
274
     */
275
    public function contactLink(User $user)
276
    {
277
        $method = $user->getPreference('contactmethod');
278
279
        switch ($method) {
280
            case 'none':
281
                return '';
282
            case 'mailto':
283
                return '<a href="mailto:' . Filter::escapeHtml($user->getEmail()) . '">' . $user->getRealNameHtml() . '</a>';
284
            default:
285
                return "<a href='#' onclick='message(\"" . Filter::escapeHtml($user->getUserName()) . "\", \"" . $method . "\", \"" . WT_BASE_URL . Filter::escapeHtml(Functions::getQueryUrl()) . "\", \"\");return false;'>" . $user->getRealNameHtml() . '</a>';
286
        }
287
    }
288
289
    /**
290
     * Create contact link for both technical and genealogy support.
291
     *
292
     * @param User $user
293
     *
294
     * @return string
295
     */
296
    protected function contactLinkEverything(User $user)
297
    {
298
        return I18N::translate('For technical support or genealogy questions contact %s.', $this->contactLink($user));
299
    }
300
301
    /**
302
     * Create contact link for genealogy support.
303
     *
304
     * @param User $user
305
     *
306
     * @return string
307
     */
308
    protected function contactLinkGenealogy(User $user)
309
    {
310
        return I18N::translate('For help with genealogy questions contact %s.', $this->contactLink($user));
311
    }
312
313
    /**
314
     * Create contact link for technical support.
315
     *
316
     * @param User $user
317
     *
318
     * @return string
319
     */
320
    protected function contactLinkTechnical(User $user)
321
    {
322
        return I18N::translate('For technical support and information contact %s.', $this->contactLink($user));
323
    }
324
325
    /**
326
     * Create contact links for the page footer.
327
     *
328
     * @return string
329
     */
330
    protected function contactLinks()
331
    {
332
        $contact_user   = User::find($this->tree->getPreference('CONTACT_USER_ID'));
0 ignored issues
show
Bug introduced by
It seems like $this->tree->getPreference('CONTACT_USER_ID') can also be of type string; however, parameter $user_id of Fisharebest\Webtrees\User::find() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

332
        $contact_user   = User::find(/** @scrutinizer ignore-type */ $this->tree->getPreference('CONTACT_USER_ID'));
Loading history...
333
        $webmaster_user = User::find($this->tree->getPreference('WEBMASTER_USER_ID'));
334
335
        if ($contact_user && $contact_user === $webmaster_user) {
336
            return $this->contactLinkEverything($contact_user);
337
        } elseif ($contact_user && $webmaster_user) {
0 ignored issues
show
introduced by
$webmaster_user is of type Fisharebest\Webtrees\User, thus it always evaluated to true.
Loading history...
338
            return $this->contactLinkGenealogy($contact_user) . '<br>' . $this->contactLinkTechnical($webmaster_user);
339
        } elseif ($contact_user) {
340
            return $this->contactLinkGenealogy($contact_user);
341
        } elseif ($webmaster_user) {
342
            return $this->contactLinkTechnical($webmaster_user);
343
        } else {
344
            return '';
345
        }
346
    }
347
348
    /**
349
     * Create a cookie warning.
350
     *
351
     * @return string
352
     */
353
    public function cookieWarning()
354
    {
355
        if (
356
            empty($_SERVER['HTTP_DNT']) &&
357
            empty($_COOKIE['cookie']) &&
358
            (Site::getPreference('GOOGLE_ANALYTICS_ID') || Site::getPreference('PIWIK_SITE_ID') || Site::getPreference('STATCOUNTER_PROJECT_ID'))
359
        ) {
360
            return
361
                '<div class="cookie-warning">' .
362
                I18N::translate('Cookies') . ' - ' .
363
                I18N::translate('This website uses cookies to learn about visitor behaviour.') . ' ' .
364
                '<button onclick="document.cookie=\'cookie=1\'; this.parentNode.classList.add(\'hidden\');">' . I18N::translate('continue') . '</button>' .
365
                '</div>';
366
        } else {
367
            return '';
368
        }
369
    }
370
371
    /**
372
     * Create the <DOCTYPE> tag.
373
     *
374
     * @return string
375
     */
376
    public function doctype()
377
    {
378
        return '<!DOCTYPE html>';
379
    }
380
381
    /**
382
     * HTML link to a "favorites icon".
383
     *
384
     * @return string
385
     */
386
    protected function favicon()
387
    {
388
        return
389
            '<link rel="icon" href="' . $this->assetUrl() . 'favicon.png" type="image/png">' .
0 ignored issues
show
Bug introduced by
The method assetUrl() does not exist on Fisharebest\Webtrees\Theme\AbstractTheme. Since it exists in all sub-types, consider adding an abstract or default implementation to Fisharebest\Webtrees\Theme\AbstractTheme. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

389
            '<link rel="icon" href="' . $this->/** @scrutinizer ignore-call */ assetUrl() . 'favicon.png" type="image/png">' .
Loading history...
390
            '<link rel="icon" type="image/png" href="' . $this->assetUrl() . 'favicon192.png" sizes="192x192">' .
391
            '<link rel="apple-touch-icon" sizes="180x180" href="' . $this->assetUrl() . 'favicon180.png">';
392
    }
393
394
    /**
395
     * Add markup to a flash message.
396
     *
397
     * @param \stdClass $message
398
     *
399
     * @return string
400
     */
401
    protected function flashMessageContainer(\stdClass $message)
402
    {
403
        return $this->htmlAlert($message->text, $message->status, true);
404
    }
405
406
    /**
407
     * Create a container for messages that are "flashed" to the session
408
     * on one request, and displayed on another. If there are many messages,
409
     * the container may need a max-height and scroll-bar.
410
     *
411
     * @param \stdClass[] $messages
412
     *
413
     * @return string
414
     */
415
    protected function flashMessagesContainer(array $messages)
416
    {
417
        $html = '';
418
        foreach ($messages as $message) {
419
            $html .= $this->flashMessageContainer($message);
420
        }
421
422
        if ($html) {
423
            return '<div class="flash-messages">' . $html . '</div>';
424
        } else {
425
            return '';
426
        }
427
    }
428
429
    /**
430
     * Close the main content and create the <footer> tag.
431
     *
432
     * @return string
433
     */
434
    public function footerContainer()
435
    {
436
        return '</main><footer>' . $this->footerContent() . '</footer>';
437
    }
438
439
    /**
440
     * Close the main content.
441
     * Note that popup windows are deprecated
442
     *
443
     * @return string
444
     */
445
    public function footerContainerPopupWindow()
446
    {
447
        return '</main>';
448
    }
449
450
    /**
451
     * Create the contents of the <footer> tag.
452
     *
453
     * @return string
454
     */
455
    protected function footerContent()
456
    {
457
        return
458
            $this->formatContactLinks() .
459
            $this->logoPoweredBy() .
460
            $this->formatPageViews($this->page_views) .
461
            $this->cookieWarning();
462
    }
463
464
    /**
465
     * Format the contents of a variable-height home-page block.
466
     *
467
     * @param string $id
468
     * @param string $title
469
     * @param string $class
470
     * @param string $content
471
     *
472
     * @return string
473
     */
474
    public function formatBlock($id, $title, $class, $content)
475
    {
476
        return
477
            '<div id="' . $id . '" class="block" >' .
478
            '<div class="blockheader">' . $title . '</div>' .
479
            '<div class="blockcontent ' . $class . '">' . $content . '</div>' .
480
            '</div>';
481
    }
482
483
    /**
484
     * Add markup to the contact links.
485
     *
486
     * @return string
487
     */
488
    protected function formatContactLinks()
489
    {
490
        if ($this->tree) {
491
            return '<div class="contact-links">' . $this->contactLinks() . '</div>';
492
        } else {
493
            return '';
494
        }
495
    }
496
497
    /**
498
     * Add markup to the hit counter.
499
     *
500
     * @param int $count
501
     *
502
     * @return string
503
     */
504
    protected function formatPageViews($count)
505
    {
506
        if ($count > 0) {
507
            return
508
                '<div class="page-views">' .
509
                I18N::plural('This page has been viewed %s time.', 'This page has been viewed %s times.', $count,
510
                    '<span class="odometer">' . I18N::digits($count) . '</span>') .
511
                '</div>';
512
        } else {
513
            return '';
514
        }
515
    }
516
517
    /**
518
     * Create a pending changes link for the page footer.
519
     *
520
     * @return string
521
     */
522
    protected function formatPendingChangesLink()
523
    {
524
        if ($this->pendingChangesExist()) {
525
            return '<div class="pending-changes-link">' . $this->pendingChangesLink() . '</div>';
526
        } else {
527
            return '';
528
        }
529
    }
530
531
    /**
532
     * Create a quick search form for the header.
533
     *
534
     * @return string
535
     */
536
    protected function formQuickSearch()
537
    {
538
        if ($this->tree) {
539
            return
540
                '<form action="search.php" class="header-search" role="search">' .
541
                '<input type="hidden" name="action" value="header">' .
542
                '<input type="hidden" name="ged" value="' . $this->tree->getNameHtml() . '">' .
543
                $this->formQuickSearchFields() .
544
                '</form>';
545
        } else {
546
            return '';
547
        }
548
    }
549
550
    /**
551
     * Create a search field and submit button for the quick search form in the header.
552
     *
553
     * @return string
554
     */
555
    protected function formQuickSearchFields()
556
    {
557
        return
558
            '<input type="search" name="query" size="15" placeholder="' . I18N::translate('Search') . '">' .
559
            '<input type="image" src="' . $this->assetUrl() . 'images/go.png" alt="' . I18N::translate('Search') . '">';
560
    }
561
562
    /**
563
     * Add markup to the tree title.
564
     *
565
     * @return string
566
     */
567
    protected function formatTreeTitle()
568
    {
569
        if ($this->tree) {
570
            return '<h1 class="header-title">' . $this->tree->getTitleHtml() . '</h1>';
571
        } else {
572
            return '';
573
        }
574
    }
575
576
    /**
577
     * Add markup to the secondary menu.
578
     *
579
     * @return string
580
     */
581
    protected function formatSecondaryMenu()
582
    {
583
        return
584
            '<ul class="secondary-menu">' .
585
            implode('', $this->secondaryMenu()) .
586
            '</ul>';
587
    }
588
589
    /**
590
     * Add markup to an item in the secondary menu.
591
     *
592
     * @param Menu $menu
593
     *
594
     * @return string
595
     */
596
    protected function formatSecondaryMenuItem(Menu $menu)
597
    {
598
        return $menu->getMenuAsList();
599
    }
600
601
    /**
602
     * Create the <head> tag.
603
     *
604
     * @param PageController $controller The current controller
605
     *
606
     * @return string
607
     */
608
    public function head(PageController $controller)
609
    {
610
        // Record this now. By the time we render the footer, $controller no longer exists.
611
        $this->page_views = $this->pageViews($controller);
612
613
        return
614
            '<head>' .
615
            $this->headContents($controller) .
616
            $this->hookHeaderExtraContent() .
617
            $this->analytics() .
618
            '</head>';
619
    }
620
621
    /**
622
     * Create the contents of the <head> tag.
623
     *
624
     * @param PageController $controller The current controller
625
     *
626
     * @return string
627
     */
628
    protected function headContents(PageController $controller)
629
    {
630
        // The title often includes the names of records, which may include HTML markup.
631
        $title = Filter::unescapeHtml($controller->getPageTitle());
632
633
        // If an extra (site) title is specified, append it.
634
        if ($this->tree && $this->tree->getPreference('META_TITLE')) {
635
            $title .= ' – ' . $this->tree->getPreference('META_TITLE');
636
        }
637
638
        $html =
639
            // modernizr.js and respond.js need to be loaded before the <body> to avoid FOUC
640
            '<!--[if IE 8]><script src="' . WT_MODERNIZR_JS_URL . '"></script><![endif]-->' .
641
            '<!--[if IE 8]><script src="' . WT_RESPOND_JS_URL . '"></script><![endif]-->' .
642
            $this->metaCharset() .
643
            $this->title($title) .
644
            $this->favicon() .
645
            $this->metaViewport() .
646
            $this->metaRobots($controller->getMetaRobots()) .
647
            $this->metaUaCompatible() .
648
            $this->metaGenerator(WT_WEBTREES . ' ' . WT_VERSION . ' - ' . WT_WEBTREES_URL);
649
650
        if ($this->tree) {
651
            $html .= $this->metaDescription($this->tree->getPreference('META_DESCRIPTION'));
652
        }
653
654
        // CSS files
655
        foreach ($this->stylesheets() as $css) {
656
            $html .= '<link rel="stylesheet" type="text/css" href="' . $css . '">';
657
        }
658
659
        return $html;
660
    }
661
662
    /**
663
     * Create the contents of the <header> tag.
664
     *
665
     * @return string
666
     */
667
    protected function headerContent()
668
    {
669
        return
670
            //$this->accessibilityLinks() .
671
            $this->logoHeader() .
672
            $this->secondaryMenuContainer($this->secondaryMenu()) .
673
            $this->formatTreeTitle() .
674
            $this->formQuickSearch();
675
    }
676
677
    /**
678
     * Create the <header> tag for a popup window.
679
     *
680
     * @return string
681
     */
682
    protected function headerSimple()
683
    {
684
        return
685
            $this->flashMessagesContainer(FlashMessages::getMessages()) .
686
            '<div id="content">';
687
    }
688
689
    /**
690
     * Allow themes to do things after initialization (since they cannot use
691
     * the constructor).
692
     */
693
    public function hookAfterInit()
694
    {
695
    }
696
697
    /**
698
     * Allow themes to add extra scripts to the page footer.
699
     *
700
     * @return string
701
     */
702
    public function hookFooterExtraJavascript()
703
    {
704
        return '';
705
    }
706
707
    /**
708
     * Allow themes to add extra content to the page header.
709
     * Typically this will be additional CSS.
710
     *
711
     * @return string
712
     */
713
    public function hookHeaderExtraContent()
714
    {
715
        return '';
716
    }
717
718
    /**
719
     * Create the <html> tag.
720
     *
721
     * @return string
722
     */
723
    public function html()
724
    {
725
        return '<html ' . I18N::htmlAttributes() . '>';
726
    }
727
728
    /**
729
     * Add HTML markup to create an alert
730
     *
731
     * @param string $html        The content of the alert
732
     * @param string $level       One of 'success', 'info', 'warning', 'danger'
733
     * @param bool   $dismissible If true, add a close button.
734
     *
735
     * @return string
736
     */
737
    public function htmlAlert($html, $level, $dismissible)
738
    {
739
        if ($dismissible) {
740
            return
741
                '<div class="alert alert-' . $level . ' alert-dismissible" role="alert">' .
742
                '<button type="button" class="close" data-dismiss="alert" aria-label="' . I18N::translate('close') . '">' .
743
                '<span aria-hidden="true">&times;</span>' .
744
                '</button>' .
745
                $html .
746
                '</div>';
747
        } else {
748
            return
749
                '<div class="alert alert-' . $level . '" role="alert">' .
750
                $html .
751
                '</div>';
752
        }
753
    }
754
755
    /**
756
     * Display an icon for this fact.
757
     *
758
     * @param Fact $fact
759
     *
760
     * @return string
761
     */
762
    public function icon(Fact $fact)
763
    {
764
        $icon = 'images/facts/' . $fact->getTag() . '.png';
765
        $dir  = substr($this->assetUrl(), strlen(WT_STATIC_URL));
766
        if (file_exists($dir . $icon)) {
767
            return '<img src="' . $this->assetUrl() . $icon . '" title="' . GedcomTag::getLabel($fact->getTag()) . '">';
768
        } elseif (file_exists($dir . 'images/facts/NULL.png')) {
769
            // Spacer image - for alignment - until we move to a sprite.
770
            return '<img src="' . Theme::theme()->assetUrl() . 'images/facts/NULL.png">';
771
        } else {
772
            return '';
773
        }
774
    }
775
776
    /**
777
     * Display an individual in a box - for charts, etc.
778
     *
779
     * @param Individual $individual
780
     *
781
     * @return string
782
     */
783
    public function individualBox(Individual $individual)
784
    {
785
        $personBoxClass = array_search($individual->getSex(), array('person_box' => 'M', 'person_boxF' => 'F', 'person_boxNN' => 'U'));
786
        if ($individual->canShow() && $individual->getTree()->getPreference('SHOW_HIGHLIGHT_IMAGES')) {
787
            $thumbnail = $individual->displayImage();
788
        } else {
789
            $thumbnail = '';
790
        }
791
792
        $content = '<span class="namedef name1">' . $individual->getFullName() . '</span>';
793
        $icons   = '';
794
        if ($individual->canShow()) {
795
            $content =
796
                '<a href="' . $individual->getHtmlUrl() . '">' . $content . '</a>' .
797
                '<div class="namedef name1">' . $individual->getAddName() . '</div>';
798
            $icons   =
799
                '<div class="noprint icons">' .
800
                '<span class="iconz icon-zoomin" title="' . I18N::translate('Zoom in/out on this box.') . '"></span>' .
801
                '<div class="itr"><i class="icon-pedigree"></i><div class="popup">' .
802
                '<ul class="' . $personBoxClass . '">' . implode('', $this->individualBoxMenu($individual)) . '</ul>' .
803
                '</div>' .
804
                '</div>' .
805
                '</div>';
806
        }
807
808
        return
809
            '<div data-pid="' . $individual->getXref() . '" class="person_box_template ' . $personBoxClass . ' box-style1" style="width: ' . $this->parameter('chart-box-x') . 'px; min-height: ' . $this->parameter('chart-box-y') . 'px">' .
810
            $icons .
811
            '<div class="chart_textbox" style="max-height:' . $this->parameter('chart-box-y') . 'px;">' .
812
            $thumbnail .
813
            $content .
814
            '<div class="inout2 details1">' . $this->individualBoxFacts($individual) . '</div>' .
815
            '</div>' .
816
            '<div class="inout"></div>' .
817
            '</div>';
818
    }
819
820
    /**
821
     * Display an empty box - for a missing individual in a chart.
822
     *
823
     * @return string
824
     */
825
    public function individualBoxEmpty()
826
    {
827
        return '<div class="person_box_template person_boxNN box-style1" style="width: ' . $this->parameter('chart-box-x') . 'px; min-height: ' . $this->parameter('chart-box-y') . 'px"></div>';
828
    }
829
830
    /**
831
     * Display an individual in a box - for charts, etc.
832
     *
833
     * @param Individual $individual
834
     *
835
     * @return string
836
     */
837
    public function individualBoxLarge(Individual $individual)
838
    {
839
        $personBoxClass = array_search($individual->getSex(), array('person_box' => 'M', 'person_boxF' => 'F', 'person_boxNN' => 'U'));
840
        if ($individual->getTree()->getPreference('SHOW_HIGHLIGHT_IMAGES')) {
841
            $thumbnail = $individual->displayImage();
842
        } else {
843
            $thumbnail = '';
844
        }
845
846
        $content = '<span class="namedef name1">' . $individual->getFullName() . '</span>';
847
        $icons   = '';
848
        if ($individual->canShow()) {
849
            $content =
850
                '<a href="' . $individual->getHtmlUrl() . '">' . $content . '</a>' .
851
                '<div class="namedef name2">' . $individual->getAddName() . '</div>';
852
            $icons   =
853
                '<div class="noprint icons">' .
854
                '<span class="iconz icon-zoomin" title="' . I18N::translate('Zoom in/out on this box.') . '"></span>' .
855
                '<div class="itr"><i class="icon-pedigree"></i><div class="popup">' .
856
                '<ul class="' . $personBoxClass . '">' . implode('', $this->individualBoxMenu($individual)) . '</ul>' .
857
                '</div>' .
858
                '</div>' .
859
                '</div>';
860
        }
861
862
        return
863
            '<div data-pid="' . $individual->getXref() . '" class="person_box_template ' . $personBoxClass . ' box-style2">' .
864
            $icons .
865
            '<div class="chart_textbox" style="max-height:' . $this->parameter('chart-box-y') . 'px;">' .
866
            $thumbnail .
867
            $content .
868
            '<div class="inout2 details2">' . $this->individualBoxFacts($individual) . '</div>' .
869
            '</div>' .
870
            '<div class="inout"></div>' .
871
            '</div>';
872
    }
873
874
    /**
875
     * Display an individual in a box - for charts, etc.
876
     *
877
     * @param Individual $individual
878
     *
879
     * @return string
880
     */
881
    public function individualBoxSmall(Individual $individual)
882
    {
883
        $personBoxClass = array_search($individual->getSex(), array('person_box' => 'M', 'person_boxF' => 'F', 'person_boxNN' => 'U'));
884
        if ($individual->getTree()->getPreference('SHOW_HIGHLIGHT_IMAGES')) {
885
            $thumbnail = $individual->displayImage();
886
        } else {
887
            $thumbnail = '';
888
        }
889
890
        return
891
            '<div data-pid="' . $individual->getXref() . '" class="person_box_template ' . $personBoxClass . ' iconz box-style0" style="width: ' . $this->parameter('compact-chart-box-x') . 'px; min-height: ' . $this->parameter('compact-chart-box-y') . 'px">' .
892
            '<div class="compact_view">' .
893
            $thumbnail .
894
            '<a href="' . $individual->getHtmlUrl() . '">' .
895
            '<span class="namedef name0">' . $individual->getFullName() . '</span>' .
896
            '</a>' .
897
            '<div class="inout2 details0">' . $individual->getLifeSpan() . '</div>' .
898
            '</div>' .
899
            '<div class="inout"></div>' .
900
            '</div>';
901
    }
902
903
    /**
904
     * Display an individual in a box - for charts, etc.
905
     *
906
     * @return string
907
     */
908
    public function individualBoxSmallEmpty()
909
    {
910
        return '<div class="person_box_template person_boxNN box-style1" style="width: ' . $this->parameter('compact-chart-box-x') . 'px; min-height: ' . $this->parameter('compact-chart-box-y') . 'px"></div>';
911
    }
912
913
    /**
914
     * Generate the facts, for display in charts.
915
     *
916
     * @param Individual $individual
917
     *
918
     * @return string
919
     */
920
    protected function individualBoxFacts(Individual $individual)
921
    {
922
        $html = '';
923
924
        $opt_tags = preg_split('/\W/', $individual->getTree()->getPreference('CHART_BOX_TAGS'), 0, PREG_SPLIT_NO_EMPTY);
925
        // Show BIRT or equivalent event
926
        foreach (explode('|', WT_EVENTS_BIRT) as $birttag) {
927
            if (!in_array($birttag, $opt_tags)) {
928
                $event = $individual->getFirstFact($birttag);
929
                if ($event) {
930
                    $html .= $event->summary();
931
                    break;
932
                }
933
            }
934
        }
935
        // Show optional events (before death)
936
        foreach ($opt_tags as $key => $tag) {
937
            if (!preg_match('/^(' . WT_EVENTS_DEAT . ')$/', $tag)) {
938
                $event = $individual->getFirstFact($tag);
939
                if ($event instanceof Fact) {
940
                    $html .= $event->summary();
941
                    unset($opt_tags[$key]);
942
                }
943
            }
944
        }
945
        // Show DEAT or equivalent event
946
        foreach (explode('|', WT_EVENTS_DEAT) as $deattag) {
947
            $event = $individual->getFirstFact($deattag);
948
            if ($event) {
949
                $html .= $event->summary();
950
                if (in_array($deattag, $opt_tags)) {
951
                    unset($opt_tags[array_search($deattag, $opt_tags)]);
952
                }
953
                break;
954
            }
955
        }
956
        // Show remaining optional events (after death)
957
        foreach ($opt_tags as $tag) {
958
            $event = $individual->getFirstFact($tag);
959
            if ($event) {
960
                $html .= $event->summary();
961
            }
962
        }
963
964
        return $html;
965
    }
966
967
    /**
968
     * Generate the LDS summary, for display in charts.
969
     *
970
     * @param Individual $individual
971
     *
972
     * @return string
973
     */
974
    protected function individualBoxLdsSummary(Individual $individual)
975
    {
976
        if ($individual->getTree()->getPreference('SHOW_LDS_AT_GLANCE')) {
977
            $BAPL = $individual->getFacts('BAPL') ? 'B' : '_';
978
            $ENDL = $individual->getFacts('ENDL') ? 'E' : '_';
979
            $SLGC = $individual->getFacts('SLGC') ? 'C' : '_';
980
            $SLGS = '_';
981
982
            foreach ($individual->getSpouseFamilies() as $family) {
983
                if ($family->getFacts('SLGS')) {
984
                    $SLGS = '';
985
                }
986
            }
987
988
            return $BAPL . $ENDL . $SLGS . $SLGC;
989
        } else {
990
            return '';
991
        }
992
    }
993
994
    /**
995
     * Links, to show in chart boxes;
996
     *
997
     * @param Individual $individual
998
     *
999
     * @return Menu[]
1000
     */
1001
    public function individualBoxMenu(Individual $individual)
1002
    {
1003
        $menus = array_merge(
1004
            $this->individualBoxMenuCharts($individual),
1005
            $this->individualBoxMenuFamilyLinks($individual)
1006
        );
1007
1008
        return $menus;
1009
    }
1010
1011
    /**
1012
     * Chart links, to show in chart boxes;
1013
     *
1014
     * @param Individual $individual
1015
     *
1016
     * @return Menu[]
1017
     */
1018
    protected function individualBoxMenuCharts(Individual $individual)
1019
    {
1020
        $menus = array();
1021
        foreach (Module::getActiveCharts($this->tree) as $chart) {
1022
            $menu = $chart->getBoxChartMenu($individual);
1023
            if ($menu) {
1024
                $menus[] = $menu;
1025
            }
1026
        }
1027
1028
        usort($menus, function (Menu $x, Menu $y) {
1029
            return I18N::strcasecmp($x->getLabel(), $y->getLabel());
1030
        });
1031
1032
        return $menus;
1033
    }
1034
1035
    /**
1036
     * Family links, to show in chart boxes.
1037
     *
1038
     * @param Individual $individual
1039
     *
1040
     * @return Menu[]
1041
     */
1042
    protected function individualBoxMenuFamilyLinks(Individual $individual)
1043
    {
1044
        $menus = array();
1045
1046
        foreach ($individual->getSpouseFamilies() as $family) {
1047
            $menus[] = new Menu('<strong>' . I18N::translate('Family with spouse') . '</strong>', $family->getHtmlUrl());
1048
            $spouse  = $family->getSpouse($individual);
1049
            if ($spouse && $spouse->canShowName()) {
1050
                $menus[] = new Menu($spouse->getFullName(), $spouse->getHtmlUrl());
1051
            }
1052
            foreach ($family->getChildren() as $child) {
1053
                if ($child->canShowName()) {
1054
                    $menus[] = new Menu($child->getFullName(), $child->getHtmlUrl());
1055
                }
1056
            }
1057
        }
1058
1059
        return $menus;
1060
    }
1061
1062
    /**
1063
     * Create part of an individual box
1064
     *
1065
     * @param Individual $individual
1066
     *
1067
     * @return string
1068
     */
1069
    protected function individualBoxSexSymbol(Individual $individual)
1070
    {
1071
        if ($individual->getTree()->getPreference('PEDIGREE_SHOW_GENDER')) {
1072
            return $individual->sexImage('large');
1073
        } else {
1074
            return '';
1075
        }
1076
    }
1077
1078
    /**
1079
     * Initialise the theme. We cannot pass these in a constructor, as the construction
1080
     * happens in a theme file, and we need to be able to change it.
1081
     *
1082
     * @param Tree|null $tree The current tree (if there is one).
1083
     */
1084
    final public function init(Tree $tree = null)
1085
    {
1086
        $this->tree     = $tree;
1087
        $this->tree_url = $tree ? 'ged=' . $tree->getNameUrl() : '';
1088
1089
        $this->hookAfterInit();
1090
    }
1091
1092
    /**
1093
     * A large webtrees logo, for the header.
1094
     *
1095
     * @return string
1096
     */
1097
    protected function logoHeader()
1098
    {
1099
        return '<div class="header-logo"></div>';
1100
    }
1101
1102
    /**
1103
     * A small "powered by webtrees" logo for the footer.
1104
     *
1105
     * @return string
1106
     */
1107
    protected function logoPoweredBy()
1108
    {
1109
        return '<a href="' . WT_WEBTREES_URL . '" class="powered-by-webtrees" title="' . WT_WEBTREES_URL . '"></a>';
1110
    }
1111
1112
    /**
1113
     * A menu for the day/month/year calendar views.
1114
     *
1115
     * @return Menu
1116
     */
1117
    protected function menuCalendar()
1118
    {
1119
        return new Menu(I18N::translate('Calendar'), '#', 'menu-calendar', array('rel' => 'nofollow'), array(
1120
            // Day view
1121
            new Menu(I18N::translate('Day'), 'calendar.php?' . $this->tree_url . '&amp;view=day', 'menu-calendar-day', array('rel' => 'nofollow')),
1122
            // Month view
1123
            new Menu(I18N::translate('Month'), 'calendar.php?' . $this->tree_url . '&amp;view=month', 'menu-calendar-month', array('rel' => 'nofollow')),
1124
            //Year view
1125
            new Menu(I18N::translate('Year'), 'calendar.php?' . $this->tree_url . '&amp;view=year', 'menu-calendar-year', array('rel' => 'nofollow')),
1126
        ));
1127
    }
1128
1129
    /**
1130
     * Generate a menu item to change the blocks on the current (index.php) page.
1131
     *
1132
     * @return Menu|null
1133
     */
1134
    protected function menuChangeBlocks()
1135
    {
1136
        if (WT_SCRIPT_NAME === 'index.php' && Auth::check() && Filter::get('ctype', 'gedcom|user', 'user') === 'user') {
0 ignored issues
show
introduced by
The condition Fisharebest\Webtrees\The...PT_NAME === 'index.php' is always false.
Loading history...
1137
            return new Menu(I18N::translate('Customize this page'), 'index_edit.php?user_id=' . Auth::id(), 'menu-change-blocks');
1138
        } elseif (WT_SCRIPT_NAME === 'index.php' && Auth::isManager($this->tree)) {
0 ignored issues
show
introduced by
The condition Fisharebest\Webtrees\The...PT_NAME === 'index.php' is always false.
Loading history...
1139
            return new Menu(I18N::translate('Customize this page'), 'index_edit.php?gedcom_id=' . $this->tree->getTreeId(), 'menu-change-blocks');
1140
        } else {
1141
            return null;
1142
        }
1143
    }
1144
1145
    /**
1146
     * Generate a menu for each of the different charts.
1147
     *
1148
     * @param Individual $individual
1149
     *
1150
     * @return Menu|null
1151
     */
1152
    protected function menuChart(Individual $individual)
1153
    {
1154
        $submenus = array();
1155
        foreach (Module::getActiveCharts($this->tree) as $chart) {
1156
            $menu = $chart->getChartMenu($individual);
1157
            if ($menu) {
1158
                $submenus[] = $menu;
1159
            }
1160
        }
1161
1162
        if ($submenus) {
1163
            usort($submenus, function (Menu $x, Menu $y) {
1164
                return I18N::strcasecmp($x->getLabel(), $y->getLabel());
1165
            });
1166
1167
            return new Menu(I18N::translate('Charts'), '#', 'menu-chart', array('rel' => 'nofollow'), $submenus);
1168
        } else {
1169
            return null;
1170
        }
1171
    }
1172
1173
    /**
1174
     * Generate a menu item for the ancestors chart.
1175
     *
1176
     * @param Individual $individual
1177
     *
1178
     * @return Menu|null
1179
     *
1180
     * @deprecated
1181
     */
1182
    protected function menuChartAncestors(Individual $individual)
1183
    {
1184
        $chart = new AncestorsChartModule(WT_ROOT . WT_MODULES_DIR . 'ancestors_chart');
1185
1186
        return $chart->getChartMenu($individual);
1187
    }
1188
1189
    /**
1190
     * Generate a menu item for the compact tree.
1191
     *
1192
     * @param Individual $individual
1193
     *
1194
     * @return Menu|null
1195
     *
1196
     * @deprecated
1197
     */
1198
    protected function menuChartCompact(Individual $individual)
1199
    {
1200
        $chart = new CompactTreeChartModule(WT_ROOT . WT_MODULES_DIR . 'compact_tree_chart');
1201
1202
        return $chart->getChartMenu($individual);
1203
    }
1204
1205
    /**
1206
     * Generate a menu item for the descendants chart.
1207
     *
1208
     * @param Individual $individual
1209
     *
1210
     * @return Menu|null
1211
     *
1212
     * @deprecated
1213
     */
1214
    protected function menuChartDescendants(Individual $individual)
1215
    {
1216
        $chart = new DescendancyChartModule(WT_ROOT . WT_MODULES_DIR . 'descendancy_chart');
1217
1218
        return $chart->getChartMenu($individual);
1219
    }
1220
1221
    /**
1222
     * Generate a menu item for the family-book chart.
1223
     *
1224
     * @param Individual $individual
1225
     *
1226
     * @return Menu|null
1227
     *
1228
     * @deprecated
1229
     */
1230
    protected function menuChartFamilyBook(Individual $individual)
1231
    {
1232
        $chart = new FamilyBookChartModule(WT_ROOT . WT_MODULES_DIR . 'family_book_chart');
1233
1234
        return $chart->getChartMenu($individual);
1235
    }
1236
1237
    /**
1238
     * Generate a menu item for the fan chart.
1239
     *
1240
     * We can only do this if the GD2 library is installed with TrueType support.
1241
     *
1242
     * @param Individual $individual
1243
     *
1244
     * @return Menu|null
1245
     *
1246
     * @deprecated
1247
     */
1248
    protected function menuChartFanChart(Individual $individual)
1249
    {
1250
        $chart = new FanChartModule(WT_ROOT . WT_MODULES_DIR . 'fan_chart');
1251
1252
        return $chart->getChartMenu($individual);
1253
    }
1254
1255
    /**
1256
     * Generate a menu item for the interactive tree.
1257
     *
1258
     * @param Individual $individual
1259
     *
1260
     * @return Menu|null
1261
     *
1262
     * @deprecated
1263
     */
1264
    protected function menuChartInteractiveTree(Individual $individual)
1265
    {
1266
        $chart = new InteractiveTreeModule(WT_ROOT . WT_MODULES_DIR . 'tree');
1267
1268
        return $chart->getChartMenu($individual);
1269
    }
1270
1271
    /**
1272
     * Generate a menu item for the hourglass chart.
1273
     *
1274
     * @param Individual $individual
1275
     *
1276
     * @return Menu|null
1277
     *
1278
     * @deprecated
1279
     */
1280
    protected function menuChartHourglass(Individual $individual)
1281
    {
1282
        $chart = new HourglassChartModule(WT_ROOT . WT_MODULES_DIR . 'hourglass_chart');
1283
1284
        return $chart->getChartMenu($individual);
1285
    }
1286
1287
    /**
1288
     * Generate a menu item for the lifepsan chart.
1289
     *
1290
     * @param Individual $individual
1291
     *
1292
     * @return Menu|null
1293
     *
1294
     * @deprecated
1295
     */
1296
    protected function menuChartLifespan(Individual $individual)
1297
    {
1298
        $chart = new LifespansChartModule(WT_ROOT . WT_MODULES_DIR . 'lifespans_chart');
1299
1300
        return $chart->getChartMenu($individual);
1301
    }
1302
1303
    /**
1304
     * Generate a menu item for the pedigree chart.
1305
     *
1306
     * @param Individual $individual
1307
     *
1308
     * @return Menu|null
1309
     *
1310
     * @deprecated
1311
     */
1312
    protected function menuChartPedigree(Individual $individual)
1313
    {
1314
        $chart = new PedigreeChartModule(WT_ROOT . WT_MODULES_DIR . 'pedigree_chart');
1315
1316
        return $chart->getChartMenu($individual);
1317
    }
1318
1319
    /**
1320
     * Generate a menu item for the pedigree map.
1321
     *
1322
     * @param Individual $individual
1323
     *
1324
     * @return Menu|null
1325
     *
1326
     * @deprecated
1327
     */
1328
    protected function menuChartPedigreeMap(Individual $individual)
1329
    {
1330
        $chart = new GoogleMapsModule(WT_ROOT . WT_MODULES_DIR . 'googlemap');
1331
1332
        return $chart->getChartMenu($individual);
1333
    }
1334
1335
    /**
1336
     * Generate a menu item for the relationship chart.
1337
     *
1338
     * @param Individual $individual
1339
     *
1340
     * @return Menu|null
1341
     *
1342
     * @deprecated
1343
     */
1344
    protected function menuChartRelationship(Individual $individual)
1345
    {
1346
        $chart = new RelationshipsChartModule(WT_ROOT . WT_MODULES_DIR . 'relationships_chart');
1347
1348
        return $chart->getChartMenu($individual);
1349
    }
1350
1351
    /**
1352
     * Generate a menu item for the statistics charts.
1353
     *
1354
     * @return Menu|null
1355
     *
1356
     * @deprecated
1357
     */
1358
    protected function menuChartStatistics()
1359
    {
1360
        $chart = new StatisticsChartModule(WT_ROOT . WT_MODULES_DIR . 'statistics_chart');
1361
1362
        return $chart->getChartMenu(null);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type Fisharebest\Webtrees\Individual expected by parameter $individual of Fisharebest\Webtrees\Mod...tModule::getChartMenu(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1362
        return $chart->getChartMenu(/** @scrutinizer ignore-type */ null);
Loading history...
1363
    }
1364
1365
    /**
1366
     * Generate a menu item for the timeline chart.
1367
     *
1368
     * @param Individual $individual
1369
     *
1370
     * @return Menu|null
1371
     *
1372
     * @deprecated
1373
     */
1374
    protected function menuChartTimeline(Individual $individual)
1375
    {
1376
        $chart = new TimelineChartModule(WT_ROOT . WT_MODULES_DIR . 'timeline_chart');
1377
1378
        return $chart->getChartMenu($individual);
1379
    }
1380
1381
    /**
1382
     * Generate a menu item for the control panel.
1383
     *
1384
     * @return Menu|null
1385
     */
1386
    protected function menuControlPanel()
1387
    {
1388
        if (Auth::isManager($this->tree)) {
1389
            return new Menu(I18N::translate('Control panel'), 'admin.php', 'menu-admin');
1390
        } else {
1391
            return null;
1392
        }
1393
    }
1394
1395
    /**
1396
     * Favorites menu.
1397
     *
1398
     * @return Menu|null
1399
     */
1400
    protected function menuFavorites()
1401
    {
1402
        global $controller;
1403
1404
        $show_user_favorites = $this->tree && Module::getModuleByName('user_favorites') && Auth::check();
1405
        $show_tree_favorites = $this->tree && Module::getModuleByName('gedcom_favorites');
1406
1407
        if ($show_user_favorites && $show_tree_favorites) {
1408
            $favorites = array_merge(
1409
                FamilyTreeFavoritesModule::getFavorites($this->tree->getTreeId()),
1410
                UserFavoritesModule::getFavorites(Auth::id())
0 ignored issues
show
Bug introduced by
It seems like Fisharebest\Webtrees\Auth::id() can also be of type string; however, parameter $user_id of Fisharebest\Webtrees\Mod...sModule::getFavorites() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1410
                UserFavoritesModule::getFavorites(/** @scrutinizer ignore-type */ Auth::id())
Loading history...
1411
            );
1412
        } elseif ($show_user_favorites) {
1413
            $favorites = UserFavoritesModule::getFavorites(Auth::id());
1414
        } elseif ($show_tree_favorites) {
1415
            $favorites = FamilyTreeFavoritesModule::getFavorites($this->tree->getTreeId());
1416
        } else {
1417
            $favorites = array();
1418
        }
1419
1420
        $submenus = array();
1421
        $records  = array();
1422
        foreach ($favorites as $favorite) {
1423
            switch ($favorite['type']) {
1424
                case 'URL':
1425
                    $submenus[] = new Menu($favorite['title'], $favorite['url']);
1426
                    break;
1427
                case 'INDI':
1428
                case 'FAM':
1429
                case 'SOUR':
1430
                case 'OBJE':
1431
                case 'NOTE':
1432
                    $record = GedcomRecord::getInstance($favorite['gid'], $this->tree);
1433
                    if ($record && $record->canShowName()) {
1434
                        $submenus[] = new Menu($record->getFullName(), $record->getHtmlUrl());
1435
                        $records[]  = $record;
1436
                    }
1437
                    break;
1438
            }
1439
        }
1440
1441
        if ($show_user_favorites && isset($controller->record) && $controller->record instanceof GedcomRecord && !in_array($controller->record, $records, true)) {
1442
            $submenus[] = new Menu(I18N::translate('Add to favorites'), '#', '', array(
1443
                'onclick' => 'jQuery.post("module.php?mod=user_favorites&mod_action=menu-add-favorite", {xref:"' . $controller->record->getXref() . '"},function(){location.reload();})',
1444
            ));
1445
        }
1446
1447
        if (empty($submenus)) {
1448
            return null;
1449
        } else {
1450
            return new Menu(I18N::translate('Favorites'), '#', 'menu-favorites', array(), $submenus);
1451
        }
1452
    }
1453
1454
    /**
1455
     * A menu for the home (family tree) pages.
1456
     *
1457
     * @return Menu
1458
     */
1459
    protected function menuHomePage()
1460
    {
1461
        if (count(Tree::getAll()) === 1 || Site::getPreference('ALLOW_CHANGE_GEDCOM') === '0') {
1462
            return new Menu(I18N::translate('Family tree'), 'index.php?ctype=gedcom&amp;' . $this->tree_url, 'menu-tree');
1463
        } else {
1464
            $submenus = array();
1465
            foreach (Tree::getAll() as $tree) {
1466
                if ($tree == $this->tree) {
1467
                    $active = 'active ';
1468
                } else {
1469
                    $active = '';
1470
                }
1471
                $submenus[] = new Menu($tree->getTitleHtml(), 'index.php?ctype=gedcom&amp;ged=' . $tree->getNameUrl(), $active . 'menu-tree-' . $tree->getTreeId());
1472
            }
1473
1474
            return new Menu(I18N::translate('Family trees'), '#', 'menu-tree', array(), $submenus);
1475
        }
1476
    }
1477
1478
    /**
1479
     * A menu to show a list of available languages.
1480
     *
1481
     * @return Menu|null
1482
     */
1483
    protected function menuLanguages()
1484
    {
1485
        $menu = new Menu(I18N::translate('Language'), '#', 'menu-language');
1486
1487
        foreach (I18N::activeLocales() as $locale) {
1488
            $language_tag = $locale->languageTag();
1489
            $class        = 'menu-language-' . $language_tag . (WT_LOCALE === $language_tag ? ' active' : '');
1490
            $menu->addSubmenu(new Menu($locale->endonym(), '#', $class, array(
1491
                'onclick'       => 'return false;',
1492
                'data-language' => $language_tag,
1493
            )));
1494
        }
1495
1496
        if (count($menu->getSubmenus()) > 1) {
1497
            return $menu;
1498
        } else {
1499
            return null;
1500
        }
1501
    }
1502
1503
    /**
1504
     * Create a menu to show lists of individuals, families, sources, etc.
1505
     *
1506
     * @param string $surname The significant surname on the page
1507
     *
1508
     * @return Menu
1509
     */
1510
    protected function menuLists($surname)
1511
    {
1512
        // Do not show empty lists
1513
        $row = Database::prepare(
1514
            "SELECT" .
1515
            " EXISTS(SELECT 1 FROM `##sources` WHERE s_file = ?) AS sour," .
1516
            " EXISTS(SELECT 1 FROM `##other` WHERE o_file = ? AND o_type='REPO') AS repo," .
1517
            " EXISTS(SELECT 1 FROM `##other` WHERE o_file = ? AND o_type='NOTE') AS note," .
1518
            " EXISTS(SELECT 1 FROM `##media` WHERE m_file = ?) AS obje"
1519
        )->execute(array(
1520
            $this->tree->getTreeId(),
1521
            $this->tree->getTreeId(),
1522
            $this->tree->getTreeId(),
1523
            $this->tree->getTreeId(),
1524
        ))->fetchOneRow();
1525
1526
        $submenus = array(
1527
            $this->menuListsIndividuals($surname),
1528
            $this->menuListsFamilies($surname),
1529
            $this->menuListsBranches($surname),
1530
            $this->menuListsPlaces(),
1531
        );
1532
        if ($row->obje) {
1533
            $submenus[] = $this->menuListsMedia();
1534
        }
1535
        if ($row->repo) {
1536
            $submenus[] = $this->menuListsRepositories();
1537
        }
1538
        if ($row->sour) {
1539
            $submenus[] = $this->menuListsSources();
1540
        }
1541
        if ($row->note) {
1542
            $submenus[] = $this->menuListsNotes();
1543
        }
1544
1545
        uasort($submenus, function (Menu $x, Menu $y) {
1546
            return I18N::strcasecmp($x->getLabel(), $y->getLabel());
1547
        });
1548
1549
        return new Menu(I18N::translate('Lists'), '#', 'menu-list', array(), $submenus);
1550
    }
1551
1552
    /**
1553
     * A menu for the list of branches
1554
     *
1555
     * @param string $surname The significant surname on the page
1556
     *
1557
     * @return Menu
1558
     */
1559
    protected function menuListsBranches($surname)
1560
    {
1561
        return new Menu(I18N::translate('Branches'), 'branches.php?ged=' . $this->tree->getNameUrl() . '&amp;surname=' . rawurlencode($surname), 'menu-branches', array('rel' => 'nofollow'));
1562
    }
1563
1564
    /**
1565
     * A menu for the list of families
1566
     *
1567
     * @param string $surname The significant surname on the page
1568
     *
1569
     * @return Menu
1570
     */
1571
    protected function menuListsFamilies($surname)
1572
    {
1573
        return new Menu(I18N::translate('Families'), 'famlist.php?ged=' . $this->tree->getNameUrl() . '&amp;surname=' . rawurlencode($surname), 'menu-list-fam', array('rel' => 'nofollow'));
1574
    }
1575
1576
    /**
1577
     * A menu for the list of individuals
1578
     *
1579
     * @param string $surname The significant surname on the page
1580
     *
1581
     * @return Menu
1582
     */
1583
    protected function menuListsIndividuals($surname)
1584
    {
1585
        return new Menu(I18N::translate('Individuals'), 'indilist.php?ged=' . $this->tree->getNameUrl() . '&amp;surname=' . rawurlencode($surname), 'menu-list-indi');
1586
    }
1587
1588
    /**
1589
     * A menu for the list of media objects
1590
     *
1591
     * @return Menu
1592
     */
1593
    protected function menuListsMedia()
1594
    {
1595
        return new Menu(I18N::translate('Media objects'), 'medialist.php?' . $this->tree_url, 'menu-list-obje', array('rel' => 'nofollow'));
1596
    }
1597
1598
    /**
1599
     * A menu for the list of notes
1600
     *
1601
     * @return Menu
1602
     */
1603
    protected function menuListsNotes()
1604
    {
1605
        return new Menu(I18N::translate('Shared notes'), 'notelist.php?' . $this->tree_url, 'menu-list-note', array('rel' => 'nofollow'));
1606
    }
1607
1608
    /**
1609
     * A menu for the list of individuals
1610
     *
1611
     * @return Menu
1612
     */
1613
    protected function menuListsPlaces()
1614
    {
1615
        return new Menu(I18N::translate('Place hierarchy'), 'placelist.php?ged=' . $this->tree->getNameUrl(), 'menu-list-plac', array('rel' => 'nofollow'));
1616
    }
1617
1618
    /**
1619
     * A menu for the list of repositories
1620
     *
1621
     * @return Menu
1622
     */
1623
    protected function menuListsRepositories()
1624
    {
1625
        return new Menu(I18N::translate('Repositories'), 'repolist.php?' . $this->tree_url, 'menu-list-repo', array('rel' => 'nofollow'));
1626
    }
1627
1628
    /**
1629
     * A menu for the list of sources
1630
     *
1631
     * @return Menu
1632
     */
1633
    protected function menuListsSources()
1634
    {
1635
        return new Menu(I18N::translate('Sources'), 'sourcelist.php?' . $this->tree_url, 'menu-list-sour', array('rel' => 'nofollow'));
1636
    }
1637
1638
    /**
1639
     * A login menu option (or null if we are already logged in).
1640
     *
1641
     * @return Menu|null
1642
     */
1643
    protected function menuLogin()
1644
    {
1645
        if (Auth::check() || WT_SCRIPT_NAME === 'login.php') {
1646
            return null;
1647
        } else {
1648
            return new Menu(I18N::translate('Sign in'), WT_LOGIN_URL . '?url=' . rawurlencode(Functions::getQueryUrl()), 'menu-login', array('rel' => 'nofollow'));
1649
        }
1650
    }
1651
1652
    /**
1653
     * A logout menu option (or null if we are already logged out).
1654
     *
1655
     * @return Menu|null
1656
     */
1657
    protected function menuLogout()
1658
    {
1659
        if (Auth::check()) {
1660
            return new Menu(I18N::translate('Sign out'), 'logout.php', 'menu-logout');
1661
        } else {
1662
            return null;
1663
        }
1664
    }
1665
1666
    /**
1667
     * Get the additional menus created by each of the modules
1668
     *
1669
     * @return Menu[]
1670
     */
1671
    protected function menuModules()
1672
    {
1673
        $menus = array();
1674
        foreach (Module::getActiveMenus($this->tree) as $module) {
1675
            $menus[] = $module->getMenu();
1676
        }
1677
1678
        return array_filter($menus);
1679
    }
1680
1681
    /**
1682
     * A link to allow users to edit their account settings (edituser.php).
1683
     *
1684
     * @return Menu|null
1685
     */
1686
    protected function menuMyAccount()
1687
    {
1688
        if (Auth::check()) {
1689
            return new Menu(I18N::translate('My account'), 'edituser.php');
1690
        } else {
1691
            return null;
1692
        }
1693
    }
1694
1695
    /**
1696
     * A link to the user's individual record (individual.php).
1697
     *
1698
     * @return Menu|null
1699
     */
1700
    protected function menuMyIndividualRecord()
1701
    {
1702
        $gedcomid = $this->tree->getUserPreference(Auth::user(), 'gedcomid');
1703
1704
        if ($gedcomid) {
1705
            return new Menu(I18N::translate('My individual record'), 'individual.php?pid=' . $gedcomid . '&amp;' . $this->tree_url, 'menu-myrecord');
1706
        } else {
1707
            return null;
1708
        }
1709
    }
1710
1711
    /**
1712
     * A link to the user's personal home page.
1713
     *
1714
     * @return Menu
1715
     */
1716
    protected function menuMyPage()
1717
    {
1718
        return new Menu(I18N::translate('My page'), 'index.php?ctype=user&amp;' . $this->tree_url, 'menu-mypage');
1719
    }
1720
1721
    /**
1722
     * A menu for the user's personal pages.
1723
     *
1724
     * @return Menu|null
1725
     */
1726
    protected function menuMyPages()
1727
    {
1728
        if (Auth::id()) {
1729
            return new Menu(I18N::translate('My pages'), '#', 'menu-mymenu', array(), array_filter(array(
1730
                $this->menuMyPage(),
1731
                $this->menuMyIndividualRecord(),
1732
                $this->menuMyPedigree(),
1733
                $this->menuMyAccount(),
1734
                $this->menuControlPanel(),
1735
                $this->menuChangeBlocks(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->menuChangeBlocks() targeting Fisharebest\Webtrees\The...eme::menuChangeBlocks() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1736
            )));
1737
        } else {
1738
            return null;
1739
        }
1740
    }
1741
1742
    /**
1743
     * A link to the user's individual record.
1744
     *
1745
     * @return Menu|null
1746
     */
1747
    protected function menuMyPedigree()
1748
    {
1749
        $gedcomid = $this->tree->getUserPreference(Auth::user(), 'gedcomid');
1750
1751
        if ($gedcomid && Module::isActiveChart($this->tree, 'pedigree_chart')) {
1752
            $showFull   = $this->tree->getPreference('PEDIGREE_FULL_DETAILS') ? 1 : 0;
1753
            $showLayout = $this->tree->getPreference('PEDIGREE_LAYOUT') ? 1 : 0;
1754
1755
            return new Menu(
1756
                I18N::translate('My pedigree'),
1757
                'pedigree.php?' . $this->tree_url . '&amp;rootid=' . $gedcomid . '&amp;show_full=' . $showFull . '&amp;talloffset=' . $showLayout,
1758
                'menu-mypedigree'
1759
            );
1760
        } else {
1761
            return null;
1762
        }
1763
    }
1764
1765
    /**
1766
     * Create a pending changes menu.
1767
     *
1768
     * @return Menu|null
1769
     */
1770
    protected function menuPendingChanges()
1771
    {
1772
        if ($this->pendingChangesExist()) {
1773
            $menu = new Menu(I18N::translate('Pending changes'), '#', 'menu-pending', array('onclick' => 'window.open("edit_changes.php", "_blank", chan_window_specs); return false;'));
1774
1775
            return $menu;
1776
        } else {
1777
            return null;
1778
        }
1779
    }
1780
1781
    /**
1782
     * A menu with a list of reports.
1783
     *
1784
     * @return Menu|null
1785
     */
1786
    protected function menuReports()
1787
    {
1788
        $submenus = array();
1789
        foreach (Module::getActiveReports($this->tree) as $report) {
1790
            $submenus[] = $report->getReportMenu();
1791
        }
1792
1793
        if ($submenus) {
1794
            return new Menu(I18N::translate('Reports'), '#', 'menu-report', array('rel' => 'nofollow'), $submenus);
1795
        } else {
1796
            return null;
1797
        }
1798
    }
1799
1800
    /**
1801
     * Create the search menu.
1802
     *
1803
     * @return Menu
1804
     */
1805
    protected function menuSearch()
1806
    {
1807
        return new Menu(I18N::translate('Search'), '#', 'menu-search', array('rel' => 'nofollow'), array_filter(array(
1808
            $this->menuSearchGeneral(),
1809
            $this->menuSearchPhonetic(),
1810
            $this->menuSearchAdvanced(),
1811
            $this->menuSearchAndReplace(),
1812
        )));
1813
    }
1814
1815
    /**
1816
     * Create the general search sub-menu.
1817
     *
1818
     * @return Menu
1819
     */
1820
    protected function menuSearchGeneral()
1821
    {
1822
        return new Menu(I18N::translate('General search'), 'search.php?' . $this->tree_url, 'menu-search-general', array('rel' => 'nofollow'));
1823
    }
1824
1825
    /**
1826
     * Create the phonetic search sub-menu.
1827
     *
1828
     * @return Menu
1829
     */
1830
    protected function menuSearchPhonetic()
1831
    {
1832
        return new Menu(/* I18N: search using “sounds like”, rather than exact spelling */ I18N::translate('Phonetic search'), 'search.php?' . $this->tree_url . '&amp;action=soundex', 'menu-search-soundex', array('rel' => 'nofollow'));
1833
    }
1834
1835
    /**
1836
     * Create the advanced search sub-menu.
1837
     *
1838
     * @return Menu
1839
     */
1840
    protected function menuSearchAdvanced()
1841
    {
1842
        return new Menu(I18N::translate('Advanced search'), 'search_advanced.php?' . $this->tree_url, 'menu-search-advanced', array('rel' => 'nofollow'));
1843
    }
1844
1845
    /**
1846
     * Create the advanced search sub-menu.
1847
     *
1848
     * @return Menu
1849
     */
1850
    protected function menuSearchAndReplace()
1851
    {
1852
        if (Auth::isEditor($this->tree)) {
1853
            return new Menu(I18N::translate('Search and replace'), 'search.php?' . $this->tree_url . '&amp;action=replace', 'menu-search-replace');
1854
        } else {
1855
            return null;
1856
        }
1857
    }
1858
1859
    /**
1860
     * Themes menu.
1861
     *
1862
     * @return Menu|null
1863
     */
1864
    public function menuThemes()
1865
    {
1866
        if ($this->tree && Site::getPreference('ALLOW_USER_THEMES') && $this->tree->getPreference('ALLOW_THEME_DROPDOWN')) {
1867
            $submenus = array();
1868
            foreach (Theme::installedThemes() as $theme) {
1869
                $class      = 'menu-theme-' . $theme->themeId() . ($theme === $this ? ' active' : '');
1870
                $submenus[] = new Menu($theme->themeName(), '#', $class, array(
1871
                    'onclick'    => 'return false;',
1872
                    'data-theme' => $theme->themeId(),
1873
                ));
1874
            }
1875
1876
            usort($submenus, function (Menu $x, Menu $y) {
1877
                return I18N::strcasecmp($x->getLabel(), $y->getLabel());
1878
            });
1879
1880
            $menu = new Menu(I18N::translate('Theme'), '#', 'menu-theme', array(), $submenus);
1881
1882
            return $menu;
1883
        } else {
1884
            return null;
1885
        }
1886
    }
1887
1888
    /**
1889
     * Create the <meta charset=""> tag.
1890
     *
1891
     * @return string
1892
     */
1893
    protected function metaCharset()
1894
    {
1895
        return '<meta charset="UTF-8">';
1896
    }
1897
1898
    /**
1899
     * Create the <meta name="description"> tag.
1900
     *
1901
     * @param string $description
1902
     *
1903
     * @return string
1904
     */
1905
    protected function metaDescription($description)
1906
    {
1907
        if ($description) {
1908
            return '<meta name="description" content="' . $description . '">';
1909
        } else {
1910
            return '';
1911
        }
1912
    }
1913
1914
    /**
1915
     * Create the <meta name="generator"> tag.
1916
     *
1917
     * @param string $generator
1918
     *
1919
     * @return string
1920
     */
1921
    protected function metaGenerator($generator)
1922
    {
1923
        if ($generator) {
1924
            return '<meta name="generator" content="' . $generator . '">';
1925
        } else {
1926
            return '';
1927
        }
1928
    }
1929
1930
    /**
1931
     * Create the <meta name="robots"> tag.
1932
     *
1933
     * @param string $robots
1934
     *
1935
     * @return string
1936
     */
1937
    protected function metaRobots($robots)
1938
    {
1939
        if ($robots) {
1940
            return '<meta name="robots" content="' . $robots . '">';
1941
        } else {
1942
            return '';
1943
        }
1944
    }
1945
1946
    /**
1947
     * Create the <meta http-equiv="X-UA-Compatible"> tag.
1948
     *
1949
     * @return string
1950
     */
1951
    protected function metaUaCompatible()
1952
    {
1953
        return '<meta http-equiv="X-UA-Compatible" content="IE=edge">';
1954
    }
1955
1956
    /**
1957
     * Create the <meta name="viewport" content="width=device-width, initial-scale=1"> tag.
1958
     *
1959
     * @return string
1960
     */
1961
    protected function metaViewport()
1962
    {
1963
        return '<meta name="viewport" content="width=device-width, initial-scale=1">';
1964
    }
1965
1966
    /**
1967
     * How many times has the current page been shown?
1968
     *
1969
     * @param  PageController $controller
1970
     *
1971
     * @return int Number of views, or zero for pages that aren't logged.
1972
     */
1973
    protected function pageViews(PageController $controller)
1974
    {
1975
        if ($this->tree && $this->tree->getPreference('SHOW_COUNTER')) {
1976
            if (isset($controller->record) && $controller->record instanceof GedcomRecord) {
1977
                return HitCounter::countHit($this->tree, WT_SCRIPT_NAME, $controller->record->getXref());
1978
            } elseif (isset($controller->root) && $controller->root instanceof GedcomRecord) {
1979
                return HitCounter::countHit($this->tree, WT_SCRIPT_NAME, $controller->root->getXref());
1980
            } elseif (WT_SCRIPT_NAME === 'index.php') {
0 ignored issues
show
introduced by
The condition Fisharebest\Webtrees\The...PT_NAME === 'index.php' is always false.
Loading history...
1981
                if (Auth::check() && Filter::get('ctype') !== 'gedcom') {
1982
                    return HitCounter::countHit($this->tree, WT_SCRIPT_NAME, 'user:' . Auth::id());
1983
                } else {
1984
                    return HitCounter::countHit($this->tree, WT_SCRIPT_NAME, 'gedcom:' . $this->tree->getTreeId());
1985
                }
1986
            }
1987
        }
1988
1989
        return 0;
1990
    }
1991
1992
    /**
1993
     * Misecellaneous dimensions, fonts, styles, etc.
1994
     *
1995
     * @param string $parameter_name
1996
     *
1997
     * @return string|int|float
1998
     */
1999
    public function parameter($parameter_name)
2000
    {
2001
        $parameters = array(
2002
            'chart-background-f'             => 'dddddd',
2003
            'chart-background-m'             => 'cccccc',
2004
            'chart-background-u'             => 'eeeeee',
2005
            'chart-box-x'                    => 250,
2006
            'chart-box-y'                    => 80,
2007
            'chart-descendancy-indent'       => 15,
2008
            'chart-font-color'               => '000000',
2009
            'chart-font-name'                => WT_ROOT . 'packages/dejavu-fonts-ttf-2.35/ttf/DejaVuSans.ttf',
2010
            'chart-font-size'                => 7,
2011
            'chart-spacing-x'                => 5,
2012
            'chart-spacing-y'                => 10,
2013
            'compact-chart-box-x'            => 240,
2014
            'compact-chart-box-y'            => 50,
2015
            'distribution-chart-high-values' => '555555',
2016
            'distribution-chart-low-values'  => 'cccccc',
2017
            'distribution-chart-no-values'   => 'ffffff',
2018
            'distribution-chart-x'           => 440,
2019
            'distribution-chart-y'           => 220,
2020
            'line-width'                     => 1.5,
2021
            'shadow-blur'                    => 0,
2022
            'shadow-color'                   => '',
2023
            'shadow-offset-x'                => 0,
2024
            'shadow-offset-y'                => 0,
2025
            'stats-small-chart-x'            => 440,
2026
            'stats-small-chart-y'            => 125,
2027
            'stats-large-chart-x'            => 900,
2028
            'image-dline'                    => $this->assetUrl() . 'images/dline.png',
2029
            'image-dline2'                   => $this->assetUrl() . 'images/dline2.png',
2030
            'image-hline'                    => $this->assetUrl() . 'images/hline.png',
2031
            'image-spacer'                   => $this->assetUrl() . 'images/spacer.png',
2032
            'image-vline'                    => $this->assetUrl() . 'images/vline.png',
2033
            'image-minus'                    => $this->assetUrl() . 'images/minus.png',
2034
            'image-plus'                     => $this->assetUrl() . 'images/plus.png',
2035
        );
2036
2037
        if (array_key_exists($parameter_name, $parameters)) {
2038
            return $parameters[$parameter_name];
2039
        } else {
2040
            throw new \InvalidArgumentException($parameter_name);
2041
        }
2042
    }
2043
2044
    /**
2045
     * Are there any pending changes for us to approve?
2046
     *
2047
     * @return bool
2048
     */
2049
    protected function pendingChangesExist()
2050
    {
2051
        return $this->tree && $this->tree->hasPendingEdit() && Auth::isModerator($this->tree);
2052
    }
2053
2054
    /**
2055
     * Create a pending changes link. Some themes prefer an alert/banner to a menu.
2056
     *
2057
     * @return string
2058
     */
2059
    protected function pendingChangesLink()
2060
    {
2061
        return
2062
            '<a href="#" onclick="window.open(\'edit_changes.php\', \'_blank\', chan_window_specs); return false;">' .
2063
            $this->pendingChangesLinkText() .
2064
            '</a>';
2065
    }
2066
2067
    /**
2068
     * Text to use in the pending changes link.
2069
     *
2070
     * @return string
2071
     */
2072
    protected function pendingChangesLinkText()
2073
    {
2074
        return I18N::translate('There are pending changes for you to moderate.');
2075
    }
2076
2077
    /**
2078
     * Generate a list of items for the main menu.
2079
     *
2080
     * @return Menu[]
2081
     */
2082
    protected function primaryMenu()
2083
    {
2084
        global $controller;
2085
2086
        if ($this->tree) {
2087
            $individual = $controller->getSignificantIndividual();
2088
2089
            return array_filter(array_merge(array(
2090
                $this->menuHomePage(),
2091
                $this->menuChart($individual),
2092
                $this->menuLists($controller->getSignificantSurname()),
2093
                $this->menuCalendar(),
2094
                $this->menuReports(),
2095
                $this->menuSearch(),
2096
            ), $this->menuModules()));
2097
        } else {
2098
            // No public trees? No genealogy menu!
2099
            return array();
2100
        }
2101
    }
2102
2103
    /**
2104
     * Add markup to the primary menu.
2105
     *
2106
     * @param Menu[] $menus
2107
     *
2108
     * @return string
2109
     */
2110
    protected function primaryMenuContainer(array $menus)
2111
    {
2112
        return '<nav><ul class="primary-menu">' . $this->primaryMenuContent($menus) . '</ul></nav>';
2113
    }
2114
2115
    /**
2116
     * Create the primary menu.
2117
     *
2118
     * @param Menu[] $menus
2119
     *
2120
     * @return string
2121
     */
2122
    protected function primaryMenuContent(array $menus)
2123
    {
2124
        return implode('', array_map(function (Menu $menu) {
2125
            return $menu->getMenuAsList();
2126
        }, $menus));
2127
    }
2128
2129
    /**
2130
     * Generate a list of items for the user menu.
2131
     *
2132
     * @return Menu[]
2133
     */
2134
    protected function secondaryMenu()
2135
    {
2136
        return array_filter(array(
2137
            $this->menuPendingChanges(),
2138
            $this->menuMyPages(),
2139
            $this->menuFavorites(),
2140
            $this->menuThemes(),
2141
            $this->menuLanguages(),
2142
            $this->menuLogin(),
2143
            $this->menuLogout(),
2144
        ));
2145
    }
2146
2147
    /**
2148
     * Add markup to the secondary menu.
2149
     *
2150
     * @param Menu[] $menus
2151
     *
2152
     * @return string
2153
     */
2154
    protected function secondaryMenuContainer(array $menus)
2155
    {
2156
        return '<ul class="nav nav-pills secondary-menu">' . $this->secondaryMenuContent($menus) . '</ul>';
2157
    }
2158
2159
    /**
2160
     * Format the secondary menu.
2161
     *
2162
     * @param Menu[] $menus
2163
     *
2164
     * @return string
2165
     */
2166
    protected function secondaryMenuContent(array $menus)
2167
    {
2168
        return implode('', array_map(function (Menu $menu) {
2169
            return $menu->getMenuAsList();
2170
        }, $menus));
2171
    }
2172
2173
    /**
2174
     * Send any HTTP headers.
2175
     */
2176
    public function sendHeaders()
2177
    {
2178
        header('Content-Type: text/html; charset=UTF-8');
2179
    }
2180
2181
    /**
2182
     * A list of CSS files to include for this page.
2183
     *
2184
     * @return string[]
2185
     */
2186
    protected function stylesheets()
2187
    {
2188
        $stylesheets = array(
2189
            WT_BOOTSTRAP_CSS_URL,
2190
            WT_FONT_AWESOME_CSS_URL,
2191
            WT_FONT_AWESOME_RTL_CSS_URL,
2192
        );
2193
2194
        if (I18N::direction() === 'rtl') {
2195
            $stylesheets[] = WT_BOOTSTRAP_RTL_CSS_URL;
2196
        }
2197
2198
        return $stylesheets;
2199
    }
2200
2201
    /**
2202
     * Create the <title> tag.
2203
     *
2204
     * @param string $title
2205
     *
2206
     * @return string
2207
     */
2208
    protected function title($title)
2209
    {
2210
        return '<title>' . Filter::escapeHtml($title) . '</title>';
2211
    }
2212
}
2213