[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

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

   1  <?php
   2  /**
   3   *
   4   *
   5   * Created on June 14, 2007
   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   * A query module to enumerate pages that belong to a category.
  29   *
  30   * @ingroup API
  31   */
  32  class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
  33  
  34  	public function __construct( ApiQuery $query, $moduleName ) {
  35          parent::__construct( $query, $moduleName, 'cm' );
  36      }
  37  
  38  	public function execute() {
  39          $this->run();
  40      }
  41  
  42  	public function getCacheMode( $params ) {
  43          return 'public';
  44      }
  45  
  46  	public function executeGenerator( $resultPageSet ) {
  47          $this->run( $resultPageSet );
  48      }
  49  
  50      /**
  51       * @param ApiPageSet $resultPageSet
  52       * @return void
  53       */
  54  	private function run( $resultPageSet = null ) {
  55          $params = $this->extractRequestParams();
  56  
  57          $categoryTitle = $this->getTitleOrPageId( $params )->getTitle();
  58          if ( $categoryTitle->getNamespace() != NS_CATEGORY ) {
  59              $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' );
  60          }
  61  
  62          $prop = array_flip( $params['prop'] );
  63          $fld_ids = isset( $prop['ids'] );
  64          $fld_title = isset( $prop['title'] );
  65          $fld_sortkey = isset( $prop['sortkey'] );
  66          $fld_sortkeyprefix = isset( $prop['sortkeyprefix'] );
  67          $fld_timestamp = isset( $prop['timestamp'] );
  68          $fld_type = isset( $prop['type'] );
  69  
  70          if ( is_null( $resultPageSet ) ) {
  71              $this->addFields( array( 'cl_from', 'cl_sortkey', 'cl_type', 'page_namespace', 'page_title' ) );
  72              $this->addFieldsIf( 'page_id', $fld_ids );
  73              $this->addFieldsIf( 'cl_sortkey_prefix', $fld_sortkeyprefix );
  74          } else {
  75              $this->addFields( $resultPageSet->getPageTableFields() ); // will include page_ id, ns, title
  76              $this->addFields( array( 'cl_from', 'cl_sortkey', 'cl_type' ) );
  77          }
  78  
  79          $this->addFieldsIf( 'cl_timestamp', $fld_timestamp || $params['sort'] == 'timestamp' );
  80  
  81          $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX'
  82  
  83          $this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() );
  84          $queryTypes = $params['type'];
  85          $contWhere = false;
  86  
  87          // Scanning large datasets for rare categories sucks, and I already told
  88          // how to have efficient subcategory access :-) ~~~~ (oh well, domas)
  89          $miser_ns = array();
  90          if ( $this->getConfig()->get( 'MiserMode' ) ) {
  91              $miser_ns = $params['namespace'];
  92          } else {
  93              $this->addWhereFld( 'page_namespace', $params['namespace'] );
  94          }
  95  
  96          $dir = in_array( $params['dir'], array( 'asc', 'ascending', 'newer' ) ) ? 'newer' : 'older';
  97  
  98          if ( $params['sort'] == 'timestamp' ) {
  99              $this->addTimestampWhereRange( 'cl_timestamp',
 100                  $dir,
 101                  $params['start'],
 102                  $params['end'] );
 103              // Include in ORDER BY for uniqueness
 104              $this->addWhereRange( 'cl_from', $dir, null, null );
 105  
 106              if ( !is_null( $params['continue'] ) ) {
 107                  $cont = explode( '|', $params['continue'] );
 108                  $this->dieContinueUsageIf( count( $cont ) != 2 );
 109                  $op = ( $dir === 'newer' ? '>' : '<' );
 110                  $db = $this->getDB();
 111                  $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
 112                  $continueFrom = (int)$cont[1];
 113                  $this->dieContinueUsageIf( $continueFrom != $cont[1] );
 114                  $this->addWhere( "cl_timestamp $op $continueTimestamp OR " .
 115                      "(cl_timestamp = $continueTimestamp AND " .
 116                      "cl_from $op= $continueFrom)"
 117                  );
 118              }
 119  
 120              $this->addOption( 'USE INDEX', 'cl_timestamp' );
 121          } else {
 122              if ( $params['continue'] ) {
 123                  $cont = explode( '|', $params['continue'], 3 );
 124                  $this->dieContinueUsageIf( count( $cont ) != 3 );
 125  
 126                  // Remove the types to skip from $queryTypes
 127                  $contTypeIndex = array_search( $cont[0], $queryTypes );
 128                  $queryTypes = array_slice( $queryTypes, $contTypeIndex );
 129  
 130                  // Add a WHERE clause for sortkey and from
 131                  // pack( "H*", $foo ) is used to convert hex back to binary
 132                  $escSortkey = $this->getDB()->addQuotes( pack( 'H*', $cont[1] ) );
 133                  $from = intval( $cont[2] );
 134                  $op = $dir == 'newer' ? '>' : '<';
 135                  // $contWhere is used further down
 136                  $contWhere = "cl_sortkey $op $escSortkey OR " .
 137                      "(cl_sortkey = $escSortkey AND " .
 138                      "cl_from $op= $from)";
 139                  // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them
 140                  $this->addWhereRange( 'cl_sortkey', $dir, null, null );
 141                  $this->addWhereRange( 'cl_from', $dir, null, null );
 142              } else {
 143                  if ( $params['startsortkeyprefix'] !== null ) {
 144                      $startsortkey = Collation::singleton()->getSortkey( $params['startsortkeyprefix'] );
 145                  } elseif ( $params['starthexsortkey'] !== null ) {
 146                      $startsortkey = pack( 'H*', $params['starthexsortkey'] );
 147                  } else {
 148                      $this->logFeatureUsage( 'list=categorymembers&cmstartsortkey' );
 149                      $startsortkey = $params['startsortkey'];
 150                  }
 151                  if ( $params['endsortkeyprefix'] !== null ) {
 152                      $endsortkey = Collation::singleton()->getSortkey( $params['endsortkeyprefix'] );
 153                  } elseif ( $params['endhexsortkey'] !== null ) {
 154                      $endsortkey = pack( 'H*', $params['endhexsortkey'] );
 155                  } else {
 156                      $this->logFeatureUsage( 'list=categorymembers&cmendsortkey' );
 157                      $endsortkey = $params['endsortkey'];
 158                  }
 159  
 160                  // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them
 161                  $this->addWhereRange( 'cl_sortkey',
 162                      $dir,
 163                      $startsortkey,
 164                      $endsortkey );
 165                  $this->addWhereRange( 'cl_from', $dir, null, null );
 166              }
 167              $this->addOption( 'USE INDEX', 'cl_sortkey' );
 168          }
 169  
 170          $this->addWhere( 'cl_from=page_id' );
 171  
 172          $limit = $params['limit'];
 173          $this->addOption( 'LIMIT', $limit + 1 );
 174  
 175          if ( $params['sort'] == 'sortkey' ) {
 176              // Run a separate SELECT query for each value of cl_type.
 177              // This is needed because cl_type is an enum, and MySQL has
 178              // inconsistencies between ORDER BY cl_type and
 179              // WHERE cl_type >= 'foo' making proper paging impossible
 180              // and unindexed.
 181              $rows = array();
 182              $first = true;
 183              foreach ( $queryTypes as $type ) {
 184                  $extraConds = array( 'cl_type' => $type );
 185                  if ( $first && $contWhere ) {
 186                      // Continuation condition. Only added to the
 187                      // first query, otherwise we'll skip things
 188                      $extraConds[] = $contWhere;
 189                  }
 190                  $res = $this->select( __METHOD__, array( 'where' => $extraConds ) );
 191                  $rows = array_merge( $rows, iterator_to_array( $res ) );
 192                  if ( count( $rows ) >= $limit + 1 ) {
 193                      break;
 194                  }
 195                  $first = false;
 196              }
 197          } else {
 198              // Sorting by timestamp
 199              // No need to worry about per-type queries because we
 200              // aren't sorting or filtering by type anyway
 201              $res = $this->select( __METHOD__ );
 202              $rows = iterator_to_array( $res );
 203          }
 204  
 205          $result = $this->getResult();
 206          $count = 0;
 207          foreach ( $rows as $row ) {
 208              if ( ++$count > $limit ) {
 209                  // We've reached the one extra which shows that there are
 210                  // additional pages to be had. Stop here...
 211                  // @todo Security issue - if the user has no right to view next
 212                  // title, it will still be shown
 213                  if ( $params['sort'] == 'timestamp' ) {
 214                      $this->setContinueEnumParameter( 'continue', "$row->cl_timestamp|$row->cl_from" );
 215                  } else {
 216                      $sortkey = bin2hex( $row->cl_sortkey );
 217                      $this->setContinueEnumParameter( 'continue',
 218                          "{$row->cl_type}|$sortkey|{$row->cl_from}"
 219                      );
 220                  }
 221                  break;
 222              }
 223  
 224              // Since domas won't tell anyone what he told long ago, apply
 225              // cmnamespace here. This means the query may return 0 actual
 226              // results, but on the other hand it could save returning 5000
 227              // useless results to the client. ~~~~
 228              if ( count( $miser_ns ) && !in_array( $row->page_namespace, $miser_ns ) ) {
 229                  continue;
 230              }
 231  
 232              if ( is_null( $resultPageSet ) ) {
 233                  $vals = array();
 234                  if ( $fld_ids ) {
 235                      $vals['pageid'] = intval( $row->page_id );
 236                  }
 237                  if ( $fld_title ) {
 238                      $title = Title::makeTitle( $row->page_namespace, $row->page_title );
 239                      ApiQueryBase::addTitleInfo( $vals, $title );
 240                  }
 241                  if ( $fld_sortkey ) {
 242                      $vals['sortkey'] = bin2hex( $row->cl_sortkey );
 243                  }
 244                  if ( $fld_sortkeyprefix ) {
 245                      $vals['sortkeyprefix'] = $row->cl_sortkey_prefix;
 246                  }
 247                  if ( $fld_type ) {
 248                      $vals['type'] = $row->cl_type;
 249                  }
 250                  if ( $fld_timestamp ) {
 251                      $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp );
 252                  }
 253                  $fit = $result->addValue( array( 'query', $this->getModuleName() ),
 254                      null, $vals );
 255                  if ( !$fit ) {
 256                      if ( $params['sort'] == 'timestamp' ) {
 257                          $this->setContinueEnumParameter( 'continue', "$row->cl_timestamp|$row->cl_from" );
 258                      } else {
 259                          $sortkey = bin2hex( $row->cl_sortkey );
 260                          $this->setContinueEnumParameter( 'continue',
 261                              "{$row->cl_type}|$sortkey|{$row->cl_from}"
 262                          );
 263                      }
 264                      break;
 265                  }
 266              } else {
 267                  $resultPageSet->processDbRow( $row );
 268              }
 269          }
 270  
 271          if ( is_null( $resultPageSet ) ) {
 272              $result->setIndexedTagName_internal(
 273                  array( 'query', $this->getModuleName() ), 'cm' );
 274          }
 275      }
 276  
 277  	public function getAllowedParams() {
 278          return array(
 279              'title' => array(
 280                  ApiBase::PARAM_TYPE => 'string',
 281              ),
 282              'pageid' => array(
 283                  ApiBase::PARAM_TYPE => 'integer'
 284              ),
 285              'prop' => array(
 286                  ApiBase::PARAM_DFLT => 'ids|title',
 287                  ApiBase::PARAM_ISMULTI => true,
 288                  ApiBase::PARAM_TYPE => array(
 289                      'ids',
 290                      'title',
 291                      'sortkey',
 292                      'sortkeyprefix',
 293                      'type',
 294                      'timestamp',
 295                  )
 296              ),
 297              'namespace' => array(
 298                  ApiBase::PARAM_ISMULTI => true,
 299                  ApiBase::PARAM_TYPE => 'namespace',
 300              ),
 301              'type' => array(
 302                  ApiBase::PARAM_ISMULTI => true,
 303                  ApiBase::PARAM_DFLT => 'page|subcat|file',
 304                  ApiBase::PARAM_TYPE => array(
 305                      'page',
 306                      'subcat',
 307                      'file'
 308                  )
 309              ),
 310              'continue' => null,
 311              'limit' => array(
 312                  ApiBase::PARAM_TYPE => 'limit',
 313                  ApiBase::PARAM_DFLT => 10,
 314                  ApiBase::PARAM_MIN => 1,
 315                  ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
 316                  ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
 317              ),
 318              'sort' => array(
 319                  ApiBase::PARAM_DFLT => 'sortkey',
 320                  ApiBase::PARAM_TYPE => array(
 321                      'sortkey',
 322                      'timestamp'
 323                  )
 324              ),
 325              'dir' => array(
 326                  ApiBase::PARAM_DFLT => 'ascending',
 327                  ApiBase::PARAM_TYPE => array(
 328                      'asc',
 329                      'desc',
 330                      // Normalising with other modules
 331                      'ascending',
 332                      'descending',
 333                      'newer',
 334                      'older',
 335                  )
 336              ),
 337              'start' => array(
 338                  ApiBase::PARAM_TYPE => 'timestamp'
 339              ),
 340              'end' => array(
 341                  ApiBase::PARAM_TYPE => 'timestamp'
 342              ),
 343              'starthexsortkey' => null,
 344              'endhexsortkey' => null,
 345              'startsortkeyprefix' => null,
 346              'endsortkeyprefix' => null,
 347              'startsortkey' => array(
 348                  ApiBase::PARAM_DEPRECATED => true,
 349              ),
 350              'endsortkey' => array(
 351                  ApiBase::PARAM_DEPRECATED => true,
 352              ),
 353          );
 354      }
 355  
 356  	public function getParamDescription() {
 357          $p = $this->getModulePrefix();
 358          $desc = array(
 359              'title' => "Which category to enumerate (required). Must include " .
 360                  "'Category:' prefix. Cannot be used together with {$p}pageid",
 361              'pageid' => "Page ID of the category to enumerate. Cannot be used together with {$p}title",
 362              'prop' => array(
 363                  'What pieces of information to include',
 364                  ' ids           - Adds the page ID',
 365                  ' title         - Adds the title and namespace ID of the page',
 366                  ' sortkey       - Adds the sortkey used for sorting in the category (hexadecimal string)',
 367                  ' sortkeyprefix - Adds the sortkey prefix used for sorting in the ' .
 368                      'category (human-readable part of the sortkey)',
 369                  ' type          - Adds the type that the page has been categorised as (page, subcat or file)',
 370                  ' timestamp     - Adds the timestamp of when the page was included',
 371              ),
 372              'namespace' => 'Only include pages in these namespaces',
 373              'type' => "What type of category members to include. Ignored when {$p}sort=timestamp is set",
 374              'sort' => 'Property to sort by',
 375              'dir' => 'In which direction to sort',
 376              'start' => "Timestamp to start listing from. Can only be used with {$p}sort=timestamp",
 377              'end' => "Timestamp to end listing at. Can only be used with {$p}sort=timestamp",
 378              'starthexsortkey' => "Sortkey to start listing from, as returned by prop=sortkey. " .
 379                  "Can only be used with {$p}sort=sortkey",
 380              'endhexsortkey' => "Sortkey to end listing from, as returned by prop=sortkey. " .
 381                  "Can only be used with {$p}sort=sortkey",
 382              'startsortkeyprefix' => "Sortkey prefix to start listing from. Can " .
 383                  "only be used with {$p}sort=sortkey. Overrides {$p}starthexsortkey",
 384              'endsortkeyprefix' => "Sortkey prefix to end listing BEFORE (not at, " .
 385                  "if this value occurs it will not be included!). Can only be used with " .
 386                  "{$p}sort=sortkey. Overrides {$p}endhexsortkey",
 387              'startsortkey' => "Use starthexsortkey instead",
 388              'endsortkey' => "Use endhexsortkey instead",
 389              'continue' => 'For large categories, give the value returned from previous query',
 390              'limit' => 'The maximum number of pages to return.',
 391          );
 392  
 393          if ( $this->getConfig()->get( 'MiserMode' ) ) {
 394              $desc['namespace'] = array(
 395                  $desc['namespace'],
 396                  "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
 397                  'returned before continuing; in extreme cases, zero results may be returned.',
 398                  "Note that you can use {$p}type=subcat or {$p}type=file instead of {$p}namespace=14 or 6.",
 399              );
 400          }
 401  
 402          return $desc;
 403      }
 404  
 405  	public function getDescription() {
 406          return 'List all pages in a given category.';
 407      }
 408  
 409  	public function getExamples() {
 410          return array(
 411              'api.php?action=query&list=categorymembers&cmtitle=Category:Physics'
 412                  => 'Get first 10 pages in [[Category:Physics]]',
 413              'api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info'
 414                  => 'Get page info about first 10 pages in [[Category:Physics]]',
 415          );
 416      }
 417  
 418  	public function getHelpUrls() {
 419          return 'https://www.mediawiki.org/wiki/API:Categorymembers';
 420      }
 421  }


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