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/DeletedContribsPager.php (1 issue)

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
 * @ingroup Pager
24
 */
25
class DeletedContribsPager extends IndexPager {
26
27
	public $mDefaultDirection = IndexPager::DIR_DESCENDING;
28
	public $messages;
29
	public $target;
30
	public $namespace = '';
31
	public $mDb;
32
33
	/**
34
	 * @var string Navigation bar with paging links.
35
	 */
36
	protected $mNavigationBar;
37
38
	function __construct( IContextSource $context, $target, $namespace = false ) {
39
		parent::__construct( $context );
40
		$msgs = [ 'deletionlog', 'undeleteviewlink', 'diff' ];
41
		foreach ( $msgs as $msg ) {
42
			$this->messages[$msg] = $this->msg( $msg )->escaped();
43
		}
44
		$this->target = $target;
45
		$this->namespace = $namespace;
0 ignored issues
show
Documentation Bug introduced by
The property $namespace was declared of type string, but $namespace is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
46
		$this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
47
	}
48
49
	function getDefaultQuery() {
50
		$query = parent::getDefaultQuery();
51
		$query['target'] = $this->target;
52
53
		return $query;
54
	}
55
56
	function getQueryInfo() {
57
		list( $index, $userCond ) = $this->getUserCond();
58
		$conds = array_merge( $userCond, $this->getNamespaceCond() );
59
		$user = $this->getUser();
60
		// Paranoia: avoid brute force searches (bug 17792)
61 View Code Duplication
		if ( !$user->isAllowed( 'deletedhistory' ) ) {
62
			$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::DELETED_USER ) . ' = 0';
63
		} elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
64
			$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::SUPPRESSED_USER ) .
65
				' != ' . Revision::SUPPRESSED_USER;
66
		}
67
68
		return [
69
			'tables' => [ 'archive' ],
70
			'fields' => [
71
				'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp', 'ar_comment',
72
				'ar_minor_edit', 'ar_user', 'ar_user_text', 'ar_deleted'
73
			],
74
			'conds' => $conds,
75
			'options' => [ 'USE INDEX' => $index ]
76
		];
77
	}
78
79
	/**
80
	 * This method basically executes the exact same code as the parent class, though with
81
	 * a hook added, to allow extensions to add additional queries.
82
	 *
83
	 * @param string $offset Index offset, inclusive
84
	 * @param int $limit Exact query limit
85
	 * @param bool $descending Query direction, false for ascending, true for descending
86
	 * @return ResultWrapper
87
	 */
88
	function reallyDoQuery( $offset, $limit, $descending ) {
89
		$data = [ parent::reallyDoQuery( $offset, $limit, $descending ) ];
90
91
		// This hook will allow extensions to add in additional queries, nearly
92
		// identical to ContribsPager::reallyDoQuery.
93
		Hooks::run(
94
			'DeletedContribsPager::reallyDoQuery',
95
			[ &$data, $this, $offset, $limit, $descending ]
96
		);
97
98
		$result = [];
99
100
		// loop all results and collect them in an array
101 View Code Duplication
		foreach ( $data as $query ) {
102
			foreach ( $query as $i => $row ) {
103
				// use index column as key, allowing us to easily sort in PHP
104
				$result[$row->{$this->getIndexField()} . "-$i"] = $row;
105
			}
106
		}
107
108
		// sort results
109
		if ( $descending ) {
110
			ksort( $result );
111
		} else {
112
			krsort( $result );
113
		}
114
115
		// enforce limit
116
		$result = array_slice( $result, 0, $limit );
117
118
		// get rid of array keys
119
		$result = array_values( $result );
120
121
		return new FakeResultWrapper( $result );
122
	}
123
124
	function getUserCond() {
125
		$condition = [];
126
127
		$condition['ar_user_text'] = $this->target;
128
		$index = 'usertext_timestamp';
129
130
		return [ $index, $condition ];
131
	}
132
133
	function getIndexField() {
134
		return 'ar_timestamp';
135
	}
136
137
	function getStartBody() {
138
		return "<ul>\n";
139
	}
140
141
	function getEndBody() {
142
		return "</ul>\n";
143
	}
144
145
	function getNavigationBar() {
146
		if ( isset( $this->mNavigationBar ) ) {
147
			return $this->mNavigationBar;
148
		}
149
150
		$linkTexts = [
151
			'prev' => $this->msg( 'pager-newer-n' )->numParams( $this->mLimit )->escaped(),
152
			'next' => $this->msg( 'pager-older-n' )->numParams( $this->mLimit )->escaped(),
153
			'first' => $this->msg( 'histlast' )->escaped(),
154
			'last' => $this->msg( 'histfirst' )->escaped()
155
		];
156
157
		$pagingLinks = $this->getPagingLinks( $linkTexts );
158
		$limitLinks = $this->getLimitLinks();
159
		$lang = $this->getLanguage();
160
		$limits = $lang->pipeList( $limitLinks );
161
162
		$firstLast = $lang->pipeList( [ $pagingLinks['first'], $pagingLinks['last'] ] );
163
		$firstLast = $this->msg( 'parentheses' )->rawParams( $firstLast )->escaped();
164
		$prevNext = $this->msg( 'viewprevnext' )
165
			->rawParams(
166
				$pagingLinks['prev'],
167
				$pagingLinks['next'],
168
				$limits
169
			)->escaped();
170
		$separator = $this->msg( 'word-separator' )->escaped();
171
		$this->mNavigationBar = $firstLast . $separator . $prevNext;
172
173
		return $this->mNavigationBar;
174
	}
