Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 |
||
42 | abstract class AbstractTheme { |
||
43 | /** @var Tree The current tree */ |
||
44 | protected $tree; |
||
45 | |||
46 | /** @var string An escaped version of the "ged=XXX" URL parameter */ |
||
47 | protected $tree_url; |
||
48 | |||
49 | /** @var int The number of times this page has been shown */ |
||
50 | protected $page_views; |
||
51 | |||
52 | /** |
||
53 | * Custom themes should place their initialization code in the function hookAfterInit(), not in |
||
54 | * the constructor, as all themes get constructed - whether they are used or not. |
||
55 | */ |
||
56 | final public function __construct() { |
||
58 | |||
59 | /** |
||
60 | * Create accessibility links for the header. |
||
61 | * |
||
62 | * "Skip to content" allows keyboard only users to navigate over the headers without |
||
63 | * pressing TAB many times. |
||
64 | * |
||
65 | * @return string |
||
66 | */ |
||
67 | protected function accessibilityLinks() { |
||
68 | return |
||
69 | '<div class="accessibility-links">' . |
||
70 | '<a class="sr-only sr-only-focusable btn btn-info btn-sm" href="#content">' . |
||
71 | /* I18N: Skip over the headers and menus, to the main content of the page */ I18N::translate('Skip to content') . |
||
72 | '</a>' . |
||
73 | '</div>'; |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * Create scripts for analytics and tracking. |
||
78 | * |
||
79 | * @return string |
||
80 | */ |
||
81 | protected function analytics() { |
||
82 | if ($this->themeId() === '_administration') { |
||
83 | return ''; |
||
84 | } else { |
||
85 | return |
||
86 | $this->analyticsGoogleWebmaster( |
||
87 | Site::getPreference('GOOGLE_WEBMASTER_ID') |
||
88 | ) . |
||
89 | $this->analyticsGoogleTracker( |
||
90 | Site::getPreference('GOOGLE_ANALYTICS_ID') |
||
91 | ) . |
||
92 | $this->analyticsPiwikTracker( |
||
93 | Site::getPreference('PIWIK_URL'), |
||
94 | Site::getPreference('PIWIK_SITE_ID') |
||
95 | ) . |
||
96 | $this->analyticsStatcounterTracker( |
||
97 | Site::getPreference('STATCOUNTER_PROJECT_ID'), |
||
98 | Site::getPreference('STATCOUNTER_SECURITY_ID') |
||
99 | ); |
||
100 | } |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * Create the verification code for Google Webmaster Tools. |
||
105 | * |
||
106 | * @param string $verification_id |
||
107 | * |
||
108 | * @return string |
||
109 | */ |
||
110 | View Code Duplication | protected function analyticsBingWebmaster($verification_id) { |
|
|
|||
111 | // Only need to add this to the home page. |
||
112 | if (WT_SCRIPT_NAME === 'index.php' && $verification_id) { |
||
113 | return '<meta name="msvalidate.01" content="' . $verification_id . '">'; |
||
114 | } else { |
||
115 | return ''; |
||
116 | } |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Create the verification code for Google Webmaster Tools. |
||
121 | * |
||
122 | * @param string $verification_id |
||
123 | * |
||
124 | * @return string |
||
125 | */ |
||
126 | View Code Duplication | protected function analyticsGoogleWebmaster($verification_id) { |
|
127 | // Only need to add this to the home page. |
||
128 | if (WT_SCRIPT_NAME === 'index.php' && $verification_id) { |
||
129 | return '<meta name="google-site-verification" content="' . $verification_id . '">'; |
||
130 | } else { |
||
131 | return ''; |
||
132 | } |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Create the tracking code for Google Analytics. |
||
137 | * |
||
138 | * See https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced |
||
139 | * |
||
140 | * @param string $analytics_id |
||
141 | * |
||
142 | * @return string |
||
143 | */ |
||
144 | protected function analyticsGoogleTracker($analytics_id) { |
||
145 | if ($analytics_id) { |
||
146 | return |
||
147 | '<script async src="https://www.google-analytics.com/analytics.js"></script>' . |
||
148 | '<script>' . |
||
149 | 'window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;' . |
||
150 | 'ga("create","' . $analytics_id . '","auto");' . |
||
151 | 'ga("send", "pageview");' . |
||
152 | '</script>'; |
||
153 | } else { |
||
154 | return ''; |
||
155 | } |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Create the tracking code for Piwik Analytics. |
||
160 | * |
||
161 | * @param string $url - The domain/path to Piwik |
||
162 | * @param string $site_id - The Piwik site identifier |
||
163 | * |
||
164 | * @return string |
||
165 | */ |
||
166 | protected function analyticsPiwikTracker($url, $site_id) { |
||
167 | $url = preg_replace(array('/^https?:\/\//', '/\/$/'), '', $url); |
||
168 | |||
169 | if ($url && $site_id) { |
||
170 | return |
||
171 | '<script>' . |
||
172 | 'var _paq=_paq||[];' . |
||
173 | '(function(){var u=(("https:"==document.location.protocol)?"https://' . $url . '/":"http://' . $url . '/");' . |
||
174 | '_paq.push(["setSiteId",' . $site_id . ']);' . |
||
175 | '_paq.push(["setTrackerUrl",u+"piwik.php"]);' . |
||
176 | '_paq.push(["trackPageView"]);' . |
||
177 | '_paq.push(["enableLinkTracking"]);' . |
||
178 | 'var d=document,g=d.createElement("script"),s=d.getElementsByTagName("script")[0];g.defer=true;g.async=true;g.src=u+"piwik.js";' . |
||
179 | 's.parentNode.insertBefore(g,s);})();' . |
||
180 | '</script>'; |
||
181 | } else { |
||
182 | return ''; |
||
183 | } |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Create the tracking code for Statcounter. |
||
188 | * |
||
189 | * @param string $project_id - The statcounter project ID |
||
190 | * @param string $security_id - The statcounter security ID |
||
191 | * |
||
192 | * @return string |
||
193 | */ |
||
194 | protected function analyticsStatcounterTracker($project_id, $security_id) { |
||
195 | if ($project_id && $security_id) { |
||
196 | return |
||
197 | '<script>' . |
||
198 | 'var sc_project=' . (int) $project_id . ',sc_invisible=1,sc_security="' . $security_id . |
||
199 | '",scJsHost = (("https:"===document.location.protocol)?"https://secure.":"http://www.");' . |
||
200 | 'document.write("<sc"+"ript src=\'"+scJsHost+"statcounter.com/counter/counter.js\'></"+"script>");' . |
||
201 | '</script>'; |
||
202 | } else { |
||
203 | return ''; |
||
204 | } |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Create the top of the <body>. |
||
209 | * |
||
210 | * @return string |
||
211 | */ |
||
212 | public function bodyHeader() { |
||
213 | return |
||
214 | '<body class="container">' . |
||
215 | '<header>' . |
||
216 | $this->headerContent() . |
||
217 | $this->primaryMenuContainer($this->primaryMenu()) . |
||
218 | '</header>' . |
||
219 | '<main id="content" role="main">' . |
||
220 | $this->flashMessagesContainer(FlashMessages::getMessages()); |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Create the top of the <body> (for popup windows). |
||
225 | * |
||
226 | * @return string |
||
227 | */ |
||
228 | public function bodyHeaderPopupWindow() { |
||
229 | return |
||
230 | '<body class="container container-popup">' . |
||
231 | '<main id="content" role="main">' . |
||
232 | $this->flashMessagesContainer(FlashMessages::getMessages()); |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Create a contact link for a user. |
||
237 | * |
||
238 | * @param User $user |
||
239 | * |
||
240 | * @return string |
||
241 | */ |
||
242 | public function contactLink(User $user) { |
||
243 | $method = $user->getPreference('contactmethod'); |
||
244 | |||
245 | switch ($method) { |
||
246 | case 'none': |
||
247 | return ''; |
||
248 | case 'mailto': |
||
249 | return '<a href="mailto:' . Filter::escapeHtml($user->getEmail()) . '">' . $user->getRealNameHtml() . '</a>'; |
||
250 | default: |
||
251 | return "<a href='#' onclick='message(\"" . Filter::escapeHtml($user->getUserName()) . "\", \"" . $method . "\", \"" . WT_BASE_URL . Filter::escapeHtml(Functions::getQueryUrl()) . "\", \"\");return false;'>" . $user->getRealNameHtml() . '</a>'; |
||
252 | } |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * Create contact link for both technical and genealogy support. |
||
257 | * |
||
258 | * @param User $user |
||
259 | * |
||
260 | * @return string |
||
261 | */ |
||
262 | protected function contactLinkEverything(User $user) { |
||
265 | |||
266 | /** |
||
267 | * Create contact link for genealogy support. |
||
268 | * |
||
269 | * @param User $user |
||
270 | * |
||
271 | * @return string |
||
272 | */ |
||
273 | protected function contactLinkGenealogy(User $user) { |
||
276 | |||
277 | /** |
||
278 | * Create contact link for technical support. |
||
279 | * |
||
280 | * @param User $user |
||
281 | * |
||
282 | * @return string |
||
283 | */ |
||
284 | protected function contactLinkTechnical(User $user) { |
||
287 | |||
288 | /** |
||
289 | * Create contact links for the page footer. |
||
290 | * |
||
291 | * @return string |
||
292 | */ |
||
293 | protected function contactLinks() { |
||
294 | $contact_user = User::find($this->tree->getPreference('CONTACT_USER_ID')); |
||
295 | $webmaster_user = User::find($this->tree->getPreference('WEBMASTER_USER_ID')); |
||
296 | |||
297 | if ($contact_user && $contact_user === $webmaster_user) { |
||
298 | return $this->contactLinkEverything($contact_user); |
||
299 | } elseif ($contact_user && $webmaster_user) { |
||
300 | return $this->contactLinkGenealogy($contact_user) . '<br>' . $this->contactLinkTechnical($webmaster_user); |
||
301 | } elseif ($contact_user) { |
||
302 | return $this->contactLinkGenealogy($contact_user); |
||
303 | } elseif ($webmaster_user) { |
||
304 | return $this->contactLinkTechnical($webmaster_user); |
||
305 | } else { |
||
306 | return ''; |
||
307 | } |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Create the <DOCTYPE> tag. |
||
312 | * |
||
313 | * @return string |
||
314 | */ |
||
315 | public function doctype() { |
||
318 | |||
319 | /** |
||
320 | * HTML link to a "favorites icon". |
||
321 | * |
||
322 | * @return string |
||
323 | */ |
||
324 | protected function favicon() { |
||
325 | return |
||
326 | '<link rel="icon" href="' . $this->assetUrl() . 'favicon.png" type="image/png">' . |
||
327 | '<link rel="icon" type="image/png" href="' . $this->assetUrl() . 'favicon192.png" sizes="192x192">' . |
||
328 | '<link rel="apple-touch-icon" sizes="180x180" href="' . $this->assetUrl() . 'favicon180.png">'; |
||
329 | } |
||
330 | |||
331 | /** |
||
332 | * Add markup to a flash message. |
||
333 | * |
||
334 | * @param \stdClass $message |
||
335 | * |
||
336 | * @return string |
||
337 | */ |
||
338 | protected function flashMessageContainer(\stdClass $message) { |
||
341 | |||
342 | /** |
||
343 | * Create a container for messages that are "flashed" to the session |
||
344 | * on one request, and displayed on another. If there are many messages, |
||
345 | * the container may need a max-height and scroll-bar. |
||
346 | * |
||
347 | * @param \stdClass[] $messages |
||
348 | * |
||
349 | * @return string |
||
350 | */ |
||
351 | protected function flashMessagesContainer(array $messages) { |
||
352 | $html = ''; |
||
353 | foreach ($messages as $message) { |
||
354 | $html .= $this->flashMessageContainer($message); |
||
355 | } |
||
356 | |||
357 | if ($html) { |
||
358 | return '<div class="flash-messages">' . $html . '</div>'; |
||
359 | } else { |
||
360 | return ''; |
||
361 | } |
||
362 | } |
||
363 | |||
364 | /** |
||
365 | * Close the main content and create the <footer> tag. |
||
366 | * |
||
367 | * @return string |
||
368 | */ |
||
369 | public function footerContainer() { |
||
372 | |||
373 | /** |
||
374 | * Close the main content. |
||
375 | * Note that popup windows are deprecated |
||
376 | * |
||
377 | * @return string |
||
378 | */ |
||
379 | public function footerContainerPopupWindow() { |
||
382 | |||
383 | /** |
||
384 | * Create the contents of the <footer> tag. |
||
385 | * |
||
386 | * @return string |
||
387 | */ |
||
388 | protected function footerContent() { |
||
389 | return |
||
390 | $this->formatContactLinks() . |
||
391 | $this->logoPoweredBy() . |
||
392 | $this->formatPageViews($this->page_views); |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Format the contents of a variable-height home-page block. |
||
397 | * |
||
398 | * @param string $id |
||
399 | * @param string $title |
||
400 | * @param string $class |
||
401 | * @param string $content |
||
402 | * |
||
403 | * @return string |
||
404 | */ |
||
405 | View Code Duplication | public function formatBlock($id, $title, $class, $content) { |
|
406 | return |
||
407 | '<div id="' . $id . '" class="block" >' . |
||
408 | '<div class="blockheader">' . $title . '</div>' . |
||
409 | '<div class="blockcontent ' . $class . '">' . $content . '</div>' . |
||
410 | '</div>'; |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * Add markup to the contact links. |
||
415 | * |
||
416 | * @return string |
||
417 | */ |
||
418 | protected function formatContactLinks() { |
||
419 | if ($this->tree) { |
||
420 | return '<div class="contact-links">' . $this->contactLinks() . '</div>'; |
||
421 | } else { |
||
422 | return ''; |
||
423 | } |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * Add markup to the hit counter. |
||
428 | * |
||
429 | * @param int $count |
||
430 | * |
||
431 | * @return string |
||
432 | */ |
||
433 | protected function formatPageViews($count) { |
||
434 | if ($count > 0) { |
||
435 | return |
||
436 | '<div class="page-views">' . |
||
437 | I18N::plural('This page has been viewed %s time.', 'This page has been viewed %s times.', $count, |
||
438 | '<span class="odometer">' . I18N::digits($count) . '</span>') . |
||
439 | '</div>'; |
||
440 | } else { |
||
441 | return ''; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Create a pending changes link for the page footer. |
||
447 | * |
||
448 | * @return string |
||
449 | */ |
||
450 | protected function formatPendingChangesLink() { |
||
451 | if ($this->pendingChangesExist()) { |
||
452 | return '<div class="pending-changes-link">' . $this->pendingChangesLink() . '</div>'; |
||
453 | } else { |
||
454 | return ''; |
||
455 | } |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * Create a quick search form for the header. |
||
460 | * |
||
461 | * @return string |
||
462 | */ |
||
463 | protected function formQuickSearch() { |
||
464 | if ($this->tree) { |
||
465 | return |
||
466 | '<form action="search.php" class="header-search" role="search">' . |
||
467 | '<input type="hidden" name="action" value="header">' . |
||
468 | '<input type="hidden" name="ged" value="' . $this->tree->getNameHtml() . '">' . |
||
469 | $this->formQuickSearchFields() . |
||
470 | '</form>'; |
||
471 | } else { |
||
472 | return ''; |
||
473 | } |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Create a search field and submit button for the quick search form in the header. |
||
478 | * |
||
479 | * @return string |
||
480 | */ |
||
481 | protected function formQuickSearchFields() { |
||
482 | return |
||
483 | '<input type="search" name="query" size="15" placeholder="' . I18N::translate('Search') . '">' . |
||
484 | '<input type="image" src="' . $this->assetUrl() . 'images/go.png" alt="' . I18N::translate('Search') . '">'; |
||
485 | } |
||
486 | |||
487 | /** |
||
488 | * Add markup to the tree title. |
||
489 | * |
||
490 | * @return string |
||
491 | */ |
||
492 | protected function formatTreeTitle() { |
||
493 | if ($this->tree) { |
||
494 | return '<h1 class="header-title">' . $this->tree->getTitleHtml() . '</h1>'; |
||
495 | } else { |
||
496 | return ''; |
||
497 | } |
||
498 | } |
||
499 | |||
500 | /** |
||
501 | * Add markup to the secondary menu. |
||
502 | * |
||
503 | * @return string |
||
504 | */ |
||
505 | protected function formatSecondaryMenu() { |
||
506 | return |
||
507 | '<ul class="secondary-menu">' . |
||
508 | implode('', $this->secondaryMenu()) . |
||
509 | '</ul>'; |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Add markup to an item in the secondary menu. |
||
514 | * |
||
515 | * @param Menu $menu |
||
516 | * |
||
517 | * @return string |
||
518 | */ |
||
519 | protected function formatSecondaryMenuItem(Menu $menu) { |
||
522 | |||
523 | /** |
||
524 | * Create the <head> tag. |
||
525 | * |
||
526 | * @param PageController $controller The current controller |
||
527 | * |
||
528 | * @return string |
||
529 | */ |
||
530 | public function head(PageController $controller) { |
||
531 | // Record this now. By the time we render the footer, $controller no longer exists. |
||
532 | $this->page_views = $this->pageViews($controller); |
||
533 | |||
534 | return |
||
535 | '<head>' . |
||
536 | $this->headContents($controller) . |
||
537 | $this->hookHeaderExtraContent() . |
||
538 | $this->analytics() . |
||
539 | '</head>'; |
||
540 | } |
||
541 | |||
542 | /** |
||
543 | * Create the contents of the <head> tag. |
||
544 | * |
||
545 | * @param PageController $controller The current controller |
||
546 | * |
||
547 | * @return string |
||
548 | */ |
||
549 | protected function headContents(PageController $controller) { |
||
550 | // The title often includes the names of records, which may include HTML markup. |
||
551 | $title = Filter::unescapeHtml($controller->getPageTitle()); |
||
552 | |||
553 | // If an extra (site) title is specified, append it. |
||
554 | if ($this->tree && $this->tree->getPreference('META_TITLE')) { |
||
555 | $title .= ' - ' . Filter::escapeHtml($this->tree->getPreference('META_TITLE')); |
||
556 | } |
||
557 | |||
558 | $html = |
||
559 | // modernizr.js and respond.js need to be loaded before the <body> to avoid FOUC |
||
560 | '<!--[if IE 8]><script src="' . WT_MODERNIZR_JS_URL . '"></script><![endif]-->' . |
||
561 | '<!--[if IE 8]><script src="' . WT_RESPOND_JS_URL . '"></script><![endif]-->' . |
||
562 | $this->metaCharset() . |
||
563 | $this->title($title) . |
||
564 | $this->favicon() . |
||
565 | $this->metaViewport() . |
||
566 | $this->metaRobots($controller->getMetaRobots()) . |
||
567 | $this->metaUaCompatible() . |
||
568 | $this->metaGenerator(WT_WEBTREES . ' ' . WT_VERSION . ' - ' . WT_WEBTREES_URL); |
||
569 | |||
570 | if ($this->tree) { |
||
571 | $html .= $this->metaDescription($this->tree->getPreference('META_DESCRIPTION')); |
||
572 | } |
||
573 | |||
574 | // CSS files |
||
575 | foreach ($this->stylesheets() as $css) { |
||
576 | $html .= '<link rel="stylesheet" type="text/css" href="' . $css . '">'; |
||
577 | } |
||
578 | |||
579 | return $html; |
||
580 | } |
||
581 | |||
582 | /** |
||
583 | * Create the contents of the <header> tag. |
||
584 | * |
||
585 | * @return string |
||
586 | */ |
||
587 | protected function headerContent() { |
||
588 | return |
||
589 | //$this->accessibilityLinks() . |
||
590 | $this->logoHeader() . |
||
591 | $this->secondaryMenuContainer($this->secondaryMenu()) . |
||
592 | $this->formatTreeTitle() . |
||
593 | $this->formQuickSearch(); |
||
594 | } |
||
595 | |||
596 | /** |
||
597 | * Create the <header> tag for a popup window. |
||
598 | * |
||
599 | * @return string |
||
600 | */ |
||
601 | protected function headerSimple() { |
||
602 | return |
||
603 | $this->flashMessagesContainer(FlashMessages::getMessages()) . |
||
604 | '<div id="content">'; |
||
605 | } |
||
606 | |||
607 | /** |
||
608 | * Allow themes to do things after initialization (since they cannot use |
||
609 | * the constructor). |
||
610 | */ |
||
611 | public function hookAfterInit() { |
||
613 | |||
614 | /** |
||
615 | * Allow themes to add extra scripts to the page footer. |
||
616 | * |
||
617 | * @return string |
||
618 | */ |
||
619 | public function hookFooterExtraJavascript() { |
||
622 | |||
623 | /** |
||
624 | * Allow themes to add extra content to the page header. |
||
625 | * Typically this will be additional CSS. |
||
626 | * |
||
627 | * @return string |
||
628 | */ |
||
629 | public function hookHeaderExtraContent() { |
||
632 | |||
633 | /** |
||
634 | * Create the <html> tag. |
||
635 | * |
||
636 | * @return string |
||
637 | */ |
||
638 | public function html() { |
||
641 | |||
642 | /** |
||
643 | * Add HTML markup to create an alert |
||
644 | * |
||
645 | * @param string $html The content of the alert |
||
646 | * @param string $level One of 'success', 'info', 'warning', 'danger' |
||
647 | * @param bool $dismissible If true, add a close button. |
||
648 | * |
||
649 | * @return string |
||
650 | */ |
||
651 | public function htmlAlert($html, $level, $dismissible) { |
||
652 | if ($dismissible) { |
||
653 | return |
||
654 | '<div class="alert alert-' . $level . ' alert-dismissible" role="alert">' . |
||
655 | '<button type="button" class="close" data-dismiss="alert" aria-label="' . I18N::translate('close') . '">' . |
||
656 | '<span aria-hidden="true">×</span>' . |
||
657 | '</button>' . |
||
658 | $html . |
||
659 | '</div>'; |
||
660 | } else { |
||
661 | return |
||
662 | '<div class="alert alert-' . $level . '" role="alert">' . |
||
663 | $html . |
||
664 | '</div>'; |
||
665 | } |
||
666 | } |
||
667 | |||
668 | /** |
||
669 | * Display an icon for this fact. |
||
670 | * |
||
671 | * @param Fact $fact |
||
672 | * |
||
673 | * @return string |
||
674 | */ |
||
675 | public function icon(Fact $fact) { |
||
687 | |||
688 | /** |
||
689 | * Display an individual in a box - for charts, etc. |
||
690 | * |
||
691 | * @param Individual $individual |
||
692 | * |
||
693 | * @return string |
||
694 | */ |
||
695 | public function individualBox(Individual $individual) { |
||
730 | |||
731 | /** |
||
732 | * Display an empty box - for a missing individual in a chart. |
||
733 | * |
||
734 | * @return string |
||
735 | */ |
||
736 | public function individualBoxEmpty() { |
||
739 | |||
740 | /** |
||
741 | * Display an individual in a box - for charts, etc. |
||
742 | * |
||
743 | * @param Individual $individual |
||
744 | * |
||
745 | * @return string |
||
746 | */ |
||
747 | public function individualBoxLarge(Individual $individual) { |
||
782 | |||
783 | /** |
||
784 | * Display an individual in a box - for charts, etc. |
||
785 | * |
||
786 | * @param Individual $individual |
||
787 | * |
||
788 | * @return string |
||
789 | */ |
||
790 | public function individualBoxSmall(Individual $individual) { |
||
810 | |||
811 | /** |
||
812 | * Display an individual in a box - for charts, etc. |
||
813 | * |
||
814 | * @return string |
||
815 | */ |
||
816 | public function individualBoxSmallEmpty() { |
||
819 | |||
820 | /** |
||
821 | * Generate the facts, for display in charts. |
||
822 | * |
||
823 | * @param Individual $individual |
||
824 | * |
||
825 | * @return string |
||
826 | */ |
||
827 | protected function individualBoxFacts(Individual $individual) { |
||
872 | |||
873 | /** |
||
874 | * Generate the LDS summary, for display in charts. |
||
875 | * |
||
876 | * @param Individual $individual |
||
877 | * |
||
878 | * @return string |
||
879 | */ |
||
880 | protected function individualBoxLdsSummary(Individual $individual) { |
||
898 | |||
899 | /** |
||
900 | * Links, to show in chart boxes; |
||
901 | * |
||
902 | * @param Individual $individual |
||
903 | * |
||
904 | * @return Menu[] |
||
905 | */ |
||
906 | protected function individualBoxMenu(Individual $individual) { |
||
914 | |||
915 | /** |
||
916 | * Chart links, to show in chart boxes; |
||
917 | * |
||
918 | * @param Individual $individual |
||
919 | * |
||
920 | * @return Menu[] |
||
921 | */ |
||
922 | protected function individualBoxMenuCharts(Individual $individual) { |
||
942 | |||
943 | /** |
||
944 | * Family links, to show in chart boxes. |
||
945 | * |
||
946 | * @param Individual $individual |
||
947 | * |
||
948 | * @return Menu[] |
||
949 | */ |
||
950 | protected function individualBoxMenuFamilyLinks(Individual $individual) { |
||
968 | |||
969 | /** |
||
970 | * Create part of an individual box |
||
971 | * |
||
972 | * @param Individual $individual |
||
973 | * |
||
974 | * @return string |
||
975 | */ |
||
976 | protected function individualBoxSexSymbol(Individual $individual) { |
||
983 | |||
984 | /** |
||
985 | * Initialise the theme. We cannot pass these in a constructor, as the construction |
||
986 | * happens in a theme file, and we need to be able to change it. |
||
987 | * |
||
988 | * @param Tree|null $tree The current tree (if there is one). |
||
989 | */ |
||
990 | final public function init(Tree $tree = null) { |
||
996 | |||
997 | /** |
||
998 | * A large webtrees logo, for the header. |
||
999 | * |
||
1000 | * @return string |
||
1001 | */ |
||
1002 | protected function logoHeader() { |
||
1005 | |||
1006 | /** |
||
1007 | * A small "powered by webtrees" logo for the footer. |
||
1008 | * |
||
1009 | * @return string |
||
1010 | */ |
||
1011 | protected function logoPoweredBy() { |
||
1014 | |||
1015 | /** |
||
1016 | * A menu for the day/month/year calendar views. |
||
1017 | * |
||
1018 | * @return Menu |
||
1019 | */ |
||
1020 | protected function menuCalendar() { |
||
1030 | |||
1031 | /** |
||
1032 | * Generate a menu item to change the blocks on the current (index.php) page. |
||
1033 | * |
||
1034 | * @return Menu|null |
||
1035 | */ |
||
1036 | protected function menuChangeBlocks() { |
||
1045 | |||
1046 | /** |
||
1047 | * Generate a menu for each of the different charts. |
||
1048 | * |
||
1049 | * @param Individual $individual |
||
1050 | * |
||
1051 | * @return Menu |
||
1052 | */ |
||
1053 | protected function menuChart(Individual $individual) { |
||
1076 | |||
1077 | /** |
||
1078 | * Generate a menu item for the ancestors chart (ancestry.php). |
||
1079 | * |
||
1080 | * @param Individual $individual |
||
1081 | * |
||
1082 | * @return Menu |
||
1083 | */ |
||
1084 | protected function menuChartAncestors(Individual $individual) { |
||
1087 | |||
1088 | /** |
||
1089 | * Generate a menu item for the compact tree (compact.php). |
||
1090 | * |
||
1091 | * @param Individual $individual |
||
1092 | * |
||
1093 | * @return Menu |
||
1094 | */ |
||
1095 | protected function menuChartCompact(Individual $individual) { |
||
1098 | |||
1099 | /** |
||
1100 | * Generate a menu item for the descendants chart (descendancy.php). |
||
1101 | * |
||
1102 | * @param Individual $individual |
||
1103 | * |
||
1104 | * @return Menu |
||
1105 | */ |
||
1106 | protected function menuChartDescendants(Individual $individual) { |
||
1109 | |||
1110 | /** |
||
1111 | * Generate a menu item for the family-book chart (familybook.php). |
||
1112 | * |
||
1113 | * @param Individual $individual |
||
1114 | * |
||
1115 | * @return Menu |
||
1116 | */ |
||
1117 | protected function menuChartFamilyBook(Individual $individual) { |
||
1120 | |||
1121 | /** |
||
1122 | * Generate a menu item for the fan chart (fanchart.php). |
||
1123 | * |
||
1124 | * We can only do this if the GD2 library is installed with TrueType support. |
||
1125 | * |
||
1126 | * @param Individual $individual |
||
1127 | * |
||
1128 | * @return Menu|null |
||
1129 | */ |
||
1130 | View Code Duplication | protected function menuChartFanChart(Individual $individual) { |
|
1137 | |||
1138 | /** |
||
1139 | * Generate a menu item for the interactive tree (tree module). |
||
1140 | * |
||
1141 | * @param Individual $individual |
||
1142 | * |
||
1143 | * @return Menu|null |
||
1144 | */ |
||
1145 | View Code Duplication | protected function menuChartInteractiveTree(Individual $individual) { |
|
1152 | |||
1153 | /** |
||
1154 | * Generate a menu item for the hourglass chart (hourglass.php). |
||
1155 | * |
||
1156 | * @param Individual $individual |
||
1157 | * |
||
1158 | * @return Menu |
||
1159 | */ |
||
1160 | protected function menuChartHourglass(Individual $individual) { |
||
1163 | |||
1164 | /** |
||
1165 | * Generate a menu item for the lifepsan chart (lifespan.php). |
||
1166 | * |
||
1167 | * @param Individual $individual |
||
1168 | * |
||
1169 | * @return Menu |
||
1170 | */ |
||
1171 | protected function menuChartLifespan(Individual $individual) { |
||
1174 | |||
1175 | /** |
||
1176 | * Generate a menu item for the pedigree chart (pedigree.php). |
||
1177 | * |
||
1178 | * @param Individual $individual |
||
1179 | * |
||
1180 | * @return Menu |
||
1181 | */ |
||
1182 | protected function menuChartPedigree(Individual $individual) { |
||
1185 | |||
1186 | /** |
||
1187 | * Generate a menu item for the pedigree map (googlemap module). |
||
1188 | * |
||
1189 | * @param Individual $individual |
||
1190 | * |
||
1191 | * @return Menu|null |
||
1192 | */ |
||
1193 | View Code Duplication | protected function menuChartPedigreeMap(Individual $individual) { |
|
1200 | |||
1201 | /** |
||
1202 | * Generate a menu item for the relationship chart (relationship.php). |
||
1203 | * |
||
1204 | * @param Individual $individual |
||
1205 | * |
||
1206 | * @return Menu |
||
1207 | */ |
||
1208 | protected function menuChartRelationship(Individual $individual) { |
||
1217 | |||
1218 | /** |
||
1219 | * Generate a menu item for the statistics charts (statistics.php). |
||
1220 | * |
||
1221 | * @return Menu |
||
1222 | */ |
||
1223 | protected function menuChartStatistics() { |
||
1226 | |||
1227 | /** |
||
1228 | * Generate a menu item for the timeline chart (timeline.php). |
||
1229 | * |
||
1230 | * @param Individual $individual |
||
1231 | * |
||
1232 | * @return Menu |
||
1233 | */ |
||
1234 | protected function menuChartTimeline(Individual $individual) { |
||
1237 | |||
1238 | /** |
||
1239 | * Generate a menu item for the control panel (admin.php). |
||
1240 | * |
||
1241 | * @return Menu|null |
||
1242 | */ |
||
1243 | protected function menuControlPanel() { |
||
1250 | |||
1251 | /** |
||
1252 | * Favorites menu. |
||
1253 | * |
||
1254 | * @return Menu|null |
||
1255 | */ |
||
1256 | protected function menuFavorites() { |
||
1304 | |||
1305 | /** |
||
1306 | * A menu for the home (family tree) pages. |
||
1307 | * |
||
1308 | * @return Menu |
||
1309 | */ |
||
1310 | protected function menuHomePage() { |
||
1327 | |||
1328 | /** |
||
1329 | * A menu to show a list of available languages. |
||
1330 | * |
||
1331 | * @return Menu|null |
||
1332 | */ |
||
1333 | protected function menuLanguages() { |
||
1351 | |||
1352 | /** |
||
1353 | * Create a menu to show lists of individuals, families, sources, etc. |
||
1354 | * |
||
1355 | * @param string $surname The significant surname on the page |
||
1356 | * |
||
1357 | * @return Menu |
||
1358 | */ |
||
1359 | protected function menuLists($surname) { |
||
1360 | // Do not show empty lists |
||
1361 | $row = Database::prepare( |
||
1362 | "SELECT SQL_CACHE" . |
||
1363 | " EXISTS(SELECT 1 FROM `##sources` WHERE s_file = ?) AS sour," . |
||
1364 | " EXISTS(SELECT 1 FROM `##other` WHERE o_file = ? AND o_type='REPO') AS repo," . |
||
1365 | " EXISTS(SELECT 1 FROM `##other` WHERE o_file = ? AND o_type='NOTE') AS note," . |
||
1366 | " EXISTS(SELECT 1 FROM `##media` WHERE m_file = ?) AS obje" |
||
1367 | )->execute(array( |
||
1368 | $this->tree->getTreeId(), |
||
1369 | $this->tree->getTreeId(), |
||
1370 | $this->tree->getTreeId(), |
||
1371 | $this->tree->getTreeId(), |
||
1372 | ))->fetchOneRow(); |
||
1373 | |||
1374 | $submenus = array( |
||
1375 | $this->menuListsIndividuals($surname), |
||
1376 | $this->menuListsFamilies($surname), |
||
1377 | $this->menuListsBranches($surname), |
||
1378 | $this->menuListsPlaces(), |
||
1379 | ); |
||
1380 | if ($row->obje) { |
||
1381 | $submenus[] = $this->menuListsMedia(); |
||
1382 | } |
||
1383 | if ($row->repo) { |
||
1384 | $submenus[] = $this->menuListsRepositories(); |
||
1385 | } |
||
1386 | if ($row->sour) { |
||
1387 | $submenus[] = $this->menuListsSources(); |
||
1388 | } |
||
1389 | if ($row->note) { |
||
1390 | $submenus[] = $this->menuListsNotes(); |
||
1391 | } |
||
1392 | |||
1393 | uasort($submenus, function (Menu $x, Menu $y) { |
||
1394 | return I18N::strcasecmp($x->getLabel(), $y->getLabel()); |
||
1395 | }); |
||
1396 | |||
1397 | return new Menu(I18N::translate('Lists'), '#', 'menu-list', array(), $submenus); |
||
1398 | } |
||
1399 | |||
1400 | /** |
||
1401 | * A menu for the list of branches |
||
1402 | * |
||
1403 | * @param string $surname The significant surname on the page |
||
1404 | * |
||
1405 | * @return Menu |
||
1406 | */ |
||
1407 | protected function menuListsBranches($surname) { |
||
1410 | |||
1411 | /** |
||
1412 | * A menu for the list of families |
||
1413 | * |
||
1414 | * @param string $surname The significant surname on the page |
||
1415 | * |
||
1416 | * @return Menu |
||
1417 | */ |
||
1418 | protected function menuListsFamilies($surname) { |
||
1421 | |||
1422 | /** |
||
1423 | * A menu for the list of individuals |
||
1424 | * |
||
1425 | * @param string $surname The significant surname on the page |
||
1426 | * |
||
1427 | * @return Menu |
||
1428 | */ |
||
1429 | protected function menuListsIndividuals($surname) { |
||
1432 | |||
1433 | /** |
||
1434 | * A menu for the list of media objects |
||
1435 | * |
||
1436 | * @return Menu |
||
1437 | */ |
||
1438 | protected function menuListsMedia() { |
||
1441 | |||
1442 | /** |
||
1443 | * A menu for the list of notes |
||
1444 | * |
||
1445 | * @return Menu |
||
1446 | */ |
||
1447 | protected function menuListsNotes() { |
||
1450 | |||
1451 | /** |
||
1452 | * A menu for the list of individuals |
||
1453 | * |
||
1454 | * @return Menu |
||
1455 | */ |
||
1456 | protected function menuListsPlaces() { |
||
1459 | |||
1460 | /** |
||
1461 | * A menu for the list of repositories |
||
1462 | * |
||
1463 | * @return Menu |
||
1464 | */ |
||
1465 | protected function menuListsRepositories() { |
||
1468 | |||
1469 | /** |
||
1470 | * A menu for the list of sources |
||
1471 | * |
||
1472 | * @return Menu |
||
1473 | */ |
||
1474 | protected function menuListsSources() { |
||
1477 | |||
1478 | /** |
||
1479 | * A login menu option (or null if we are already logged in). |
||
1480 | * |
||
1481 | * @return Menu|null |
||
1482 | */ |
||
1483 | protected function menuLogin() { |
||
1490 | |||
1491 | /** |
||
1492 | * A logout menu option (or null if we are already logged out). |
||
1493 | * |
||
1494 | * @return Menu|null |
||
1495 | */ |
||
1496 | protected function menuLogout() { |
||
1503 | |||
1504 | /** |
||
1505 | * Get the additional menus created by each of the modules |
||
1506 | * |
||
1507 | * @return Menu[] |
||
1508 | */ |
||
1509 | protected function menuModules() { |
||
1517 | |||
1518 | /** |
||
1519 | * A link to allow users to edit their account settings (edituser.php). |
||
1520 | * |
||
1521 | * @return Menu|null |
||
1522 | */ |
||
1523 | protected function menuMyAccount() { |
||
1530 | |||
1531 | /** |
||
1532 | * A link to the user's individual record (individual.php). |
||
1533 | * |
||
1534 | * @return Menu|null |
||
1535 | */ |
||
1536 | protected function menuMyIndividualRecord() { |
||
1545 | |||
1546 | /** |
||
1547 | * A link to the user's personal home page. |
||
1548 | * |
||
1549 | * @return Menu |
||
1550 | */ |
||
1551 | protected function menuMyPage() { |
||
1554 | |||
1555 | /** |
||
1556 | * A menu for the user's personal pages. |
||
1557 | * |
||
1558 | * @return Menu|null |
||
1559 | */ |
||
1560 | protected function menuMyPages() { |
||
1574 | |||
1575 | /** |
||
1576 | * A link to the user's individual record (pedigree.php). |
||
1577 | * |
||
1578 | * @return Menu|null |
||
1579 | */ |
||
1580 | protected function menuMyPedigree() { |
||
1596 | |||
1597 | /** |
||
1598 | * Create a pending changes menu. |
||
1599 | * |
||
1600 | * @return Menu|null |
||
1601 | */ |
||
1602 | protected function menuPendingChanges() { |
||
1611 | |||
1612 | /** |
||
1613 | * A menu with a list of reports. |
||
1614 | * |
||
1615 | * @return Menu|null |
||
1616 | */ |
||
1617 | protected function menuReports() { |
||
1629 | |||
1630 | /** |
||
1631 | * Create the search menu |
||
1632 | * |
||
1633 | * @return Menu |
||
1634 | */ |
||
1635 | protected function menuSearch() { |
||
1652 | |||
1653 | /** |
||
1654 | * Themes menu. |
||
1655 | * |
||
1656 | * @return Menu|null |
||
1657 | */ |
||
1658 | public function menuThemes() { |
||
1680 | |||
1681 | /** |
||
1682 | * Create the <meta charset=""> tag. |
||
1683 | * |
||
1684 | * @return string |
||
1685 | */ |
||
1686 | protected function metaCharset() { |
||
1689 | |||
1690 | /** |
||
1691 | * Create the <meta name="description"> tag. |
||
1692 | * |
||
1693 | * @param string $description |
||
1694 | * |
||
1695 | * @return string |
||
1696 | */ |
||
1697 | protected function metaDescription($description) { |
||
1704 | |||
1705 | /** |
||
1706 | * Create the <meta name="generator"> tag. |
||
1707 | * |
||
1708 | * @param string $generator |
||
1709 | * |
||
1710 | * @return string |
||
1711 | */ |
||
1712 | protected function metaGenerator($generator) { |
||
1719 | |||
1720 | /** |
||
1721 | * Create the <meta name="robots"> tag. |
||
1722 | * |
||
1723 | * @param string $robots |
||
1724 | * |
||
1725 | * @return string |
||
1726 | */ |
||
1727 | protected function metaRobots($robots) { |
||
1734 | |||
1735 | /** |
||
1736 | * Create the <meta http-equiv="X-UA-Compatible"> tag. |
||
1737 | * |
||
1738 | * @return string |
||
1739 | */ |
||
1740 | protected function metaUaCompatible() { |
||
1743 | |||
1744 | /** |
||
1745 | * Create the <meta name="viewport" content="width=device-width, initial-scale=1"> tag. |
||
1746 | * |
||
1747 | * @return string |
||
1748 | */ |
||
1749 | protected function metaViewport() { |
||
1752 | |||
1753 | /** |
||
1754 | * How many times has the current page been shown? |
||
1755 | * |
||
1756 | * @param PageController $controller |
||
1757 | * |
||
1758 | * @return int Number of views, or zero for pages that aren't logged. |
||
1759 | */ |
||
1760 | protected function pageViews(PageController $controller) { |
||
1777 | |||
1778 | /** |
||
1779 | * Misecellaneous dimensions, fonts, styles, etc. |
||
1780 | * |
||
1781 | * @param string $parameter_name |
||
1782 | * |
||
1783 | * @return string|int|float |
||
1784 | */ |
||
1785 | public function parameter($parameter_name) { |
||
1828 | |||
1829 | /** |
||
1830 | * Are there any pending changes for us to approve? |
||
1831 | * |
||
1832 | * @return bool |
||
1833 | */ |
||
1834 | protected function pendingChangesExist() { |
||
1837 | |||
1838 | /** |
||
1839 | * Create a pending changes link. Some themes prefer an alert/banner to a menu. |
||
1840 | * |
||
1841 | * @return string |
||
1842 | */ |
||
1843 | protected function pendingChangesLink() { |
||
1849 | |||
1850 | /** |
||
1851 | * Text to use in the pending changes link. |
||
1852 | * |
||
1853 | * @return string |
||
1854 | */ |
||
1855 | protected function pendingChangesLinkText() { |
||
1858 | |||
1859 | /** |
||
1860 | * Generate a list of items for the main menu. |
||
1861 | * |
||
1862 | * @return Menu[] |
||
1863 | */ |
||
1864 | protected function primaryMenu() { |
||
1883 | |||
1884 | /** |
||
1885 | * Add markup to the primary menu. |
||
1886 | * |
||
1887 | * @param Menu[] $menus |
||
1888 | * |
||
1889 | * @return string |
||
1890 | */ |
||
1891 | protected function primaryMenuContainer(array $menus) { |
||
1894 | |||
1895 | /** |
||
1896 | * Create the primary menu. |
||
1897 | * |
||
1898 | * @param Menu[] $menus |
||
1899 | * |
||
1900 | * @return string |
||
1901 | */ |
||
1902 | protected function primaryMenuContent(array $menus) { |
||
1905 | |||
1906 | /** |
||
1907 | * Generate a list of items for the user menu. |
||
1908 | * |
||
1909 | * @return Menu[] |
||
1910 | */ |
||
1911 | View Code Duplication | protected function secondaryMenu() { |
|
1922 | |||
1923 | /** |
||
1924 | * Add markup to the secondary menu. |
||
1925 | * |
||
1926 | * @param Menu[] $menus |
||
1927 | * |
||
1928 | * @return string |
||
1929 | */ |
||
1930 | protected function secondaryMenuContainer(array $menus) { |
||
1933 | |||
1934 | /** |
||
1935 | * Format the secondary menu. |
||
1936 | * |
||
1937 | * @param Menu[] $menus |
||
1938 | * |
||
1939 | * @return string |
||
1940 | */ |
||
1941 | protected function secondaryMenuContent(array $menus) { |
||
1944 | |||
1945 | /** |
||
1946 | * Send any HTTP headers. |
||
1947 | */ |
||
1948 | public function sendHeaders() { |
||
1951 | |||
1952 | /** |
||
1953 | * A list of CSS files to include for this page. |
||
1954 | * |
||
1955 | * @return string[] |
||
1956 | */ |
||
1957 | protected function stylesheets() { |
||
1970 | |||
1971 | /** |
||
1972 | * Create the <title> tag. |
||
1973 | * |
||
1974 | * @param string $title |
||
1975 | * |
||
1976 | * @return string |
||
1977 | */ |
||
1978 | protected function title($title) { |
||
1981 | } |
||
1982 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.