[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * 5 * Created on Sep 24, 2006 6 * 7 * Copyright © 2006, 2013 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 class contains a list of pages that the client has requested. 29 * Initially, when the client passes in titles=, pageids=, or revisions= 30 * parameter, an instance of the ApiPageSet class will normalize titles, 31 * determine if the pages/revisions exist, and prefetch any additional page 32 * data requested. 33 * 34 * When a generator is used, the result of the generator will become the input 35 * for the second instance of this class, and all subsequent actions will use 36 * the second instance for all their work. 37 * 38 * @ingroup API 39 * @since 1.21 derives from ApiBase instead of ApiQueryBase 40 */ 41 class ApiPageSet extends ApiBase { 42 /** 43 * Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter 44 * @since 1.21 45 */ 46 const DISABLE_GENERATORS = 1; 47 48 private $mDbSource; 49 private $mParams; 50 private $mResolveRedirects; 51 private $mConvertTitles; 52 private $mAllowGenerator; 53 54 private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing 55 private $mTitles = array(); 56 private $mGoodTitles = array(); 57 private $mMissingTitles = array(); 58 private $mInvalidTitles = array(); 59 private $mMissingPageIDs = array(); 60 private $mRedirectTitles = array(); 61 private $mSpecialTitles = array(); 62 private $mNormalizedTitles = array(); 63 private $mInterwikiTitles = array(); 64 /** @var Title[] */ 65 private $mPendingRedirectIDs = array(); 66 private $mConvertedTitles = array(); 67 private $mGoodRevIDs = array(); 68 private $mMissingRevIDs = array(); 69 private $mFakePageId = -1; 70 private $mCacheMode = 'public'; 71 private $mRequestedPageFields = array(); 72 /** @var int */ 73 private $mDefaultNamespace = NS_MAIN; 74 75 /** 76 * Add all items from $values into the result 77 * @param array $result Output 78 * @param array $values Values to add 79 * @param string $flag The name of the boolean flag to mark this element 80 * @param string $name If given, name of the value 81 */ 82 private static function addValues( array &$result, $values, $flag = null, $name = null ) { 83 foreach ( $values as $val ) { 84 if ( $val instanceof Title ) { 85 $v = array(); 86 ApiQueryBase::addTitleInfo( $v, $val ); 87 } elseif ( $name !== null ) { 88 $v = array( $name => $val ); 89 } else { 90 $v = $val; 91 } 92 if ( $flag !== null ) { 93 $v[$flag] = ''; 94 } 95 $result[] = $v; 96 } 97 } 98 99 /** 100 * @param ApiBase $dbSource Module implementing getDB(). 101 * Allows PageSet to reuse existing db connection from the shared state like ApiQuery. 102 * @param int $flags Zero or more flags like DISABLE_GENERATORS 103 * @param int $defaultNamespace The namespace to use if none is specified by a prefix. 104 * @since 1.21 accepts $flags instead of two boolean values 105 */ 106 public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) { 107 parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() ); 108 $this->mDbSource = $dbSource; 109 $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0; 110 $this->mDefaultNamespace = $defaultNamespace; 111 112 $this->profileIn(); 113 $this->mParams = $this->extractRequestParams(); 114 $this->mResolveRedirects = $this->mParams['redirects']; 115 $this->mConvertTitles = $this->mParams['converttitles']; 116 $this->profileOut(); 117 } 118 119 /** 120 * In case execute() is not called, call this method to mark all relevant parameters as used 121 * This prevents unused parameters from being reported as warnings 122 */ 123 public function executeDryRun() { 124 $this->executeInternal( true ); 125 } 126 127 /** 128 * Populate the PageSet from the request parameters. 129 */ 130 public function execute() { 131 $this->executeInternal( false ); 132 } 133 134 /** 135 * Populate the PageSet from the request parameters. 136 * @param bool $isDryRun If true, instantiates generator, but only to mark 137 * relevant parameters as used 138 */ 139 private function executeInternal( $isDryRun ) { 140 $this->profileIn(); 141 142 $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null; 143 if ( isset( $generatorName ) ) { 144 $dbSource = $this->mDbSource; 145 $isQuery = $dbSource instanceof ApiQuery; 146 if ( !$isQuery ) { 147 // If the parent container of this pageset is not ApiQuery, we must create it to run generator 148 $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' ); 149 // Enable profiling for query module because it will be used for db sql profiling 150 $dbSource->profileIn(); 151 } 152 $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true ); 153 if ( $generator === null ) { 154 $this->dieUsage( 'Unknown generator=' . $generatorName, 'badgenerator' ); 155 } 156 if ( !$generator instanceof ApiQueryGeneratorBase ) { 157 $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' ); 158 } 159 // Create a temporary pageset to store generator's output, 160 // add any additional fields generator may need, and execute pageset to populate titles/pageids 161 $tmpPageSet = new ApiPageSet( $dbSource, ApiPageSet::DISABLE_GENERATORS ); 162 $generator->setGeneratorMode( $tmpPageSet ); 163 $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() ); 164 165 if ( !$isDryRun ) { 166 $generator->requestExtraData( $tmpPageSet ); 167 } 168 $tmpPageSet->executeInternal( $isDryRun ); 169 170 // populate this pageset with the generator output 171 $this->profileOut(); 172 $generator->profileIn(); 173 174 if ( !$isDryRun ) { 175 $generator->executeGenerator( $this ); 176 wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) ); 177 } else { 178 // Prevent warnings from being reported on these parameters 179 $main = $this->getMain(); 180 foreach ( $generator->extractRequestParams() as $paramName => $param ) { 181 $main->getVal( $generator->encodeParamName( $paramName ) ); 182 } 183 } 184 $generator->profileOut(); 185 $this->profileIn(); 186 187 if ( !$isDryRun ) { 188 $this->resolvePendingRedirects(); 189 } 190 191 if ( !$isQuery ) { 192 // If this pageset is not part of the query, we called profileIn() above 193 $dbSource->profileOut(); 194 } 195 } else { 196 // Only one of the titles/pageids/revids is allowed at the same time 197 $dataSource = null; 198 if ( isset( $this->mParams['titles'] ) ) { 199 $dataSource = 'titles'; 200 } 201 if ( isset( $this->mParams['pageids'] ) ) { 202 if ( isset( $dataSource ) ) { 203 $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' ); 204 } 205 $dataSource = 'pageids'; 206 } 207 if ( isset( $this->mParams['revids'] ) ) { 208 if ( isset( $dataSource ) ) { 209 $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' ); 210 } 211 $dataSource = 'revids'; 212 } 213 214 if ( !$isDryRun ) { 215 // Populate page information with the original user input 216 switch ( $dataSource ) { 217 case 'titles': 218 $this->initFromTitles( $this->mParams['titles'] ); 219 break; 220 case 'pageids': 221 $this->initFromPageIds( $this->mParams['pageids'] ); 222 break; 223 case 'revids': 224 if ( $this->mResolveRedirects ) { 225 $this->setWarning( 'Redirect resolution cannot be used ' . 226 'together with the revids= parameter. Any redirects ' . 227 'the revids= point to have not been resolved.' ); 228 } 229 $this->mResolveRedirects = false; 230 $this->initFromRevIDs( $this->mParams['revids'] ); 231 break; 232 default: 233 // Do nothing - some queries do not need any of the data sources. 234 break; 235 } 236 } 237 } 238 $this->profileOut(); 239 } 240 241 /** 242 * Check whether this PageSet is resolving redirects 243 * @return bool 244 */ 245 public function isResolvingRedirects() { 246 return $this->mResolveRedirects; 247 } 248 249 /** 250 * Return the parameter name that is the source of data for this PageSet 251 * 252 * If multiple source parameters are specified (e.g. titles and pageids), 253 * one will be named arbitrarily. 254 * 255 * @return string|null 256 */ 257 public function getDataSource() { 258 if ( $this->mAllowGenerator && isset( $this->mParams['generator'] ) ) { 259 return 'generator'; 260 } 261 if ( isset( $this->mParams['titles'] ) ) { 262 return 'titles'; 263 } 264 if ( isset( $this->mParams['pageids'] ) ) { 265 return 'pageids'; 266 } 267 if ( isset( $this->mParams['revids'] ) ) { 268 return 'revids'; 269 } 270 271 return null; 272 } 273 274 /** 275 * Request an additional field from the page table. 276 * Must be called before execute() 277 * @param string $fieldName Field name 278 */ 279 public function requestField( $fieldName ) { 280 $this->mRequestedPageFields[$fieldName] = null; 281 } 282 283 /** 284 * Get the value of a custom field previously requested through 285 * requestField() 286 * @param string $fieldName Field name 287 * @return mixed Field value 288 */ 289 public function getCustomField( $fieldName ) { 290 return $this->mRequestedPageFields[$fieldName]; 291 } 292 293 /** 294 * Get the fields that have to be queried from the page table: 295 * the ones requested through requestField() and a few basic ones 296 * we always need 297 * @return array Array of field names 298 */ 299 public function getPageTableFields() { 300 // Ensure we get minimum required fields 301 // DON'T change this order 302 $pageFlds = array( 303 'page_namespace' => null, 304 'page_title' => null, 305 'page_id' => null, 306 ); 307 308 if ( $this->mResolveRedirects ) { 309 $pageFlds['page_is_redirect'] = null; 310 } 311 312 // only store non-default fields 313 $this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds ); 314 315 $pageFlds = array_merge( $pageFlds, $this->mRequestedPageFields ); 316 317 return array_keys( $pageFlds ); 318 } 319 320 /** 321 * Returns an array [ns][dbkey] => page_id for all requested titles. 322 * page_id is a unique negative number in case title was not found. 323 * Invalid titles will also have negative page IDs and will be in namespace 0 324 * @return array 325 */ 326 public function getAllTitlesByNamespace() { 327 return $this->mAllPages; 328 } 329 330 /** 331 * All Title objects provided. 332 * @return Title[] 333 */ 334 public function getTitles() { 335 return $this->mTitles; 336 } 337 338 /** 339 * Returns the number of unique pages (not revisions) in the set. 340 * @return int 341 */ 342 public function getTitleCount() { 343 return count( $this->mTitles ); 344 } 345 346 /** 347 * Title objects that were found in the database. 348 * @return Title[] Array page_id (int) => Title (obj) 349 */ 350 public function getGoodTitles() { 351 return $this->mGoodTitles; 352 } 353 354 /** 355 * Returns the number of found unique pages (not revisions) in the set. 356 * @return int 357 */ 358 public function getGoodTitleCount() { 359 return count( $this->mGoodTitles ); 360 } 361 362 /** 363 * Title objects that were NOT found in the database. 364 * The array's index will be negative for each item 365 * @return Title[] 366 */ 367 public function getMissingTitles() { 368 return $this->mMissingTitles; 369 } 370 371 /** 372 * Titles that were deemed invalid by Title::newFromText() 373 * The array's index will be unique and negative for each item 374 * @return string[] Array of strings (not Title objects) 375 */ 376 public function getInvalidTitles() { 377 return $this->mInvalidTitles; 378 } 379 380 /** 381 * Page IDs that were not found in the database 382 * @return array Array of page IDs 383 */ 384 public function getMissingPageIDs() { 385 return $this->mMissingPageIDs; 386 } 387 388 /** 389 * Get a list of redirect resolutions - maps a title to its redirect 390 * target, as an array of output-ready arrays 391 * @return Title[] 392 */ 393 public function getRedirectTitles() { 394 return $this->mRedirectTitles; 395 } 396 397 /** 398 * Get a list of redirect resolutions - maps a title to its redirect 399 * target. 400 * @param ApiResult $result 401 * @return array Array of prefixed_title (string) => Title object 402 * @since 1.21 403 */ 404 public function getRedirectTitlesAsResult( $result = null ) { 405 $values = array(); 406 foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) { 407 $r = array( 408 'from' => strval( $titleStrFrom ), 409 'to' => $titleTo->getPrefixedText(), 410 ); 411 if ( $titleTo->hasFragment() ) { 412 $r['tofragment'] = $titleTo->getFragment(); 413 } 414 $values[] = $r; 415 } 416 if ( !empty( $values ) && $result ) { 417 $result->setIndexedTagName( $values, 'r' ); 418 } 419 420 return $values; 421 } 422 423 /** 424 * Get a list of title normalizations - maps a title to its normalized 425 * version. 426 * @return array Array of raw_prefixed_title (string) => prefixed_title (string) 427 */ 428 public function getNormalizedTitles() { 429 return $this->mNormalizedTitles; 430 } 431 432 /** 433 * Get a list of title normalizations - maps a title to its normalized 434 * version in the form of result array. 435 * @param ApiResult $result 436 * @return array Array of raw_prefixed_title (string) => prefixed_title (string) 437 * @since 1.21 438 */ 439 public function getNormalizedTitlesAsResult( $result = null ) { 440 $values = array(); 441 foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) { 442 $values[] = array( 443 'from' => $rawTitleStr, 444 'to' => $titleStr 445 ); 446 } 447 if ( !empty( $values ) && $result ) { 448 $result->setIndexedTagName( $values, 'n' ); 449 } 450 451 return $values; 452 } 453 454 /** 455 * Get a list of title conversions - maps a title to its converted 456 * version. 457 * @return array Array of raw_prefixed_title (string) => prefixed_title (string) 458 */ 459 public function getConvertedTitles() { 460 return $this->mConvertedTitles; 461 } 462 463 /** 464 * Get a list of title conversions - maps a title to its converted 465 * version as a result array. 466 * @param ApiResult $result 467 * @return array Array of (from, to) strings 468 * @since 1.21 469 */ 470 public function getConvertedTitlesAsResult( $result = null ) { 471 $values = array(); 472 foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) { 473 $values[] = array( 474 'from' => $rawTitleStr, 475 'to' => $titleStr 476 ); 477 } 478 if ( !empty( $values ) && $result ) { 479 $result->setIndexedTagName( $values, 'c' ); 480 } 481 482 return $values; 483 } 484 485 /** 486 * Get a list of interwiki titles - maps a title to its interwiki 487 * prefix. 488 * @return array Array of raw_prefixed_title (string) => interwiki_prefix (string) 489 */ 490 public function getInterwikiTitles() { 491 return $this->mInterwikiTitles; 492 } 493 494 /** 495 * Get a list of interwiki titles - maps a title to its interwiki 496 * prefix as result. 497 * @param ApiResult $result 498 * @param bool $iwUrl 499 * @return array Array of raw_prefixed_title (string) => interwiki_prefix (string) 500 * @since 1.21 501 */ 502 public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) { 503 $values = array(); 504 foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) { 505 $item = array( 506 'title' => $rawTitleStr, 507 'iw' => $interwikiStr, 508 ); 509 if ( $iwUrl ) { 510 $title = Title::newFromText( $rawTitleStr ); 511 $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT ); 512 } 513 $values[] = $item; 514 } 515 if ( !empty( $values ) && $result ) { 516 $result->setIndexedTagName( $values, 'i' ); 517 } 518 519 return $values; 520 } 521 522 /** 523 * Get an array of invalid/special/missing titles. 524 * 525 * @param array $invalidChecks List of types of invalid titles to include. 526 * Recognized values are: 527 * - invalidTitles: Titles from $this->getInvalidTitles() 528 * - special: Titles from $this->getSpecialTitles() 529 * - missingIds: ids from $this->getMissingPageIDs() 530 * - missingRevIds: ids from $this->getMissingRevisionIDs() 531 * - missingTitles: Titles from $this->getMissingTitles() 532 * - interwikiTitles: Titles from $this->getInterwikiTitlesAsResult() 533 * @return array Array suitable for inclusion in the response 534 * @since 1.23 535 */ 536 public function getInvalidTitlesAndRevisions( $invalidChecks = array( 'invalidTitles', 537 'special', 'missingIds', 'missingRevIds', 'missingTitles', 'interwikiTitles' ) 538 ) { 539 $result = array(); 540 if ( in_array( "invalidTitles", $invalidChecks ) ) { 541 self::addValues( $result, $this->getInvalidTitles(), 'invalid', 'title' ); 542 } 543 if ( in_array( "special", $invalidChecks ) ) { 544 self::addValues( $result, $this->getSpecialTitles(), 'special', 'title' ); 545 } 546 if ( in_array( "missingIds", $invalidChecks ) ) { 547 self::addValues( $result, $this->getMissingPageIDs(), 'missing', 'pageid' ); 548 } 549 if ( in_array( "missingRevIds", $invalidChecks ) ) { 550 self::addValues( $result, $this->getMissingRevisionIDs(), 'missing', 'revid' ); 551 } 552 if ( in_array( "missingTitles", $invalidChecks ) ) { 553 self::addValues( $result, $this->getMissingTitles(), 'missing' ); 554 } 555 if ( in_array( "interwikiTitles", $invalidChecks ) ) { 556 self::addValues( $result, $this->getInterwikiTitlesAsResult() ); 557 } 558 559 return $result; 560 } 561 562 /** 563 * Get the list of revision IDs (requested with the revids= parameter) 564 * @return array Array of revID (int) => pageID (int) 565 */ 566 public function getRevisionIDs() { 567 return $this->mGoodRevIDs; 568 } 569 570 /** 571 * Revision IDs that were not found in the database 572 * @return array Array of revision IDs 573 */ 574 public function getMissingRevisionIDs() { 575 return $this->mMissingRevIDs; 576 } 577 578 /** 579 * Revision IDs that were not found in the database as result array. 580 * @param ApiResult $result 581 * @return array Array of revision IDs 582 * @since 1.21 583 */ 584 public function getMissingRevisionIDsAsResult( $result = null ) { 585 $values = array(); 586 foreach ( $this->getMissingRevisionIDs() as $revid ) { 587 $values[$revid] = array( 588 'revid' => $revid 589 ); 590 } 591 if ( !empty( $values ) && $result ) { 592 $result->setIndexedTagName( $values, 'rev' ); 593 } 594 595 return $values; 596 } 597 598 /** 599 * Get the list of titles with negative namespace 600 * @return Title[] 601 */ 602 public function getSpecialTitles() { 603 return $this->mSpecialTitles; 604 } 605 606 /** 607 * Returns the number of revisions (requested with revids= parameter). 608 * @return int Number of revisions. 609 */ 610 public function getRevisionCount() { 611 return count( $this->getRevisionIDs() ); 612 } 613 614 /** 615 * Populate this PageSet from a list of Titles 616 * @param array $titles Array of Title objects 617 */ 618 public function populateFromTitles( $titles ) { 619 $this->profileIn(); 620 $this->initFromTitles( $titles ); 621 $this->profileOut(); 622 } 623 624 /** 625 * Populate this PageSet from a list of page IDs 626 * @param array $pageIDs Array of page IDs 627 */ 628 public function populateFromPageIDs( $pageIDs ) { 629 $this->profileIn(); 630 $this->initFromPageIds( $pageIDs ); 631 $this->profileOut(); 632 } 633 634 /** 635 * Populate this PageSet from a rowset returned from the database 636 * @param DatabaseBase $db 637 * @param ResultWrapper $queryResult Query result object 638 */ 639 public function populateFromQueryResult( $db, $queryResult ) { 640 $this->profileIn(); 641 $this->initFromQueryResult( $queryResult ); 642 $this->profileOut(); 643 } 644 645 /** 646 * Populate this PageSet from a list of revision IDs 647 * @param array $revIDs Array of revision IDs 648 */ 649 public function populateFromRevisionIDs( $revIDs ) { 650 $this->profileIn(); 651 $this->initFromRevIDs( $revIDs ); 652 $this->profileOut(); 653 } 654 655 /** 656 * Extract all requested fields from the row received from the database 657 * @param stdClass $row Result row 658 */ 659 public function processDbRow( $row ) { 660 // Store Title object in various data structures 661 $title = Title::newFromRow( $row ); 662 663 $pageId = intval( $row->page_id ); 664 $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId; 665 $this->mTitles[] = $title; 666 667 if ( $this->mResolveRedirects && $row->page_is_redirect == '1' ) { 668 $this->mPendingRedirectIDs[$pageId] = $title; 669 } else { 670 $this->mGoodTitles[$pageId] = $title; 671 } 672 673 foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) { 674 $fieldValues[$pageId] = $row->$fieldName; 675 } 676 } 677 678 /** 679 * Do not use, does nothing, will be removed 680 * @deprecated since 1.21 681 */ 682 public function finishPageSetGeneration() { 683 wfDeprecated( __METHOD__, '1.21' ); 684 } 685 686 /** 687 * This method populates internal variables with page information 688 * based on the given array of title strings. 689 * 690 * Steps: 691 * #1 For each title, get data from `page` table 692 * #2 If page was not found in the DB, store it as missing 693 * 694 * Additionally, when resolving redirects: 695 * #3 If no more redirects left, stop. 696 * #4 For each redirect, get its target from the `redirect` table. 697 * #5 Substitute the original LinkBatch object with the new list 698 * #6 Repeat from step #1 699 * 700 * @param array $titles Array of Title objects or strings 701 */ 702 private function initFromTitles( $titles ) { 703 // Get validated and normalized title objects 704 $linkBatch = $this->processTitlesArray( $titles ); 705 if ( $linkBatch->isEmpty() ) { 706 return; 707 } 708 709 $db = $this->getDB(); 710 $set = $linkBatch->constructSet( 'page', $db ); 711 712 // Get pageIDs data from the `page` table 713 $this->profileDBIn(); 714 $res = $db->select( 'page', $this->getPageTableFields(), $set, 715 __METHOD__ ); 716 $this->profileDBOut(); 717 718 // Hack: get the ns:titles stored in array(ns => array(titles)) format 719 $this->initFromQueryResult( $res, $linkBatch->data, true ); // process Titles 720 721 // Resolve any found redirects 722 $this->resolvePendingRedirects(); 723 } 724 725 /** 726 * Does the same as initFromTitles(), but is based on page IDs instead 727 * @param array $pageids Array of page IDs 728 */ 729 private function initFromPageIds( $pageids ) { 730 if ( !$pageids ) { 731 return; 732 } 733 734 $pageids = array_map( 'intval', $pageids ); // paranoia 735 $remaining = array_flip( $pageids ); 736 737 $pageids = self::getPositiveIntegers( $pageids ); 738 739 $res = null; 740 if ( !empty( $pageids ) ) { 741 $set = array( 742 'page_id' => $pageids 743 ); 744 $db = $this->getDB(); 745 746 // Get pageIDs data from the `page` table 747 $this->profileDBIn(); 748 $res = $db->select( 'page', $this->getPageTableFields(), $set, 749 __METHOD__ ); 750 $this->profileDBOut(); 751 } 752 753 $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs 754 755 // Resolve any found redirects 756 $this->resolvePendingRedirects(); 757 } 758 759 /** 760 * Iterate through the result of the query on 'page' table, 761 * and for each row create and store title object and save any extra fields requested. 762 * @param ResultWrapper $res DB Query result 763 * @param array $remaining Array of either pageID or ns/title elements (optional). 764 * If given, any missing items will go to $mMissingPageIDs and $mMissingTitles 765 * @param bool $processTitles Must be provided together with $remaining. 766 * If true, treat $remaining as an array of [ns][title] 767 * If false, treat it as an array of [pageIDs] 768 */ 769 private function initFromQueryResult( $res, &$remaining = null, $processTitles = null ) { 770 if ( !is_null( $remaining ) && is_null( $processTitles ) ) { 771 ApiBase::dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' ); 772 } 773 774 $usernames = array(); 775 if ( $res ) { 776 foreach ( $res as $row ) { 777 $pageId = intval( $row->page_id ); 778 779 // Remove found page from the list of remaining items 780 if ( isset( $remaining ) ) { 781 if ( $processTitles ) { 782 unset( $remaining[$row->page_namespace][$row->page_title] ); 783 } else { 784 unset( $remaining[$pageId] ); 785 } 786 } 787 788 // Store any extra fields requested by modules 789 $this->processDbRow( $row ); 790 791 // Need gender information 792 if ( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) { 793 $usernames[] = $row->page_title; 794 } 795 } 796 } 797 798 if ( isset( $remaining ) ) { 799 // Any items left in the $remaining list are added as missing 800 if ( $processTitles ) { 801 // The remaining titles in $remaining are non-existent pages 802 foreach ( $remaining as $ns => $dbkeys ) { 803 foreach ( array_keys( $dbkeys ) as $dbkey ) { 804 $title = Title::makeTitle( $ns, $dbkey ); 805 $this->mAllPages[$ns][$dbkey] = $this->mFakePageId; 806 $this->mMissingTitles[$this->mFakePageId] = $title; 807 $this->mFakePageId--; 808 $this->mTitles[] = $title; 809 810 // need gender information 811 if ( MWNamespace::hasGenderDistinction( $ns ) ) { 812 $usernames[] = $dbkey; 813 } 814 } 815 } 816 } else { 817 // The remaining pageids do not exist 818 if ( !$this->mMissingPageIDs ) { 819 $this->mMissingPageIDs = array_keys( $remaining ); 820 } else { 821 $this->mMissingPageIDs = array_merge( $this->mMissingPageIDs, array_keys( $remaining ) ); 822 } 823 } 824 } 825 826 // Get gender information 827 $genderCache = GenderCache::singleton(); 828 $genderCache->doQuery( $usernames, __METHOD__ ); 829 } 830 831 /** 832 * Does the same as initFromTitles(), but is based on revision IDs 833 * instead 834 * @param array $revids Array of revision IDs 835 */ 836 private function initFromRevIDs( $revids ) { 837 if ( !$revids ) { 838 return; 839 } 840 841 $revids = array_map( 'intval', $revids ); // paranoia 842 $db = $this->getDB(); 843 $pageids = array(); 844 $remaining = array_flip( $revids ); 845 846 $revids = self::getPositiveIntegers( $revids ); 847 848 if ( !empty( $revids ) ) { 849 $tables = array( 'revision', 'page' ); 850 $fields = array( 'rev_id', 'rev_page' ); 851 $where = array( 'rev_id' => $revids, 'rev_page = page_id' ); 852 853 // Get pageIDs data from the `page` table 854 $this->profileDBIn(); 855 $res = $db->select( $tables, $fields, $where, __METHOD__ ); 856 foreach ( $res as $row ) { 857 $revid = intval( $row->rev_id ); 858 $pageid = intval( $row->rev_page ); 859 $this->mGoodRevIDs[$revid] = $pageid; 860 $pageids[$pageid] = ''; 861 unset( $remaining[$revid] ); 862 } 863 $this->profileDBOut(); 864 } 865 866 $this->mMissingRevIDs = array_keys( $remaining ); 867 868 // Populate all the page information 869 $this->initFromPageIds( array_keys( $pageids ) ); 870 } 871 872 /** 873 * Resolve any redirects in the result if redirect resolution was 874 * requested. This function is called repeatedly until all redirects 875 * have been resolved. 876 */ 877 private function resolvePendingRedirects() { 878 if ( $this->mResolveRedirects ) { 879 $db = $this->getDB(); 880 $pageFlds = $this->getPageTableFields(); 881 882 // Repeat until all redirects have been resolved 883 // The infinite loop is prevented by keeping all known pages in $this->mAllPages 884 while ( $this->mPendingRedirectIDs ) { 885 // Resolve redirects by querying the pagelinks table, and repeat the process 886 // Create a new linkBatch object for the next pass 887 $linkBatch = $this->getRedirectTargets(); 888 889 if ( $linkBatch->isEmpty() ) { 890 break; 891 } 892 893 $set = $linkBatch->constructSet( 'page', $db ); 894 if ( $set === false ) { 895 break; 896 } 897 898 // Get pageIDs data from the `page` table 899 $this->profileDBIn(); 900 $res = $db->select( 'page', $pageFlds, $set, __METHOD__ ); 901 $this->profileDBOut(); 902 903 // Hack: get the ns:titles stored in array(ns => array(titles)) format 904 $this->initFromQueryResult( $res, $linkBatch->data, true ); 905 } 906 } 907 } 908 909 /** 910 * Get the targets of the pending redirects from the database 911 * 912 * Also creates entries in the redirect table for redirects that don't 913 * have one. 914 * @return LinkBatch 915 */ 916 private function getRedirectTargets() { 917 $lb = new LinkBatch(); 918 $db = $this->getDB(); 919 920 $this->profileDBIn(); 921 $res = $db->select( 922 'redirect', 923 array( 924 'rd_from', 925 'rd_namespace', 926 'rd_fragment', 927 'rd_interwiki', 928 'rd_title' 929 ), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ), 930 __METHOD__ 931 ); 932 $this->profileDBOut(); 933 foreach ( $res as $row ) { 934 $rdfrom = intval( $row->rd_from ); 935 $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText(); 936 $to = Title::makeTitle( 937 $row->rd_namespace, 938 $row->rd_title, 939 $row->rd_fragment, 940 $row->rd_interwiki 941 ); 942 unset( $this->mPendingRedirectIDs[$rdfrom] ); 943 if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) { 944 $lb->add( $row->rd_namespace, $row->rd_title ); 945 } 946 $this->mRedirectTitles[$from] = $to; 947 } 948 949 if ( $this->mPendingRedirectIDs ) { 950 // We found pages that aren't in the redirect table 951 // Add them 952 foreach ( $this->mPendingRedirectIDs as $id => $title ) { 953 $page = WikiPage::factory( $title ); 954 $rt = $page->insertRedirect(); 955 if ( !$rt ) { 956 // What the hell. Let's just ignore this 957 continue; 958 } 959 $lb->addObj( $rt ); 960 $this->mRedirectTitles[$title->getPrefixedText()] = $rt; 961 unset( $this->mPendingRedirectIDs[$id] ); 962 } 963 } 964 965 return $lb; 966 } 967 968 /** 969 * Get the cache mode for the data generated by this module. 970 * All PageSet users should take into account whether this returns a more-restrictive 971 * cache mode than the using module itself. For possible return values and other 972 * details about cache modes, see ApiMain::setCacheMode() 973 * 974 * Public caching will only be allowed if *all* the modules that supply 975 * data for a given request return a cache mode of public. 976 * 977 * @param array|null $params 978 * @return string 979 * @since 1.21 980 */ 981 public function getCacheMode( $params = null ) { 982 return $this->mCacheMode; 983 } 984 985 /** 986 * Given an array of title strings, convert them into Title objects. 987 * Alternatively, an array of Title objects may be given. 988 * This method validates access rights for the title, 989 * and appends normalization values to the output. 990 * 991 * @param array $titles Array of Title objects or strings 992 * @return LinkBatch 993 */ 994 private function processTitlesArray( $titles ) { 995 $usernames = array(); 996 $linkBatch = new LinkBatch(); 997 998 foreach ( $titles as $title ) { 999 if ( is_string( $title ) ) { 1000 $titleObj = Title::newFromText( $title, $this->mDefaultNamespace ); 1001 } else { 1002 $titleObj = $title; 1003 } 1004 if ( !$titleObj ) { 1005 // Handle invalid titles gracefully 1006 $this->mAllPages[0][$title] = $this->mFakePageId; 1007 $this->mInvalidTitles[$this->mFakePageId] = $title; 1008 $this->mFakePageId--; 1009 continue; // There's nothing else we can do 1010 } 1011 $unconvertedTitle = $titleObj->getPrefixedText(); 1012 $titleWasConverted = false; 1013 if ( $titleObj->isExternal() ) { 1014 // This title is an interwiki link. 1015 $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki(); 1016 } else { 1017 // Variants checking 1018 global $wgContLang; 1019 if ( $this->mConvertTitles && 1020 count( $wgContLang->getVariants() ) > 1 && 1021 !$titleObj->exists() 1022 ) { 1023 // Language::findVariantLink will modify titleText and titleObj into 1024 // the canonical variant if possible 1025 $titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText(); 1026 $wgContLang->findVariantLink( $titleText, $titleObj ); 1027 $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText(); 1028 } 1029 1030 if ( $titleObj->getNamespace() < 0 ) { 1031 // Handle Special and Media pages 1032 $titleObj = $titleObj->fixSpecialName(); 1033 $this->mSpecialTitles[$this->mFakePageId] = $titleObj; 1034 $this->mFakePageId--; 1035 } else { 1036 // Regular page 1037 $linkBatch->addObj( $titleObj ); 1038 } 1039 } 1040 1041 // Make sure we remember the original title that was 1042 // given to us. This way the caller can correlate new 1043 // titles with the originally requested when e.g. the 1044 // namespace is localized or the capitalization is 1045 // different 1046 if ( $titleWasConverted ) { 1047 $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText(); 1048 // In this case the page can't be Special. 1049 if ( is_string( $title ) && $title !== $unconvertedTitle ) { 1050 $this->mNormalizedTitles[$title] = $unconvertedTitle; 1051 } 1052 } elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) { 1053 $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText(); 1054 } 1055 1056 // Need gender information 1057 if ( MWNamespace::hasGenderDistinction( $titleObj->getNamespace() ) ) { 1058 $usernames[] = $titleObj->getText(); 1059 } 1060 } 1061 // Get gender information 1062 $genderCache = GenderCache::singleton(); 1063 $genderCache->doQuery( $usernames, __METHOD__ ); 1064 1065 return $linkBatch; 1066 } 1067 1068 /** 1069 * Get the database connection (read-only) 1070 * @return DatabaseBase 1071 */ 1072 protected function getDB() { 1073 return $this->mDbSource->getDB(); 1074 } 1075 1076 /** 1077 * Returns the input array of integers with all values < 0 removed 1078 * 1079 * @param array $array 1080 * @return array 1081 */ 1082 private static function getPositiveIntegers( $array ) { 1083 // bug 25734 API: possible issue with revids validation 1084 // It seems with a load of revision rows, MySQL gets upset 1085 // Remove any < 0 integers, as they can't be valid 1086 foreach ( $array as $i => $int ) { 1087 if ( $int < 0 ) { 1088 unset( $array[$i] ); 1089 } 1090 } 1091 1092 return $array; 1093 } 1094 1095 public function getAllowedParams( $flags = 0 ) { 1096 $result = array( 1097 'titles' => array( 1098 ApiBase::PARAM_ISMULTI => true 1099 ), 1100 'pageids' => array( 1101 ApiBase::PARAM_TYPE => 'integer', 1102 ApiBase::PARAM_ISMULTI => true 1103 ), 1104 'revids' => array( 1105 ApiBase::PARAM_TYPE => 'integer', 1106 ApiBase::PARAM_ISMULTI => true 1107 ), 1108 'redirects' => false, 1109 'converttitles' => false, 1110 ); 1111 if ( $this->mAllowGenerator ) { 1112 if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) { 1113 $result['generator'] = array( 1114 ApiBase::PARAM_TYPE => $this->getGenerators() 1115 ); 1116 } else { 1117 $result['generator'] = null; 1118 } 1119 } 1120 1121 return $result; 1122 } 1123 1124 private static $generators = null; 1125 1126 /** 1127 * Get an array of all available generators 1128 * @return array 1129 */ 1130 private function getGenerators() { 1131 if ( self::$generators === null ) { 1132 $query = $this->mDbSource; 1133 if ( !( $query instanceof ApiQuery ) ) { 1134 // If the parent container of this pageset is not ApiQuery, 1135 // we must create it to get module manager 1136 $query = $this->getMain()->getModuleManager()->getModule( 'query' ); 1137 } 1138 $gens = array(); 1139 $mgr = $query->getModuleManager(); 1140 foreach ( $mgr->getNamesWithClasses() as $name => $class ) { 1141 if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) { 1142 $gens[] = $name; 1143 } 1144 } 1145 sort( $gens ); 1146 self::$generators = $gens; 1147 } 1148 1149 return self::$generators; 1150 } 1151 1152 public function getParamDescription() { 1153 return array( 1154 'titles' => 'A list of titles to work on', 1155 'pageids' => 'A list of page IDs to work on', 1156 'revids' => 'A list of revision IDs to work on', 1157 'generator' => array( 1158 'Get the list of pages to work on by executing the specified query module.', 1159 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' 1160 ), 1161 'redirects' => 'Automatically resolve redirects', 1162 'converttitles' => array( 1163 'Convert titles to other variants if necessary. Only works if ' . 1164 'the wiki\'s content language supports variant conversion.', 1165 'Languages that support variant conversion include ' . 1166 implode( ', ', LanguageConverter::$languagesWithVariants ) 1167 ), 1168 ); 1169 } 1170 }
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 |