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/specials/pagers/AllMessagesTablePager.php (11 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
 * @ingroup Pager
20
 */
21
22
/**
23
 * Use TablePager for prettified output. We have to pretend that we're
24
 * getting data from a table when in fact not all of it comes from the database.
25
 *
26
 * @ingroup Pager
27
 */
28
class AllMessagesTablePager extends TablePager {
29
30
	protected $filter, $prefix, $langcode, $displayPrefix;
0 ignored issues
show
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
31
32
	public $mLimitsShown;
33
34
	/**
35
	 * @var Language
36
	 */
37
	public $lang;
38
39
	/**
40
	 * @var null|bool
41
	 */
42
	public $custom;
43
44
	function __construct( $page, $conds, $langObj = null ) {
45
		parent::__construct( $page->getContext() );
46
		$this->mIndexField = 'am_title';
47
		$this->mPage = $page;
0 ignored issues
show
The property mPage 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...
48
		$this->mConds = $conds;
0 ignored issues
show
The property mConds 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...
49
		// FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
50
		$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
51
		$this->mLimitsShown = [ 20, 50, 100, 250, 500, 5000 ];
52
53
		global $wgContLang;
54
55
		$this->talk = $this->msg( 'talkpagelinktext' )->escaped();
0 ignored issues
show
The property talk 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...
56
57
		$this->lang = ( $langObj ? $langObj : $wgContLang );
58
		$this->langcode = $this->lang->getCode();
59
		$this->foreign = !$this->lang->equals( $wgContLang );
0 ignored issues
show
The property foreign 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...
60
61
		$request = $this->getRequest();
62
63
		$this->filter = $request->getVal( 'filter', 'all' );
64
		if ( $this->filter === 'all' ) {
65
			$this->custom = null; // So won't match in either case
66
		} else {
67
			$this->custom = ( $this->filter === 'unmodified' );
68
		}
69
70
		$prefix = $this->getLanguage()->ucfirst( $request->getVal( 'prefix', '' ) );
71
		$prefix = $prefix !== '' ?
72
			Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) :
73
			null;
74
75
		if ( $prefix !== null ) {
76
			$this->displayPrefix = $prefix->getDBkey();
77
			$this->prefix = '/^' . preg_quote( $this->displayPrefix, '/' ) . '/i';
78
		} else {
79
			$this->displayPrefix = false;
80
			$this->prefix = false;
81
		}
82
83
		// The suffix that may be needed for message names if we're in a
84
		// different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
85
		if ( $this->foreign ) {
86
			$this->suffix = '/' . $this->langcode;
0 ignored issues
show
The property suffix 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...
87
		} else {
88
			$this->suffix = '';
89
		}
90
	}
91
92
	function buildForm() {
93
		$attrs = [ 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' ];
94
		$msg = wfMessage( 'allmessages-language' );
95
		$langSelect = Xml::languageSelector( $this->langcode, false, null, $attrs, $msg );
96
97
		$out = Xml::openElement( 'form', [
98
				'method' => 'get',
99
				'action' => $this->getConfig()->get( 'Script' ),
100
				'id' => 'mw-allmessages-form'
101
			] ) .
102
			Xml::fieldset( $this->msg( 'allmessages-filter-legend' )->text() ) .
103
			Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
104
			Xml::openElement( 'table', [ 'class' => 'mw-allmessages-table' ] ) . "\n" .
105
			'<tr>
106
				<td class="mw-label">' .
107
			Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) .
108
			"</td>\n
109
			<td class=\"mw-input\">" .
110
			Xml::input(
111
				'prefix',
112
				20,
113
				str_replace( '_', ' ', $this->displayPrefix ),
114
				[ 'id' => 'mw-allmessages-form-prefix' ]
115
			) .
116
			"</td>\n
117
			</tr>
118
			<tr>\n
119
			<td class='mw-label'>" .
120
			$this->msg( 'allmessages-filter' )->escaped() .
121
			"</td>\n
122
				<td class='mw-input'>" .
123
			Xml::radioLabel( $this->msg( 'allmessages-filter-unmodified' )->text(),
124
				'filter',
125
				'unmodified',
126
				'mw-allmessages-form-filter-unmodified',
127
				( $this->filter === 'unmodified' )
128
			) .
129
			Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(),
130
				'filter',
131
				'all',
132
				'mw-allmessages-form-filter-all',
133
				( $this->filter === 'all' )
134
			) .
135
			Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(),
136
				'filter',
137
				'modified',
138
				'mw-allmessages-form-filter-modified',
139
				( $this->filter === 'modified' )
140
			) .
141
			"</td>\n
142
			</tr>
143
			<tr>\n
144
				<td class=\"mw-label\">" . $langSelect[0] . "</td>\n
145
				<td class=\"mw-input\">" . $langSelect[1] . "</td>\n
146
			</tr>" .
147
148
			'<tr>
149
				<td class="mw-label">' .
150
			Xml::label( $this->msg( 'table_pager_limit_label' )->text(), 'mw-table_pager_limit_label' ) .
151
			'</td>
152
			<td class="mw-input">' .
153
			$this->getLimitSelect( [ 'id' => 'mw-table_pager_limit_label' ] ) .
154
			'</td>
155
			<tr>
156
				<td></td>
157
				<td>' .
158
			Xml::submitButton( $this->msg( 'allmessages-filter-submit' )->text() ) .
159
			"</td>\n
160
			</tr>" .
161
162
			Xml::closeElement( 'table' ) .
163
			$this->getHiddenFields( [ 'title', 'prefix', 'filter', 'lang', 'limit' ] ) .
164
			Xml::closeElement( 'fieldset' ) .
165
			Xml::closeElement( 'form' );
166
167
		return $out;
168
	}
169
170
	function getAllMessages( $descending ) {
171
		$messageNames = Language::getLocalisationCache()->getSubitemList( 'en', 'messages' );
172
173
		// Normalise message names so they look like page titles and sort correctly - T86139
174
		$messageNames = array_map( [ $this->lang, 'ucfirst' ], $messageNames );
175
176
		if ( $descending ) {
177
			rsort( $messageNames );
178
		} else {
179
			asort( $messageNames );
180
		}
181
182
		return $messageNames;
183
	}
184
185
	/**
186
	 * Determine which of the MediaWiki and MediaWiki_talk namespace pages exist.
187
	 * Returns [ 'pages' => ..., 'talks' => ... ], where the subarrays have
188
	 * an entry for each existing page, with the key being the message name and
189
	 * value arbitrary.
190
	 *
191
	 * @param array $messageNames
192
	 * @param string $langcode What language code
193
	 * @param bool $foreign Whether the $langcode is not the content language
194
	 * @return array A 'pages' and 'talks' array with the keys of existing pages
195
	 */
196
	public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) {
197
		// FIXME: This function should be moved to Language:: or something.
198
199
		$dbr = wfGetDB( DB_REPLICA );
200
		$res = $dbr->select( 'page',
201
			[ 'page_namespace', 'page_title' ],
202
			[ 'page_namespace' => [ NS_MEDIAWIKI, NS_MEDIAWIKI_TALK ] ],
203
			__METHOD__,
204
			[ 'USE INDEX' => 'name_title' ]
205
		);
206
		$xNames = array_flip( $messageNames );
207
208
		$pageFlags = $talkFlags = [];
209
210
		foreach ( $res as $s ) {
211
			$exists = false;
212
213
			if ( $foreign ) {
214
				$titleParts = explode( '/', $s->page_title );
215
				if ( count( $titleParts ) === 2 &&
216
					$langcode === $titleParts[1] &&
217
					isset( $xNames[$titleParts[0]] )
218
				) {
219
					$exists = $titleParts[0];
220
				}
221
			} elseif ( isset( $xNames[$s->page_title] ) ) {
222
				$exists = $s->page_title;
223
			}
224
225
			$title = Title::newFromRow( $s );
226
			if ( $exists && $title->inNamespace( NS_MEDIAWIKI ) ) {
227
				$pageFlags[$exists] = true;
228
			} elseif ( $exists && $title->inNamespace( NS_MEDIAWIKI_TALK ) ) {
229
				$talkFlags[$exists] = true;
230
			}
231
		}
232
233
		return [ 'pages' => $pageFlags, 'talks' => $talkFlags ];
234
	}
235
236
	/**
237
	 *  This function normally does a database query to get the results; we need
238
	 * to make a pretend result using a FakeResultWrapper.
239
	 * @param string $offset
240
	 * @param int $limit
241
	 * @param bool $descending
242
	 * @return FakeResultWrapper
243
	 */
244
	function reallyDoQuery( $offset, $limit, $descending ) {
245
		$result = new FakeResultWrapper( [] );
246
247
		$messageNames = $this->getAllMessages( $descending );
248
		$statuses = self::getCustomisedStatuses( $messageNames, $this->langcode, $this->foreign );
249
250
		$count = 0;
251
		foreach ( $messageNames as $key ) {
252
			$customised = isset( $statuses['pages'][$key] );
253
			if ( $customised !== $this->custom &&
254
				( $descending && ( $key < $offset || !$offset ) || !$descending && $key > $offset ) &&
255
				( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->prefix 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...
256
			) {
257
				$actual = wfMessage( $key )->inLanguage( $this->langcode )->plain();
258
				$default = wfMessage( $key )->inLanguage( $this->langcode )->useDatabase( false )->plain();
259
				$result->result[] = [
260
					'am_title' => $key,
261
					'am_actual' => $actual,
262
					'am_default' => $default,
263
					'am_customised' => $customised,
264
					'am_talk_exists' => isset( $statuses['talks'][$key] )
265
				];
266
				$count++;
267
			}
268
269
			if ( $count === $limit ) {
270
				break;
271
			}
272
		}
273
274
		return $result;
275
	}
276
277
	function getStartBody() {
278
		$tableClass = $this->getTableClass();
279
		return Xml::openElement( 'table', [
280
			'class' => "mw-datatable $tableClass",
281
			'id' => 'mw-allmessagestable'
282
		] ) .
283
		"\n" .
284
		"<thead><tr>
285
				<th rowspan=\"2\">" .
286
		$this->msg( 'allmessagesname' )->escaped() . "
287
				</th>
288
				<th>" .
289
		$this->msg( 'allmessagesdefault' )->escaped() .
290
		"</th>
291
			</tr>\n
292
			<tr>
293
				<th>" .
294
		$this->msg( 'allmessagescurrent' )->escaped() .
295
		"</th>
296
			</tr></thead><tbody>\n";
297
	}
298
299
	function formatValue( $field, $value ) {
300
		switch ( $field ) {
301
			case 'am_title' :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
302
				$title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
303
				$talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
304
				$translation = Linker::makeExternalLink(
305
					'https://translatewiki.net/w/i.php?' . wfArrayToCgi( [
306
						'title' => 'Special:SearchTranslations',
307
						'group' => 'mediawiki',
308
						'grouppath' => 'mediawiki',
309
						'language' => $this->getLanguage()->getCode(),
310
						'query' => $value . ' ' . $this->msg( $value )->plain()
311
					] ),
312
					$this->msg( 'allmessages-filter-translate' )->text()
313
				);
314
315
				if ( $this->mCurrentRow->am_customised ) {
316
					$title = Linker::linkKnown( $title, $this->getLanguage()->lcfirst( $value ) );
317
				} else {
318
					$title = Linker::link(
319
						$title,
320
						$this->getLanguage()->lcfirst( $value ),
321
						[],
322
						[],
323
						[ 'broken' ]
324
					);
325
				}
326
				if ( $this->mCurrentRow->am_talk_exists ) {
327
					$talk = Linker::linkKnown( $talk, $this->talk );
328
				} else {
329
					$talk = Linker::link(
330
						$talk,
331
						$this->talk,
332
						[],
333
						[],
334
						[ 'broken' ]
335
					);
336
				}
337
338
				return $title . ' ' .
339
				$this->msg( 'parentheses' )->rawParams( $talk )->escaped() .
340
				' ' .
341
				$this->msg( 'parentheses' )->rawParams( $translation )->escaped();
342
343
			case 'am_default' :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
344
			case 'am_actual' :
0 ignored issues
show
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
345
				return Sanitizer::escapeHtmlAllowEntities( $value );
346
		}
347
348
		return '';
349
	}
350
351
	function formatRow( $row ) {
352
		// Do all the normal stuff
353
		$s = parent::formatRow( $row );
0 ignored issues
show
It seems like $row defined by parameter $row on line 351 can also be of type array; however, TablePager::formatRow() does only seem to accept object<stdClass>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
354
355
		// But if there's a customised message, add that too.
356
		if ( $row->am_customised ) {
357
			$s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) );
358
			$formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
359
360
			if ( $formatted === '' ) {
361
				$formatted = '&#160;';
362
			}
363
364
			$s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
365
				. "</tr>\n";
366
		}
367
368
		return $s;
369
	}
370
371
	function getRowAttrs( $row, $isSecond = false ) {
372
		$arr = [];
373
374
		if ( $row->am_customised ) {
375
			$arr['class'] = 'allmessages-customised';
376
		}
377
378
		if ( !$isSecond ) {
379
			$arr['id'] = Sanitizer::escapeId( 'msg_' . $this->getLanguage()->lcfirst( $row->am_title ) );
380
		}
381
382
		return $arr;
383
	}
384
385
	function getCellAttrs( $field, $value ) {
386
		if ( $this->mCurrentRow->am_customised && $field === 'am_title' ) {
387
			return [ 'rowspan' => '2', 'class' => $field ];
388
		} elseif ( $field === 'am_title' ) {
389
			return [ 'class' => $field ];
390
		} else {
391
			return [
392
				'lang' => $this->lang->getHtmlCode(),
393
				'dir' => $this->lang->getDir(),
394
				'class' => $field
395
			];
396
		}
397
	}
398
399
	// This is not actually used, as getStartBody is overridden above
400
	function getFieldNames() {
401
		return [
402
			'am_title' => $this->msg( 'allmessagesname' )->text(),
403
			'am_default' => $this->msg( 'allmessagesdefault' )->text()
404
		];
405
	}
406
407
	function getTitle() {
408
		return SpecialPage::getTitleFor( 'Allmessages', false );
409
	}
410
411
	function isFieldSortable( $x ) {
412
		return false;
413
	}
414
415
	function getDefaultSort() {
416
		return '';
417
	}
418
419
	function getQueryInfo() {
420
		return '';
421
	}
422
423
}
424