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 SkinTemplate 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 SkinTemplate, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 35 | class SkinTemplate extends Skin { | 
            ||
| 36 | /**  | 
            ||
| 37 | * @var string Name of our skin, it probably needs to be all lower case.  | 
            ||
| 38 | * Child classes should override the default.  | 
            ||
| 39 | */  | 
            ||
| 40 | public $skinname = 'monobook';  | 
            ||
| 41 | |||
| 42 | /**  | 
            ||
| 43 | * @var string For QuickTemplate, the name of the subclass which will  | 
            ||
| 44 | * actually fill the template. Child classes should override the default.  | 
            ||
| 45 | */  | 
            ||
| 46 | public $template = 'QuickTemplate';  | 
            ||
| 47 | |||
| 48 | public $thispage;  | 
            ||
| 49 | public $titletxt;  | 
            ||
| 50 | public $userpage;  | 
            ||
| 51 | public $thisquery;  | 
            ||
| 52 | public $loggedin;  | 
            ||
| 53 | public $username;  | 
            ||
| 54 | public $userpageUrlDetails;  | 
            ||
| 55 | |||
| 56 | /**  | 
            ||
| 57 | * Add specific styles for this skin  | 
            ||
| 58 | *  | 
            ||
| 59 | * @param OutputPage $out  | 
            ||
| 60 | */  | 
            ||
| 61 | 	function setupSkinUserCss( OutputPage $out ) { | 
            ||
| 62 | $moduleStyles = [  | 
            ||
| 63 | 'mediawiki.legacy.shared',  | 
            ||
| 64 | 'mediawiki.legacy.commonPrint',  | 
            ||
| 65 | 'mediawiki.sectionAnchor'  | 
            ||
| 66 | ];  | 
            ||
| 67 | 		if ( $out->isSyndicated() ) { | 
            ||
| 68 | $moduleStyles[] = 'mediawiki.feedlink';  | 
            ||
| 69 | }  | 
            ||
| 70 | |||
| 71 | // Deprecated since 1.26: Unconditional loading of mediawiki.ui.button  | 
            ||
| 72 | // on every page is deprecated. Express a dependency instead.  | 
            ||
| 73 | 		if ( strpos( $out->getHTML(), 'mw-ui-button' ) !== false ) { | 
            ||
| 74 | $moduleStyles[] = 'mediawiki.ui.button';  | 
            ||
| 75 | }  | 
            ||
| 76 | |||
| 77 | $out->addModuleStyles( $moduleStyles );  | 
            ||
| 78 | }  | 
            ||
| 79 | |||
| 80 | /**  | 
            ||
| 81 | * Create the template engine object; we feed it a bunch of data  | 
            ||
| 82 | * and eventually it spits out some HTML. Should have interface  | 
            ||
| 83 | * roughly equivalent to PHPTAL 0.7.  | 
            ||
| 84 | *  | 
            ||
| 85 | * @param string $classname  | 
            ||
| 86 | * @param bool|string $repository Subdirectory where we keep template files  | 
            ||
| 87 | * @param bool|string $cache_dir  | 
            ||
| 88 | * @return QuickTemplate  | 
            ||
| 89 | * @private  | 
            ||
| 90 | */  | 
            ||
| 91 | 	function setupTemplate( $classname, $repository = false, $cache_dir = false ) { | 
            ||
| 92 | return new $classname( $this->getConfig() );  | 
            ||
| 93 | }  | 
            ||
| 94 | |||
| 95 | /**  | 
            ||
| 96 | * Generates array of language links for the current page  | 
            ||
| 97 | *  | 
            ||
| 98 | * @return array  | 
            ||
| 99 | */  | 
            ||
| 100 | 	public function getLanguages() { | 
            ||
| 101 | global $wgHideInterlanguageLinks;  | 
            ||
| 102 | 		if ( $wgHideInterlanguageLinks ) { | 
            ||
| 103 | return [];  | 
            ||
| 104 | }  | 
            ||
| 105 | |||
| 106 | $userLang = $this->getLanguage();  | 
            ||
| 107 | $languageLinks = [];  | 
            ||
| 108 | |||
| 109 | 		foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) { | 
            ||
| 110 | $class = 'interlanguage-link interwiki-' . explode( ':', $languageLinkText, 2 )[0];  | 
            ||
| 111 | |||
| 112 | $languageLinkTitle = Title::newFromText( $languageLinkText );  | 
            ||
| 113 | 			if ( $languageLinkTitle ) { | 
            ||
| 114 | $ilInterwikiCode = $languageLinkTitle->getInterwiki();  | 
            ||
| 115 | $ilLangName = Language::fetchLanguageName( $ilInterwikiCode );  | 
            ||
| 116 | |||
| 117 | 				if ( strval( $ilLangName ) === '' ) { | 
            ||
| 118 | $ilDisplayTextMsg = wfMessage( "interlanguage-link-$ilInterwikiCode" );  | 
            ||
| 119 | 					if ( !$ilDisplayTextMsg->isDisabled() ) { | 
            ||
| 120 | // Use custom MW message for the display text  | 
            ||
| 121 | $ilLangName = $ilDisplayTextMsg->text();  | 
            ||
| 122 | 					} else { | 
            ||
| 123 | // Last resort: fallback to the language link target  | 
            ||
| 124 | $ilLangName = $languageLinkText;  | 
            ||
| 125 | }  | 
            ||
| 126 | 				} else { | 
            ||
| 127 | // Use the language autonym as display text  | 
            ||
| 128 | $ilLangName = $this->formatLanguageName( $ilLangName );  | 
            ||
| 129 | }  | 
            ||
| 130 | |||
| 131 | // CLDR extension or similar is required to localize the language name;  | 
            ||
| 132 | // otherwise we'll end up with the autonym again.  | 
            ||
| 133 | $ilLangLocalName = Language::fetchLanguageName(  | 
            ||
| 134 | $ilInterwikiCode,  | 
            ||
| 135 | $userLang->getCode()  | 
            ||
| 136 | );  | 
            ||
| 137 | |||
| 138 | $languageLinkTitleText = $languageLinkTitle->getText();  | 
            ||
| 139 | 				if ( $ilLangLocalName === '' ) { | 
            ||
| 140 | $ilFriendlySiteName = wfMessage( "interlanguage-link-sitename-$ilInterwikiCode" );  | 
            ||
| 141 | 					if ( !$ilFriendlySiteName->isDisabled() ) { | 
            ||
| 142 | 						if ( $languageLinkTitleText === '' ) { | 
            ||
| 143 | $ilTitle = wfMessage(  | 
            ||
| 144 | 'interlanguage-link-title-nonlangonly',  | 
            ||
| 145 | $ilFriendlySiteName->text()  | 
            ||
| 146 | )->text();  | 
            ||
| 147 | 						} else { | 
            ||
| 148 | $ilTitle = wfMessage(  | 
            ||
| 149 | 'interlanguage-link-title-nonlang',  | 
            ||
| 150 | $languageLinkTitleText,  | 
            ||
| 151 | $ilFriendlySiteName->text()  | 
            ||
| 152 | )->text();  | 
            ||
| 153 | }  | 
            ||
| 154 | 					} else { | 
            ||
| 155 | // we have nothing friendly to put in the title, so fall back to  | 
            ||
| 156 | // displaying the interlanguage link itself in the title text  | 
            ||
| 157 | // (similar to what is done in page content)  | 
            ||
| 158 | $ilTitle = $languageLinkTitle->getInterwiki() .  | 
            ||
| 159 | ":$languageLinkTitleText";  | 
            ||
| 160 | }  | 
            ||
| 161 | 				} elseif ( $languageLinkTitleText === '' ) { | 
            ||
| 162 | $ilTitle = wfMessage(  | 
            ||
| 163 | 'interlanguage-link-title-langonly',  | 
            ||
| 164 | $ilLangLocalName  | 
            ||
| 165 | )->text();  | 
            ||
| 166 | 				} else { | 
            ||
| 167 | $ilTitle = wfMessage(  | 
            ||
| 168 | 'interlanguage-link-title',  | 
            ||
| 169 | $languageLinkTitleText,  | 
            ||
| 170 | $ilLangLocalName  | 
            ||
| 171 | )->text();  | 
            ||
| 172 | }  | 
            ||
| 173 | |||
| 174 | $ilInterwikiCodeBCP47 = wfBCP47( $ilInterwikiCode );  | 
            ||
| 175 | $languageLink = [  | 
            ||
| 176 | 'href' => $languageLinkTitle->getFullURL(),  | 
            ||
| 177 | 'text' => $ilLangName,  | 
            ||
| 178 | 'title' => $ilTitle,  | 
            ||
| 179 | 'class' => $class,  | 
            ||
| 180 | 'lang' => $ilInterwikiCodeBCP47,  | 
            ||
| 181 | 'hreflang' => $ilInterwikiCodeBCP47,  | 
            ||
| 182 | ];  | 
            ||
| 183 | Hooks::run(  | 
            ||
| 184 | 'SkinTemplateGetLanguageLink',  | 
            ||
| 185 | [ &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() ]  | 
            ||
| 186 | );  | 
            ||
| 187 | $languageLinks[] = $languageLink;  | 
            ||
| 188 | }  | 
            ||
| 189 | }  | 
            ||
| 190 | |||
| 191 | return $languageLinks;  | 
            ||
| 192 | }  | 
            ||
