This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
|
|||
39 | $parentLens; |
||
40 | private $fld_ids = false, $fld_title = false, $fld_timestamp = false, |
||
0 ignored issues
–
show
|
|||
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 replica DBs |
||
61 | // with extra user based indexes or partioning by user. The additional metadata |
||
62 | // queries should use a regular replica DB since the lookup pattern is not all by user. |
||
63 | $dbSecondary = $this->getDB(); // any random replica DB |
||
64 | |||
65 | // TODO: if the query is going only against the revision table, should this be done? |
||
66 | $this->selectNamedDB( 'contributions', DB_REPLICA, '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
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.
![]() |
|||
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
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.
![]() |
|||
106 | $this->userids[] = $row->user_id; |
||
107 | } |
||
108 | $this->idMode = count( $this->userids ) === count( $this->usernames ); |
||
109 | } |
||
110 | } |
||
111 | |||
112 | $this->prepareQuery(); |
||
113 | |||
114 | $hookData = []; |
||
115 | // Do the actual query. |
||
116 | $res = $this->select( __METHOD__, [], $hookData ); |
||
117 | |||
118 | if ( $this->fld_sizediff ) { |
||
119 | $revIds = []; |
||
120 | foreach ( $res as $row ) { |
||
121 | if ( $row->rev_parent_id ) { |
||
122 | $revIds[] = $row->rev_parent_id; |
||
123 | } |
||
124 | } |
||
125 | $this->parentLens = Revision::getParentLengths( $dbSecondary, $revIds ); |
||
126 | $res->rewind(); // reset |
||
127 | } |
||
128 | |||
129 | // Initialise some variables |
||
130 | $count = 0; |
||
131 | $limit = $this->params['limit']; |
||
132 | |||
133 | // Fetch each row |
||
134 | foreach ( $res as $row ) { |
||
135 | if ( ++$count > $limit ) { |
||
136 | // We've reached the one extra which shows that there are |
||
137 | // additional pages to be had. Stop here... |
||
138 | $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) ); |
||
139 | break; |
||
140 | } |
||
141 | |||
142 | $vals = $this->extractRowInfo( $row ); |
||
0 ignored issues
–
show
It seems like
$row defined by $row on line 134 can be null ; however, ApiQueryContributions::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);
}
}
![]() |
|||
143 | $fit = $this->processRow( $row, $vals, $hookData ) && |
||
0 ignored issues
–
show
It seems like
$row defined by $row on line 134 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);
}
}
![]() 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. ![]() |
|||
144 | $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals ); |
||
145 | if ( !$fit ) { |
||
146 | $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) ); |
||
147 | break; |
||
148 | } |
||
149 | } |
||
150 | |||
151 | $this->getResult()->addIndexedTagName( |
||
152 | [ 'query', $this->getModuleName() ], |
||
153 | 'item' |
||
154 | ); |
||
155 | } |
||
156 | |||
157 | /** |
||
158 | * Prepares the query and returns the limit of rows requested |
||
159 | */ |
||
160 | private function prepareQuery() { |
||
161 | // We're after the revision table, and the corresponding page |
||
162 | // row for anything we retrieve. We may also need the |
||
163 | // recentchanges row and/or tag summary row. |
||
164 | $user = $this->getUser(); |
||
165 | $tables = [ 'page', 'revision' ]; // Order may change |
||
166 | $this->addWhere( 'page_id=rev_page' ); |
||
167 | |||
168 | // Handle continue parameter |
||
169 | if ( !is_null( $this->params['continue'] ) ) { |
||
170 | $continue = explode( '|', $this->params['continue'] ); |
||
171 | $db = $this->getDB(); |
||
172 | if ( $this->multiUserMode ) { |
||
173 | $this->dieContinueUsageIf( count( $continue ) != 4 ); |
||
174 | $modeFlag = array_shift( $continue ); |
||
175 | $this->dieContinueUsageIf( !in_array( $modeFlag, [ 'id', 'name' ] ) ); |
||
176 | if ( $this->idMode && $modeFlag === 'name' ) { |
||
177 | // The users were created since this query started, but we |
||
178 | // can't go back and change modes now. So just keep on with |
||
179 | // name mode. |
||
180 | $this->idMode = false; |
||
181 | } |
||
182 | $this->dieContinueUsageIf( ( $modeFlag === 'id' ) !== $this->idMode ); |
||
183 | $userField = $this->idMode ? 'rev_user' : 'rev_user_text'; |
||
184 | $encUser = $db->addQuotes( array_shift( $continue ) ); |
||
185 | } else { |
||
186 | $this->dieContinueUsageIf( count( $continue ) != 2 ); |
||
187 | } |
||
188 | $encTS = $db->addQuotes( $db->timestamp( $continue[0] ) ); |
||
189 | $encId = (int)$continue[1]; |
||
190 | $this->dieContinueUsageIf( $encId != $continue[1] ); |
||
191 | $op = ( $this->params['dir'] == 'older' ? '<' : '>' ); |
||
192 | if ( $this->multiUserMode ) { |
||
193 | $this->addWhere( |
||
194 | "$userField $op $encUser OR " . |
||
0 ignored issues
–
show
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
![]() 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
![]() |
|||
195 | "($userField = $encUser AND " . |
||
196 | "(rev_timestamp $op $encTS OR " . |
||
197 | "(rev_timestamp = $encTS AND " . |
||
198 | "rev_id $op= $encId)))" |
||
199 | ); |
||
200 | } else { |
||
201 | $this->addWhere( |
||
202 | "rev_timestamp $op $encTS OR " . |
||
203 | "(rev_timestamp = $encTS AND " . |
||
204 | "rev_id $op= $encId)" |
||
205 | ); |
||
206 | } |
||
207 | } |
||
208 | |||
209 | // Don't include any revisions where we're not supposed to be able to |
||
210 | // see the username. |
||
211 | if ( !$user->isAllowed( 'deletedhistory' ) ) { |
||
212 | $bitmask = Revision::DELETED_USER; |
||
213 | } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) { |
||
214 | $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; |
||
215 | } else { |
||
216 | $bitmask = 0; |
||
217 | } |
||
218 | if ( $bitmask ) { |
||
219 | $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" ); |
||
220 | } |
||
221 | |||
222 | // We only want pages by the specified users. |
||
223 | if ( $this->prefixMode ) { |
||
224 | $this->addWhere( 'rev_user_text' . |
||
225 | $this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) ); |
||
226 | } elseif ( $this->idMode ) { |
||
227 | $this->addWhereFld( 'rev_user', $this->userids ); |
||
228 | } else { |
||
229 | $this->addWhereFld( 'rev_user_text', $this->usernames ); |
||
230 | } |
||
231 | // ... and in the specified timeframe. |
||
232 | // Ensure the same sort order for rev_user/rev_user_text and rev_timestamp |
||
233 | // so our query is indexed |
||
234 | if ( $this->multiUserMode ) { |
||
235 | $this->addWhereRange( $this->idMode ? 'rev_user' : 'rev_user_text', |
||
236 | $this->params['dir'], null, null ); |
||
237 | } |
||
238 | $this->addTimestampWhereRange( 'rev_timestamp', |
||
239 | $this->params['dir'], $this->params['start'], $this->params['end'] ); |
||
240 | // Include in ORDER BY for uniqueness |
||
241 | $this->addWhereRange( 'rev_id', $this->params['dir'], null, null ); |
||
242 | |||
243 | $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); |
||
244 | |||
245 | $show = $this->params['show']; |
||
246 | if ( $this->params['toponly'] ) { // deprecated/old param |
||
247 | $show[] = 'top'; |
||
248 | } |
||
249 | if ( !is_null( $show ) ) { |
||
250 | $show = array_flip( $show ); |
||
251 | |||
252 | View Code Duplication | if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) ) |
|
253 | || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) |
||
254 | || ( isset( $show['top'] ) && isset( $show['!top'] ) ) |
||
255 | || ( isset( $show['new'] ) && isset( $show['!new'] ) ) |
||
256 | ) { |
||
257 | $this->dieUsageMsg( 'show' ); |
||
258 | } |
||
259 | |||
260 | $this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) ); |
||
261 | $this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) ); |
||
262 | $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) ); |
||
263 | $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) ); |
||
264 | $this->addWhereIf( 'rev_id != page_latest', isset( $show['!top'] ) ); |
||
265 | $this->addWhereIf( 'rev_id = page_latest', isset( $show['top'] ) ); |
||
266 | $this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) ); |
||
267 | $this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) ); |
||
268 | } |
||
269 | $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); |
||
270 | |||
271 | // Mandatory fields: timestamp allows request continuation |
||
272 | // ns+title checks if the user has access rights for this page |
||
273 | // user_text is necessary if multiple users were specified |
||
274 | $this->addFields( [ |
||
275 | 'rev_id', |
||
276 | 'rev_timestamp', |
||
277 | 'page_namespace', |
||
278 | 'page_title', |
||
279 | 'rev_user', |
||
280 | 'rev_user_text', |
||
281 | 'rev_deleted' |
||
282 | ] ); |
||
283 | |||
284 | if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) || |
||
285 | $this->fld_patrolled |
||
286 | ) { |
||
287 | if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) { |
||
288 | $this->dieUsage( |
||
289 | 'You need the patrol right to request the patrolled flag', |
||
290 | 'permissiondenied' |
||
291 | ); |
||
292 | } |
||
293 | |||
294 | // Use a redundant join condition on both |
||
295 | // timestamp and ID so we can use the timestamp |
||
296 | // index |
||
297 | $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 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. ![]() |
|||
298 | if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) { |
||
299 | // Put the tables in the right order for |
||
300 | // STRAIGHT_JOIN |
||
301 | $tables = [ 'revision', 'recentchanges', 'page' ]; |
||
302 | $this->addOption( 'STRAIGHT_JOIN' ); |
||
303 | $this->addWhere( 'rc_user_text=rev_user_text' ); |
||
304 | $this->addWhere( 'rc_timestamp=rev_timestamp' ); |
||
305 | $this->addWhere( 'rc_this_oldid=rev_id' ); |
||
306 | } else { |
||
307 | $tables[] = 'recentchanges'; |
||
308 | $this->addJoinConds( [ 'recentchanges' => [ |
||
309 | 'LEFT JOIN', [ |
||
310 | 'rc_user_text=rev_user_text', |
||
311 | 'rc_timestamp=rev_timestamp', |
||
312 | 'rc_this_oldid=rev_id' ] ] ] ); |
||
313 | } |
||
314 | } |
||
315 | |||
316 | $this->addTables( $tables ); |
||
317 | $this->addFieldsIf( 'rev_page', $this->fld_ids ); |
||
318 | $this->addFieldsIf( 'page_latest', $this->fld_flags ); |
||
319 | // $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed? |
||
320 | $this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment ); |
||
321 | $this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff ); |
||
322 | $this->addFieldsIf( 'rev_minor_edit', $this->fld_flags ); |
||
323 | $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff || $this->fld_ids ); |
||
324 | $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled ); |
||
325 | |||
326 | View Code Duplication | if ( $this->fld_tags ) { |
|
327 | $this->addTables( 'tag_summary' ); |
||
328 | $this->addJoinConds( |
||
329 | [ 'tag_summary' => [ 'LEFT JOIN', [ 'rev_id=ts_rev_id' ] ] ] |
||
330 | ); |
||
331 | $this->addFields( 'ts_tags' ); |
||
332 | } |
||
333 | |||
334 | if ( isset( $this->params['tag'] ) ) { |
||
335 | $this->addTables( 'change_tag' ); |
||
336 | $this->addJoinConds( |
||
337 | [ 'change_tag' => [ 'INNER JOIN', [ 'rev_id=ct_rev_id' ] ] ] |
||
338 | ); |
||
339 | $this->addWhereFld( 'ct_tag', $this->params['tag'] ); |
||
340 | } |
||
341 | |||
342 | if ( isset( $index ) ) { |
||
343 | $this->addOption( 'USE INDEX', $index ); |
||
344 | } |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * Extract fields from the database row and append them to a result array |
||
349 | * |
||
350 | * @param stdClass $row |
||
351 | * @return array |
||
352 | */ |
||
353 | private function extractRowInfo( $row ) { |
||
354 | $vals = []; |
||
355 | $anyHidden = false; |
||
356 | |||
357 | if ( $row->rev_deleted & Revision::DELETED_TEXT ) { |
||
358 | $vals['texthidden'] = true; |
||
359 | $anyHidden = true; |
||
360 | } |
||
361 | |||
362 | // Any rows where we can't view the user were filtered out in the query. |
||
363 | $vals['userid'] = (int)$row->rev_user; |
||
364 | $vals['user'] = $row->rev_user_text; |
||
365 | if ( $row->rev_deleted & Revision::DELETED_USER ) { |
||
366 | $vals['userhidden'] = true; |
||
367 | $anyHidden = true; |
||
368 | } |
||
369 | if ( $this->fld_ids ) { |
||
370 | $vals['pageid'] = intval( $row->rev_page ); |
||
371 | $vals['revid'] = intval( $row->rev_id ); |
||
372 | // $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed? |
||
373 | |||
374 | if ( !is_null( $row->rev_parent_id ) ) { |
||
375 | $vals['parentid'] = intval( $row->rev_parent_id ); |
||
376 | } |
||
377 | } |
||
378 | |||
379 | $title = Title::makeTitle( $row->page_namespace, $row->page_title ); |
||
380 | |||
381 | if ( $this->fld_title ) { |
||
382 | ApiQueryBase::addTitleInfo( $vals, $title ); |
||
383 | } |
||
384 | |||
385 | if ( $this->fld_timestamp ) { |
||
386 | $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp ); |
||
387 | } |
||
388 | |||
389 | if ( $this->fld_flags ) { |
||
390 | $vals['new'] = $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id ); |
||
391 | $vals['minor'] = (bool)$row->rev_minor_edit; |
||
392 | $vals['top'] = $row->page_latest == $row->rev_id; |
||
393 | } |
||
394 | |||
395 | View Code Duplication | if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) { |
|
396 | if ( $row->rev_deleted & Revision::DELETED_COMMENT ) { |
||
397 | $vals['commenthidden'] = true; |
||
398 | $anyHidden = true; |
||
399 | } |
||
400 | |||
401 | $userCanView = Revision::userCanBitfield( |
||
402 | $row->rev_deleted, |
||
403 | Revision::DELETED_COMMENT, $this->getUser() |
||
404 | ); |
||
405 | |||
406 | if ( $userCanView ) { |
||
407 | if ( $this->fld_comment ) { |
||
408 | $vals['comment'] = $row->rev_comment; |
||
409 | } |
||
410 | |||
411 | if ( $this->fld_parsedcomment ) { |
||
412 | $vals['parsedcomment'] = Linker::formatComment( $row->rev_comment, $title ); |
||
413 | } |
||
414 | } |
||
415 | } |
||
416 | |||
417 | if ( $this->fld_patrolled ) { |
||
418 | $vals['patrolled'] = (bool)$row->rc_patrolled; |
||
419 | } |
||
420 | |||
421 | if ( $this->fld_size && !is_null( $row->rev_len ) ) { |
||
422 | $vals['size'] = intval( $row->rev_len ); |
||
423 | } |
||
424 | |||
425 | if ( $this->fld_sizediff |
||
426 | && !is_null( $row->rev_len ) |
||
427 | && !is_null( $row->rev_parent_id ) |
||
428 | ) { |
||
429 | $parentLen = isset( $this->parentLens[$row->rev_parent_id] ) |
||
430 | ? $this->parentLens[$row->rev_parent_id] |
||
431 | : 0; |
||
432 | $vals['sizediff'] = intval( $row->rev_len - $parentLen ); |
||
433 | } |
||
434 | |||
435 | View Code Duplication | if ( $this->fld_tags ) { |
|
436 | if ( $row->ts_tags ) { |
||
437 | $tags = explode( ',', $row->ts_tags ); |
||
438 | ApiResult::setIndexedTagName( $tags, 'tag' ); |
||
439 | $vals['tags'] = $tags; |
||
440 | } else { |
||
441 | $vals['tags'] = []; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | if ( $anyHidden && $row->rev_deleted & Revision::DELETED_RESTRICTED ) { |
||
446 | $vals['suppressed'] = true; |
||
447 | } |
||
448 | |||
449 | return $vals; |
||
450 | } |
||
451 | |||
452 | private function continueStr( $row ) { |
||
453 | if ( $this->multiUserMode ) { |
||
454 | if ( $this->idMode ) { |
||
455 | return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id"; |
||
456 | } else { |
||
457 | return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id"; |
||
458 | } |
||
459 | } else { |
||
460 | return "$row->rev_timestamp|$row->rev_id"; |
||
461 | } |
||
462 | } |
||
463 | |||
464 | public function getCacheMode( $params ) { |
||
465 | // This module provides access to deleted revisions and patrol flags if |
||
466 | // the requester is logged in |
||
467 | return 'anon-public-user-private'; |
||
468 | } |
||
469 | |||
470 | public function getAllowedParams() { |
||
471 | return [ |
||
472 | 'limit' => [ |
||
473 | ApiBase::PARAM_DFLT => 10, |
||
474 | ApiBase::PARAM_TYPE => 'limit', |
||
475 | ApiBase::PARAM_MIN => 1, |
||
476 | ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, |
||
477 | ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 |
||
478 | ], |
||
479 | 'start' => [ |
||
480 | ApiBase::PARAM_TYPE => 'timestamp' |
||
481 | ], |
||
482 | 'end' => [ |
||
483 | ApiBase::PARAM_TYPE => 'timestamp' |
||
484 | ], |
||
485 | 'continue' => [ |
||
486 | ApiBase::PARAM_HELP_MSG => 'api-help-param-continue', |
||
487 | ], |
||
488 | 'user' => [ |
||
489 | ApiBase::PARAM_TYPE => 'user', |
||
490 | ApiBase::PARAM_ISMULTI => true |
||
491 | ], |
||
492 | 'userprefix' => null, |
||
493 | 'dir' => [ |
||
494 | ApiBase::PARAM_DFLT => 'older', |
||
495 | ApiBase::PARAM_TYPE => [ |
||
496 | 'newer', |
||
497 | 'older' |
||
498 | ], |
||
499 | ApiBase::PARAM_HELP_MSG => 'api-help-param-direction', |
||
500 | ], |
||
501 | 'namespace' => [ |
||
502 | ApiBase::PARAM_ISMULTI => true, |
||
503 | ApiBase::PARAM_TYPE => 'namespace' |
||
504 | ], |
||
505 | 'prop' => [ |
||
506 | ApiBase::PARAM_ISMULTI => true, |
||
507 | ApiBase::PARAM_DFLT => 'ids|title|timestamp|comment|size|flags', |
||
508 | ApiBase::PARAM_TYPE => [ |
||
509 | 'ids', |
||
510 | 'title', |
||
511 | 'timestamp', |
||
512 | 'comment', |
||
513 | 'parsedcomment', |
||
514 | 'size', |
||
515 | 'sizediff', |
||
516 | 'flags', |
||
517 | 'patrolled', |
||
518 | 'tags' |
||
519 | ], |
||
520 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [], |
||
521 | ], |
||
522 | 'show' => [ |
||
523 | ApiBase::PARAM_ISMULTI => true, |
||
524 | ApiBase::PARAM_TYPE => [ |
||
525 | 'minor', |
||
526 | '!minor', |
||
527 | 'patrolled', |
||
528 | '!patrolled', |
||
529 | 'top', |
||
530 | '!top', |
||
531 | 'new', |
||
532 | '!new', |
||
533 | ], |
||
534 | ApiBase::PARAM_HELP_MSG => [ |
||
535 | 'apihelp-query+usercontribs-param-show', |
||
536 | $this->getConfig()->get( 'RCMaxAge' ) |
||
537 | ], |
||
538 | ], |
||
539 | 'tag' => null, |
||
540 | 'toponly' => [ |
||
541 | ApiBase::PARAM_DFLT => false, |
||
542 | ApiBase::PARAM_DEPRECATED => true, |
||
543 | ], |
||
544 | ]; |
||
545 | } |
||
546 | |||
547 | protected function getExamplesMessages() { |
||
548 | return [ |
||
549 | 'action=query&list=usercontribs&ucuser=Example' |
||
550 | => 'apihelp-query+usercontribs-example-user', |
||
551 | 'action=query&list=usercontribs&ucuserprefix=192.0.2.' |
||
552 | => 'apihelp-query+usercontribs-example-ipprefix', |
||
553 | ]; |
||
554 | } |
||
555 | |||
556 | public function getHelpUrls() { |
||
557 | return 'https://www.mediawiki.org/wiki/API:Usercontribs'; |
||
558 | } |
||
559 | } |
||
560 |
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.