Completed
Branch master (420c52)
by
unknown
26:22
created

ApiQueryContributions::prepareUsername()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 5
nop 1
dl 0
loc 14
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 *
4
 *
5
 * Created on Oct 16, 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
 * This query action adds a list of a specified user's contributions to the output.
29
 *
30
 * @ingroup API
31
 */
32
class ApiQueryContributions extends ApiQueryBase {
33
34
	public function __construct( ApiQuery $query, $moduleName ) {
35
		parent::__construct( $query, $moduleName, 'uc' );
36
	}
37
38
	private $params, $prefixMode, $userprefix, $multiUserMode, $idMode, $usernames, $userids,
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...
39
		$parentLens;
40
	private $fld_ids = false, $fld_title = false, $fld_timestamp = 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...
41
		$fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
42
		$fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
43
44
	public function execute() {
45
		// Parse some parameters
46
		$this->params = $this->extractRequestParams();
47
48
		$prop = array_flip( $this->params['prop'] );
49
		$this->fld_ids = isset( $prop['ids'] );
50
		$this->fld_title = isset( $prop['title'] );
51
		$this->fld_comment = isset( $prop['comment'] );
52
		$this->fld_parsedcomment = isset( $prop['parsedcomment'] );
53
		$this->fld_size = isset( $prop['size'] );
54
		$this->fld_sizediff = isset( $prop['sizediff'] );
55
		$this->fld_flags = isset( $prop['flags'] );
56
		$this->fld_timestamp = isset( $prop['timestamp'] );
57
		$this->fld_patrolled = isset( $prop['patrolled'] );
58
		$this->fld_tags = isset( $prop['tags'] );
59
60
		// Most of this code will use the 'contributions' group DB, which can map to slaves
61
		// with extra user based indexes or partioning by user. The additional metadata
62
		// queries should use a regular slave since the lookup pattern is not all by user.
63
		$dbSecondary = $this->getDB(); // any random slave
64
65
		// TODO: if the query is going only against the revision table, should this be done?
66
		$this->selectNamedDB( 'contributions', DB_SLAVE, 'contributions' );
67
68
		$this->idMode = false;
69
		if ( isset( $this->params['userprefix'] ) ) {
70
			$this->prefixMode = true;
71
			$this->multiUserMode = true;
72
			$this->userprefix = $this->params['userprefix'];
73
		} else {
74
			$anyIPs = false;
75
			$this->userids = [];
76
			$this->usernames = [];
77
			if ( !is_array( $this->params['user'] ) ) {
78
				$this->params['user'] = [ $this->params['user'] ];
79
			}
80
			if ( !count( $this->params['user'] ) ) {
81
				$this->dieUsage( 'User parameter may not be empty.', 'param_user' );
82
			}
83
			foreach ( $this->params['user'] as $u ) {
0 ignored issues
show
Bug introduced by
The expression $this->params['user'] of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
84
				if ( is_null( $u ) || $u === '' ) {
85
					$this->dieUsage( 'User parameter may not be empty', 'param_user' );
86
				}
87
88
				if ( User::isIP( $u ) ) {
89
					$anyIPs = true;
90
					$this->usernames[] = $u;
91
				} else {
92
					$name = User::getCanonicalName( $u, 'valid' );
93
					if ( $name === false ) {
94
						$this->dieUsage( "User name {$u} is not valid", 'param_user' );
95
					}
96
					$this->usernames[] = $name;
97
				}
98
			}
99
			$this->prefixMode = false;
100
			$this->multiUserMode = ( count( $this->params['user'] ) > 1 );
101
102
			if ( !$anyIPs ) {
103
				$dbr = $this->getDB();
104
				$res = $dbr->select( 'user', 'user_id', [ 'user_name' => $this->usernames ], __METHOD__ );
105
				foreach ( $res as $row ) {
0 ignored issues
show
Bug introduced by
The expression $res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
106
					$this->userids[] = $row->user_id;
107
				}
108
				$this->idMode = count( $this->userids ) === count( $this->usernames );
109
			}
110
		}
111
112
		$this->prepareQuery();
113
114
		// Do the actual query.
115
		$res = $this->select( __METHOD__ );
116
117
		if ( $this->fld_sizediff ) {
118
			$revIds = [];
119
			foreach ( $res as $row ) {
120
				if ( $row->rev_parent_id ) {
121
					$revIds[] = $row->rev_parent_id;
122
				}
123
			}
124
			$this->parentLens = Revision::getParentLengths( $dbSecondary, $revIds );
125
			$res->rewind(); // reset
126
		}
127
128
		// Initialise some variables
129
		$count = 0;
130
		$limit = $this->params['limit'];
131
132
		// Fetch each row
133
		foreach ( $res as $row ) {
134
			if ( ++$count > $limit ) {
135
				// We've reached the one extra which shows that there are
136
				// additional pages to be had. Stop here...
137
				$this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
138
				break;
139
			}
140
141
			$vals = $this->extractRowInfo( $row );
142
			$fit = $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
143
			if ( !$fit ) {
144
				$this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
145
				break;
146
			}
147
		}
148
149
		$this->getResult()->addIndexedTagName(
150
			[ 'query', $this->getModuleName() ],
151
			'item'
152
		);
153
	}
154
155
	/**
156
	 * Prepares the query and returns the limit of rows requested
157
	 */
158
	private function prepareQuery() {
159
		// We're after the revision table, and the corresponding page
160
		// row for anything we retrieve. We may also need the
161
		// recentchanges row and/or tag summary row.
162
		$user = $this->getUser();
163
		$tables = [ 'page', 'revision' ]; // Order may change
164
		$this->addWhere( 'page_id=rev_page' );
165
166
		// Handle continue parameter
167
		if ( !is_null( $this->params['continue'] ) ) {
168
			$continue = explode( '|', $this->params['continue'] );
169
			$db = $this->getDB();
170
			if ( $this->multiUserMode ) {
171
				$this->dieContinueUsageIf( count( $continue ) != 4 );
172
				$modeFlag = array_shift( $continue );
173
				$this->dieContinueUsageIf( !in_array( $modeFlag, [ 'id', 'name' ] ) );
174
				if ( $this->idMode && $modeFlag === 'name' ) {
175
					// The users were created since this query started, but we
176
					// can't go back and change modes now. So just keep on with
177
					// name mode.
178
					$this->idMode = false;
179
				}
180
				$this->dieContinueUsageIf( ( $modeFlag === 'id' ) !== $this->idMode );
181
				$userField = $this->idMode ? 'rev_user' : 'rev_user_text';
182
				$encUser = $db->addQuotes( array_shift( $continue ) );
183
			} else {
184
				$this->dieContinueUsageIf( count( $continue ) != 2 );
185
			}
186
			$encTS = $db->addQuotes( $db->timestamp( $continue[0] ) );
0 ignored issues
show
Security Bug introduced by
It seems like $db->timestamp($continue[0]) targeting DatabaseBase::timestamp() can also be of type false; however, DatabaseBase::addQuotes() does only seem to accept string|object<Blob>, did you maybe forget to handle an error condition?
Loading history...
187
			$encId = (int)$continue[1];
188
			$this->dieContinueUsageIf( $encId != $continue[1] );
189
			$op = ( $this->params['dir'] == 'older' ? '<' : '>' );
190
			if ( $this->multiUserMode ) {
191
				$this->addWhere(
192
					"$userField $op $encUser OR " .
0 ignored issues
show
Bug introduced by
The variable $userField does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $encUser does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
193
					"($userField = $encUser AND " .
194
					"(rev_timestamp $op $encTS OR " .
195
					"(rev_timestamp = $encTS AND " .
196
					"rev_id $op= $encId)))"
197
				);
198
			} else {
199
				$this->addWhere(
200
					"rev_timestamp $op $encTS OR " .
201
					"(rev_timestamp = $encTS AND " .
202
					"rev_id $op= $encId)"
203
				);
204
			}
205
		}
206
207
		// Don't include any revisions where we're not supposed to be able to
208
		// see the username.
209
		if ( !$user->isAllowed( 'deletedhistory' ) ) {
210
			$bitmask = Revision::DELETED_USER;
211
		} elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
212
			$bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
213
		} else {
214
			$bitmask = 0;
215
		}
216
		if ( $bitmask ) {
217
			$this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
218
		}
219
220
		// We only want pages by the specified users.
221
		if ( $this->prefixMode ) {
222
			$this->addWhere( 'rev_user_text' .
223
				$this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) );
224
		} elseif ( $this->idMode ) {
225
			$this->addWhereFld( 'rev_user', $this->userids );
226
		} else {
227
			$this->addWhereFld( 'rev_user_text', $this->usernames );
228
		}