| 193 | |||
| 194 | 	protected function setupTemplateForOutput() { | 
            ||
| 195 | |||
| 196 | $request = $this->getRequest();  | 
            ||
| 197 | $user = $this->getUser();  | 
            ||
| 198 | $title = $this->getTitle();  | 
            ||
| 199 | |||
| 200 | $tpl = $this->setupTemplate( $this->template, 'skins' );  | 
            ||
| 201 | |||
| 202 | $this->thispage = $title->getPrefixedDBkey();  | 
            ||
| 203 | $this->titletxt = $title->getPrefixedText();  | 
            ||
| 204 | $this->userpage = $user->getUserPage()->getPrefixedText();  | 
            ||
| 205 | $query = [];  | 
            ||
| 206 | View Code Duplication | 		if ( !$request->wasPosted() ) { | 
            |
| 207 | $query = $request->getValues();  | 
            ||
| 208 | unset( $query['title'] );  | 
            ||
| 209 | unset( $query['returnto'] );  | 
            ||
| 210 | unset( $query['returntoquery'] );  | 
            ||
| 211 | }  | 
            ||
| 212 | $this->thisquery = wfArrayToCgi( $query );  | 
            ||
| 213 | $this->loggedin = $user->isLoggedIn();  | 
            ||
| 214 | $this->username = $user->getName();  | 
            ||
| 215 | |||
| 216 | 		if ( $this->loggedin ) { | 
            ||
| 217 | $this->userpageUrlDetails = self::makeUrlDetails( $this->userpage );  | 
            ||
| 218 | 		} else { | 
            ||
| 219 | # This won't be used in the standard skins, but we define it to preserve the interface  | 
            ||
| 220 | # To save time, we check for existence  | 
            ||
| 221 | $this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage );  | 
            ||
| 222 | }  | 
            ||