175
176
	function getNamespaceCond() {
177
		if ( $this->namespace !== '' ) {
178
			return [ 'ar_namespace' => (int)$this->namespace ];
179
		} else {
180
			return [];
181
		}
182
	}
183
184
	/**
185
	 * Generates each row in the contributions list.
186
	 *
187
	 * @todo This would probably look a lot nicer in a table.
188
	 * @param stdClass $row
189
	 * @return string
190
	 */
191
	function formatRow( $row ) {
192
		$ret = '';
193
		$classes = [];
194
195
		/*
196
		 * There may be more than just revision rows. To make sure that we'll only be processing
197
		 * revisions here, let's _try_ to build a revision out of our row (without displaying
198
		 * notices though) and then trying to grab data from the built object. If we succeed,
199
		 * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
200
		 * to extensions to subscribe to the hook to parse the row.
201
		 */
202
		MediaWiki\suppressWarnings();
203
		try {
204
			$rev = Revision::newFromArchiveRow( $row );
205
			$validRevision = (bool)$rev->getId();
206
		} catch ( Exception $e ) {
207
			$validRevision = false;
208
		}
209
		MediaWiki\restoreWarnings();
210
211
		if ( $validRevision ) {
212
			$ret = $this->formatRevisionRow( $row );
213
		}
214
215
		// Let extensions add data
216
		Hooks::run( 'DeletedContributionsLineEnding', [ $this, &$ret, $row, &$classes ] );
217
218
		if ( $classes === [] && $ret === '' ) {
219
			wfDebug( "Dropping Special:DeletedContribution row that could not be formatted\n" );
220
			$ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
221
		} else {
222
			$ret = Html::rawElement( 'li', [ 'class' => $classes ], $ret ) . "\n";
223
		}
224
225
		return $ret;
226
	}
227
228
	/**
229
	 * Generates each row in the contributions list for archive entries.
230
	 *
231
	 * Contributions which are marked "top" are currently on top of the history.
232
	 * For these contributions, a [rollback] link is shown for users with sysop
233
	 * privileges. The rollback link restores the most recent version that was not
234
	 * written by the target user.
235
	 *
236
	 * @todo This would probably look a lot nicer in a table.
237
	 * @param stdClass $row
238
	 * @return string
239
	 */
240
	function formatRevisionRow( $row ) {
241
		$page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
242
243
		$rev = new Revision( [
244
			'title' => $page,
245
			'id' => $row->ar_rev_id,
246
			'comment' => $row->ar_comment,
247
			'user' => $row->ar_user,
248
			'user_text' => $row->ar_user_text,
249
			'timestamp' => $row->ar_timestamp,
250
			'minor_edit' => $row->ar_minor_edit,
251
			'deleted' => $row->ar_deleted,
252
		] );
253
254
		$undelete = SpecialPage::getTitleFor( 'Undelete' );
255
256
		$logs = SpecialPage::getTitleFor( 'Log' );
257
		$dellog = Linker::linkKnown(
258
			$logs,
259
			$this->messages['deletionlog'],
260
			[],
261
			[
262
				'type' => 'delete',
263
				'page' => $page->getPrefixedText()
264
			]
265
		);
266
267
		$reviewlink = Linker::linkKnown(
268
			SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
269
			$this->messages['undeleteviewlink']
270
		);
271
272
		$user = $this->getUser();
273
274 View Code Duplication
		if ( $user->isAllowed( 'deletedtext' ) ) {
275
			$last = Linker::linkKnown(
276
				$undelete,
277
				$this->messages['diff'],
278
				[],
279
				[
280
					'target' => $page->getPrefixedText(),
281
					'timestamp' => $rev->getTimestamp(),
282
					'diff' => 'prev'
283
				]
284
			);
285
		} else {
286
			$last = $this->messages['diff'];
287
		}
288
289
		$comment = Linker::revComment( $rev );
290
		$date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user );
291
		$date = htmlspecialchars( $date );
292
293
		if ( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
294
			$link = $date; // unusable link
295
		} else {
296
			$link = Linker::linkKnown(
297
				$undelete,
298
				$date,
299
				[ 'class' => 'mw-changeslist-date' ],
300
				[
301
					'target' => $page->getPrefixedText(),
302
					'timestamp' => $rev->getTimestamp()
303
				]
304
			);
305
		}
306
		// Style deleted items
307
		if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
308
			$link = '<span class="history-deleted">' . $link . '</span>';
309
		}
310
311
		$pagelink = Linker::link(
312
			$page,
313
			null,
314
			[ 'class' => 'mw-changeslist-title' ]
315
		);
316
317
		if ( $rev->isMinor() ) {
318
			$mflag = ChangesList::flag( 'minor' );
319
		} else {
320
			$mflag = '';
321
		}
322
323
		// Revision delete link
324
		$del = Linker::getRevDeleteLink( $user, $rev, $page );
325
		if ( $del ) {
326
			$del .= ' ';
327
		}
328
329
		$tools = Html::rawElement(
330
			'span',
331
			[ 'class' => 'mw-deletedcontribs-tools' ],
332
			$this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
333
				[ $last, $dellog, $reviewlink ] ) )->escaped()
334
		);
335
336
		$separator = '<span class="mw-changeslist-separator">. .</span>';
337
		$ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}";
338
339
		# Denote if username is redacted for this edit
340
		if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
341
			$ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
342
		}
343
344
		return $ret;
345
	}
346
347
	/**
348
	 * Get the Database object in use
349
	 *
350
	 * @return IDatabase
351
	 */
352
	public function getDatabase() {
353
		return $this->mDb;
354
	}
355
}
356