Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/skins/BaseTemplate.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This program is free software; you can redistribute it and/or modify
4
 * it under the terms of the GNU General Public License as published by
5
 * the Free Software Foundation; either version 2 of the License, or
6
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License along
14
 * with this program; if not, write to the Free Software Foundation, Inc.,
15
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
 * http://www.gnu.org/copyleft/gpl.html
17
 *
18
 * @file
19
 */
20
21
/**
22
 * New base template for a skin's template extended from QuickTemplate
23
 * this class features helper methods that provide common ways of interacting
24
 * with the data stored in the QuickTemplate
25
 */
26
abstract class BaseTemplate extends QuickTemplate {
27
28
	/**
29
	 * Get a Message object with its context set
30
	 *
31
	 * @param string $name Message name
32
	 * @param ... $params Message params
0 ignored issues
show
The doc-type ... could not be parsed: Unknown type name "..." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
There is no parameter named $params. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
33
	 * @return Message
34
	 */
35
	public function getMsg( $name /* ... */ ) {
36
		return call_user_func_array( [ $this->getSkin(), 'msg' ], func_get_args() );
37
	}
38
39
	function msg( $str ) {
40
		echo $this->getMsg( $str )->escaped();
41
	}
42
43
	function msgHtml( $str ) {
44
		echo $this->getMsg( $str )->text();
45
	}
46
47
	function msgWiki( $str ) {
48
		echo $this->getMsg( $str )->parseAsBlock();
49
	}
50
51
	/**
52
	 * Create an array of common toolbox items from the data in the quicktemplate
53
	 * stored by SkinTemplate.
54
	 * The resulting array is built according to a format intended to be passed
55
	 * through makeListItem to generate the html.
56
	 * @return array
57
	 */
58
	function getToolbox() {
59
		$toolbox = [];
60 View Code Duplication
		if ( isset( $this->data['nav_urls']['whatlinkshere'] )
61
			&& $this->data['nav_urls']['whatlinkshere']
62
		) {
63
			$toolbox['whatlinkshere'] = $this->data['nav_urls']['whatlinkshere'];
0 ignored issues
show
The property data does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
64
			$toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
65
		}
66
		if ( isset( $this->data['nav_urls']['recentchangeslinked'] )
67
			&& $this->data['nav_urls']['recentchangeslinked']
68
		) {
69
			$toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
70
			$toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
71
			$toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
72
			$toolbox['recentchangeslinked']['rel'] = 'nofollow';
73
		}
74
		if ( isset( $this->data['feeds'] ) && $this->data['feeds'] ) {
75
			$toolbox['feeds']['id'] = 'feedlinks';
76
			$toolbox['feeds']['links'] = [];
77
			foreach ( $this->data['feeds'] as $key => $feed ) {
78
				$toolbox['feeds']['links'][$key] = $feed;
79
				$toolbox['feeds']['links'][$key]['id'] = "feed-$key";
80
				$toolbox['feeds']['links'][$key]['rel'] = 'alternate';
81
				$toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
82
				$toolbox['feeds']['links'][$key]['class'] = 'feedlink';
83
			}
84
		}
85
		foreach ( [ 'contributions', 'log', 'blockip', 'emailuser',
86
			'userrights', 'upload', 'specialpages' ] as $special
87
		) {
88
			if ( isset( $this->data['nav_urls'][$special] ) && $this->data['nav_urls'][$special] ) {
89
				$toolbox[$special] = $this->data['nav_urls'][$special];
90
				$toolbox[$special]['id'] = "t-$special";
91
			}
92
		}
93
		if ( isset( $this->data['nav_urls']['print'] ) && $this->data['nav_urls']['print'] ) {
94
			$toolbox['print'] = $this->data['nav_urls']['print'];
95
			$toolbox['print']['id'] = 't-print';
96
			$toolbox['print']['rel'] = 'alternate';
97
			$toolbox['print']['msg'] = 'printableversion';
98
		}
99
		if ( isset( $this->data['nav_urls']['permalink'] ) && $this->data['nav_urls']['permalink'] ) {
100
			$toolbox['permalink'] = $this->data['nav_urls']['permalink'];
101
			if ( $toolbox['permalink']['href'] === '' ) {
102
				unset( $toolbox['permalink']['href'] );
103
				$toolbox['ispermalink']['tooltiponly'] = true;
104
				$toolbox['ispermalink']['id'] = 't-ispermalink';
105
				$toolbox['ispermalink']['msg'] = 'permalink';
106
			} else {
107
				$toolbox['permalink']['id'] = 't-permalink';
108
			}
109
		}
110 View Code Duplication
		if ( isset( $this->data['nav_urls']['info'] ) && $this->data['nav_urls']['info'] ) {
111
			$toolbox['info'] = $this->data['nav_urls']['info'];
112
			$toolbox['info']['id'] = 't-info';
113
		}
114
115
		Hooks::run( 'BaseTemplateToolbox', [ &$this, &$toolbox ] );
116
		return $toolbox;
117
	}
118
119
	/**
120
	 * Create an array of personal tools items from the data in the quicktemplate
121
	 * stored by SkinTemplate.
122
	 * The resulting array is built according to a format intended to be passed
123
	 * through makeListItem to generate the html.
124
	 * This is in reality the same list as already stored in personal_urls
125
	 * however it is reformatted so that you can just pass the individual items
126
	 * to makeListItem instead of hardcoding the element creation boilerplate.
127
	 * @return array
128
	 */
129
	function getPersonalTools() {
130
		$personal_tools = [];
131
		foreach ( $this->get( 'personal_urls' ) as $key => $plink ) {
132
			# The class on a personal_urls item is meant to go on the <a> instead
133
			# of the <li> so we have to use a single item "links" array instead
134
			# of using most of the personal_url's keys directly.
135
			$ptool = [
136
				'links' => [
137
					[ 'single-id' => "pt-$key" ],
138
				],
139
				'id' => "pt-$key",
140
			];
141
			if ( isset( $plink['active'] ) ) {
142
				$ptool['active'] = $plink['active'];
143
			}
144
			foreach ( [ 'href', 'class', 'text', 'dir', 'data' ] as $k ) {
145
				if ( isset( $plink[$k] ) ) {
146
					$ptool['links'][0][$k] = $plink[$k];
147
				}
148
			}
149
			$personal_tools[$key] = $ptool;
150
		}
151
		return $personal_tools;
152
	}
153
154
	function getSidebar( $options = [] ) {
155
		// Force the rendering of the following portals
156
		$sidebar = $this->data['sidebar'];
157
		if ( !isset( $sidebar['SEARCH'] ) ) {
158
			$sidebar['SEARCH'] = true;
159
		}
160
		if ( !isset( $sidebar['TOOLBOX'] ) ) {
161
			$sidebar['TOOLBOX'] = true;
162
		}
163
		if ( !isset( $sidebar['LANGUAGES'] ) ) {
164
			$sidebar['LANGUAGES'] = true;
165
		}
166
167
		if ( !isset( $options['search'] ) || $options['search'] !== true ) {
168
			unset( $sidebar['SEARCH'] );
169
		}
170
		if ( isset( $options['toolbox'] ) && $options['toolbox'] === false ) {
171
			unset( $sidebar['TOOLBOX'] );
172
		}
173
		if ( isset( $options['languages'] ) && $options['languages'] === false ) {
174
			unset( $sidebar['LANGUAGES'] );
175
		}
176
177
		$boxes = [];
178
		foreach ( $sidebar as $boxName => $content ) {
179
			if ( $content === false ) {
180
				continue;
181
			}
182
			switch ( $boxName ) {
183
			case 'SEARCH':
184
				// Search is a special case, skins should custom implement this
185
				$boxes[$boxName] = [
186
					'id' => 'p-search',
187
					'header' => $this->getMsg( 'search' )->text(),
188
					'generated' => false,
189
					'content' => true,
190
				];
191
				break;
192
			case 'TOOLBOX':
193
				$msgObj = $this->getMsg( 'toolbox' );
194
				$boxes[$boxName] = [
195
					'id' => 'p-tb',
196
					'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
197
					'generated' => false,
198
					'content' => $this->getToolbox(),
199
				];
200
				break;
201
			case 'LANGUAGES':
202
				if ( $this->data['language_urls'] ) {
203
					$msgObj = $this->getMsg( 'otherlanguages' );
204
					$boxes[$boxName] = [
205
						'id' => 'p-lang',
206
						'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
207
						'generated' => false,
208
						'content' => $this->data['language_urls'],
209
					];
210
				}
211
				break;
212
			default:
213
				$msgObj = $this->getMsg( $boxName );
214
				$boxes[$boxName] = [
215
					'id' => "p-$boxName",
216
					'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
217
					'generated' => true,
218
					'content' => $content,
219
				];
220
				break;
221
			}
222
		}
223
224
		// HACK: Compatibility with extensions still using SkinTemplateToolboxEnd
225
		$hookContents = null;
226
		if ( isset( $boxes['TOOLBOX'] ) ) {
227
			ob_start();
228
			// We pass an extra 'true' at the end so extensions using BaseTemplateToolbox
229
			// can abort and avoid outputting double toolbox links
230
			Hooks::run( 'SkinTemplateToolboxEnd', [ &$this, true ] );
231
			$hookContents = ob_get_contents();
232
			ob_end_clean();
233
			if ( !trim( $hookContents ) ) {
234
				$hookContents = null;
235
			}
236
		}
237
		// END hack
238
239
		if ( isset( $options['htmlOnly'] ) && $options['htmlOnly'] === true ) {
240
			foreach ( $boxes as $boxName => $box ) {
241
				if ( is_array( $box['content'] ) ) {
242
					$content = '<ul>';
243
					foreach ( $box['content'] as $key => $val ) {
244
						$content .= "\n	" . $this->makeListItem( $key, $val );
245
					}
246
					// HACK, shove the toolbox end onto the toolbox if we're rendering itself
247
					if ( $hookContents ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $hookContents of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
248
						$content .= "\n	$hookContents";
249
					}
250
					// END hack
251
					$content .= "\n</ul>\n";
252
					$boxes[$boxName]['content'] = $content;
253
				}
254
			}
255
		} else {
256
			if ( $hookContents ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $hookContents of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
257
				$boxes['TOOLBOXEND'] = [
258
					'id' => 'p-toolboxend',
259
					'header' => $boxes['TOOLBOX']['header'],
260
					'generated' => false,
261
					'content' => "<ul>{$hookContents}</ul>",
262
				];
263
				// HACK: Make sure that TOOLBOXEND is sorted next to TOOLBOX
264
				$boxes2 = [];
265
				foreach ( $boxes as $key => $box ) {
266
					if ( $key === 'TOOLBOXEND' ) {
267
						continue;
268
					}
269
					$boxes2[$key] = $box;
270
					if ( $key === 'TOOLBOX' ) {
271
						$boxes2['TOOLBOXEND'] = $boxes['TOOLBOXEND'];
272
					}
273
				}
274
				$boxes = $boxes2;
275
				// END hack
276
			}
277
		}
278
279
		return $boxes;
280
	}
281
282
	/**
283
	 * @param string $name
284
	 */
285
	protected function renderAfterPortlet( $name ) {
286
		$content = '';
287
		Hooks::run( 'BaseTemplateAfterPortlet', [ $this, $name, &$content ] );
288
289
		if ( $content !== '' ) {
290
			echo "<div class='after-portlet after-portlet-$name'>$content</div>";
291
		}
292
	}
293
294
	/**
295
	 * Makes a link, usually used by makeListItem to generate a link for an item
296
	 * in a list used in navigation lists, portlets, portals, sidebars, etc...
297
	 *
298
	 * @param string $key Usually a key from the list you are generating this
299
	 * link from.
300
	 * @param array $item Contains some of a specific set of keys.
301
	 *
302
	 * The text of the link will be generated either from the contents of the
303
	 * "text" key in the $item array, if a "msg" key is present a message by
304
	 * that name will be used, and if neither of those are set the $key will be
305
	 * used as a message name.
306
	 *
307
	 * If a "href" key is not present makeLink will just output htmlescaped text.
308
	 * The "href", "id", "class", "rel", and "type" keys are used as attributes
309
	 * for the link if present.
310
	 *
311
	 * If an "id" or "single-id" (if you don't want the actual id to be output
312
	 * on the link) is present it will be used to generate a tooltip and
313
	 * accesskey for the link.
314
	 *
315
	 * The keys "context" and "primary" are ignored; these keys are used
316
	 * internally by skins and are not supposed to be included in the HTML
317
	 * output.
318
	 *
319
	 * If you don't want an accesskey, set $item['tooltiponly'] = true;
320
	 *
321
	 * If a "data" key is present, it must be an array, where the keys represent
322
	 * the data-xxx properties with their provided values. For example,
323
	 *  $item['data'] = [
324
	 *  	 'foo' => 1,
325
	 *  	 'bar' => 'baz',
326
	 *  ];
327
	 * will render as element properties:
328
	 *  data-foo='1' data-bar='baz'
329
	 *
330
	 * @param array $options Can be used to affect the output of a link.
331
	 * Possible options are:
332
	 *   - 'text-wrapper' key to specify a list of elements to wrap the text of
333
	 *   a link in. This should be an array of arrays containing a 'tag' and
334
	 *   optionally an 'attributes' key. If you only have one element you don't
335
	 *   need to wrap it in another array. eg: To use <a><span>...</span></a>
336
	 *   in all links use [ 'text-wrapper' => [ 'tag' => 'span' ] ]
337
	 *   for your options.
338
	 *   - 'link-class' key can be used to specify additional classes to apply
339
	 *   to all links.
340
	 *   - 'link-fallback' can be used to specify a tag to use instead of "<a>"
341
	 *   if there is no link. eg: If you specify 'link-fallback' => 'span' than
342
	 *   any non-link will output a "<span>" instead of just text.
343
	 *
344
	 * @return string
345
	 */
346
	function makeLink( $key, $item, $options = [] ) {
347
		if ( isset( $item['text'] ) ) {
348
			$text = $item['text'];
349
		} else {
350
			$text = $this->translator->translate( isset( $item['msg'] ) ? $item['msg'] : $key );
0 ignored issues
show
The property translator does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
351
		}
352
353
		$html = htmlspecialchars( $text );
354
355
		if ( isset( $options['text-wrapper'] ) ) {
356
			$wrapper = $options['text-wrapper'];
357
			if ( isset( $wrapper['tag'] ) ) {
358
				$wrapper = [ $wrapper ];
359
			}
360
			while ( count( $wrapper ) > 0 ) {
361
				$element = array_pop( $wrapper );
362
				$html = Html::rawElement( $element['tag'], isset( $element['attributes'] )
363
					? $element['attributes']
364
					: null, $html );
365
			}
366
		}
367
368
		if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
369
			$attrs = $item;
370
			foreach ( [ 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary',
371
				'tooltip-params' ] as $k ) {
372
				unset( $attrs[$k] );
373
			}
374
375
			if ( isset( $attrs['data'] ) ) {
376
				foreach ( $attrs['data'] as $key => $value ) {
377
					$attrs[ 'data-' . $key ] = $value;
378
				}
379
				unset( $attrs[ 'data' ] );
380
			}
381
382 View Code Duplication
			if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
383
				$item['single-id'] = $item['id'];
384
			}
385
386
			$tooltipParams = [];
387
			if ( isset( $item['tooltip-params'] ) ) {
388
				$tooltipParams = $item['tooltip-params'];
389
			}
390
391
			if ( isset( $item['single-id'] ) ) {
392
				if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
393
					$title = Linker::titleAttrib( $item['single-id'], null, $tooltipParams );
394
					if ( $title !== false ) {
395
						$attrs['title'] = $title;
396
					}
397
				} else {
398
					$tip = Linker::tooltipAndAccesskeyAttribs( $item['single-id'], $tooltipParams );
399
					if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
400
						$attrs['title'] = $tip['title'];
401
					}
402
					if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
403
						$attrs['accesskey'] = $tip['accesskey'];
404
					}
405
				}
406
			}
407 View Code Duplication
			if ( isset( $options['link-class'] ) ) {
408
				if ( isset( $attrs['class'] ) ) {
409
					$attrs['class'] .= " {$options['link-class']}";
410
				} else {
411
					$attrs['class'] = $options['link-class'];
412
				}
413
			}
414
			$html = Html::rawElement( isset( $attrs['href'] )
415
				? 'a'
416
				: $options['link-fallback'], $attrs, $html );
417
		}
418
419
		return $html;
420
	}
421
422
	/**
423
	 * Generates a list item for a navigation, portlet, portal, sidebar... list
424
	 *
425
	 * @param string $key Usually a key from the list you are generating this link from.
426
	 * @param array $item Array of list item data containing some of a specific set of keys.
427
	 * The "id", "class" and "itemtitle" keys will be used as attributes for the list item,
428
	 * if "active" contains a value of true a "active" class will also be appended to class.
429
	 *
430
	 * @param array $options
431
	 *
432
	 * If you want something other than a "<li>" you can pass a tag name such as
433
	 * "tag" => "span" in the $options array to change the tag used.
434
	 * link/content data for the list item may come in one of two forms
435
	 * A "links" key may be used, in which case it should contain an array with
436
	 * a list of links to include inside the list item, see makeLink for the
437
	 * format of individual links array items.
438
	 *
439
	 * Otherwise the relevant keys from the list item $item array will be passed
440
	 * to makeLink instead. Note however that "id" and "class" are used by the
441
	 * list item directly so they will not be passed to makeLink
442
	 * (however the link will still support a tooltip and accesskey from it)
443
	 * If you need an id or class on a single link you should include a "links"
444
	 * array with just one link item inside of it. You can also set "link-class" in
445
	 * $item to set a class on the link itself. If you want to add a title
446
	 * to the list item itself, you can set "itemtitle" to the value.
447
	 * $options is also passed on to makeLink calls
448
	 *
449
	 * @return string
450
	 */
