[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * 5 * Created on Oct 16, 2006 6 * 7 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 * http://www.gnu.org/copyleft/gpl.html 23 * 24 * @file 25 */ 26 27 /** 28 * This query action adds a list of a specified user's contributions to the output. 29 * 30 * @ingroup API 31 */ 32 class ApiQueryContributions extends ApiQueryBase { 33 34 public function __construct( ApiQuery $query, $moduleName ) { 35 parent::__construct( $query, $moduleName, 'uc' ); 36 } 37 38 private $params, $prefixMode, $userprefix, $multiUserMode, $usernames, $parentLens; 39 private $fld_ids = false, $fld_title = false, $fld_timestamp = false, 40 $fld_comment = false, $fld_parsedcomment = false, $fld_flags = false, 41 $fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false; 42 43 public function execute() { 44 // Parse some parameters 45 $this->params = $this->extractRequestParams(); 46 47 $prop = array_flip( $this->params['prop'] ); 48 $this->fld_ids = isset( $prop['ids'] ); 49 $this->fld_title = isset( $prop['title'] ); 50 $this->fld_comment = isset( $prop['comment'] ); 51 $this->fld_parsedcomment = isset( $prop['parsedcomment'] ); 52 $this->fld_size = isset( $prop['size'] ); 53 $this->fld_sizediff = isset( $prop['sizediff'] ); 54 $this->fld_flags = isset( $prop['flags'] ); 55 $this->fld_timestamp = isset( $prop['timestamp'] ); 56 $this->fld_patrolled = isset( $prop['patrolled'] ); 57 $this->fld_tags = isset( $prop['tags'] ); 58 59 // Most of this code will use the 'contributions' group DB, which can map to slaves 60 // with extra user based indexes or partioning by user. The additional metadata 61 // queries should use a regular slave since the lookup pattern is not all by user. 62 $dbSecondary = $this->getDB(); // any random slave 63 64 // TODO: if the query is going only against the revision table, should this be done? 65 $this->selectNamedDB( 'contributions', DB_SLAVE, 'contributions' ); 66 67 if ( isset( $this->params['userprefix'] ) ) { 68 $this->prefixMode = true; 69 $this->multiUserMode = true; 70 $this->userprefix = $this->params['userprefix']; 71 } else { 72 $this->usernames = array(); 73 if ( !is_array( $this->params['user'] ) ) { 74 $this->params['user'] = array( $this->params['user'] ); 75 } 76 if ( !count( $this->params['user'] ) ) { 77 $this->dieUsage( 'User parameter may not be empty.', 'param_user' ); 78 } 79 foreach ( $this->params['user'] as $u ) { 80 $this->prepareUsername( $u ); 81 } 82 $this->prefixMode = false; 83 $this->multiUserMode = ( count( $this->params['user'] ) > 1 ); 84 } 85 86 $this->prepareQuery(); 87 88 // Do the actual query. 89 $res = $this->select( __METHOD__ ); 90 91 if ( $this->fld_sizediff ) { 92 $revIds = array(); 93 foreach ( $res as $row ) { 94 if ( $row->rev_parent_id ) { 95 $revIds[] = $row->rev_parent_id; 96 } 97 } 98 $this->parentLens = Revision::getParentLengths( $dbSecondary, $revIds ); 99 $res->rewind(); // reset 100 } 101 102 // Initialise some variables 103 $count = 0; 104 $limit = $this->params['limit']; 105 106 // Fetch each row 107 foreach ( $res as $row ) { 108 if ( ++$count > $limit ) { 109 // We've reached the one extra which shows that there are 110 // additional pages to be had. Stop here... 111 $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) ); 112 break; 113 } 114 115 $vals = $this->extractRowInfo( $row ); 116 $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals ); 117 if ( !$fit ) { 118 $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) ); 119 break; 120 } 121 } 122 123 $this->getResult()->setIndexedTagName_internal( 124 array( 'query', $this->getModuleName() ), 125 'item' 126 ); 127 } 128 129 /** 130 * Validate the 'user' parameter and set the value to compare 131 * against `revision`.`rev_user_text` 132 * 133 * @param string $user 134 */ 135 private function prepareUsername( $user ) { 136 if ( !is_null( $user ) && $user !== '' ) { 137 $name = User::isIP( $user ) 138 ? $user 139 : User::getCanonicalName( $user, 'valid' ); 140 if ( $name === false ) { 141 $this->dieUsage( "User name {$user} is not valid", 'param_user' ); 142 } else { 143 $this->usernames[] = $name; 144 } 145 } else { 146 $this->dieUsage( 'User parameter may not be empty', 'param_user' ); 147 } 148 } 149 150 /** 151 * Prepares the query and returns the limit of rows requested 152 */ 153 private function prepareQuery() { 154 // We're after the revision table, and the corresponding page 155 // row for anything we retrieve. We may also need the 156 // recentchanges row and/or tag summary row. 157 $user = $this->getUser(); 158 $tables = array( 'page', 'revision' ); // Order may change 159 $this->addWhere( 'page_id=rev_page' ); 160 161 // Handle continue parameter 162 if ( !is_null( $this->params['continue'] ) ) { 163 $continue = explode( '|', $this->params['continue'] ); 164 $db = $this->getDB(); 165 if ( $this->multiUserMode ) { 166 $this->dieContinueUsageIf( count( $continue ) != 3 ); 167 $encUser = $db->addQuotes( array_shift( $continue ) ); 168 } else { 169 $this->dieContinueUsageIf( count( $continue ) != 2 ); 170 } 171 $encTS = $db->addQuotes( $db->timestamp( $continue[0] ) ); 172 $encId = (int)$continue[1]; 173 $this->dieContinueUsageIf( $encId != $continue[1] ); 174 $op = ( $this->params['dir'] == 'older' ? '<' : '>' ); 175 if ( $this->multiUserMode ) { 176 $this->addWhere( 177 "rev_user_text $op $encUser OR " . 178 "(rev_user_text = $encUser AND " . 179 "(rev_timestamp $op $encTS OR " . 180 "(rev_timestamp = $encTS AND " . 181 "rev_id $op= $encId)))" 182 ); 183 } else { 184 $this->addWhere( 185 "rev_timestamp $op $encTS OR " . 186 "(rev_timestamp = $encTS AND " . 187 "rev_id $op= $encId)" 188 ); 189 } 190 } 191 192 // Don't include any revisions where we're not supposed to be able to 193 // see the username. 194 if ( !$user->isAllowed( 'deletedhistory' ) ) { 195 $bitmask = Revision::DELETED_USER; 196 } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) { 197 $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; 198 } else { 199 $bitmask = 0; 200 } 201 if ( $bitmask ) { 202 $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" ); 203 } 204 205 // We only want pages by the specified users. 206 if ( $this->prefixMode ) { 207 $this->addWhere( 'rev_user_text' . 208 $this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) ); 209 } else { 210 $this->addWhereFld( 'rev_user_text', $this->usernames ); 211 } 212 // ... and in the specified timeframe. 213 // Ensure the same sort order for rev_user_text and rev_timestamp 214 // so our query is indexed 215 if ( $this->multiUserMode ) { 216 $this->addWhereRange( 'rev_user_text', $this->params['dir'], null, null ); 217 } 218 $this->addTimestampWhereRange( 'rev_timestamp', 219 $this->params['dir'], $this->params['start'], $this->params['end'] ); 220 // Include in ORDER BY for uniqueness 221 $this->addWhereRange( 'rev_id', $this->params['dir'], null, null ); 222 223 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 224 225 $show = $this->params['show']; 226 if ( $this->params['toponly'] ) { // deprecated/old param 227 $this->logFeatureUsage( 'list=usercontribs&uctoponly' ); 228 $show[] = 'top'; 229 } 230 if ( !is_null( $show ) ) { 231 $show = array_flip( $show ); 232 233 if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) ) 234 || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) 235 || ( isset( $show['top'] ) && isset( $show['!top'] ) ) 236 || ( isset( $show['new'] ) && isset( $show['!new'] ) ) 237 ) { 238 $this->dieUsageMsg( 'show' ); 239 } 240 241 $this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) ); 242 $this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) ); 243 $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) ); 244 $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) ); 245 $this->addWhereIf( 'rev_id != page_latest', isset( $show['!top'] ) ); 246 $this->addWhereIf( 'rev_id = page_latest', isset( $show['top'] ) ); 247 $this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) ); 248 $this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) ); 249 } 250 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 251 $index = array( 'revision' => 'usertext_timestamp' ); 252 253 // Mandatory fields: timestamp allows request continuation 254 // ns+title checks if the user has access rights for this page 255 // user_text is necessary if multiple users were specified 256 $this->addFields( array( 257 'rev_id', 258 'rev_timestamp', 259 'page_namespace', 260 'page_title', 261 'rev_user', 262 'rev_user_text', 263 'rev_deleted' 264 ) ); 265 266 if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) || 267 $this->fld_patrolled 268 ) { 269 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) { 270 $this->dieUsage( 271 'You need the patrol right to request the patrolled flag', 272 'permissiondenied' 273 ); 274 } 275 276 // Use a redundant join condition on both 277 // timestamp and ID so we can use the timestamp 278 // index 279 $index['recentchanges'] = 'rc_user_text'; 280 if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) { 281 // Put the tables in the right order for 282 // STRAIGHT_JOIN 283 $tables = array( 'revision', 'recentchanges', 'page' ); 284 $this->addOption( 'STRAIGHT_JOIN' ); 285 $this->addWhere( 'rc_user_text=rev_user_text' ); 286 $this->addWhere( 'rc_timestamp=rev_timestamp' ); 287 $this->addWhere( 'rc_this_oldid=rev_id' ); 288 } else { 289 $tables[] = 'recentchanges'; 290 $this->addJoinConds( array( 'recentchanges' => array( 291 'LEFT JOIN', array( 292 'rc_user_text=rev_user_text', 293 'rc_timestamp=rev_timestamp', 294 'rc_this_oldid=rev_id' ) ) ) ); 295 } 296 } 297 298 $this->addTables( $tables ); 299 $this->addFieldsIf( 'rev_page', $this->fld_ids ); 300 $this->addFieldsIf( 'page_latest', $this->fld_flags ); 301 // $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed? 302 $this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment ); 303 $this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff ); 304 $this->addFieldsIf( 'rev_minor_edit', $this->fld_flags ); 305 $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff || $this->fld_ids ); 306 $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled ); 307 308 if ( $this->fld_tags ) { 309 $this->addTables( 'tag_summary' ); 310 $this->addJoinConds( 311 array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) ) 312 ); 313 $this->addFields( 'ts_tags' ); 314 } 315 316 if ( isset( $this->params['tag'] ) ) { 317 $this->addTables( 'change_tag' ); 318 $this->addJoinConds( 319 array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) 320 ); 321 $this->addWhereFld( 'ct_tag', $this->params['tag'] ); 322 } 323 324 $this->addOption( 'USE INDEX', $index ); 325 } 326 327 /** 328 * Extract fields from the database row and append them to a result array 329 * 330 * @param stdClass $row 331 * @return array 332 */ 333 private function extractRowInfo( $row ) { 334 $vals = array(); 335 $anyHidden = false; 336 337 if ( $row->rev_deleted & Revision::DELETED_TEXT ) { 338 $vals['texthidden'] = ''; 339 $anyHidden = true; 340 } 341 342 // Any rows where we can't view the user were filtered out in the query. 343 $vals['userid'] = $row->rev_user; 344 $vals['user'] = $row->rev_user_text; 345 if ( $row->rev_deleted & Revision::DELETED_USER ) { 346 $vals['userhidden'] = ''; 347 $anyHidden = true; 348 } 349 if ( $this->fld_ids ) { 350 $vals['pageid'] = intval( $row->rev_page ); 351 $vals['revid'] = intval( $row->rev_id ); 352 // $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed? 353 354 if ( !is_null( $row->rev_parent_id ) ) { 355 $vals['parentid'] = intval( $row->rev_parent_id ); 356 } 357 } 358 359 $title = Title::makeTitle( $row->page_namespace, $row->page_title ); 360 361 if ( $this->fld_title ) { 362 ApiQueryBase::addTitleInfo( $vals, $title ); 363 } 364 365 if ( $this->fld_timestamp ) { 366 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rev_timestamp ); 367 } 368 369 if ( $this->fld_flags ) { 370 if ( $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id ) ) { 371 $vals['new'] = ''; 372 } 373 if ( $row->rev_minor_edit ) { 374 $vals['minor'] = ''; 375 } 376 if ( $row->page_latest == $row->rev_id ) { 377 $vals['top'] = ''; 378 } 379 } 380 381 if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) { 382 if ( $row->rev_deleted & Revision::DELETED_COMMENT ) { 383 $vals['commenthidden'] = ''; 384 $anyHidden = true; 385 } 386 387 $userCanView = Revision::userCanBitfield( 388 $row->rev_deleted, 389 Revision::DELETED_COMMENT, $this->getUser() 390 ); 391 392 if ( $userCanView ) { 393 if ( $this->fld_comment ) { 394 $vals['comment'] = $row->rev_comment; 395 } 396 397 if ( $this->fld_parsedcomment ) { 398 $vals['parsedcomment'] = Linker::formatComment( $row->rev_comment, $title ); 399 } 400 } 401 } 402 403 if ( $this->fld_patrolled && $row->rc_patrolled ) { 404 $vals['patrolled'] = ''; 405 } 406 407 if ( $this->fld_size && !is_null( $row->rev_len ) ) { 408 $vals['size'] = intval( $row->rev_len ); 409 } 410 411 if ( $this->fld_sizediff 412 && !is_null( $row->rev_len ) 413 && !is_null( $row->rev_parent_id ) 414 ) { 415 $parentLen = isset( $this->parentLens[$row->rev_parent_id] ) 416 ? $this->parentLens[$row->rev_parent_id] 417 : 0; 418 $vals['sizediff'] = intval( $row->rev_len - $parentLen ); 419 } 420 421 if ( $this->fld_tags ) { 422 if ( $row->ts_tags ) { 423 $tags = explode( ',', $row->ts_tags ); 424 $this->getResult()->setIndexedTagName( $tags, 'tag' ); 425 $vals['tags'] = $tags; 426 } else { 427 $vals['tags'] = array(); 428 } 429 } 430 431 if ( $anyHidden && $row->rev_deleted & Revision::DELETED_RESTRICTED ) { 432 $vals['suppressed'] = ''; 433 } 434 435 return $vals; 436 } 437 438 private function continueStr( $row ) { 439 if ( $this->multiUserMode ) { 440 return "$row->rev_user_text|$row->rev_timestamp|$row->rev_id"; 441 } else { 442 return "$row->rev_timestamp|$row->rev_id"; 443 } 444 } 445 446 public function getCacheMode( $params ) { 447 // This module provides access to deleted revisions and patrol flags if 448 // the requester is logged in 449 return 'anon-public-user-private'; 450 } 451 452 public function getAllowedParams() { 453 return array( 454 'limit' => array( 455 ApiBase::PARAM_DFLT => 10, 456 ApiBase::PARAM_TYPE => 'limit', 457 ApiBase::PARAM_MIN => 1, 458 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, 459 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 460 ), 461 'start' => array( 462 ApiBase::PARAM_TYPE => 'timestamp' 463 ), 464 'end' => array( 465 ApiBase::PARAM_TYPE => 'timestamp' 466 ), 467 'continue' => null, 468 'user' => array( 469 ApiBase::PARAM_ISMULTI => true 470 ), 471 'userprefix' => null, 472 'dir' => array( 473 ApiBase::PARAM_DFLT => 'older', 474 ApiBase::PARAM_TYPE => array( 475 'newer', 476 'older' 477 ) 478 ), 479 'namespace' => array( 480 ApiBase::PARAM_ISMULTI => true, 481 ApiBase::PARAM_TYPE => 'namespace' 482 ), 483 'prop' => array( 484 ApiBase::PARAM_ISMULTI => true, 485 ApiBase::PARAM_DFLT => 'ids|title|timestamp|comment|size|flags', 486 ApiBase::PARAM_TYPE => array( 487 'ids', 488 'title', 489 'timestamp', 490 'comment', 491 'parsedcomment', 492 'size', 493 'sizediff', 494 'flags', 495 'patrolled', 496 'tags' 497 ) 498 ), 499 'show' => array( 500 ApiBase::PARAM_ISMULTI => true, 501 ApiBase::PARAM_TYPE => array( 502 'minor', 503 '!minor', 504 'patrolled', 505 '!patrolled', 506 'top', 507 '!top', 508 'new', 509 '!new', 510 ) 511 ), 512 'tag' => null, 513 'toponly' => array( 514 ApiBase::PARAM_DFLT => false, 515 ApiBase::PARAM_DEPRECATED => true, 516 ), 517 ); 518 } 519 520 public function getParamDescription() { 521 $p = $this->getModulePrefix(); 522 $RCMaxAge = $this->getConfig()->get( 'RCMaxAge' ); 523 524 return array( 525 'limit' => 'The maximum number of contributions to return', 526 'start' => 'The start timestamp to return from', 527 'end' => 'The end timestamp to return to', 528 'continue' => 'When more results are available, use this to continue', 529 'user' => 'The users to retrieve contributions for', 530 'userprefix' => array( 531 "Retrieve contributions for all users whose names begin with this value.", 532 "Overrides {$p}user", 533 ), 534 'dir' => $this->getDirectionDescription( $p ), 535 'namespace' => 'Only list contributions in these namespaces', 536 'prop' => array( 537 'Include additional pieces of information', 538 ' ids - Adds the page ID and revision ID', 539 ' title - Adds the title and namespace ID of the page', 540 ' timestamp - Adds the timestamp of the edit', 541 ' comment - Adds the comment of the edit', 542 ' parsedcomment - Adds the parsed comment of the edit', 543 ' size - Adds the new size of the edit', 544 ' sizediff - Adds the size delta of the edit against its parent', 545 ' flags - Adds flags of the edit', 546 ' patrolled - Tags patrolled edits', 547 ' tags - Lists tags for the edit', 548 ), 549 'show' => array( 550 "Show only items that meet thse criteria, e.g. non minor edits only: {$p}show=!minor", 551 "NOTE: If {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than", 552 "\$wgRCMaxAge ($RCMaxAge) won't be shown", 553 ), 554 'tag' => 'Only list revisions tagged with this tag', 555 'toponly' => 'Only list changes which are the latest revision', 556 ); 557 } 558 559 public function getDescription() { 560 return 'Get all edits by a user.'; 561 } 562 563 public function getExamples() { 564 return array( 565 'api.php?action=query&list=usercontribs&ucuser=YurikBot', 566 'api.php?action=query&list=usercontribs&ucuserprefix=217.121.114.', 567 ); 568 } 569 570 public function getHelpUrls() { 571 return 'https://www.mediawiki.org/wiki/API:Usercontribs'; 572 } 573 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |