ApiQueryRecentChanges::run()   F
last analyzed

Complexity

Conditions 68
Paths > 20000

Size

Total Lines 277
Code Lines 173

Duplication

Lines 60
Ratio 21.66 %

Importance

Changes 0
Metric Value
cc 68
eloc 173
nc 977961600
nop 1
dl 60
loc 277
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 *
4
 *
5
 * Created on Oct 19, 2006
6
 *
7
 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License along
20
 * with this program; if not, write to the Free Software Foundation, Inc.,
21
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
 * http://www.gnu.org/copyleft/gpl.html
23
 *
24
 * @file
25
 */
26
27
/**
28
 * A query action to enumerate the recent changes that were done to the wiki.
29
 * Various filters are supported.
30
 *
31
 * @ingroup API
32
 */
33
class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
34
35
	public function __construct( ApiQuery $query, $moduleName ) {
36
		parent::__construct( $query, $moduleName, 'rc' );
37
	}
38
39
	private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
0 ignored issues
show
Coding Style introduced by
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...
40
		$fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
41
		$fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
42
		$fld_tags = false, $fld_sha1 = false, $token = [];
43
44
	private $tokenFunctions;
45
46
	/**
47
	 * Get an array mapping token names to their handler functions.
48
	 * The prototype for a token function is func($pageid, $title, $rc)
49
	 * it should return a token or false (permission denied)
50
	 * @deprecated since 1.24
51
	 * @return array [ tokenname => function ]
52
	 */
53 View Code Duplication
	protected function getTokenFunctions() {
54
		// Don't call the hooks twice
55
		if ( isset( $this->tokenFunctions ) ) {
56
			return $this->tokenFunctions;
57
		}
58
59
		// If we're in a mode that breaks the same-origin policy, no tokens can
60
		// be obtained
61
		if ( $this->lacksSameOriginSecurity() ) {
62
			return [];
63
		}
64
65
		$this->tokenFunctions = [
66
			'patrol' => [ 'ApiQueryRecentChanges', 'getPatrolToken' ]
67
		];
68
		Hooks::run( 'APIQueryRecentChangesTokens', [ &$this->tokenFunctions ] );
69
70
		return $this->tokenFunctions;
71
	}
72
73
	/**
74
	 * @deprecated since 1.24
75
	 * @param int $pageid
76
	 * @param Title $title
77
	 * @param RecentChange|null $rc
78
	 * @return bool|string
79
	 */
80
	public static function getPatrolToken( $pageid, $title, $rc = null ) {
81
		global $wgUser;
82
83
		$validTokenUser = false;
84
85
		if ( $rc ) {
86
			if ( ( $wgUser->useRCPatrol() && $rc->getAttribute( 'rc_type' ) == RC_EDIT ) ||
87
				( $wgUser->useNPPatrol() && $rc->getAttribute( 'rc_type' ) == RC_NEW )
88
			) {
89
				$validTokenUser = true;
90
			}
91
		} elseif ( $wgUser->useRCPatrol() || $wgUser->useNPPatrol() ) {
92
			$validTokenUser = true;
93
		}
94
95
		if ( $validTokenUser ) {
96
			// The patrol token is always the same, let's exploit that
97
			static $cachedPatrolToken = null;
98
99
			if ( is_null( $cachedPatrolToken ) ) {
100
				$cachedPatrolToken = $wgUser->getEditToken( 'patrol' );
101
			}
102
103
			return $cachedPatrolToken;
104
		}
105
106
		return false;
107
	}
108
109
	/**
110
	 * Sets internal state to include the desired properties in the output.
111
	 * @param array $prop Associative array of properties, only keys are used here
112
	 */
113
	public function initProperties( $prop ) {
114
		$this->fld_comment = isset( $prop['comment'] );
115
		$this->fld_parsedcomment = isset( $prop['parsedcomment'] );
116
		$this->fld_user = isset( $prop['user'] );
117
		$this->fld_userid = isset( $prop['userid'] );
118
		$this->fld_flags = isset( $prop['flags'] );
119
		$this->fld_timestamp = isset( $prop['timestamp'] );
120
		$this->fld_title = isset( $prop['title'] );
121
		$this->fld_ids = isset( $prop['ids'] );
122
		$this->fld_sizes = isset( $prop['sizes'] );
123
		$this->fld_redirect = isset( $prop['redirect'] );
124
		$this->fld_patrolled = isset( $prop['patrolled'] );
125
		$this->fld_loginfo = isset( $prop['loginfo'] );
126
		$this->fld_tags = isset( $prop['tags'] );
127
		$this->fld_sha1 = isset( $prop['sha1'] );
128
	}