451
	function makeListItem( $key, $item, $options = [] ) {
452
		if ( isset( $item['links'] ) ) {
453
			$links = [];
454
			foreach ( $item['links'] as $linkKey => $link ) {
455
				$links[] = $this->makeLink( $linkKey, $link, $options );
456
			}
457
			$html = implode( ' ', $links );
458
		} else {
459
			$link = $item;
460
			// These keys are used by makeListItem and shouldn't be passed on to the link
461
			foreach ( [ 'id', 'class', 'active', 'tag', 'itemtitle' ] as $k ) {
462
				unset( $link[$k] );
463
			}
464 View Code Duplication
			if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
465
				// The id goes on the <li> not on the <a> for single links
466
				// but makeSidebarLink still needs to know what id to use when
467
				// generating tooltips and accesskeys.
468
				$link['single-id'] = $item['id'];
469
			}
470
			if ( isset( $link['link-class'] ) ) {
471
				// link-class should be set on the <a> itself,
472
				// so pass it in as 'class'
473
				$link['class'] = $link['link-class'];
474
				unset( $link['link-class'] );
475
			}
476
			$html = $this->makeLink( $key, $link, $options );
477
		}
478
479
		$attrs = [];
480
		foreach ( [ 'id', 'class' ] as $attr ) {
481
			if ( isset( $item[$attr] ) ) {
482
				$attrs[$attr] = $item[$attr];
483
			}
484
		}
485
		if ( isset( $item['active'] ) && $item['active'] ) {
486
			if ( !isset( $attrs['class'] ) ) {
487
				$attrs['class'] = '';
488
			}
489
			$attrs['class'] .= ' active';
490
			$attrs['class'] = trim( $attrs['class'] );
491
		}
492
		if ( isset( $item['itemtitle'] ) ) {
493
			$attrs['title'] = $item['itemtitle'];
494
		}
495
		return Html::rawElement( isset( $options['tag'] ) ? $options['tag'] : 'li', $attrs, $html );
496
	}
497
498
	function makeSearchInput( $attrs = [] ) {
499
		$realAttrs = [
500
			'type' => 'search',
501
			'name' => 'search',
502
			'placeholder' => wfMessage( 'searchsuggest-search' )->text(),
503
			'value' => $this->get( 'search', '' ),
504
		];
505
		$realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
506
		return Html::element( 'input', $realAttrs );
507
	}
508
509
	function makeSearchButton( $mode, $attrs = [] ) {
510
		switch ( $mode ) {
511
			case 'go':
512
			case 'fulltext':
513
				$realAttrs = [
514
					'type' => 'submit',
515
					'name' => $mode,
516
					'value' => $this->translator->translate(
517
						$mode == 'go' ? 'searcharticle' : 'searchbutton' ),
518
				];
519
				$realAttrs = array_merge(
520
					$realAttrs,
521
					Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
522
					$attrs
523
				);
524
				return Html::element( 'input', $realAttrs );
525
			case 'image':
526
				$buttonAttrs = [
527
					'type' => 'submit',
528
					'name' => 'button',
529
				];
530
				$buttonAttrs = array_merge(
531
					$buttonAttrs,
532
					Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
533
					$attrs
534
				);
535
				unset( $buttonAttrs['src'] );
536
				unset( $buttonAttrs['alt'] );
537
				unset( $buttonAttrs['width'] );
538
				unset( $buttonAttrs['height'] );
539
				$imgAttrs = [
540
					'src' => $attrs['src'],
541
					'alt' => isset( $attrs['alt'] )
542
						? $attrs['alt']
543
						: $this->translator->translate( 'searchbutton' ),
544
					'width' => isset( $attrs['width'] ) ? $attrs['width'] : null,
545
					'height' => isset( $attrs['height'] ) ? $attrs['height'] : null,
546
				];
547
				return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
548
			default:
549
				throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
550
		}
551
	}
552
553
	/**
554
	 * Returns an array of footerlinks trimmed down to only those footer links that
555
	 * are valid.
556
	 * If you pass "flat" as an option then the returned array will be a flat array
557
	 * of footer icons instead of a key/value array of footerlinks arrays broken
558
	 * up into categories.
559
	 * @param string $option
560
	 * @return array|mixed
561
	 */
562
	function getFooterLinks( $option = null ) {
563
		$footerlinks = $this->get( 'footerlinks' );
564
565
		// Reduce footer links down to only those which are being used
566
		$validFooterLinks = [];
567
		foreach ( $footerlinks as $category => $links ) {
568
			$validFooterLinks[$category] = [];
569
			foreach ( $links as $link ) {
570
				if ( isset( $this->data[$link] ) && $this->data[$link] ) {
571
					$validFooterLinks[$category][] = $link;
572
				}
573
			}
574
			if ( count( $validFooterLinks[$category] ) <= 0 ) {
575
				unset( $validFooterLinks[$category] );
576
			}
577
		}
578
579
		if ( $option == 'flat' ) {
580
			// fold footerlinks into a single array using a bit of trickery
581
			$validFooterLinks = call_user_func_array(
582
				'array_merge',
583
				array_values( $validFooterLinks )
584
			);
585
		}
586
587
		return $validFooterLinks;
588
	}
589
590
	/**
591
	 * Returns an array of footer icons filtered down by options relevant to how
592
	 * the skin wishes to display them.
593
	 * If you pass "icononly" as the option all footer icons which do not have an
594
	 * image icon set will be filtered out.
595
	 * If you pass "nocopyright" then MediaWiki's copyright icon will not be included
596
	 * in the list of footer icons. This is mostly useful for skins which only
597
	 * display the text from footericons instead of the images and don't want a
598
	 * duplicate copyright statement because footerlinks already rendered one.
599
	 * @param string $option
600
	 * @return array
601
	 */
602
	function getFooterIcons( $option = null ) {
603
		// Generate additional footer icons
604
		$footericons = $this->get( 'footericons' );
605
606
		if ( $option == 'icononly' ) {
607
			// Unset any icons which don't have an image
608
			foreach ( $footericons as &$footerIconsBlock ) {
609
				foreach ( $footerIconsBlock as $footerIconKey => $footerIcon ) {
610
					if ( !is_string( $footerIcon ) && !isset( $footerIcon['src'] ) ) {
611
						unset( $footerIconsBlock[$footerIconKey] );
612
					}
613
				}
614
			}
615
			// Redo removal of any empty blocks
616
			foreach ( $footericons as $footerIconsKey => &$footerIconsBlock ) {
617
				if ( count( $footerIconsBlock ) <= 0 ) {
618
					unset( $footericons[$footerIconsKey] );
619
				}
620
			}
621
		} elseif ( $option == 'nocopyright' ) {
622
			unset( $footericons['copyright']['copyright'] );
623
			if ( count( $footericons['copyright'] ) <= 0 ) {
624
				unset( $footericons['copyright'] );
625
			}
626
		}
627
628
		return $footericons;
629
	}
630
631
	/**
632
	 * Get the suggested HTML for page status indicators: icons (or short text snippets) usually
633
	 * displayed in the top-right corner of the page, outside of the main content.
634
	 *
635
	 * Your skin may implement this differently, for example by handling some indicator names
636
	 * specially with a different UI. However, it is recommended to use a `<div class="mw-indicator"
637
	 * id="mw-indicator-<id>" />` as a wrapper element for each indicator, for better compatibility
638
	 * with extensions and user scripts.
639
	 *
640
	 * The raw data is available in `$this->data['indicators']` as an associative array (keys:
641
	 * identifiers, values: contents) internally ordered by keys.
642
	 *
643
	 * @return string HTML
644
	 * @since 1.25
645
	 */
646
	public function getIndicators() {
647
		$out = "<div class=\"mw-indicators\">\n";
648
		foreach ( $this->data['indicators'] as $id => $content ) {
649
			$out .= Html::rawElement(
650
				'div',
651
				[
652
					'id' => Sanitizer::escapeId( "mw-indicator-$id" ),
653
					'class' => 'mw-indicator',
654
				],
655
				$content
656
			) . "\n";
657
		}
658
		$out .= "</div>\n";
659
		return $out;
660
	}
661
662
	/**
663
	 * Output the basic end-page trail including bottomscripts, reporttime, and
664
	 * debug stuff. This should be called right before outputting the closing
665
	 * body and html tags.
666
	 */
667
	function printTrail() {
668
?>
669
<?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() ); ?>
670
<?php $this->html( 'bottomscripts' ); /* JS call to runBodyOnloadHook */ ?>
671
<?php $this->html( 'reporttime' ) ?>
672
<?php
673
	}
674
}
675