| 223 | |||
| 224 | return $tpl;  | 
            ||
| 225 | }  | 
            ||
| 226 | |||
| 227 | /**  | 
            ||
| 228 | * initialize various variables and generate the template  | 
            ||
| 229 | *  | 
            ||
| 230 | * @param OutputPage $out  | 
            ||
| 231 | */  | 
            ||
| 232 | 	function outputPage( OutputPage $out = null ) { | 
            ||
| 233 | Profiler::instance()->setTemplated( true );  | 
            ||
| 234 | |||
| 235 | $oldContext = null;  | 
            ||
| 236 | 		if ( $out !== null ) { | 
            ||
| 237 | // Deprecated since 1.20, note added in 1.25  | 
            ||
| 238 | wfDeprecated( __METHOD__, '1.25' );  | 
            ||
| 239 | $oldContext = $this->getContext();  | 
            ||
| 240 | $this->setContext( $out->getContext() );  | 
            ||
| 241 | }  | 
            ||
| 242 | |||
| 243 | $out = $this->getOutput();  | 
            ||
| 244 | |||
| 245 | $this->initPage( $out );  | 
            ||
| 246 | $tpl = $this->prepareQuickTemplate( $out );  | 
            ||
| 247 | // execute template  | 
            ||
| 248 | $res = $tpl->execute();  | 
            ||
| 249 | |||
| 250 | // result may be an error  | 
            ||
| 251 | $this->printOrError( $res );  | 
            ||
| 252 | |||
| 253 | 		if ( $oldContext ) { | 
            ||
| 254 | $this->setContext( $oldContext );  | 
            ||
| 255 | }  | 
            ||
| 256 | |||
| 257 | }  | 
            ||
| 258 | |||
| 259 | /**  | 
            ||
| 260 | * Wrap the body text with language information and identifiable element  | 
            ||
| 261 | *  | 
            ||
| 262 | * @param Title $title  | 
            ||
| 263 | * @param string $html body text  | 
            ||
| 264 | * @return string html  | 
            ||
| 265 | */  | 
            ||
| 266 | 	protected function wrapHTML( $title, $html ) { | 
            ||
| 267 | # An ID that includes the actual body text; without categories, contentSub, ...  | 
            ||
| 268 | $realBodyAttribs = [ 'id' => 'mw-content-text' ];  | 
            ||
| 269 | |||
| 270 | # Add a mw-content-ltr/rtl class to be able to style based on text direction  | 
            ||
| 271 | # when the content is different from the UI language, i.e.:  | 
            ||
| 272 | # not for special pages or file pages AND only when viewing  | 
            ||
| 273 | if ( !in_array( $title->getNamespace(), [ NS_SPECIAL, NS_FILE ] ) &&  | 
            ||
| 274 | 			Action::getActionName( $this ) === 'view' ) { | 
            ||
| 275 | $pageLang = $title->getPageViewLanguage();  | 
            ||
| 276 | $realBodyAttribs['lang'] = $pageLang->getHtmlCode();  | 
            ||
| 277 | $realBodyAttribs['dir'] = $pageLang->getDir();  | 
            ||
| 278 | $realBodyAttribs['class'] = 'mw-content-' . $pageLang->getDir();  | 
            ||
| 279 | }  | 
            ||
| 280 | |||
| 281 | return Html::rawElement( 'div', $realBodyAttribs, $html );  | 
            ||
| 282 | }  | 
            ||