129
130
	public function execute() {
131
		$this->run();
132
	}
133
134
	public function executeGenerator( $resultPageSet ) {
135
		$this->run( $resultPageSet );
136
	}
137
138
	/**
139
	 * Generates and outputs the result of this query based upon the provided parameters.
140
	 *
141
	 * @param ApiPageSet $resultPageSet
142
	 */
143
	public function run( $resultPageSet = null ) {
144
		$user = $this->getUser();
145
		/* Get the parameters of the request. */
146
		$params = $this->extractRequestParams();
147
148
		/* Build our basic query. Namely, something along the lines of:
149
		 * SELECT * FROM recentchanges WHERE rc_timestamp > $start
150
		 * 		AND rc_timestamp < $end AND rc_namespace = $namespace
151
		 */
152
		$this->addTables( 'recentchanges' );
153
		$this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
154
155 View Code Duplication
		if ( !is_null( $params['continue'] ) ) {
156
			$cont = explode( '|', $params['continue'] );
157
			$this->dieContinueUsageIf( count( $cont ) != 2 );
158
			$db = $this->getDB();
159
			$timestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
160
			$id = intval( $cont[1] );
161
			$this->dieContinueUsageIf( $id != $cont[1] );
162
			$op = $params['dir'] === 'older' ? '<' : '>';
163
			$this->addWhere(
164
				"rc_timestamp $op $timestamp OR " .
165
				"(rc_timestamp = $timestamp AND " .
166
				"rc_id $op= $id)"
167
			);
168
		}
169
170
		$order = $params['dir'] === 'older' ? 'DESC' : 'ASC';
171
		$this->addOption( 'ORDER BY', [
172
			"rc_timestamp $order",
173
			"rc_id $order",
174
		] );
175
176
		$this->addWhereFld( 'rc_namespace', $params['namespace'] );
177
178 View Code Duplication
		if ( !is_null( $params['type'] ) ) {
179
			try {
180
				$this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
181
			} catch ( Exception $e ) {
182
				ApiBase::dieDebug( __METHOD__, $e->getMessage() );
183
			}
184
		}
185
186
		if ( !is_null( $params['show'] ) ) {
187
			$show = array_flip( $params['show'] );
188
189
			/* Check for conflicting parameters. */
190
			if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
191
				|| ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
192
				|| ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
193
				|| ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
194
				|| ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
195
				|| ( isset( $show['patrolled'] ) && isset( $show['unpatrolled'] ) )
196
				|| ( isset( $show['!patrolled'] ) && isset( $show['unpatrolled'] ) )
197
			) {
198
				$this->dieUsageMsg( 'show' );
199
			}
200
201
			// Check permissions
202
			if ( isset( $show['patrolled'] )
203
				|| isset( $show['!patrolled'] )
204
				|| isset( $show['unpatrolled'] )
205
			) {
206
				if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
207
					$this->dieUsage(
208
						'You need patrol or patrolmarks permission to request the patrolled flag',
209
						'permissiondenied'
210
					);
211
				}
212
			}
213
214
			/* Add additional conditions to query depending upon parameters. */
215
			$this->addWhereIf( 'rc_minor = 0', isset( $show['!minor'] ) );
216
			$this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
217
			$this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
218
			$this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
219
			$this->addWhereIf( 'rc_user = 0', isset( $show['anon'] ) );
220
			$this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
221
			$this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
222
			$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
223
			$this->addWhereIf( 'page_is_redirect = 1', isset( $show['redirect'] ) );
224
225
			if ( isset( $show['unpatrolled'] ) ) {
226
				// See ChangesList::isUnpatrolled
227
				if ( $user->useRCPatrol() ) {
228
					$this->addWhere( 'rc_patrolled = 0' );
229
				} elseif ( $user->useNPPatrol() ) {
230
					$this->addWhere( 'rc_patrolled = 0' );
231
					$this->addWhereFld( 'rc_type', RC_NEW );
232
				}
233
			}
