MediaWiki master
ApiQueryBase.php
Go to the documentation of this file.
1<?php
32
42abstract class ApiQueryBase extends ApiBase {
44
45 private $mQueryModule, $mDb;
46
50 private $queryBuilder;
51
58 public function __construct( ApiQuery $queryModule, $moduleName, $paramPrefix = '' ) {
59 parent::__construct( $queryModule->getMain(), $moduleName, $paramPrefix );
60 $this->mQueryModule = $queryModule;
61 $this->mDb = null;
62 $this->resetQueryParams();
63 }
64
65 /***************************************************************************/
66 // region Methods to implement
81 public function getCacheMode( $params ) {
82 return 'private';
83 }
84
95 public function requestExtraData( $pageSet ) {
96 }
97
98 // endregion -- end of methods to implement
99
100 /***************************************************************************/
101 // region Data access
108 public function getQuery() {
109 return $this->mQueryModule;
110 }
111
113 public function getParent() {
114 return $this->getQuery();
115 }
116
122 protected function getDB() {
123 $this->mDb ??= $this->getQuery()->getDB();
124
125 return $this->mDb;
126 }
127
133 protected function getPageSet() {
134 return $this->getQuery()->getPageSet();
135 }
136
137 // endregion -- end of data access
138
139 /***************************************************************************/
140 // region Querying
146 protected function resetQueryParams() {
147 $this->queryBuilder = null;
148 }
149
158 protected function getQueryBuilder() {
159 $this->queryBuilder ??= $this->getDB()->newSelectQueryBuilder();
160 return $this->queryBuilder;
161 }
162
170 protected function addTables( $tables, $alias = null ) {
171 if ( is_array( $tables ) ) {
172 if ( $alias !== null ) {
173 ApiBase::dieDebug( __METHOD__, 'Multiple table aliases not supported' );
174 }
175 $this->getQueryBuilder()->rawTables( $tables );
176 } else {
177 $this->getQueryBuilder()->table( $tables, $alias );
178 }
179 }
180
189 protected function addJoinConds( $join_conds ) {
190 if ( !is_array( $join_conds ) ) {
191 ApiBase::dieDebug( __METHOD__, 'Join conditions have to be arrays' );
192 }
193 $this->getQueryBuilder()->joinConds( $join_conds );
194 }
195
200 protected function addFields( $value ) {
201 $this->getQueryBuilder()->fields( $value );
202 }
203
210 protected function addFieldsIf( $value, $condition ) {
211 if ( $condition ) {
212 $this->addFields( $value );
213
214 return true;
215 }
216
217 return false;
218 }
219
233 protected function addWhere( $value ) {
234 if ( is_array( $value ) ) {
235 // Double check: don't insert empty arrays,
236 // Database::makeList() chokes on them
237 if ( count( $value ) ) {
238 $this->getQueryBuilder()->where( $value );
239 }
240 } else {
241 $this->getQueryBuilder()->where( $value );
242 }
243 }
244
251 protected function addWhereIf( $value, $condition ) {
252 if ( $condition ) {
253 $this->addWhere( $value );
254
255 return true;
256 }
257
258 return false;
259 }
260
270 protected function addWhereFld( $field, $value ) {
271 if ( $value !== null && !( is_array( $value ) && !$value ) ) {
272 $this->getQueryBuilder()->where( [ $field => $value ] );
273 }
274 }
275
297 protected function addWhereIDsFld( $table, $field, $ids ) {
298 // Use count() to its full documented capabilities to simultaneously
299 // test for null, empty array or empty countable object
300 if ( count( $ids ) ) {
301 $ids = $this->filterIDs( [ [ $table, $field ] ], $ids );
302
303 if ( $ids === [] ) {
304 // Return nothing, no IDs are valid
305 $this->getQueryBuilder()->where( '0 = 1' );
306 } else {
307 $this->getQueryBuilder()->where( [ $field => $ids ] );
308 }
309 }
310 return count( $ids );
311 }
312
325 protected function addWhereRange( $field, $dir, $start, $end, $sort = true ) {
326 $isDirNewer = ( $dir === 'newer' );
327 $after = ( $isDirNewer ? '>=' : '<=' );
328 $before = ( $isDirNewer ? '<=' : '>=' );
329 $db = $this->getDB();
330
331 if ( $start !== null ) {
332 $this->addWhere( $db->expr( $field, $after, $start ) );
333 }
334
335 if ( $end !== null ) {
336 $this->addWhere( $db->expr( $field, $before, $end ) );
337 }
338
339 if ( $sort ) {
340 $this->getQueryBuilder()->orderBy( $field, $isDirNewer ? null : 'DESC' );
341 }
342 }
343
354 protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) {
355 $db = $this->getDB();
356 $this->addWhereRange( $field, $dir,
357 $db->timestampOrNull( $start ), $db->timestampOrNull( $end ), $sort );
358 }
359
366 protected function addOption( $name, $value = null ) {
367 $this->getQueryBuilder()->option( $name, $value );
368 }
369
387 protected function select( $method, $extraQuery = [], array &$hookData = null ) {
388 $queryBuilder = clone $this->getQueryBuilder();
389 if ( isset( $extraQuery['tables'] ) ) {
390 $queryBuilder->rawTables( (array)$extraQuery['tables'] );
391 }
392 if ( isset( $extraQuery['fields'] ) ) {
393 $queryBuilder->fields( (array)$extraQuery['fields'] );
394 }
395 if ( isset( $extraQuery['where'] ) ) {
396 $queryBuilder->where( (array)$extraQuery['where'] );
397 }
398 if ( isset( $extraQuery['options'] ) ) {
399 $queryBuilder->options( (array)$extraQuery['options'] );
400 }
401 if ( isset( $extraQuery['join_conds'] ) ) {
402 $queryBuilder->joinConds( (array)$extraQuery['join_conds'] );
403 }
404
405 if ( $hookData !== null && $this->getHookContainer()->isRegistered( 'ApiQueryBaseBeforeQuery' ) ) {
406 $info = $queryBuilder->getQueryInfo();
407 $this->getHookRunner()->onApiQueryBaseBeforeQuery(
408 $this, $info['tables'], $info['fields'], $info['conds'],
409 $info['options'], $info['join_conds'], $hookData
410 );
411 $queryBuilder = $this->getDB()->newSelectQueryBuilder()->queryInfo( $info );
412 }
413
414 $queryBuilder->caller( $method );
415 $res = $queryBuilder->fetchResultSet();
416
417 if ( $hookData !== null ) {
418 $this->getHookRunner()->onApiQueryBaseAfterQuery( $this, $res, $hookData );
419 }
420
421 return $res;
422 }
423
437 protected function processRow( $row, array &$data, array &$hookData ) {
438 return $this->getHookRunner()->onApiQueryBaseProcessRow( $this, $row, $data, $hookData );
439 }
440
441 // endregion -- end of querying
442
443 /***************************************************************************/
444 // region Utility methods
454 public static function addTitleInfo( &$arr, $title, $prefix = '' ) {
455 $arr[$prefix . 'ns'] = $title->getNamespace();
456 $arr[$prefix . 'title'] = $title->getPrefixedText();
457 }
458
465 protected function addPageSubItems( $pageId, $data ) {
466 $result = $this->getResult();
467 ApiResult::setIndexedTagName( $data, $this->getModulePrefix() );
468
469 return $result->addValue( [ 'query', 'pages', (int)$pageId ],
470 $this->getModuleName(),
471 $data );
472 }
473
482 protected function addPageSubItem( $pageId, $item, $elemname = null ) {
483 $result = $this->getResult();
484 $fit = $result->addValue( [ 'query', 'pages', $pageId,
485 $this->getModuleName() ], null, $item );
486 if ( !$fit ) {
487 return false;
488 }
489 $result->addIndexedTagName(
490 [ 'query', 'pages', $pageId, $this->getModuleName() ],
491 $elemname ?? $this->getModulePrefix()
492 );
493
494 return true;
495 }
496
502 protected function setContinueEnumParameter( $paramName, $paramValue ) {
503 $this->getContinuationManager()->addContinueParam( $this, $paramName, $paramValue );
504 }
505
516 public function titlePartToKey( $titlePart, $namespace = NS_MAIN ) {
517 $t = Title::makeTitleSafe( $namespace, $titlePart . 'x' );
518 if ( !$t || $t->hasFragment() ) {
519 // Invalid title (e.g. bad chars) or contained a '#'.
520 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] );
521 }
522 if ( $namespace != $t->getNamespace() || $t->isExternal() ) {
523 // This can happen in two cases. First, if you call titlePartToKey with a title part
524 // that looks like a namespace, but with $defaultNamespace = NS_MAIN. It would be very
525 // difficult to handle such a case. Such cases cannot exist and are therefore treated
526 // as invalid user input. The second case is when somebody specifies a title interwiki
527 // prefix.
528 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] );
529 }
530
531 return substr( $t->getDBkey(), 0, -1 );
532 }
533
542 protected function parsePrefixedTitlePart( $titlePart, $defaultNamespace = NS_MAIN ) {
543 try {
544 $titleParser = MediaWikiServices::getInstance()->getTitleParser();
545 $t = $titleParser->parseTitle( $titlePart . 'X', $defaultNamespace );
546 } catch ( MalformedTitleException $e ) {
547 $t = null;
548 }
549
550 if ( !$t || $t->hasFragment() || $t->isExternal() || $t->getDBkey() === 'X' ) {
551 // Invalid title (e.g. bad chars) or contained a '#'.
552 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] );
553 }
554
555 return new TitleValue( $t->getNamespace(), substr( $t->getDBkey(), 0, -1 ) );
556 }
557
562 public function validateSha1Hash( $hash ) {
563 return (bool)preg_match( '/^[a-f0-9]{40}$/', $hash );
564 }
565
570 public function validateSha1Base36Hash( $hash ) {
571 return (bool)preg_match( '/^[a-z0-9]{31}$/', $hash );
572 }
573
579 public function userCanSeeRevDel() {
580 return $this->getAuthority()->isAllowedAny(
581 'deletedhistory',
582 'deletedtext',
583 'deleterevision',
584 'suppressrevision',
585 'viewsuppressed'
586 );
587 }
588
599 IResultWrapper $res, $fname = __METHOD__, $fieldPrefix = 'page'
600 ) {
601 if ( !$res->numRows() ) {
602 return;
603 }
604
605 $services = MediaWikiServices::getInstance();
606 if ( !$services->getContentLanguage()->needsGenderDistinction() ) {
607 return;
608 }
609
610 $nsInfo = $services->getNamespaceInfo();
611 $namespaceField = $fieldPrefix . '_namespace';
612 $titleField = $fieldPrefix . '_title';
613
614 $usernames = [];
615 foreach ( $res as $row ) {
616 if ( $nsInfo->hasGenderDistinction( $row->$namespaceField ) ) {
617 $usernames[] = $row->$titleField;
618 }
619 }
620
621 if ( $usernames === [] ) {
622 return;
623 }
624
625 $genderCache = $services->getGenderCache();
626 $genderCache->doQuery( $usernames, $fname );
627 }
628
629 // endregion -- end of utility methods
630}
getAuthority()
getDB()
addWhere( $conds)
addFields( $fields)
getQueryBuilder()
const NS_MAIN
Definition Defines.php:64
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:62
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1529
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition ApiBase.php:537
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition ApiBase.php:1404
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1773
getMain()
Get the main module.
Definition ApiBase.php:546
getResult()
Get the result object.
Definition ApiBase.php:667
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:528
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:752
getContinuationManager()
Definition ApiBase.php:704
getHookContainer()
Get a HookContainer, for running extension hooks or for hook metadata.
Definition ApiBase.php:737
This is a base class for all Query modules.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
parsePrefixedTitlePart( $titlePart, $defaultNamespace=NS_MAIN)
Convert an input title or title prefix into a TitleValue.
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
validateSha1Base36Hash( $hash)
resetQueryParams()
Blank the internal arrays with query parameters.
getCacheMode( $params)
Get the cache mode for the data generated by this module.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
addWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, and an ORDER BY clause to sort in the right direction.
getQueryBuilder()
Get the SelectQueryBuilder.
addPageSubItems( $pageId, $data)
Add a sub-element under the page element with the given page ID.
validateSha1Hash( $hash)
addFields( $value)
Add a set of fields to select to the internal array.
addPageSubItem( $pageId, $item, $elemname=null)
Same as addPageSubItems(), but one element of $data at a time.
getParent()
Get the parent of this module.to override 1.25 ApiBase|null
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
getDB()
Get the Query database connection (read-only)
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
addWhereIDsFld( $table, $field, $ids)
Like addWhereFld for an integer list of IDs.
requestExtraData( $pageSet)
Override this method to request extra fields from the pageSet using $pageSet->requestField('fieldName...
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
getQuery()
Get the main Query module.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
getPageSet()
Get the PageSet object to work on.
titlePartToKey( $titlePart, $namespace=NS_MAIN)
Convert an input title or title prefix into a dbkey.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
__construct(ApiQuery $queryModule, $moduleName, $paramPrefix='')
userCanSeeRevDel()
Check whether the current user has permission to view revision-deleted fields.
This is the main query class.
Definition ApiQuery.php:43
Service locator for MediaWiki core services.
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:79
Build SELECT queries with a fluent interface.
getQueryInfo( $joinsName='join_conds')
Get an associative array describing the query in terms of its raw parameters to Database::select().
rawTables( $tables)
Given a table or table array as might be passed to Database::select(), append it to the existing tabl...
fetchResultSet()
Run the constructed SELECT query and return all results.
queryInfo( $info)
Set the query parameters to the given values, appending to the values which were already set.
options(array $options)
Manually set multiple options in the $options array to be passed to IReadableDatabase::select().
caller( $fname)
Set the method name to be included in an SQL comment.
joinConds(array $joinConds)
Manually append to the $join_conds array which will be passed to IReadableDatabase::select().
fields( $fields)
Add a field or an array of fields to the query.
where( $conds)
Add conditions to the query.
trait ApiQueryBlockInfoTrait
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.
numRows()
Get the number of rows in a result object.