| 283 | |||
| 284 | /**  | 
            ||
| 285 | * initialize various variables and generate the template  | 
            ||
| 286 | *  | 
            ||
| 287 | * @since 1.23  | 
            ||
| 288 | * @return QuickTemplate The template to be executed by outputPage  | 
            ||
| 289 | */  | 
            ||
| 290 | 	protected function prepareQuickTemplate() { | 
            ||
| 291 | global $wgContLang, $wgScript, $wgStylePath, $wgMimeType, $wgJsMimeType,  | 
            ||
| 292 | $wgSitename, $wgLogo, $wgMaxCredits,  | 
            ||
| 293 | $wgShowCreditsIfMax, $wgArticlePath,  | 
            ||
| 294 | $wgScriptPath, $wgServer;  | 
            ||
| 295 | |||
| 296 | $title = $this->getTitle();  | 
            ||
| 297 | $request = $this->getRequest();  | 
            ||
| 298 | $out = $this->getOutput();  | 
            ||
| 299 | $tpl = $this->setupTemplateForOutput();  | 
            ||
| 300 | |||
| 301 | $tpl->set( 'title', $out->getPageTitle() );  | 
            ||
| 302 | $tpl->set( 'pagetitle', $out->getHTMLTitle() );  | 
            ||
| 303 | $tpl->set( 'displaytitle', $out->mPageLinkTitle );  | 
            ||
| 304 | |||
| 305 | $tpl->setRef( 'thispage', $this->thispage );  | 
            ||
| 306 | $tpl->setRef( 'titleprefixeddbkey', $this->thispage );  | 
            ||
| 307 | $tpl->set( 'titletext', $title->getText() );  | 
            ||
| 308 | $tpl->set( 'articleid', $title->getArticleID() );  | 
            ||
| 309 | |||
| 310 | $tpl->set( 'isarticle', $out->isArticle() );  | 
            ||
| 311 | |||
| 312 | $subpagestr = $this->subPageSubtitle();  | 
            ||
| 313 | 		if ( $subpagestr !== '' ) { | 
            ||
| 314 | $subpagestr = '<span class="subpages">' . $subpagestr . '</span>';  | 
            ||
| 315 | }  | 
            ||
| 316 | $tpl->set( 'subtitle', $subpagestr . $out->getSubtitle() );  | 
            ||
| 317 | |||
| 318 | $undelete = $this->getUndeleteLink();  | 
            ||
| 319 | 		if ( $undelete === '' ) { | 
            ||
| 320 | $tpl->set( 'undelete', '' );  | 
            ||
| 321 | 		} else { | 
            ||
| 322 | $tpl->set( 'undelete', '<span class="subpages">' . $undelete . '</span>' );  | 
            ||
| 323 | }  | 
            ||
| 324 | |||
| 325 | $tpl->set( 'catlinks', $this->getCategories() );  | 
            ||
| 326 | 		if ( $out->isSyndicated() ) { | 
            ||
| 327 | $feeds = [];  | 
            ||
| 328 | 			foreach ( $out->getSyndicationLinks() as $format => $link ) { | 
            ||
| 329 | $feeds[$format] = [  | 
            ||
| 330 | // Messages: feed-atom, feed-rss  | 
            ||
| 331 | 'text' => $this->msg( "feed-$format" )->text(),  | 
            ||
| 332 | 'href' => $link  | 
            ||
| 333 | ];  | 
            ||
| 334 | }  | 
            ||
| 335 | $tpl->setRef( 'feeds', $feeds );  | 
            ||
| 336 | 		} else { | 
            ||
| 337 | $tpl->set( 'feeds', false );  | 
            ||
| 338 | }  | 
            ||
| 339 | |||
| 340 | $tpl->setRef( 'mimetype', $wgMimeType );  | 
            ||
| 341 | $tpl->setRef( 'jsmimetype', $wgJsMimeType );  | 
            ||
| 342 | $tpl->set( 'charset', 'UTF-8' );  | 
            ||
| 343 | $tpl->setRef( 'wgScript', $wgScript );  | 
            ||
| 344 | $tpl->setRef( 'skinname', $this->skinname );  | 
            ||
| 345 | $tpl->set( 'skinclass', get_class( $this ) );  | 
            ||
| 346 | $tpl->setRef( 'skin', $this );  | 
            ||
| 347 | $tpl->setRef( 'stylename', $this->stylename );  | 
            ||
| 348 | $tpl->set( 'printable', $out->isPrintable() );  | 
            ||
| 349 | $tpl->set( 'handheld', $request->getBool( 'handheld' ) );  | 
            ||
| 350 | $tpl->setRef( 'loggedin', $this->loggedin );  | 
            ||
| 351 | $tpl->set( 'notspecialpage', !$title->isSpecialPage() );  | 
            ||
| 352 | $tpl->set( 'searchaction', $this->escapeSearchLink() );  | 
            ||
| 353 | $tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBkey() );  | 
            ||
| 354 | $tpl->set( 'search', trim( $request->getVal( 'search' ) ) );  | 
            ||
| 355 | $tpl->setRef( 'stylepath', $wgStylePath );  | 
            ||
| 356 | $tpl->setRef( 'articlepath', $wgArticlePath );  | 
            ||
| 357 | $tpl->setRef( 'scriptpath', $wgScriptPath );  | 
            ||
| 358 | $tpl->setRef( 'serverurl', $wgServer );  | 
            ||
| 359 | $tpl->setRef( 'logopath', $wgLogo );  | 
            ||
| 360 | $tpl->setRef( 'sitename', $wgSitename );  | 
            ||
| 361 | |||
| 362 | $userLang = $this->getLanguage();  | 
            ||
| 363 | $userLangCode = $userLang->getHtmlCode();  | 
            ||
| 364 | $userLangDir = $userLang->getDir();  | 
            ||
| 365 | |||
| 366 | $tpl->set( 'lang', $userLangCode );  | 
            ||
| 367 | $tpl->set( 'dir', $userLangDir );  | 
            ||
| 368 | $tpl->set( 'rtl', $userLang->isRTL() );  | 
            ||
| 369 | |||
| 370 | $tpl->set( 'capitalizeallnouns', $userLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );  | 
            ||
| 371 | $tpl->set( 'showjumplinks', true ); // showjumplinks preference has been removed  | 
            ||
| 372 | $tpl->set( 'username', $this->loggedin ? $this->username : null );  | 
            ||
| 373 | $tpl->setRef( 'userpage', $this->userpage );  | 
            ||
| 374 | $tpl->setRef( 'userpageurl', $this->userpageUrlDetails['href'] );  | 
            ||
| 375 | $tpl->set( 'userlang', $userLangCode );  | 
            ||
| 376 | |||
| 377 | // Users can have their language set differently than the  | 
            ||
| 378 | // content of the wiki. For these users, tell the web browser  | 
            ||
| 379 | // that interface elements are in a different language.  | 
            ||
| 380 | $tpl->set( 'userlangattributes', '' );  | 
            ||
| 381 | $tpl->set( 'specialpageattributes', '' ); # obsolete  | 
            ||
| 382 | // Used by VectorBeta to insert HTML before content but after the  | 
            ||
| 383 | // heading for the page title. Defaults to empty string.  | 
            ||
| 384 | $tpl->set( 'prebodyhtml', '' );  | 
            ||
| 385 | |||
| 386 | 		if ( $userLangCode !== $wgContLang->getHtmlCode() || $userLangDir !== $wgContLang->getDir() ) { | 
            ||
| 387 | $escUserlang = htmlspecialchars( $userLangCode );  | 
            ||
| 388 | $escUserdir = htmlspecialchars( $userLangDir );  | 
            ||
| 389 | // Attributes must be in double quotes because htmlspecialchars() doesn't  | 
            ||
| 390 | // escape single quotes  | 
            ||
| 391 | $attrs = " lang=\"$escUserlang\" dir=\"$escUserdir\"";  | 
            ||
| 392 | $tpl->set( 'userlangattributes', $attrs );  | 
            ||
| 393 | }  | 
            ||
| 394 | |||
| 395 | $tpl->set( 'newtalk', $this->getNewtalks() );  | 
            ||
| 396 | $tpl->set( 'logo', $this->logoText() );  | 
            ||
| 397 | |||
| 398 | $tpl->set( 'copyright', false );  | 
            ||
| 399 | // No longer used  | 
            ||
| 400 | $tpl->set( 'viewcount', false );  | 
            ||
| 401 | $tpl->set( 'lastmod', false );  | 
            ||
| 402 | $tpl->set( 'credits', false );  | 
            ||
| 403 | $tpl->set( 'numberofwatchingusers', false );  | 
            ||
| 404 | 		if ( $out->isArticle() && $title->exists() ) { | 
            ||
| 405 | 			if ( $this->isRevisionCurrent() ) { | 
            ||
| 406 | 				if ( $wgMaxCredits != 0 ) { | 
            ||
| 407 | $tpl->set( 'credits', Action::factory( 'credits', $this->getWikiPage(),  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 408 | $this->getContext() )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) );  | 
            ||
| 409 | 				} else { | 
            ||
| 410 | $tpl->set( 'lastmod', $this->lastModified() );  | 
            ||
| 411 | }  | 
            ||
| 412 | }  | 
            ||
