Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 428
0.00% covered (danger)
0.00%
0 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
NostalgiaTemplate
0.00% covered (danger)
0.00%
0 / 428
0.00% covered (danger)
0.00%
0 / 25
11772
0.00% covered (danger)
0.00%
0 / 1
 execute
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 beforeContent
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 afterContent
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 searchForm
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 pageStats
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 topLinks
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
42
 variantLinks
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 bottomLinks
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
272
 otherLanguages
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
72
 specialPagesList
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 pageTitleLinks
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
156
 pageTitle
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 printableLink
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 editThisPage
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 deleteThisPage
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 protectThisPage
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 watchThisPage
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
12
 moveThisPage
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 historyLink
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 whatLinksHere
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 userContribsLink
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 emailUserLink
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 watchPageLinksLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 talkLink
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
182
 getUploadLink
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Nostalgia: A skin which looks like Wikipedia did in its first year (2001).
4 *
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 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23namespace MediaWiki\Skin\Nostalgia;
24
25use BaseTemplate;
26use MediaWiki\Html\Html;
27use MediaWiki\Language\RawMessage;
28use MediaWiki\Linker\Linker;
29use MediaWiki\MediaWikiServices;
30use MediaWiki\SpecialPage\SpecialPage;
31use MediaWiki\Title\Title;
32use UploadBase;
33use XmlSelect;
34
35/**
36 * @todo document
37 * @ingroup Skins
38 */
39class NostalgiaTemplate extends BaseTemplate {
40
41    /**
42     * How many search boxes have we made?  Avoid duplicate id's.
43     * @var string|int
44     */
45    protected $searchboxes = '';
46
47    /** @var int */
48    protected $mWatchLinkNum = 0;
49
50    public function execute() {
51        echo $this->beforeContent();
52        $this->html( 'bodytext' );
53        echo "\n";
54        echo $this->afterContent();
55        $this->html( 'dataAfterContent' );
56    }
57
58    /**
59     * This will be called immediately after the "<body>" tag.
60     * @return string
61     */
62    public function beforeContent() {
63        $skin = $this->getSkin();
64        $s = "\n<div id='content'>\n<div id='top'>\n";
65        $s .= '<div id="logo">' . $skin->logoText( 'right' ) . '</div>';
66
67        $s .= $this->pageTitle();
68        $s .= "<p class='subtitle'>" . $skin->prepareSubtitle() . "</p>\n";
69
70        $s .= '<div id="topbar">';
71        $s .= $this->topLinks() . "\n<br />";
72
73        $notice = $skin->getSiteNotice();
74        if ( $notice ) {
75            $s .= "\n<div id='siteNotice'>$notice</div>\n";
76        }
77        $s .= $this->pageTitleLinks();
78
79        $ol = $this->otherLanguages();
80        if ( $ol ) {
81            $s .= '<br />' . $ol;
82        }
83
84        $s .= $skin->getCategories();
85
86        $s .= "<br clear='all' /></div><hr />\n</div>\n";
87        $s .= "\n<div id='article'>";
88
89        return $s;
90    }
91
92    /**
93     * This gets called shortly before the "</body>" tag.
94     * @return string HTML to be put before "</body>"
95     */
96    public function afterContent() {
97        $s = "\n</div><br clear='all' />\n";
98
99        $s .= "\n<div id='footer'><hr />";
100
101        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
102        $s .= $this->bottomLinks();
103        $s .= "\n<br />" . $this->pageStats();
104        $s .= "\n<br />"
105            . $linkRenderer->makeKnownLink(
106                Title::newMainPage(),
107                $this->getMsg( 'mainpage' )->text()
108            )
109            . ' | ' . $this->get( 'about', '' )
110            . ' | ' . $this->searchForm();
111
112        $s .= "\n</div>\n</div>\n";
113
114        return $s;
115    }
116
117    /**
118     * @return string
119     */
120    private function searchForm() {
121        $skin = $this->getSkin();
122        $search = $skin->getRequest()->getText( 'search' );
123        $specialAction = SpecialPage::getTitleFor( 'Search' )->getLocalURL();
124
125        $s = '<form id="searchform' . $this->searchboxes
126            . '" name="search" class="inline" method="get" action="'
127            . htmlspecialchars( $specialAction ) . "\">\n"
128            . '<input type="text" id="searchInput' . $this->searchboxes
129            . '" name="search" size="19" value="'
130            . htmlspecialchars( substr( $search, 0, 256 ) ) . "\" />\n"
131            . '<input type="submit" name="go" value="' . $skin->msg( 'searcharticle' )->escaped()
132            . '" />'
133            . '&#160;<input type="submit" name="fulltext" value="'
134            . $skin->msg( 'searchbutton' )->escaped() . "\" />\n"
135            . '</form>';
136
137        // Ensure unique id's for search boxes made after the first
138        $this->searchboxes = $this->searchboxes == '' ? 2 : $this->searchboxes + 1;
139
140        return $s;
141    }
142
143    /**
144     * @return string
145     */
146    private function pageStats() {
147        $ret = [];
148        $items = [ 'viewcount', 'credits', 'lastmod', 'numberofwatchingusers', 'copyright' ];
149
150        foreach ( $items as $item ) {
151            if ( $this->data[$item] !== false ) {
152                $ret[] = $this->data[$item];
153            }
154        }
155
156        return implode( ' ', $ret );
157    }
158
159    /**
160     * @return string
161     */
162    private function topLinks() {
163        $sep = " |\n";
164        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
165
166        $skin = $this->getSkin();
167        $s = $linkRenderer->makeKnownLink(
168                Title::newMainPage(),
169                $this->getMsg( 'mainpage' )->text()
170            ) . $sep
171            . Linker::specialLink( 'Recentchanges' );
172
173        if ( $this->data['isarticle'] ) {
174            $s .= $sep . '<strong>' . $this->editThisPage() . '</strong>' . $sep . $this->talkLink()
175                . $sep . $this->historyLink();
176        }
177
178        /* show links to different language variants */
179        $s .= $this->variantLinks();
180        if ( !$this->data['loggedin'] ) {
181            $s .= $sep . Linker::specialLink( 'Userlogin' );
182        } else {
183            /* show user page and user talk links */
184            $user = $skin->getUser();
185            $s .= $sep . Linker::link( $user->getUserPage(), $skin->msg( 'mypage' )->escaped() );
186            $s .= $sep . Linker::link( $user->getTalkPage(), $skin->msg( 'mytalk' )->escaped() );
187
188            $userHasNewMessages = MediaWikiServices::getInstance()
189                ->getTalkPageNotificationManager()->userHasNewMessages( $user );
190            if ( $userHasNewMessages ) {
191                $s .= ' *';
192            }
193            /* show watchlist link */
194            $s .= $sep . Linker::specialLink( 'Watchlist' );
195            /* show my contributions link */
196            $s .= $sep . Linker::link(
197                SpecialPage::getSafeTitleFor( 'Contributions', $this->data['username'] ),
198                $skin->msg( 'mycontris' )->escaped() );
199            /* show my preferences link */
200            $s .= $sep . Linker::specialLink( 'Preferences' );
201            /* show upload file link */
202            if ( UploadBase::isEnabled() && UploadBase::isAllowed( $user ) === true ) {
203                $s .= $sep . $this->getUploadLink();
204            }
205
206            /* show log out link */
207            $s .= $sep . Linker::specialLink( 'Userlogout' );
208        }
209
210        $s .= $sep . $this->specialPagesList();
211
212        return $s;
213    }
214
215    /**
216     * Language/charset variant links for classic-style skins
217     * @return string
218     */
219    private function variantLinks() {
220        $s = '';
221
222        /* show links to different language variants */
223        global $wgDisableLangConversion;
224
225        $skin = $this->getSkin();
226        $title = $skin->getTitle();
227        $lang = $title->getPageLanguage();
228        $variants = MediaWikiServices::getInstance()->getLanguageConverterFactory()
229            ->getLanguageConverter( $lang )
230            ->getVariants();
231        $userLang = $skin->getLanguage();
232
233        if ( !$wgDisableLangConversion && count( $variants ) > 1
234            && !$title->isSpecialPage() ) {
235            foreach ( $variants as $code ) {
236                $varname = $lang->getVariantname( $code );
237
238                if ( $varname == 'disable' ) {
239                    continue;
240                }
241                $s = $userLang->pipeList( [
242                    $s,
243                    '<a href="' . htmlspecialchars( $title->getLocalURL( 'variant=' . $code ) )
244                        . '" lang="' . $code . '" hreflang="' . $code . '">'
245                        . htmlspecialchars( $varname ) . '</a>',
246                ] );
247            }
248        }
249
250        return $s;
251    }
252
253    /**
254     * @return string
255     */
256    private function bottomLinks() {
257        $skin = $this->getSkin();
258        $sep = $skin->msg( 'pipe-separator' )->escaped() . "\n";
259        $out = $skin->getOutput();
260        $user = $skin->getUser();
261
262        $s = '';
263        if ( $out->isArticleRelated() ) {
264            $element = [ '<strong>' . $this->editThisPage() . '</strong>' ];
265
266            if ( $user->isRegistered() ) {
267                $element[] = $this->watchThisPage();
268            }
269
270            $element[] = $this->talkLink();
271            $element[] = $this->historyLink();
272            $element[] = $this->whatLinksHere();
273            $element[] = $this->watchPageLinksLink();
274
275            $title = $skin->getTitle();
276
277            if (
278                $title->getNamespace() == NS_USER ||
279                $title->getNamespace() == NS_USER_TALK
280            ) {
281                $services = MediaWikiServices::getInstance();
282                $userIdentity = $services->getUserIdentityLookup()->getUserIdentityByName( $title->getText() );
283                $userNameUtils = $services->getUserNameUtils();
284                $ip = $userNameUtils->isIP( $title->getText() );
285
286                # Both anons and non-anons have contributions list
287                if ( ( $userIdentity && $userIdentity->isRegistered() ) || $ip ) {
288                    $element[] = $this->userContribsLink();
289                }
290
291                if ( $userIdentity && $userIdentity->isRegistered() && $skin->showEmailUser( $userIdentity ) ) {
292                    $element[] = $this->emailUserLink();
293                }
294            }
295
296            $s = implode( $sep, $element );
297
298            if ( $title->getArticleID() ) {
299                $s .= "\n<br />";
300
301                // Delete/protect/move links for privileged users
302                if ( $user->isAllowed( 'delete' ) ) {
303                    $s .= $this->deleteThisPage();
304                }
305
306                if ( $user->isAllowed( 'protect' ) &&
307                    MediaWikiServices::getInstance()->getRestrictionStore()->listApplicableRestrictionTypes( $title )
308                ) {
309                    $s .= $sep . $this->protectThisPage();
310                }
311
312                if ( $user->isAllowed( 'move' ) ) {
313                    $s .= $sep . $this->moveThisPage();
314                }
315            }
316
317            $s .= "<br />\n" . $this->otherLanguages();
318        }
319
320        return $s;
321    }
322
323    /**
324     * @return string
325     */
326    private function otherLanguages() {
327        global $wgHideInterlanguageLinks;
328
329        if ( $wgHideInterlanguageLinks ) {
330            return '';
331        }
332
333        $skin = $this->getSkin();
334        $a = $skin->getOutput()->getLanguageLinks();
335
336        if ( count( $a ) === 0 ) {
337            return '';
338        }
339
340        $s = $skin->msg( 'otherlanguages' )->escaped() . $skin->msg( 'colon-separator' )->escaped();
341        $first = true;
342        $lang = $skin->getLanguage();
343
344        if ( $lang->isRTL() ) {
345            $s .= '<span dir="ltr">';
346        }
347
348        $languageNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
349        foreach ( $a as $l ) {
350            if ( !$first ) {
351                $s .= $skin->msg( 'pipe-separator' )->escaped();
352            }
353
354            $first = false;
355
356            $nt = Title::newFromText( $l );
357            $text = $languageNameUtils->getLanguageName( $nt->getInterwiki() );
358
359            $s .= Html::element( 'a',
360                [ 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => 'external' ],
361                $text == '' ? $l : $text );
362        }
363
364        if ( $lang->isRTL() ) {
365            $s .= '</span>';
366        }
367
368        return $s;
369    }
370
371    /**
372     * Show a drop-down box of special pages
373     * @return string
374     */
375    private function specialPagesList() {
376        global $wgScript;
377
378        $skin = $this->getSkin();
379        $select = new XmlSelect( 'title' );
380        $factory = MediaWikiServices::getInstance()->getSpecialPageFactory();
381        $pages = $factory->getUsablePages( $skin->getUser() );
382        array_unshift( $pages, $factory->getPage( 'SpecialPages' ) );
383        /** @var SpecialPage[] $pages */
384        foreach ( $pages as $obj ) {
385            $desc = $obj->getDescription();
386            if ( is_string( $desc ) ) {
387                // T343849: returning a string from ::getDescription() is deprecated.
388                $desc = ( new RawMessage( '$1' ) )->rawParams( $desc );
389            }
390            $select->addOption( $desc->text(),
391                $obj->getPageTitle()->getPrefixedDBkey() );
392        }
393
394        return Html::rawElement( 'form',
395            [ 'id' => 'specialpages', 'method' => 'get', 'action' => $wgScript ],
396            $select->getHTML() . Html::element(
397                'input',
398                [ 'type' => 'submit', 'value' => $skin->msg( 'go' )->text() ]
399            )
400        );
401    }
402
403    /**
404     * @return string
405     */
406    private function pageTitleLinks() {
407        $skin = $this->getSkin();
408        $title = $skin->getTitle();
409        $out = $skin->getOutput();
410        $user = $skin->getUser();
411        $lang = $skin->getLanguage();
412        $request = $skin->getRequest();
413
414        $oldid = $request->getVal( 'oldid' );
415        $diff = $request->getVal( 'diff' );
416        $action = $request->getText( 'action' );
417
418        $s = [ $this->printableLink() ];
419        $disclaimer = $this->get( 'disclaimers', '' );
420
421        # may be empty
422        if ( $disclaimer ) {
423            $s[] = $disclaimer;
424        }
425
426        $privacy = $this->get( 'privacy' );
427
428        # may be empty too
429        if ( $privacy ) {
430            $s[] = $privacy;
431        }
432
433        if ( $out->isArticleRelated() && $title->getNamespace() == NS_FILE ) {
434            $image = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $title );
435
436            if ( $image ) {
437                $href = $image->getUrl();
438                $s[] = Html::element( 'a', [ 'href' => $href,
439                    'title' => $href ], $title->getText() );
440
441            }
442        }
443
444        if ( $action == 'history' || $diff !== null || $oldid !== null ) {
445            $s[] .= Linker::linkKnown(
446                $title,
447                $skin->msg( 'currentrev' )->escaped()
448            );
449        }
450
451        $userHasNewMessages = MediaWikiServices::getInstance()
452            ->getTalkPageNotificationManager()->userHasNewMessages( $user );
453        # do not show "You have new messages" text when we are viewing our
454        # own talk page
455        if ( $userHasNewMessages && !$title->equals( $user->getTalkPage() ) ) {
456            $tl = Linker::linkKnown(
457                $user->getTalkPage(),
458                $skin->msg( 'nostalgia-newmessageslink' )->escaped(),
459                [],
460                [ 'redirect' => 'no' ]
461            );
462
463            $dl = Linker::linkKnown(
464                $user->getTalkPage(),
465                $skin->msg( 'nostalgia-newmessagesdifflink' )->escaped(),
466                [],
467                [ 'diff' => 'cur' ]
468            );
469            $s[] = '<strong>' . $skin->msg( 'new-messages' )
470                ->rawParams( $tl, $dl )->escaped() . '</strong>';
471            # disable caching
472            $out->setCdnMaxage( 0 );
473            $out->disableClientCache();
474        }
475
476        $undelete = $skin->getUndeleteLink();
477
478        if ( $undelete !== '' ) {
479            $s[] = $undelete;
480        }
481
482        return $lang->pipeList( $s );
483    }
484
485    /**
486     * Gets the h1 element with the page title.
487     * @return string
488     */
489    private function pageTitle() {
490        return '<h1 class="pagetitle">' .
491            $this->getSkin()->getOutput()->getPageTitle() .
492            '</h1>';
493    }
494
495    /**
496     * @return string
497     */
498    private function printableLink() {
499        $skin = $this->getSkin();
500        $out = $skin->getOutput();
501        $lang = $skin->getLanguage();
502        $request = $skin->getRequest();
503
504        $s = [];
505
506        if ( !$out->isPrintable() ) {
507            $printurl = htmlspecialchars( $skin->getTitle()->getLocalURL(
508                $request->appendQueryValue( 'printable', 'yes' ) ) );
509            $s[] = "<a href=\"$printurl\" rel=\"alternate\">"
510                . $skin->msg( 'printableversion' )->escaped() . '</a>';
511        }
512
513        if ( $out->isSyndicated() ) {
514            foreach ( $out->getSyndicationLinks() as $format => $link ) {
515                $feedUrl = htmlspecialchars( $link );
516                $s[] = "<a href=\"$feedUrl\" rel=\"alternate\" type=\"application/{$format}+xml\""
517                        . " class=\"feedlink\">" . $skin->msg( "feed-$format" )->escaped() . "</a>";
518            }
519        }
520        return $lang->pipeList( $s );
521    }
522
523    /**
524     * @return string
525     */
526    private function editThisPage() {
527        $skin = $this->getSkin();
528        if ( !$skin->getOutput()->isArticleRelated() ) {
529            $s = $skin->msg( 'protectedpage' )->escaped();
530        } else {
531            $title = $skin->getTitle();
532            $user = $skin->getUser();
533            $permManager = MediaWikiServices::getInstance()->getPermissionManager();
534            if ( $permManager->quickUserCan( 'edit', $user, $title ) && $title->exists() ) {
535                $t = $skin->msg( 'nostalgia-editthispage' )->escaped();
536            } elseif ( $permManager->quickUserCan( 'create', $user, $title ) && !$title->exists() ) {
537                $t = $skin->msg( 'nostalgia-create-this-page' )->escaped();
538            } else {
539                $t = $skin->msg( 'viewsource' )->escaped();
540            }
541
542            $s = Linker::linkKnown(
543                $title,
544                $t,
545                [],
546                $skin->editUrlOptions()
547            );
548        }
549
550        return $s;
551    }
552
553    /**
554     * @return string
555     */
556    private function deleteThisPage() {
557        $skin = $this->getSkin();
558        $diff = $skin->getRequest()->getVal( 'diff' );
559        $title = $skin->getTitle();
560
561        if ( $title->getArticleID() && ( !$diff ) &&
562            $skin->getUser()->isAllowed( 'delete' ) ) {
563            $t = $skin->msg( 'nostalgia-deletethispage' )->escaped();
564
565            $s = Linker::linkKnown(
566                $title,
567                $t,
568                [],
569                [ 'action' => 'delete' ]
570            );
571        } else {
572            $s = '';
573        }
574
575        return $s;
576    }
577
578    /**
579     * @return string
580     */
581    private function protectThisPage() {
582        $skin = $this->getSkin();
583        $diff = $skin->getRequest()->getVal( 'diff' );
584        $title = $skin->getTitle();
585        $restrictionStore = MediaWikiServices::getInstance()->getRestrictionStore();
586
587        if ( $title->getArticleID() && ( !$diff ) &&
588            $skin->getUser()->isAllowed( 'protect' ) &&
589            $restrictionStore->listApplicableRestrictionTypes( $title )
590        ) {
591            if ( $restrictionStore->isProtected( $title ) ) {
592                $text = $skin->msg( 'nostalgia-unprotectthispage' )->escaped();
593                $query = [ 'action' => 'unprotect' ];
594            } else {
595                $text = $skin->msg( 'nostalgia-protectthispage' )->escaped();
596                $query = [ 'action' => 'protect' ];
597            }
598
599            $s = Linker::linkKnown(
600                $title,
601                $text,
602                [],
603                $query
604            );
605        } else {
606            $s = '';
607        }
608
609        return $s;
610    }
611
612    /**
613     * @return string
614     */
615    private function watchThisPage() {
616        ++$this->mWatchLinkNum;
617
618        // Cache
619        $skin = $this->getSkin();
620        $title = $skin->getTitle();
621
622        if ( $skin->getOutput()->isArticleRelated() ) {
623            if ( MediaWikiServices::getInstance()->getWatchlistManager()->isWatched( $skin->getUser(), $title ) ) {
624                $text = $skin->msg( 'unwatchthispage' )->escaped();
625                $query = [
626                    'action' => 'unwatch',
627                ];
628                $id = 'mw-unwatch-link' . $this->mWatchLinkNum;
629            } else {
630                $text = $skin->msg( 'watchthispage' )->escaped();
631                $query = [
632                    'action' => 'watch',
633                ];
634                $id = 'mw-watch-link' . $this->mWatchLinkNum;
635            }
636
637            $s = Linker::linkKnown(
638                $title,
639                $text,
640                [
641                    'id' => $id,
642                    'class' => 'mw-watchlink',
643                ],
644                $query
645            );
646        } else {
647            $s = $skin->msg( 'notanarticle' )->escaped();
648        }
649
650        return $s;
651    }
652
653    /**
654     * @return string
655     */
656    private function moveThisPage() {
657        $skin = $this->getSkin();
658        $title = $skin->getTitle();
659        $permManager = MediaWikiServices::getInstance()->getPermissionManager();
660        $user = $skin->getUser();
661
662        if ( $permManager->quickUserCan( 'move', $user, $title ) ) {
663            return Linker::linkKnown(
664                SpecialPage::getTitleFor( 'Movepage' ),
665                $skin->msg( 'movethispage' )->escaped(),
666                [],
667                [ 'target' => $title->getPrefixedDBkey() ]
668            );
669        }
670
671        // no message if page is protected - would be redundant
672        return '';
673    }
674
675    /**
676     * @return string
677     */
678    private function historyLink() {
679        $skin = $this->getSkin();
680        return Linker::link(
681            $skin->getTitle(),
682            $skin->msg( 'history' )->escaped(),
683            [ 'rel' => 'archives' ],
684            [ 'action' => 'history' ]
685        );
686    }
687
688    /**
689     * @return string
690     */
691    private function whatLinksHere() {
692        $skin = $this->getSkin();
693        return Linker::linkKnown(
694            SpecialPage::getTitleFor( 'Whatlinkshere', $skin->getTitle()->getPrefixedDBkey() ),
695            $skin->msg( 'whatlinkshere' )->escaped()
696        );
697    }
698
699    /**
700     * @return string
701     */
702    private function userContribsLink() {
703        $skin = $this->getSkin();
704        return Linker::linkKnown(
705            SpecialPage::getTitleFor( 'Contributions', $skin->getTitle()->getDBkey() ),
706            $skin->msg( 'contributions' )->escaped()
707        );
708    }
709
710    /**
711     * @return string
712     */
713    private function emailUserLink() {
714        $skin = $this->getSkin();
715        return Linker::linkKnown(
716            SpecialPage::getTitleFor( 'Emailuser', $skin->getTitle()->getDBkey() ),
717            $skin->msg( 'emailuser' )->escaped()
718        );
719    }
720
721    /**
722     * @return string
723     */
724    private function watchPageLinksLink() {
725        $skin = $this->getSkin();
726        if ( !$skin->getOutput()->isArticleRelated() ) {
727            return $skin->msg( 'parentheses', $skin->msg( 'notanarticle' )->text() )->escaped();
728        }
729
730        return Linker::linkKnown(
731            SpecialPage::getTitleFor( 'Recentchangeslinked',
732                $skin->getTitle()->getPrefixedDBkey() ),
733            $skin->msg( 'recentchangeslinked-toolbox' )->escaped()
734        );
735    }
736
737    /**
738     * @return string
739     */
740    private function talkLink() {
741        $skin = $this->getSkin();
742        $title = $skin->getTitle();
743        if ( $title->isSpecialPage() ) {
744            # No discussion links for special pages
745            return '';
746        }
747
748        $linkOptions = [];
749
750        if ( $title->isTalkPage() ) {
751            $link = $title->getSubjectPage();
752            switch ( $link->getNamespace() ) {
753                case NS_MAIN:
754                    $text = $skin->msg( 'nostalgia-articlepage' );
755                    break;
756                case NS_USER:
757                    $text = $skin->msg( 'nostalgia-userpage' );
758                    break;
759                case NS_PROJECT:
760                    $text = $skin->msg( 'nostalgia-projectpage' );
761                    break;
762                case NS_FILE:
763                    $text = $skin->msg( 'imagepage' );
764                    # Make link known if image exists, even if the desc. page doesn't.
765                    if ( MediaWikiServices::getInstance()->getRepoGroup()->findFile( $link ) ) {
766                        $linkOptions[] = 'known';
767                    }
768                    break;
769                case NS_MEDIAWIKI:
770                    $text = $skin->msg( 'mediawikipage' );
771                    break;
772                case NS_TEMPLATE:
773                    $text = $skin->msg( 'templatepage' );
774                    break;
775                case NS_HELP:
776                    $text = $skin->msg( 'viewhelppage' );
777                    break;
778                case NS_CATEGORY:
779                    $text = $skin->msg( 'categorypage' );
780                    break;
781                default:
782                    $text = $skin->msg( 'nostalgia-articlepage' );
783            }
784        } else {
785            $link = $title->getTalkPage();
786            $text = $skin->msg( 'nostalgia-talkpage' );
787        }
788
789        return Linker::link( $link, $text->escaped(), [], [], $linkOptions );
790    }
791
792    /**
793     * @return string
794     */
795    private function getUploadLink() {
796        global $wgUploadNavigationUrl;
797
798        if ( $wgUploadNavigationUrl ) {
799            # Using an empty class attribute to avoid automatic setting of "external" class
800            return Linker::makeExternalLink( $wgUploadNavigationUrl,
801                $this->getSkin()->msg( 'upload' )->text(),
802                true, '', [ 'class' => '' ] );
803        }
804
805        return Linker::linkKnown(
806            SpecialPage::getTitleFor( 'Upload' ),
807            $this->getSkin()->msg( 'upload' )->escaped()
808        );
809    }
810}