Completed
Branch master (efd8f8)
by
unknown
29:11
created

Skin   F

Complexity

Total Complexity 253

Size/Duplication

Total Lines 1807
Duplicated Lines 1 %

Coupling/Cohesion

Components 4
Dependencies 26

Importance

Changes 3
Bugs 0 Features 2
Metric Value
wmc 253
c 3
b 0
f 2
lcom 4
cbo 26
dl 18
loc 1807
rs 0.5217

120 Methods

Rating   Name   Duplication   Size   Complexity  
A getSkinNames() 0 3 1
A getSkinNameMessages() 0 7 2
A getAllowedSkins() 0 11 2
D getDefaultModules() 0 42 10
B preloadExistence() 0 29 5
A getRevisionId() 0 3 1
A isRevisionCurrent() 0 4 2
A setRelevantTitle() 0 3 1
A getRelevantTitle() 0 6 2
A setRelevantUser() 0 3 1
B getRelevantUser() 0 24 6
outputPage() 0 1 ?
A makeVariablesScript() 0 9 2
A getDynamicStylesheetQuery() 0 11 1
setupSkinUserCss() 0 1 ?
A getSkinName() 0 3 1
A initPage() 0 5 1
C normalizeKey() 0 40 7
B getPageClasses() 0 23 4
A getHtmlElementAttributes() 0 8 1
A addToBodyAttributes() 0 3 1
A getLogo() 0 4 1
C getCategoryLinks() 0 62 8
A drawCategoryBrowser() 0 19 3
B getCategories() 0 20 5
A afterContentHook() 0 20 3
A generateDebugHTML() 0 3 1
A bottomScripts() 0 9 1
A printSource() 0 14 2
B getUndeleteLink() 0 24 6
C subPageSubtitle() 0 49 9
A showIPinHeader() 0 4 1
A getSearchLink() 0 4 1
A escapeSearchLink() 0 3 1
C getCopyright() 0 42 8
B getCopyrightIcon() 0 25 5
A getPoweredBy() 0 18 1
B lastModified() 0 22 4
A logoText() 0 16 3
B makeFooterIcon() 0 18 6
A mainPageLink() 0 8 1
A footerLink() 0 21 3
A privacyLink() 0 3 1
A aboutLink() 0 3 1
A disclaimerLink() 0 3 1
A editUrlOptions() 0 9 2
A showEmailUser() 0 12 3
A getSkinStylePath() 0 10 2
A makeMainPageUrl() 0 6 1
A makeSpecialUrl() 0 8 2
A makeSpecialUrlSubpage() 0 4 1
A makeI18nUrl() 0 5 1
A makeUrl() 0 6 1
A makeInternalOrExternalUrl() 0 7 2
A makeNSUrl() 0 6 1
A makeUrlDetails() 9 9 1
A makeKnownUrlDetails() 9 9 1
A checkTitle() 0 8 3
B buildSidebar() 0 29 2
A addToSidebar() 0 3 1
D addToSidebarPlain() 0 84 15
D getNewtalks() 0 86 15
C getCachedNotice() 0 45 8
B getSiteNotice() 0 22 6
A doEditSectionLink() 0 55 3
A accesskey() 0 4 1
A blockLink() 0 4 1
A buildRollbackLink() 0 4 1
A commentBlock() 0 4 1
A emailLink() 0 4 1
A formatComment() 0 4 1
A formatHiddenCategories() 0 4 1
A formatLinksInComment() 0 4 1
A formatRevisionSize() 0 4 1
A formatSize() 0 4 1
A formatTemplates() 0 4 1
A generateRollback() 0 4 1
A generateTOC() 0 4 1
A getInternalLinkAttributes() 0 4 1
A getInternalLinkAttributesObj() 0 4 1
A getInterwikiLinkAttributes() 0 4 1
A getInvalidTitleDescription() 0 4 1
A getLinkColour() 0 4 1
A getRevDeleteLink() 0 4 1
A getRollbackEditCount() 0 4 1
A link() 0 4 1
A linkKnown() 0 4 1
A makeBrokenImageLinkObj() 0 4 1
A makeCommentLink() 0 4 1
A makeExternalImage() 0 4 1
A makeExternalLink() 0 4 1
A makeHeadline() 0 4 1
A makeImageLink() 0 4 1
A makeMediaLinkFile() 0 4 1
A makeMediaLinkObj() 0 4 1
A makeSelfLinkObj() 0 4 1
A makeThumbLink2() 0 4 1
A makeThumbLinkObj() 0 4 1
A normaliseSpecialPage() 0 4 1
A normalizeSubpageLink() 0 4 1
A processResponsiveImages() 0 4 1
A revComment() 0 4 1
A revDeleteLink() 0 4 1
A revDeleteLinkDisabled() 0 4 1
A revUserLink() 0 4 1
A revUserTools() 0 4 1
A specialLink() 0 4 1
A splitTrail() 0 4 1
A titleAttrib() 0 4 1
A tocIndent() 0 4 1
A tocLine() 0 4 1
A tocLineEnd() 0 4 1
A tocList() 0 4 1
A tocUnindent() 0 4 1
A tooltip() 0 4 1
A tooltipAndAccesskeyAttribs() 0 4 1
A userLink() 0 4 1
A userTalkLink() 0 4 1
A userToolLinks() 0 4 1
A userToolLinksRedContribs() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * Base class for all skins.
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
23
/**
24
 * @defgroup Skins Skins
25
 */
26
27
/**
28
 * The main skin class which provides methods and properties for all other skins.
29
 *
30
 * See docs/skin.txt for more information.
31
 *
32
 * @ingroup Skins
33
 */