| 413 | $tpl->set( 'copyright', $this->getCopyright() );  | 
            ||
| 414 | }  | 
            ||
| 415 | |||
| 416 | $tpl->set( 'copyrightico', $this->getCopyrightIcon() );  | 
            ||
| 417 | $tpl->set( 'poweredbyico', $this->getPoweredBy() );  | 
            ||
| 418 | $tpl->set( 'disclaimer', $this->disclaimerLink() );  | 
            ||
| 419 | $tpl->set( 'privacy', $this->privacyLink() );  | 
            ||
| 420 | $tpl->set( 'about', $this->aboutLink() );  | 
            ||
| 421 | |||
| 422 | $tpl->set( 'footerlinks', [  | 
            ||
| 423 | 'info' => [  | 
            ||
| 424 | 'lastmod',  | 
            ||
| 425 | 'numberofwatchingusers',  | 
            ||
| 426 | 'credits',  | 
            ||
| 427 | 'copyright',  | 
            ||
| 428 | ],  | 
            ||
| 429 | 'places' => [  | 
            ||
| 430 | 'privacy',  | 
            ||
| 431 | 'about',  | 
            ||
| 432 | 'disclaimer',  | 
            ||
| 433 | ],  | 
            ||
| 434 | ] );  | 
            ||
| 435 | |||
| 436 | global $wgFooterIcons;  | 
            ||
| 437 | $tpl->set( 'footericons', $wgFooterIcons );  | 
            ||
| 438 | 		foreach ( $tpl->data['footericons'] as $footerIconsKey => &$footerIconsBlock ) { | 
            ||
| 439 | 			if ( count( $footerIconsBlock ) > 0 ) { | 
            ||
| 440 | 				foreach ( $footerIconsBlock as &$footerIcon ) { | 
            ||
| 441 | 					if ( isset( $footerIcon['src'] ) ) { | 
            ||
| 442 | 						if ( !isset( $footerIcon['width'] ) ) { | 
            ||
| 443 | $footerIcon['width'] = 88;  | 
            ||
| 444 | }  | 
            ||
| 445 | 						if ( !isset( $footerIcon['height'] ) ) { | 
            ||
| 446 | $footerIcon['height'] = 31;  | 
            ||
| 447 | }  | 
            ||
| 448 | }  | 
            ||
| 449 | }  | 
            ||
| 450 | 			} else { | 
            ||
| 451 | unset( $tpl->data['footericons'][$footerIconsKey] );  | 
            ||
| 452 | }  | 
            ||
| 453 | }  | 
            ||
| 454 | |||
| 455 | $tpl->set( 'indicators', $out->getIndicators() );  | 
            ||
| 456 | |||
| 457 | $tpl->set( 'sitenotice', $this->getSiteNotice() );  | 
            ||
| 458 | $tpl->set( 'bottomscripts', $this->bottomScripts() );  | 
            ||
| 459 | $tpl->set( 'printfooter', $this->printSource() );  | 
            ||
| 460 | // Wrap the bodyText with #mw-content-text element  | 
            ||
| 461 | $out->mBodytext = $this->wrapHTML( $title, $out->mBodytext );  | 
            ||
| 462 | $tpl->setRef( 'bodytext', $out->mBodytext );  | 
            ||
| 463 | |||
| 464 | $language_urls = $this->getLanguages();  | 
            ||
| 465 | 		if ( count( $language_urls ) ) { | 
            ||
| 466 | $tpl->setRef( 'language_urls', $language_urls );  | 
            ||
| 467 | 		} else { | 
            ||
| 468 | $tpl->set( 'language_urls', false );  | 
            ||
| 469 | }  | 
            ||
| 470 | |||
| 471 | # Personal toolbar  | 
            ||
| 472 | $tpl->set( 'personal_urls', $this->buildPersonalUrls() );  | 
            ||
| 473 | $content_navigation = $this->buildContentNavigationUrls();  | 
            ||
| 474 | $content_actions = $this->buildContentActionUrls( $content_navigation );  | 
            ||
| 475 | $tpl->setRef( 'content_navigation', $content_navigation );  | 
            ||
| 476 | $tpl->setRef( 'content_actions', $content_actions );  | 
            ||
| 477 | |||
| 478 | $tpl->set( 'sidebar', $this->buildSidebar() );  | 
            ||
| 479 | $tpl->set( 'nav_urls', $this->buildNavUrls() );  | 
            ||
| 480 | |||
| 481 | // Set the head scripts near the end, in case the above actions resulted in added scripts  | 
            ||
| 482 | $tpl->set( 'headelement', $out->headElement( $this ) );  | 
            ||
| 483 | |||
| 484 | $tpl->set( 'debug', '' );  | 
            ||
