[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * 5 * Created on Sep 7, 2006 6 * 7 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 * http://www.gnu.org/copyleft/gpl.html 23 * 24 * @file 25 */ 26 27 /** 28 * This is a base class for all Query modules. 29 * It provides some common functionality such as constructing various SQL 30 * queries. 31 * 32 * @ingroup API 33 */ 34 abstract class ApiQueryBase extends ApiBase { 35 36 private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds; 37 38 /** 39 * @param ApiQuery $queryModule 40 * @param string $moduleName 41 * @param string $paramPrefix 42 */ 43 public function __construct( ApiQuery $queryModule, $moduleName, $paramPrefix = '' ) { 44 parent::__construct( $queryModule->getMain(), $moduleName, $paramPrefix ); 45 $this->mQueryModule = $queryModule; 46 $this->mDb = null; 47 $this->resetQueryParams(); 48 } 49 50 /************************************************************************//** 51 * @name Methods to implement 52 * @{ 53 */ 54 55 /** 56 * Get the cache mode for the data generated by this module. Override 57 * this in the module subclass. For possible return values and other 58 * details about cache modes, see ApiMain::setCacheMode() 59 * 60 * Public caching will only be allowed if *all* the modules that supply 61 * data for a given request return a cache mode of public. 62 * 63 * @param array $params 64 * @return string 65 */ 66 public function getCacheMode( $params ) { 67 return 'private'; 68 } 69 70 /** 71 * Override this method to request extra fields from the pageSet 72 * using $pageSet->requestField('fieldName') 73 * @param ApiPageSet $pageSet 74 */ 75 public function requestExtraData( $pageSet ) { 76 } 77 78 /**@}*/ 79 80 /************************************************************************//** 81 * @name Data access 82 * @{ 83 */ 84 85 /** 86 * Get the main Query module 87 * @return ApiQuery 88 */ 89 public function getQuery() { 90 return $this->mQueryModule; 91 } 92 93 /** 94 * Get the Query database connection (read-only) 95 * @return DatabaseBase 96 */ 97 protected function getDB() { 98 if ( is_null( $this->mDb ) ) { 99 $this->mDb = $this->getQuery()->getDB(); 100 } 101 102 return $this->mDb; 103 } 104 105 /** 106 * Selects the query database connection with the given name. 107 * See ApiQuery::getNamedDB() for more information 108 * @param string $name Name to assign to the database connection 109 * @param int $db One of the DB_* constants 110 * @param array $groups Query groups 111 * @return DatabaseBase 112 */ 113 public function selectNamedDB( $name, $db, $groups ) { 114 $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups ); 115 } 116 117 /** 118 * Get the PageSet object to work on 119 * @return ApiPageSet 120 */ 121 protected function getPageSet() { 122 return $this->getQuery()->getPageSet(); 123 } 124 125 /**@}*/ 126 127 /************************************************************************//** 128 * @name Querying 129 * @{ 130 */ 131 132 /** 133 * Blank the internal arrays with query parameters 134 */ 135 protected function resetQueryParams() { 136 $this->tables = array(); 137 $this->where = array(); 138 $this->fields = array(); 139 $this->options = array(); 140 $this->join_conds = array(); 141 } 142 143 /** 144 * Add a set of tables to the internal array 145 * @param string|string[] $tables Table name or array of table names 146 * @param string|null $alias Table alias, or null for no alias. Cannot be 147 * used with multiple tables 148 */ 149 protected function addTables( $tables, $alias = null ) { 150 if ( is_array( $tables ) ) { 151 if ( !is_null( $alias ) ) { 152 ApiBase::dieDebug( __METHOD__, 'Multiple table aliases not supported' ); 153 } 154 $this->tables = array_merge( $this->tables, $tables ); 155 } else { 156 if ( !is_null( $alias ) ) { 157 $this->tables[$alias] = $tables; 158 } else { 159 $this->tables[] = $tables; 160 } 161 } 162 } 163 164 /** 165 * Add a set of JOIN conditions to the internal array 166 * 167 * JOIN conditions are formatted as array( tablename => array(jointype, 168 * conditions) e.g. array('page' => array('LEFT JOIN', 169 * 'page_id=rev_page')) . conditions may be a string or an 170 * addWhere()-style array 171 * @param array $join_conds JOIN conditions 172 */ 173 protected function addJoinConds( $join_conds ) { 174 if ( !is_array( $join_conds ) ) { 175 ApiBase::dieDebug( __METHOD__, 'Join conditions have to be arrays' ); 176 } 177 $this->join_conds = array_merge( $this->join_conds, $join_conds ); 178 } 179 180 /** 181 * Add a set of fields to select to the internal array 182 * @param array|string $value Field name or array of field names 183 */ 184 protected function addFields( $value ) { 185 if ( is_array( $value ) ) { 186 $this->fields = array_merge( $this->fields, $value ); 187 } else { 188 $this->fields[] = $value; 189 } 190 } 191 192 /** 193 * Same as addFields(), but add the fields only if a condition is met 194 * @param array|string $value See addFields() 195 * @param bool $condition If false, do nothing 196 * @return bool $condition 197 */ 198 protected function addFieldsIf( $value, $condition ) { 199 if ( $condition ) { 200 $this->addFields( $value ); 201 202 return true; 203 } 204 205 return false; 206 } 207 208 /** 209 * Add a set of WHERE clauses to the internal array. 210 * Clauses can be formatted as 'foo=bar' or array('foo' => 'bar'), 211 * the latter only works if the value is a constant (i.e. not another field) 212 * 213 * If $value is an empty array, this function does nothing. 214 * 215 * For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates 216 * to "foo=bar AND baz='3' AND bla='foo'" 217 * @param string|array $value 218 */ 219 protected function addWhere( $value ) { 220 if ( is_array( $value ) ) { 221 // Sanity check: don't insert empty arrays, 222 // Database::makeList() chokes on them 223 if ( count( $value ) ) { 224 $this->where = array_merge( $this->where, $value ); 225 } 226 } else { 227 $this->where[] = $value; 228 } 229 } 230 231 /** 232 * Same as addWhere(), but add the WHERE clauses only if a condition is met 233 * @param string|array $value 234 * @param bool $condition If false, do nothing 235 * @return bool $condition 236 */ 237 protected function addWhereIf( $value, $condition ) { 238 if ( $condition ) { 239 $this->addWhere( $value ); 240 241 return true; 242 } 243 244 return false; 245 } 246 247 /** 248 * Equivalent to addWhere(array($field => $value)) 249 * @param string $field Field name 250 * @param string $value Value; ignored if null or empty array; 251 */ 252 protected function addWhereFld( $field, $value ) { 253 // Use count() to its full documented capabilities to simultaneously 254 // test for null, empty array or empty countable object 255 if ( count( $value ) ) { 256 $this->where[$field] = $value; 257 } 258 } 259 260 /** 261 * Add a WHERE clause corresponding to a range, and an ORDER BY 262 * clause to sort in the right direction 263 * @param string $field Field name 264 * @param string $dir If 'newer', sort in ascending order, otherwise 265 * sort in descending order 266 * @param string $start Value to start the list at. If $dir == 'newer' 267 * this is the lower boundary, otherwise it's the upper boundary 268 * @param string $end Value to end the list at. If $dir == 'newer' this 269 * is the upper boundary, otherwise it's the lower boundary 270 * @param bool $sort If false, don't add an ORDER BY clause 271 */ 272 protected function addWhereRange( $field, $dir, $start, $end, $sort = true ) { 273 $isDirNewer = ( $dir === 'newer' ); 274 $after = ( $isDirNewer ? '>=' : '<=' ); 275 $before = ( $isDirNewer ? '<=' : '>=' ); 276 $db = $this->getDB(); 277 278 if ( !is_null( $start ) ) { 279 $this->addWhere( $field . $after . $db->addQuotes( $start ) ); 280 } 281 282 if ( !is_null( $end ) ) { 283 $this->addWhere( $field . $before . $db->addQuotes( $end ) ); 284 } 285 286 if ( $sort ) { 287 $order = $field . ( $isDirNewer ? '' : ' DESC' ); 288 // Append ORDER BY 289 $optionOrderBy = isset( $this->options['ORDER BY'] ) 290 ? (array)$this->options['ORDER BY'] 291 : array(); 292 $optionOrderBy[] = $order; 293 $this->addOption( 'ORDER BY', $optionOrderBy ); 294 } 295 } 296 297 /** 298 * Add a WHERE clause corresponding to a range, similar to addWhereRange, 299 * but converts $start and $end to database timestamps. 300 * @see addWhereRange 301 * @param string $field 302 * @param string $dir 303 * @param string $start 304 * @param string $end 305 * @param bool $sort 306 */ 307 protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) { 308 $db = $this->getDb(); 309 $this->addWhereRange( $field, $dir, 310 $db->timestampOrNull( $start ), $db->timestampOrNull( $end ), $sort ); 311 } 312 313 /** 314 * Add an option such as LIMIT or USE INDEX. If an option was set 315 * before, the old value will be overwritten 316 * @param string $name Option name 317 * @param string $value Option value 318 */ 319 protected function addOption( $name, $value = null ) { 320 if ( is_null( $value ) ) { 321 $this->options[] = $name; 322 } else { 323 $this->options[$name] = $value; 324 } 325 } 326 327 /** 328 * Execute a SELECT query based on the values in the internal arrays 329 * @param string $method Function the query should be attributed to. 330 * You should usually use __METHOD__ here 331 * @param array $extraQuery Query data to add but not store in the object 332 * Format is array( 333 * 'tables' => ..., 334 * 'fields' => ..., 335 * 'where' => ..., 336 * 'options' => ..., 337 * 'join_conds' => ... 338 * ) 339 * @return ResultWrapper 340 */ 341 protected function select( $method, $extraQuery = array() ) { 342 343 $tables = array_merge( 344 $this->tables, 345 isset( $extraQuery['tables'] ) ? (array)$extraQuery['tables'] : array() 346 ); 347 $fields = array_merge( 348 $this->fields, 349 isset( $extraQuery['fields'] ) ? (array)$extraQuery['fields'] : array() 350 ); 351 $where = array_merge( 352 $this->where, 353 isset( $extraQuery['where'] ) ? (array)$extraQuery['where'] : array() 354 ); 355 $options = array_merge( 356 $this->options, 357 isset( $extraQuery['options'] ) ? (array)$extraQuery['options'] : array() 358 ); 359 $join_conds = array_merge( 360 $this->join_conds, 361 isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array() 362 ); 363 364 // getDB has its own profileDBIn/Out calls 365 $db = $this->getDB(); 366 367 $this->profileDBIn(); 368 $res = $db->select( $tables, $fields, $where, $method, $options, $join_conds ); 369 $this->profileDBOut(); 370 371 return $res; 372 } 373 374 /** 375 * @param string $query 376 * @param string $protocol 377 * @return null|string 378 */ 379 public function prepareUrlQuerySearchString( $query = null, $protocol = null ) { 380 $db = $this->getDb(); 381 if ( !is_null( $query ) || $query != '' ) { 382 if ( is_null( $protocol ) ) { 383 $protocol = 'http://'; 384 } 385 386 $likeQuery = LinkFilter::makeLikeArray( $query, $protocol ); 387 if ( !$likeQuery ) { 388 $this->dieUsage( 'Invalid query', 'bad_query' ); 389 } 390 391 $likeQuery = LinkFilter::keepOneWildcard( $likeQuery ); 392 393 return 'el_index ' . $db->buildLike( $likeQuery ); 394 } elseif ( !is_null( $protocol ) ) { 395 return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() ); 396 } 397 398 return null; 399 } 400 401 /** 402 * Filters hidden users (where the user doesn't have the right to view them) 403 * Also adds relevant block information 404 * 405 * @param bool $showBlockInfo 406 * @return void 407 */ 408 public function showHiddenUsersAddBlockInfo( $showBlockInfo ) { 409 $this->addTables( 'ipblocks' ); 410 $this->addJoinConds( array( 411 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ), 412 ) ); 413 414 $this->addFields( 'ipb_deleted' ); 415 416 if ( $showBlockInfo ) { 417 $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry', 'ipb_timestamp' ) ); 418 } 419 420 // Don't show hidden names 421 if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { 422 $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' ); 423 } 424 } 425 426 /**@}*/ 427 428 /************************************************************************//** 429 * @name Utility methods 430 * @{ 431 */ 432 433 /** 434 * Add information (title and namespace) about a Title object to a 435 * result array 436 * @param array $arr Result array à la ApiResult 437 * @param Title $title 438 * @param string $prefix Module prefix 439 */ 440 public static function addTitleInfo( &$arr, $title, $prefix = '' ) { 441 $arr[$prefix . 'ns'] = intval( $title->getNamespace() ); 442 $arr[$prefix . 'title'] = $title->getPrefixedText(); 443 } 444 445 /** 446 * Add a sub-element under the page element with the given page ID 447 * @param int $pageId Page ID 448 * @param array $data Data array à la ApiResult 449 * @return bool Whether the element fit in the result 450 */ 451 protected function addPageSubItems( $pageId, $data ) { 452 $result = $this->getResult(); 453 $result->setIndexedTagName( $data, $this->getModulePrefix() ); 454 455 return $result->addValue( array( 'query', 'pages', intval( $pageId ) ), 456 $this->getModuleName(), 457 $data ); 458 } 459 460 /** 461 * Same as addPageSubItems(), but one element of $data at a time 462 * @param int $pageId Page ID 463 * @param array $item Data array à la ApiResult 464 * @param string $elemname XML element name. If null, getModuleName() 465 * is used 466 * @return bool Whether the element fit in the result 467 */ 468 protected function addPageSubItem( $pageId, $item, $elemname = null ) { 469 if ( is_null( $elemname ) ) { 470 $elemname = $this->getModulePrefix(); 471 } 472 $result = $this->getResult(); 473 $fit = $result->addValue( array( 'query', 'pages', $pageId, 474 $this->getModuleName() ), null, $item ); 475 if ( !$fit ) { 476 return false; 477 } 478 $result->setIndexedTagName_internal( array( 'query', 'pages', $pageId, 479 $this->getModuleName() ), $elemname ); 480 481 return true; 482 } 483 484 /** 485 * Set a query-continue value 486 * @param string $paramName Parameter name 487 * @param string|array $paramValue Parameter value 488 */ 489 protected function setContinueEnumParameter( $paramName, $paramValue ) { 490 $this->getResult()->setContinueParam( $this, $paramName, $paramValue ); 491 } 492 493 /** 494 * Convert an input title or title prefix into a dbkey. 495 * 496 * $namespace should always be specified in order to handle per-namespace 497 * capitalization settings. 498 * 499 * @param string $titlePart Title part 500 * @param int $defaultNamespace Namespace of the title 501 * @return string DBkey (no namespace prefix) 502 */ 503 public function titlePartToKey( $titlePart, $namespace = NS_MAIN ) { 504 $t = Title::makeTitleSafe( $namespace, $titlePart . 'x' ); 505 if ( !$t ) { 506 $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) ); 507 } 508 if ( $namespace != $t->getNamespace() || $t->isExternal() ) { 509 // This can happen in two cases. First, if you call titlePartToKey with a title part 510 // that looks like a namespace, but with $defaultNamespace = NS_MAIN. It would be very 511 // difficult to handle such a case. Such cases cannot exist and are therefore treated 512 // as invalid user input. The second case is when somebody specifies a title interwiki 513 // prefix. 514 $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) ); 515 } 516 517 return substr( $t->getDbKey(), 0, -1 ); 518 } 519 520 /** 521 * Gets the personalised direction parameter description 522 * 523 * @param string $p ModulePrefix 524 * @param string $extraDirText Any extra text to be appended on the description 525 * @return array 526 */ 527 public function getDirectionDescription( $p = '', $extraDirText = '' ) { 528 return array( 529 "In which direction to enumerate{$extraDirText}", 530 " newer - List oldest first. Note: {$p}start has to be before {$p}end.", 531 " older - List newest first (default). Note: {$p}start has to be later than {$p}end.", 532 ); 533 } 534 535 /** 536 * @param string $hash 537 * @return bool 538 */ 539 public function validateSha1Hash( $hash ) { 540 return preg_match( '/^[a-f0-9]{40}$/', $hash ); 541 } 542 543 /** 544 * @param string $hash 545 * @return bool 546 */ 547 public function validateSha1Base36Hash( $hash ) { 548 return preg_match( '/^[a-z0-9]{31}$/', $hash ); 549 } 550 551 /** 552 * Check whether the current user has permission to view revision-deleted 553 * fields. 554 * @return bool 555 */ 556 public function userCanSeeRevDel() { 557 return $this->getUser()->isAllowedAny( 558 'deletedhistory', 559 'deletedtext', 560 'suppressrevision', 561 'viewsuppressed' 562 ); 563 } 564 565 /**@}*/ 566 567 /************************************************************************//** 568 * @name Deprecated 569 * @{ 570 */ 571 572 /** 573 * Estimate the row count for the SELECT query that would be run if we 574 * called select() right now, and check if it's acceptable. 575 * @deprecated since 1.24 576 * @return bool True if acceptable, false otherwise 577 */ 578 protected function checkRowCount() { 579 wfDeprecated( __METHOD__, '1.24' ); 580 $db = $this->getDB(); 581 $this->profileDBIn(); 582 $rowcount = $db->estimateRowCount( 583 $this->tables, 584 $this->fields, 585 $this->where, 586 __METHOD__, 587 $this->options 588 ); 589 $this->profileDBOut(); 590 591 if ( $rowcount > $this->getConfig()->get( 'APIMaxDBRows' ) ) { 592 return false; 593 } 594 595 return true; 596 } 597 598 /** 599 * Convert a title to a DB key 600 * @deprecated since 1.24, past uses of this were always incorrect and should 601 * have used self::titlePartToKey() instead 602 * @param string $title Page title with spaces 603 * @return string Page title with underscores 604 */ 605 public function titleToKey( $title ) { 606 wfDeprecated( __METHOD__, '1.24' ); 607 // Don't throw an error if we got an empty string 608 if ( trim( $title ) == '' ) { 609 return ''; 610 } 611 $t = Title::newFromText( $title ); 612 if ( !$t ) { 613 $this->dieUsageMsg( array( 'invalidtitle', $title ) ); 614 } 615 616 return $t->getPrefixedDBkey(); 617 } 618 619 /** 620 * The inverse of titleToKey() 621 * @deprecated since 1.24, unused and probably never needed 622 * @param string $key Page title with underscores 623 * @return string Page title with spaces 624 */ 625 public function keyToTitle( $key ) { 626 wfDeprecated( __METHOD__, '1.24' ); 627 // Don't throw an error if we got an empty string 628 if ( trim( $key ) == '' ) { 629 return ''; 630 } 631 $t = Title::newFromDBkey( $key ); 632 // This really shouldn't happen but we gotta check anyway 633 if ( !$t ) { 634 $this->dieUsageMsg( array( 'invalidtitle', $key ) ); 635 } 636 637 return $t->getPrefixedText(); 638 } 639 640 /** 641 * Inverse of titlePartToKey() 642 * @deprecated since 1.24, unused and probably never needed 643 * @param string $keyPart DBkey, with prefix 644 * @return string Key part with underscores 645 */ 646 public function keyPartToTitle( $keyPart ) { 647 wfDeprecated( __METHOD__, '1.24' ); 648 return substr( $this->keyToTitle( $keyPart . 'x' ), 0, -1 ); 649 } 650 651 /**@}*/ 652 } 653 654 /** 655 * @ingroup API 656 */ 657 abstract class ApiQueryGeneratorBase extends ApiQueryBase { 658 659 private $mGeneratorPageSet = null; 660 661 /** 662 * Switch this module to generator mode. By default, generator mode is 663 * switched off and the module acts like a normal query module. 664 * @since 1.21 requires pageset parameter 665 * @param ApiPageSet $generatorPageSet ApiPageSet object that the module will get 666 * by calling getPageSet() when in generator mode. 667 */ 668 public function setGeneratorMode( ApiPageSet $generatorPageSet ) { 669 if ( $generatorPageSet === null ) { 670 ApiBase::dieDebug( __METHOD__, 'Required parameter missing - $generatorPageSet' ); 671 } 672 $this->mGeneratorPageSet = $generatorPageSet; 673 } 674 675 /** 676 * Get the PageSet object to work on. 677 * If this module is generator, the pageSet object is different from other module's 678 * @return ApiPageSet 679 */ 680 protected function getPageSet() { 681 if ( $this->mGeneratorPageSet !== null ) { 682 return $this->mGeneratorPageSet; 683 } 684 685 return parent::getPageSet(); 686 } 687 688 /** 689 * Overrides ApiBase to prepend 'g' to every generator parameter 690 * @param string $paramName Parameter name 691 * @return string Prefixed parameter name 692 */ 693 public function encodeParamName( $paramName ) { 694 if ( $this->mGeneratorPageSet !== null ) { 695 return 'g' . parent::encodeParamName( $paramName ); 696 } else { 697 return parent::encodeParamName( $paramName ); 698 } 699 } 700 701 /** 702 * Overridden to set the generator param if in generator mode 703 * @param string $paramName Parameter name 704 * @param string|array $paramValue Parameter value 705 */ 706 protected function setContinueEnumParameter( $paramName, $paramValue ) { 707 if ( $this->mGeneratorPageSet !== null ) { 708 $this->getResult()->setGeneratorContinueParam( $this, $paramName, $paramValue ); 709 } else { 710 parent::setContinueEnumParameter( $paramName, $paramValue ); 711 } 712 } 713 714 /** 715 * Execute this module as a generator 716 * @param ApiPageSet $resultPageSet All output should be appended to this object 717 */ 718 abstract public function executeGenerator( $resultPageSet ); 719 }
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 |