34
abstract class Skin extends ContextSource {
35
	protected $skinname = null;
36
	protected $mRelevantTitle = null;
37
	protected $mRelevantUser = null;
38
39
	/**
40
	 * @var string Stylesheets set to use. Subdirectory in skins/ where various stylesheets are
41
	 *   located. Only needs to be set if you intend to use the getSkinStylePath() method.
42
	 */
43
	public $stylename = null;
44
45
	/**
46
	 * Fetch the set of available skins.
47
	 * @return array Associative array of strings
48
	 */
49
	static function getSkinNames() {
50
		return SkinFactory::getDefaultInstance()->getSkinNames();
51
	}
52
53
	/**
54
	 * Fetch the skinname messages for available skins.
55
	 * @return string[]
56
	 */
57
	static function getSkinNameMessages() {
58
		$messages = [];
59
		foreach ( self::getSkinNames() as $skinKey => $skinName ) {
60
			$messages[] = "skinname-$skinKey";
61
		}
62
		return $messages;
63
	}
64
65
	/**
66
	 * Fetch the list of user-selectable skins in regards to $wgSkipSkins.
67
	 * Useful for Special:Preferences and other places where you
68
	 * only want to show skins users _can_ use.
69
	 * @return string[]
70
	 * @since 1.23
71
	 */
72
	public static function getAllowedSkins() {
73
		global $wgSkipSkins;
74
75
		$allowedSkins = self::getSkinNames();
76
77
		foreach ( $wgSkipSkins as $skip ) {
78
			unset( $allowedSkins[$skip] );
79
		}
80
81
		return $allowedSkins;
82
	}
83
84
	/**
85
	 * Normalize a skin preference value to a form that can be loaded.
86
	 *
87
	 * If a skin can't be found, it will fall back to the configured default ($wgDefaultSkin), or the
88
	 * hardcoded default ($wgFallbackSkin) if the default skin is unavailable too.
89
	 *
90
	 * @param string $key 'monobook', 'vector', etc.
91
	 * @return string
92
	 */
93
	static function normalizeKey( $key ) {
94
		global $wgDefaultSkin, $wgFallbackSkin;
95
96
		$skinNames = Skin::getSkinNames();
97
98
		// Make keys lowercase for case-insensitive matching.
99
		$skinNames = array_change_key_case( $skinNames, CASE_LOWER );
100
		$key = strtolower( $key );
101
		$defaultSkin = strtolower( $wgDefaultSkin );
102
		$fallbackSkin = strtolower( $wgFallbackSkin );
103
104
		if ( $key == '' || $key == 'default' ) {
105
			// Don't return the default immediately;
106
			// in a misconfiguration we need to fall back.
107
			$key = $defaultSkin;
108
		}
109
110
		if ( isset( $skinNames[$key] ) ) {
111
			return $key;
112
		}
113
114
		// Older versions of the software used a numeric setting
115
		// in the user preferences.
116
		$fallback = [
117
			0 => $defaultSkin,
118
			2 => 'cologneblue'
119
		];
120
121
		if ( isset( $fallback[$key] ) ) {
122
			$key = $fallback[$key];
123
		}
124
125
		if ( isset( $skinNames[$key] ) ) {
126
			return $key;
127
		} elseif ( isset( $skinNames[$defaultSkin] ) ) {
128
			return $defaultSkin;
129
		} else {
130
			return $fallbackSkin;
131
		}
132
	}
133
134
	/**
135
	 * @return string Skin name
136
	 */
137
	public function getSkinName() {
138
		return $this->skinname;
139
	}
140
141
	/**
142
	 * @param OutputPage $out
143
	 */
144
	function initPage( OutputPage $out ) {
145
146
		$this->preloadExistence();
147
148
	}
149
150
	/**
151
	 * Defines the ResourceLoader modules that should be added to the skin
152
	 * It is recommended that skins wishing to override call parent::getDefaultModules()
153
	 * and substitute out any modules they wish to change by using a key to look them up
154
	 * @return array Array of modules with helper keys for easy overriding
155
	 */
156
	public function getDefaultModules() {
157
		global $wgUseAjax, $wgEnableAPI, $wgEnableWriteAPI;
158
159
		$out = $this->getOutput();
160
		$user = $out->getUser();
161
		$modules = [
162
			// modules that enhance the page content in some way
163
			'content' => [
164
				'mediawiki.page.ready',
165
			],
166
			// modules that exist for legacy reasons
167
			'legacy' => ResourceLoaderStartUpModule::getLegacyModules(),
168
			// modules relating to search functionality
169
			'search' => [],
170
			// modules relating to functionality relating to watching an article
171
			'watch' => [],
172
			// modules which relate to the current users preferences
173
			'user' => [],
174
		];
175
176
		// Add various resources if required
177
		if ( $wgUseAjax && $wgEnableAPI ) {
178
			if ( $wgEnableWriteAPI && $user->isLoggedIn()
179
				&& $user->isAllowedAll( 'writeapi', 'viewmywatchlist', 'editmywatchlist' )
180
				&& $this->getRelevantTitle()->canExist()
181
			) {
182
				$modules['watch'][] = 'mediawiki.page.watch.ajax';
183
			}
184
185
			$modules['search'][] = 'mediawiki.searchSuggest';
186
		}
187
188
		if ( $user->getBoolOption( 'editsectiononrightclick' ) ) {
189
			$modules['user'][] = 'mediawiki.action.view.rightClickEdit';
190
		}
191
192
		// Crazy edit-on-double-click stuff
193
		if ( $out->isArticle() && $user->getOption( 'editondblclick' ) ) {
194
			$modules['user'][] = 'mediawiki.action.view.dblClickEdit';
195
		}
196
		return $modules;
197
	}
198
199
	/**
200
	 * Preload the existence of three commonly-requested pages in a single query
201
	 */
202
	function preloadExistence() {
203
		$titles = [];
204
205
		$user = $this->getUser();
206
		$title = $this->getRelevantTitle();
207
208
		// User/talk link
209
		if ( $user->isLoggedIn() ) {
210
			$titles[] = $user->getUserPage();
211
			$titles[] = $user->getTalkPage();
212
		}
213
214
		// Check, if the page can hold some kind of content, otherwise do nothing
215
		if ( !$title->canExist() ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
216
			// nothing
217
		} elseif ( $title->isTalkPage() ) {
218
			$titles[] = $title->getSubjectPage();
219
		} else {
220
			$titles[] = $title->getTalkPage();
221
		}
222
223
		Hooks::run( 'SkinPreloadExistence', [ &$titles, $this ] );
224
225
		if ( count( $titles ) ) {
226
			$lb = new LinkBatch( $titles );
227
			$lb->setCaller( __METHOD__ );
228
			$lb->execute();
229
		}
230
	}
231
232
	/**
233
	 * Get the current revision ID
234
	 *
235
	 * @return int
236
	 */
237
	public function getRevisionId() {
238
		return $this->getOutput()->getRevisionId();
239
	}
240
241
	/**
242
	 * Whether the revision displayed is the latest revision of the page
243
	 *
244
	 * @return bool
245
	 */
246
	public function isRevisionCurrent() {
247
		$revID = $this->getRevisionId();
248
		return $revID == 0 || $revID == $this->getTitle()->getLatestRevID();
249
	}
250
251
	/**
252
	 * Set the "relevant" title
253
	 * @see self::getRelevantTitle()
254
	 * @param Title $t
255
	 */
256
	public function setRelevantTitle( $t ) {
257
		$this->mRelevantTitle = $t;
258
	}
259
260
	/**
261
	 * Return the "relevant" title.
262
	 * A "relevant" title is not necessarily the actual title of the page.
263
	 * Special pages like Special:MovePage use set the page they are acting on
264
	 * as their "relevant" title, this allows the skin system to display things
265
	 * such as content tabs which belong to to that page instead of displaying
266
	 * a basic special page tab which has almost no meaning.
267
	 *
268
	 * @return Title
269
	 */
270
	public function getRelevantTitle() {
271
		if ( isset( $this->mRelevantTitle ) ) {
272
			return $this->mRelevantTitle;
273
		}
274
		return $this->getTitle();
275
	}
276
277
	/**
278
	 * Set the "relevant" user
279
	 * @see self::getRelevantUser()
280
	 * @param User $u
281
	 */
282
	public function setRelevantUser( $u ) {
283
		$this->mRelevantUser = $u;
284
	}
285
286
	/**
287
	 * Return the "relevant" user.
288
	 * A "relevant" user is similar to a relevant title. Special pages like
289
	 * Special:Contributions mark the user which they are relevant to so that
290
	 * things like the toolbox can display the information they usually are only
291
	 * able to display on a user's userpage and talkpage.
292
	 * @return User
293
	 */
294
	public function getRelevantUser() {
295
		if ( isset( $this->mRelevantUser ) ) {
296
			return $this->mRelevantUser;
297
		}
298
		$title = $this->getRelevantTitle();
299
		if ( $title->hasSubjectNamespace( NS_USER ) ) {
300
			$rootUser = $title->getRootText();
301
			if ( User::isIP( $rootUser ) ) {
302
				$this->mRelevantUser = User::newFromName( $rootUser, false );
303
			} else {
304
				$user = User::newFromName( $rootUser, false );
305
306
				if ( $user ) {
307
					$user->load( User::READ_NORMAL );
308
309
					if ( $user->isLoggedIn() ) {
310
						$this->mRelevantUser = $user;
311
					}
312
				}
313
			}
314
			return $this->mRelevantUser;
315
		}
316
		return null;
317
	}
318
319
	/**
320
	 * Outputs the HTML generated by other functions.
321
	 * @param OutputPage $out
322
	 */
323
	abstract function outputPage( OutputPage $out = null );
324
325
	/**
326
	 * @param array $data
327
	 * @return string
328
	 */
329
	static function makeVariablesScript( $data ) {
330
		if ( $data ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
331
			return ResourceLoader::makeInlineScript(
332
				ResourceLoader::makeConfigSetScript( $data )
0 ignored issues
show
Security Bug introduced by
It seems like \ResourceLoader::makeConfigSetScript($data) targeting ResourceLoader::makeConfigSetScript() can also be of type false; however, ResourceLoader::makeInlineScript() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
333
			);
334
		} else {
335
			return '';
336
		}
337
	}
338
339
	/**
340
	 * Get the query to generate a dynamic stylesheet
341
	 *
342
	 * @return array
343
	 */
344
	public static function getDynamicStylesheetQuery() {
345
		global $wgSquidMaxage;
346
347
		return [
348
				'action' => 'raw',
349
				'maxage' => $wgSquidMaxage,
350
				'usemsgcache' => 'yes',
351
				'ctype' => 'text/css',
352
				'smaxage' => $wgSquidMaxage,
353
			];
354
	}
355
356
	/**
357
	 * Add skin specific stylesheets
358
	 * Calling this method with an $out of anything but the same OutputPage
359
	 * inside ->getOutput() is deprecated. The $out arg is kept
360
	 * for compatibility purposes with skins.
361
	 * @param OutputPage $out
362
	 * @todo delete
363
	 */
364
	abstract function setupSkinUserCss( OutputPage $out );
365
366
	/**
367
	 * TODO: document
368
	 * @param Title $title
369
	 * @return string
370
	 */
371
	function getPageClasses( $title ) {
372
		$numeric = 'ns-' . $title->getNamespace();
373
374
		if ( $title->isSpecialPage() ) {
375
			$type = 'ns-special';
376
			// bug 23315: provide a class based on the canonical special page name without subpages
377
			list( $canonicalName ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
378
			if ( $canonicalName ) {
379
				$type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" );
380
			} else {
381
				$type .= ' mw-invalidspecialpage';
382
			}
383
		} elseif ( $title->isTalkPage() ) {
384
			$type = 'ns-talk';
385
		} else {
386
			$type = 'ns-subject';
387
		}
388
389
		$name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
390
		$root = Sanitizer::escapeClass( 'rootpage-' . $title->getRootTitle()->getPrefixedText() );
391
392
		return "$numeric $type $name $root";
393
	}
394
395
	/**
396
	 * Return values for <html> element
397
	 * @return array Array of associative name-to-value elements for <html> element
398
	 */
399
	public function getHtmlElementAttributes() {
400
		$lang = $this->getLanguage();
401
		return [
402
			'lang' => $lang->getHtmlCode(),
403
			'dir' => $lang->getDir(),
404
			'class' => 'client-nojs',
405
		];
406
	}
407
408
	/**
409
	 * This will be called by OutputPage::headElement when it is creating the
410
	 * "<body>" tag, skins can override it if they have a need to add in any
411
	 * body attributes or classes of their own.
412
	 * @param OutputPage $out
413
	 * @param array $bodyAttrs
414
	 */
415
	function addToBodyAttributes( $out, &$bodyAttrs ) {
416
		// does nothing by default
417
	}
418
419
	/**
420
	 * URL to the logo
421
	 * @return string
422
	 */
423
	function getLogo() {
424
		global $wgLogo;
425
		return $wgLogo;
426
	}
427
428
	/**
429
	 * @return string HTML
430
	 */
431
	function getCategoryLinks() {
432
		global $wgUseCategoryBrowser;
433
434
		$out = $this->getOutput();
435
		$allCats = $out->getCategoryLinks();
436
437
		if ( !count( $allCats ) ) {
438
			return '';
439
		}
440
441
		$embed = "<li>";
442
		$pop = "</li>";
443
444
		$s = '';
445
		$colon = $this->msg( 'colon-separator' )->escaped();
446
447
		if ( !empty( $allCats['normal'] ) ) {
448
			$t = $embed . implode( "{$pop}{$embed}", $allCats['normal'] ) . $pop;
449
450
			$msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped();
451
			$linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
452
			$title = Title::newFromText( $linkPage );
453
			$link = $title ? Linker::link( $title, $msg ) : $msg;
454
			$s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
455
				$link . $colon . '<ul>' . $t . '</ul>' . '</div>';
456
		}
457
458
		# Hidden categories
459
		if ( isset( $allCats['hidden'] ) ) {
460
			if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
461
				$class = ' mw-hidden-cats-user-shown';
462
			} elseif ( $this->getTitle()->getNamespace() == NS_CATEGORY ) {
463
				$class = ' mw-hidden-cats-ns-shown';
464
			} else {
465
				$class = ' mw-hidden-cats-hidden';
466
			}
467
468
			$s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
469
				$this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() .
470
				$colon . '<ul>' . $embed . implode( "{$pop}{$embed}", $allCats['hidden'] ) . $pop . '</ul>' .
471
				'</div>';
472
		}
473
474
		# optional 'dmoz-like' category browser. Will be shown under the list
475
		# of categories an article belong to
476
		if ( $wgUseCategoryBrowser ) {
477
			$s .= '<br /><hr />';
478
479
			# get a big array of the parents tree
480
			$parenttree = $this->getTitle()->getParentCategoryTree();
481
			# Skin object passed by reference cause it can not be
482
			# accessed under the method subfunction drawCategoryBrowser
483
			$tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) );
484
			# Clean out bogus first entry and sort them
485
			unset( $tempout[0] );
486
			asort( $tempout );
487
			# Output one per line
488
			$s .= implode( "<br />\n", $tempout );
489
		}
490
491
		return $s;
492
	}
493
494
	/**
495
	 * Render the array as a series of links.
496
	 * @param array $tree Categories tree returned by Title::getParentCategoryTree
497
	 * @return string Separated by &gt;, terminate with "\n"
498
	 */
499
	function drawCategoryBrowser( $tree ) {
500
		$return = '';
501
502
		foreach ( $tree as $element => $parent ) {
503
			if ( empty( $parent ) ) {
504
				# element start a new list
505
				$return .= "\n";
506
			} else {
507
				# grab the others elements
508
				$return .= $this->drawCategoryBrowser( $parent ) . ' &gt; ';
509
			}
510
511
			# add our current element to the list
512
			$eltitle = Title::newFromText( $element );
513
			$return .= Linker::link( $eltitle, htmlspecialchars( $eltitle->getText() ) );
0 ignored issues
show
Bug introduced by
It seems like $eltitle defined by \Title::newFromText($element) on line 512 can be null; however, Linker::link() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
514
		}
515
516
		return $return;
517
	}
518
519
	/**
520
	 * @return string HTML
521
	 */
522
	function getCategories() {
523
		$out = $this->getOutput();
524
		$catlinks = $this->getCategoryLinks();
525
526
		// Check what we're showing
527
		$allCats = $out->getCategoryLinks();
528
		$showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) ||
529
						$this->getTitle()->getNamespace() == NS_CATEGORY;
530
531
		$classes = [ 'catlinks' ];
532
		if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
533
			$classes[] = 'catlinks-allhidden';
534
		}
535
536
		return Html::rawElement(
537
			'div',
538
			[ 'id' => 'catlinks', 'class' => $classes, 'data-mw' => 'interface' ],
539
			$catlinks
540
		);
541
	}
542
543
	/**
544
	 * This runs a hook to allow extensions placing their stuff after content
545
	 * and article metadata (e.g. categories).
546
	 * Note: This function has nothing to do with afterContent().
547
	 *
548
	 * This hook is placed here in order to allow using the same hook for all
549
	 * skins, both the SkinTemplate based ones and the older ones, which directly
550
	 * use this class to get their data.
551
	 *
552
	 * The output of this function gets processed in SkinTemplate::outputPage() for
553
	 * the SkinTemplate based skins, all other skins should directly echo it.
554
	 *
555
	 * @return string Empty by default, if not changed by any hook function.
556
	 */
557
	protected function afterContentHook() {
558
		$data = '';
559
560
		if ( Hooks::run( 'SkinAfterContent', [ &$data, $this ] ) ) {
561
			// adding just some spaces shouldn't toggle the output
562
			// of the whole <div/>, so we use trim() here
563
			if ( trim( $data ) != '' ) {
564
				// Doing this here instead of in the skins to
565
				// ensure that the div has the same ID in all
566
				// skins
567
				$data = "<div id='mw-data-after-content'>\n" .
568
					"\t$data\n" .
569
					"</div>\n";
570
			}
571
		} else {
572
			wfDebug( "Hook SkinAfterContent changed output processing.\n" );
573
		}
574
575
		return $data;
576
	}
577
578
	/**
579
	 * Generate debug data HTML for displaying at the bottom of the main content
580
	 * area.
581
	 * @return string HTML containing debug data, if enabled (otherwise empty).
582
	 */
583
	protected function generateDebugHTML() {
584
		return MWDebug::getHTMLDebugLog();
585
	}
586
587
	/**
588
	 * This gets called shortly before the "</body>" tag.
589
	 *
590
	 * @return string HTML-wrapped JS code to be put before "</body>"
591
	 */
592
	function bottomScripts() {
593
		// TODO and the suckage continues. This function is really just a wrapper around
594
		// OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
595
		// up at some point
596
		$bottomScriptText = $this->getOutput()->getBottomScripts();
597
		Hooks::run( 'SkinAfterBottomScripts', [ $this, &$bottomScriptText ] );
598
599
		return $bottomScriptText;
600
	}
601
602
	/**
603
	 * Text with the permalink to the source page,
604
	 * usually shown on the footer of a printed page
605
	 *
606
	 * @return string HTML text with an URL
607
	 */
608
	function printSource() {
609
		$oldid = $this->getRevisionId();
610
		if ( $oldid ) {
611
			$canonicalUrl = $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid );
612
			$url = htmlspecialchars( wfExpandIRI( $canonicalUrl ) );
613
		} else {
614
			// oldid not available for non existing pages
615
			$url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL() ) );
616
		}
617
618
		return $this->msg( 'retrievedfrom' )
619
			->rawParams( '<a dir="ltr" href="' . $url . '">' . $url . '</a>' )
620
			->parse();
621
	}
622
623
	/**
624
	 * @return string HTML
625
	 */
626
	function getUndeleteLink() {
627
		$action = $this->getRequest()->getVal( 'action', 'view' );
628
629
		if ( $this->getTitle()->userCan( 'deletedhistory', $this->getUser() ) &&
630
			( !$this->getTitle()->exists() || $action == 'history' ) ) {
631
			$n = $this->getTitle()->isDeleted();
632
633
			if ( $n ) {
634
				if ( $this->getTitle()->quickUserCan( 'undelete', $this->getUser() ) ) {
635
					$msg = 'thisisdeleted';
636
				} else {
637
					$msg = 'viewdeleted';
638
				}
639
640
				return $this->msg( $msg )->rawParams(
641
					Linker::linkKnown(
642
						SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ),
643
						$this->msg( 'restorelink' )->numParams( $n )->escaped() )
644
					)->escaped();
645
			}
646
		}
647
648
		return '';
649
	}
650
651
	/**
652
	 * @return string
653
	 */
654
	function subPageSubtitle() {
655
		$out = $this->getOutput();
656
		$subpages = '';
657
658
		if ( !Hooks::run( 'SkinSubPageSubtitle', [ &$subpages, $this, $out ] ) ) {
659
			return $subpages;
660
		}
661
662
		if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) {
663
			$ptext = $this->getTitle()->getPrefixedText();
664
			if ( preg_match( '/\//', $ptext ) ) {
665
				$links = explode( '/', $ptext );
666
				array_pop( $links );
667
				$c = 0;
668
				$growinglink = '';
669
				$display = '';
670
				$lang = $this->getLanguage();
671
672
				foreach ( $links as $link ) {
673
					$growinglink .= $link;
674
					$display .= $link;
675
					$linkObj = Title::newFromText( $growinglink );
676
677
					if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
678
						$getlink = Linker::linkKnown(
679
							$linkObj,
680
							htmlspecialchars( $display )
681
						);
682
683
						$c++;
684
685
						if ( $c > 1 ) {
686
							$subpages .= $lang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
687
						} else {
688
							$subpages .= '&lt; ';
689
						}
690
691
						$subpages .= $getlink;
692
						$display = '';
693
					} else {
694
						$display .= '/';
695
					}
696
					$growinglink .= '/';
697
				}
698
			}
699
		}
700
701
		return $subpages;
702
	}
703
704
	/**
705
	 * @deprecated since 1.27, feature removed
706
	 * @return bool Always false
707
	 */
708
	function showIPinHeader() {
709
		wfDeprecated( __METHOD__, '1.27' );
710
		return false;
711
	}
712
713
	/**
714
	 * @return string
715
	 */
716
	function getSearchLink() {
717
		$searchPage = SpecialPage::getTitleFor( 'Search' );
718
		return $searchPage->getLocalURL();
719
	}
720
721
	/**
722
	 * @return string
723
	 */
724
	function escapeSearchLink() {
725
		return htmlspecialchars( $this->getSearchLink() );
726
	}
727
728
	/**
729
	 * @param string $type
730
	 * @return string
731
	 */
732
	function getCopyright( $type = 'detect' ) {
733
		global $wgRightsPage, $wgRightsUrl, $wgRightsText;
734
735
		if ( $type == 'detect' ) {
736
			if ( !$this->isRevisionCurrent()
737
				&& !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled()
738
			) {
739
				$type = 'history';
740
			} else {
741
				$type = 'normal';
742
			}
743
		}
744
745
		if ( $type == 'history' ) {
746
			$msg = 'history_copyright';
747
		} else {
748
			$msg = 'copyright';
749
		}
750
751
		if ( $wgRightsPage ) {
752
			$title = Title::newFromText( $wgRightsPage );
753
			$link = Linker::linkKnown( $title, $wgRightsText );
754
		} elseif ( $wgRightsUrl ) {
755
			$link = Linker::makeExternalLink( $wgRightsUrl, $wgRightsText );
756
		} elseif ( $wgRightsText ) {
757
			$link = $wgRightsText;
758
		} else {
759
			# Give up now
760
			return '';
761
		}
762
763
		// Allow for site and per-namespace customization of copyright notice.
764
		// @todo Remove deprecated $forContent param from hook handlers and then remove here.
765
		$forContent = true;
766
767
		Hooks::run(
768
			'SkinCopyrightFooter',
769
			[ $this->getTitle(), $type, &$msg, &$link, &$forContent ]
770
		);
771
772
		return $this->msg( $msg )->rawParams( $link )->text();
773
	}
774
775
	/**
776
	 * @return null|string
777
	 */
778
	function getCopyrightIcon() {
779
		global $wgRightsUrl, $wgRightsText, $wgRightsIcon, $wgFooterIcons;
780
781
		$out = '';
782
783
		if ( $wgFooterIcons['copyright']['copyright'] ) {
784
			$out = $wgFooterIcons['copyright']['copyright'];
785
		} elseif ( $wgRightsIcon ) {
786
			$icon = htmlspecialchars( $wgRightsIcon );
787
788
			if ( $wgRightsUrl ) {
789
				$url = htmlspecialchars( $wgRightsUrl );
790
				$out .= '<a href="' . $url . '">';
791
			}
792
793
			$text = htmlspecialchars( $wgRightsText );
794
			$out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
795
796
			if ( $wgRightsUrl ) {
797
				$out .= '</a>';
798
			}
799
		}
800
801
		return $out;
802
	}
803
804
	/**
805
	 * Gets the powered by MediaWiki icon.
806
	 * @return string
807
	 */
808
	function getPoweredBy() {
809
		global $wgResourceBasePath;
810
811
		$url1 = htmlspecialchars(
812
			"$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
813
		);
814
		$url1_5 = htmlspecialchars(
815
			"$wgResourceBasePath/resources/assets/poweredby_mediawiki_132x47.png"
816
		);
817
		$url2 = htmlspecialchars(
818
			"$wgResourceBasePath/resources/assets/poweredby_mediawiki_176x62.png"
819
		);
820
		$text = '<a href="//www.mediawiki.org/"><img src="' . $url1
821
			. '" srcset="' . $url1_5 . ' 1.5x, ' . $url2 . ' 2x" '
822
			. 'height="31" width="88" alt="Powered by MediaWiki" /></a>';
823
		Hooks::run( 'SkinGetPoweredBy', [ &$text, $this ] );
824
		return $text;
825
	}
826
827
	/**
828
	 * Get the timestamp of the latest revision, formatted in user language
829
	 *
830
	 * @return string
831
	 */
832
	protected function lastModified() {
833
		$timestamp = $this->getOutput()->getRevisionTimestamp();
834
835
		# No cached timestamp, load it from the database
836
		if ( $timestamp === null ) {
837
			$timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() );
0 ignored issues
show
Bug introduced by
It seems like $this->getTitle() can be null; however, getTimestampFromId() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
838
		}
839
840
		if ( $timestamp ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $timestamp of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
841
			$d = $this->getLanguage()->userDate( $timestamp, $this->getUser() );
842
			$t = $this->getLanguage()->userTime( $timestamp, $this->getUser() );
843
			$s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->parse();
844
		} else {
845
			$s = '';
846
		}
847
848
		if ( wfGetLB()->getLaggedSlaveMode() ) {
849
			$s .= ' <strong>' . $this->msg( 'laggedslavemode' )->parse() . '</strong>';
850
		}
851
852
		return $s;
853
	}
854
855
	/**
856
	 * @param string $align
857
	 * @return string
858
	 */
859
	function logoText( $align = '' ) {
860
		if ( $align != '' ) {
861
			$a = " style='float: {$align};'";
862
		} else {
863
			$a = '';
864
		}
865
866
		$mp = $this->msg( 'mainpage' )->escaped();
867
		$mptitle = Title::newMainPage();
868
		$url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' );
869
870
		$logourl = $this->getLogo();
871
		$s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
872
873
		return $s;
874
	}
875
876
	/**
877
	 * Renders a $wgFooterIcons icon according to the method's arguments
878
	 * @param array $icon The icon to build the html for, see $wgFooterIcons
879
	 *   for the format of this array.
880
	 * @param bool|string $withImage Whether to use the icon's image or output
881
	 *   a text-only footericon.
882
	 * @return string HTML
883
	 */
884
	function makeFooterIcon( $icon, $withImage = 'withImage' ) {
885
		if ( is_string( $icon ) ) {
886
			$html = $icon;
887
		} else { // Assuming array
888
			$url = isset( $icon["url"] ) ? $icon["url"] : null;
889
			unset( $icon["url"] );
890
			if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
891
				// do this the lazy way, just pass icon data as an attribute array
892
				$html = Html::element( 'img', $icon );
893
			} else {
894
				$html = htmlspecialchars( $icon["alt"] );
895
			}
896
			if ( $url ) {
897
				$html = Html::rawElement( 'a', [ "href" => $url ], $html );
898
			}
899
		}
900
		return $html;
901
	}
902
903
	/**
904
	 * Gets the link to the wiki's main page.
905
	 * @return string
906
	 */
907
	function mainPageLink() {
908
		$s = Linker::linkKnown(
909
			Title::newMainPage(),
910
			$this->msg( 'mainpage' )->escaped()
911
		);
912
913
		return $s;
914
	}
915
916
	/**
917
	 * Returns an HTML link for use in the footer
918
	 * @param string $desc The i18n message key for the link text
919
	 * @param string $page The i18n message key for the page to link to
920
	 * @return string HTML anchor
921
	 */
922
	public function footerLink( $desc, $page ) {
923
		// if the link description has been set to "-" in the default language,
924
		if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) {
925
			// then it is disabled, for all languages.
926
			return '';
927
		} else {
928
			// Otherwise, we display the link for the user, described in their
929
			// language (which may or may not be the same as the default language),
930
			// but we make the link target be the one site-wide page.
931
			$title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
932
933
			if ( !$title ) {
934
				return '';
935
			}
936
937
			return Linker::linkKnown(
938
				$title,
939
				$this->msg( $desc )->escaped()
940
			);
941
		}
942
	}
943
944
	/**
945
	 * Gets the link to the wiki's privacy policy page.
946
	 * @return string HTML
947
	 */
948
	function privacyLink() {
949
		return $this->footerLink( 'privacy', 'privacypage' );
950
	}
951
952
	/**
953
	 * Gets the link to the wiki's about page.
954
	 * @return string HTML
955
	 */
956
	function aboutLink() {
957
		return $this->footerLink( 'aboutsite', 'aboutpage' );
958
	}
959
960
	/**
961
	 * Gets the link to the wiki's general disclaimers page.
962
	 * @return string HTML
963
	 */
964
	function disclaimerLink() {
965
		return $this->footerLink( 'disclaimers', 'disclaimerpage' );
966
	}
967
968
	/**
969
	 * Return URL options for the 'edit page' link.
970
	 * This may include an 'oldid' specifier, if the current page view is such.
971
	 *
972
	 * @return array
973
	 * @private
974
	 */
975
	function editUrlOptions() {
976
		$options = [ 'action' => 'edit' ];
977
978
		if ( !$this->isRevisionCurrent() ) {
979
			$options['oldid'] = intval( $this->getRevisionId() );
980
		}
981
982
		return $options;
983
	}
984
985
	/**
986
	 * @param User|int $id
987
	 * @return bool
988
	 */
989
	function showEmailUser( $id ) {
990
		if ( $id instanceof User ) {
991
			$targetUser = $id;
992
		} else {
993
			$targetUser = User::newFromId( $id );
994
		}
995
996
		# The sending user must have a confirmed email address and the target
997
		# user must have a confirmed email address and allow emails from users.
998
		return $this->getUser()->canSendEmail() &&
999
			$targetUser->canReceiveEmail();
1000
	}
1001
1002
	/**
1003
	 * Return a fully resolved style path url to images or styles stored in the current skins's folder.
1004
	 * This method returns a url resolved using the configured skin style path
1005
	 * and includes the style version inside of the url.
1006
	 *
1007
	 * Requires $stylename to be set, otherwise throws MWException.
1008
	 *
1009
	 * @param string $name The name or path of a skin resource file
1010
	 * @return string The fully resolved style path url including styleversion
1011
	 * @throws MWException
1012
	 */
1013
	function getSkinStylePath( $name ) {
1014
		global $wgStylePath, $wgStyleVersion;
1015
1016
		if ( $this->stylename === null ) {
1017
			$class = get_class( $this );
1018
			throw new MWException( "$class::\$stylename must be set to use getSkinStylePath()" );
1019
		}
1020
1021
		return "$wgStylePath/{$this->stylename}/$name?$wgStyleVersion";
1022
	}
1023
1024
	/* these are used extensively in SkinTemplate, but also some other places */
1025
1026
	/**
1027
	 * @param string $urlaction
1028
	 * @return string
1029
	 */
1030
	static function makeMainPageUrl( $urlaction = '' ) {
1031
		$title = Title::newMainPage();
1032
		self::checkTitle( $title, '' );
0 ignored issues
show
Bug introduced by
It seems like $title defined by \Title::newMainPage() on line 1031 can be null; however, Skin::checkTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1033
1034
		return $title->getLocalURL( $urlaction );
1035
	}
1036
1037
	/**
1038
	 * Make a URL for a Special Page using the given query and protocol.
1039
	 *
1040
	 * If $proto is set to null, make a local URL. Otherwise, make a full
1041
	 * URL with the protocol specified.
1042
	 *
1043
	 * @param string $name Name of the Special page
1044
	 * @param string $urlaction Query to append
1045
	 * @param string|null $proto Protocol to use or null for a local URL
1046
	 * @return string
1047
	 */
1048
	static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
1049
		$title = SpecialPage::getSafeTitleFor( $name );
1050
		if ( is_null( $proto ) ) {
1051
			return $title->getLocalURL( $urlaction );
1052
		} else {
1053
			return $title->getFullURL( $urlaction, false, $proto );
1054
		}
1055
	}
1056
1057
	/**
1058
	 * @param string $name
1059
	 * @param string $subpage
1060
	 * @param string $urlaction
1061
	 * @return string
1062
	 */
1063
	static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
1064
		$title = SpecialPage::getSafeTitleFor( $name, $subpage );
1065
		return $title->getLocalURL( $urlaction );
1066
	}
1067
1068
	/**
1069
	 * @param string $name
1070
	 * @param string $urlaction
1071
	 * @return string
1072
	 */
1073
	static function makeI18nUrl( $name, $urlaction = '' ) {
1074
		$title = Title::newFromText( wfMessage( $name )->inContentLanguage()->text() );
1075
		self::checkTitle( $title, $name );
0 ignored issues
show
Bug introduced by
It seems like $title defined by \Title::newFromText(wfMe...tentLanguage()->text()) on line 1074 can be null; however, Skin::checkTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1076
		return $title->getLocalURL( $urlaction );
1077
	}
1078
1079
	/**
1080
	 * @param string $name
1081
	 * @param string $urlaction
1082
	 * @return string
1083
	 */
1084
	static function makeUrl( $name, $urlaction = '' ) {
1085
		$title = Title::newFromText( $name );
1086
		self::checkTitle( $title, $name );
0 ignored issues
show
Bug introduced by
It seems like $title defined by \Title::newFromText($name) on line 1085 can be null; however, Skin::checkTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1087
1088
		return $title->getLocalURL( $urlaction );
1089
	}
1090
1091
	/**
1092
	 * If url string starts with http, consider as external URL, else
1093
	 * internal
1094
	 * @param string $name
1095
	 * @return string URL
1096
	 */
1097
	static function makeInternalOrExternalUrl( $name ) {
1098
		if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) {
1099
			return $name;
1100
		} else {
1101
			return self::makeUrl( $name );
1102
		}
1103
	}
1104
1105
	/**
1106
	 * this can be passed the NS number as defined in Language.php
1107
	 * @param string $name
1108
	 * @param string $urlaction
1109
	 * @param int $namespace
1110
	 * @return string
1111
	 */
1112
	static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
1113
		$title = Title::makeTitleSafe( $namespace, $name );
1114
		self::checkTitle( $title, $name );
0 ignored issues
show
Bug introduced by
It seems like $title defined by \Title::makeTitleSafe($namespace, $name) on line 1113 can be null; however, Skin::checkTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1115
1116
		return $title->getLocalURL( $urlaction );
1117
	}
1118
1119
	/**
1120
	 * these return an array with the 'href' and boolean 'exists'
1121
	 * @param string $name
1122
	 * @param string $urlaction
1123
	 * @return array
1124
	 */
1125 View Code Duplication
	static function makeUrlDetails( $name, $urlaction = '' ) {
1126
		$title = Title::newFromText( $name );
1127
		self::checkTitle( $title, $name );
0 ignored issues
show
Bug introduced by
It seems like $title defined by \Title::newFromText($name) on line 1126 can be null; however, Skin::checkTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1128
1129
		return [
1130
			'href' => $title->getLocalURL( $urlaction ),
1131
			'exists' => $title->isKnown(),
1132
		];
1133
	}
1134
1135
	/**
1136
	 * Make URL details where the article exists (or at least it's convenient to think so)
1137
	 * @param string $name Article name
1138
	 * @param string $urlaction
1139
	 * @return array
1140
	 */
1141 View Code Duplication
	static function makeKnownUrlDetails( $name, $urlaction = '' ) {
1142
		$title = Title::newFromText( $name );
1143
		self::checkTitle( $title, $name );
0 ignored issues
show
Bug introduced by
It seems like $title defined by \Title::newFromText($name) on line 1142 can be null; however, Skin::checkTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1144
1145
		return [
1146
			'href' => $title->getLocalURL( $urlaction ),
1147
			'exists' => true
1148
		];
1149
	}
1150
1151
	/**
1152
	 * make sure we have some title to operate on
1153
	 *
1154
	 * @param Title $title
1155
	 * @param string $name
1156
	 */
1157
	static function checkTitle( &$title, $name ) {
1158
		if ( !is_object( $title ) ) {
1159
			$title = Title::newFromText( $name );
1160
			if ( !is_object( $title ) ) {
1161
				$title = Title::newFromText( '--error: link target missing--' );
1162
			}
1163
		}
1164
	}
1165
1166
	/**
1167
	 * Build an array that represents the sidebar(s), the navigation bar among them.
1168
	 *
1169
	 * BaseTemplate::getSidebar can be used to simplify the format and id generation in new skins.
1170
	 *
1171
	 * The format of the returned array is array( heading => content, ... ), where:
1172
	 * - heading is the heading of a navigation portlet. It is either:
1173
	 *   - magic string to be handled by the skins ('SEARCH' / 'LANGUAGES' / 'TOOLBOX' / ...)
1174
	 *   - a message name (e.g. 'navigation'), the message should be HTML-escaped by the skin
1175
	 *   - plain text, which should be HTML-escaped by the skin
1176
	 * - content is the contents of the portlet. It is either:
1177
	 *   - HTML text (<ul><li>...</li>...</ul>)
1178
	 *   - array of link data in a format accepted by BaseTemplate::makeListItem()
1179
	 *   - (for a magic string as a key, any value)
1180
	 *
1181
	 * Note that extensions can control the sidebar contents using the SkinBuildSidebar hook
1182
	 * and can technically insert anything in here; skin creators are expected to handle
1183
	 * values described above.
1184
	 *
1185
	 * @return array
1186
	 */
1187
	function buildSidebar() {
1188
		global $wgEnableSidebarCache, $wgSidebarCacheExpiry;
1189
1190
		$that = $this;
1191
		$callback = function () use ( $that ) {
1192
			$bar = [];
1193
			$that->addToSidebar( $bar, 'sidebar' );
1194
			Hooks::run( 'SkinBuildSidebar', [ $that, &$bar ] );
1195
1196
			return $bar;
1197
		};
1198
1199
		if ( $wgEnableSidebarCache ) {
1200
			$cache = ObjectCache::getMainWANInstance();
1201
			$sidebar = $cache->getWithSetCallback(
1202
				$cache->makeKey( 'sidebar', $this->getLanguage()->getCode() ),
1203
				$wgSidebarCacheExpiry,
1204
				$callback,
1205
				[ 'lockTSE' => 30 ]
1206
			);
1207
		} else {
1208
			$sidebar = $callback();
1209
		}
1210
1211
		// Apply post-processing to the cached value
1212
		Hooks::run( 'SidebarBeforeOutput', [ $this, &$sidebar ] );
1213
1214
		return $sidebar;
1215
	}
1216
1217
	/**
1218
	 * Add content from a sidebar system message
1219
	 * Currently only used for MediaWiki:Sidebar (but may be used by Extensions)
1220
	 *
1221
	 * This is just a wrapper around addToSidebarPlain() for backwards compatibility
1222
	 *
1223
	 * @param array $bar
1224
	 * @param string $message
1225
	 */
1226
	public function addToSidebar( &$bar, $message ) {
1227
		$this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() );
1228
	}
1229
1230
	/**
1231
	 * Add content from plain text
1232
	 * @since 1.17
1233
	 * @param array $bar
1234
	 * @param string $text
1235
	 * @return array
1236
	 */
1237
	function addToSidebarPlain( &$bar, $text ) {
1238
		$lines = explode( "\n", $text );
1239
1240
		$heading = '';
1241
1242
		foreach ( $lines as $line ) {
1243
			if ( strpos( $line, '*' ) !== 0 ) {
1244
				continue;
1245
			}
1246
			$line = rtrim( $line, "\r" ); // for Windows compat
1247
1248
			if ( strpos( $line, '**' ) !== 0 ) {
1249
				$heading = trim( $line, '* ' );
1250
				if ( !array_key_exists( $heading, $bar ) ) {
1251
					$bar[$heading] = [];
1252
				}
1253
			} else {
1254
				$line = trim( $line, '* ' );
1255
1256
				if ( strpos( $line, '|' ) !== false ) { // sanity check
1257
					$line = MessageCache::singleton()->transform( $line, false, null, $this->getTitle() );
1258
					$line = array_map( 'trim', explode( '|', $line, 2 ) );
1259
					if ( count( $line ) !== 2 ) {
1260
						// Second sanity check, could be hit by people doing
1261
						// funky stuff with parserfuncs... (bug 33321)
1262
						continue;
1263
					}
1264
1265
					$extraAttribs = [];
1266
1267
					$msgLink = $this->msg( $line[0] )->inContentLanguage();
1268
					if ( $msgLink->exists() ) {
1269
						$link = $msgLink->text();
1270
						if ( $link == '-' ) {
1271
							continue;
1272
						}
1273
					} else {
1274
						$link = $line[0];
1275
					}
1276
					$msgText = $this->msg( $line[1] );
1277
					if ( $msgText->exists() ) {
1278
						$text = $msgText->text();
1279
					} else {
1280
						$text = $line[1];
1281
					}
1282
1283
					if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $link ) ) {
1284
						$href = $link;
1285
1286
						// Parser::getExternalLinkAttribs won't work here because of the Namespace things
1287
						global $wgNoFollowLinks, $wgNoFollowDomainExceptions;
1288
						if ( $wgNoFollowLinks && !wfMatchesDomainList( $href, $wgNoFollowDomainExceptions ) ) {
1289
							$extraAttribs['rel'] = 'nofollow';
1290
						}
1291
1292
						global $wgExternalLinkTarget;
1293
						if ( $wgExternalLinkTarget ) {
1294
							$extraAttribs['target'] = $wgExternalLinkTarget;
1295
						}
1296
					} else {
1297
						$title = Title::newFromText( $link );
1298
1299
						if ( $title ) {
1300
							$title = $title->fixSpecialName();
1301
							$href = $title->getLinkURL();
1302
						} else {
1303
							$href = 'INVALID-TITLE';
1304
						}
1305
					}
1306
1307
					$bar[$heading][] = array_merge( [
1308
						'text' => $text,
1309
						'href' => $href,
1310
						'id' => 'n-' . Sanitizer::escapeId( strtr( $line[1], ' ', '-' ), 'noninitial' ),
1311
						'active' => false
1312
					], $extraAttribs );
1313
				} else {
1314
					continue;
1315
				}
1316
			}
1317
		}
1318
1319
		return $bar;
1320
	}
1321
1322
	/**
1323
	 * Gets new talk page messages for the current user and returns an
1324
	 * appropriate alert message (or an empty string if there are no messages)
1325
	 * @return string
1326
	 */
1327
	function getNewtalks() {
1328
1329
		$newMessagesAlert = '';
1330
		$user = $this->getUser();
1331
		$newtalks = $user->getNewMessageLinks();
1332
		$out = $this->getOutput();
1333
1334
		// Allow extensions to disable or modify the new messages alert
1335
		if ( !Hooks::run( 'GetNewMessagesAlert', [ &$newMessagesAlert, $newtalks, $user, $out ] ) ) {
1336
			return '';
1337
		}
1338
		if ( $newMessagesAlert ) {
1339
			return $newMessagesAlert;
1340
		}
1341
1342
		if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
1343
			$uTalkTitle = $user->getTalkPage();
1344
			$lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
1345
			$nofAuthors = 0;
1346
			if ( $lastSeenRev !== null ) {
1347
				$plural = true; // Default if we have a last seen revision: if unknown, use plural
1348
				$latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL );
1349
				if ( $latestRev !== null ) {
1350
					// Singular if only 1 unseen revision, plural if several unseen revisions.
1351
					$plural = $latestRev->getParentId() !== $lastSeenRev->getId();
1352
					$nofAuthors = $uTalkTitle->countAuthorsBetween(
1353
						$lastSeenRev, $latestRev, 10, 'include_new' );
1354
				}
1355
			} else {
1356
				// Singular if no revision -> diff link will show latest change only in any case
1357
				$plural = false;
1358
			}
1359
			$plural = $plural ? 999 : 1;
1360
			// 999 signifies "more than one revision". We don't know how many, and even if we did,
1361
			// the number of revisions or authors is not necessarily the same as the number of
1362
			// "messages".
1363
			$newMessagesLink = Linker::linkKnown(
1364
				$uTalkTitle,
1365
				$this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
1366
				[],
1367
				[ 'redirect' => 'no' ]
1368
			);
1369
1370
			$newMessagesDiffLink = Linker::linkKnown(
1371
				$uTalkTitle,
1372
				$this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
1373
				[],
1374
				$lastSeenRev !== null
1375
					? [ 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' ]
1376
					: [ 'diff' => 'cur' ]
1377
			);
1378
1379
			if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
1380
				$newMessagesAlert = $this->msg(
1381
					'youhavenewmessagesfromusers',
1382
					$newMessagesLink,
1383
					$newMessagesDiffLink
1384
				)->numParams( $nofAuthors, $plural );
1385
			} else {
1386
				// $nofAuthors === 11 signifies "11 or more" ("more than 10")
1387
				$newMessagesAlert = $this->msg(
1388
					$nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
1389
					$newMessagesLink,
1390
					$newMessagesDiffLink
1391
				)->numParams( $plural );
1392
			}
1393
			$newMessagesAlert = $newMessagesAlert->text();
1394
			# Disable CDN cache
1395
			$out->setCdnMaxage( 0 );
1396
		} elseif ( count( $newtalks ) ) {
1397
			$sep = $this->msg( 'newtalkseparator' )->escaped();
1398
			$msgs = [];
1399
1400
			foreach ( $newtalks as $newtalk ) {
1401
				$msgs[] = Xml::element(
1402
					'a',
1403
					[ 'href' => $newtalk['link'] ], $newtalk['wiki']
1404
				);
1405
			}
1406
			$parts = implode( $sep, $msgs );
1407
			$newMessagesAlert = $this->msg( 'youhavenewmessagesmulti' )->rawParams( $parts )->escaped();
1408
			$out->setCdnMaxage( 0 );
1409
		}
1410
1411
		return $newMessagesAlert;
1412
	}
1413
1414
	/**
1415
	 * Get a cached notice
1416
	 *
1417
	 * @param string $name Message name, or 'default' for $wgSiteNotice
1418
	 * @return string|bool HTML fragment, or false to indicate that the caller
1419
	 *   should fall back to the next notice in its sequence
1420
	 */
1421
	private function getCachedNotice( $name ) {
1422
		global $wgRenderHashAppend, $parserMemc, $wgContLang;
1423
1424
		$needParse = false;
1425
1426
		if ( $name === 'default' ) {
1427
			// special case
1428
			global $wgSiteNotice;
1429
			$notice = $wgSiteNotice;
1430
			if ( empty( $notice ) ) {
1431
				return false;
1432
			}
1433
		} else {
1434
			$msg = $this->msg( $name )->inContentLanguage();
1435
			if ( $msg->isBlank() ) {
1436
				return '';
1437
			} elseif ( $msg->isDisabled() ) {
1438
				return false;
1439
			}
1440
			$notice = $msg->plain();
1441
		}
1442
1443
		// Use the extra hash appender to let eg SSL variants separately cache.
1444
		$key = wfMemcKey( $name . $wgRenderHashAppend );
1445
		$cachedNotice = $parserMemc->get( $key );
1446
		if ( is_array( $cachedNotice ) ) {
1447
			if ( md5( $notice ) == $cachedNotice['hash'] ) {
1448
				$notice = $cachedNotice['html'];
1449
			} else {
1450
				$needParse = true;
1451
			}
1452
		} else {
1453
			$needParse = true;
1454
		}
1455
1456
		if ( $needParse ) {
1457
			$parsed = $this->getOutput()->parse( $notice );
1458
			$parserMemc->set( $key, [ 'html' => $parsed, 'hash' => md5( $notice ) ], 600 );
1459
			$notice = $parsed;
1460
		}
1461
1462
		$notice = Html::rawElement( 'div', [ 'id' => 'localNotice',
1463
			'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ], $notice );
1464
		return $notice;
1465
	}
1466
1467
	/**
1468
	 * Get the site notice
1469
	 *
1470
	 * @return string HTML fragment
1471
	 */
1472
	function getSiteNotice() {
1473
		$siteNotice = '';
1474
1475
		if ( Hooks::run( 'SiteNoticeBefore', [ &$siteNotice, $this ] ) ) {
1476
			if ( is_object( $this->getUser() ) && $this->getUser()->isLoggedIn() ) {
1477
				$siteNotice = $this->getCachedNotice( 'sitenotice' );
1478
			} else {
1479
				$anonNotice = $this->getCachedNotice( 'anonnotice' );
1480
				if ( $anonNotice === false ) {
1481
					$siteNotice = $this->getCachedNotice( 'sitenotice' );
1482
				} else {
1483
					$siteNotice = $anonNotice;
1484
				}
1485
			}
1486
			if ( $siteNotice === false ) {
1487
				$siteNotice = $this->getCachedNotice( 'default' );
1488
			}
1489
		}
1490
1491
		Hooks::run( 'SiteNoticeAfter', [ &$siteNotice, $this ] );
1492
		return $siteNotice;
1493
	}
1494
1495
	/**
1496
	 * Create a section edit link.  This supersedes editSectionLink() and
1497
	 * editSectionLinkForOther().
1498
	 *
1499
	 * @param Title $nt The title being linked to (may not be the same as
1500
	 *   the current page, if the section is included from a template)
1501
	 * @param string $section The designation of the section being pointed to,
1502
	 *   to be included in the link, like "&section=$section"
1503
	 * @param string $tooltip The tooltip to use for the link: will be escaped
1504
	 *   and wrapped in the 'editsectionhint' message
1505
	 * @param string $lang Language code
1506
	 * @return string HTML to use for edit link
1507
	 */
1508
	public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
1509
		// HTML generated here should probably have userlangattributes
1510
		// added to it for LTR text on RTL pages
1511
1512
		$lang = wfGetLangObj( $lang );
1513
1514
		$attribs = [];
1515
		if ( !is_null( $tooltip ) ) {
1516
			# Bug 25462: undo double-escaping.
1517
			$tooltip = Sanitizer::decodeCharReferences( $tooltip );
1518
			$attribs['title'] = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
1519
				->inLanguage( $lang )->text();
1520
		}
1521
1522
		$links = [
1523
			'editsection' => [
1524
				'text' => wfMessage( 'editsection' )->inLanguage( $lang )->escaped(),
1525
				'targetTitle' => $nt,
1526
				'attribs' => $attribs,
1527
				'query' => [ 'action' => 'edit', 'section' => $section ],
1528
				'options' => [ 'noclasses', 'known' ]
1529
			]
1530
		];
1531
1532
		Hooks::run( 'SkinEditSectionLinks', [ $this, $nt, $section, $tooltip, &$links, $lang ] );
1533
1534
		$result = '<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>';
1535
1536
		$linksHtml = [];
1537
		foreach ( $links as $k => $linkDetails ) {
1538
			$linksHtml[] = Linker::link(
1539
				$linkDetails['targetTitle'],
1540
				$linkDetails['text'],
1541
				$linkDetails['attribs'],
1542
				$linkDetails['query'],
1543
				$linkDetails['options']
1544
			);
1545
		}
1546
1547
		$result .= implode(
1548
			'<span class="mw-editsection-divider">'
1549
				. wfMessage( 'pipe-separator' )->inLanguage( $lang )->text()
1550
				. '</span>',
1551
			$linksHtml
1552
		);
1553
1554
		$result .= '<span class="mw-editsection-bracket">]</span></span>';
1555
		// Deprecated, use SkinEditSectionLinks hook instead
1556
		Hooks::run(
1557
			'DoEditSectionLink',
1558
			[ $this, $nt, $section, $tooltip, &$result, $lang ],
1559
			'1.25'
1560
		);
1561
		return $result;
1562
	}
1563
1564
	/** @deprecated in 1.21 */
1565
	public function accesskey() {
1566
		wfDeprecated( __METHOD__, '1.21' );
1567
		return call_user_func_array( [ 'Linker', 'accesskey' ], func_get_args() );
1568
	}
1569
	/** @deprecated in 1.21 */
1570
	public function blockLink() {
1571
		wfDeprecated( __METHOD__, '1.21' );
1572
		return call_user_func_array( [ 'Linker', 'blockLink' ], func_get_args() );
1573
	}
1574
	/** @deprecated in 1.21 */
1575
	public function buildRollbackLink() {
1576
		wfDeprecated( __METHOD__, '1.21' );
1577
		return call_user_func_array( [ 'Linker', 'buildRollbackLink' ], func_get_args() );
1578
	}
1579
	/** @deprecated in 1.21 */
1580
	public function commentBlock() {
1581
		wfDeprecated( __METHOD__, '1.21' );
1582
		return call_user_func_array( [ 'Linker', 'commentBlock' ], func_get_args() );
1583
	}
1584
	/** @deprecated in 1.21 */
1585
	public function emailLink() {
1586
		wfDeprecated( __METHOD__, '1.21' );
1587
		return call_user_func_array( [ 'Linker', 'emailLink' ], func_get_args() );
1588
	}
1589
	/** @deprecated in 1.21 */
1590
	public function formatComment() {
1591
		wfDeprecated( __METHOD__, '1.21' );
1592
		return call_user_func_array( [ 'Linker', 'formatComment' ], func_get_args() );
1593
	}
1594
	/** @deprecated in 1.21 */
1595
	public function formatHiddenCategories() {
1596
		wfDeprecated( __METHOD__, '1.21' );
1597
		return call_user_func_array( [ 'Linker', 'formatHiddenCategories' ], func_get_args() );
1598
	}
1599
	/** @deprecated in 1.21 */
1600
	public function formatLinksInComment() {
1601
		wfDeprecated( __METHOD__, '1.21' );
1602
		return call_user_func_array( [ 'Linker', 'formatLinksInComment' ], func_get_args() );
1603
	}
1604
	/** @deprecated in 1.21 */
1605
	public function formatRevisionSize() {
1606
		wfDeprecated( __METHOD__, '1.21' );
1607
		return call_user_func_array( [ 'Linker', 'formatRevisionSize' ], func_get_args() );
1608
	}
1609
	/** @deprecated in 1.21 */
1610
	public function formatSize() {
1611
		wfDeprecated( __METHOD__, '1.21' );
1612
		return call_user_func_array( [ 'Linker', 'formatSize' ], func_get_args() );
1613
	}
1614
	/** @deprecated in 1.21 */
1615
	public function formatTemplates() {
1616
		wfDeprecated( __METHOD__, '1.21' );
1617
		return call_user_func_array( [ 'Linker', 'formatTemplates' ], func_get_args() );
1618
	}
1619
	/** @deprecated in 1.21 */
1620
	public function generateRollback() {
1621
		wfDeprecated( __METHOD__, '1.21' );
1622
		return call_user_func_array( [ 'Linker', 'generateRollback' ], func_get_args() );
1623
	}
1624
	/** @deprecated in 1.21 */
1625
	public function generateTOC() {
1626
		wfDeprecated( __METHOD__, '1.21' );
1627
		return call_user_func_array( [ 'Linker', 'generateTOC' ], func_get_args() );
1628
	}
1629
	/** @deprecated in 1.21 */
1630
	public function getInternalLinkAttributes() {
1631
		wfDeprecated( __METHOD__, '1.21' );
1632
		return call_user_func_array( [ 'Linker', 'getInternalLinkAttributes' ], func_get_args() );
1633
	}
1634
	/** @deprecated in 1.21 */
1635
	public function getInternalLinkAttributesObj() {
1636
		wfDeprecated( __METHOD__, '1.21' );
1637
		return call_user_func_array( [ 'Linker', 'getInternalLinkAttributesObj' ], func_get_args() );
1638
	}
1639
	/** @deprecated in 1.21 */
1640
	public function getInterwikiLinkAttributes() {
1641
		wfDeprecated( __METHOD__, '1.21' );
1642
		return call_user_func_array( [ 'Linker', 'getInterwikiLinkAttributes' ], func_get_args() );
1643
	}
1644
	/** @deprecated in 1.21 */
1645
	public function getInvalidTitleDescription() {
1646
		wfDeprecated( __METHOD__, '1.21' );
1647
		return call_user_func_array( [ 'Linker', 'getInvalidTitleDescription' ], func_get_args() );
1648
	}
1649
	/** @deprecated in 1.21 */
1650
	public function getLinkColour() {
1651
		wfDeprecated( __METHOD__, '1.21' );
1652
		return call_user_func_array( [ 'Linker', 'getLinkColour' ], func_get_args() );
1653
	}
1654
	/** @deprecated in 1.21 */
1655
	public function getRevDeleteLink() {
1656
		wfDeprecated( __METHOD__, '1.21' );
1657
		return call_user_func_array( [ 'Linker', 'getRevDeleteLink' ], func_get_args() );
1658
	}
1659
	/** @deprecated in 1.21 */
1660
	public function getRollbackEditCount() {
1661
		wfDeprecated( __METHOD__, '1.21' );
1662
		return call_user_func_array( [ 'Linker', 'getRollbackEditCount' ], func_get_args() );
1663
	}
1664
	/** @deprecated in 1.21 */
1665
	public function link() {
1666
		wfDeprecated( __METHOD__, '1.21' );
1667
		return call_user_func_array( [ 'Linker', 'link' ], func_get_args() );
1668
	}
1669
	/** @deprecated in 1.21 */
1670
	public function linkKnown() {
1671
		wfDeprecated( __METHOD__, '1.21' );
1672
		return call_user_func_array( [ 'Linker', 'linkKnown' ], func_get_args() );
1673
	}
1674
	/** @deprecated in 1.21 */
1675
	public function makeBrokenImageLinkObj() {
1676
		wfDeprecated( __METHOD__, '1.21' );
1677
		return call_user_func_array( [ 'Linker', 'makeBrokenImageLinkObj' ], func_get_args() );
1678
	}
1679
	/** @deprecated in 1.21 */
1680
	public function makeCommentLink() {
1681
		wfDeprecated( __METHOD__, '1.21' );
1682
		return call_user_func_array( [ 'Linker', 'makeCommentLink' ], func_get_args() );
1683
	}
1684
	/** @deprecated in 1.21 */
1685
	public function makeExternalImage() {
1686
		wfDeprecated( __METHOD__, '1.21' );
1687
		return call_user_func_array( [ 'Linker', 'makeExternalImage' ], func_get_args() );
1688
	}
1689
	/** @deprecated in 1.21 */
1690
	public function makeExternalLink() {
1691
		wfDeprecated( __METHOD__, '1.21' );
1692
		return call_user_func_array( [ 'Linker', 'makeExternalLink' ], func_get_args() );
1693
	}
1694
	/** @deprecated in 1.21 */
1695
	public function makeHeadline() {
1696
		wfDeprecated( __METHOD__, '1.21' );
1697
		return call_user_func_array( [ 'Linker', 'makeHeadline' ], func_get_args() );
1698
	}
1699
	/** @deprecated in 1.21 */
1700
	public function makeImageLink() {
1701
		wfDeprecated( __METHOD__, '1.21' );
1702
		return call_user_func_array( [ 'Linker', 'makeImageLink' ], func_get_args() );
1703
	}
1704
	/** @deprecated in 1.21 */
1705
	public function makeMediaLinkFile() {
1706
		wfDeprecated( __METHOD__, '1.21' );
1707
		return call_user_func_array( [ 'Linker', 'makeMediaLinkFile' ], func_get_args() );
1708
	}
1709
	/** @deprecated in 1.21 */
1710
	public function makeMediaLinkObj() {
1711
		wfDeprecated( __METHOD__, '1.21' );
1712
		return call_user_func_array( [ 'Linker', 'makeMediaLinkObj' ], func_get_args() );
1713
	}
1714
	/** @deprecated in 1.21 */
1715
	public function makeSelfLinkObj() {
1716
		wfDeprecated( __METHOD__, '1.21' );
1717
		return call_user_func_array( [ 'Linker', 'makeSelfLinkObj' ], func_get_args() );
1718
	}
1719
	/** @deprecated in 1.21 */
1720
	public function makeThumbLink2() {
1721
		wfDeprecated( __METHOD__, '1.21' );
1722
		return call_user_func_array( [ 'Linker', 'makeThumbLink2' ], func_get_args() );
1723
	}
1724
	/** @deprecated in 1.21 */
1725
	public function makeThumbLinkObj() {
1726
		wfDeprecated( __METHOD__, '1.21' );
1727
		return call_user_func_array( [ 'Linker', 'makeThumbLinkObj' ], func_get_args() );
1728
	}
1729
	/** @deprecated in 1.21 */
1730
	public function normaliseSpecialPage() {
1731
		wfDeprecated( __METHOD__, '1.21' );
1732
		return call_user_func_array( [ 'Linker', 'normaliseSpecialPage' ], func_get_args() );
1733
	}
1734
	/** @deprecated in 1.21 */
1735
	public function normalizeSubpageLink() {
1736
		wfDeprecated( __METHOD__, '1.21' );
1737
		return call_user_func_array( [ 'Linker', 'normalizeSubpageLink' ], func_get_args() );
1738
	}
1739
	/** @deprecated in 1.21 */
1740
	public function processResponsiveImages() {
1741
		wfDeprecated( __METHOD__, '1.21' );
1742
		return call_user_func_array( [ 'Linker', 'processResponsiveImages' ], func_get_args() );
1743
	}
1744
	/** @deprecated in 1.21 */
1745
	public function revComment() {
1746
		wfDeprecated( __METHOD__, '1.21' );
1747
		return call_user_func_array( [ 'Linker', 'revComment' ], func_get_args() );
1748
	}
1749
	/** @deprecated in 1.21 */
1750
	public function revDeleteLink() {
1751
		wfDeprecated( __METHOD__, '1.21' );
1752
		return call_user_func_array( [ 'Linker', 'revDeleteLink' ], func_get_args() );
1753
	}
1754
	/** @deprecated in 1.21 */
1755
	public function revDeleteLinkDisabled() {
1756
		wfDeprecated( __METHOD__, '1.21' );
1757
		return call_user_func_array( [ 'Linker', 'revDeleteLinkDisabled' ], func_get_args() );
1758
	}
1759
	/** @deprecated in 1.21 */
1760
	public function revUserLink() {
1761
		wfDeprecated( __METHOD__, '1.21' );
1762
		return call_user_func_array( [ 'Linker', 'revUserLink' ], func_get_args() );
1763
	}
1764
	/** @deprecated in 1.21 */
1765
	public function revUserTools() {
1766
		wfDeprecated( __METHOD__, '1.21' );
1767
		return call_user_func_array( [ 'Linker', 'revUserTools' ], func_get_args() );
1768
	}
1769
	/** @deprecated in 1.21 */
1770
	public function specialLink() {
1771
		wfDeprecated( __METHOD__, '1.21' );
1772
		return call_user_func_array( [ 'Linker', 'specialLink' ], func_get_args() );
1773
	}
1774
	/** @deprecated in 1.21 */
1775
	public function splitTrail() {
1776
		wfDeprecated( __METHOD__, '1.21' );
1777
		return call_user_func_array( [ 'Linker', 'splitTrail' ], func_get_args() );
1778
	}
1779
	/** @deprecated in 1.21 */
1780
	public function titleAttrib() {
1781
		wfDeprecated( __METHOD__, '1.21' );
1782
		return call_user_func_array( [ 'Linker', 'titleAttrib' ], func_get_args() );
1783
	}
1784
	/** @deprecated in 1.21 */
1785
	public function tocIndent() {
1786
		wfDeprecated( __METHOD__, '1.21' );
1787
		return call_user_func_array( [ 'Linker', 'tocIndent' ], func_get_args() );
1788
	}
1789
	/** @deprecated in 1.21 */
1790
	public function tocLine() {
1791
		wfDeprecated( __METHOD__, '1.21' );
1792
		return call_user_func_array( [ 'Linker', 'tocLine' ], func_get_args() );
1793
	}
1794
	/** @deprecated in 1.21 */
1795
	public function tocLineEnd() {
1796
		wfDeprecated( __METHOD__, '1.21' );
1797
		return call_user_func_array( [ 'Linker', 'tocLineEnd' ], func_get_args() );
1798
	}
1799
	/** @deprecated in 1.21 */
1800
	public function tocList() {
1801
		wfDeprecated( __METHOD__, '1.21' );
1802
		return call_user_func_array( [ 'Linker', 'tocList' ], func_get_args() );
1803
	}
1804
	/** @deprecated in 1.21 */
1805
	public function tocUnindent() {
1806
		wfDeprecated( __METHOD__, '1.21' );
1807
		return call_user_func_array( [ 'Linker', 'tocUnindent' ], func_get_args() );
1808
	}
1809
	/** @deprecated in 1.21 */
1810
	public function tooltip() {
1811
		wfDeprecated( __METHOD__, '1.21' );
1812
		return call_user_func_array( [ 'Linker', 'tooltip' ], func_get_args() );
1813
	}
1814
	/** @deprecated in 1.21 */
1815
	public function tooltipAndAccesskeyAttribs() {
1816
		wfDeprecated( __METHOD__, '1.21' );
1817
		return call_user_func_array( [ 'Linker', 'tooltipAndAccesskeyAttribs' ], func_get_args() );
1818
	}
1819
	/** @deprecated in 1.21 */
1820
	public function userLink() {
1821
		wfDeprecated( __METHOD__, '1.21' );
1822
		return call_user_func_array( [ 'Linker', 'userLink' ], func_get_args() );
1823
	}
1824
	/** @deprecated in 1.21 */
1825
	public function userTalkLink() {
1826
		wfDeprecated( __METHOD__, '1.21' );
1827
		return call_user_func_array( [ 'Linker', 'userTalkLink' ], func_get_args() );
1828
	}
1829
	/** @deprecated in 1.21 */
1830
	public function userToolLinks() {
1831
		wfDeprecated( __METHOD__, '1.21' );
1832
		return call_user_func_array( [ 'Linker', 'userToolLinks' ], func_get_args() );
1833
	}
1834
	/** @deprecated in 1.21 */
1835
	public function userToolLinksRedContribs() {
1836
		wfDeprecated( __METHOD__, '1.21' );
1837
		return call_user_func_array( [ 'Linker', 'userToolLinksRedContribs' ], func_get_args() );
1838
	}
1839
1840
}
1841