| 485 | $tpl->set( 'debughtml', $this->generateDebugHTML() );  | 
            ||
| 486 | $tpl->set( 'reporttime', wfReportTime() );  | 
            ||
| 487 | |||
| 488 | // original version by hansm  | 
            ||
| 489 | 		if ( !Hooks::run( 'SkinTemplateOutputPageBeforeExec', [ &$this, &$tpl ] ) ) { | 
            ||
| 490 | wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" );  | 
            ||
| 491 | }  | 
            ||
| 492 | |||
| 493 | // Set the bodytext to another key so that skins can just output it on its own  | 
            ||
| 494 | // and output printfooter and debughtml separately  | 
            ||
| 495 | $tpl->set( 'bodycontent', $tpl->data['bodytext'] );  | 
            ||
| 496 | |||
| 497 | // Append printfooter and debughtml onto bodytext so that skins that  | 
            ||
| 498 | // were already using bodytext before they were split out don't suddenly  | 
            ||
| 499 | // start not outputting information.  | 
            ||
| 500 | $tpl->data['bodytext'] .= Html::rawElement(  | 
            ||
| 501 | 'div',  | 
            ||
| 502 | [ 'class' => 'printfooter' ],  | 
            ||
| 503 | 			"\n{$tpl->data['printfooter']}" | 
            ||
| 504 | ) . "\n";  | 
            ||
| 505 | $tpl->data['bodytext'] .= $tpl->data['debughtml'];  | 
            ||
| 506 | |||
| 507 | // allow extensions adding stuff after the page content.  | 
            ||
| 508 | // See Skin::afterContentHook() for further documentation.  | 
            ||
| 509 | $tpl->set( 'dataAfterContent', $this->afterContentHook() );  | 
            ||
| 510 | |||
| 511 | return $tpl;  | 
            ||
| 512 | }  | 
            ||
| 513 | |||
| 514 | /**  | 
            ||
| 515 | * Get the HTML for the p-personal list  | 
            ||
| 516 | * @return string  | 
            ||
| 517 | */  | 
            ||
| 518 | 	public function getPersonalToolsList() { | 
            ||
| 519 | $tpl = $this->setupTemplateForOutput();  | 
            ||
| 520 | $tpl->set( 'personal_urls', $this->buildPersonalUrls() );  | 
            ||
| 521 | $html = '';  | 
            ||
| 522 | 		foreach ( $tpl->getPersonalTools() as $key => $item ) { | 
            ||
| 523 | $html .= $tpl->makeListItem( $key, $item );  | 
            ||
| 524 | }  | 
            ||
| 525 | return $html;  | 
            ||
| 526 | }  | 
            ||
| 527 | |||
| 528 | /**  | 
            ||
| 529 | * Format language name for use in sidebar interlanguage links list.  | 
            ||
| 530 | * By default it is capitalized.  | 
            ||
| 531 | *  | 
            ||
| 532 | * @param string $name Language name, e.g. "English" or "español"  | 
            ||
| 533 | * @return string  | 
            ||
| 534 | * @private  | 
            ||
| 535 | */  | 
            ||
| 536 | 	function formatLanguageName( $name ) { | 
            ||
| 537 | return $this->getLanguage()->ucfirst( $name );  | 
            ||
| 538 | }  | 
            ||
| 539 | |||
| 540 | /**  | 
            ||
| 541 | * Output the string, or print error message if it's  | 
            ||
| 542 | * an error object of the appropriate type.  | 
            ||
| 543 | * For the base class, assume strings all around.  | 
            ||
| 544 | *  | 
            ||
| 545 | * @param string $str  | 
            ||
| 546 | * @private  | 
            ||
| 547 | */  | 
            ||
| 548 | 	function printOrError( $str ) { | 
            ||
| 549 | echo $str;  | 
            ||
| 550 | }  | 
            ||
| 551 | |||
| 552 | /**  | 
            ||
| 553 | * Output a boolean indicating if buildPersonalUrls should output separate  | 
            ||
| 554 | * login and create account links or output a combined link  | 
            ||
| 555 | * By default we simply return a global config setting that affects most skins  | 
            ||
| 556 | * This is setup as a method so that like with $wgLogo and getLogo() a skin  | 
            ||
| 557 | * can override this setting and always output one or the other if it has  | 
            ||
| 558 | * a reason it can't output one of the two modes.  | 
            ||
| 559 | * @return bool  | 
            ||
| 560 | */  | 
            ||
| 561 | 	function useCombinedLoginLink() { | 
            ||
| 562 | global $wgUseCombinedLoginLink;  | 
            ||
| 563 | return $wgUseCombinedLoginLink;  | 
            ||
| 564 | }  | 
            ||
| 565 | |||
| 566 | /**  | 
            ||
| 567 | * build array of urls for personal toolbar  | 
            ||
| 568 | * @return array  | 
            ||
| 569 | */  | 
            ||