234
235
			// Don't throw log entries out the window here
236
			$this->addWhereIf(
237
				'page_is_redirect = 0 OR page_is_redirect IS NULL',
238
				isset( $show['!redirect'] )
239
			);
240
		}
241
242 View Code Duplication
		if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
243
			$this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
244
		}
245
246
		if ( !is_null( $params['user'] ) ) {
247
			$this->addWhereFld( 'rc_user_text', $params['user'] );
248
		}
249
250 View Code Duplication
		if ( !is_null( $params['excludeuser'] ) ) {
251
			// We don't use the rc_user_text index here because
252
			// * it would require us to sort by rc_user_text before rc_timestamp
253
			// * the != condition doesn't throw out too many rows anyway
254
			$this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
255
		}
256
257
		/* Add the fields we're concerned with to our query. */
258
		$this->addFields( [
259
			'rc_id',
260
			'rc_timestamp',
261
			'rc_namespace',
262
			'rc_title',
263
			'rc_cur_id',
264
			'rc_type',
265
			'rc_deleted'
266
		] );
267
268
		$showRedirects = false;
269
		/* Determine what properties we need to display. */
270
		if ( !is_null( $params['prop'] ) ) {
271
			$prop = array_flip( $params['prop'] );
272
273
			/* Set up internal members based upon params. */
274
			$this->initProperties( $prop );
275
276
			if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
277
				$this->dieUsage(
278
					'You need patrol or patrolmarks permission to request the patrolled flag',
279
					'permissiondenied'
280
				);
281
			}
282
283
			/* Add fields to our query if they are specified as a needed parameter. */
284
			$this->addFieldsIf( [ 'rc_this_oldid', 'rc_last_oldid' ], $this->fld_ids );
285
			$this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
286
			$this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid );
287
			$this->addFieldsIf( 'rc_user_text', $this->fld_user );
288
			$this->addFieldsIf( [ 'rc_minor', 'rc_type', 'rc_bot' ], $this->fld_flags );
289
			$this->addFieldsIf( [ 'rc_old_len', 'rc_new_len' ], $this->fld_sizes );
290
			$this->addFieldsIf( [ 'rc_patrolled', 'rc_log_type' ], $this->fld_patrolled );
291
			$this->addFieldsIf(
292
				[ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
293
				$this->fld_loginfo
294
			);
295
			$showRedirects = $this->fld_redirect || isset( $show['redirect'] )
296
				|| isset( $show['!redirect'] );
297
		}
298
		$this->addFieldsIf( [ 'rc_this_oldid' ],
299
			$resultPageSet && $params['generaterevisions'] );
300
301 View Code Duplication
		if ( $this->fld_tags ) {
302
			$this->addTables( 'tag_summary' );
303
			$this->addJoinConds( [ 'tag_summary' => [ 'LEFT JOIN', [ 'rc_id=ts_rc_id' ] ] ] );
304
			$this->addFields( 'ts_tags' );
305
		}
306
307
		if ( $this->fld_sha1 ) {
308
			$this->addTables( 'revision' );
309
			$this->addJoinConds( [ 'revision' => [ 'LEFT JOIN',
310
				[ 'rc_this_oldid=rev_id' ] ] ] );
311
			$this->addFields( [ 'rev_sha1', 'rev_deleted' ] );
312
		}
313
314
		if ( $params['toponly'] || $showRedirects ) {
315
			$this->addTables( 'page' );
316
			$this->addJoinConds( [ 'page' => [ 'LEFT JOIN',
317
				[ 'rc_namespace=page_namespace', 'rc_title=page_title' ] ] ] );
318
			$this->addFields( 'page_is_redirect' );
319
320
			if ( $params['toponly'] ) {
321
				$this->addWhere( 'rc_this_oldid = page_latest' );
322
			}
323
		}
324
325 View Code Duplication
		if ( !is_null( $params['tag'] ) ) {
326
			$this->addTables( 'change_tag' );
327
			$this->addJoinConds( [ 'change_tag' => [ 'INNER JOIN', [ 'rc_id=ct_rc_id' ] ] ] );
328
			$this->addWhereFld( 'ct_tag', $params['tag'] );
329
		}
330
331
		// Paranoia: avoid brute force searches (bug 17342)
332 View Code Duplication
		if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
