MediaWiki  REL1_23
ApiQueryBase.php
Go to the documentation of this file.
00001 <?php
00034 abstract class ApiQueryBase extends ApiBase {
00035 
00036     private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
00037 
00043     public function __construct( ApiBase $query, $moduleName, $paramPrefix = '' ) {
00044         parent::__construct( $query->getMain(), $moduleName, $paramPrefix );
00045         $this->mQueryModule = $query;
00046         $this->mDb = null;
00047         $this->resetQueryParams();
00048     }
00049 
00061     public function getCacheMode( $params ) {
00062         return 'private';
00063     }
00064 
00068     protected function resetQueryParams() {
00069         $this->tables = array();
00070         $this->where = array();
00071         $this->fields = array();
00072         $this->options = array();
00073         $this->join_conds = array();
00074     }
00075 
00082     protected function addTables( $tables, $alias = null ) {
00083         if ( is_array( $tables ) ) {
00084             if ( !is_null( $alias ) ) {
00085                 ApiBase::dieDebug( __METHOD__, 'Multiple table aliases not supported' );
00086             }
00087             $this->tables = array_merge( $this->tables, $tables );
00088         } else {
00089             if ( !is_null( $alias ) ) {
00090                 $this->tables[$alias] = $tables;
00091             } else {
00092                 $this->tables[] = $tables;
00093             }
00094         }
00095     }
00096 
00106     protected function addJoinConds( $join_conds ) {
00107         if ( !is_array( $join_conds ) ) {
00108             ApiBase::dieDebug( __METHOD__, 'Join conditions have to be arrays' );
00109         }
00110         $this->join_conds = array_merge( $this->join_conds, $join_conds );
00111     }
00112 
00117     protected function addFields( $value ) {
00118         if ( is_array( $value ) ) {
00119             $this->fields = array_merge( $this->fields, $value );
00120         } else {
00121             $this->fields[] = $value;
00122         }
00123     }
00124 
00131     protected function addFieldsIf( $value, $condition ) {
00132         if ( $condition ) {
00133             $this->addFields( $value );
00134 
00135             return true;
00136         }
00137 
00138         return false;
00139     }
00140 
00152     protected function addWhere( $value ) {
00153         if ( is_array( $value ) ) {
00154             // Sanity check: don't insert empty arrays,
00155             // Database::makeList() chokes on them
00156             if ( count( $value ) ) {
00157                 $this->where = array_merge( $this->where, $value );
00158             }
00159         } else {
00160             $this->where[] = $value;
00161         }
00162     }
00163 
00170     protected function addWhereIf( $value, $condition ) {
00171         if ( $condition ) {
00172             $this->addWhere( $value );
00173 
00174             return true;
00175         }
00176 
00177         return false;
00178     }
00179 
00185     protected function addWhereFld( $field, $value ) {
00186         // Use count() to its full documented capabilities to simultaneously
00187         // test for null, empty array or empty countable object
00188         if ( count( $value ) ) {
00189             $this->where[$field] = $value;
00190         }
00191     }
00192 
00205     protected function addWhereRange( $field, $dir, $start, $end, $sort = true ) {
00206         $isDirNewer = ( $dir === 'newer' );
00207         $after = ( $isDirNewer ? '>=' : '<=' );
00208         $before = ( $isDirNewer ? '<=' : '>=' );
00209         $db = $this->getDB();
00210 
00211         if ( !is_null( $start ) ) {
00212             $this->addWhere( $field . $after . $db->addQuotes( $start ) );
00213         }
00214 
00215         if ( !is_null( $end ) ) {
00216             $this->addWhere( $field . $before . $db->addQuotes( $end ) );
00217         }
00218 
00219         if ( $sort ) {
00220             $order = $field . ( $isDirNewer ? '' : ' DESC' );
00221             // Append ORDER BY
00222             $optionOrderBy = isset( $this->options['ORDER BY'] )
00223                 ? (array)$this->options['ORDER BY']
00224                 : array();
00225             $optionOrderBy[] = $order;
00226             $this->addOption( 'ORDER BY', $optionOrderBy );
00227         }
00228     }
00229 
00240     protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) {
00241         $db = $this->getDb();
00242         $this->addWhereRange( $field, $dir,
00243             $db->timestampOrNull( $start ), $db->timestampOrNull( $end ), $sort );
00244     }
00245 
00252     protected function addOption( $name, $value = null ) {
00253         if ( is_null( $value ) ) {
00254             $this->options[] = $name;
00255         } else {
00256             $this->options[$name] = $value;
00257         }
00258     }
00259 
00274     protected function select( $method, $extraQuery = array() ) {
00275 
00276         $tables = array_merge(
00277             $this->tables,
00278             isset( $extraQuery['tables'] ) ? (array)$extraQuery['tables'] : array()
00279         );
00280         $fields = array_merge(
00281             $this->fields,
00282             isset( $extraQuery['fields'] ) ? (array)$extraQuery['fields'] : array()
00283         );
00284         $where = array_merge(
00285             $this->where,
00286             isset( $extraQuery['where'] ) ? (array)$extraQuery['where'] : array()
00287         );
00288         $options = array_merge(
00289             $this->options,
00290             isset( $extraQuery['options'] ) ? (array)$extraQuery['options'] : array()
00291         );
00292         $join_conds = array_merge(
00293             $this->join_conds,
00294             isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array()
00295         );
00296 
00297         // getDB has its own profileDBIn/Out calls
00298         $db = $this->getDB();
00299 
00300         $this->profileDBIn();
00301         $res = $db->select( $tables, $fields, $where, $method, $options, $join_conds );
00302         $this->profileDBOut();
00303 
00304         return $res;
00305     }
00306 
00312     protected function checkRowCount() {
00313         $db = $this->getDB();
00314         $this->profileDBIn();
00315         $rowcount = $db->estimateRowCount(
00316             $this->tables,
00317             $this->fields,
00318             $this->where,
00319             __METHOD__,
00320             $this->options
00321         );
00322         $this->profileDBOut();
00323 
00324         global $wgAPIMaxDBRows;
00325         if ( $rowcount > $wgAPIMaxDBRows ) {
00326             return false;
00327         }
00328 
00329         return true;
00330     }
00331 
00339     public static function addTitleInfo( &$arr, $title, $prefix = '' ) {
00340         $arr[$prefix . 'ns'] = intval( $title->getNamespace() );
00341         $arr[$prefix . 'title'] = $title->getPrefixedText();
00342     }
00343 
00349     public function requestExtraData( $pageSet ) {
00350     }
00351 
00356     public function getQuery() {
00357         return $this->mQueryModule;
00358     }
00359 
00366     protected function addPageSubItems( $pageId, $data ) {
00367         $result = $this->getResult();
00368         $result->setIndexedTagName( $data, $this->getModulePrefix() );
00369 
00370         return $result->addValue( array( 'query', 'pages', intval( $pageId ) ),
00371             $this->getModuleName(),
00372             $data );
00373     }
00374 
00383     protected function addPageSubItem( $pageId, $item, $elemname = null ) {
00384         if ( is_null( $elemname ) ) {
00385             $elemname = $this->getModulePrefix();
00386         }
00387         $result = $this->getResult();
00388         $fit = $result->addValue( array( 'query', 'pages', $pageId,
00389             $this->getModuleName() ), null, $item );
00390         if ( !$fit ) {
00391             return false;
00392         }
00393         $result->setIndexedTagName_internal( array( 'query', 'pages', $pageId,
00394             $this->getModuleName() ), $elemname );
00395 
00396         return true;
00397     }
00398 
00404     protected function setContinueEnumParameter( $paramName, $paramValue ) {
00405         $paramName = $this->encodeParamName( $paramName );
00406         $msg = array( $paramName => $paramValue );
00407         $result = $this->getResult();
00408         $result->disableSizeCheck();
00409         $result->addValue( 'query-continue', $this->getModuleName(), $msg, ApiResult::ADD_ON_TOP );
00410         $result->enableSizeCheck();
00411     }
00412 
00417     protected function getDB() {
00418         if ( is_null( $this->mDb ) ) {
00419             $this->mDb = $this->getQuery()->getDB();
00420         }
00421 
00422         return $this->mDb;
00423     }
00424 
00433     public function selectNamedDB( $name, $db, $groups ) {
00434         $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups );
00435     }
00436 
00441     protected function getPageSet() {
00442         return $this->getQuery()->getPageSet();
00443     }
00444 
00450     public function titleToKey( $title ) {
00451         // Don't throw an error if we got an empty string
00452         if ( trim( $title ) == '' ) {
00453             return '';
00454         }
00455         $t = Title::newFromText( $title );
00456         if ( !$t ) {
00457             $this->dieUsageMsg( array( 'invalidtitle', $title ) );
00458         }
00459 
00460         return $t->getPrefixedDBkey();
00461     }
00462 
00468     public function keyToTitle( $key ) {
00469         // Don't throw an error if we got an empty string
00470         if ( trim( $key ) == '' ) {
00471             return '';
00472         }
00473         $t = Title::newFromDBkey( $key );
00474         // This really shouldn't happen but we gotta check anyway
00475         if ( !$t ) {
00476             $this->dieUsageMsg( array( 'invalidtitle', $key ) );
00477         }
00478 
00479         return $t->getPrefixedText();
00480     }
00481 
00491     public function titlePartToKey( $titlePart, $defaultNamespace = NS_MAIN ) {
00492         $t = Title::makeTitleSafe( $defaultNamespace, $titlePart . 'x' );
00493         if ( !$t ) {
00494             $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) );
00495         }
00496         if ( $defaultNamespace != $t->getNamespace() || $t->isExternal() ) {
00497             // This can happen in two cases. First, if you call titlePartToKey with a title part
00498             // that looks like a namespace, but with $defaultNamespace = NS_MAIN. It would be very
00499             // difficult to handle such a case. Such cases cannot exist and are therefore treated
00500             // as invalid user input. The second case is when somebody specifies a title interwiki
00501             // prefix.
00502             $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) );
00503         }
00504 
00505         return substr( $t->getDbKey(), 0, -1 );
00506     }
00507 
00513     public function keyPartToTitle( $keyPart ) {
00514         return substr( $this->keyToTitle( $keyPart . 'x' ), 0, -1 );
00515     }
00516 
00524     public function getDirectionDescription( $p = '', $extraDirText = '' ) {
00525         return array(
00526             "In which direction to enumerate{$extraDirText}",
00527             " newer          - List oldest first. Note: {$p}start has to be before {$p}end.",
00528             " older          - List newest first (default). Note: {$p}start has to be later than {$p}end.",
00529         );
00530     }
00531 
00537     public function prepareUrlQuerySearchString( $query = null, $protocol = null ) {
00538         $db = $this->getDb();
00539         if ( !is_null( $query ) || $query != '' ) {
00540             if ( is_null( $protocol ) ) {
00541                 $protocol = 'http://';
00542             }
00543 
00544             $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
00545             if ( !$likeQuery ) {
00546                 $this->dieUsage( 'Invalid query', 'bad_query' );
00547             }
00548 
00549             $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
00550 
00551             return 'el_index ' . $db->buildLike( $likeQuery );
00552         } elseif ( !is_null( $protocol ) ) {
00553             return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() );
00554         }
00555 
00556         return null;
00557     }
00558 
00566     public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
00567         $this->addTables( 'ipblocks' );
00568         $this->addJoinConds( array(
00569             'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ),
00570         ) );
00571 
00572         $this->addFields( 'ipb_deleted' );
00573 
00574         if ( $showBlockInfo ) {
00575             $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry' ) );
00576         }
00577 
00578         // Don't show hidden names
00579         if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
00580             $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
00581         }
00582     }
00583 
00588     public function validateSha1Hash( $hash ) {
00589         return preg_match( '/^[a-f0-9]{40}$/', $hash );
00590     }
00591 
00596     public function validateSha1Base36Hash( $hash ) {
00597         return preg_match( '/^[a-z0-9]{31}$/', $hash );
00598     }
00599 
00603     public function getPossibleErrors() {
00604         $errors = parent::getPossibleErrors();
00605         $errors = array_merge( $errors, array(
00606             array( 'invalidtitle', 'title' ),
00607             array( 'invalidtitle', 'key' ),
00608         ) );
00609 
00610         return $errors;
00611     }
00612 
00618     public function userCanSeeRevDel() {
00619         return $this->getUser()->isAllowedAny( 'deletedhistory', 'deletedtext', 'suppressrevision' );
00620     }
00621 }
00622 
00626 abstract class ApiQueryGeneratorBase extends ApiQueryBase {
00627 
00628     private $mGeneratorPageSet = null;
00629 
00637     public function setGeneratorMode( ApiPageSet $generatorPageSet ) {
00638         if ( $generatorPageSet === null ) {
00639             ApiBase::dieDebug( __METHOD__, 'Required parameter missing - $generatorPageSet' );
00640         }
00641         $this->mGeneratorPageSet = $generatorPageSet;
00642     }
00643 
00649     protected function getPageSet() {
00650         if ( $this->mGeneratorPageSet !== null ) {
00651             return $this->mGeneratorPageSet;
00652         }
00653 
00654         return parent::getPageSet();
00655     }
00656 
00662     public function encodeParamName( $paramName ) {
00663         if ( $this->mGeneratorPageSet !== null ) {
00664             return 'g' . parent::encodeParamName( $paramName );
00665         } else {
00666             return parent::encodeParamName( $paramName );
00667         }
00668     }
00669 
00676     protected function setContinueEnumParameter( $paramName, $paramValue ) {
00677         // If this is a generator and query->setGeneratorContinue() returns false, treat as before
00678         if ( $this->mGeneratorPageSet === null
00679             || !$this->getQuery()->setGeneratorContinue( $this, $paramName, $paramValue )
00680         ) {
00681             parent::setContinueEnumParameter( $paramName, $paramValue );
00682         }
00683     }
00684 
00690     abstract public function executeGenerator( $resultPageSet );
00691 }