| 570 | 	protected function buildPersonalUrls() { | 
            ||
| 726 | |||
| 727 | /**  | 
            ||
| 728 | * Builds an array with tab definition  | 
            ||
| 729 | *  | 
            ||
| 730 | * @param Title $title Page Where the tab links to  | 
            ||
| 731 | * @param string|array $message Message key or an array of message keys (will fall back)  | 
            ||
| 732 | * @param bool $selected Display the tab as selected  | 
            ||
| 733 | * @param string $query Query string attached to tab URL  | 
            ||
| 734 | * @param bool $checkEdit Check if $title exists and mark with .new if one doesn't  | 
            ||
| 735 | *  | 
            ||
| 736 | * @return array  | 
            ||
| 737 | */  | 
            ||
| 738 | 	function tabAction( $title, $message, $selected, $query = '', $checkEdit = false ) { | 
            ||
| 780 | |||
| 781 | 	function makeTalkUrlDetails( $name, $urlaction = '' ) { | 
            ||
| 793 | |||
| 794 | /**  | 
            ||
| 795 | * @todo is this even used?  | 
            ||
| 796 | */  | 
            ||
| 797 | View Code Duplication | 	function makeArticleUrlDetails( $name, $urlaction = '' ) { | 
            |
| 806 | |||
| 807 | /**  | 
            ||
| 808 | * a structured array of links usually used for the tabs in a skin  | 
            ||
| 809 | *  | 
            ||
| 810 | * There are 4 standard sections  | 
            ||
| 811 | * namespaces: Used for namespace tabs like special, page, and talk namespaces  | 
            ||
| 812 | * views: Used for primary page views like read, edit, history  | 
            ||
| 813 | * actions: Used for most extra page actions like deletion, protection, etc...  | 
            ||
| 814 | * variants: Used to list the language variants for the page  | 
            ||
| 815 | *  | 
            ||
| 816 | * Each section's value is a key/value array of links for that section.  | 
            ||
| 817 | * The links themselves have these common keys:  | 
            ||
| 818 | * - class: The css classes to apply to the tab  | 
            ||
| 819 | * - text: The text to display on the tab  | 
            ||
| 820 | * - href: The href for the tab to point to  | 
            ||
| 821 | * - rel: An optional rel= for the tab's link  | 
            ||
| 822 | * - redundant: If true the tab will be dropped in skins using content_actions  | 
            ||
| 823 | * this is useful for tabs like "Read" which only have meaning in skins that  | 
            ||
| 824 | * take special meaning from the grouped structure of content_navigation  | 
            ||
| 825 | *  | 
            ||
| 826 | * Views also have an extra key which can be used:  | 
            ||
| 827 | * - primary: If this is not true skins like vector may try to hide the tab  | 
            ||
| 828 | * when the user has limited space in their browser window  | 
            ||
| 829 | *  | 
            ||
| 830 | * content_navigation using code also expects these ids to be present on the  | 
            ||
| 831 | * links, however these are usually automatically generated by SkinTemplate  | 
            ||
| 832 | * itself and are not necessary when using a hook. The only things these may  | 
            ||
| 833 | * matter to are people modifying content_navigation after it's initial creation:  | 
            ||
| 834 | * - id: A "preferred" id, most skins are best off outputting this preferred  | 
            ||
| 835 | * id for best compatibility.  | 
            ||
| 836 | * - tooltiponly: This is set to true for some tabs in cases where the system  | 
            ||
| 837 | * believes that the accesskey should not be added to the tab.  | 
            ||
| 838 | *  | 
            ||
| 839 | * @return array  | 
            ||
| 840 | */  | 
            ||
| 841 | 	protected function buildContentNavigationUrls() { | 
            ||
| 1155 | |||
| 1156 | /**  | 
            ||
| 1157 | * an array of edit links by default used for the tabs  | 
            ||
| 1158 | * @param array $content_navigation  | 
            ||
| 1159 | * @return array  | 
            ||
| 1160 | */  | 
            ||
| 1161 | 	private function buildContentActionUrls( $content_navigation ) { | 
            ||
| 1196 | |||
| 1197 | /**  | 
            ||
| 1198 | * build array of common navigation links  | 
            ||
| 1199 | * @return array  | 
            ||
| 1200 | */  | 
            ||
| 1201 | 	protected function buildNavUrls() { | 
            ||
| 1312 | |||
| 1313 | /**  | 
            ||
| 1314 | * Generate strings used for xml 'id' names  | 
            ||
| 1315 | * @return string  | 
            ||
| 1316 | */  | 
            ||
| 1317 | 	protected function getNameSpaceKey() { | 
            ||
| 1320 | }  | 
            ||
| 1321 | 
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the parent class: