MediaWiki
REL1_19
|
00001 <?php 00032 class ApiQueryContributions extends ApiQueryBase { 00033 00034 public function __construct( $query, $moduleName ) { 00035 parent::__construct( $query, $moduleName, 'uc' ); 00036 } 00037 00038 private $params, $prefixMode, $userprefix, $multiUserMode, $usernames; 00039 private $fld_ids = false, $fld_title = false, $fld_timestamp = false, 00040 $fld_comment = false, $fld_parsedcomment = false, $fld_flags = false, 00041 $fld_patrolled = false, $fld_tags = false, $fld_size = false; 00042 00043 public function execute() { 00044 // Parse some parameters 00045 $this->params = $this->extractRequestParams(); 00046 00047 $prop = array_flip( $this->params['prop'] ); 00048 $this->fld_ids = isset( $prop['ids'] ); 00049 $this->fld_title = isset( $prop['title'] ); 00050 $this->fld_comment = isset( $prop['comment'] ); 00051 $this->fld_parsedcomment = isset ( $prop['parsedcomment'] ); 00052 $this->fld_size = isset( $prop['size'] ); 00053 $this->fld_flags = isset( $prop['flags'] ); 00054 $this->fld_timestamp = isset( $prop['timestamp'] ); 00055 $this->fld_patrolled = isset( $prop['patrolled'] ); 00056 $this->fld_tags = isset( $prop['tags'] ); 00057 00058 // TODO: if the query is going only against the revision table, should this be done? 00059 $this->selectNamedDB( 'contributions', DB_SLAVE, 'contributions' ); 00060 00061 if ( isset( $this->params['userprefix'] ) ) { 00062 $this->prefixMode = true; 00063 $this->multiUserMode = true; 00064 $this->userprefix = $this->params['userprefix']; 00065 } else { 00066 $this->usernames = array(); 00067 if ( !is_array( $this->params['user'] ) ) { 00068 $this->params['user'] = array( $this->params['user'] ); 00069 } 00070 if ( !count( $this->params['user'] ) ) { 00071 $this->dieUsage( 'User parameter may not be empty.', 'param_user' ); 00072 } 00073 foreach ( $this->params['user'] as $u ) { 00074 $this->prepareUsername( $u ); 00075 } 00076 $this->prefixMode = false; 00077 $this->multiUserMode = ( count( $this->params['user'] ) > 1 ); 00078 } 00079 00080 $this->prepareQuery(); 00081 00082 // Do the actual query. 00083 $res = $this->select( __METHOD__ ); 00084 00085 // Initialise some variables 00086 $count = 0; 00087 $limit = $this->params['limit']; 00088 00089 // Fetch each row 00090 foreach ( $res as $row ) { 00091 if ( ++ $count > $limit ) { 00092 // We've reached the one extra which shows that there are additional pages to be had. Stop here... 00093 if ( $this->multiUserMode ) { 00094 $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) ); 00095 } else { 00096 $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rev_timestamp ) ); 00097 } 00098 break; 00099 } 00100 00101 $vals = $this->extractRowInfo( $row ); 00102 $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals ); 00103 if ( !$fit ) { 00104 if ( $this->multiUserMode ) { 00105 $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) ); 00106 } else { 00107 $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rev_timestamp ) ); 00108 } 00109 break; 00110 } 00111 } 00112 00113 $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' ); 00114 } 00115 00122 private function prepareUsername( $user ) { 00123 if ( !is_null( $user ) && $user !== '' ) { 00124 $name = User::isIP( $user ) 00125 ? $user 00126 : User::getCanonicalName( $user, 'valid' ); 00127 if ( $name === false ) { 00128 $this->dieUsage( "User name {$user} is not valid", 'param_user' ); 00129 } else { 00130 $this->usernames[] = $name; 00131 } 00132 } else { 00133 $this->dieUsage( 'User parameter may not be empty', 'param_user' ); 00134 } 00135 } 00136 00140 private function prepareQuery() { 00141 // We're after the revision table, and the corresponding page 00142 // row for anything we retrieve. We may also need the 00143 // recentchanges row and/or tag summary row. 00144 $user = $this->getUser(); 00145 $tables = array( 'page', 'revision' ); // Order may change 00146 $this->addWhere( 'page_id=rev_page' ); 00147 00148 // Handle continue parameter 00149 if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) { 00150 $continue = explode( '|', $this->params['continue'] ); 00151 if ( count( $continue ) != 2 ) { 00152 $this->dieUsage( 'Invalid continue param. You should pass the original ' . 00153 'value returned by the previous query', '_badcontinue' ); 00154 } 00155 $encUser = $this->getDB()->strencode( $continue[0] ); 00156 $encTS = wfTimestamp( TS_MW, $continue[1] ); 00157 $op = ( $this->params['dir'] == 'older' ? '<' : '>' ); 00158 $this->addWhere( 00159 "rev_user_text $op '$encUser' OR " . 00160 "(rev_user_text = '$encUser' AND " . 00161 "rev_timestamp $op= '$encTS')" 00162 ); 00163 } 00164 00165 if ( !$user->isAllowed( 'hideuser' ) ) { 00166 $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ); 00167 } 00168 // We only want pages by the specified users. 00169 if ( $this->prefixMode ) { 00170 $this->addWhere( 'rev_user_text' . $this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) ); 00171 } else { 00172 $this->addWhereFld( 'rev_user_text', $this->usernames ); 00173 } 00174 // ... and in the specified timeframe. 00175 // Ensure the same sort order for rev_user_text and rev_timestamp 00176 // so our query is indexed 00177 if ( $this->multiUserMode ) { 00178 $this->addWhereRange( 'rev_user_text', $this->params['dir'], null, null ); 00179 } 00180 $this->addTimestampWhereRange( 'rev_timestamp', 00181 $this->params['dir'], $this->params['start'], $this->params['end'] ); 00182 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 00183 00184 $show = $this->params['show']; 00185 if ( !is_null( $show ) ) { 00186 $show = array_flip( $show ); 00187 if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) ) 00188 || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) ) { 00189 $this->dieUsageMsg( 'show' ); 00190 } 00191 00192 $this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) ); 00193 $this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) ); 00194 $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) ); 00195 $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) ); 00196 } 00197 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 00198 $index = array( 'revision' => 'usertext_timestamp' ); 00199 00200 // Mandatory fields: timestamp allows request continuation 00201 // ns+title checks if the user has access rights for this page 00202 // user_text is necessary if multiple users were specified 00203 $this->addFields( array( 00204 'rev_timestamp', 00205 'page_namespace', 00206 'page_title', 00207 'rev_user', 00208 'rev_user_text', 00209 'rev_deleted' 00210 ) ); 00211 00212 if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) || 00213 $this->fld_patrolled ) { 00214 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) { 00215 $this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' ); 00216 } 00217 00218 // Use a redundant join condition on both 00219 // timestamp and ID so we can use the timestamp 00220 // index 00221 $index['recentchanges'] = 'rc_user_text'; 00222 if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) { 00223 // Put the tables in the right order for 00224 // STRAIGHT_JOIN 00225 $tables = array( 'revision', 'recentchanges', 'page' ); 00226 $this->addOption( 'STRAIGHT_JOIN' ); 00227 $this->addWhere( 'rc_user_text=rev_user_text' ); 00228 $this->addWhere( 'rc_timestamp=rev_timestamp' ); 00229 $this->addWhere( 'rc_this_oldid=rev_id' ); 00230 } else { 00231 $tables[] = 'recentchanges'; 00232 $this->addJoinConds( array( 'recentchanges' => array( 00233 'LEFT JOIN', array( 00234 'rc_user_text=rev_user_text', 00235 'rc_timestamp=rev_timestamp', 00236 'rc_this_oldid=rev_id' ) ) ) ); 00237 } 00238 } 00239 00240 $this->addTables( $tables ); 00241 $this->addFieldsIf( 'rev_page', $this->fld_ids ); 00242 $this->addFieldsIf( 'rev_id', $this->fld_ids || $this->fld_flags ); 00243 $this->addFieldsIf( 'page_latest', $this->fld_flags ); 00244 // $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed? 00245 $this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment ); 00246 $this->addFieldsIf( 'rev_len', $this->fld_size ); 00247 $this->addFieldsIf( array( 'rev_minor_edit', 'rev_parent_id' ), $this->fld_flags ); 00248 $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled ); 00249 00250 if ( $this->fld_tags ) { 00251 $this->addTables( 'tag_summary' ); 00252 $this->addJoinConds( array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) ); 00253 $this->addFields( 'ts_tags' ); 00254 } 00255 00256 if ( isset( $this->params['tag'] ) ) { 00257 $this->addTables( 'change_tag' ); 00258 $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) ); 00259 $this->addWhereFld( 'ct_tag', $this->params['tag'] ); 00260 global $wgOldChangeTagsIndex; 00261 $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; 00262 } 00263 00264 if ( $this->params['toponly'] ) { 00265 $this->addWhere( 'rev_id = page_latest' ); 00266 } 00267 00268 $this->addOption( 'USE INDEX', $index ); 00269 } 00270 00277 private function extractRowInfo( $row ) { 00278 $vals = array(); 00279 00280 $vals['userid'] = $row->rev_user; 00281 $vals['user'] = $row->rev_user_text; 00282 if ( $row->rev_deleted & Revision::DELETED_USER ) { 00283 $vals['userhidden'] = ''; 00284 } 00285 if ( $this->fld_ids ) { 00286 $vals['pageid'] = intval( $row->rev_page ); 00287 $vals['revid'] = intval( $row->rev_id ); 00288 // $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed? 00289 } 00290 00291 $title = Title::makeTitle( $row->page_namespace, $row->page_title ); 00292 00293 if ( $this->fld_title ) { 00294 ApiQueryBase::addTitleInfo( $vals, $title ); 00295 } 00296 00297 if ( $this->fld_timestamp ) { 00298 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp ); 00299 } 00300 00301 if ( $this->fld_flags ) { 00302 if ( $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id ) ) { 00303 $vals['new'] = ''; 00304 } 00305 if ( $row->rev_minor_edit ) { 00306 $vals['minor'] = ''; 00307 } 00308 if ( $row->page_latest == $row->rev_id ) { 00309 $vals['top'] = ''; 00310 } 00311 } 00312 00313 if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) { 00314 if ( $row->rev_deleted & Revision::DELETED_COMMENT ) { 00315 $vals['commenthidden'] = ''; 00316 } else { 00317 if ( $this->fld_comment ) { 00318 $vals['comment'] = $row->rev_comment; 00319 } 00320 00321 if ( $this->fld_parsedcomment ) { 00322 $vals['parsedcomment'] = Linker::formatComment( $row->rev_comment, $title ); 00323 } 00324 } 00325 } 00326 00327 if ( $this->fld_patrolled && $row->rc_patrolled ) { 00328 $vals['patrolled'] = ''; 00329 } 00330 00331 if ( $this->fld_size && !is_null( $row->rev_len ) ) { 00332 $vals['size'] = intval( $row->rev_len ); 00333 } 00334 00335 if ( $this->fld_tags ) { 00336 if ( $row->ts_tags ) { 00337 $tags = explode( ',', $row->ts_tags ); 00338 $this->getResult()->setIndexedTagName( $tags, 'tag' ); 00339 $vals['tags'] = $tags; 00340 } else { 00341 $vals['tags'] = array(); 00342 } 00343 } 00344 00345 return $vals; 00346 } 00347 00348 private function continueStr( $row ) { 00349 return $row->rev_user_text . '|' . 00350 wfTimestamp( TS_ISO_8601, $row->rev_timestamp ); 00351 } 00352 00353 public function getCacheMode( $params ) { 00354 // This module provides access to deleted revisions and patrol flags if 00355 // the requester is logged in 00356 return 'anon-public-user-private'; 00357 } 00358 00359 public function getAllowedParams() { 00360 return array( 00361 'limit' => array( 00362 ApiBase::PARAM_DFLT => 10, 00363 ApiBase::PARAM_TYPE => 'limit', 00364 ApiBase::PARAM_MIN => 1, 00365 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, 00366 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 00367 ), 00368 'start' => array( 00369 ApiBase::PARAM_TYPE => 'timestamp' 00370 ), 00371 'end' => array( 00372 ApiBase::PARAM_TYPE => 'timestamp' 00373 ), 00374 'continue' => null, 00375 'user' => array( 00376 ApiBase::PARAM_ISMULTI => true 00377 ), 00378 'userprefix' => null, 00379 'dir' => array( 00380 ApiBase::PARAM_DFLT => 'older', 00381 ApiBase::PARAM_TYPE => array( 00382 'newer', 00383 'older' 00384 ) 00385 ), 00386 'namespace' => array( 00387 ApiBase::PARAM_ISMULTI => true, 00388 ApiBase::PARAM_TYPE => 'namespace' 00389 ), 00390 'prop' => array( 00391 ApiBase::PARAM_ISMULTI => true, 00392 ApiBase::PARAM_DFLT => 'ids|title|timestamp|comment|size|flags', 00393 ApiBase::PARAM_TYPE => array( 00394 'ids', 00395 'title', 00396 'timestamp', 00397 'comment', 00398 'parsedcomment', 00399 'size', 00400 'flags', 00401 'patrolled', 00402 'tags' 00403 ) 00404 ), 00405 'show' => array( 00406 ApiBase::PARAM_ISMULTI => true, 00407 ApiBase::PARAM_TYPE => array( 00408 'minor', 00409 '!minor', 00410 'patrolled', 00411 '!patrolled', 00412 ) 00413 ), 00414 'tag' => null, 00415 'toponly' => false, 00416 ); 00417 } 00418 00419 public function getParamDescription() { 00420 global $wgRCMaxAge; 00421 $p = $this->getModulePrefix(); 00422 return array( 00423 'limit' => 'The maximum number of contributions to return', 00424 'start' => 'The start timestamp to return from', 00425 'end' => 'The end timestamp to return to', 00426 'continue' => 'When more results are available, use this to continue', 00427 'user' => 'The users to retrieve contributions for', 00428 'userprefix' => "Retrieve contibutions for all users whose names begin with this value. Overrides {$p}user", 00429 'dir' => $this->getDirectionDescription( $p ), 00430 'namespace' => 'Only list contributions in these namespaces', 00431 'prop' => array( 00432 'Include additional pieces of information', 00433 ' ids - Adds the page ID and revision ID', 00434 ' title - Adds the title and namespace ID of the page', 00435 ' timestamp - Adds the timestamp of the edit', 00436 ' comment - Adds the comment of the edit', 00437 ' parsedcomment - Adds the parsed comment of the edit', 00438 ' size - Adds the size of the page', 00439 ' flags - Adds flags of the edit', 00440 ' patrolled - Tags patrolled edits', 00441 ' tags - Lists tags for the edit', 00442 ), 00443 'show' => array( "Show only items that meet this criteria, e.g. non minor edits only: {$p}show=!minor", 00444 "NOTE: if {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than \$wgRCMaxAge ($wgRCMaxAge) won't be shown", ), 00445 'tag' => 'Only list revisions tagged with this tag', 00446 'toponly' => 'Only list changes which are the latest revision', 00447 ); 00448 } 00449 00450 public function getDescription() { 00451 return 'Get all edits by a user'; 00452 } 00453 00454 public function getPossibleErrors() { 00455 return array_merge( parent::getPossibleErrors(), array( 00456 array( 'code' => 'param_user', 'info' => 'User parameter may not be empty.' ), 00457 array( 'code' => 'param_user', 'info' => 'User name user is not valid' ), 00458 array( 'show' ), 00459 array( 'code' => 'permissiondenied', 'info' => 'You need the patrol right to request the patrolled flag' ), 00460 ) ); 00461 } 00462 00463 public function getExamples() { 00464 return array( 00465 'api.php?action=query&list=usercontribs&ucuser=YurikBot', 00466 'api.php?action=query&list=usercontribs&ucuserprefix=217.121.114.', 00467 ); 00468 } 00469 00470 public function getHelpUrls() { 00471 return 'https://www.mediawiki.org/wiki/API:Usercontribs'; 00472 } 00473 00474 public function getVersion() { 00475 return __CLASS__ . ': $Id$'; 00476 } 00477 }