summaryrefslogtreecommitdiff
path: root/includes/api/ApiQueryBase.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/api/ApiQueryBase.php')
-rw-r--r--includes/api/ApiQueryBase.php490
1 files changed, 291 insertions, 199 deletions
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 8668e04b..65e10ab7 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -36,17 +36,22 @@ abstract class ApiQueryBase extends ApiBase {
private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
/**
- * @param $query ApiBase
- * @param $moduleName string
- * @param $paramPrefix string
+ * @param ApiQuery $queryModule
+ * @param string $moduleName
+ * @param string $paramPrefix
*/
- public function __construct( ApiBase $query, $moduleName, $paramPrefix = '' ) {
- parent::__construct( $query->getMain(), $moduleName, $paramPrefix );
- $this->mQueryModule = $query;
+ public function __construct( ApiQuery $queryModule, $moduleName, $paramPrefix = '' ) {
+ parent::__construct( $queryModule->getMain(), $moduleName, $paramPrefix );
+ $this->mQueryModule = $queryModule;
$this->mDb = null;
$this->resetQueryParams();
}
+ /************************************************************************//**
+ * @name Methods to implement
+ * @{
+ */
+
/**
* Get the cache mode for the data generated by this module. Override
* this in the module subclass. For possible return values and other
@@ -55,7 +60,7 @@ abstract class ApiQueryBase extends ApiBase {
* Public caching will only be allowed if *all* the modules that supply
* data for a given request return a cache mode of public.
*
- * @param $params
+ * @param array $params
* @return string
*/
public function getCacheMode( $params ) {
@@ -63,6 +68,68 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
+ * Override this method to request extra fields from the pageSet
+ * using $pageSet->requestField('fieldName')
+ * @param ApiPageSet $pageSet
+ */
+ public function requestExtraData( $pageSet ) {
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Data access
+ * @{
+ */
+
+ /**
+ * Get the main Query module
+ * @return ApiQuery
+ */
+ public function getQuery() {
+ return $this->mQueryModule;
+ }
+
+ /**
+ * Get the Query database connection (read-only)
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ if ( is_null( $this->mDb ) ) {
+ $this->mDb = $this->getQuery()->getDB();
+ }
+
+ return $this->mDb;
+ }
+
+ /**
+ * Selects the query database connection with the given name.
+ * See ApiQuery::getNamedDB() for more information
+ * @param string $name Name to assign to the database connection
+ * @param int $db One of the DB_* constants
+ * @param array $groups Query groups
+ * @return DatabaseBase
+ */
+ public function selectNamedDB( $name, $db, $groups ) {
+ $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups );
+ }
+
+ /**
+ * Get the PageSet object to work on
+ * @return ApiPageSet
+ */
+ protected function getPageSet() {
+ return $this->getQuery()->getPageSet();
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Querying
+ * @{
+ */
+
+ /**
* Blank the internal arrays with query parameters
*/
protected function resetQueryParams() {
@@ -75,8 +142,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of tables to the internal array
- * @param $tables mixed Table name or array of table names
- * @param $alias mixed Table alias, or null for no alias. Cannot be
+ * @param string|string[] $tables Table name or array of table names
+ * @param string|null $alias Table alias, or null for no alias. Cannot be
* used with multiple tables
*/
protected function addTables( $tables, $alias = null ) {
@@ -101,7 +168,7 @@ abstract class ApiQueryBase extends ApiBase {
* conditions) e.g. array('page' => array('LEFT JOIN',
* 'page_id=rev_page')) . conditions may be a string or an
* addWhere()-style array
- * @param $join_conds array JOIN conditions
+ * @param array $join_conds JOIN conditions
*/
protected function addJoinConds( $join_conds ) {
if ( !is_array( $join_conds ) ) {
@@ -131,8 +198,10 @@ abstract class ApiQueryBase extends ApiBase {
protected function addFieldsIf( $value, $condition ) {
if ( $condition ) {
$this->addFields( $value );
+
return true;
}
+
return false;
}
@@ -145,7 +214,7 @@ abstract class ApiQueryBase extends ApiBase {
*
* For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates
* to "foo=bar AND baz='3' AND bla='foo'"
- * @param $value mixed String or array
+ * @param string|array $value
*/
protected function addWhere( $value ) {
if ( is_array( $value ) ) {
@@ -161,15 +230,17 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addWhere(), but add the WHERE clauses only if a condition is met
- * @param $value mixed See addWhere()
+ * @param string|array $value
* @param bool $condition If false, do nothing
* @return bool $condition
*/
protected function addWhereIf( $value, $condition ) {
if ( $condition ) {
$this->addWhere( $value );
+
return true;
}
+
return false;
}
@@ -215,7 +286,9 @@ abstract class ApiQueryBase extends ApiBase {
if ( $sort ) {
$order = $field . ( $isDirNewer ? '' : ' DESC' );
// Append ORDER BY
- $optionOrderBy = isset( $this->options['ORDER BY'] ) ? (array)$this->options['ORDER BY'] : array();
+ $optionOrderBy = isset( $this->options['ORDER BY'] )
+ ? (array)$this->options['ORDER BY']
+ : array();
$optionOrderBy[] = $order;
$this->addOption( 'ORDER BY', $optionOrderBy );
}
@@ -225,11 +298,11 @@ abstract class ApiQueryBase extends ApiBase {
* Add a WHERE clause corresponding to a range, similar to addWhereRange,
* but converts $start and $end to database timestamps.
* @see addWhereRange
- * @param $field
- * @param $dir
- * @param $start
- * @param $end
- * @param $sort bool
+ * @param string $field
+ * @param string $dir
+ * @param string $start
+ * @param string $end
+ * @param bool $sort
*/
protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) {
$db = $this->getDb();
@@ -256,16 +329,37 @@ abstract class ApiQueryBase extends ApiBase {
* @param string $method Function the query should be attributed to.
* You should usually use __METHOD__ here
* @param array $extraQuery Query data to add but not store in the object
- * Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... )
+ * Format is array(
+ * 'tables' => ...,
+ * 'fields' => ...,
+ * 'where' => ...,
+ * 'options' => ...,
+ * 'join_conds' => ...
+ * )
* @return ResultWrapper
*/
protected function select( $method, $extraQuery = array() ) {
- $tables = array_merge( $this->tables, isset( $extraQuery['tables'] ) ? (array)$extraQuery['tables'] : array() );
- $fields = array_merge( $this->fields, isset( $extraQuery['fields'] ) ? (array)$extraQuery['fields'] : array() );
- $where = array_merge( $this->where, isset( $extraQuery['where'] ) ? (array)$extraQuery['where'] : array() );
- $options = array_merge( $this->options, isset( $extraQuery['options'] ) ? (array)$extraQuery['options'] : array() );
- $join_conds = array_merge( $this->join_conds, isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array() );
+ $tables = array_merge(
+ $this->tables,
+ isset( $extraQuery['tables'] ) ? (array)$extraQuery['tables'] : array()
+ );
+ $fields = array_merge(
+ $this->fields,
+ isset( $extraQuery['fields'] ) ? (array)$extraQuery['fields'] : array()
+ );
+ $where = array_merge(
+ $this->where,
+ isset( $extraQuery['where'] ) ? (array)$extraQuery['where'] : array()
+ );
+ $options = array_merge(
+ $this->options,
+ isset( $extraQuery['options'] ) ? (array)$extraQuery['options'] : array()
+ );
+ $join_conds = array_merge(
+ $this->join_conds,
+ isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array()
+ );
// getDB has its own profileDBIn/Out calls
$db = $this->getDB();
@@ -278,28 +372,69 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
- * Estimate the row count for the SELECT query that would be run if we
- * called select() right now, and check if it's acceptable.
- * @return bool true if acceptable, false otherwise
+ * @param string $query
+ * @param string $protocol
+ * @return null|string
*/
- protected function checkRowCount() {
- $db = $this->getDB();
- $this->profileDBIn();
- $rowcount = $db->estimateRowCount( $this->tables, $this->fields, $this->where, __METHOD__, $this->options );
- $this->profileDBOut();
+ public function prepareUrlQuerySearchString( $query = null, $protocol = null ) {
+ $db = $this->getDb();
+ if ( !is_null( $query ) || $query != '' ) {
+ if ( is_null( $protocol ) ) {
+ $protocol = 'http://';
+ }
- global $wgAPIMaxDBRows;
- if ( $rowcount > $wgAPIMaxDBRows ) {
- return false;
+ $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
+ if ( !$likeQuery ) {
+ $this->dieUsage( 'Invalid query', 'bad_query' );
+ }
+
+ $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
+
+ return 'el_index ' . $db->buildLike( $likeQuery );
+ } elseif ( !is_null( $protocol ) ) {
+ return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() );
+ }
+
+ return null;
+ }
+
+ /**
+ * Filters hidden users (where the user doesn't have the right to view them)
+ * Also adds relevant block information
+ *
+ * @param bool $showBlockInfo
+ * @return void
+ */
+ public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
+ $this->addTables( 'ipblocks' );
+ $this->addJoinConds( array(
+ 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ),
+ ) );
+
+ $this->addFields( 'ipb_deleted' );
+
+ if ( $showBlockInfo ) {
+ $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry', 'ipb_timestamp' ) );
+ }
+
+ // Don't show hidden names
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
+ $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
}
- return true;
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Utility methods
+ * @{
+ */
+
/**
* Add information (title and namespace) about a Title object to a
* result array
* @param array $arr Result array à la ApiResult
- * @param $title Title
+ * @param Title $title
* @param string $prefix Module prefix
*/
public static function addTitleInfo( &$arr, $title, $prefix = '' ) {
@@ -308,22 +443,6 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
- * Override this method to request extra fields from the pageSet
- * using $pageSet->requestField('fieldName')
- * @param $pageSet ApiPageSet
- */
- public function requestExtraData( $pageSet ) {
- }
-
- /**
- * Get the main Query module
- * @return ApiQuery
- */
- public function getQuery() {
- return $this->mQueryModule;
- }
-
- /**
* Add a sub-element under the page element with the given page ID
* @param int $pageId Page ID
* @param array $data Data array à la ApiResult
@@ -332,6 +451,7 @@ abstract class ApiQueryBase extends ApiBase {
protected function addPageSubItems( $pageId, $data ) {
$result = $this->getResult();
$result->setIndexedTagName( $data, $this->getModulePrefix() );
+
return $result->addValue( array( 'query', 'pages', intval( $pageId ) ),
$this->getModuleName(),
$data );
@@ -356,61 +476,134 @@ abstract class ApiQueryBase extends ApiBase {
return false;
}
$result->setIndexedTagName_internal( array( 'query', 'pages', $pageId,
- $this->getModuleName() ), $elemname );
+ $this->getModuleName() ), $elemname );
+
return true;
}
/**
* Set a query-continue value
* @param string $paramName Parameter name
- * @param string $paramValue Parameter value
+ * @param string|array $paramValue Parameter value
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
- $paramName = $this->encodeParamName( $paramName );
- $msg = array( $paramName => $paramValue );
- $result = $this->getResult();
- $result->disableSizeCheck();
- $result->addValue( 'query-continue', $this->getModuleName(), $msg, ApiResult::ADD_ON_TOP );
- $result->enableSizeCheck();
+ $this->getResult()->setContinueParam( $this, $paramName, $paramValue );
}
/**
- * Get the Query database connection (read-only)
- * @return DatabaseBase
+ * Convert an input title or title prefix into a dbkey.
+ *
+ * $namespace should always be specified in order to handle per-namespace
+ * capitalization settings.
+ *
+ * @param string $titlePart Title part
+ * @param int $defaultNamespace Namespace of the title
+ * @return string DBkey (no namespace prefix)
*/
- protected function getDB() {
- if ( is_null( $this->mDb ) ) {
- $this->mDb = $this->getQuery()->getDB();
+ public function titlePartToKey( $titlePart, $namespace = NS_MAIN ) {
+ $t = Title::makeTitleSafe( $namespace, $titlePart . 'x' );
+ if ( !$t ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) );
}
- return $this->mDb;
+ if ( $namespace != $t->getNamespace() || $t->isExternal() ) {
+ // This can happen in two cases. First, if you call titlePartToKey with a title part
+ // that looks like a namespace, but with $defaultNamespace = NS_MAIN. It would be very
+ // difficult to handle such a case. Such cases cannot exist and are therefore treated
+ // as invalid user input. The second case is when somebody specifies a title interwiki
+ // prefix.
+ $this->dieUsageMsg( array( 'invalidtitle', $titlePart ) );
+ }
+
+ return substr( $t->getDbKey(), 0, -1 );
}
/**
- * Selects the query database connection with the given name.
- * See ApiQuery::getNamedDB() for more information
- * @param string $name Name to assign to the database connection
- * @param int $db One of the DB_* constants
- * @param array $groups Query groups
- * @return DatabaseBase
+ * Gets the personalised direction parameter description
+ *
+ * @param string $p ModulePrefix
+ * @param string $extraDirText Any extra text to be appended on the description
+ * @return array
*/
- public function selectNamedDB( $name, $db, $groups ) {
- $this->mDb = $this->getQuery()->getNamedDB( $name, $db, $groups );
+ public function getDirectionDescription( $p = '', $extraDirText = '' ) {
+ return array(
+ "In which direction to enumerate{$extraDirText}",
+ " newer - List oldest first. Note: {$p}start has to be before {$p}end.",
+ " older - List newest first (default). Note: {$p}start has to be later than {$p}end.",
+ );
}
/**
- * Get the PageSet object to work on
- * @return ApiPageSet
+ * @param string $hash
+ * @return bool
*/
- protected function getPageSet() {
- return $this->getQuery()->getPageSet();
+ public function validateSha1Hash( $hash ) {
+ return preg_match( '/^[a-f0-9]{40}$/', $hash );
+ }
+
+ /**
+ * @param string $hash
+ * @return bool
+ */
+ public function validateSha1Base36Hash( $hash ) {
+ return preg_match( '/^[a-z0-9]{31}$/', $hash );
+ }
+
+ /**
+ * Check whether the current user has permission to view revision-deleted
+ * fields.
+ * @return bool
+ */
+ public function userCanSeeRevDel() {
+ return $this->getUser()->isAllowedAny(
+ 'deletedhistory',
+ 'deletedtext',
+ 'suppressrevision',
+ 'viewsuppressed'
+ );
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Deprecated
+ * @{
+ */
+
+ /**
+ * Estimate the row count for the SELECT query that would be run if we
+ * called select() right now, and check if it's acceptable.
+ * @deprecated since 1.24
+ * @return bool True if acceptable, false otherwise
+ */
+ protected function checkRowCount() {
+ wfDeprecated( __METHOD__, '1.24' );
+ $db = $this->getDB();
+ $this->profileDBIn();
+ $rowcount = $db->estimateRowCount(
+ $this->tables,
+ $this->fields,
+ $this->where,
+ __METHOD__,
+ $this->options
+ );
+ $this->profileDBOut();
+
+ if ( $rowcount > $this->getConfig()->get( 'APIMaxDBRows' ) ) {
+ return false;
+ }
+
+ return true;
}
/**
* Convert a title to a DB key
+ * @deprecated since 1.24, past uses of this were always incorrect and should
+ * have used self::titlePartToKey() instead
* @param string $title Page title with spaces
* @return string Page title with underscores
*/
public function titleToKey( $title ) {
+ wfDeprecated( __METHOD__, '1.24' );
// Don't throw an error if we got an empty string
if ( trim( $title ) == '' ) {
return '';
@@ -419,15 +612,18 @@ abstract class ApiQueryBase extends ApiBase {
if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
+
return $t->getPrefixedDBkey();
}
/**
* The inverse of titleToKey()
+ * @deprecated since 1.24, unused and probably never needed
* @param string $key Page title with underscores
* @return string Page title with spaces
*/
public function keyToTitle( $key ) {
+ wfDeprecated( __METHOD__, '1.24' );
// Don't throw an error if we got an empty string
if ( trim( $key ) == '' ) {
return '';
@@ -437,124 +633,22 @@ abstract class ApiQueryBase extends ApiBase {
if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $key ) );
}
- return $t->getPrefixedText();
- }
- /**
- * An alternative to titleToKey() that doesn't trim trailing spaces
- * @param string $titlePart Title part with spaces
- * @return string Title part with underscores
- */
- public function titlePartToKey( $titlePart ) {
- return substr( $this->titleToKey( $titlePart . 'x' ), 0, - 1 );
+ return $t->getPrefixedText();
}
/**
- * An alternative to keyToTitle() that doesn't trim trailing spaces
- * @param string $keyPart Key part with spaces
+ * Inverse of titlePartToKey()
+ * @deprecated since 1.24, unused and probably never needed
+ * @param string $keyPart DBkey, with prefix
* @return string Key part with underscores
*/
public function keyPartToTitle( $keyPart ) {
- return substr( $this->keyToTitle( $keyPart . 'x' ), 0, - 1 );
+ wfDeprecated( __METHOD__, '1.24' );
+ return substr( $this->keyToTitle( $keyPart . 'x' ), 0, -1 );
}
- /**
- * Gets the personalised direction parameter description
- *
- * @param string $p ModulePrefix
- * @param string $extraDirText Any extra text to be appended on the description
- * @return array
- */
- public function getDirectionDescription( $p = '', $extraDirText = '' ) {
- return array(
- "In which direction to enumerate{$extraDirText}",
- " newer - List oldest first. Note: {$p}start has to be before {$p}end.",
- " older - List newest first (default). Note: {$p}start has to be later than {$p}end.",
- );
- }
-
- /**
- * @param $query String
- * @param $protocol String
- * @return null|string
- */
- public function prepareUrlQuerySearchString( $query = null, $protocol = null ) {
- $db = $this->getDb();
- if ( !is_null( $query ) || $query != '' ) {
- if ( is_null( $protocol ) ) {
- $protocol = 'http://';
- }
-
- $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
- if ( !$likeQuery ) {
- $this->dieUsage( 'Invalid query', 'bad_query' );
- }
-
- $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
- return 'el_index ' . $db->buildLike( $likeQuery );
- } elseif ( !is_null( $protocol ) ) {
- return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() );
- }
-
- return null;
- }
-
- /**
- * Filters hidden users (where the user doesn't have the right to view them)
- * Also adds relevant block information
- *
- * @param bool $showBlockInfo
- * @return void
- */
- public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
- $userCanViewHiddenUsers = $this->getUser()->isAllowed( 'hideuser' );
-
- if ( $showBlockInfo || !$userCanViewHiddenUsers ) {
- $this->addTables( 'ipblocks' );
- $this->addJoinConds( array(
- 'ipblocks' => array( 'LEFT JOIN', 'ipb_user=user_id' ),
- ) );
-
- $this->addFields( 'ipb_deleted' );
-
- if ( $showBlockInfo ) {
- $this->addFields( array( 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_reason', 'ipb_expiry' ) );
- }
-
- // Don't show hidden names
- if ( !$userCanViewHiddenUsers ) {
- $this->addWhere( 'ipb_deleted = 0 OR ipb_deleted IS NULL' );
- }
- }
- }
-
- /**
- * @param $hash string
- * @return bool
- */
- public function validateSha1Hash( $hash ) {
- return preg_match( '/^[a-f0-9]{40}$/', $hash );
- }
-
- /**
- * @param $hash string
- * @return bool
- */
- public function validateSha1Base36Hash( $hash ) {
- return preg_match( '/^[a-z0-9]{31}$/', $hash );
- }
-
- /**
- * @return array
- */
- public function getPossibleErrors() {
- $errors = parent::getPossibleErrors();
- $errors = array_merge( $errors, array(
- array( 'invalidtitle', 'title' ),
- array( 'invalidtitle', 'key' ),
- ) );
- return $errors;
- }
+ /**@}*/
}
/**
@@ -568,7 +662,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
* Switch this module to generator mode. By default, generator mode is
* switched off and the module acts like a normal query module.
* @since 1.21 requires pageset parameter
- * @param $generatorPageSet ApiPageSet object that the module will get
+ * @param ApiPageSet $generatorPageSet ApiPageSet object that the module will get
* by calling getPageSet() when in generator mode.
*/
public function setGeneratorMode( ApiPageSet $generatorPageSet ) {
@@ -587,11 +681,12 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
if ( $this->mGeneratorPageSet !== null ) {
return $this->mGeneratorPageSet;
}
+
return parent::getPageSet();
}
/**
- * Overrides base class to prepend 'g' to every generator parameter
+ * Overrides ApiBase to prepend 'g' to every generator parameter
* @param string $paramName Parameter name
* @return string Prefixed parameter name
*/
@@ -604,24 +699,21 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
}
/**
- * Overrides base in case of generator & smart continue to
- * notify ApiQueryMain instead of adding them to the result right away.
+ * Overridden to set the generator param if in generator mode
* @param string $paramName Parameter name
- * @param string $paramValue Parameter value
+ * @param string|array $paramValue Parameter value
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
- // If this is a generator and query->setGeneratorContinue() returns false, treat as before
- if ( $this->mGeneratorPageSet === null
- || !$this->getQuery()->setGeneratorContinue( $this, $paramName, $paramValue )
- ) {
+ if ( $this->mGeneratorPageSet !== null ) {
+ $this->getResult()->setGeneratorContinueParam( $this, $paramName, $paramValue );
+ } else {
parent::setContinueEnumParameter( $paramName, $paramValue );
}
}
/**
* Execute this module as a generator
- * @param $resultPageSet ApiPageSet: All output should be appended to
- * this object
+ * @param ApiPageSet $resultPageSet All output should be appended to this object
*/
abstract public function executeGenerator( $resultPageSet );
}