333
			if ( !$user->isAllowed( 'deletedhistory' ) ) {
334
				$bitmask = Revision::DELETED_USER;
335
			} elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
336
				$bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
337
			} else {
338
				$bitmask = 0;
339
			}
340
			if ( $bitmask ) {
341
				$this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
342
			}
343
		}
344
		if ( $this->getRequest()->getCheck( 'namespace' ) ) {
345
			// LogPage::DELETED_ACTION hides the affected page, too.
346
			if ( !$user->isAllowed( 'deletedhistory' ) ) {
347
				$bitmask = LogPage::DELETED_ACTION;
348
			} elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
349
				$bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
350
			} else {
351
				$bitmask = 0;
352
			}
353
			if ( $bitmask ) {
354
				$this->addWhere( $this->getDB()->makeList( [
355
					'rc_type != ' . RC_LOG,
356
					$this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
357
				], LIST_OR ) );
358
			}
359
		}
360
361
		$this->token = $params['token'];
362
		$this->addOption( 'LIMIT', $params['limit'] + 1 );
363
364
		$hookData = [];
365
		$count = 0;
366
		/* Perform the actual query. */
367
		$res = $this->select( __METHOD__, [], $hookData );
368
369
		$revids = [];
370
		$titles = [];
371
372
		$result = $this->getResult();
373
374
		/* Iterate through the rows, adding data extracted from them to our query result. */
375
		foreach ( $res as $row ) {
376
			if ( $count === 0 && $resultPageSet !== null ) {
377
				// Set the non-continue since the list of recentchanges is
378
				// prone to having entries added at the start frequently.
379
				$this->getContinuationManager()->addGeneratorNonContinueParam(
380
					$this, 'continue', "$row->rc_timestamp|$row->rc_id"
381
				);
382
			}
383
			if ( ++$count > $params['limit'] ) {
384
				// We've reached the one extra which shows that there are
385
				// additional pages to be had. Stop here...
386
				$this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
387
				break;
388
			}
389
390
			if ( is_null( $resultPageSet ) ) {
391
				/* Extract the data from a single row. */
392
				$vals = $this->extractRowInfo( $row );
0 ignored issues
show
Bug introduced by
It seems like $row defined by $row on line 375 can be null; however, ApiQueryRecentChanges::extractRowInfo() 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...
393
394
				/* Add that row's data to our final output. */
395
				$fit = $this->processRow( $row, $vals, $hookData ) &&
0 ignored issues
show
Bug introduced by
It seems like $row defined by $row on line 375 can be null; however, ApiQueryBase::processRow() 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...
Bug introduced by
It seems like $hookData can also be of type null; however, ApiQueryBase::processRow() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
396
					$result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
397
				if ( !$fit ) {
398
					$this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
399
					break;
400
				}
401
			} elseif ( $params['generaterevisions'] ) {
402
				$revid = (int)$row->rc_this_oldid;
403
				if ( $revid > 0 ) {
404
					$revids[] = $revid;
405
				}
406
			} else {
407
				$titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
408
			}
409
		}
410
411 View Code Duplication
		if ( is_null( $resultPageSet ) ) {
412
			/* Format the result */
413
			$result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'rc' );
414
		} elseif ( $params['generaterevisions'] ) {
415
			$resultPageSet->populateFromRevisionIDs( $revids );
416
		} else {
417
			$resultPageSet->populateFromTitles( $titles );
418
		}
419
	}
420
421
	/**
422
	 * Extracts from a single sql row the data needed to describe one recent change.
423
	 *
424
	 * @param stdClass $row The row from which to extract the data.
425
	 * @return array An array mapping strings (descriptors) to their respective string values.
426
	 * @access public
427
	 */
428
	public function extractRowInfo( $row ) {
429
		/* Determine the title of the page that has been changed. */
430
		$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
431
		$user = $this->getUser();
432
433
		/* Our output data. */
434
		$vals = [];
435
436
		$type = intval( $row->rc_type );
437
		$vals['type'] = RecentChange::parseFromRCType( $type );
438
439
		$anyHidden = false;
440
441
		/* Create a new entry in the result for the title. */
442
		if ( $this->fld_title || $this->fld_ids ) {
443 View Code Duplication
			if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
444
				$vals['actionhidden'] = true;
445
				$anyHidden = true;
446
			}
447
			if ( $type !== RC_LOG ||
448
				LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
449
			) {
450
				if ( $this->fld_title ) {
451
					ApiQueryBase::addTitleInfo( $vals, $title );
452
				}
453
				if ( $this->fld_ids ) {
454
					$vals['pageid'] = intval( $row->rc_cur_id );
455
					$vals['revid'] = intval( $row->rc_this_oldid );
456
					$vals['old_revid'] = intval( $row->rc_last_oldid );
457
				}
458
			}
459
		}
460
461
		if ( $this->fld_ids ) {
462
			$vals['rcid'] = intval( $row->rc_id );
463
		}
464
465
		/* Add user data and 'anon' flag, if user is anonymous. */
466
		if ( $this->fld_user || $this->fld_userid ) {
467 View Code Duplication
			if ( $row->rc_deleted & Revision::DELETED_USER ) {
468
				$vals['userhidden'] = true;
469
				$anyHidden = true;
470
			}
471
			if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
472
				if ( $this->fld_user ) {
473
					$vals['user'] = $row->rc_user_text;
474
				}
475
476
				if ( $this->fld_userid ) {
477
					$vals['userid'] = (int)$row->rc_user;
478
				}
479
480
				if ( !$row->rc_user ) {
481
					$vals['anon'] = true;
482
				}
483
			}
484
		}
485
486
		/* Add flags, such as new, minor, bot. */
487
		if ( $this->fld_flags ) {
488
			$vals['bot'] = (bool)$row->rc_bot;
489
			$vals['new'] = $row->rc_type == RC_NEW;
490
			$vals['minor'] = (bool)$row->rc_minor;
491
		}
492
493
		/* Add sizes of each revision. (Only available on 1.10+) */
494
		if ( $this->fld_sizes ) {
495
			$vals['oldlen'] = intval( $row->rc_old_len );
496
			$vals['newlen'] = intval( $row->rc_new_len );
497
		}
498
499
		/* Add the timestamp. */
500
		if ( $this->fld_timestamp ) {
501
			$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
502
		}
503
504
		/* Add edit summary / log summary. */
505 View Code Duplication
		if ( $this->fld_comment || $this->fld_parsedcomment ) {
506
			if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
507
				$vals['commenthidden'] = true;
508
				$anyHidden = true;
509
			}
510
			if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
511
				if ( $this->fld_comment && isset( $row->rc_comment ) ) {
512
					$vals['comment'] = $row->rc_comment;
513
				}
514
515
				if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
516
					$vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
517
				}
518
			}
519
		}
520
521
		if ( $this->fld_redirect ) {
522
			$vals['redirect'] = (bool)$row->page_is_redirect;
523
		}
524
525
		/* Add the patrolled flag */
526 View Code Duplication
		if ( $this->fld_patrolled ) {
527
			$vals['patrolled'] = $row->rc_patrolled == 1;
528
			$vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
529
		}
530
531
		if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
532 View Code Duplication
			if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
533
				$vals['actionhidden'] = true;
534
				$anyHidden = true;
535
			}
536
			if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
537
				$vals['logid'] = intval( $row->rc_logid );
538
				$vals['logtype'] = $row->rc_log_type;
539
				$vals['logaction'] = $row->rc_log_action;
540
				$vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
541
			}
542
		}
543
544 View Code Duplication
		if ( $this->fld_tags ) {
545
			if ( $row->ts_tags ) {
546
				$tags = explode( ',', $row->ts_tags );
547
				ApiResult::setIndexedTagName( $tags, 'tag' );
548
				$vals['tags'] = $tags;
549
			} else {
550
				$vals['tags'] = [];
551
			}
552
		}
553
554
		if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
555
			if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
556
				$vals['sha1hidden'] = true;
557
				$anyHidden = true;
558
			}
559 View Code Duplication
			if ( Revision::userCanBitfield( $row->rev_deleted, Revision::DELETED_TEXT, $user ) ) {
560
				if ( $row->rev_sha1 !== '' ) {
561
					$vals['sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
562
				} else {
563
					$vals['sha1'] = '';
564
				}
565
			}
566
		}
567
568
		if ( !is_null( $this->token ) ) {
569
			$tokenFunctions = $this->getTokenFunctions();
570
			foreach ( $this->token as $t ) {
571
				$val = call_user_func( $tokenFunctions[$t], $row->rc_cur_id,
572
					$title, RecentChange::newFromRow( $row ) );
573
				if ( $val === false ) {
574
					$this->setWarning( "Action '$t' is not allowed for the current user" );
575
				} else {
576
					$vals[$t . 'token'] = $val;
577
				}
578
			}
579
		}
580
581
		if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
582
			$vals['suppressed'] = true;
583
		}
584
585
		return $vals;
586
	}
587
588
	public function getCacheMode( $params ) {
589
		if ( isset( $params['show'] ) ) {
590
			foreach ( $params['show'] as $show ) {
591
				if ( $show === 'patrolled' || $show === '!patrolled' ) {
592
					return 'private';
593
				}
594
			}
595
		}
596
		if ( isset( $params['token'] ) ) {
597
			return 'private';
598
		}
599
		if ( $this->userCanSeeRevDel() ) {
600
			return 'private';
601
		}
602 View Code Duplication
		if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
603
			// formatComment() calls wfMessage() among other things
604
			return 'anon-public-user-private';
605
		}
606
607
		return 'public';
608
	}
609
610
	public function getAllowedParams() {
611
		return [
612
			'start' => [
613
				ApiBase::PARAM_TYPE => 'timestamp'
614
			],
615
			'end' => [
616
				ApiBase::PARAM_TYPE => 'timestamp'
617
			],
618
			'dir' => [
619
				ApiBase::PARAM_DFLT => 'older',
620
				ApiBase::PARAM_TYPE => [
621
					'newer',
622
					'older'
623
				],
624
				ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
625
			],
626
			'namespace' => [
627
				ApiBase::PARAM_ISMULTI => true,
628
				ApiBase::PARAM_TYPE => 'namespace'
629
			],
630
			'user' => [
631
				ApiBase::PARAM_TYPE => 'user'
632
			],
633
			'excludeuser' => [
634
				ApiBase::PARAM_TYPE => 'user'
635
			],
636
			'tag' => null,
637
			'prop' => [
638
				ApiBase::PARAM_ISMULTI => true,
639
				ApiBase::PARAM_DFLT => 'title|timestamp|ids',
640
				ApiBase::PARAM_TYPE => [
641
					'user',
642
					'userid',
643
					'comment',
644
					'parsedcomment',
645
					'flags',
646
					'timestamp',
647
					'title',
648
					'ids',
649
					'sizes',
650
					'redirect',
651
					'patrolled',
652
					'loginfo',
653
					'tags',
654
					'sha1',
655
				],
656
				ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
657
			],
658
			'token' => [
659
				ApiBase::PARAM_DEPRECATED => true,
660
				ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
661
				ApiBase::PARAM_ISMULTI => true
662
			],
663
			'show' => [
664
				ApiBase::PARAM_ISMULTI => true,
665
				ApiBase::PARAM_TYPE => [
666
					'minor',
667
					'!minor',
668
					'bot',
669
					'!bot',
670
					'anon',
671
					'!anon',
672
					'redirect',
673
					'!redirect',
674
					'patrolled',
675
					'!patrolled',
676
					'unpatrolled'
677
				]
678
			],
679
			'limit' => [
680
				ApiBase::PARAM_DFLT => 10,
681
				ApiBase::PARAM_TYPE => 'limit',
682
				ApiBase::PARAM_MIN => 1,
683
				ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
684
				ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
685
			],
686
			'type' => [
687
				ApiBase::PARAM_DFLT => 'edit|new|log|categorize',
688
				ApiBase::PARAM_ISMULTI => true,
689
				ApiBase::PARAM_TYPE => RecentChange::getChangeTypes()
690
			],
691
			'toponly' => false,
692
			'continue' => [
693
				ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
694
			],
695
			'generaterevisions' => false,
696
		];
697
	}
698
699
	protected function getExamplesMessages() {
700
		return [
701
			'action=query&list=recentchanges'
702
				=> 'apihelp-query+recentchanges-example-simple',
703
			'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
704
				=> 'apihelp-query+recentchanges-example-generator',
705
		];
706
	}
707
708
	public function getHelpUrls() {
709
		return 'https://www.mediawiki.org/wiki/API:Recentchanges';
710
	}
711
}
712