229
		// ... and in the specified timeframe.
230
		// Ensure the same sort order for rev_user/rev_user_text and rev_timestamp
231
		// so our query is indexed
232
		if ( $this->multiUserMode ) {
233
			$this->addWhereRange( $this->idMode ? 'rev_user' : 'rev_user_text',
234
				$this->params['dir'], null, null );
235
		}
236
		$this->addTimestampWhereRange( 'rev_timestamp',
237
			$this->params['dir'], $this->params['start'], $this->params['end'] );
238
		// Include in ORDER BY for uniqueness
239
		$this->addWhereRange( 'rev_id', $this->params['dir'], null, null );
240
241
		$this->addWhereFld( 'page_namespace', $this->params['namespace'] );
242
243
		$show = $this->params['show'];
244
		if ( $this->params['toponly'] ) { // deprecated/old param
245
			$show[] = 'top';
246
		}
247
		if ( !is_null( $show ) ) {
248
			$show = array_flip( $show );
249
250 View Code Duplication
			if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
251
				|| ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
252
				|| ( isset( $show['top'] ) && isset( $show['!top'] ) )
253
				|| ( isset( $show['new'] ) && isset( $show['!new'] ) )
254
			) {
255
				$this->dieUsageMsg( 'show' );
256
			}
257
258
			$this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
259
			$this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) );
260
			$this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
261
			$this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
262
			$this->addWhereIf( 'rev_id != page_latest', isset( $show['!top'] ) );
263
			$this->addWhereIf( 'rev_id = page_latest', isset( $show['top'] ) );
264
			$this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) );
265
			$this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) );
266
		}
267
		$this->addOption( 'LIMIT', $this->params['limit'] + 1 );
268
269
		// Mandatory fields: timestamp allows request continuation
270
		// ns+title checks if the user has access rights for this page
271
		// user_text is necessary if multiple users were specified
272
		$this->addFields( [
273
			'rev_id',
274
			'rev_timestamp',
275
			'page_namespace',
276
			'page_title',
277
			'rev_user',
278
			'rev_user_text',
279
			'rev_deleted'
280
		] );
281
282
		if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
283
			$this->fld_patrolled
284
		) {
285
			if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
286
				$this->dieUsage(
287
					'You need the patrol right to request the patrolled flag',
288
					'permissiondenied'
289
				);
290
			}
291
292
			// Use a redundant join condition on both
293
			// timestamp and ID so we can use the timestamp
294
			// index
295
			$index['recentchanges'] = 'rc_user_text';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$index was never initialized. Although not strictly required by PHP, it is generally a good practice to add $index = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
296
			if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
297
				// Put the tables in the right order for
298
				// STRAIGHT_JOIN
299
				$tables = [ 'revision', 'recentchanges', 'page' ];
300
				$this->addOption( 'STRAIGHT_JOIN' );
301
				$this->addWhere( 'rc_user_text=rev_user_text' );
302
				$this->addWhere( 'rc_timestamp=rev_timestamp' );
303
				$this->addWhere( 'rc_this_oldid=rev_id' );
304
			} else {
305
				$tables[] = 'recentchanges';
306
				$this->addJoinConds( [ 'recentchanges' => [
307
					'LEFT JOIN', [
308
						'rc_user_text=rev_user_text',
309
						'rc_timestamp=rev_timestamp',
310
						'rc_this_oldid=rev_id' ] ] ] );
311
			}
312
		}
313
314
		$this->addTables( $tables );
315
		$this->addFieldsIf( 'rev_page', $this->fld_ids );
316
		$this->addFieldsIf( 'page_latest', $this->fld_flags );
317
		// $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed?
318
		$this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment );
319
		$this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff );
320
		$this->addFieldsIf( 'rev_minor_edit', $this->fld_flags );
321
		$this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff || $this->fld_ids );
322
		$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
323
324 View Code Duplication
		if ( $this->fld_tags ) {
325
			$this->addTables( 'tag_summary' );
326
			$this->addJoinConds(
327
				[ 'tag_summary' => [ 'LEFT JOIN', [ 'rev_id=ts_rev_id' ] ] ]
328
			);
329
			$this->addFields( 'ts_tags' );
330
		}
331
332
		if ( isset( $this->params['tag'] ) ) {
333
			$this->addTables( 'change_tag' );
334
			$this->addJoinConds(
335
				[ 'change_tag' => [ 'INNER JOIN', [ 'rev_id=ct_rev_id' ] ] ]
336
			);
337
			$this->addWhereFld( 'ct_tag', $this->params['tag'] );
338
		}
339
340
		$this->addOption( 'USE INDEX', $index );
0 ignored issues
show
Bug introduced by
The variable $index does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
341
	}
342
343
	/**
344
	 * Extract fields from the database row and append them to a result array
345
	 *
346
	 * @param stdClass $row
347
	 * @return array
348
	 */
349
	private function extractRowInfo( $row ) {
350
		$vals = [];
351
		$anyHidden = false;
352
353
		if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
354
			$vals['texthidden'] = true;
355
			$anyHidden = true;
356
		}
357
358
		// Any rows where we can't view the user were filtered out in the query.
359
		$vals['userid'] = (int)$row->rev_user;
360
		$vals['user'] = $row->rev_user_text;
361
		if ( $row->rev_deleted & Revision::DELETED_USER ) {
362
			$vals['userhidden'] = true;
363
			$anyHidden = true;
364
		}
365
		if ( $this->fld_ids ) {
366
			$vals['pageid'] = intval( $row->rev_page );
367
			$vals['revid'] = intval( $row->rev_id );
368
			// $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed?
369
370
			if ( !is_null( $row->rev_parent_id ) ) {
371
				$vals['parentid'] = intval( $row->rev_parent_id );
372
			}
373
		}
374
375
		$title = Title::makeTitle( $row->page_namespace, $row->page_title );
376
377
		if ( $this->fld_title ) {
378
			ApiQueryBase::addTitleInfo( $vals, $title );
379
		}
380
381
		if ( $this->fld_timestamp ) {
382
			$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
383
		}
384
385
		if ( $this->fld_flags ) {
386
			$vals['new'] = $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id );
387
			$vals['minor'] = (bool)$row->rev_minor_edit;
388
			$vals['top'] = $row->page_latest == $row->rev_id;
389
		}
390
391 View Code Duplication
		if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) {
392
			if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
393
				$vals['commenthidden'] = true;
394
				$anyHidden = true;
395
			}
396
397
			$userCanView = Revision::userCanBitfield(
398
				$row->rev_deleted,
399
				Revision::DELETED_COMMENT, $this->getUser()
400
			);
401
402
			if ( $userCanView ) {
403
				if ( $this->fld_comment ) {
404
					$vals['comment'] = $row->rev_comment;
405
				}
406
407
				if ( $this->fld_parsedcomment ) {
408
					$vals['parsedcomment'] = Linker::formatComment( $row->rev_comment, $title );
409
				}
410
			}
411
		}
412
413
		if ( $this->fld_patrolled ) {
414
			$vals['patrolled'] = (bool)$row->rc_patrolled;
415
		}
416
417
		if ( $this->fld_size && !is_null( $row->rev_len ) ) {
418
			$vals['size'] = intval( $row->rev_len );
419
		}
420
421
		if ( $this->fld_sizediff
422
			&& !is_null( $row->rev_len )
423
			&& !is_null( $row->rev_parent_id )
424
		) {
425
			$parentLen = isset( $this->parentLens[$row->rev_parent_id] )
426
				? $this->parentLens[$row->rev_parent_id]
427
				: 0;
428
			$vals['sizediff'] = intval( $row->rev_len - $parentLen );
429
		}
430
431 View Code Duplication
		if ( $this->fld_tags ) {
432
			if ( $row->ts_tags ) {
433
				$tags = explode( ',', $row->ts_tags );
434
				ApiResult::setIndexedTagName( $tags, 'tag' );
435
				$vals['tags'] = $tags;
436
			} else {
437
				$vals['tags'] = [];
438
			}
439
		}
440
441
		if ( $anyHidden && $row->rev_deleted & Revision::DELETED_RESTRICTED ) {
442
			$vals['suppressed'] = true;
443
		}
444
445
		return $vals;
446
	}
447
448
	private function continueStr( $row ) {
449
		if ( $this->multiUserMode ) {
450
			if ( $this->idMode ) {
451
				return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
452
			} else {
453
				return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
454
			}
455
		} else {
456
			return "$row->rev_timestamp|$row->rev_id";
457
		}
458
	}
459
460
	public function getCacheMode( $params ) {
461
		// This module provides access to deleted revisions and patrol flags if
462
		// the requester is logged in
463
		return 'anon-public-user-private';
464
	}
465
466
	public function getAllowedParams() {
467
		return [
468
			'limit' => [
469
				ApiBase::PARAM_DFLT => 10,
470
				ApiBase::PARAM_TYPE => 'limit',
471
				ApiBase::PARAM_MIN => 1,
472
				ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
473
				ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
474
			],
475
			'start' => [
476
				ApiBase::PARAM_TYPE => 'timestamp'
477
			],
478
			'end' => [
479
				ApiBase::PARAM_TYPE => 'timestamp'
480
			],
481
			'continue' => [
482
				ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
483
			],
484
			'user' => [
485
				ApiBase::PARAM_TYPE => 'user',
486
				ApiBase::PARAM_ISMULTI => true
487
			],
488
			'userprefix' => null,
489
			'dir' => [
490
				ApiBase::PARAM_DFLT => 'older',
491
				ApiBase::PARAM_TYPE => [
492
					'newer',
493
					'older'
494
				],
495
				ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
496
			],
497
			'namespace' => [
498
				ApiBase::PARAM_ISMULTI => true,
499
				ApiBase::PARAM_TYPE => 'namespace'
500
			],
501
			'prop' => [
502
				ApiBase::PARAM_ISMULTI => true,
503
				ApiBase::PARAM_DFLT => 'ids|title|timestamp|comment|size|flags',
504
				ApiBase::PARAM_TYPE => [
505
					'ids',
506
					'title',
507
					'timestamp',
508
					'comment',
509
					'parsedcomment',
510
					'size',
511
					'sizediff',
512
					'flags',
513
					'patrolled',
514
					'tags'
515
				],
516
				ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
517
			],
518
			'show' => [
519
				ApiBase::PARAM_ISMULTI => true,
520
				ApiBase::PARAM_TYPE => [
521
					'minor',
522
					'!minor',
523
					'patrolled',
524
					'!patrolled',
525
					'top',
526
					'!top',
527
					'new',
528
					'!new',
529
				],
530
				ApiBase::PARAM_HELP_MSG => [
531
					'apihelp-query+usercontribs-param-show',
532
					$this->getConfig()->get( 'RCMaxAge' )
533
				],
534
			],
535
			'tag' => null,
536
			'toponly' => [
537
				ApiBase::PARAM_DFLT => false,
538
				ApiBase::PARAM_DEPRECATED => true,
539
			],
540
		];
541
	}
542
543
	protected function getExamplesMessages() {
544
		return [
545
			'action=query&list=usercontribs&ucuser=Example'
546
				=> 'apihelp-query+usercontribs-example-user',
547
			'action=query&list=usercontribs&ucuserprefix=192.0.2.'
548
				=> 'apihelp-query+usercontribs-example-ipprefix',
549
		];
550
	}
551
552
	public function getHelpUrls() {
553
		return 'https://www.mediawiki.org/wiki/API:Usercontribs';
554
	}
555
}
556