Merge "Remove ApiQueryBase::getDirectionDescription()"
[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 $this->addFieldsIf( array( 'rc_this_oldid' ),
301 $resultPageSet && $params['generaterevisions'] );
302
303 if ( $this->fld_tags ) {
304 $this->addTables( 'tag_summary' );
305 $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rc_id=ts_rc_id' ) ) ) );
306 $this->addFields( 'ts_tags' );
307 }
308
309 if ( $this->fld_sha1 ) {
310 $this->addTables( 'revision' );
311 $this->addJoinConds( array( 'revision' => array( 'LEFT JOIN',
312 array( 'rc_this_oldid=rev_id' ) ) ) );
313 $this->addFields( array( 'rev_sha1', 'rev_deleted' ) );
314 }
315
316 if ( $params['toponly'] || $showRedirects ) {
317 $this->addTables( 'page' );
318 $this->addJoinConds( array( 'page' => array( 'LEFT JOIN',
319 array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) );
320 $this->addFields( 'page_is_redirect' );
321
322 if ( $params['toponly'] ) {
323 $this->addWhere( 'rc_this_oldid = page_latest' );
324 }
325 }
326
327 if ( !is_null( $params['tag'] ) ) {
328 $this->addTables( 'change_tag' );
329 $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
330 $this->addWhereFld( 'ct_tag', $params['tag'] );
331 }
332
333 // Paranoia: avoid brute force searches (bug 17342)
334 if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
335 if ( !$user->isAllowed( 'deletedhistory' ) ) {
336 $bitmask = Revision::DELETED_USER;
337 } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
338 $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
339 } else {
340 $bitmask = 0;
341 }
342 if ( $bitmask ) {
343 $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
344 }
345 }
346 if ( $this->getRequest()->getCheck( 'namespace' ) ) {
347 // LogPage::DELETED_ACTION hides the affected page, too.
348 if ( !$user->isAllowed( 'deletedhistory' ) ) {
349 $bitmask = LogPage::DELETED_ACTION;
350 } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
351 $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
352 } else {
353 $bitmask = 0;
354 }
355 if ( $bitmask ) {
356 $this->addWhere( $this->getDB()->makeList( array(
357 'rc_type != ' . RC_LOG,
358 $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
359 ), LIST_OR ) );
360 }
361 }
362
363 $this->token = $params['token'];
364 $this->addOption( 'LIMIT', $params['limit'] + 1 );
365 $this->addOption( 'USE INDEX', $index );
366
367 $count = 0;
368 /* Perform the actual query. */
369 $res = $this->select( __METHOD__ );
370
371 $revids = array();
372 $titles = array();
373
374 $result = $this->getResult();
375
376 /* Iterate through the rows, adding data extracted from them to our query result. */
377 foreach ( $res as $row ) {
378 if ( ++$count > $params['limit'] ) {
379 // We've reached the one extra which shows that there are
380 // additional pages to be had. Stop here...
381 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
382 break;
383 }
384
385 if ( is_null( $resultPageSet ) ) {
386 /* Extract the data from a single row. */
387 $vals = $this->extractRowInfo( $row );
388
389 /* Add that row's data to our final output. */
390 $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
391 if ( !$fit ) {
392 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
393 break;
394 }
395 } elseif ( $params['generaterevisions'] ) {
396 $revid = (int)$row->rc_this_oldid;
397 if ( $revid > 0 ) {
398 $revids[] = $revid;
399 }
400 } else {
401 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
402 }
403 }
404
405 if ( is_null( $resultPageSet ) ) {
406 /* Format the result */
407 $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'rc' );
408 } elseif ( $params['generaterevisions'] ) {
409 $resultPageSet->populateFromRevisionIDs( $revids );
410 } else {
411 $resultPageSet->populateFromTitles( $titles );
412 }
413 }
414
415 /**
416 * Extracts from a single sql row the data needed to describe one recent change.
417 *
418 * @param stdClass $row The row from which to extract the data.
419 * @return array An array mapping strings (descriptors) to their respective string values.
420 * @access public
421 */
422 public function extractRowInfo( $row ) {
423 /* Determine the title of the page that has been changed. */
424 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
425 $user = $this->getUser();
426
427 /* Our output data. */
428 $vals = array();
429
430 $type = intval( $row->rc_type );
431 $vals['type'] = RecentChange::parseFromRCType( $type );
432
433 $anyHidden = false;
434
435 /* Create a new entry in the result for the title. */
436 if ( $this->fld_title || $this->fld_ids ) {
437 if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
438 $vals['actionhidden'] = true;
439 $anyHidden = true;
440 }
441 if ( $type !== RC_LOG ||
442 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
443 ) {
444 if ( $this->fld_title ) {
445 ApiQueryBase::addTitleInfo( $vals, $title );
446 }
447 if ( $this->fld_ids ) {
448 $vals['pageid'] = intval( $row->rc_cur_id );
449 $vals['revid'] = intval( $row->rc_this_oldid );
450 $vals['old_revid'] = intval( $row->rc_last_oldid );
451 }
452 }
453 }
454
455 if ( $this->fld_ids ) {
456 $vals['rcid'] = intval( $row->rc_id );
457 }
458
459 /* Add user data and 'anon' flag, if user is anonymous. */
460 if ( $this->fld_user || $this->fld_userid ) {
461 if ( $row->rc_deleted & Revision::DELETED_USER ) {
462 $vals['userhidden'] = true;
463 $anyHidden = true;
464 }
465 if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
466 if ( $this->fld_user ) {
467 $vals['user'] = $row->rc_user_text;
468 }
469
470 if ( $this->fld_userid ) {
471 $vals['userid'] = (int)$row->rc_user;
472 }
473
474 if ( !$row->rc_user ) {
475 $vals['anon'] = true;
476 }
477 }
478 }
479
480 /* Add flags, such as new, minor, bot. */
481 if ( $this->fld_flags ) {
482 $vals['bot'] = (bool)$row->rc_bot;
483 $vals['new'] = $row->rc_type == RC_NEW;
484 $vals['minor'] = (bool)$row->rc_minor;
485 }
486
487 /* Add sizes of each revision. (Only available on 1.10+) */
488 if ( $this->fld_sizes ) {
489 $vals['oldlen'] = intval( $row->rc_old_len );
490 $vals['newlen'] = intval( $row->rc_new_len );
491 }
492
493 /* Add the timestamp. */
494 if ( $this->fld_timestamp ) {
495 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
496 }
497
498 /* Add edit summary / log summary. */
499 if ( $this->fld_comment || $this->fld_parsedcomment ) {
500 if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
501 $vals['commenthidden'] = true;
502 $anyHidden = true;
503 }
504 if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
505 if ( $this->fld_comment && isset( $row->rc_comment ) ) {
506 $vals['comment'] = $row->rc_comment;
507 }
508
509 if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
510 $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
511 }
512 }
513 }
514
515 if ( $this->fld_redirect ) {
516 $vals['redirect'] = (bool)$row->page_is_redirect;
517 }
518
519 /* Add the patrolled flag */
520 if ( $this->fld_patrolled ) {
521 $vals['patrolled'] = $row->rc_patrolled == 1;
522 $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
523 }
524
525 if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
526 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
527 $vals['actionhidden'] = true;
528 $anyHidden = true;
529 }
530 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
531 $vals['logid'] = intval( $row->rc_logid );
532 $vals['logtype'] = $row->rc_log_type;
533 $vals['logaction'] = $row->rc_log_action;
534 $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
535 }
536 }
537
538 if ( $this->fld_tags ) {
539 if ( $row->ts_tags ) {
540 $tags = explode( ',', $row->ts_tags );
541 ApiResult::setIndexedTagName( $tags, 'tag' );
542 $vals['tags'] = $tags;
543 } else {
544 $vals['tags'] = array();
545 }
546 }
547
548 if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
549 if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
550 $vals['sha1hidden'] = true;
551 $anyHidden = true;
552 }
553 if ( Revision::userCanBitfield( $row->rev_deleted, Revision::DELETED_TEXT, $user ) ) {
554 if ( $row->rev_sha1 !== '' ) {
555 $vals['sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
556 } else {
557 $vals['sha1'] = '';
558 }
559 }
560 }
561
562 if ( !is_null( $this->token ) ) {
563 $tokenFunctions = $this->getTokenFunctions();
564 foreach ( $this->token as $t ) {
565 $val = call_user_func( $tokenFunctions[$t], $row->rc_cur_id,
566 $title, RecentChange::newFromRow( $row ) );
567 if ( $val === false ) {
568 $this->setWarning( "Action '$t' is not allowed for the current user" );
569 } else {
570 $vals[$t . 'token'] = $val;
571 }
572 }
573 }
574
575 if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
576 $vals['suppressed'] = true;
577 }
578
579 return $vals;
580 }
581
582 public function getCacheMode( $params ) {
583 if ( isset( $params['show'] ) ) {
584 foreach ( $params['show'] as $show ) {
585 if ( $show === 'patrolled' || $show === '!patrolled' ) {
586 return 'private';
587 }
588 }
589 }
590 if ( isset( $params['token'] ) ) {
591 return 'private';
592 }
593 if ( $this->userCanSeeRevDel() ) {
594 return 'private';
595 }
596 if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
597 // formatComment() calls wfMessage() among other things
598 return 'anon-public-user-private';
599 }
600
601 return 'public';
602 }
603
604 public function getAllowedParams() {
605 return array(
606 'start' => array(
607 ApiBase::PARAM_TYPE => 'timestamp'
608 ),
609 'end' => array(
610 ApiBase::PARAM_TYPE => 'timestamp'
611 ),
612 'dir' => array(
613 ApiBase::PARAM_DFLT => 'older',
614 ApiBase::PARAM_TYPE => array(
615 'newer',
616 'older'
617 ),
618 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
619 ),
620 'namespace' => array(
621 ApiBase::PARAM_ISMULTI => true,
622 ApiBase::PARAM_TYPE => 'namespace'
623 ),
624 'user' => array(
625 ApiBase::PARAM_TYPE => 'user'
626 ),
627 'excludeuser' => array(
628 ApiBase::PARAM_TYPE => 'user'
629 ),
630 'tag' => null,
631 'prop' => array(
632 ApiBase::PARAM_ISMULTI => true,
633 ApiBase::PARAM_DFLT => 'title|timestamp|ids',
634 ApiBase::PARAM_TYPE => array(
635 'user',
636 'userid',
637 'comment',
638 'parsedcomment',
639 'flags',
640 'timestamp',
641 'title',
642 'ids',
643 'sizes',
644 'redirect',
645 'patrolled',
646 'loginfo',
647 'tags',
648 'sha1',
649 ),
650 ApiBase::PARAM_HELP_MSG_PER_VALUE => array(),
651 ),
652 'token' => array(
653 ApiBase::PARAM_DEPRECATED => true,
654 ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
655 ApiBase::PARAM_ISMULTI => true
656 ),
657 'show' => array(
658 ApiBase::PARAM_ISMULTI => true,
659 ApiBase::PARAM_TYPE => array(
660 'minor',
661 '!minor',
662 'bot',
663 '!bot',
664 'anon',
665 '!anon',
666 'redirect',
667 '!redirect',
668 'patrolled',
669 '!patrolled',
670 'unpatrolled'
671 )
672 ),
673 'limit' => array(
674 ApiBase::PARAM_DFLT => 10,
675 ApiBase::PARAM_TYPE => 'limit',
676 ApiBase::PARAM_MIN => 1,
677 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
678 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
679 ),
680 'type' => array(
681 ApiBase::PARAM_DFLT => 'edit|new|log|categorize',
682 ApiBase::PARAM_ISMULTI => true,
683 ApiBase::PARAM_TYPE => RecentChange::getChangeTypes()
684 ),
685 'toponly' => false,
686 'continue' => array(
687 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
688 ),
689 'generaterevisions' => false,
690 );
691 }
692
693 protected function getExamplesMessages() {
694 return array(
695 'action=query&list=recentchanges'
696 => 'apihelp-query+recentchanges-example-simple',
697 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
698 => 'apihelp-query+recentchanges-example-generator',
699 );
700 }
701
702 public function getHelpUrls() {
703 return 'https://www.mediawiki.org/wiki/API:Recentchanges';
704 }
705 }