[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/api/ -> ApiPageSet.php (source)

   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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1