Merge "Don't check namespace in SpecialWantedtemplates"
[lhc/web/wiklou.git] / includes / api / ApiQueryRecentChanges.php
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,
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 = array();
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 Array(tokenname => function)
52 */
53 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 array();
63 }
64
65 $this->tokenFunctions = array(
66 'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' )
67 );
68 Hooks::run( 'APIQueryRecentChangesTokens', array( &$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 $index = array( 'recentchanges' => 'rc_timestamp' ); // May change
154 $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
155
156 if ( !is_null( $params['continue'] ) ) {
157 $cont = explode( '|', $params['continue'] );
158 $this->dieContinueUsageIf( count( $cont ) != 2 );
159 $db = $this->getDB();
160 $timestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
161 $id = intval( $cont[1] );
162 $this->dieContinueUsageIf( $id != $cont[1] );
163 $op = $params['dir'] === 'older' ? '<' : '>';
164 $this->addWhere(
165 "rc_timestamp $op $timestamp OR " .
166 "(rc_timestamp = $timestamp AND " .
167 "rc_id $op= $id)"
168 );
169 }
170
171 $order = $params['dir'] === 'older' ? 'DESC' : 'ASC';
172 $this->addOption( 'ORDER BY', array(
173 "rc_timestamp $order",
174 "rc_id $order",
175 ) );
176
177 $this->addWhereFld( 'rc_namespace', $params['namespace'] );
178
179 if ( !is_null( $params['type'] ) ) {
180 try {
181 $this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
182 } catch ( Exception $e ) {
183 ApiBase::dieDebug( __METHOD__, $e->getMessage() );
184 }
185 }
186
187 if ( !is_null( $params['show'] ) ) {
188 $show = array_flip( $params['show'] );
189
190 /* Check for conflicting parameters. */
191 if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
192 || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
193 || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
194 || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
195 || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
196 || ( isset( $show['patrolled'] ) && isset( $show['unpatrolled'] ) )
197 || ( isset( $show['!patrolled'] ) && isset( $show['unpatrolled'] ) )
198 ) {
199 $this->dieUsageMsg( 'show' );
200 }
201
202 // Check permissions
203 if ( isset( $show['patrolled'] )
204 || isset( $show['!patrolled'] )
205 || isset( $show['unpatrolled'] )
206 ) {
207 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
208 $this->dieUsage(
209 'You need the patrol right to request the patrolled flag',
210 'permissiondenied'
211 );
212 }
213 }
214
215 /* Add additional conditions to query depending upon parameters. */
216 $this->addWhereIf( 'rc_minor = 0', isset( $show['!minor'] ) );
217 $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
218 $this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
219 $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
220 $this->addWhereIf( 'rc_user = 0', isset( $show['anon'] ) );
221 $this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
222 $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
223 $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
224 $this->addWhereIf( 'page_is_redirect = 1', isset( $show['redirect'] ) );
225
226 if ( isset( $show['unpatrolled'] ) ) {
227 // See ChangesList:isUnpatrolled
228 if ( $user->useRCPatrol() ) {
229 $this->addWhere( 'rc_patrolled = 0' );
230 } elseif ( $user->useNPPatrol() ) {
231 $this->addWhere( 'rc_patrolled = 0' );
232 $this->addWhereFld( 'rc_type', RC_NEW );
233 }
234 }
235
236 // Don't throw log entries out the window here
237 $this->addWhereIf(
238 'page_is_redirect = 0 OR page_is_redirect IS NULL',
239 isset( $show['!redirect'] )
240 );
241 }
242
243 if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
244 $this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
245 }
246
247 if ( !is_null( $params['user'] ) ) {
248 $this->addWhereFld( 'rc_user_text', $params['user'] );
249 $index['recentchanges'] = 'rc_user_text';
250 }
251
252 if ( !is_null( $params['excludeuser'] ) ) {
253 // We don't use the rc_user_text index here because
254 // * it would require us to sort by rc_user_text before rc_timestamp
255 // * the != condition doesn't throw out too many rows anyway
256 $this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
257 }
258
259 /* Add the fields we're concerned with to our query. */
260 $this->addFields( array(
261 'rc_id',
262 'rc_timestamp',
263 'rc_namespace',
264 'rc_title',
265 'rc_cur_id',
266 'rc_type',
267 'rc_deleted'
268 ) );
269
270 $showRedirects = false;
271 /* Determine what properties we need to display. */
272 if ( !is_null( $params['prop'] ) ) {
273 $prop = array_flip( $params['prop'] );
274
275 /* Set up internal members based upon params. */
276 $this->initProperties( $prop );
277
278 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
279 $this->dieUsage(
280 'You need the patrol right to request the patrolled flag',
281 'permissiondenied'
282 );
283 }
284
285 /* Add fields to our query if they are specified as a needed parameter. */
286 $this->addFieldsIf( array( 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
287 $this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
288 $this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid );
289 $this->addFieldsIf( 'rc_user_text', $this->fld_user );
290 $this->addFieldsIf( array( 'rc_minor', 'rc_type', 'rc_bot' ), $this->fld_flags );
291 $this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
292 $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
293 $this->addFieldsIf(
294 array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ),
295 $this->fld_loginfo
296 );
297 $showRedirects = $this->fld_redirect || isset( $show['redirect'] )
298 || isset( $show['!redirect'] );
299 }
300
301 if ( $this->fld_tags ) {
302 $this->addTables( 'tag_summary' );
303 $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rc_id=ts_rc_id' ) ) ) );
304 $this->addFields( 'ts_tags' );
305 }
306
307 if ( $this->fld_sha1 ) {
308 $this->addTables( 'revision' );
309 $this->addJoinConds( array( 'revision' => array( 'LEFT JOIN',
310 array( 'rc_this_oldid=rev_id' ) ) ) );
311 $this->addFields( array( 'rev_sha1', 'rev_deleted' ) );
312 }
313
314 if ( $params['toponly'] || $showRedirects ) {
315 $this->addTables( 'page' );
316 $this->addJoinConds( array( 'page' => array( 'LEFT JOIN',
317 array( '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 if ( !is_null( $params['tag'] ) ) {
326 $this->addTables( 'change_tag' );
327 $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
328 $this->addWhereFld( 'ct_tag', $params['tag'] );
329 }
330
331 // Paranoia: avoid brute force searches (bug 17342)
332 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( array(
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 $this->addOption( 'USE INDEX', $index );
364
365 $count = 0;
366 /* Perform the actual query. */
367 $res = $this->select( __METHOD__ );
368
369 $titles = array();
370
371 $result = $this->getResult();
372
373 /* Iterate through the rows, adding data extracted from them to our query result. */
374 foreach ( $res as $row ) {
375 if ( ++$count > $params['limit'] ) {
376 // We've reached the one extra which shows that there are
377 // additional pages to be had. Stop here...
378 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
379 break;
380 }
381
382 if ( is_null( $resultPageSet ) ) {
383 /* Extract the data from a single row. */
384 $vals = $this->extractRowInfo( $row );
385
386 /* Add that row's data to our final output. */
387 $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
388 if ( !$fit ) {
389 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
390 break;
391 }
392 } else {
393 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
394 }
395 }
396
397 if ( is_null( $resultPageSet ) ) {
398 /* Format the result */
399 $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'rc' );
400 } else {
401 $resultPageSet->populateFromTitles( $titles );
402 }
403 }
404
405 /**
406 * Extracts from a single sql row the data needed to describe one recent change.
407 *
408 * @param stdClass $row The row from which to extract the data.
409 * @return array An array mapping strings (descriptors) to their respective string values.
410 * @access public
411 */
412 public function extractRowInfo( $row ) {
413 /* Determine the title of the page that has been changed. */
414 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
415 $user = $this->getUser();
416
417 /* Our output data. */
418 $vals = array();
419
420 $type = intval( $row->rc_type );
421 $vals['type'] = RecentChange::parseFromRCType( $type );
422
423 $anyHidden = false;
424
425 /* Create a new entry in the result for the title. */
426 if ( $this->fld_title || $this->fld_ids ) {
427 if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
428 $vals['actionhidden'] = true;
429 $anyHidden = true;
430 }
431 if ( $type !== RC_LOG ||
432 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
433 ) {
434 if ( $this->fld_title ) {
435 ApiQueryBase::addTitleInfo( $vals, $title );
436 }
437 if ( $this->fld_ids ) {
438 $vals['pageid'] = intval( $row->rc_cur_id );
439 $vals['revid'] = intval( $row->rc_this_oldid );
440 $vals['old_revid'] = intval( $row->rc_last_oldid );
441 }
442 }
443 }
444
445 if ( $this->fld_ids ) {
446 $vals['rcid'] = intval( $row->rc_id );
447 }
448
449 /* Add user data and 'anon' flag, if user is anonymous. */
450 if ( $this->fld_user || $this->fld_userid ) {
451 if ( $row->rc_deleted & Revision::DELETED_USER ) {
452 $vals['userhidden'] = true;
453 $anyHidden = true;
454 }
455 if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
456 if ( $this->fld_user ) {
457 $vals['user'] = $row->rc_user_text;
458 }
459
460 if ( $this->fld_userid ) {
461 $vals['userid'] = (int)$row->rc_user;
462 }
463
464 if ( !$row->rc_user ) {
465 $vals['anon'] = true;
466 }
467 }
468 }
469
470 /* Add flags, such as new, minor, bot. */
471 if ( $this->fld_flags ) {
472 $vals['bot'] = (bool)$row->rc_bot;
473 $vals['new'] = $row->rc_type == RC_NEW;
474 $vals['minor'] = (bool)$row->rc_minor;
475 }
476
477 /* Add sizes of each revision. (Only available on 1.10+) */
478 if ( $this->fld_sizes ) {
479 $vals['oldlen'] = intval( $row->rc_old_len );
480 $vals['newlen'] = intval( $row->rc_new_len );
481 }
482
483 /* Add the timestamp. */
484 if ( $this->fld_timestamp ) {
485 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
486 }
487
488 /* Add edit summary / log summary. */
489 if ( $this->fld_comment || $this->fld_parsedcomment ) {
490 if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
491 $vals['commenthidden'] = true;
492 $anyHidden = true;
493 }
494 if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
495 if ( $this->fld_comment && isset( $row->rc_comment ) ) {
496 $vals['comment'] = $row->rc_comment;
497 }
498
499 if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
500 $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
501 }
502 }
503 }
504
505 if ( $this->fld_redirect ) {
506 $vals['redirect'] = (bool)$row->page_is_redirect;
507 }
508
509 /* Add the patrolled flag */
510 if ( $this->fld_patrolled ) {
511 $vals['patrolled'] = $row->rc_patrolled == 1;
512 $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
513 }
514
515 if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
516 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
517 $vals['actionhidden'] = true;
518 $anyHidden = true;
519 }
520 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
521 $vals['logid'] = intval( $row->rc_logid );
522 $vals['logtype'] = $row->rc_log_type;
523 $vals['logaction'] = $row->rc_log_action;
524 $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
525 }
526 }
527
528 if ( $this->fld_tags ) {
529 if ( $row->ts_tags ) {
530 $tags = explode( ',', $row->ts_tags );
531 ApiResult::setIndexedTagName( $tags, 'tag' );
532 $vals['tags'] = $tags;
533 } else {
534 $vals['tags'] = array();
535 }
536 }
537
538 if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
539 if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
540 $vals['sha1hidden'] = true;
541 $anyHidden = true;
542 }
543 if ( Revision::userCanBitfield( $row->rev_deleted, Revision::DELETED_TEXT, $user ) ) {
544 if ( $row->rev_sha1 !== '' ) {
545 $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 );
546 } else {
547 $vals['sha1'] = '';
548 }
549 }
550 }
551
552 if ( !is_null( $this->token ) ) {
553 $tokenFunctions = $this->getTokenFunctions();
554 foreach ( $this->token as $t ) {
555 $val = call_user_func( $tokenFunctions[$t], $row->rc_cur_id,
556 $title, RecentChange::newFromRow( $row ) );
557 if ( $val === false ) {
558 $this->setWarning( "Action '$t' is not allowed for the current user" );
559 } else {
560 $vals[$t . 'token'] = $val;
561 }
562 }
563 }
564
565 if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
566 $vals['suppressed'] = true;
567 }
568
569 return $vals;
570 }
571
572 public function getCacheMode( $params ) {
573 if ( isset( $params['show'] ) ) {
574 foreach ( $params['show'] as $show ) {
575 if ( $show === 'patrolled' || $show === '!patrolled' ) {
576 return 'private';
577 }
578 }
579 }
580 if ( isset( $params['token'] ) ) {
581 return 'private';
582 }
583 if ( $this->userCanSeeRevDel() ) {
584 return 'private';
585 }
586 if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
587 // formatComment() calls wfMessage() among other things
588 return 'anon-public-user-private';
589 }
590
591 return 'public';
592 }
593
594 public function getAllowedParams() {
595 return array(
596 'start' => array(
597 ApiBase::PARAM_TYPE => 'timestamp'
598 ),
599 'end' => array(
600 ApiBase::PARAM_TYPE => 'timestamp'
601 ),
602 'dir' => array(
603 ApiBase::PARAM_DFLT => 'older',
604 ApiBase::PARAM_TYPE => array(
605 'newer',
606 'older'
607 ),
608 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
609 ),
610 'namespace' => array(
611 ApiBase::PARAM_ISMULTI => true,
612 ApiBase::PARAM_TYPE => 'namespace'
613 ),
614 'user' => array(
615 ApiBase::PARAM_TYPE => 'user'
616 ),
617 'excludeuser' => array(
618 ApiBase::PARAM_TYPE => 'user'
619 ),
620 'tag' => null,
621 'prop' => array(
622 ApiBase::PARAM_ISMULTI => true,
623 ApiBase::PARAM_DFLT => 'title|timestamp|ids',
624 ApiBase::PARAM_TYPE => array(
625 'user',
626 'userid',
627 'comment',
628 'parsedcomment',
629 'flags',
630 'timestamp',
631 'title',
632 'ids',
633 'sizes',
634 'redirect',
635 'patrolled',
636 'loginfo',
637 'tags',
638 'sha1',
639 ),
640 ApiBase::PARAM_HELP_MSG_PER_VALUE => array(),
641 ),
642 'token' => array(
643 ApiBase::PARAM_DEPRECATED => true,
644 ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
645 ApiBase::PARAM_ISMULTI => true
646 ),
647 'show' => array(
648 ApiBase::PARAM_ISMULTI => true,
649 ApiBase::PARAM_TYPE => array(
650 'minor',
651 '!minor',
652 'bot',
653 '!bot',
654 'anon',
655 '!anon',
656 'redirect',
657 '!redirect',
658 'patrolled',
659 '!patrolled',
660 'unpatrolled'
661 )
662 ),
663 'limit' => array(
664 ApiBase::PARAM_DFLT => 10,
665 ApiBase::PARAM_TYPE => 'limit',
666 ApiBase::PARAM_MIN => 1,
667 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
668 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
669 ),
670 'type' => array(
671 ApiBase::PARAM_DFLT => 'edit|new|log',
672 ApiBase::PARAM_ISMULTI => true,
673 ApiBase::PARAM_TYPE => array(
674 'edit',
675 'external',
676 'new',
677 'log'
678 )
679 ),
680 'toponly' => false,
681 'continue' => array(
682 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
683 ),
684 );
685 }
686
687 protected function getExamplesMessages() {
688 return array(
689 'action=query&list=recentchanges'
690 => 'apihelp-query+recentchanges-example-simple',
691 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
692 => 'apihelp-query+recentchanges-example-generator',
693 );
694 }
695
696 public function getHelpUrls() {
697 return 'https://www.mediawiki.org/wiki/API:Recentchanges';
698 }
699 }