MediaWiki  REL1_22
ApiQueryRecentChanges.php
Go to the documentation of this file.
00001 <?php
00033 class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
00034 
00035     public function __construct( $query, $moduleName ) {
00036         parent::__construct( $query, $moduleName, 'rc' );
00037     }
00038 
00039     private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
00040             $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
00041             $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
00042             $fld_tags = false, $fld_sha1 = false, $token = array();
00043 
00044     private $tokenFunctions;
00045 
00052     protected function getTokenFunctions() {
00053         // Don't call the hooks twice
00054         if ( isset( $this->tokenFunctions ) ) {
00055             return $this->tokenFunctions;
00056         }
00057 
00058         // If we're in JSON callback mode, no tokens can be obtained
00059         if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
00060             return array();
00061         }
00062 
00063         $this->tokenFunctions = array(
00064             'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' )
00065         );
00066         wfRunHooks( 'APIQueryRecentChangesTokens', array( &$this->tokenFunctions ) );
00067         return $this->tokenFunctions;
00068     }
00069 
00076     public static function getPatrolToken( $pageid, $title, $rc = null ) {
00077         global $wgUser;
00078 
00079         $validTokenUser = false;
00080 
00081         if ( $rc ) {
00082             if ( ( $wgUser->useRCPatrol() && $rc->getAttribute( 'rc_type' ) == RC_EDIT ) ||
00083                 ( $wgUser->useNPPatrol() && $rc->getAttribute( 'rc_type' ) == RC_NEW ) )
00084             {
00085                 $validTokenUser = true;
00086             }
00087         } else {
00088             if ( $wgUser->useRCPatrol() || $wgUser->useNPPatrol() ) {
00089                 $validTokenUser = true;
00090             }
00091         }
00092 
00093         if ( $validTokenUser ) {
00094             // The patrol token is always the same, let's exploit that
00095             static $cachedPatrolToken = null;
00096             if ( is_null( $cachedPatrolToken ) ) {
00097                 $cachedPatrolToken = $wgUser->getEditToken( 'patrol' );
00098             }
00099             return $cachedPatrolToken;
00100         } else {
00101             return false;
00102         }
00103 
00104     }
00105 
00110     public function initProperties( $prop ) {
00111         $this->fld_comment = isset( $prop['comment'] );
00112         $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
00113         $this->fld_user = isset( $prop['user'] );
00114         $this->fld_userid = isset( $prop['userid'] );
00115         $this->fld_flags = isset( $prop['flags'] );
00116         $this->fld_timestamp = isset( $prop['timestamp'] );
00117         $this->fld_title = isset( $prop['title'] );
00118         $this->fld_ids = isset( $prop['ids'] );
00119         $this->fld_sizes = isset( $prop['sizes'] );
00120         $this->fld_redirect = isset( $prop['redirect'] );
00121         $this->fld_patrolled = isset( $prop['patrolled'] );
00122         $this->fld_loginfo = isset( $prop['loginfo'] );
00123         $this->fld_tags = isset( $prop['tags'] );
00124         $this->fld_sha1 = isset( $prop['sha1'] );
00125     }
00126 
00127     public function execute() {
00128         $this->run();
00129     }
00130 
00131     public function executeGenerator( $resultPageSet ) {
00132         $this->run( $resultPageSet );
00133     }
00134 
00140     public function run( $resultPageSet = null ) {
00141         $user = $this->getUser();
00142         /* Get the parameters of the request. */
00143         $params = $this->extractRequestParams();
00144 
00145         /* Build our basic query. Namely, something along the lines of:
00146          * SELECT * FROM recentchanges WHERE rc_timestamp > $start
00147          *      AND rc_timestamp < $end AND rc_namespace = $namespace
00148          *      AND rc_deleted = 0
00149          */
00150         $this->addTables( 'recentchanges' );
00151         $index = array( 'recentchanges' => 'rc_timestamp' ); // May change
00152         $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
00153 
00154         if ( !is_null( $params['continue'] ) ) {
00155             $cont = explode( '|', $params['continue'] );
00156             if ( count( $cont ) != 2 ) {
00157                 $this->dieUsage( 'Invalid continue param. You should pass the ' .
00158                                 'original value returned by the previous query', '_badcontinue' );
00159             }
00160 
00161             $timestamp = $this->getDB()->addQuotes( wfTimestamp( TS_MW, $cont[0] ) );
00162             $id = intval( $cont[1] );
00163             $op = $params['dir'] === 'older' ? '<' : '>';
00164 
00165             $this->addWhere(
00166                 "rc_timestamp $op $timestamp OR " .
00167                 "(rc_timestamp = $timestamp AND " .
00168                 "rc_id $op= $id)"
00169             );
00170         }
00171 
00172         $order = $params['dir'] === 'older' ? 'DESC' : 'ASC';
00173         $this->addOption( 'ORDER BY', array(
00174             "rc_timestamp $order",
00175             "rc_id $order",
00176         ) );
00177 
00178         $this->addWhereFld( 'rc_namespace', $params['namespace'] );
00179         $this->addWhereFld( 'rc_deleted', 0 );
00180 
00181         if ( !is_null( $params['type'] ) ) {
00182             $this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) );
00183         }
00184 
00185         if ( !is_null( $params['show'] ) ) {
00186             $show = array_flip( $params['show'] );
00187 
00188             /* Check for conflicting parameters. */
00189             if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
00190                     || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
00191                     || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
00192                     || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
00193                     || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
00194             ) {
00195                 $this->dieUsageMsg( 'show' );
00196             }
00197 
00198             // Check permissions
00199             if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
00200                 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
00201                     $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
00202                 }
00203             }
00204 
00205             /* Add additional conditions to query depending upon parameters. */
00206             $this->addWhereIf( 'rc_minor = 0', isset( $show['!minor'] ) );
00207             $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
00208             $this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
00209             $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
00210             $this->addWhereIf( 'rc_user = 0', isset( $show['anon'] ) );
00211             $this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
00212             $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
00213             $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
00214             $this->addWhereIf( 'page_is_redirect = 1', isset( $show['redirect'] ) );
00215 
00216             // Don't throw log entries out the window here
00217             $this->addWhereIf( 'page_is_redirect = 0 OR page_is_redirect IS NULL', isset( $show['!redirect'] ) );
00218         }
00219 
00220         if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
00221             $this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' );
00222         }
00223 
00224         if ( !is_null( $params['user'] ) ) {
00225             $this->addWhereFld( 'rc_user_text', $params['user'] );
00226             $index['recentchanges'] = 'rc_user_text';
00227         }
00228 
00229         if ( !is_null( $params['excludeuser'] ) ) {
00230             // We don't use the rc_user_text index here because
00231             // * it would require us to sort by rc_user_text before rc_timestamp
00232             // * the != condition doesn't throw out too many rows anyway
00233             $this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
00234         }
00235 
00236         /* Add the fields we're concerned with to our query. */
00237         $this->addFields( array(
00238             'rc_timestamp',
00239             'rc_namespace',
00240             'rc_title',
00241             'rc_cur_id',
00242             'rc_type',
00243             'rc_deleted'
00244         ) );
00245 
00246         $showRedirects = false;
00247         /* Determine what properties we need to display. */
00248         if ( !is_null( $params['prop'] ) ) {
00249             $prop = array_flip( $params['prop'] );
00250 
00251             /* Set up internal members based upon params. */
00252             $this->initProperties( $prop );
00253 
00254             if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
00255                 $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
00256             }
00257 
00258             $this->addFields( 'rc_id' );
00259             /* Add fields to our query if they are specified as a needed parameter. */
00260             $this->addFieldsIf( array( 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
00261             $this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
00262             $this->addFieldsIf( 'rc_user', $this->fld_user );
00263             $this->addFieldsIf( 'rc_user_text', $this->fld_user || $this->fld_userid );
00264             $this->addFieldsIf( array( 'rc_minor', 'rc_type', 'rc_bot' ), $this->fld_flags );
00265             $this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
00266             $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
00267             $this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo );
00268             $showRedirects = $this->fld_redirect || isset( $show['redirect'] ) || isset( $show['!redirect'] );
00269         }
00270 
00271         if ( $this->fld_tags ) {
00272             $this->addTables( 'tag_summary' );
00273             $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rc_id=ts_rc_id' ) ) ) );
00274             $this->addFields( 'ts_tags' );
00275         }
00276 
00277         if ( $this->fld_sha1 ) {
00278             $this->addTables( 'revision' );
00279             $this->addJoinConds( array( 'revision' => array( 'LEFT JOIN', array( 'rc_this_oldid=rev_id' ) ) ) );
00280             $this->addFields( array( 'rev_sha1', 'rev_deleted' ) );
00281         }
00282 
00283         if ( $params['toponly'] || $showRedirects ) {
00284             $this->addTables( 'page' );
00285             $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) );
00286             $this->addFields( 'page_is_redirect' );
00287 
00288             if ( $params['toponly'] ) {
00289                 $this->addWhere( 'rc_this_oldid = page_latest' );
00290             }
00291         }
00292 
00293         if ( !is_null( $params['tag'] ) ) {
00294             $this->addTables( 'change_tag' );
00295             $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
00296             $this->addWhereFld( 'ct_tag', $params['tag'] );
00297             $index['change_tag'] = 'change_tag_tag_id';
00298         }
00299 
00300         $this->token = $params['token'];
00301         $this->addOption( 'LIMIT', $params['limit'] + 1 );
00302         $this->addOption( 'USE INDEX', $index );
00303 
00304         $count = 0;
00305         /* Perform the actual query. */
00306         $res = $this->select( __METHOD__ );
00307 
00308         $titles = array();
00309 
00310         $result = $this->getResult();
00311 
00312         /* Iterate through the rows, adding data extracted from them to our query result. */
00313         foreach ( $res as $row ) {
00314             if ( ++ $count > $params['limit'] ) {
00315                 // We've reached the one extra which shows that there are additional pages to be had. Stop here...
00316                 $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
00317                 break;
00318             }
00319 
00320             if ( is_null( $resultPageSet ) ) {
00321                 /* Extract the data from a single row. */
00322                 $vals = $this->extractRowInfo( $row );
00323 
00324                 /* Add that row's data to our final output. */
00325                 if ( !$vals ) {
00326                     continue;
00327                 }
00328                 $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
00329                 if ( !$fit ) {
00330                     $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
00331                     break;
00332                 }
00333             } else {
00334                 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
00335             }
00336         }
00337 
00338         if ( is_null( $resultPageSet ) ) {
00339             /* Format the result */
00340             $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' );
00341         } else {
00342             $resultPageSet->populateFromTitles( $titles );
00343         }
00344     }
00345 
00353     public function extractRowInfo( $row ) {
00354         /* Determine the title of the page that has been changed. */
00355         $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
00356 
00357         /* Our output data. */
00358         $vals = array();
00359 
00360         $type = intval( $row->rc_type );
00361 
00362         /* Determine what kind of change this was. */
00363         switch ( $type ) {
00364             case RC_EDIT:
00365                 $vals['type'] = 'edit';
00366                 break;
00367             case RC_NEW:
00368                 $vals['type'] = 'new';
00369                 break;
00370             case RC_MOVE:
00371                 $vals['type'] = 'move';
00372                 break;
00373             case RC_LOG:
00374                 $vals['type'] = 'log';
00375                 break;
00376             case RC_EXTERNAL:
00377                 $vals['type'] = 'external';
00378                 break;
00379             case RC_MOVE_OVER_REDIRECT:
00380                 $vals['type'] = 'move over redirect';
00381                 break;
00382             default:
00383                 $vals['type'] = $type;
00384         }
00385 
00386         /* Create a new entry in the result for the title. */
00387         if ( $this->fld_title ) {
00388             ApiQueryBase::addTitleInfo( $vals, $title );
00389         }
00390 
00391         /* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */
00392         if ( $this->fld_ids ) {
00393             $vals['rcid'] = intval( $row->rc_id );
00394             $vals['pageid'] = intval( $row->rc_cur_id );
00395             $vals['revid'] = intval( $row->rc_this_oldid );
00396             $vals['old_revid'] = intval( $row->rc_last_oldid );
00397         }
00398 
00399         /* Add user data and 'anon' flag, if use is anonymous. */
00400         if ( $this->fld_user || $this->fld_userid ) {
00401 
00402             if ( $this->fld_user ) {
00403                 $vals['user'] = $row->rc_user_text;
00404             }
00405 
00406             if ( $this->fld_userid ) {
00407                 $vals['userid'] = $row->rc_user;
00408             }
00409 
00410             if ( !$row->rc_user ) {
00411                 $vals['anon'] = '';
00412             }
00413         }
00414 
00415         /* Add flags, such as new, minor, bot. */
00416         if ( $this->fld_flags ) {
00417             if ( $row->rc_bot ) {
00418                 $vals['bot'] = '';
00419             }
00420             if ( $row->rc_type == RC_NEW ) {
00421                 $vals['new'] = '';
00422             }
00423             if ( $row->rc_minor ) {
00424                 $vals['minor'] = '';
00425             }
00426         }
00427 
00428         /* Add sizes of each revision. (Only available on 1.10+) */
00429         if ( $this->fld_sizes ) {
00430             $vals['oldlen'] = intval( $row->rc_old_len );
00431             $vals['newlen'] = intval( $row->rc_new_len );
00432         }
00433 
00434         /* Add the timestamp. */
00435         if ( $this->fld_timestamp ) {
00436             $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
00437         }
00438 
00439         /* Add edit summary / log summary. */
00440         if ( $this->fld_comment && isset( $row->rc_comment ) ) {
00441             $vals['comment'] = $row->rc_comment;
00442         }
00443 
00444         if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
00445             $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
00446         }
00447 
00448         if ( $this->fld_redirect ) {
00449             if ( $row->page_is_redirect ) {
00450                 $vals['redirect'] = '';
00451             }
00452         }
00453 
00454         /* Add the patrolled flag */
00455         if ( $this->fld_patrolled && $row->rc_patrolled == 1 ) {
00456             $vals['patrolled'] = '';
00457         }
00458 
00459         if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
00460             $vals['logid'] = intval( $row->rc_logid );
00461             $vals['logtype'] = $row->rc_log_type;
00462             $vals['logaction'] = $row->rc_log_action;
00463             $logEntry = DatabaseLogEntry::newFromRow( (array)$row );
00464             ApiQueryLogEvents::addLogParams(
00465                 $this->getResult(),
00466                 $vals,
00467                 $logEntry->getParameters(),
00468                 $logEntry->getType(),
00469                 $logEntry->getSubtype(),
00470                 $logEntry->getTimestamp()
00471             );
00472         }
00473 
00474         if ( $this->fld_tags ) {
00475             if ( $row->ts_tags ) {
00476                 $tags = explode( ',', $row->ts_tags );
00477                 $this->getResult()->setIndexedTagName( $tags, 'tag' );
00478                 $vals['tags'] = $tags;
00479             } else {
00480                 $vals['tags'] = array();
00481             }
00482         }
00483 
00484         if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
00485             // The RevDel check should currently never pass due to the
00486             // rc_deleted = 0 condition in the WHERE clause, but in case that
00487             // ever changes we check it here too.
00488             if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
00489                 $vals['sha1hidden'] = '';
00490             } elseif ( $row->rev_sha1 !== '' ) {
00491                 $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 );
00492             } else {
00493                 $vals['sha1'] = '';
00494             }
00495         }
00496 
00497         if ( !is_null( $this->token ) ) {
00498             $tokenFunctions = $this->getTokenFunctions();
00499             foreach ( $this->token as $t ) {
00500                 $val = call_user_func( $tokenFunctions[$t], $row->rc_cur_id,
00501                     $title, RecentChange::newFromRow( $row ) );
00502                 if ( $val === false ) {
00503                     $this->setWarning( "Action '$t' is not allowed for the current user" );
00504                 } else {
00505                     $vals[$t . 'token'] = $val;
00506                 }
00507             }
00508         }
00509 
00510         return $vals;
00511     }
00512 
00513     private function parseRCType( $type ) {
00514         if ( is_array( $type ) ) {
00515             $retval = array();
00516             foreach ( $type as $t ) {
00517                 $retval[] = $this->parseRCType( $t );
00518             }
00519             return $retval;
00520         }
00521         switch ( $type ) {
00522             case 'edit':
00523                 return RC_EDIT;
00524             case 'new':
00525                 return RC_NEW;
00526             case 'log':
00527                 return RC_LOG;
00528             case 'external':
00529                 return RC_EXTERNAL;
00530         }
00531     }
00532 
00533     public function getCacheMode( $params ) {
00534         if ( isset( $params['show'] ) ) {
00535             foreach ( $params['show'] as $show ) {
00536                 if ( $show === 'patrolled' || $show === '!patrolled' ) {
00537                     return 'private';
00538                 }
00539             }
00540         }
00541         if ( isset( $params['token'] ) ) {
00542             return 'private';
00543         }
00544         if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
00545             // formatComment() calls wfMessage() among other things
00546             return 'anon-public-user-private';
00547         }
00548         return 'public';
00549     }
00550 
00551     public function getAllowedParams() {
00552         return array(
00553             'start' => array(
00554                 ApiBase::PARAM_TYPE => 'timestamp'
00555             ),
00556             'end' => array(
00557                 ApiBase::PARAM_TYPE => 'timestamp'
00558             ),
00559             'dir' => array(
00560                 ApiBase::PARAM_DFLT => 'older',
00561                 ApiBase::PARAM_TYPE => array(
00562                     'newer',
00563                     'older'
00564                 )
00565             ),
00566             'namespace' => array(
00567                 ApiBase::PARAM_ISMULTI => true,
00568                 ApiBase::PARAM_TYPE => 'namespace'
00569             ),
00570             'user' => array(
00571                 ApiBase::PARAM_TYPE => 'user'
00572             ),
00573             'excludeuser' => array(
00574                 ApiBase::PARAM_TYPE => 'user'
00575             ),
00576             'tag' => null,
00577             'prop' => array(
00578                 ApiBase::PARAM_ISMULTI => true,
00579                 ApiBase::PARAM_DFLT => 'title|timestamp|ids',
00580                 ApiBase::PARAM_TYPE => array(
00581                     'user',
00582                     'userid',
00583                     'comment',
00584                     'parsedcomment',
00585                     'flags',
00586                     'timestamp',
00587                     'title',
00588                     'ids',
00589                     'sizes',
00590                     'redirect',
00591                     'patrolled',
00592                     'loginfo',
00593                     'tags',
00594                     'sha1',
00595                 )
00596             ),
00597             'token' => array(
00598                 ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
00599                 ApiBase::PARAM_ISMULTI => true
00600             ),
00601             'show' => array(
00602                 ApiBase::PARAM_ISMULTI => true,
00603                 ApiBase::PARAM_TYPE => array(
00604                     'minor',
00605                     '!minor',
00606                     'bot',
00607                     '!bot',
00608                     'anon',
00609                     '!anon',
00610                     'redirect',
00611                     '!redirect',
00612                     'patrolled',
00613                     '!patrolled'
00614                 )
00615             ),
00616             'limit' => array(
00617                 ApiBase::PARAM_DFLT => 10,
00618                 ApiBase::PARAM_TYPE => 'limit',
00619                 ApiBase::PARAM_MIN => 1,
00620                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
00621                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
00622             ),
00623             'type' => array(
00624                 ApiBase::PARAM_ISMULTI => true,
00625                 ApiBase::PARAM_TYPE => array(
00626                     'edit',
00627                     'external',
00628                     'new',
00629                     'log'
00630                 )
00631             ),
00632             'toponly' => false,
00633             'continue' => null,
00634         );
00635     }
00636 
00637     public function getParamDescription() {
00638         $p = $this->getModulePrefix();
00639         return array(
00640             'start' => 'The timestamp to start enumerating from',
00641             'end' => 'The timestamp to end enumerating',
00642             'dir' => $this->getDirectionDescription( $p ),
00643             'namespace' => 'Filter log entries to only this namespace(s)',
00644             'user' => 'Only list changes by this user',
00645             'excludeuser' => 'Don\'t list changes by this user',
00646             'prop' => array(
00647                 'Include additional pieces of information',
00648                 ' user           - Adds the user responsible for the edit and tags if they are an IP',
00649                 ' userid         - Adds the user id responsible for the edit',
00650                 ' comment        - Adds the comment for the edit',
00651                 ' parsedcomment  - Adds the parsed comment for the edit',
00652                 ' flags          - Adds flags for the edit',
00653                 ' timestamp      - Adds timestamp of the edit',
00654                 ' title          - Adds the page title of the edit',
00655                 ' ids            - Adds the page ID, recent changes ID and the new and old revision ID',
00656                 ' sizes          - Adds the new and old page length in bytes',
00657                 ' redirect       - Tags edit if page is a redirect',
00658                 ' patrolled      - Tags edits that have been patrolled',
00659                 ' loginfo        - Adds log information (logid, logtype, etc) to log entries',
00660                 ' tags           - Lists tags for the entry',
00661                 ' sha1           - Adds the content checksum for entries associated with a revision',
00662             ),
00663             'token' => 'Which tokens to obtain for each change',
00664             'show' => array(
00665                 'Show only items that meet this criteria.',
00666                 "For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon"
00667             ),
00668             'type' => 'Which types of changes to show',
00669             'limit' => 'How many total changes to return',
00670             'tag' => 'Only list changes tagged with this tag',
00671             'toponly' => 'Only list changes which are the latest revision',
00672             'continue' => 'When more results are available, use this to continue',
00673         );
00674     }
00675 
00676     public function getResultProperties() {
00677         global $wgLogTypes;
00678         $props = array(
00679             '' => array(
00680                 'type' => array(
00681                     ApiBase::PROP_TYPE => array(
00682                         'edit',
00683                         'new',
00684                         'move',
00685                         'log',
00686                         'move over redirect'
00687                     )
00688                 )
00689             ),
00690             'title' => array(
00691                 'ns' => 'namespace',
00692                 'title' => 'string',
00693                 'new_ns' => array(
00694                     ApiBase::PROP_TYPE => 'namespace',
00695                     ApiBase::PROP_NULLABLE => true
00696                 ),
00697                 'new_title' => array(
00698                     ApiBase::PROP_TYPE => 'string',
00699                     ApiBase::PROP_NULLABLE => true
00700                 )
00701             ),
00702             'ids' => array(
00703                 'rcid' => 'integer',
00704                 'pageid' => 'integer',
00705                 'revid' => 'integer',
00706                 'old_revid' => 'integer'
00707             ),
00708             'user' => array(
00709                 'user' => 'string',
00710                 'anon' => 'boolean'
00711             ),
00712             'userid' => array(
00713                 'userid' => 'integer',
00714                 'anon' => 'boolean'
00715             ),
00716             'flags' => array(
00717                 'bot' => 'boolean',
00718                 'new' => 'boolean',
00719                 'minor' => 'boolean'
00720             ),
00721             'sizes' => array(
00722                 'oldlen' => 'integer',
00723                 'newlen' => 'integer'
00724             ),
00725             'timestamp' => array(
00726                 'timestamp' => 'timestamp'
00727             ),
00728             'comment' => array(
00729                 'comment' => array(
00730                     ApiBase::PROP_TYPE => 'string',
00731                     ApiBase::PROP_NULLABLE => true
00732                 )
00733             ),
00734             'parsedcomment' => array(
00735                 'parsedcomment' => array(
00736                     ApiBase::PROP_TYPE => 'string',
00737                     ApiBase::PROP_NULLABLE => true
00738                 )
00739             ),
00740             'redirect' => array(
00741                 'redirect' => 'boolean'
00742             ),
00743             'patrolled' => array(
00744                 'patrolled' => 'boolean'
00745             ),
00746             'loginfo' => array(
00747                 'logid' => array(
00748                     ApiBase::PROP_TYPE => 'integer',
00749                     ApiBase::PROP_NULLABLE => true
00750                 ),
00751                 'logtype' => array(
00752                     ApiBase::PROP_TYPE => $wgLogTypes,
00753                     ApiBase::PROP_NULLABLE => true
00754                 ),
00755                 'logaction' => array(
00756                     ApiBase::PROP_TYPE => 'string',
00757                     ApiBase::PROP_NULLABLE => true
00758                 )
00759             ),
00760             'sha1' => array(
00761                 'sha1' => array(
00762                     ApiBase::PROP_TYPE => 'string',
00763                     ApiBase::PROP_NULLABLE => true
00764                 ),
00765                 'sha1hidden' => array(
00766                     ApiBase::PROP_TYPE => 'boolean',
00767                     ApiBase::PROP_NULLABLE => true
00768                 ),
00769             ),
00770         );
00771 
00772         self::addTokenProperties( $props, $this->getTokenFunctions() );
00773 
00774         return $props;
00775     }
00776 
00777     public function getDescription() {
00778         return 'Enumerate recent changes';
00779     }
00780 
00781     public function getPossibleErrors() {
00782         return array_merge( parent::getPossibleErrors(), array(
00783             array( 'show' ),
00784             array( 'code' => 'permissiondenied', 'info' => 'You need the patrol right to request the patrolled flag' ),
00785             array( 'code' => 'user-excludeuser', 'info' => 'user and excludeuser cannot be used together' ),
00786         ) );
00787     }
00788 
00789     public function getExamples() {
00790         return array(
00791             'api.php?action=query&list=recentchanges'
00792         );
00793     }
00794 
00795     public function getHelpUrls() {
00796         return 'https://www.mediawiki.org/wiki/API:Recentchanges';
00797     }
00798 }