summaryrefslogtreecommitdiff
path: root/includes/api
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2014-12-27 15:41:37 +0100
committerPierre Schmitz <pierre@archlinux.de>2014-12-31 11:43:28 +0100
commitc1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch)
tree2b38796e738dd74cb42ecd9bfd151803108386bc /includes/api
parentb88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff)
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes/api')
-rw-r--r--includes/api/ApiBase.php2267
-rw-r--r--includes/api/ApiBlock.php77
-rw-r--r--includes/api/ApiClearHasMsg.php58
-rw-r--r--includes/api/ApiComparePages.php55
-rw-r--r--includes/api/ApiCreateAccount.php106
-rw-r--r--includes/api/ApiDelete.php91
-rw-r--r--includes/api/ApiDisabled.php2
-rw-r--r--includes/api/ApiEditPage.php217
-rw-r--r--includes/api/ApiEmailUser.php43
-rw-r--r--includes/api/ApiExpandTemplates.php120
-rw-r--r--includes/api/ApiFeedContributions.php55
-rw-r--r--includes/api/ApiFeedRecentChanges.php207
-rw-r--r--includes/api/ApiFeedWatchlist.php64
-rw-r--r--includes/api/ApiFileRevert.php67
-rw-r--r--includes/api/ApiFormatBase.php146
-rw-r--r--includes/api/ApiFormatDbg.php4
-rw-r--r--includes/api/ApiFormatDump.php4
-rw-r--r--includes/api/ApiFormatFeedWrapper.php101
-rw-r--r--includes/api/ApiFormatJson.php13
-rw-r--r--includes/api/ApiFormatPhp.php3
-rw-r--r--includes/api/ApiFormatRaw.php8
-rw-r--r--includes/api/ApiFormatTxt.php4
-rw-r--r--includes/api/ApiFormatWddx.php17
-rw-r--r--includes/api/ApiFormatXml.php36
-rw-r--r--includes/api/ApiFormatYaml.php8
-rw-r--r--includes/api/ApiHelp.php32
-rw-r--r--includes/api/ApiImageRotate.php42
-rw-r--r--includes/api/ApiImport.php50
-rw-r--r--includes/api/ApiLogin.php105
-rw-r--r--includes/api/ApiLogout.php6
-rw-r--r--includes/api/ApiMain.php476
-rw-r--r--includes/api/ApiModuleManager.php176
-rw-r--r--includes/api/ApiMove.php105
-rw-r--r--includes/api/ApiOpenSearch.php34
-rw-r--r--includes/api/ApiOptions.php51
-rw-r--r--includes/api/ApiPageSet.php207
-rw-r--r--includes/api/ApiParamInfo.php93
-rw-r--r--includes/api/ApiParse.php278
-rw-r--r--includes/api/ApiPatrol.php39
-rw-r--r--includes/api/ApiProtect.php99
-rw-r--r--includes/api/ApiPurge.php95
-rw-r--r--includes/api/ApiQuery.php302
-rw-r--r--includes/api/ApiQueryAllCategories.php40
-rw-r--r--includes/api/ApiQueryAllImages.php136
-rw-r--r--includes/api/ApiQueryAllLinks.php124
-rw-r--r--includes/api/ApiQueryAllMessages.php40
-rw-r--r--includes/api/ApiQueryAllPages.php63
-rw-r--r--includes/api/ApiQueryAllUsers.php175
-rw-r--r--includes/api/ApiQueryBacklinks.php83
-rw-r--r--includes/api/ApiQueryBacklinksprop.php472
-rw-r--r--includes/api/ApiQueryBase.php490
-rw-r--r--includes/api/ApiQueryBlocks.php147
-rw-r--r--includes/api/ApiQueryCategories.php52
-rw-r--r--includes/api/ApiQueryCategoryInfo.php42
-rw-r--r--includes/api/ApiQueryCategoryMembers.php141
-rw-r--r--includes/api/ApiQueryContributors.php282
-rw-r--r--includes/api/ApiQueryDeletedrevs.php275
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php18
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php44
-rw-r--r--includes/api/ApiQueryExternalLinks.php25
-rw-r--r--includes/api/ApiQueryFileRepoInfo.php18
-rw-r--r--includes/api/ApiQueryFilearchive.php178
-rw-r--r--includes/api/ApiQueryIWBacklinks.php51
-rw-r--r--includes/api/ApiQueryIWLinks.php77
-rw-r--r--includes/api/ApiQueryImageInfo.php425
-rw-r--r--includes/api/ApiQueryImages.php30
-rw-r--r--includes/api/ApiQueryInfo.php166
-rw-r--r--includes/api/ApiQueryLangBacklinks.php48
-rw-r--r--includes/api/ApiQueryLangLinks.php73
-rw-r--r--includes/api/ApiQueryLinks.php30
-rw-r--r--includes/api/ApiQueryLogEvents.php289
-rw-r--r--includes/api/ApiQueryORM.php3
-rw-r--r--includes/api/ApiQueryPagePropNames.php7
-rw-r--r--includes/api/ApiQueryPageProps.php14
-rw-r--r--includes/api/ApiQueryPagesWithProp.php15
-rw-r--r--includes/api/ApiQueryPrefixSearch.php124
-rw-r--r--includes/api/ApiQueryProtectedTitles.php88
-rw-r--r--includes/api/ApiQueryQueryPage.php65
-rw-r--r--includes/api/ApiQueryRandom.php61
-rw-r--r--includes/api/ApiQueryRecentChanges.php446
-rw-r--r--includes/api/ApiQueryRevisions.php316
-rw-r--r--includes/api/ApiQuerySearch.php165
-rw-r--r--includes/api/ApiQuerySiteinfo.php340
-rw-r--r--includes/api/ApiQueryStashImageInfo.php20
-rw-r--r--includes/api/ApiQueryTags.php25
-rw-r--r--includes/api/ApiQueryTokens.php104
-rw-r--r--includes/api/ApiQueryUserContributions.php262
-rw-r--r--includes/api/ApiQueryUserInfo.php114
-rw-r--r--includes/api/ApiQueryUsers.php117
-rw-r--r--includes/api/ApiQueryWatchlist.php410
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php41
-rw-r--r--includes/api/ApiResult.php314
-rw-r--r--includes/api/ApiRevisionDelete.php236
-rw-r--r--includes/api/ApiRollback.php126
-rw-r--r--includes/api/ApiRsd.php10
-rw-r--r--includes/api/ApiSetNotificationTimestamp.php185
-rw-r--r--includes/api/ApiTokens.php21
-rw-r--r--includes/api/ApiUnblock.php50
-rw-r--r--includes/api/ApiUndelete.php58
-rw-r--r--includes/api/ApiUpload.php186
-rw-r--r--includes/api/ApiUserrights.php51
-rw-r--r--includes/api/ApiWatch.php155
102 files changed, 8106 insertions, 6020 deletions
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index c1454e76..944e4895 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -30,36 +30,40 @@
* The class functions are divided into several areas of functionality:
*
* Module parameters: Derived classes can define getAllowedParams() to specify
- * which parameters to expect, how to parse and validate them.
+ * which parameters to expect, how to parse and validate them.
*
* Profiling: various methods to allow keeping tabs on various tasks and their
- * time costs
+ * time costs
*
* Self-documentation: code to allow the API to document its own state
*
* @ingroup API
*/
abstract class ApiBase extends ContextSource {
-
// These constants allow modules to specify exactly how to treat incoming parameters.
- const PARAM_DFLT = 0; // Default value of the parameter
- const PARAM_ISMULTI = 1; // Boolean, do we accept more than one item for this parameter (e.g.: titles)?
- const PARAM_TYPE = 2; // Can be either a string type (e.g.: 'integer') or an array of allowed values
- const PARAM_MAX = 3; // Max value allowed for a parameter. Only applies if TYPE='integer'
- const PARAM_MAX2 = 4; // Max value allowed for a parameter for bots and sysops. Only applies if TYPE='integer'
- const PARAM_MIN = 5; // Lowest value allowed for a parameter. Only applies if TYPE='integer'
- const PARAM_ALLOW_DUPLICATES = 6; // Boolean, do we allow the same value to be set more than once when ISMULTI=true
- const PARAM_DEPRECATED = 7; // Boolean, is the parameter deprecated (will show a warning)
+ // Default value of the parameter
+ const PARAM_DFLT = 0;
+ // Boolean, do we accept more than one item for this parameter (e.g.: titles)?
+ const PARAM_ISMULTI = 1;
+ // Can be either a string type (e.g.: 'integer') or an array of allowed values
+ const PARAM_TYPE = 2;
+ // Max value allowed for a parameter. Only applies if TYPE='integer'
+ const PARAM_MAX = 3;
+ // Max value allowed for a parameter for bots and sysops. Only applies if TYPE='integer'
+ const PARAM_MAX2 = 4;
+ // Lowest value allowed for a parameter. Only applies if TYPE='integer'
+ const PARAM_MIN = 5;
+ // Boolean, do we allow the same value to be set more than once when ISMULTI=true
+ const PARAM_ALLOW_DUPLICATES = 6;
+ // Boolean, is the parameter deprecated (will show a warning)
+ const PARAM_DEPRECATED = 7;
/// @since 1.17
const PARAM_REQUIRED = 8; // Boolean, is the parameter required?
/// @since 1.17
- const PARAM_RANGE_ENFORCE = 9; // Boolean, if MIN/MAX are set, enforce (die) these? Only applies if TYPE='integer' Use with extreme caution
-
- const PROP_ROOT = 'ROOT'; // Name of property group that is on the root element of the result, i.e. not part of a list
- const PROP_LIST = 'LIST'; // Boolean, is the result multiple items? Defaults to true for query modules, to false for other modules
- const PROP_TYPE = 0; // Type of the property, uses same format as PARAM_TYPE
- const PROP_NULLABLE = 1; // Boolean, can the property be not included in the result? Defaults to false
+ // Boolean, if MIN/MAX are set, enforce (die) these?
+ // Only applies if TYPE='integer' Use with extreme caution
+ const PARAM_RANGE_ENFORCE = 9;
const LIMIT_BIG1 = 500; // Fast query, std user limit
const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
@@ -73,17 +77,19 @@ abstract class ApiBase extends ContextSource {
*/
const GET_VALUES_FOR_HELP = 1;
- private $mMainModule, $mModuleName, $mModulePrefix;
+ /** @var ApiMain */
+ private $mMainModule;
+ /** @var string */
+ private $mModuleName, $mModulePrefix;
private $mSlaveDB = null;
private $mParamCache = array();
/**
- * Constructor
- * @param $mainModule ApiMain object
+ * @param ApiMain $mainModule
* @param string $moduleName Name of this module
* @param string $modulePrefix Prefix to use for parameter names
*/
- public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
+ public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
$this->mMainModule = $mainModule;
$this->mModuleName = $moduleName;
$this->mModulePrefix = $modulePrefix;
@@ -93,9 +99,11 @@ abstract class ApiBase extends ContextSource {
}
}
- /*****************************************************************************
- * ABSTRACT METHODS *
- *****************************************************************************/
+
+ /************************************************************************//**
+ * @name Methods to implement
+ * @{
+ */
/**
* Evaluates the parameters, performs the requested query, and sets up
@@ -116,432 +124,232 @@ abstract class ApiBase extends ContextSource {
abstract public function execute();
/**
- * Returns a string that identifies the version of the extending class.
- * Typically includes the class name, the svn revision, timestamp, and
- * last author. Usually done with SVN's Id keyword
- * @return string
- * @deprecated since 1.21, version string is no longer supported
+ * Get the module manager, or null if this module has no sub-modules
+ * @since 1.21
+ * @return ApiModuleManager
*/
- public function getVersion() {
- wfDeprecated( __METHOD__, '1.21' );
- return '';
+ public function getModuleManager() {
+ return null;
}
/**
- * Get the name of the module being executed by this instance
- * @return string
+ * If the module may only be used with a certain format module,
+ * it should override this method to return an instance of that formatter.
+ * A value of null means the default format will be used.
+ * @return mixed Instance of a derived class of ApiFormatBase, or null
*/
- public function getModuleName() {
- return $this->mModuleName;
+ public function getCustomPrinter() {
+ return null;
}
/**
- * Get the module manager, or null if this module has no sub-modules
- * @since 1.21
- * @return ApiModuleManager
+ * Returns the description string for this module
+ * @return string|array
*/
- public function getModuleManager() {
- return null;
+ protected function getDescription() {
+ return false;
}
/**
- * Get parameter prefix (usually two letters or an empty string).
- * @return string
+ * Returns usage examples for this module. Return false if no examples are available.
+ * @return bool|string|array
*/
- public function getModulePrefix() {
- return $this->mModulePrefix;
+ protected function getExamples() {
+ return false;
}
/**
- * Get the name of the module as shown in the profiler log
+ * @return bool|string|array Returns a false if the module has no help URL,
+ * else returns a (array of) string
+ */
+ public function getHelpUrls() {
+ return false;
+ }
+
+ /**
+ * Returns an array of allowed parameters (parameter name) => (default
+ * value) or (parameter name) => (array with PARAM_* constants as keys)
+ * Don't call this function directly: use getFinalParams() to allow
+ * hooks to modify parameters as needed.
*
- * @param $db DatabaseBase|bool
+ * Some derived classes may choose to handle an integer $flags parameter
+ * in the overriding methods. Callers of this method can pass zero or
+ * more OR-ed flags like GET_VALUES_FOR_HELP.
*
- * @return string
+ * @return array|bool
*/
- public function getModuleProfileName( $db = false ) {
- if ( $db ) {
- return 'API:' . $this->mModuleName . '-DB';
- } else {
- return 'API:' . $this->mModuleName;
- }
+ protected function getAllowedParams( /* $flags = 0 */ ) {
+ // int $flags is not declared because it causes "Strict standards"
+ // warning. Most derived classes do not implement it.
+ return false;
}
/**
- * Get the main module
- * @return ApiMain object
+ * Returns an array of parameter descriptions.
+ * Don't call this function directly: use getFinalParamDescription() to
+ * allow hooks to modify descriptions as needed.
+ * @return array|bool False on no parameter descriptions
*/
- public function getMain() {
- return $this->mMainModule;
+ protected function getParamDescription() {
+ return false;
}
/**
- * Returns true if this module is the main module ($this === $this->mMainModule),
- * false otherwise.
+ * Indicates if this module needs maxlag to be checked
* @return bool
*/
- public function isMain() {
- return $this === $this->mMainModule;
+ public function shouldCheckMaxlag() {
+ return true;
}
/**
- * Get the result object
- * @return ApiResult
+ * Indicates whether this module requires read rights
+ * @return bool
*/
- public function getResult() {
- // Main module has getResult() method overridden
- // Safety - avoid infinite loop:
- if ( $this->isMain() ) {
- ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
- }
- return $this->getMain()->getResult();
+ public function isReadMode() {
+ return true;
}
/**
- * Get the result data array (read-only)
- * @return array
+ * Indicates whether this module requires write mode
+ * @return bool
*/
- public function getResultData() {
- return $this->getResult()->getData();
+ public function isWriteMode() {
+ return false;
}
/**
- * Create a new RequestContext object to use e.g. for calls to other parts
- * the software.
- * The object will have the WebRequest and the User object set to the ones
- * used in this instance.
- *
- * @deprecated since 1.19 use getContext to get the current context
- * @return DerivativeContext
+ * Indicates whether this module must be called with a POST request
+ * @return bool
*/
- public function createContext() {
- wfDeprecated( __METHOD__, '1.19' );
- return new DerivativeContext( $this->getContext() );
+ public function mustBePosted() {
+ return $this->needsToken() !== false;
}
/**
- * Set warning section for this module. Users should monitor this
- * section to notice any changes in API. Multiple calls to this
- * function will result in the warning messages being separated by
- * newlines
- * @param string $warning Warning message
+ * Returns the token type this module requires in order to execute.
+ *
+ * Modules are strongly encouraged to use the core 'csrf' type unless they
+ * have specialized security needs. If the token type is not one of the
+ * core types, you must use the ApiQueryTokensRegisterTypes hook to
+ * register it.
+ *
+ * Returning a non-falsey value here will cause self::getFinalParams() to
+ * return a required string 'token' parameter and
+ * self::getFinalParamDescription() to ensure there is standardized
+ * documentation for it. Also, self::mustBePosted() must return true when
+ * tokens are used.
+ *
+ * In previous versions of MediaWiki, true was a valid return value.
+ * Returning true will generate errors indicating that the API module needs
+ * updating.
+ *
+ * @return string|false
*/
- public function setWarning( $warning ) {
- $result = $this->getResult();
- $data = $result->getData();
- $moduleName = $this->getModuleName();
- if ( isset( $data['warnings'][$moduleName] ) ) {
- // Don't add duplicate warnings
- $oldWarning = $data['warnings'][$moduleName]['*'];
- $warnPos = strpos( $oldWarning, $warning );
- // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
- if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
- // Check if $warning is followed by "\n" or the end of the $oldWarning
- $warnPos += strlen( $warning );
- if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
- return;
- }
- }
- // If there is a warning already, append it to the existing one
- $warning = "$oldWarning\n$warning";
- }
- $msg = array();
- ApiResult::setContent( $msg, $warning );
- $result->disableSizeCheck();
- $result->addValue( 'warnings', $moduleName,
- $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
- $result->enableSizeCheck();
+ public function needsToken() {
+ return false;
}
/**
- * If the module may only be used with a certain format module,
- * it should override this method to return an instance of that formatter.
- * A value of null means the default format will be used.
- * @return mixed instance of a derived class of ApiFormatBase, or null
+ * Fetch the salt used in the Web UI corresponding to this module.
+ *
+ * Only override this if the Web UI uses a token with a non-constant salt.
+ *
+ * @since 1.24
+ * @param array $params All supplied parameters for the module
+ * @return string|array|null
*/
- public function getCustomPrinter() {
+ protected function getWebUITokenSalt( array $params ) {
return null;
}
- /**
- * Generates help message for this module, or false if there is no description
- * @return mixed string or false
- */
- public function makeHelpMsg() {
- static $lnPrfx = "\n ";
-
- $msg = $this->getFinalDescription();
-
- if ( $msg !== false ) {
-
- if ( !is_array( $msg ) ) {
- $msg = array(
- $msg
- );
- }
- $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+ /**@}*/
- $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
-
- if ( $this->isReadMode() ) {
- $msg .= "\nThis module requires read rights";
- }
- if ( $this->isWriteMode() ) {
- $msg .= "\nThis module requires write rights";
- }
- if ( $this->mustBePosted() ) {
- $msg .= "\nThis module only accepts POST requests";
- }
- if ( $this->isReadMode() || $this->isWriteMode() ||
- $this->mustBePosted() ) {
- $msg .= "\n";
- }
-
- // Parameters
- $paramsMsg = $this->makeHelpMsgParameters();
- if ( $paramsMsg !== false ) {
- $msg .= "Parameters:\n$paramsMsg";
- }
-
- $examples = $this->getExamples();
- if ( $examples ) {
- if ( !is_array( $examples ) ) {
- $examples = array(
- $examples
- );
- }
- $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
- foreach ( $examples as $k => $v ) {
- if ( is_numeric( $k ) ) {
- $msg .= " $v\n";
- } else {
- if ( is_array( $v ) ) {
- $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
- } else {
- $msgExample = " $v";
- }
- $msgExample .= ":";
- $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n $k\n";
- }
- }
- }
- }
-
- return $msg;
- }
+ /************************************************************************//**
+ * @name Data access methods
+ * @{
+ */
/**
- * @param $item string
+ * Get the name of the module being executed by this instance
* @return string
*/
- private function indentExampleText( $item ) {
- return " " . $item;
+ public function getModuleName() {
+ return $this->mModuleName;
}
/**
- * @param string $prefix Text to split output items
- * @param string $title What is being output
- * @param $input string|array
+ * Get parameter prefix (usually two letters or an empty string).
* @return string
*/
- protected function makeHelpArrayToString( $prefix, $title, $input ) {
- if ( $input === false ) {
- return '';
- }
- if ( !is_array( $input ) ) {
- $input = array( $input );
- }
-
- if ( count( $input ) > 0 ) {
- if ( $title ) {
- $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
- } else {
- $msg = ' ';
- }
- $msg .= implode( $prefix, $input ) . "\n";
- return $msg;
- }
- return '';
+ public function getModulePrefix() {
+ return $this->mModulePrefix;
}
/**
- * Generates the parameter descriptions for this module, to be displayed in the
- * module's help.
- * @return string or false
+ * Get the main module
+ * @return ApiMain
*/
- public function makeHelpMsgParameters() {
- $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
- if ( $params ) {
-
- $paramsDescription = $this->getFinalParamDescription();
- $msg = '';
- $paramPrefix = "\n" . str_repeat( ' ', 24 );
- $descWordwrap = "\n" . str_repeat( ' ', 28 );
- foreach ( $params as $paramName => $paramSettings ) {
- $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
- if ( is_array( $desc ) ) {
- $desc = implode( $paramPrefix, $desc );
- }
-
- //handle shorthand
- if ( !is_array( $paramSettings ) ) {
- $paramSettings = array(
- self::PARAM_DFLT => $paramSettings,
- );
- }
-
- //handle missing type
- if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
- $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] ) ? $paramSettings[ApiBase::PARAM_DFLT] : null;
- if ( is_bool( $dflt ) ) {
- $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
- } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
- $paramSettings[ApiBase::PARAM_TYPE] = 'string';
- } elseif ( is_int( $dflt ) ) {
- $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
- }
- }
-
- if ( isset( $paramSettings[self::PARAM_DEPRECATED] ) && $paramSettings[self::PARAM_DEPRECATED] ) {
- $desc = "DEPRECATED! $desc";
- }
-
- if ( isset( $paramSettings[self::PARAM_REQUIRED] ) && $paramSettings[self::PARAM_REQUIRED] ) {
- $desc .= $paramPrefix . "This parameter is required";
- }
-
- $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
- if ( isset( $type ) ) {
- $hintPipeSeparated = true;
- $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
- if ( $multi ) {
- $prompt = 'Values (separate with \'|\'): ';
- } else {
- $prompt = 'One value: ';
- }
-
- if ( is_array( $type ) ) {
- $choices = array();
- $nothingPrompt = '';
- foreach ( $type as $t ) {
- if ( $t === '' ) {
- $nothingPrompt = 'Can be empty, or ';
- } else {
- $choices[] = $t;
- }
- }
- $desc .= $paramPrefix . $nothingPrompt . $prompt;
- $choicesstring = implode( ', ', $choices );
- $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
- $hintPipeSeparated = false;
- } else {
- switch ( $type ) {
- case 'namespace':
- // Special handling because namespaces are type-limited, yet they are not given
- $desc .= $paramPrefix . $prompt;
- $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
- 100, $descWordwrap );
- $hintPipeSeparated = false;
- break;
- case 'limit':
- $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
- if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
- $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
- }
- $desc .= ' allowed';
- break;
- case 'integer':
- $s = $multi ? 's' : '';
- $hasMin = isset( $paramSettings[self::PARAM_MIN] );
- $hasMax = isset( $paramSettings[self::PARAM_MAX] );
- if ( $hasMin || $hasMax ) {
- if ( !$hasMax ) {
- $intRangeStr = "The value$s must be no less than {$paramSettings[self::PARAM_MIN]}";
- } elseif ( !$hasMin ) {
- $intRangeStr = "The value$s must be no more than {$paramSettings[self::PARAM_MAX]}";
- } else {
- $intRangeStr = "The value$s must be between {$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
- }
-
- $desc .= $paramPrefix . $intRangeStr;
- }
- break;
- case 'upload':
- $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
- break;
- }
- }
-
- if ( $multi ) {
- if ( $hintPipeSeparated ) {
- $desc .= $paramPrefix . "Separate values with '|'";
- }
-
- $isArray = is_array( $type );
- if ( !$isArray
- || $isArray && count( $type ) > self::LIMIT_SML1 ) {
- $desc .= $paramPrefix . "Maximum number of values " .
- self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
- }
- }
- }
-
- $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
- if ( !is_null( $default ) && $default !== false ) {
- $desc .= $paramPrefix . "Default: $default";
- }
-
- $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
- }
- return $msg;
+ public function getMain() {
+ return $this->mMainModule;
+ }
- } else {
- return false;
- }
+ /**
+ * Returns true if this module is the main module ($this === $this->mMainModule),
+ * false otherwise.
+ * @return bool
+ */
+ public function isMain() {
+ return $this === $this->mMainModule;
}
/**
- * Returns the description string for this module
- * @return mixed string or array of strings
+ * Get the result object
+ * @return ApiResult
*/
- protected function getDescription() {
- return false;
+ public function getResult() {
+ // Main module has getResult() method overridden
+ // Safety - avoid infinite loop:
+ if ( $this->isMain() ) {
+ ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+ }
+
+ return $this->getMain()->getResult();
}
/**
- * Returns usage examples for this module. Return false if no examples are available.
- * @return bool|string|array
+ * Get the result data array (read-only)
+ * @return array
*/
- protected function getExamples() {
- return false;
+ public function getResultData() {
+ return $this->getResult()->getData();
}
/**
- * Returns an array of allowed parameters (parameter name) => (default
- * value) or (parameter name) => (array with PARAM_* constants as keys)
- * Don't call this function directly: use getFinalParams() to allow
- * hooks to modify parameters as needed.
- *
- * Some derived classes may choose to handle an integer $flags parameter
- * in the overriding methods. Callers of this method can pass zero or
- * more OR-ed flags like GET_VALUES_FOR_HELP.
- *
- * @return array|bool
+ * Gets a default slave database connection object
+ * @return DatabaseBase
*/
- protected function getAllowedParams( /* $flags = 0 */ ) {
- // int $flags is not declared because it causes "Strict standards"
- // warning. Most derived classes do not implement it.
- return false;
+ protected function getDB() {
+ if ( !isset( $this->mSlaveDB ) ) {
+ $this->profileDBIn();
+ $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
+ $this->profileDBOut();
+ }
+
+ return $this->mSlaveDB;
}
/**
- * Returns an array of parameter descriptions.
- * Don't call this function directly: use getFinalParamDescription() to
- * allow hooks to modify descriptions as needed.
- * @return array|bool False on no parameter descriptions
+ * Get final module description, after hooks have had a chance to tweak it as
+ * needed.
+ *
+ * @return array|bool False on no parameters
*/
- protected function getParamDescription() {
- return false;
+ public function getFinalDescription() {
+ $desc = $this->getDescription();
+ wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
+
+ return $desc;
}
/**
@@ -549,12 +357,21 @@ abstract class ApiBase extends ContextSource {
* tweak it as needed.
*
* @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
- * @return array|Bool False on no parameters
+ * @return array|bool False on no parameters
* @since 1.21 $flags param added
*/
public function getFinalParams( $flags = 0 ) {
$params = $this->getAllowedParams( $flags );
+
+ if ( $this->needsToken() ) {
+ $params['token'] = array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ );
+ }
+
wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
+
return $params;
}
@@ -566,67 +383,33 @@ abstract class ApiBase extends ContextSource {
*/
public function getFinalParamDescription() {
$desc = $this->getParamDescription();
- wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
- return $desc;
- }
- /**
- * Returns possible properties in the result, grouped by the value of the prop parameter
- * that shows them.
- *
- * Properties that are shown always are in a group with empty string as a key.
- * Properties that can be shown by several values of prop are included multiple times.
- * If some properties are part of a list and some are on the root object (see ApiQueryQueryPage),
- * those on the root object are under the key PROP_ROOT.
- * The array can also contain a boolean under the key PROP_LIST,
- * indicating whether the result is a list.
- *
- * Don't call this function directly: use getFinalResultProperties() to
- * allow hooks to modify descriptions as needed.
- *
- * @return array|bool False on no properties
- */
- protected function getResultProperties() {
- return false;
- }
-
- /**
- * Get final possible result properties, after hooks have had a chance to tweak it as
- * needed.
- *
- * @return array
- */
- public function getFinalResultProperties() {
- $properties = $this->getResultProperties();
- wfRunHooks( 'APIGetResultProperties', array( $this, &$properties ) );
- return $properties;
- }
-
- /**
- * Add token properties to the array used by getResultProperties,
- * based on a token functions mapping.
- */
- protected static function addTokenProperties( &$props, $tokenFunctions ) {
- foreach ( array_keys( $tokenFunctions ) as $token ) {
- $props[''][$token . 'token'] = array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
+ $tokenType = $this->needsToken();
+ if ( $tokenType ) {
+ if ( !isset( $desc['token'] ) ) {
+ $desc['token'] = array();
+ } elseif ( !is_array( $desc['token'] ) ) {
+ // We ignore a plain-string token, because it's probably an
+ // extension that is supplying the string for BC.
+ $desc['token'] = array();
+ }
+ array_unshift( $desc['token'],
+ "A '$tokenType' token retrieved from action=query&meta=tokens"
);
}
- }
- /**
- * Get final module description, after hooks have had a chance to tweak it as
- * needed.
- *
- * @return array|bool False on no parameters
- */
- public function getFinalDescription() {
- $desc = $this->getDescription();
- wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
+ wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
+
return $desc;
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Parameter handling
+ * @{
+ */
+
/**
* This method mangles parameter name based on the prefix supplied to the constructor.
* Override this method to change parameter name during runtime
@@ -643,7 +426,7 @@ abstract class ApiBase extends ContextSource {
* value - validated value from user or default. limits will not be
* parsed if $parseLimit is set to false; use this when the max
* limit is not definitive yet, e.g. when getting revisions.
- * @param $parseLimit Boolean: true by default
+ * @param bool $parseLimit True by default
* @return array
*/
public function extractRequestParams( $parseLimit = true ) {
@@ -660,26 +443,30 @@ abstract class ApiBase extends ContextSource {
}
$this->mParamCache[$parseLimit] = $results;
}
+
return $this->mParamCache[$parseLimit];
}
/**
* Get a value for the given parameter
* @param string $paramName Parameter name
- * @param bool $parseLimit see extractRequestParams()
+ * @param bool $parseLimit See extractRequestParams()
* @return mixed Parameter value
*/
protected function getParameter( $paramName, $parseLimit = true ) {
$params = $this->getFinalParams();
$paramSettings = $params[$paramName];
+
return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
}
/**
* Die if none or more than one of a certain set of parameters is set and not false.
- * @param array $params of parameter names
+ *
+ * @param array $params User provided set of parameters, as from $this->extractRequestParams()
+ * @param string $required,... Names of parameters of which exactly one must be set
*/
- public function requireOnlyOneParameter( $params ) {
+ public function requireOnlyOneParameter( $params, $required /*...*/ ) {
$required = func_get_args();
array_shift( $required );
$p = $this->getModulePrefix();
@@ -688,34 +475,24 @@ abstract class ApiBase extends ContextSource {
array( $this, "parameterNotEmpty" ) ) ), $required );
if ( count( $intersection ) > 1 ) {
- $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' );
+ $this->dieUsage(
+ "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
+ 'invalidparammix' );
} elseif ( count( $intersection ) == 0 ) {
- $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', 'missingparam' );
+ $this->dieUsage(
+ "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
+ 'missingparam'
+ );
}
}
/**
- * Generates the possible errors requireOnlyOneParameter() can die with
- *
- * @param $params array
- * @return array
- */
- public function getRequireOnlyOneParameterErrorMessages( $params ) {
- $p = $this->getModulePrefix();
- $params = implode( ", {$p}", $params );
-
- return array(
- array( 'code' => "{$p}missingparam", 'info' => "One of the parameters {$p}{$params} is required" ),
- array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
- );
- }
-
- /**
* Die if more than one of a certain set of parameters is set and not false.
*
- * @param $params array
+ * @param array $params User provided set of parameters, as from $this->extractRequestParams()
+ * @param string $required,... Names of parameters of which at most one must be set
*/
- public function requireMaxOneParameter( $params ) {
+ public function requireMaxOneParameter( $params, $required /*...*/ ) {
$required = func_get_args();
array_shift( $required );
$p = $this->getModulePrefix();
@@ -724,27 +501,51 @@ abstract class ApiBase extends ContextSource {
array( $this, "parameterNotEmpty" ) ) ), $required );
if ( count( $intersection ) > 1 ) {
- $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' );
+ $this->dieUsage(
+ "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
+ 'invalidparammix'
+ );
}
}
/**
- * Generates the possible error requireMaxOneParameter() can die with
+ * Die if none of a certain set of parameters is set and not false.
*
- * @param $params array
- * @return array
+ * @since 1.23
+ * @param array $params User provided set of parameters, as from $this->extractRequestParams()
+ * @param string $required,... Names of parameters of which at least one must be set
*/
- public function getRequireMaxOneParameterErrorMessages( $params ) {
+ public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
+ $required = func_get_args();
+ array_shift( $required );
$p = $this->getModulePrefix();
- $params = implode( ", {$p}", $params );
- return array(
- array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
+ $intersection = array_intersect(
+ array_keys( array_filter( $params, array( $this, "parameterNotEmpty" ) ) ),
+ $required
);
+
+ if ( count( $intersection ) == 0 ) {
+ $this->dieUsage( "At least one of the parameters {$p}" .
+ implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
+ }
+ }
+
+ /**
+ * Callback function used in requireOnlyOneParameter to check whether required parameters are set
+ *
+ * @param object $x Parameter to check is not null/false
+ * @return bool
+ */
+ private function parameterNotEmpty( $x ) {
+ return !is_null( $x ) && $x !== false;
}
/**
- * @param $params array
+ * Get a WikiPage object from a title or pageid param, if possible.
+ * Can die, if no param is set or if the title or page id is not valid.
+ *
+ * @param array $params
* @param bool|string $load Whether load the object's state from the database:
* - false: don't load (if the pageid is given, it will still be loaded)
* - 'fromdb': load from a slave database
@@ -781,44 +582,11 @@ abstract class ApiBase extends ContextSource {
}
/**
- * @return array
- */
- public function getTitleOrPageIdErrorMessage() {
- return array_merge(
- $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
- array(
- array( 'invalidtitle', 'title' ),
- array( 'nosuchpageid', 'pageid' ),
- )
- );
- }
-
- /**
- * Callback function used in requireOnlyOneParameter to check whether required parameters are set
- *
- * @param $x object Parameter to check is not null/false
- * @return bool
- */
- private function parameterNotEmpty( $x ) {
- return !is_null( $x ) && $x !== false;
- }
-
- /**
- * @deprecated since 1.17 use MWNamespace::getValidNamespaces()
- *
- * @return array
- */
- public static function getValidNamespaces() {
- wfDeprecated( __METHOD__, '1.17' );
- return MWNamespace::getValidNamespaces();
- }
-
- /**
* Return true if we're to watch the page, false if not, null if no change.
* @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
- * @param $titleObj Title the page under consideration
+ * @param Title $titleObj The page under consideration
* @param string $userOption The user option to consider when $watchlist=preferences.
- * If not set will magically default to either watchdefault or watchcreations
+ * If not set will use watchdefault always and watchcreations if $titleObj doesn't exist.
* @return bool
*/
protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
@@ -837,11 +605,12 @@ abstract class ApiBase extends ContextSource {
if ( $userWatching ) {
return true;
}
- # If no user option was passed, use watchdefault or watchcreations
+ # If no user option was passed, use watchdefault and watchcreations
if ( is_null( $userOption ) ) {
- $userOption = $titleObj->exists()
- ? 'watchdefault' : 'watchcreations';
+ return $this->getUser()->getBoolOption( 'watchdefault' ) ||
+ $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
}
+
# Watch the article based on the user preference
return $this->getUser()->getBoolOption( $userOption );
@@ -854,27 +623,12 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Set a watch (or unwatch) based the based on a watchlist parameter.
- * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
- * @param $titleObj Title the article's title to change
- * @param string $userOption The user option to consider when $watch=preferences
- */
- protected function setWatch( $watch, $titleObj, $userOption = null ) {
- $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
- if ( $value === null ) {
- return;
- }
-
- WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
- }
-
- /**
* Using the settings determine the value for the given parameter
*
- * @param string $paramName parameter name
- * @param array|mixed $paramSettings default value or an array of settings
+ * @param string $paramName Parameter name
+ * @param array|mixed $paramSettings Default value or an array of settings
* using PARAM_* constants.
- * @param $parseLimit Boolean: parse limit?
+ * @param bool $parseLimit Parse limit?
* @return mixed Parameter value
*/
protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
@@ -889,12 +643,24 @@ abstract class ApiBase extends ContextSource {
$deprecated = false;
$required = false;
} else {
- $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
- $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false;
- $type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
- $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] ) ? $paramSettings[self::PARAM_ALLOW_DUPLICATES] : false;
- $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ? $paramSettings[self::PARAM_DEPRECATED] : false;
- $required = isset( $paramSettings[self::PARAM_REQUIRED] ) ? $paramSettings[self::PARAM_REQUIRED] : false;
+ $default = isset( $paramSettings[self::PARAM_DFLT] )
+ ? $paramSettings[self::PARAM_DFLT]
+ : null;
+ $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
+ ? $paramSettings[self::PARAM_ISMULTI]
+ : false;
+ $type = isset( $paramSettings[self::PARAM_TYPE] )
+ ? $paramSettings[self::PARAM_TYPE]
+ : null;
+ $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
+ ? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
+ : false;
+ $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
+ ? $paramSettings[self::PARAM_DEPRECATED]
+ : false;
+ $required = isset( $paramSettings[self::PARAM_REQUIRED] )
+ ? $paramSettings[self::PARAM_REQUIRED]
+ : false;
// When type is not given, and no choices, the type is the same as $default
if ( !isset( $type ) ) {
@@ -909,14 +675,21 @@ abstract class ApiBase extends ContextSource {
if ( $type == 'boolean' ) {
if ( isset( $default ) && $default !== false ) {
// Having a default value of anything other than 'false' is not allowed
- ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'. Boolean parameters must default to false." );
+ ApiBase::dieDebug(
+ __METHOD__,
+ "Boolean param $encParamName's default is set to '$default'. " .
+ "Boolean parameters must default to false."
+ );
}
$value = $this->getMain()->getCheck( $encParamName );
} elseif ( $type == 'upload' ) {
if ( isset( $default ) ) {
// Having a default value is not allowed
- ApiBase::dieDebug( __METHOD__, "File upload param $encParamName's default is set to '$default'. File upload parameters may not have a default." );
+ ApiBase::dieDebug(
+ __METHOD__,
+ "File upload param $encParamName's default is set to " .
+ "'$default'. File upload parameters may not have a default." );
}
if ( $multi ) {
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
@@ -930,8 +703,8 @@ abstract class ApiBase extends ContextSource {
if ( $value !== null ) {
$this->dieUsage(
"File upload param $encParamName is not a file upload; " .
- "be sure to use multipart/form-data for your POST and include " .
- "a filename in the Content-Disposition header.",
+ "be sure to use multipart/form-data for your POST and include " .
+ "a filename in the Content-Disposition header.",
"badupload_{$encParamName}"
);
}
@@ -942,10 +715,18 @@ abstract class ApiBase extends ContextSource {
if ( isset( $value ) && $type == 'namespace' ) {
$type = MWNamespace::getValidNamespaces();
}
+ if ( isset( $value ) && $type == 'submodule' ) {
+ $type = $this->getModuleManager()->getNames( $paramName );
+ }
}
if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
- $value = $this->parseMultiValue( $encParamName, $value, $multi, is_array( $type ) ? $type : null );
+ $value = $this->parseMultiValue(
+ $encParamName,
+ $value,
+ $multi,
+ is_array( $type ) ? $type : null
+ );
}
// More validation only when choices were not given
@@ -964,7 +745,7 @@ abstract class ApiBase extends ContextSource {
$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
$max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
$enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
- ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
+ ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
if ( is_array( $value ) ) {
$value = array_map( 'intval', $value );
@@ -985,19 +766,32 @@ abstract class ApiBase extends ContextSource {
// Don't do any validation whatsoever
break;
}
- if ( !isset( $paramSettings[self::PARAM_MAX] ) || !isset( $paramSettings[self::PARAM_MAX2] ) ) {
- ApiBase::dieDebug( __METHOD__, "MAX1 or MAX2 are not defined for the limit $encParamName" );
+ if ( !isset( $paramSettings[self::PARAM_MAX] )
+ || !isset( $paramSettings[self::PARAM_MAX2] )
+ ) {
+ ApiBase::dieDebug(
+ __METHOD__,
+ "MAX1 or MAX2 are not defined for the limit $encParamName"
+ );
}
if ( $multi ) {
ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
}
$min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
if ( $value == 'max' ) {
- $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self::PARAM_MAX2] : $paramSettings[self::PARAM_MAX];
+ $value = $this->getMain()->canApiHighLimits()
+ ? $paramSettings[self::PARAM_MAX2]
+ : $paramSettings[self::PARAM_MAX];
$this->getResult()->setParsedLimit( $this->getModuleName(), $value );
} else {
$value = intval( $value );
- $this->validateLimit( $paramName, $value, $min, $paramSettings[self::PARAM_MAX], $paramSettings[self::PARAM_MAX2] );
+ $this->validateLimit(
+ $paramName,
+ $value,
+ $min,
+ $paramSettings[self::PARAM_MAX],
+ $paramSettings[self::PARAM_MAX2]
+ );
}
break;
case 'boolean':
@@ -1052,25 +846,28 @@ abstract class ApiBase extends ContextSource {
*
* @param string $valueName The name of the parameter (for error
* reporting)
- * @param $value mixed The value being parsed
+ * @param mixed $value The value being parsed
* @param bool $allowMultiple Can $value contain more than one value
* separated by '|'?
- * @param $allowedValues mixed An array of values to check against. If
+ * @param string[]|null $allowedValues An array of values to check against. If
* null, all values are accepted.
- * @return mixed (allowMultiple ? an_array_of_values : a_single_value)
+ * @return string|string[] (allowMultiple ? an_array_of_values : a_single_value)
*/
protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
if ( trim( $value ) === '' && $allowMultiple ) {
return array();
}
- // This is a bit awkward, but we want to avoid calling canApiHighLimits() because it unstubs $wgUser
+ // This is a bit awkward, but we want to avoid calling canApiHighLimits()
+ // because it unstubs $wgUser
$valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
- $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits() ?
- self::LIMIT_SML2 : self::LIMIT_SML1;
+ $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
+ ? self::LIMIT_SML2
+ : self::LIMIT_SML1;
if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
- $this->setWarning( "Too many values supplied for parameter '$valueName': the limit is $sizeLimit" );
+ $this->setWarning( "Too many values supplied for parameter '$valueName': " .
+ "the limit is $sizeLimit" );
}
if ( !$allowMultiple && count( $valuesList ) != 1 ) {
@@ -1079,8 +876,13 @@ abstract class ApiBase extends ContextSource {
return $value;
}
- $possibleValues = is_array( $allowedValues ) ? "of '" . implode( "', '", $allowedValues ) . "'" : '';
- $this->dieUsage( "Only one $possibleValues is allowed for parameter '$valueName'", "multival_$valueName" );
+ $possibleValues = is_array( $allowedValues )
+ ? "of '" . implode( "', '", $allowedValues ) . "'"
+ : '';
+ $this->dieUsage(
+ "Only one $possibleValues is allowed for parameter '$valueName'",
+ "multival_$valueName"
+ );
}
if ( is_array( $allowedValues ) ) {
@@ -1092,7 +894,10 @@ abstract class ApiBase extends ContextSource {
$vals = implode( ", ", $unknown );
$this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
} else {
- $this->dieUsage( "Unrecognized value for parameter '$valueName': {$valuesList[0]}", "unknown_$valueName" );
+ $this->dieUsage(
+ "Unrecognized value for parameter '$valueName': {$valuesList[0]}",
+ "unknown_$valueName"
+ );
}
}
// Now throw them out
@@ -1110,9 +915,9 @@ abstract class ApiBase extends ContextSource {
* @param int|null $min Minimum value
* @param int|null $max Maximum value for users
* @param int $botMax Maximum value for sysops/bots
- * @param $enforceLimits Boolean Whether to enforce (die) if value is outside limits
+ * @param bool $enforceLimits Whether to enforce (die) if value is outside limits
*/
- function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
+ protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
if ( !is_null( $min ) && $value < $min ) {
$msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
@@ -1120,7 +925,8 @@ abstract class ApiBase extends ContextSource {
$value = $min;
}
- // Minimum is always validated, whereas maximum is checked only if not running in internal call mode
+ // Minimum is always validated, whereas maximum is checked only if not
+ // running in internal call mode
if ( $this->getMain()->isInternalMode() ) {
return;
}
@@ -1130,7 +936,8 @@ abstract class ApiBase extends ContextSource {
if ( !is_null( $max ) && $value > $max ) {
if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
if ( $value > $botMax ) {
- $msg = $this->encodeParamName( $paramName ) . " may not be over $botMax (set to $value) for bots or sysops";
+ $msg = $this->encodeParamName( $paramName ) .
+ " may not be over $botMax (set to $value) for bots or sysops";
$this->warnOrDie( $msg, $enforceLimits );
$value = $botMax;
}
@@ -1148,15 +955,57 @@ abstract class ApiBase extends ContextSource {
* @param string $encParamName Parameter name
* @return string Validated and normalized parameter
*/
- function validateTimestamp( $value, $encParamName ) {
+ protected function validateTimestamp( $value, $encParamName ) {
$unixTimestamp = wfTimestamp( TS_UNIX, $value );
if ( $unixTimestamp === false ) {
- $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" );
+ $this->dieUsage(
+ "Invalid value '$value' for timestamp parameter $encParamName",
+ "badtimestamp_{$encParamName}"
+ );
}
+
return wfTimestamp( TS_MW, $unixTimestamp );
}
/**
+ * Validate the supplied token.
+ *
+ * @since 1.24
+ * @param string $token Supplied token
+ * @param array $params All supplied parameters for the module
+ * @return bool
+ */
+ public final function validateToken( $token, array $params ) {
+ $tokenType = $this->needsToken();
+ $salts = ApiQueryTokens::getTokenTypeSalts();
+ if ( !isset( $salts[$tokenType] ) ) {
+ throw new MWException(
+ "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
+ 'without registering it'
+ );
+ }
+
+ if ( $this->getUser()->matchEditToken(
+ $token,
+ $salts[$tokenType],
+ $this->getRequest()
+ ) ) {
+ return true;
+ }
+
+ $webUiSalt = $this->getWebUITokenSalt( $params );
+ if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
+ $token,
+ $webUiSalt,
+ $this->getRequest()
+ ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Validate and normalize of parameters of type 'user'
* @param string $value Parameter value
* @param string $encParamName Parameter name
@@ -1165,23 +1014,35 @@ abstract class ApiBase extends ContextSource {
private function validateUser( $value, $encParamName ) {
$title = Title::makeTitleSafe( NS_USER, $value );
if ( $title === null ) {
- $this->dieUsage( "Invalid value '$value' for user parameter $encParamName", "baduser_{$encParamName}" );
+ $this->dieUsage(
+ "Invalid value '$value' for user parameter $encParamName",
+ "baduser_{$encParamName}"
+ );
}
+
return $title->getText();
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Utility methods
+ * @{
+ */
+
/**
- * Adds a warning to the output, else dies
- *
- * @param $msg String Message to show as a warning, or error message if dying
- * @param $enforceLimits Boolean Whether this is an enforce (die)
+ * Set a watch (or unwatch) based the based on a watchlist parameter.
+ * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param Title $titleObj The article's title to change
+ * @param string $userOption The user option to consider when $watch=preferences
*/
- private function warnOrDie( $msg, $enforceLimits = false ) {
- if ( $enforceLimits ) {
- $this->dieUsage( $msg, 'integeroutofrange' );
- } else {
- $this->setWarning( $msg );
+ protected function setWatch( $watch, $titleObj, $userOption = null ) {
+ $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
+ if ( $value === null ) {
+ return;
}
+
+ WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
}
/**
@@ -1196,10 +1057,96 @@ abstract class ApiBase extends ContextSource {
array_pop( $arr );
$modified = true;
}
+
return $modified;
}
/**
+ * Gets the user for whom to get the watchlist
+ *
+ * @param array $params
+ * @return User
+ */
+ public function getWatchlistUser( $params ) {
+ if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
+ $user = User::newFromName( $params['owner'], false );
+ if ( !( $user && $user->getId() ) ) {
+ $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
+ }
+ $token = $user->getOption( 'watchlisttoken' );
+ if ( $token == '' || $token != $params['token'] ) {
+ $this->dieUsage(
+ 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
+ 'bad_wltoken'
+ );
+ }
+ } else {
+ if ( !$this->getUser()->isLoggedIn() ) {
+ $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
+ }
+ if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
+ $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
+ }
+ $user = $this->getUser();
+ }
+
+ return $user;
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Warning and error reporting
+ * @{
+ */
+
+ /**
+ * Set warning section for this module. Users should monitor this
+ * section to notice any changes in API. Multiple calls to this
+ * function will result in the warning messages being separated by
+ * newlines
+ * @param string $warning Warning message
+ */
+ public function setWarning( $warning ) {
+ $result = $this->getResult();
+ $data = $result->getData();
+ $moduleName = $this->getModuleName();
+ if ( isset( $data['warnings'][$moduleName] ) ) {
+ // Don't add duplicate warnings
+ $oldWarning = $data['warnings'][$moduleName]['*'];
+ $warnPos = strpos( $oldWarning, $warning );
+ // If $warning was found in $oldWarning, check if it starts at 0 or after "\n"
+ if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
+ // Check if $warning is followed by "\n" or the end of the $oldWarning
+ $warnPos += strlen( $warning );
+ if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
+ return;
+ }
+ }
+ // If there is a warning already, append it to the existing one
+ $warning = "$oldWarning\n$warning";
+ }
+ $msg = array();
+ ApiResult::setContent( $msg, $warning );
+ $result->addValue( 'warnings', $moduleName,
+ $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+
+ /**
+ * Adds a warning to the output, else dies
+ *
+ * @param string $msg Message to show as a warning, or error message if dying
+ * @param bool $enforceLimits Whether this is an enforce (die)
+ */
+ private function warnOrDie( $msg, $enforceLimits = false ) {
+ if ( $enforceLimits ) {
+ $this->dieUsage( $msg, 'integeroutofrange' );
+ }
+
+ $this->setWarning( $msg );
+ }
+
+ /**
* Throw a UsageException, which will (if uncaught) call the main module's
* error handler and die with an error message.
*
@@ -1213,17 +1160,22 @@ abstract class ApiBase extends ContextSource {
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
Profiler::instance()->close();
- throw new UsageException( $description, $this->encodeParamName( $errorCode ), $httpRespCode, $extradata );
+ throw new UsageException(
+ $description,
+ $this->encodeParamName( $errorCode ),
+ $httpRespCode,
+ $extradata
+ );
}
/**
- * Throw a UsageException based on the errors in the Status object.
+ * Get error (as code, string) from a Status object.
*
- * @since 1.22
- * @param Status $status Status object
- * @throws UsageException
+ * @since 1.23
+ * @param Status $status
+ * @return array Array of code and error string
*/
- public function dieStatus( $status ) {
+ public function getErrorFromStatus( $status ) {
if ( $status->isGood() ) {
throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
}
@@ -1248,13 +1200,28 @@ abstract class ApiBase extends ContextSource {
$msg = wfMessage( $code, $errors[0] );
}
if ( isset( ApiBase::$messageMap[$code] ) ) {
- // Translate message to code, for backwards compatability
+ // Translate message to code, for backwards compatibility
$code = ApiBase::$messageMap[$code]['code'];
}
- $this->dieUsage( $msg->inLanguage( 'en' )->useDatabase( false )->plain(), $code );
+
+ return array( $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() );
}
/**
+ * Throw a UsageException based on the errors in the Status object.
+ *
+ * @since 1.22
+ * @param Status $status
+ * @throws MWException
+ */
+ public function dieStatus( $status ) {
+
+ list( $code, $msg ) = $this->getErrorFromStatus( $status );
+ $this->dieUsage( $msg, $code );
+ }
+
+ // @codingStandardsIgnoreStart Allow long lines. Cannot split these.
+ /**
* Array that maps message keys to error messages. $1 and friends are replaced.
*/
public static $messageMap = array(
@@ -1263,74 +1230,243 @@ abstract class ApiBase extends ContextSource {
'unknownerror-nocode' => array( 'code' => 'unknownerror', 'info' => 'Unknown error' ),
// Messages from Title::getUserPermissionsErrors()
- 'ns-specialprotected' => array( 'code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited" ),
- 'protectedinterface' => array( 'code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages" ),
- 'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the \"\$1\" namespace" ),
- 'customcssprotected' => array( 'code' => 'customcssprotected', 'info' => "You're not allowed to edit custom CSS pages" ),
- 'customjsprotected' => array( 'code' => 'customjsprotected', 'info' => "You're not allowed to edit custom JavaScript pages" ),
- 'cascadeprotected' => array( 'code' => 'cascadeprotected', 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" ),
- 'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The \"\$1\" right is required to edit this page" ),
- 'protect-cantedit' => array( 'code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it" ),
- 'badaccess-group0' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ), // Generic permission denied message
- 'badaccess-groups' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ),
- 'titleprotected' => array( 'code' => 'protectedtitle', 'info' => "This title has been protected from creation" ),
- 'nocreate-loggedin' => array( 'code' => 'cantcreate', 'info' => "You don't have permission to create new pages" ),
- 'nocreatetext' => array( 'code' => 'cantcreate-anon', 'info' => "Anonymous users can't create new pages" ),
- 'movenologintext' => array( 'code' => 'cantmove-anon', 'info' => "Anonymous users can't move pages" ),
- 'movenotallowed' => array( 'code' => 'cantmove', 'info' => "You don't have permission to move pages" ),
- 'confirmedittext' => array( 'code' => 'confirmemail', 'info' => "You must confirm your email address before you can edit" ),
- 'blockedtext' => array( 'code' => 'blocked', 'info' => "You have been blocked from editing" ),
- 'autoblockedtext' => array( 'code' => 'autoblocked', 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user" ),
+ 'ns-specialprotected' => array(
+ 'code' => 'unsupportednamespace',
+ 'info' => "Pages in the Special namespace can't be edited"
+ ),
+ 'protectedinterface' => array(
+ 'code' => 'protectednamespace-interface',
+ 'info' => "You're not allowed to edit interface messages"
+ ),
+ 'namespaceprotected' => array(
+ 'code' => 'protectednamespace',
+ 'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
+ ),
+ 'customcssprotected' => array(
+ 'code' => 'customcssprotected',
+ 'info' => "You're not allowed to edit custom CSS pages"
+ ),
+ 'customjsprotected' => array(
+ 'code' => 'customjsprotected',
+ 'info' => "You're not allowed to edit custom JavaScript pages"
+ ),
+ 'cascadeprotected' => array(
+ 'code' => 'cascadeprotected',
+ 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
+ ),
+ 'protectedpagetext' => array(
+ 'code' => 'protectedpage',
+ 'info' => "The \"\$1\" right is required to edit this page"
+ ),
+ 'protect-cantedit' => array(
+ 'code' => 'cantedit',
+ 'info' => "You can't protect this page because you can't edit it"
+ ),
+ 'deleteprotected' => array(
+ 'code' => 'cantedit',
+ 'info' => "You can't delete this page because it has been protected"
+ ),
+ 'badaccess-group0' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "Permission denied"
+ ), // Generic permission denied message
+ 'badaccess-groups' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "Permission denied"
+ ),
+ 'titleprotected' => array(
+ 'code' => 'protectedtitle',
+ 'info' => "This title has been protected from creation"
+ ),
+ 'nocreate-loggedin' => array(
+ 'code' => 'cantcreate',
+ 'info' => "You don't have permission to create new pages"
+ ),
+ 'nocreatetext' => array(
+ 'code' => 'cantcreate-anon',
+ 'info' => "Anonymous users can't create new pages"
+ ),
+ 'movenologintext' => array(
+ 'code' => 'cantmove-anon',
+ 'info' => "Anonymous users can't move pages"
+ ),
+ 'movenotallowed' => array(
+ 'code' => 'cantmove',
+ 'info' => "You don't have permission to move pages"
+ ),
+ 'confirmedittext' => array(
+ 'code' => 'confirmemail',
+ 'info' => "You must confirm your email address before you can edit"
+ ),
+ 'blockedtext' => array(
+ 'code' => 'blocked',
+ 'info' => "You have been blocked from editing"
+ ),
+ 'autoblockedtext' => array(
+ 'code' => 'autoblocked',
+ 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user"
+ ),
// Miscellaneous interface messages
- 'actionthrottledtext' => array( 'code' => 'ratelimited', 'info' => "You've exceeded your rate limit. Please wait some time and try again" ),
- 'alreadyrolled' => array( 'code' => 'alreadyrolled', 'info' => "The page you tried to rollback was already rolled back" ),
- 'cantrollback' => array( 'code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author" ),
- 'readonlytext' => array( 'code' => 'readonly', 'info' => "The wiki is currently in read-only mode" ),
- 'sessionfailure' => array( 'code' => 'badtoken', 'info' => "Invalid token" ),
- 'cannotdelete' => array( 'code' => 'cantdelete', 'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else" ),
- 'notanarticle' => array( 'code' => 'missingtitle', 'info' => "The page you requested doesn't exist" ),
- 'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself" ),
- 'immobile_namespace' => array( 'code' => 'immobilenamespace', 'info' => "You tried to move pages from or to a namespace that is protected from moving" ),
- 'articleexists' => array( 'code' => 'articleexists', 'info' => "The destination article already exists and is not a redirect to the source article" ),
- 'protectedpage' => array( 'code' => 'protectedpage', 'info' => "You don't have permission to perform this move" ),
- 'hookaborted' => array( 'code' => 'hookaborted', 'info' => "The modification you tried to make was aborted by an extension hook" ),
- 'cantmove-titleprotected' => array( 'code' => 'protectedtitle', 'info' => "The destination article has been protected from creation" ),
- 'imagenocrossnamespace' => array( 'code' => 'nonfilenamespace', 'info' => "Can't move a file to a non-file namespace" ),
- 'imagetypemismatch' => array( 'code' => 'filetypemismatch', 'info' => "The new file extension doesn't match its type" ),
+ 'actionthrottledtext' => array(
+ 'code' => 'ratelimited',
+ 'info' => "You've exceeded your rate limit. Please wait some time and try again"
+ ),
+ 'alreadyrolled' => array(
+ 'code' => 'alreadyrolled',
+ 'info' => "The page you tried to rollback was already rolled back"
+ ),
+ 'cantrollback' => array(
+ 'code' => 'onlyauthor',
+ 'info' => "The page you tried to rollback only has one author"
+ ),
+ 'readonlytext' => array(
+ 'code' => 'readonly',
+ 'info' => "The wiki is currently in read-only mode"
+ ),
+ 'sessionfailure' => array(
+ 'code' => 'badtoken',
+ 'info' => "Invalid token" ),
+ 'cannotdelete' => array(
+ 'code' => 'cantdelete',
+ 'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
+ ),
+ 'notanarticle' => array(
+ 'code' => 'missingtitle',
+ 'info' => "The page you requested doesn't exist"
+ ),
+ 'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself"
+ ),
+ 'immobile_namespace' => array(
+ 'code' => 'immobilenamespace',
+ 'info' => "You tried to move pages from or to a namespace that is protected from moving"
+ ),
+ 'articleexists' => array(
+ 'code' => 'articleexists',
+ 'info' => "The destination article already exists and is not a redirect to the source article"
+ ),
+ 'protectedpage' => array(
+ 'code' => 'protectedpage',
+ 'info' => "You don't have permission to perform this move"
+ ),
+ 'hookaborted' => array(
+ 'code' => 'hookaborted',
+ 'info' => "The modification you tried to make was aborted by an extension hook"
+ ),
+ 'cantmove-titleprotected' => array(
+ 'code' => 'protectedtitle',
+ 'info' => "The destination article has been protected from creation"
+ ),
+ 'imagenocrossnamespace' => array(
+ 'code' => 'nonfilenamespace',
+ 'info' => "Can't move a file to a non-file namespace"
+ ),
+ 'imagetypemismatch' => array(
+ 'code' => 'filetypemismatch',
+ 'info' => "The new file extension doesn't match its type"
+ ),
// 'badarticleerror' => shouldn't happen
// 'badtitletext' => shouldn't happen
'ip_range_invalid' => array( 'code' => 'invalidrange', 'info' => "Invalid IP range" ),
- 'range_block_disabled' => array( 'code' => 'rangedisabled', 'info' => "Blocking IP ranges has been disabled" ),
- 'nosuchusershort' => array( 'code' => 'nosuchuser', 'info' => "The user you specified doesn't exist" ),
+ 'range_block_disabled' => array(
+ 'code' => 'rangedisabled',
+ 'info' => "Blocking IP ranges has been disabled"
+ ),
+ 'nosuchusershort' => array(
+ 'code' => 'nosuchuser',
+ 'info' => "The user you specified doesn't exist"
+ ),
'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
- 'ipb_already_blocked' => array( 'code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked" ),
- 'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole." ),
- 'ipb_cant_unblock' => array( 'code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already" ),
- 'mailnologin' => array( 'code' => 'cantsend', 'info' => "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email" ),
- 'ipbblocked' => array( 'code' => 'ipbblocked', 'info' => 'You cannot block or unblock users while you are yourself blocked' ),
- 'ipbnounblockself' => array( 'code' => 'ipbnounblockself', 'info' => 'You are not allowed to unblock yourself' ),
- 'usermaildisabled' => array( 'code' => 'usermaildisabled', 'info' => "User email has been disabled" ),
- 'blockedemailuser' => array( 'code' => 'blockedfrommail', 'info' => "You have been blocked from sending email" ),
- 'notarget' => array( 'code' => 'notarget', 'info' => "You have not specified a valid target for this action" ),
- 'noemail' => array( 'code' => 'noemail', 'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users" ),
- 'rcpatroldisabled' => array( 'code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki" ),
- 'markedaspatrollederror-noautopatrol' => array( 'code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes" ),
- 'delete-toobig' => array( 'code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions" ),
- 'movenotallowedfile' => array( 'code' => 'cantmovefile', 'info' => "You don't have permission to move files" ),
- 'userrights-no-interwiki' => array( 'code' => 'nointerwikiuserrights', 'info' => "You don't have permission to change user rights on other wikis" ),
- 'userrights-nodatabase' => array( 'code' => 'nosuchdatabase', 'info' => "Database \"\$1\" does not exist or is not local" ),
+ 'ipb_already_blocked' => array(
+ 'code' => 'alreadyblocked',
+ 'info' => "The user you tried to block was already blocked"
+ ),
+ 'ipb_blocked_as_range' => array(
+ 'code' => 'blockedasrange',
+ 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole."
+ ),
+ 'ipb_cant_unblock' => array(
+ 'code' => 'cantunblock',
+ 'info' => "The block you specified was not found. It may have been unblocked already"
+ ),
+ 'mailnologin' => array(
+ 'code' => 'cantsend',
+ 'info' => "You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email"
+ ),
+ 'ipbblocked' => array(
+ 'code' => 'ipbblocked',
+ 'info' => 'You cannot block or unblock users while you are yourself blocked'
+ ),
+ 'ipbnounblockself' => array(
+ 'code' => 'ipbnounblockself',
+ 'info' => 'You are not allowed to unblock yourself'
+ ),
+ 'usermaildisabled' => array(
+ 'code' => 'usermaildisabled',
+ 'info' => "User email has been disabled"
+ ),
+ 'blockedemailuser' => array(
+ 'code' => 'blockedfrommail',
+ 'info' => "You have been blocked from sending email"
+ ),
+ 'notarget' => array(
+ 'code' => 'notarget',
+ 'info' => "You have not specified a valid target for this action"
+ ),
+ 'noemail' => array(
+ 'code' => 'noemail',
+ 'info' => "The user has not specified a valid email address, or has chosen not to receive email from other users"
+ ),
+ 'rcpatroldisabled' => array(
+ 'code' => 'patroldisabled',
+ 'info' => "Patrolling is disabled on this wiki"
+ ),
+ 'markedaspatrollederror-noautopatrol' => array(
+ 'code' => 'noautopatrol',
+ 'info' => "You don't have permission to patrol your own changes"
+ ),
+ 'delete-toobig' => array(
+ 'code' => 'bigdelete',
+ 'info' => "You can't delete this page because it has more than \$1 revisions"
+ ),
+ 'movenotallowedfile' => array(
+ 'code' => 'cantmovefile',
+ 'info' => "You don't have permission to move files"
+ ),
+ 'userrights-no-interwiki' => array(
+ 'code' => 'nointerwikiuserrights',
+ 'info' => "You don't have permission to change user rights on other wikis"
+ ),
+ 'userrights-nodatabase' => array(
+ 'code' => 'nosuchdatabase',
+ 'info' => "Database \"\$1\" does not exist or is not local"
+ ),
'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ),
- 'import-rootpage-invalid' => array( 'code' => 'import-rootpage-invalid', 'info' => 'Root page is an invalid title' ),
- 'import-rootpage-nosubpage' => array( 'code' => 'import-rootpage-nosubpage', 'info' => 'Namespace "$1" of the root page does not allow subpages' ),
+ 'import-rootpage-invalid' => array(
+ 'code' => 'import-rootpage-invalid',
+ 'info' => 'Root page is an invalid title'
+ ),
+ 'import-rootpage-nosubpage' => array(
+ 'code' => 'import-rootpage-nosubpage',
+ 'info' => 'Namespace "$1" of the root page does not allow subpages'
+ ),
// API-specific messages
- 'readrequired' => array( 'code' => 'readapidenied', 'info' => "You need read permission to use this module" ),
- 'writedisabled' => array( 'code' => 'noapiwrite', 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file" ),
- 'writerequired' => array( 'code' => 'writeapidenied', 'info' => "You're not allowed to edit this wiki through the API" ),
+ 'readrequired' => array(
+ 'code' => 'readapidenied',
+ 'info' => "You need read permission to use this module"
+ ),
+ 'writedisabled' => array(
+ 'code' => 'noapiwrite',
+ 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file"
+ ),
+ 'writerequired' => array(
+ 'code' => 'writeapidenied',
+ 'info' => "You're not allowed to edit this wiki through the API"
+ ),
'missingparam' => array( 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ),
'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ),
'nosuchpageid' => array( 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ),
@@ -1339,81 +1475,253 @@ abstract class ApiBase extends ContextSource {
'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ),
'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ),
- 'create-titleexists' => array( 'code' => 'create-titleexists', 'info' => "Existing titles can't be protected with 'create'" ),
- 'missingtitle-createonly' => array( 'code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'" ),
- 'cantblock' => array( 'code' => 'cantblock', 'info' => "You don't have permission to block users" ),
- 'canthide' => array( 'code' => 'canthide', 'info' => "You don't have permission to hide user names from the block log" ),
- 'cantblock-email' => array( 'code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending email through the wiki" ),
- 'unblock-notarget' => array( 'code' => 'notarget', 'info' => "Either the id or the user parameter must be set" ),
- 'unblock-idanduser' => array( 'code' => 'idanduser', 'info' => "The id and user parameters can't be used together" ),
- 'cantunblock' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to unblock users" ),
- 'cannotundelete' => array( 'code' => 'cantundelete', 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already" ),
- 'permdenied-undelete' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions" ),
- 'createonly-exists' => array( 'code' => 'articleexists', 'info' => "The article you tried to create has been created already" ),
- 'nocreate-missing' => array( 'code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist" ),
- 'cantchangecontentmodel' => array( 'code' => 'cantchangecontentmodel', 'info' => "You don't have permission to change the content model of a page" ),
- 'nosuchrcid' => array( 'code' => 'nosuchrcid', 'info' => "There is no change with rcid \"\$1\"" ),
- 'protect-invalidaction' => array( 'code' => 'protect-invalidaction', 'info' => "Invalid protection type \"\$1\"" ),
- 'protect-invalidlevel' => array( 'code' => 'protect-invalidlevel', 'info' => "Invalid protection level \"\$1\"" ),
- 'toofewexpiries' => array( 'code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed" ),
- 'cantimport' => array( 'code' => 'cantimport', 'info' => "You don't have permission to import pages" ),
- 'cantimport-upload' => array( 'code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages" ),
+ 'create-titleexists' => array(
+ 'code' => 'create-titleexists',
+ 'info' => "Existing titles can't be protected with 'create'"
+ ),
+ 'missingtitle-createonly' => array(
+ 'code' => 'missingtitle-createonly',
+ 'info' => "Missing titles can only be protected with 'create'"
+ ),
+ 'cantblock' => array( 'code' => 'cantblock',
+ 'info' => "You don't have permission to block users"
+ ),
+ 'canthide' => array(
+ 'code' => 'canthide',
+ 'info' => "You don't have permission to hide user names from the block log"
+ ),
+ 'cantblock-email' => array(
+ 'code' => 'cantblock-email',
+ 'info' => "You don't have permission to block users from sending email through the wiki"
+ ),
+ 'unblock-notarget' => array(
+ 'code' => 'notarget',
+ 'info' => "Either the id or the user parameter must be set"
+ ),
+ 'unblock-idanduser' => array(
+ 'code' => 'idanduser',
+ 'info' => "The id and user parameters can't be used together"
+ ),
+ 'cantunblock' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "You don't have permission to unblock users"
+ ),
+ 'cannotundelete' => array(
+ 'code' => 'cantundelete',
+ 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
+ ),
+ 'permdenied-undelete' => array(
+ 'code' => 'permissiondenied',
+ 'info' => "You don't have permission to restore deleted revisions"
+ ),
+ 'createonly-exists' => array(
+ 'code' => 'articleexists',
+ 'info' => "The article you tried to create has been created already"
+ ),
+ 'nocreate-missing' => array(
+ 'code' => 'missingtitle',
+ 'info' => "The article you tried to edit doesn't exist"
+ ),
+ 'cantchangecontentmodel' => array(
+ 'code' => 'cantchangecontentmodel',
+ 'info' => "You don't have permission to change the content model of a page"
+ ),
+ 'nosuchrcid' => array(
+ 'code' => 'nosuchrcid',
+ 'info' => "There is no change with rcid \"\$1\""
+ ),
+ 'protect-invalidaction' => array(
+ 'code' => 'protect-invalidaction',
+ 'info' => "Invalid protection type \"\$1\""
+ ),
+ 'protect-invalidlevel' => array(
+ 'code' => 'protect-invalidlevel',
+ 'info' => "Invalid protection level \"\$1\""
+ ),
+ 'toofewexpiries' => array(
+ 'code' => 'toofewexpiries',
+ 'info' => "\$1 expiry timestamps were provided where \$2 were needed"
+ ),
+ 'cantimport' => array(
+ 'code' => 'cantimport',
+ 'info' => "You don't have permission to import pages"
+ ),
+ 'cantimport-upload' => array(
+ 'code' => 'cantimport-upload',
+ 'info' => "You don't have permission to import uploaded pages"
+ ),
'importnofile' => array( 'code' => 'nofile', 'info' => "You didn't upload a file" ),
- 'importuploaderrorsize' => array( 'code' => 'filetoobig', 'info' => 'The file you uploaded is bigger than the maximum upload size' ),
- 'importuploaderrorpartial' => array( 'code' => 'partialupload', 'info' => 'The file was only partially uploaded' ),
- 'importuploaderrortemp' => array( 'code' => 'notempdir', 'info' => 'The temporary upload directory is missing' ),
- 'importcantopen' => array( 'code' => 'cantopenfile', 'info' => "Couldn't open the uploaded file" ),
- 'import-noarticle' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
- 'importbadinterwiki' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
- 'import-unknownerror' => array( 'code' => 'import-unknownerror', 'info' => "Unknown error on import: \"\$1\"" ),
- 'cantoverwrite-sharedfile' => array( 'code' => 'cantoverwrite-sharedfile', 'info' => 'The target file exists on a shared repository and you do not have permission to override it' ),
- 'sharedfile-exists' => array( 'code' => 'fileexists-sharedrepo-perm', 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' ),
- 'mustbeposted' => array( 'code' => 'mustbeposted', 'info' => "The \$1 module requires a POST request" ),
- 'show' => array( 'code' => 'show', 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' ),
- 'specialpage-cantexecute' => array( 'code' => 'specialpage-cantexecute', 'info' => "You don't have permission to view the results of this special page" ),
- 'invalidoldimage' => array( 'code' => 'invalidoldimage', 'info' => 'The oldimage parameter has invalid format' ),
- 'nodeleteablefile' => array( 'code' => 'nodeleteablefile', 'info' => 'No such old version of the file' ),
- 'fileexists-forbidden' => array( 'code' => 'fileexists-forbidden', 'info' => 'A file with name "$1" already exists, and cannot be overwritten.' ),
- 'fileexists-shared-forbidden' => array( 'code' => 'fileexists-shared-forbidden', 'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.' ),
- 'filerevert-badversion' => array( 'code' => 'filerevert-badversion', 'info' => 'There is no previous local version of this file with the provided timestamp.' ),
+ 'importuploaderrorsize' => array(
+ 'code' => 'filetoobig',
+ 'info' => 'The file you uploaded is bigger than the maximum upload size'
+ ),
+ 'importuploaderrorpartial' => array(
+ 'code' => 'partialupload',
+ 'info' => 'The file was only partially uploaded'
+ ),
+ 'importuploaderrortemp' => array(
+ 'code' => 'notempdir',
+ 'info' => 'The temporary upload directory is missing'
+ ),
+ 'importcantopen' => array(
+ 'code' => 'cantopenfile',
+ 'info' => "Couldn't open the uploaded file"
+ ),
+ 'import-noarticle' => array(
+ 'code' => 'badinterwiki',
+ 'info' => 'Invalid interwiki title specified'
+ ),
+ 'importbadinterwiki' => array(
+ 'code' => 'badinterwiki',
+ 'info' => 'Invalid interwiki title specified'
+ ),
+ 'import-unknownerror' => array(
+ 'code' => 'import-unknownerror',
+ 'info' => "Unknown error on import: \"\$1\""
+ ),
+ 'cantoverwrite-sharedfile' => array(
+ 'code' => 'cantoverwrite-sharedfile',
+ 'info' => 'The target file exists on a shared repository and you do not have permission to override it'
+ ),
+ 'sharedfile-exists' => array(
+ 'code' => 'fileexists-sharedrepo-perm',
+ 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
+ ),
+ 'mustbeposted' => array(
+ 'code' => 'mustbeposted',
+ 'info' => "The \$1 module requires a POST request"
+ ),
+ 'show' => array(
+ 'code' => 'show',
+ 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
+ ),
+ 'specialpage-cantexecute' => array(
+ 'code' => 'specialpage-cantexecute',
+ 'info' => "You don't have permission to view the results of this special page"
+ ),
+ 'invalidoldimage' => array(
+ 'code' => 'invalidoldimage',
+ 'info' => 'The oldimage parameter has invalid format'
+ ),
+ 'nodeleteablefile' => array(
+ 'code' => 'nodeleteablefile',
+ 'info' => 'No such old version of the file'
+ ),
+ 'fileexists-forbidden' => array(
+ 'code' => 'fileexists-forbidden',
+ 'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
+ ),
+ 'fileexists-shared-forbidden' => array(
+ 'code' => 'fileexists-shared-forbidden',
+ 'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
+ ),
+ 'filerevert-badversion' => array(
+ 'code' => 'filerevert-badversion',
+ 'info' => 'There is no previous local version of this file with the provided timestamp.'
+ ),
// ApiEditPage messages
- 'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ),
- 'noimageredirect-logged' => array( 'code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects" ),
- 'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\"" ),
- 'contenttoobig' => array( 'code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" ),
+ 'noimageredirect-anon' => array(
+ 'code' => 'noimageredirect-anon',
+ 'info' => "Anonymous users can't create image redirects"
+ ),
+ 'noimageredirect-logged' => array(
+ 'code' => 'noimageredirect',
+ 'info' => "You don't have permission to create image redirects"
+ ),
+ 'spamdetected' => array(
+ 'code' => 'spamdetected',
+ 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
+ ),
+ 'contenttoobig' => array(
+ 'code' => 'contenttoobig',
+ 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
+ ),
'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ),
- 'wasdeleted' => array( 'code' => 'pagedeleted', 'info' => "The page has been deleted since you fetched its timestamp" ),
- 'blankpage' => array( 'code' => 'emptypage', 'info' => "Creating new, empty pages is not allowed" ),
+ 'wasdeleted' => array(
+ 'code' => 'pagedeleted',
+ 'info' => "The page has been deleted since you fetched its timestamp"
+ ),
+ 'blankpage' => array(
+ 'code' => 'emptypage',
+ 'info' => "Creating new, empty pages is not allowed"
+ ),
'editconflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
'hashcheckfailed' => array( 'code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect" ),
- 'missingtext' => array( 'code' => 'notext', 'info' => "One of the text, appendtext, prependtext and undo parameters must be set" ),
- 'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ),
- 'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of \"\$2\"" ),
- 'undo-failure' => array( 'code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits' ),
+ 'missingtext' => array(
+ 'code' => 'notext',
+ 'info' => "One of the text, appendtext, prependtext and undo parameters must be set"
+ ),
+ 'emptynewsection' => array(
+ 'code' => 'emptynewsection',
+ 'info' => 'Creating empty new sections is not possible.'
+ ),
+ 'revwrongpage' => array(
+ 'code' => 'revwrongpage',
+ 'info' => "r\$1 is not a revision of \"\$2\""
+ ),
+ 'undo-failure' => array(
+ 'code' => 'undofailure',
+ 'info' => 'Undo failed due to conflicting intermediate edits'
+ ),
+ 'content-not-allowed-here' => array(
+ 'code' => 'contentnotallowedhere',
+ 'info' => 'Content model "$1" is not allowed at title "$2"'
+ ),
// Messages from WikiPage::doEit()
- 'edit-hook-aborted' => array( 'code' => 'edit-hook-aborted', 'info' => "Your edit was aborted by an ArticleSave hook" ),
- 'edit-gone-missing' => array( 'code' => 'edit-gone-missing', 'info' => "The page you tried to edit doesn't seem to exist anymore" ),
+ 'edit-hook-aborted' => array(
+ 'code' => 'edit-hook-aborted',
+ 'info' => "Your edit was aborted by an ArticleSave hook"
+ ),
+ 'edit-gone-missing' => array(
+ 'code' => 'edit-gone-missing',
+ 'info' => "The page you tried to edit doesn't seem to exist anymore"
+ ),
'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
- 'edit-already-exists' => array( 'code' => 'edit-already-exists', 'info' => "It seems the page you tried to create already exist" ),
+ 'edit-already-exists' => array(
+ 'code' => 'edit-already-exists',
+ 'info' => 'It seems the page you tried to create already exist'
+ ),
// uploadMsgs
'invalid-file-key' => array( 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ),
'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ),
- 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ),
- 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ),
- 'copyuploadbaddomain' => array( 'code' => 'copyuploadbaddomain', 'info' => 'Uploads by URL are not allowed from this domain.' ),
- 'copyuploadbadurl' => array( 'code' => 'copyuploadbadurl', 'info' => 'Upload not allowed from this URL.' ),
-
- 'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
+ 'uploaddisabled' => array(
+ 'code' => 'uploaddisabled',
+ 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
+ ),
+ 'copyuploaddisabled' => array(
+ 'code' => 'copyuploaddisabled',
+ 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
+ ),
+ 'copyuploadbaddomain' => array(
+ 'code' => 'copyuploadbaddomain',
+ 'info' => 'Uploads by URL are not allowed from this domain.'
+ ),
+ 'copyuploadbadurl' => array(
+ 'code' => 'copyuploadbadurl',
+ 'info' => 'Upload not allowed from this URL.'
+ ),
+
+ 'filename-tooshort' => array(
+ 'code' => 'filename-tooshort',
+ 'info' => 'The filename is too short'
+ ),
'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ),
- 'illegal-filename' => array( 'code' => 'illegal-filename', 'info' => 'The filename is not allowed' ),
- 'filetype-missing' => array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
+ 'illegal-filename' => array(
+ 'code' => 'illegal-filename',
+ 'info' => 'The filename is not allowed'
+ ),
+ 'filetype-missing' => array(
+ 'code' => 'filetype-missing',
+ 'info' => 'The file is missing an extension'
+ ),
'mustbeloggedin' => array( 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' )
);
+ // @codingStandardsIgnoreEnd
/**
* Helper function for readonly errors
@@ -1426,7 +1734,7 @@ abstract class ApiBase extends ContextSource {
/**
* Output the error message related to a certain array
- * @param $error (array|string) Element of a getUserPermissionsErrors()-style array
+ * @param array|string $error Element of a getUserPermissionsErrors()-style array
*/
public function dieUsageMsg( $error ) {
# most of the time we send a 1 element, so we might as well send it as
@@ -1441,26 +1749,25 @@ abstract class ApiBase extends ContextSource {
/**
* Will only set a warning instead of failing if the global $wgDebugAPI
* is set to true. Otherwise behaves exactly as dieUsageMsg().
- * @param $error (array|string) Element of a getUserPermissionsErrors()-style array
+ * @param array|string $error Element of a getUserPermissionsErrors()-style array
* @since 1.21
*/
public function dieUsageMsgOrDebug( $error ) {
- global $wgDebugAPI;
- if ( $wgDebugAPI !== true ) {
+ if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
$this->dieUsageMsg( $error );
- } else {
- if ( is_string( $error ) ) {
- $error = array( $error );
- }
- $parsed = $this->parseMsg( $error );
- $this->setWarning( '$wgDebugAPI: ' . $parsed['code']
- . ' - ' . $parsed['info'] );
}
+
+ if ( is_string( $error ) ) {
+ $error = array( $error );
+ }
+ $parsed = $this->parseMsg( $error );
+ $this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
}
/**
- * Die with the $prefix.'badcontinue' error. This call is common enough to make it into the base method.
- * @param $condition boolean will only die if this value is true
+ * Die with the $prefix.'badcontinue' error. This call is common enough to
+ * make it into the base method.
+ * @param bool $condition Will only die if this value is true
* @since 1.21
*/
protected function dieContinueUsageIf( $condition ) {
@@ -1502,193 +1809,311 @@ abstract class ApiBase extends ContextSource {
* Internal code errors should be reported with this method
* @param string $method Method or function name
* @param string $message Error message
+ * @throws MWException
*/
protected static function dieDebug( $method, $message ) {
throw new MWException( "Internal error in $method: $message" );
}
- /**
- * Indicates if this module needs maxlag to be checked
- * @return bool
- */
- public function shouldCheckMaxlag() {
- return true;
- }
+ /**@}*/
- /**
- * Indicates whether this module requires read rights
- * @return bool
+ /************************************************************************//**
+ * @name Help message generation
+ * @{
*/
- public function isReadMode() {
- return true;
- }
- /**
- * Indicates whether this module requires write mode
- * @return bool
- */
- public function isWriteMode() {
- return false;
- }
/**
- * Indicates whether this module must be called with a POST request
- * @return bool
+ * Generates help message for this module, or false if there is no description
+ * @return string|bool
*/
- public function mustBePosted() {
- return false;
- }
+ public function makeHelpMsg() {
+ static $lnPrfx = "\n ";
- /**
- * Returns whether this module requires a token to execute
- * It is used to show possible errors in action=paraminfo
- * see bug 25248
- * @return bool
- */
- public function needsToken() {
- return false;
- }
+ $msg = $this->getFinalDescription();
- /**
- * Returns the token salt if there is one,
- * '' if the module doesn't require a salt,
- * else false if the module doesn't need a token
- * You have also to override needsToken()
- * Value is passed to User::getEditToken
- * @return bool|string|array
- */
- public function getTokenSalt() {
- return false;
- }
+ if ( $msg !== false ) {
- /**
- * Gets the user for whom to get the watchlist
- *
- * @param $params array
- * @return User
- */
- public function getWatchlistUser( $params ) {
- if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
- $user = User::newFromName( $params['owner'], false );
- if ( !( $user && $user->getId() ) ) {
- $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
+ if ( !is_array( $msg ) ) {
+ $msg = array(
+ $msg
+ );
}
- $token = $user->getOption( 'watchlisttoken' );
- if ( $token == '' || $token != $params['token'] ) {
- $this->dieUsage( 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', 'bad_wltoken' );
+ $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+
+ $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
+
+ if ( $this->isReadMode() ) {
+ $msg .= "\nThis module requires read rights";
}
- } else {
- if ( !$this->getUser()->isLoggedIn() ) {
- $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
+ if ( $this->isWriteMode() ) {
+ $msg .= "\nThis module requires write rights";
}
- if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
- $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
+ if ( $this->mustBePosted() ) {
+ $msg .= "\nThis module only accepts POST requests";
+ }
+ if ( $this->isReadMode() || $this->isWriteMode() ||
+ $this->mustBePosted()
+ ) {
+ $msg .= "\n";
+ }
+
+ // Parameters
+ $paramsMsg = $this->makeHelpMsgParameters();
+ if ( $paramsMsg !== false ) {
+ $msg .= "Parameters:\n$paramsMsg";
+ }
+
+ $examples = $this->getExamples();
+ if ( $examples ) {
+ if ( !is_array( $examples ) ) {
+ $examples = array(
+ $examples
+ );
+ }
+ $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
+ foreach ( $examples as $k => $v ) {
+ if ( is_numeric( $k ) ) {
+ $msg .= " $v\n";
+ } else {
+ if ( is_array( $v ) ) {
+ $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
+ } else {
+ $msgExample = " $v";
+ }
+ $msgExample .= ":";
+ $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n $k\n";
+ }
+ }
}
- $user = $this->getUser();
}
- return $user;
+
+ return $msg;
}
/**
- * @return bool|string|array Returns a false if the module has no help url, else returns a (array of) string
+ * @param string $item
+ * @return string
*/
- public function getHelpUrls() {
- return false;
+ private function indentExampleText( $item ) {
+ return " " . $item;
}
/**
- * Returns a list of all possible errors returned by the module
- *
- * Don't call this function directly: use getFinalPossibleErrors() to allow
- * hooks to modify parameters as needed.
- *
- * @return array in the format of array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
+ * @param string $prefix Text to split output items
+ * @param string $title What is being output
+ * @param string|array $input
+ * @return string
*/
- public function getPossibleErrors() {
- $ret = array();
-
- $params = $this->getFinalParams();
- if ( $params ) {
- foreach ( $params as $paramName => $paramSettings ) {
- if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) && $paramSettings[ApiBase::PARAM_REQUIRED] ) {
- $ret[] = array( 'missingparam', $paramName );
- }
- }
- if ( array_key_exists( 'continue', $params ) ) {
- $ret[] = array(
- 'code' => 'badcontinue',
- 'info' => 'Invalid continue param. You should pass the original value returned by the previous query'
- );
- }
- }
-
- if ( $this->mustBePosted() ) {
- $ret[] = array( 'mustbeposted', $this->getModuleName() );
- }
-
- if ( $this->isReadMode() ) {
- $ret[] = array( 'readrequired' );
+ protected function makeHelpArrayToString( $prefix, $title, $input ) {
+ if ( $input === false ) {
+ return '';
}
-
- if ( $this->isWriteMode() ) {
- $ret[] = array( 'writerequired' );
- $ret[] = array( 'writedisabled' );
+ if ( !is_array( $input ) ) {
+ $input = array( $input );
}
- if ( $this->needsToken() ) {
- if ( !isset( $params['token'][ApiBase::PARAM_REQUIRED] )
- || !$params['token'][ApiBase::PARAM_REQUIRED]
- ) {
- // Add token as possible missing parameter, if not already done
- $ret[] = array( 'missingparam', 'token' );
+ if ( count( $input ) > 0 ) {
+ if ( $title ) {
+ $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
+ } else {
+ $msg = ' ';
}
- $ret[] = array( 'sessionfailure' );
+ $msg .= implode( $prefix, $input ) . "\n";
+
+ return $msg;
}
- return $ret;
+ return '';
}
/**
- * Get final list of possible errors, after hooks have had a chance to
- * tweak it as needed.
- *
- * @return array
- * @since 1.22
+ * Generates the parameter descriptions for this module, to be displayed in the
+ * module's help.
+ * @return string|bool
*/
- public function getFinalPossibleErrors() {
- $possibleErrors = $this->getPossibleErrors();
- wfRunHooks( 'APIGetPossibleErrors', array( $this, &$possibleErrors ) );
- return $possibleErrors;
- }
+ public function makeHelpMsgParameters() {
+ $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+ if ( $params ) {
- /**
- * Parses a list of errors into a standardised format
- * @param array $errors List of errors. Items can be in the for array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
- * @return array Parsed list of errors with items in the form array( 'code' => ..., 'info' => ... )
- */
- public function parseErrors( $errors ) {
- $ret = array();
+ $paramsDescription = $this->getFinalParamDescription();
+ $msg = '';
+ $paramPrefix = "\n" . str_repeat( ' ', 24 );
+ $descWordwrap = "\n" . str_repeat( ' ', 28 );
+ foreach ( $params as $paramName => $paramSettings ) {
+ $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
+ if ( is_array( $desc ) ) {
+ $desc = implode( $paramPrefix, $desc );
+ }
- foreach ( $errors as $row ) {
- if ( isset( $row['code'] ) && isset( $row['info'] ) ) {
- $ret[] = $row;
- } else {
- $ret[] = $this->parseMsg( $row );
+ //handle shorthand
+ if ( !is_array( $paramSettings ) ) {
+ $paramSettings = array(
+ self::PARAM_DFLT => $paramSettings,
+ );
+ }
+
+ //handle missing type
+ if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
+ $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
+ ? $paramSettings[ApiBase::PARAM_DFLT]
+ : null;
+ if ( is_bool( $dflt ) ) {
+ $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
+ } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
+ $paramSettings[ApiBase::PARAM_TYPE] = 'string';
+ } elseif ( is_int( $dflt ) ) {
+ $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
+ }
+ }
+
+ if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
+ && $paramSettings[self::PARAM_DEPRECATED]
+ ) {
+ $desc = "DEPRECATED! $desc";
+ }
+
+ if ( isset( $paramSettings[self::PARAM_REQUIRED] )
+ && $paramSettings[self::PARAM_REQUIRED]
+ ) {
+ $desc .= $paramPrefix . "This parameter is required";
+ }
+
+ $type = isset( $paramSettings[self::PARAM_TYPE] )
+ ? $paramSettings[self::PARAM_TYPE]
+ : null;
+ if ( isset( $type ) ) {
+ $hintPipeSeparated = true;
+ $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
+ ? $paramSettings[self::PARAM_ISMULTI]
+ : false;
+ if ( $multi ) {
+ $prompt = 'Values (separate with \'|\'): ';
+ } else {
+ $prompt = 'One value: ';
+ }
+
+ if ( $type === 'submodule' ) {
+ $type = $this->getModuleManager()->getNames( $paramName );
+ sort( $type );
+ }
+ if ( is_array( $type ) ) {
+ $choices = array();
+ $nothingPrompt = '';
+ foreach ( $type as $t ) {
+ if ( $t === '' ) {
+ $nothingPrompt = 'Can be empty, or ';
+ } else {
+ $choices[] = $t;
+ }
+ }
+ $desc .= $paramPrefix . $nothingPrompt . $prompt;
+ $choicesstring = implode( ', ', $choices );
+ $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
+ $hintPipeSeparated = false;
+ } else {
+ switch ( $type ) {
+ case 'namespace':
+ // Special handling because namespaces are
+ // type-limited, yet they are not given
+ $desc .= $paramPrefix . $prompt;
+ $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
+ 100, $descWordwrap );
+ $hintPipeSeparated = false;
+ break;
+ case 'limit':
+ $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
+ if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
+ $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
+ }
+ $desc .= ' allowed';
+ break;
+ case 'integer':
+ $s = $multi ? 's' : '';
+ $hasMin = isset( $paramSettings[self::PARAM_MIN] );
+ $hasMax = isset( $paramSettings[self::PARAM_MAX] );
+ if ( $hasMin || $hasMax ) {
+ if ( !$hasMax ) {
+ $intRangeStr = "The value$s must be no less than " .
+ "{$paramSettings[self::PARAM_MIN]}";
+ } elseif ( !$hasMin ) {
+ $intRangeStr = "The value$s must be no more than " .
+ "{$paramSettings[self::PARAM_MAX]}";
+ } else {
+ $intRangeStr = "The value$s must be between " .
+ "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
+ }
+
+ $desc .= $paramPrefix . $intRangeStr;
+ }
+ break;
+ case 'upload':
+ $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
+ break;
+ }
+ }
+
+ if ( $multi ) {
+ if ( $hintPipeSeparated ) {
+ $desc .= $paramPrefix . "Separate values with '|'";
+ }
+
+ $isArray = is_array( $type );
+ if ( !$isArray
+ || $isArray && count( $type ) > self::LIMIT_SML1
+ ) {
+ $desc .= $paramPrefix . "Maximum number of values " .
+ self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
+ }
+ }
+ }
+
+ $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
+ if ( !is_null( $default ) && $default !== false ) {
+ $desc .= $paramPrefix . "Default: $default";
+ }
+
+ $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
}
+
+ return $msg;
}
- return $ret;
+
+ return false;
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Profiling
+ * @{
+ */
+
/**
* Profiling: total module execution time
*/
private $mTimeIn = 0, $mModuleTime = 0;
/**
+ * Get the name of the module as shown in the profiler log
+ *
+ * @param DatabaseBase|bool $db
+ *
+ * @return string
+ */
+ public function getModuleProfileName( $db = false ) {
+ if ( $db ) {
+ return 'API:' . $this->mModuleName . '-DB';
+ }
+
+ return 'API:' . $this->mModuleName;
+ }
+
+ /**
* Start module profiling
*/
public function profileIn() {
if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called twice without calling profileOut()' );
+ ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileOut()' );
}
$this->mTimeIn = microtime( true );
wfProfileIn( $this->getModuleProfileName() );
@@ -1699,10 +2124,13 @@ abstract class ApiBase extends ContextSource {
*/
public function profileOut() {
if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileIn() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileIn() first' );
}
if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'must be called after database profiling is done with profileDBOut()' );
+ ApiBase::dieDebug(
+ __METHOD__,
+ 'Must be called after database profiling is done with profileDBOut()'
+ );
}
$this->mModuleTime += microtime( true ) - $this->mTimeIn;
@@ -1729,8 +2157,9 @@ abstract class ApiBase extends ContextSource {
*/
public function getProfileTime() {
if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileOut() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileOut() first' );
}
+
return $this->mModuleTime;
}
@@ -1744,10 +2173,13 @@ abstract class ApiBase extends ContextSource {
*/
public function profileDBIn() {
if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
+ ApiBase::dieDebug(
+ __METHOD__,
+ 'Must be called while profiling the entire module with profileIn()'
+ );
}
if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called twice without calling profileDBOut()' );
+ ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileDBOut()' );
}
$this->mDBTimeIn = microtime( true );
wfProfileIn( $this->getModuleProfileName( true ) );
@@ -1758,10 +2190,11 @@ abstract class ApiBase extends ContextSource {
*/
public function profileDBOut() {
if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'must be called while profiling the entire module with profileIn()' );
+ ApiBase::dieDebug( __METHOD__, 'Must be called while profiling ' .
+ 'the entire module with profileIn()' );
}
if ( $this->mDBTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileDBIn() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBIn() first' );
}
$time = microtime( true ) - $this->mDBTimeIn;
@@ -1778,36 +2211,168 @@ abstract class ApiBase extends ContextSource {
*/
public function getProfileDBTime() {
if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'called without calling profileDBOut() first' );
+ ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBOut() first' );
}
+
return $this->mDBTime;
}
/**
- * Gets a default slave database connection object
- * @return DatabaseBase
+ * Write logging information for API features to a debug log, for usage
+ * analysis.
+ * @param string $feature Feature being used.
*/
- protected function getDB() {
- if ( !isset( $this->mSlaveDB ) ) {
- $this->profileDBIn();
- $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
- $this->profileDBOut();
- }
- return $this->mSlaveDB;
+ protected function logFeatureUsage( $feature ) {
+ $request = $this->getRequest();
+ $s = '"' . addslashes( $feature ) . '"' .
+ ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
+ ' "' . $request->getIP() . '"' .
+ ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
+ ' "' . addslashes( $request->getHeader( 'User-agent' ) ) . '"';
+ wfDebugLog( 'api-feature-usage', $s, 'private' );
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Deprecated
+ * @{
+ */
+
+ /// @deprecated since 1.24
+ const PROP_ROOT = 'ROOT';
+ /// @deprecated since 1.24
+ const PROP_LIST = 'LIST';
+ /// @deprecated since 1.24
+ const PROP_TYPE = 0;
+ /// @deprecated since 1.24
+ const PROP_NULLABLE = 1;
+
/**
- * Debugging function that prints a value and an optional backtrace
- * @param $value mixed Value to print
- * @param string $name Description of the printed value
- * @param bool $backtrace If true, print a backtrace
+ * Formerly returned a string that identifies the version of the extending
+ * class. Typically included the class name, the svn revision, timestamp,
+ * and last author. Usually done with SVN's Id keyword
+ *
+ * @deprecated since 1.21, version string is no longer supported
+ * @return string
*/
- public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) {
- print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
- var_export( $value );
- if ( $backtrace ) {
- print "\n" . wfBacktrace();
- }
- print "\n</pre>\n";
+ public function getVersion() {
+ wfDeprecated( __METHOD__, '1.21' );
+ return '';
+ }
+
+ /**
+ * Formerly used to fetch a list of possible properites in the result,
+ * somehow organized with respect to the prop parameter that causes them to
+ * be returned. The specific semantics of the return value was never
+ * specified. Since this was never possible to be accurately updated, it
+ * has been removed.
+ *
+ * @deprecated since 1.24
+ * @return array|bool
+ */
+ protected function getResultProperties() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return false;
+ }
+
+ /**
+ * @see self::getResultProperties()
+ * @deprecated since 1.24
+ * @return array|bool
+ */
+ public function getFinalResultProperties() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getResultProperties()
+ * @deprecated since 1.24
+ */
+ protected static function addTokenProperties( &$props, $tokenFunctions ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getRequireOnlyOneParameterErrorMessages( $params ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getRequireMaxOneParameterErrorMessages( $params ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getRequireAtLeastOneParameterErrorMessages( $params ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getTitleOrPageIdErrorMessage() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * This formerly attempted to return a list of all possible errors returned
+ * by the module. However, this was impossible to maintain in many cases
+ * since errors could come from other areas of MediaWiki and in some cases
+ * from arbitrary extension hooks. Since a partial list claiming to be
+ * comprehensive is unlikely to be useful, it was removed.
+ *
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getPossibleErrors() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
}
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function getFinalPossibleErrors() {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**
+ * @see self::getPossibleErrors()
+ * @deprecated since 1.24
+ * @return array
+ */
+ public function parseErrors( $errors ) {
+ wfDeprecated( __METHOD__, '1.24' );
+ return array();
+ }
+
+ /**@}*/
}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index 975153ac..07f62c66 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -55,8 +55,11 @@ class ApiBlock extends ApiBase {
}
$target = User::newFromName( $params['user'] );
- // Bug 38633 - if the target is a user (not an IP address), but it doesn't exist or is unusable, error.
- if ( $target instanceof User && ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) ) ) {
+ // Bug 38633 - if the target is a user (not an IP address), but it
+ // doesn't exist or is unusable, error.
+ if ( $target instanceof User &&
+ ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) )
+ ) {
$this->dieUsageMsg( array( 'nosuchuser', $params['user'] ) );
}
@@ -149,7 +152,6 @@ class ApiBlock extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => null,
'expiry' => 'never',
'reason' => '',
'anononly' => false,
@@ -166,80 +168,35 @@ class ApiBlock extends ApiBase {
public function getParamDescription() {
return array(
'user' => 'Username, IP address or IP range you want to block',
- 'token' => 'A block token previously obtained through prop=info',
- 'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.',
+ 'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. ' .
+ 'If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.',
'reason' => 'Reason for block',
'anononly' => 'Block anonymous users only (i.e. disable anonymous edits for this IP)',
'nocreate' => 'Prevent account creation',
- 'autoblock' => 'Automatically block the last used IP address, and any subsequent IP addresses they try to login from',
- 'noemail' => 'Prevent user from sending email through the wiki. (Requires the "blockemail" right.)',
+ 'autoblock' => 'Automatically block the last used IP address, and ' .
+ 'any subsequent IP addresses they try to login from',
+ 'noemail'
+ => 'Prevent user from sending email through the wiki. (Requires the "blockemail" right.)',
'hidename' => 'Hide the username from the block log. (Requires the "hideuser" right.)',
- 'allowusertalk' => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)',
+ 'allowusertalk'
+ => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)',
'reblock' => 'If the user is already blocked, overwrite the existing block',
'watchuser' => 'Watch the user/IP\'s user and talk pages',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'user' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'userID' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'expiry' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'id' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'reason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'anononly' => 'boolean',
- 'nocreate' => 'boolean',
- 'autoblock' => 'boolean',
- 'noemail' => 'boolean',
- 'hidename' => 'boolean',
- 'allowusertalk' => 'boolean',
- 'watchuser' => 'boolean'
- )
- );
- }
-
public function getDescription() {
- return 'Block a user';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'cantblock' ),
- array( 'canthide' ),
- array( 'cantblock-email' ),
- array( 'ipbblocked' ),
- array( 'ipbnounblockself' ),
- ) );
+ return 'Block a user.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike',
- 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail='
+ 'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike&token=123ABC',
+ 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
);
}
diff --git a/includes/api/ApiClearHasMsg.php b/includes/api/ApiClearHasMsg.php
new file mode 100644
index 00000000..32e20e80
--- /dev/null
+++ b/includes/api/ApiClearHasMsg.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Created on August 26, 2014
+ *
+ * Copyright © 2014 Petr Bena (benapetr@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * API module that clears the hasmsg flag for current user
+ * @ingroup API
+ */
+class ApiClearHasMsg extends ApiBase {
+ public function execute() {
+ $user = $this->getUser();
+ $user->setNewtalk( false );
+ $this->getResult()->addValue( null, $this->getModuleName(), 'success' );
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function mustBePosted() {
+ return false;
+ }
+
+ public function getDescription() {
+ return array( 'Clears the hasmsg flag for current user.' );
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=clearhasmsg' => 'Clears the hasmsg flag for current user',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:ClearHasMsg';
+ }
+}
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
index 1e35c349..48559268 100644
--- a/includes/api/ApiComparePages.php
+++ b/includes/api/ApiComparePages.php
@@ -65,19 +65,22 @@ class ApiComparePages extends ApiBase {
$difftext = $de->getDiffBody();
if ( $difftext === false ) {
- $this->dieUsage( 'The diff cannot be retrieved. ' .
- 'Maybe one or both revisions do not exist or you do not have permission to view them.', 'baddiff' );
- } else {
- ApiResult::setContent( $vals, $difftext );
+ $this->dieUsage(
+ 'The diff cannot be retrieved. Maybe one or both revisions do ' .
+ 'not exist or you do not have permission to view them.',
+ 'baddiff'
+ );
}
+ ApiResult::setContent( $vals, $difftext );
+
$this->getResult()->addValue( null, $this->getModuleName(), $vals );
}
/**
- * @param $revision int
- * @param $titleText string
- * @param $titleId int
+ * @param int $revision
+ * @param string $titleText
+ * @param int $titleId
* @return int
*/
private function revisionOrTitleOrId( $revision, $titleText, $titleId ) {
@@ -88,15 +91,20 @@ class ApiComparePages extends ApiBase {
if ( !$title || $title->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
}
+
return $title->getLatestRevID();
} elseif ( $titleId ) {
$title = Title::newFromID( $titleId );
if ( !$title ) {
$this->dieUsageMsg( array( 'nosuchpageid', $titleId ) );
}
+
return $title->getLatestRevID();
}
- $this->dieUsage( 'inputneeded', 'A title, a page ID, or a revision number is needed for both the from and the to parameters' );
+ $this->dieUsage(
+ 'A title, a page ID, or a revision number is needed for both the from and the to parameters',
+ 'inputneeded'
+ );
}
public function getAllowedParams() {
@@ -129,40 +137,13 @@ class ApiComparePages extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'fromtitle' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'fromrevid' => 'integer',
- 'totitle' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'torevid' => 'integer',
- '*' => 'string'
- )
- );
- }
-
public function getDescription() {
return array(
- 'Get the difference between 2 pages',
- 'You must pass a revision number or a page title or a page ID id for each part (1 and 2)'
+ 'Get the difference between 2 pages.',
+ 'You must pass a revision number or a page title or a page ID id for each part (1 and 2).'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'inputneeded', 'info' => 'A title or a revision is needed' ),
- array( 'invalidtitle', 'title' ),
- array( 'nosuchpageid', 'pageid' ),
- array( 'code' => 'baddiff', 'info' => 'The diff cannot be retrieved. Maybe one or both revisions do not exist or you do not have permission to view them.' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=compare&fromrev=1&torev=2' => 'Create a diff between revision 1 and 2',
diff --git a/includes/api/ApiCreateAccount.php b/includes/api/ApiCreateAccount.php
index 0e752c56..2ce532b9 100644
--- a/includes/api/ApiCreateAccount.php
+++ b/includes/api/ApiCreateAccount.php
@@ -39,7 +39,10 @@ class ApiCreateAccount extends ApiBase {
// Use userCan in order to hit GlobalBlock checks (according to Special:userlogin)
$loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
if ( !$loginTitle->userCan( 'createaccount', $this->getUser() ) ) {
- $this->dieUsage( 'You do not have the right to create a new account', 'permdenied-createaccount' );
+ $this->dieUsage(
+ 'You do not have the right to create a new account',
+ 'permdenied-createaccount'
+ );
}
if ( $this->getUser()->isBlockedFromCreateAccount() ) {
$this->dieUsage( 'You cannot create a new account because you are blocked', 'blocked' );
@@ -80,13 +83,13 @@ class ApiCreateAccount extends ApiBase {
$loginForm = new LoginForm();
$loginForm->setContext( $context );
+ wfRunHooks( 'AddNewAccountApiForm', array( $this, $loginForm ) );
$loginForm->load();
$status = $loginForm->addNewaccountInternal();
$result = array();
if ( $status->isGood() ) {
// Success!
- global $wgEmailAuthentication;
$user = $status->getValue();
if ( $params['language'] ) {
@@ -96,8 +99,13 @@ class ApiCreateAccount extends ApiBase {
if ( $params['mailpassword'] ) {
// If mailpassword was set, disable the password and send an email.
$user->setPassword( null );
- $status->merge( $loginForm->mailPasswordInternal( $user, false, 'createaccount-title', 'createaccount-text' ) );
- } elseif ( $wgEmailAuthentication && Sanitizer::validateEmail( $user->getEmail() ) ) {
+ $status->merge( $loginForm->mailPasswordInternal(
+ $user,
+ false,
+ 'createaccount-title',
+ 'createaccount-text'
+ ) );
+ } elseif ( $this->getConfig()->get( 'EmailAuthentication' ) && Sanitizer::validateEmail( $user->getEmail() ) ) {
// Send out an email authentication message if needed
$status->merge( $user->sendConfirmationMail() );
}
@@ -129,13 +137,13 @@ class ApiCreateAccount extends ApiBase {
// since not having the correct token is part of the normal
// flow of events.
$result['token'] = LoginForm::getCreateaccountToken();
- $result['result'] = 'needtoken';
+ $result['result'] = 'NeedToken';
} elseif ( !$status->isOK() ) {
// There was an error. Die now.
$this->dieStatus( $status );
} elseif ( !$status->isGood() ) {
// Status is not good, but OK. This means warnings.
- $result['result'] = 'warning';
+ $result['result'] = 'Warning';
// Add any warnings to the result
$warnings = $status->getErrorsByType( 'warning' );
@@ -148,9 +156,12 @@ class ApiCreateAccount extends ApiBase {
}
} else {
// Everything was fine.
- $result['result'] = 'success';
+ $result['result'] = 'Success';
}
+ // Give extensions a chance to modify the API result data
+ wfRunHooks( 'AddNewAccountApiResult', array( $this, $loginForm, &$result ) );
+
$apiResult->addValue( null, 'createaccount', $result );
}
@@ -171,7 +182,6 @@ class ApiCreateAccount extends ApiBase {
}
public function getAllowedParams() {
- global $wgEmailConfirmToEdit;
return array(
'name' => array(
ApiBase::PARAM_TYPE => 'user',
@@ -182,7 +192,7 @@ class ApiCreateAccount extends ApiBase {
'token' => null,
'email' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => $wgEmailConfirmToEdit
+ ApiBase::PARAM_REQUIRED => $this->getConfig()->get( 'EmailConfirmToEdit' ),
),
'realname' => null,
'mailpassword' => array(
@@ -196,6 +206,7 @@ class ApiCreateAccount extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'name' => 'Username',
'password' => "Password (ignored if {$p}mailpassword is set)",
@@ -205,82 +216,9 @@ class ApiCreateAccount extends ApiBase {
'realname' => 'Real name of user (optional)',
'mailpassword' => 'If set to any value, a random password will be emailed to the user',
'reason' => 'Optional reason for creating the account to be put in the logs',
- 'language' => 'Language code to set as default for the user (optional, defaults to content language)'
- );
- }
-
- public function getResultProperties() {
- return array(
- 'createaccount' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'success',
- 'warning',
- 'needtoken'
- )
- ),
- 'username' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'userid' => array(
- ApiBase::PROP_TYPE => 'int',
- ApiBase::PROP_NULLABLE => true
- ),
- 'token' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- )
- );
- }
-
- public function getPossibleErrors() {
- // Note the following errors aren't possible and don't need to be listed:
- // sessionfailure, nocookiesfornew, badretype
- $localErrors = array(
- 'wrongpassword', // Actually caused by wrong domain field. Riddle me that...
- 'sorbs_create_account_reason',
- 'noname',
- 'userexists',
- 'password-name-match', // from User::getPasswordValidity
- 'password-login-forbidden', // from User::getPasswordValidity
- 'noemailtitle',
- 'invalidemailaddress',
- 'externaldberror',
- 'acct_creation_throttle_hit',
- );
-
- $errors = parent::getPossibleErrors();
- // All local errors are from LoginForm, which means they're actually message keys.
- foreach ( $localErrors as $error ) {
- $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->inLanguage( 'en' )->useDatabase( false )->parse() );
- }
-
- $errors[] = array(
- 'code' => 'permdenied-createaccount',
- 'info' => 'You do not have the right to create a new account'
- );
- $errors[] = array(
- 'code' => 'blocked',
- 'info' => 'You cannot create a new account because you are blocked'
- );
- $errors[] = array(
- 'code' => 'aborted',
- 'info' => 'Account creation aborted by hook (info may vary)'
- );
- $errors[] = array(
- 'code' => 'langinvalid',
- 'info' => 'Invalid language parameter'
- );
-
- // 'passwordtooshort' has parameters. :(
- global $wgMinimalPasswordLength;
- $errors[] = array(
- 'code' => 'passwordtooshort',
- 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->inLanguage( 'en' )->useDatabase( false )->parse()
+ 'language'
+ => 'Language code to set as default for the user (optional, defaults to content language)'
);
- return $errors;
}
public function getExamples() {
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index aea10482..abca8245 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -31,7 +31,6 @@
* @ingroup API
*/
class ApiDelete extends ApiBase {
-
/**
* Extracts the title, token, and reason from the request parameters and invokes
* the local delete() function with these as arguments. It does not make use of
@@ -52,7 +51,14 @@ class ApiDelete extends ApiBase {
$user = $this->getUser();
if ( $titleObj->getNamespace() == NS_FILE ) {
- $status = self::deleteFile( $pageObj, $user, $params['token'], $params['oldimage'], $reason, false );
+ $status = self::deleteFile(
+ $pageObj,
+ $user,
+ $params['token'],
+ $params['oldimage'],
+ $reason,
+ false
+ );
} else {
$status = self::delete( $pageObj, $user, $params['token'], $reason );
}
@@ -66,8 +72,10 @@ class ApiDelete extends ApiBase {
// Deprecated parameters
if ( $params['watch'] ) {
+ $this->logFeatureUsage( 'action=delete&watch' );
$watch = 'watch';
} elseif ( $params['unwatch'] ) {
+ $this->logFeatureUsage( 'action=delete&unwatch' );
$watch = 'unwatch';
} else {
$watch = $params['watchlist'];
@@ -83,9 +91,9 @@ class ApiDelete extends ApiBase {
}
/**
- * @param $title Title
- * @param $user User doing the action
- * @param $token String
+ * @param Title $title
+ * @param User $user User doing the action
+ * @param string $token
* @return array
*/
private static function getPermissionsError( $title, $user, $token ) {
@@ -96,10 +104,10 @@ class ApiDelete extends ApiBase {
/**
* We have our own delete() function, since Article.php's implementation is split in two phases
*
- * @param $page Page|WikiPage object to work on
- * @param $user User doing the action
- * @param string $token delete token (same as edit token)
- * @param string|null $reason reason for the deletion. Autogenerated if NULL
+ * @param Page|WikiPage $page Page or WikiPage object to work on
+ * @param User $user User doing the action
+ * @param string $token Delete token (same as edit token)
+ * @param string|null $reason Reason for the deletion. Autogenerated if null
* @return Status|array
*/
public static function delete( Page $page, User $user, $token, &$reason = null ) {
@@ -121,20 +129,23 @@ class ApiDelete extends ApiBase {
}
$error = '';
+
// Luckily, Article.php provides a reusable delete function that does the hard work for us
return $page->doDeleteArticleReal( $reason, false, 0, true, $error );
}
/**
- * @param $page WikiPage|Page object to work on
- * @param $user User doing the action
- * @param $token
- * @param $oldimage
- * @param $reason
- * @param $suppress bool
+ * @param Page $page Object to work on
+ * @param User $user User doing the action
+ * @param string $token Delete token (same as edit token)
+ * @param string $oldimage Archive name
+ * @param string $reason Reason for the deletion. Autogenerated if null.
+ * @param bool $suppress Whether to mark all deleted versions as restricted
* @return Status|array
*/
- public static function deleteFile( Page $page, User $user, $token, $oldimage, &$reason = null, $suppress = false ) {
+ public static function deleteFile( Page $page, User $user, $token, $oldimage,
+ &$reason = null, $suppress = false
+ ) {
$title = $page->getTitle();
$errors = self::getPermissionsError( $title, $user, $token );
if ( count( $errors ) ) {
@@ -159,6 +170,7 @@ class ApiDelete extends ApiBase {
if ( is_null( $reason ) ) { // Log and RC don't like null reasons
$reason = '';
}
+
return FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress, $user );
}
@@ -176,10 +188,6 @@ class ApiDelete extends ApiBase {
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => null,
'watch' => array(
ApiBase::PARAM_DFLT => false,
@@ -204,58 +212,33 @@ class ApiDelete extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'title' => "Title of the page you want to delete. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the page you want to delete. Cannot be used together with {$p}title",
- 'token' => 'A delete token previously retrieved through prop=info',
- 'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used',
+ 'reason'
+ => 'Reason for the deletion. If not set, an automatically generated reason will be used',
'watch' => 'Add the page to your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
'unwatch' => 'Remove the page from your watchlist',
'oldimage' => 'The name of the old image to delete as provided by iiprop=archivename'
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'title' => 'string',
- 'reason' => 'string',
- 'logid' => 'integer'
- )
- );
- }
-
public function getDescription() {
- return 'Delete a page';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'notanarticle' ),
- array( 'hookaborted', 'error' ),
- array( 'delete-toobig', 'limit' ),
- array( 'cannotdelete', 'title' ),
- array( 'invalidoldimage' ),
- array( 'nodeleteablefile' ),
- )
- );
+ return 'Delete a page.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
'api.php?action=delete&title=Main%20Page&token=123ABC' => 'Delete the Main Page',
- 'api.php?action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move' => 'Delete the Main Page with the reason "Preparing for move"',
+ 'api.php?action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move'
+ => 'Delete the Main Page with the reason "Preparing for move"',
);
}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index e5ef3b7e..6ea5d202 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -53,7 +53,7 @@ class ApiDisabled extends ApiBase {
}
public function getDescription() {
- return 'This module has been disabled';
+ return 'This module has been disabled.';
}
public function getExamples() {
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index 51c9efc6..a423b560 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -32,15 +32,14 @@
* @ingroup API
*/
class ApiEditPage extends ApiBase {
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
- is_null( $params['prependtext'] ) &&
- $params['undo'] == 0 )
- {
+ is_null( $params['prependtext'] ) &&
+ $params['undo'] == 0
+ ) {
$this->dieUsageMsg( 'missingtext' );
}
@@ -49,12 +48,15 @@ class ApiEditPage extends ApiBase {
$apiResult = $this->getResult();
if ( $params['redirect'] ) {
+ if ( $params['prependtext'] === null && $params['appendtext'] === null && $params['section'] !== 'new' ) {
+ $this->dieUsage( 'You have attempted to edit using the "redirect"-following mode, which must be used in conjuction with section=new, prependtext, or appendtext.', 'redirect-appendonly' );
+ }
if ( $titleObj->isRedirect() ) {
$oldTitle = $titleObj;
$titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
- ->getContent( Revision::FOR_THIS_USER, $user )
- ->getRedirectChain();
+ ->getContent( Revision::FOR_THIS_USER, $user )
+ ->getRedirectChain();
// array_shift( $titles );
$redirValues = array();
@@ -88,7 +90,8 @@ class ApiEditPage extends ApiBase {
$contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
}
- // @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such
+ // @todo Ask handler whether direct editing is supported at all! make
+ // allowFlatEdit() method or some such
if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
$params['contentformat'] = $contentHandler->getDefaultFormat();
@@ -101,7 +104,7 @@ class ApiEditPage extends ApiBase {
$model = $contentHandler->getModelID();
$this->dieUsage( "The requested format $contentFormat is not supported for content model " .
- " $model used by $name", 'badformat' );
+ " $model used by $name", 'badformat' );
}
if ( $params['createonly'] && $titleObj->exists() ) {
@@ -121,8 +124,7 @@ class ApiEditPage extends ApiBase {
}
$toMD5 = $params['text'];
- if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
- {
+ if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) ) {
$content = $pageObj->getContent();
if ( !$content ) {
@@ -138,6 +140,7 @@ class ApiEditPage extends ApiBase {
$content = ContentHandler::makeContent( $text, $this->getTitle() );
} catch ( MWContentSerializationException $ex ) {
$this->dieUsage( $ex->getMessage(), 'parseerror' );
+
return;
}
} else {
@@ -156,7 +159,10 @@ class ApiEditPage extends ApiBase {
if ( !is_null( $params['section'] ) ) {
if ( !$contentHandler->supportsSections() ) {
$modelName = $contentHandler->getModelID();
- $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
+ $this->dieUsage(
+ "Sections are not supported for this content model: $modelName.",
+ 'sectionsnotsupported'
+ );
}
if ( $params['section'] == 'new' ) {
@@ -164,7 +170,7 @@ class ApiEditPage extends ApiBase {
$content = null;
} else {
// Process the content for section edits
- $section = intval( $params['section'] );
+ $section = $params['section'];
$content = $content->getSection( $section );
if ( !$content ) {
@@ -187,7 +193,7 @@ class ApiEditPage extends ApiBase {
if ( $params['undoafter'] > 0 ) {
if ( $params['undo'] < $params['undoafter'] ) {
list( $params['undo'], $params['undoafter'] ) =
- array( $params['undoafter'], $params['undo'] );
+ array( $params['undoafter'], $params['undo'] );
}
$undoafterRev = Revision::newFromID( $params['undoafter'] );
}
@@ -204,13 +210,19 @@ class ApiEditPage extends ApiBase {
}
if ( $undoRev->getPage() != $pageObj->getID() ) {
- $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
+ $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(),
+ $titleObj->getPrefixedText() ) );
}
if ( $undoafterRev->getPage() != $pageObj->getID() ) {
- $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
+ $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(),
+ $titleObj->getPrefixedText() ) );
}
- $newContent = $contentHandler->getUndoContent( $pageObj->getRevision(), $undoRev, $undoafterRev );
+ $newContent = $contentHandler->getUndoContent(
+ $pageObj->getRevision(),
+ $undoRev,
+ $undoafterRev
+ );
if ( !$newContent ) {
$this->dieUsageMsg( 'undo-failure' );
@@ -220,8 +232,11 @@ class ApiEditPage extends ApiBase {
// If no summary was given and we only undid one rev,
// use an autosummary
- if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] ) {
- $params['summary'] = wfMessage( 'undo-summary', $params['undo'], $undoRev->getUserText() )->inContentLanguage()->text();
+ if ( is_null( $params['summary'] ) &&
+ $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo']
+ ) {
+ $params['summary'] = wfMessage( 'undo-summary' )
+ ->params ( $params['undo'], $undoRev->getUserText() )->inContentLanguage()->text();
}
}
@@ -237,7 +252,8 @@ class ApiEditPage extends ApiBase {
'format' => $contentFormat,
'model' => $contentHandler->getModelID(),
'wpEditToken' => $params['token'],
- 'wpIgnoreBlankSummary' => ''
+ 'wpIgnoreBlankSummary' => '',
+ 'wpIgnoreBlankArticle' => true
);
if ( !is_null( $params['summary'] ) ) {
@@ -276,12 +292,12 @@ class ApiEditPage extends ApiBase {
}
if ( !is_null( $params['section'] ) ) {
- $section = intval( $params['section'] );
- if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' ) {
- $this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" );
+ $section = $params['section'];
+ if ( !preg_match( '/^((T-)?\d+|new)$/', $section ) ) {
+ $this->dieUsage( "The section parameter must be a valid section id or 'new'", "invalidsection" );
}
$content = $pageObj->getContent();
- if ( $section !== 0 && ( !$content || !$content->getSection( $section ) ) ) {
+ if ( $section !== '0' && $section != 'new' && ( !$content || !$content->getSection( $section ) ) ) {
$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
}
$requestArray['wpSection'] = $params['section'];
@@ -293,8 +309,10 @@ class ApiEditPage extends ApiBase {
// Deprecated parameters
if ( $params['watch'] ) {
+ $this->logFeatureUsage( 'action=edit&watch' );
$watch = true;
} elseif ( $params['unwatch'] ) {
+ $this->logFeatureUsage( 'action=edit&unwatch' );
$watch = false;
}
@@ -333,9 +351,9 @@ class ApiEditPage extends ApiBase {
// The following is needed to give the hook the full content of the
// new revision rather than just the current section. (Bug 52077)
- if ( !is_null( $params['section'] ) && $contentHandler->supportsSections() && $titleObj->exists() ) {
-
- $sectionTitle = '';
+ if ( !is_null( $params['section'] ) &&
+ $contentHandler->supportsSections() && $titleObj->exists()
+ ) {
// If sectiontitle is set, use it, otherwise use the summary as the section title (for
// backwards compatibility with old forms/bots).
if ( $ep->sectiontitle !== '' ) {
@@ -346,7 +364,11 @@ class ApiEditPage extends ApiBase {
$contentObj = $contentHandler->unserializeContent( $content, $contentFormat );
- $fullContentObj = $articleObject->replaceSectionContent( $params['section'], $contentObj, $sectionTitle );
+ $fullContentObj = $articleObject->replaceSectionContent(
+ $params['section'],
+ $contentObj,
+ $sectionTitle
+ );
if ( $fullContentObj ) {
$content = $fullContentObj->serialize( $contentFormat );
} else {
@@ -363,10 +385,11 @@ class ApiEditPage extends ApiBase {
if ( count( $r ) ) {
$r['result'] = 'Failure';
$apiResult->addValue( null, $this->getModuleName(), $r );
+
return;
- } else {
- $this->dieUsageMsg( 'hookaborted' );
}
+
+ $this->dieUsageMsg( 'hookaborted' );
}
// Do the actual save
@@ -379,7 +402,6 @@ class ApiEditPage extends ApiBase {
$status = $ep->internalAttemptSave( $result, $user->isAllowed( 'bot' ) && $params['bot'] );
$wgRequest = $oldRequest;
- global $wgMaxArticleSize;
switch ( $status->value ) {
case EditPage::AS_HOOK_ERROR:
@@ -403,7 +425,7 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
case EditPage::AS_CONTENT_TOO_BIG:
- $this->dieUsageMsg( array( 'contenttoobig', $wgMaxArticleSize ) );
+ $this->dieUsageMsg( array( 'contenttoobig', $this->getConfig()->get( 'MaxArticleSize' ) ) );
case EditPage::AS_READ_ONLY_PAGE_ANON:
$this->dieUsageMsg( 'noedit-anon' );
@@ -481,52 +503,6 @@ class ApiEditPage extends ApiBase {
return 'Create and edit pages.';
}
- public function getPossibleErrors() {
- global $wgMaxArticleSize;
-
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'missingtext' ),
- array( 'createonly-exists' ),
- array( 'nocreate-missing' ),
- array( 'nosuchrevid', 'undo' ),
- array( 'nosuchrevid', 'undoafter' ),
- array( 'revwrongpage', 'id', 'text' ),
- array( 'undo-failure' ),
- array( 'hashcheckfailed' ),
- array( 'hookaborted' ),
- array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
- array( 'noimageredirect-anon' ),
- array( 'noimageredirect-logged' ),
- array( 'spamdetected', 'spam' ),
- array( 'summaryrequired' ),
- array( 'blockedtext' ),
- array( 'contenttoobig', $wgMaxArticleSize ),
- array( 'noedit-anon' ),
- array( 'noedit' ),
- array( 'actionthrottledtext' ),
- array( 'wasdeleted' ),
- array( 'nocreate-loggedin' ),
- array( 'blankpage' ),
- array( 'editconflict' ),
- array( 'emptynewsection' ),
- array( 'unknownerror', 'retval' ),
- array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
- array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
- array( 'code' => 'sectionsnotsupported', 'info' => 'Sections are not supported for this type of page.' ),
- array( 'code' => 'editnotsupported', 'info' => 'Editing of this type of page is not supported using '
- . 'the text based edit API.' ),
- array( 'code' => 'appendnotsupported', 'info' => 'This type of page can not be edited by appending '
- . 'or prepending text.' ),
- array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied to '
- . 'the page\'s content model' ),
- array( 'customcssprotected' ),
- array( 'customjsprotected' ),
- )
- );
- }
-
public function getAllowedParams() {
return array(
'title' => array(
@@ -540,10 +516,6 @@ class ApiEditPage extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
),
'text' => null,
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => null,
'minor' => false,
'notminor' => false,
@@ -594,36 +566,47 @@ class ApiEditPage extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'title' => "Title of the page you want to edit. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the page you want to edit. Cannot be used together with {$p}title",
'section' => 'Section number. 0 for the top section, \'new\' for a new section',
'sectiontitle' => 'The title for a new section',
'text' => 'Page content',
- 'token' => array( 'Edit token. You can get one of these through prop=info.',
- "The token should always be sent as the last parameter, or at least, after the {$p}text parameter"
+ 'token' => array(
+ /* Standard description is automatically prepended */
+ 'The token should always be sent as the last parameter, or at ' .
+ "least, after the {$p}text parameter"
),
- 'summary' => "Edit summary. Also section title when {$p}section=new and {$p}sectiontitle is not set",
+ 'summary'
+ => "Edit summary. Also section title when {$p}section=new and {$p}sectiontitle is not set",
'minor' => 'Minor edit',
'notminor' => 'Non-minor edit',
'bot' => 'Mark this edit as bot',
- 'basetimestamp' => array( 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
- 'Used to detect edit conflicts; leave unset to ignore conflicts'
+ 'basetimestamp' => array(
+ 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
+ 'Used to detect edit conflicts; leave unset to ignore conflicts'
),
- 'starttimestamp' => array( 'Timestamp when you obtained the edit token.',
- 'Used to detect edit conflicts; leave unset to ignore conflicts'
+ 'starttimestamp' => array(
+ 'Timestamp when you began the editing process, e.g. when the current page content ' .
+ 'was loaded for editing.',
+ 'Used to detect edit conflicts; leave unset to ignore conflicts'
),
'recreate' => 'Override any errors about the article having been deleted in the meantime',
'createonly' => 'Don\'t edit the page if it exists already',
'nocreate' => 'Throw an error if the page doesn\'t exist',
'watch' => 'Add the page to your watchlist',
'unwatch' => 'Remove the page from your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- 'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
- 'If set, the edit won\'t be done unless the hash is correct' ),
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
+ 'md5' => array(
+ "The MD5 hash of the {$p}text parameter, or the {$p}prependtext " .
+ "and {$p}appendtext parameters concatenated.",
+ 'If set, the edit won\'t be done unless the hash is correct'
+ ),
'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
'appendtext' => array( "Add this text to the end of the page. Overrides {$p}text.",
- "Use {$p}section=new to append a new section" ),
+ "Use {$p}section=new to append a new section" ),
'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
'redirect' => 'Automatically resolve redirects',
@@ -632,56 +615,20 @@ class ApiEditPage extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'new' => 'boolean',
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'Failure'
- ),
- ),
- 'pageid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'title' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'nochange' => 'boolean',
- 'oldrevid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'newrevid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'newtimestamp' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\'
+ 'api.php?action=edit&title=Test&summary=test%20summary&' .
+ 'text=article%20content&basetimestamp=20070824123454&token=%2B\\'
=> 'Edit a page (anonymous user)',
- 'api.php?action=edit&title=Test&summary=NOTOC&minor=&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\'
+ 'api.php?action=edit&title=Test&summary=NOTOC&minor=&' .
+ 'prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\'
=> 'Prepend __NOTOC__ to a page (anonymous user)',
- 'api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\'
+ 'api.php?action=edit&title=Test&undo=13585&undoafter=13579&' .
+ 'basetimestamp=20070824123454&token=%2B\\'
=> 'Undo r13579 through r13585 with autosummary (anonymous user)',
);
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index cd0d0cba..9870b2de 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -40,7 +40,11 @@ class ApiEmailUser extends ApiBase {
}
// Check permissions and errors
- $error = SpecialEmailUser::getPermissionsError( $this->getUser(), $params['token'] );
+ $error = SpecialEmailUser::getPermissionsError(
+ $this->getUser(),
+ $params['token'],
+ $this->getConfig()
+ );
if ( $error ) {
$this->dieUsageMsg( array( $error ) );
}
@@ -94,10 +98,6 @@ class ApiEmailUser extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'ccme' => false,
);
}
@@ -107,49 +107,22 @@ class ApiEmailUser extends ApiBase {
'target' => 'User to send email to',
'subject' => 'Subject header',
'text' => 'Mail body',
- 'token' => 'A token previously acquired via prop=info',
'ccme' => 'Send a copy of this mail to me',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'Failure'
- ),
- ),
- 'message' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
return 'Email a user.';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'usermaildisabled' ),
- ) );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=emailuser&target=WikiSysop&text=Content' => 'Send an email to the User "WikiSysop" with the text "Content"',
+ 'api.php?action=emailuser&target=WikiSysop&text=Content&token=123ABC'
+ => 'Send an email to the User "WikiSysop" with the text "Content"',
);
}
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index d5c789c3..8a3b534d 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -39,6 +39,18 @@ class ApiExpandTemplates extends ApiBase {
// Get parameters
$params = $this->extractRequestParams();
+ $this->requireMaxOneParameter( $params, 'prop', 'generatexml' );
+
+ if ( $params['prop'] === null ) {
+ $this->logFeatureUsage( 'action=expandtemplates&!prop' );
+ $this->setWarning( 'Because no values have been specified for the prop parameter, a ' .
+ 'legacy format has been used for the output. This format is deprecated, and in ' .
+ 'the future, a default value will be set for the prop parameter, causing the new' .
+ 'format to always be used.' );
+ $prop = array();
+ } else {
+ $prop = array_flip( $params['prop'] );
+ }
// Create title for parser
$title_obj = Title::newFromText( $params['title'] );
@@ -56,7 +68,13 @@ class ApiExpandTemplates extends ApiBase {
$options->setRemoveComments( false );
}
- if ( $params['generatexml'] ) {
+ $retval = array();
+
+ if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
+ if ( !isset( $prop['parsetree'] ) ) {
+ $this->logFeatureUsage( 'action=expandtemplates&generatexml' );
+ }
+
$wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $params['text'] );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
@@ -64,16 +82,54 @@ class ApiExpandTemplates extends ApiBase {
} else {
$xml = $dom->__toString();
}
- $xml_result = array();
- ApiResult::setContent( $xml_result, $xml );
- $result->addValue( null, 'parsetree', $xml_result );
+ if ( isset( $prop['parsetree'] ) ) {
+ unset( $prop['parsetree'] );
+ $retval['parsetree'] = $xml;
+ } else {
+ // the old way
+ $xml_result = array();
+ ApiResult::setContent( $xml_result, $xml );
+ $result->addValue( null, 'parsetree', $xml_result );
+ }
}
- $retval = $wgParser->preprocess( $params['text'], $title_obj, $options );
- // Return result
- $retval_array = array();
- ApiResult::setContent( $retval_array, $retval );
- $result->addValue( null, $this->getModuleName(), $retval_array );
+ // if they didn't want any output except (probably) the parse tree,
+ // then don't bother actually fully expanding it
+ if ( $prop || $params['prop'] === null ) {
+ $wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
+ $frame = $wgParser->getPreprocessor()->newFrame();
+ $wikitext = $wgParser->preprocess( $params['text'], $title_obj, $options, null, $frame );
+ if ( $params['prop'] === null ) {
+ // the old way
+ ApiResult::setContent( $retval, $wikitext );
+ } else {
+ if ( isset( $prop['categories'] ) ) {
+ $categories = $wgParser->getOutput()->getCategories();
+ if ( !empty( $categories ) ) {
+ $categories_result = array();
+ foreach ( $categories as $category => $sortkey ) {
+ $entry = array();
+ $entry['sortkey'] = $sortkey;
+ ApiResult::setContent( $entry, $category );
+ $categories_result[] = $entry;
+ }
+ $result->setIndexedTagName( $categories_result, 'category' );
+ $retval['categories'] = $categories_result;
+ }
+ }
+ if ( isset( $prop['volatile'] ) && $frame->isVolatile() ) {
+ $retval['volatile'] = '';
+ }
+ if ( isset( $prop['ttl'] ) && $frame->getTTL() !== null ) {
+ $retval['ttl'] = $frame->getTTL();
+ }
+ if ( isset( $prop['wikitext'] ) ) {
+ $retval['wikitext'] = $wikitext;
+ }
+ }
+ }
+ $result->setSubelements( $retval, array( 'wikitext', 'parsetree' ) );
+ $result->addValue( null, $this->getModuleName(), $retval );
}
public function getAllowedParams() {
@@ -85,8 +141,21 @@ class ApiExpandTemplates extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
),
- 'generatexml' => false,
+ 'prop' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'wikitext',
+ 'categories',
+ 'volatile',
+ 'ttl',
+ 'parsetree',
+ ),
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'includecomments' => false,
+ 'generatexml' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
);
}
@@ -94,27 +163,26 @@ class ApiExpandTemplates extends ApiBase {
return array(
'text' => 'Wikitext to convert',
'title' => 'Title of page',
- 'generatexml' => 'Generate XML parse tree',
+ 'prop' => array(
+ 'Which pieces of information to get',
+ ' wikitext - The expanded wikitext',
+ ' categories - Any categories present in the input that are not represented in ' .
+ 'the wikitext output',
+ ' volatile - Whether the output is volatile and should not be reused ' .
+ 'elsewhere within the page',
+ ' ttl - The maximum time after which caches of the result should be ' .
+ 'invalidated',
+ ' parsetree - The XML parse tree of the input',
+ 'Note that if no values are selected, the result will contain the wikitext,',
+ 'but the output will be in a deprecated format.',
+ ),
'includecomments' => 'Whether to include HTML comments in the output',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => 'string'
- )
+ 'generatexml' => 'Generate XML parse tree (replaced by prop=parsetree)',
);
}
public function getDescription() {
- return 'Expands all templates in wikitext';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'invalidtitle', 'title' ),
- ) );
+ return 'Expands all templates in wikitext.';
}
public function getExamples() {
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
index 05691093..374203eb 100644
--- a/includes/api/ApiFeedContributions.php
+++ b/includes/api/ApiFeedContributions.php
@@ -41,30 +41,29 @@ class ApiFeedContributions extends ApiBase {
public function execute() {
$params = $this->extractRequestParams();
- global $wgFeed, $wgFeedClasses, $wgSitename, $wgLanguageCode;
-
- if ( !$wgFeed ) {
+ $config = $this->getConfig();
+ if ( !$config->get( 'Feed' ) ) {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if ( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
+ $feedClasses = $config->get( 'FeedClasses' );
+ if ( !isset( $feedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
- global $wgMiserMode;
- if ( $params['showsizediff'] && $wgMiserMode ) {
+ if ( $params['showsizediff'] && $this->getConfig()->get( 'MiserMode' ) ) {
$this->dieUsage( 'Size difference is disabled in Miser Mode', 'sizediffdisabled' );
}
$msg = wfMessage( 'Contributions' )->inContentLanguage()->text();
- $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']';
+ $feedTitle = $config->get( 'Sitename' ) . ' - ' . $msg . ' [' . $config->get( 'LanguageCode' ) . ']';
$feedUrl = SpecialPage::getTitleFor( 'Contributions', $params['user'] )->getFullURL();
$target = $params['user'] == 'newbies'
- ? 'newbies'
- : Title::makeTitleSafe( NS_USER, $params['user'] )->getText();
+ ? 'newbies'
+ : Title::makeTitleSafe( NS_USER, $params['user'] )->getText();
- $feed = new $wgFeedClasses[$params['feedformat']] (
+ $feed = new $feedClasses[$params['feedformat']] (
$feedTitle,
htmlspecialchars( $msg ),
$feedUrl
@@ -78,12 +77,24 @@ class ApiFeedContributions extends ApiBase {
'tagFilter' => $params['tagfilter'],
'deletedOnly' => $params['deletedonly'],
'topOnly' => $params['toponly'],
+ 'newOnly' => $params['newonly'],
'showSizeDiff' => $params['showsizediff'],
) );
+ $feedLimit = $this->getConfig()->get( 'FeedLimit' );
+ if ( $pager->getLimit() > $feedLimit ) {
+ $pager->setLimit( $feedLimit );
+ }
+
$feedItems = array();
if ( $pager->getNumRows() > 0 ) {
+ $count = 0;
+ $limit = $pager->getLimit();
foreach ( $pager->mResult as $row ) {
+ // ContribsPager selects one more row for navigation, skip that row
+ if ( ++$count > $limit ) {
+ break;
+ }
$feedItems[] = $this->feedItem( $row );
}
}
@@ -101,17 +112,18 @@ class ApiFeedContributions extends ApiBase {
return new FeedItem(
$title->getPrefixedText(),
$this->feedItemDesc( $revision ),
- $title->getFullURL(),
+ $title->getFullURL( array( 'diff' => $revision->getId() ) ),
$date,
$this->feedItemAuthor( $revision ),
$comments
);
}
+
return null;
}
/**
- * @param $revision Revision
+ * @param Revision $revision
* @return string
*/
protected function feedItemAuthor( $revision ) {
@@ -119,7 +131,7 @@ class ApiFeedContributions extends ApiBase {
}
/**
- * @param $revision Revision
+ * @param Revision $revision
* @return string
*/
protected function feedItemDesc( $revision ) {
@@ -142,12 +154,13 @@ class ApiFeedContributions extends ApiBase {
htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
"</p>\n<hr />\n<div>" . $html . "</div>";
}
+
return '';
}
public function getAllowedParams() {
- global $wgFeedClasses;
- $feedFormatNames = array_keys( $wgFeedClasses );
+ $feedFormatNames = array_keys( $this->getConfig()->get( 'FeedClasses' ) );
+
return array(
'feedformat' => array(
ApiBase::PARAM_DFLT => 'rss',
@@ -173,6 +186,7 @@ class ApiFeedContributions extends ApiBase {
),
'deletedonly' => false,
'toponly' => false,
+ 'newonly' => false,
'showsizediff' => false,
);
}
@@ -187,20 +201,13 @@ class ApiFeedContributions extends ApiBase {
'tagfilter' => 'Filter contributions that have these tags',
'deletedonly' => 'Show only deleted contributions',
'toponly' => 'Only show edits that are latest revisions',
+ 'newonly' => 'Only show edits that are page creations',
'showsizediff' => 'Show the size difference between revisions. Disabled in Miser Mode',
);
}
public function getDescription() {
- return 'Returns a user contributions feed';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ),
- array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ),
- array( 'code' => 'sizediffdisabled', 'info' => 'Size difference is disabled in Miser Mode' ),
- ) );
+ return 'Returns a user contributions feed.';
}
public function getExamples() {
diff --git a/includes/api/ApiFeedRecentChanges.php b/includes/api/ApiFeedRecentChanges.php
new file mode 100644
index 00000000..7239a296
--- /dev/null
+++ b/includes/api/ApiFeedRecentChanges.php
@@ -0,0 +1,207 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.23
+ */
+
+/**
+ * Recent changes feed.
+ *
+ * @ingroup API
+ */
+class ApiFeedRecentChanges extends ApiBase {
+
+ /**
+ * This module uses a custom feed wrapper printer.
+ *
+ * @return ApiFormatFeedWrapper
+ */
+ public function getCustomPrinter() {
+ return new ApiFormatFeedWrapper( $this->getMain() );
+ }
+
+ /**
+ * Format the rows (generated by SpecialRecentchanges or SpecialRecentchangeslinked)
+ * as an RSS/Atom feed.
+ */
+ public function execute() {
+ $config = $this->getConfig();
+
+ $this->params = $this->extractRequestParams();
+
+ if ( !$config->get( 'Feed' ) ) {
+ $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
+ }
+
+ $feedClasses = $config->get( 'FeedClasses' );
+ if ( !isset( $feedClasses[$this->params['feedformat']] ) ) {
+ $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
+ }
+
+ $this->getMain()->setCacheMode( 'public' );
+ if ( !$this->getMain()->getParameter( 'smaxage' ) ) {
+ // bug 63249: This page gets hit a lot, cache at least 15 seconds.
+ $this->getMain()->setCacheMaxAge( 15 );
+ }
+
+ $feedFormat = $this->params['feedformat'];
+ $specialClass = $this->params['target'] !== null
+ ? 'SpecialRecentchangeslinked'
+ : 'SpecialRecentchanges';
+
+ $formatter = $this->getFeedObject( $feedFormat, $specialClass );
+
+ // Everything is passed implicitly via $wgRequest… :(
+ // The row-getting functionality should maybe be factored out of ChangesListSpecialPage too…
+ $rc = new $specialClass();
+ $rows = $rc->getRows();
+
+ $feedItems = $rows ? ChangesFeed::buildItems( $rows ) : array();
+
+ ApiFormatFeedWrapper::setResult( $this->getResult(), $formatter, $feedItems );
+ }
+
+ /**
+ * Return a ChannelFeed object.
+ *
+ * @param string $feedFormat Feed's format (either 'rss' or 'atom')
+ * @param string $specialClass Relevant special page name (either 'SpecialRecentchanges' or
+ * 'SpecialRecentchangeslinked')
+ * @return ChannelFeed
+ */
+ public function getFeedObject( $feedFormat, $specialClass ) {
+ if ( $specialClass === 'SpecialRecentchangeslinked' ) {
+ $title = Title::newFromText( $this->params['target'] );
+ if ( !$title ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $this->params['target'] ) );
+ }
+
+ $feed = new ChangesFeed( $feedFormat, false );
+ $feedObj = $feed->getFeedObject(
+ $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() )
+ ->inContentLanguage()->text(),
+ $this->msg( 'recentchangeslinked-feed' )->inContentLanguage()->text(),
+ SpecialPage::getTitleFor( 'Recentchangeslinked' )->getFullURL()
+ );
+ } else {
+ $feed = new ChangesFeed( $feedFormat, 'rcfeed' );
+ $feedObj = $feed->getFeedObject(
+ $this->msg( 'recentchanges' )->inContentLanguage()->text(),
+ $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(),
+ SpecialPage::getTitleFor( 'Recentchanges' )->getFullURL()
+ );
+ }
+
+ return $feedObj;
+ }
+
+ public function getAllowedParams() {
+ $config = $this->getConfig();
+ $feedFormatNames = array_keys( $config->get( 'FeedClasses' ) );
+
+ $ret = array(
+ 'feedformat' => array(
+ ApiBase::PARAM_DFLT => 'rss',
+ ApiBase::PARAM_TYPE => $feedFormatNames,
+ ),
+
+ 'namespace' => array(
+ ApiBase::PARAM_TYPE => 'namespace',
+ ),
+ 'invert' => false,
+ 'associated' => false,
+
+ 'days' => array(
+ ApiBase::PARAM_DFLT => 7,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 50,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => $config->get( 'FeedLimit' ),
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
+ 'from' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ),
+
+ 'hideminor' => false,
+ 'hidebots' => false,
+ 'hideanons' => false,
+ 'hideliu' => false,
+ 'hidepatrolled' => false,
+ 'hidemyself' => false,
+
+ 'tagfilter' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
+
+ 'target' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
+ 'showlinkedto' => false,
+ );
+
+ if ( $config->get( 'AllowCategorizedRecentChanges' ) ) {
+ $ret += array(
+ 'categories' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'categories_any' => false,
+ );
+ }
+
+ return $ret;
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'feedformat' => 'The format of the feed',
+ 'namespace' => 'Namespace to limit the results to',
+ 'invert' => 'All namespaces but the selected one',
+ 'associated' => 'Include associated (talk or main) namespace',
+ 'days' => 'Days to limit the results to',
+ 'limit' => 'Maximum number of results to return',
+ 'from' => 'Show changes since then',
+ 'hideminor' => 'Hide minor changes',
+ 'hidebots' => 'Hide changes made by bots',
+ 'hideanons' => 'Hide changes made by anonymous users',
+ 'hideliu' => 'Hide changes made by registered users',
+ 'hidepatrolled' => 'Hide patrolled changes',
+ 'hidemyself' => 'Hide changes made by yourself',
+ 'tagfilter' => 'Filter by tag',
+ 'target' => 'Show only changes on pages linked from this page',
+ 'showlinkedto' => 'Show changes on pages linked to the selected page instead',
+ 'categories' => 'Show only changes on pages in all of these categories',
+ 'categories_any' => 'Show only changes on pages in any of the categories instead',
+ );
+ }
+
+ public function getDescription() {
+ return 'Returns a recent changes feed';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=feedrecentchanges',
+ 'api.php?action=feedrecentchanges&days=30'
+ );
+ }
+}
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index fbb70fbc..6aef8fc2 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -34,7 +34,6 @@
class ApiFeedWatchlist extends ApiBase {
private $watchlistModule = null;
- private $linkToDiffs = false;
private $linkToSections = false;
/**
@@ -51,16 +50,16 @@ class ApiFeedWatchlist extends ApiBase {
* Wrap the result as an RSS/Atom feed.
*/
public function execute() {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgSitename, $wgLanguageCode;
-
+ $config = $this->getConfig();
+ $feedClasses = $config->get( 'FeedClasses' );
try {
$params = $this->extractRequestParams();
- if ( !$wgFeed ) {
+ if ( !$config->get( 'Feed' ) ) {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if ( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
+ if ( !isset( $feedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
@@ -73,10 +72,10 @@ class ApiFeedWatchlist extends ApiBase {
'meta' => 'siteinfo',
'siprop' => 'general',
'list' => 'watchlist',
- 'wlprop' => 'title|user|comment|timestamp',
+ 'wlprop' => 'title|user|comment|timestamp|ids',
'wldir' => 'older', // reverse order - from newest to oldest
'wlend' => $endTime, // stop at this time
- 'wllimit' => min( 50, $wgFeedLimit )
+ 'wllimit' => min( 50, $this->getConfig()->get( 'FeedLimit' ) )
);
if ( $params['wlowner'] !== null ) {
@@ -95,12 +94,6 @@ class ApiFeedWatchlist extends ApiBase {
$fauxReqArr['wltype'] = $params['wltype'];
}
- // Support linking to diffs instead of article
- if ( $params['linktodiffs'] ) {
- $this->linkToDiffs = true;
- $fauxReqArr['wlprop'] .= '|ids';
- }
-
// Support linking directly to sections when possible
// (possible only if section name is present in comment)
if ( $params['linktosections'] ) {
@@ -129,24 +122,29 @@ class ApiFeedWatchlist extends ApiBase {
$msg = wfMessage( 'watchlist' )->inContentLanguage()->text();
- $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']';
+ $feedTitle = $this->getConfig()->get( 'Sitename' ) . ' - ' . $msg . ' [' . $this->getConfig()->get( 'LanguageCode' ) . ']';
$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
- $feed = new $wgFeedClasses[$params['feedformat']] ( $feedTitle, htmlspecialchars( $msg ), $feedUrl );
+ $feed = new $feedClasses[$params['feedformat']] (
+ $feedTitle,
+ htmlspecialchars( $msg ),
+ $feedUrl
+ );
ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
-
} catch ( Exception $e ) {
-
// Error results should not be cached
$this->getMain()->setCacheMaxAge( 0 );
- $feedTitle = $wgSitename . ' - Error - ' . wfMessage( 'watchlist' )->inContentLanguage()->text() . ' [' . $wgLanguageCode . ']';
+ // @todo FIXME: Localise brackets
+ $feedTitle = $this->getConfig()->get( 'Sitename' ) . ' - Error - ' .
+ wfMessage( 'watchlist' )->inContentLanguage()->text() .
+ ' [' . $this->getConfig()->get( 'LanguageCode' ) . ']';
$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
$feedFormat = isset( $params['feedformat'] ) ? $params['feedformat'] : 'rss';
$msg = wfMessage( 'watchlist' )->inContentLanguage()->escaped();
- $feed = new $wgFeedClasses[$feedFormat] ( $feedTitle, $msg, $feedUrl );
+ $feed = new $feedClasses[$feedFormat] ( $feedTitle, $msg, $feedUrl );
if ( $e instanceof UsageException ) {
$errorCode = $e->getCodeString();
@@ -162,13 +160,13 @@ class ApiFeedWatchlist extends ApiBase {
}
/**
- * @param $info array
+ * @param array $info
* @return FeedItem
*/
private function createFeedItem( $info ) {
$titleStr = $info['title'];
$title = Title::newFromText( $titleStr );
- if ( $this->linkToDiffs && isset( $info['revid'] ) ) {
+ if ( isset( $info['revid'] ) ) {
$titleUrl = $title->getFullURL( array( 'diff' => $info['revid'] ) );
} else {
$titleUrl = $title->getFullURL();
@@ -179,8 +177,11 @@ class ApiFeedWatchlist extends ApiBase {
// The anchor won't work for sections that have dupes on page
// as there's no way to strip that info from ApiWatchlist (apparently?).
// RegExp in the line below is equal to Linker::formatAutocomments().
- if ( $this->linkToSections && $comment !== null && preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches ) ) {
+ if ( $this->linkToSections && $comment !== null &&
+ preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches )
+ ) {
global $wgParser;
+
$sectionTitle = $wgParser->stripSectionName( $matches[2] );
$sectionTitle = Sanitizer::normalizeSectionNameWhitespace( $sectionTitle );
$titleUrl .= Title::newFromText( '#' . $sectionTitle )->getFragmentForURL();
@@ -199,12 +200,12 @@ class ApiFeedWatchlist extends ApiBase {
$this->watchlistModule = $this->getMain()->getModuleManager()->getModule( 'query' )
->getModuleManager()->getModule( 'watchlist' );
}
+
return $this->watchlistModule;
}
public function getAllowedParams( $flags = 0 ) {
- global $wgFeedClasses;
- $feedFormatNames = array_keys( $wgFeedClasses );
+ $feedFormatNames = array_keys( $this->getConfig()->get( 'FeedClasses' ) );
$ret = array(
'feedformat' => array(
ApiBase::PARAM_DFLT => 'rss',
@@ -216,7 +217,6 @@ class ApiFeedWatchlist extends ApiBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => 72,
),
- 'linktodiffs' => false,
'linktosections' => false,
);
if ( $flags ) {
@@ -235,15 +235,16 @@ class ApiFeedWatchlist extends ApiBase {
$ret['wltype'] = null;
$ret['wlexcludeuser'] = null;
}
+
return $ret;
}
public function getParamDescription() {
$wldescr = $this->getWatchlistModule()->getParamDescription();
+
return array(
'feedformat' => 'The format of the feed',
'hours' => 'List pages modified within this many hours from now',
- 'linktodiffs' => 'Link to change differences instead of article pages',
'linktosections' => 'Link directly to changed sections if possible',
'allrev' => $wldescr['allrev'],
'wlowner' => $wldescr['owner'],
@@ -255,20 +256,13 @@ class ApiFeedWatchlist extends ApiBase {
}
public function getDescription() {
- return 'Returns a watchlist feed';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ),
- array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ),
- ) );
+ return 'Returns a watchlist feed.';
}
public function getExamples() {
return array(
'api.php?action=feedwatchlist',
- 'api.php?action=feedwatchlist&allrev=&linktodiffs=&hours=6'
+ 'api.php?action=feedwatchlist&allrev=&hours=6'
);
}
diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php
index cbb2ba6a..f518e172 100644
--- a/includes/api/ApiFileRevert.php
+++ b/includes/api/ApiFileRevert.php
@@ -28,13 +28,13 @@
* @ingroup API
*/
class ApiFileRevert extends ApiBase {
-
- /**
- * @var File
- */
+ /** @var LocalFile */
protected $file;
+
+ /** @var string */
protected $archiveName;
+ /** @var array */
protected $params;
public function execute() {
@@ -46,7 +46,15 @@ class ApiFileRevert extends ApiBase {
$this->checkPermissions( $this->getUser() );
$sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName );
- $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'], 0, false, false, $this->getUser() );
+ $status = $this->file->upload(
+ $sourceUrl,
+ $this->params['comment'],
+ $this->params['comment'],
+ 0,
+ false,
+ false,
+ $this->getUser()
+ );
if ( $status->isGood() ) {
$result = array( 'result' => 'Success' );
@@ -58,13 +66,12 @@ class ApiFileRevert extends ApiBase {
}
$this->getResult()->addValue( null, $this->getModuleName(), $result );
-
}
/**
* Checks that the user has permissions to perform this revert.
* Dies with usage message on inadequate permissions.
- * @param $user User The user to check.
+ * @param User $user The user to check.
*/
protected function checkPermissions( $user ) {
$title = $this->file->getTitle();
@@ -125,69 +132,31 @@ class ApiFileRevert extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
);
-
}
public function getParamDescription() {
return array(
'filename' => 'Target filename without the File: prefix',
- 'token' => 'Edit token. You can get one of these through prop=info',
'comment' => 'Upload comment',
'archivename' => 'Archive name of the revision to revert to',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'Failure'
- )
- ),
- 'errors' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
return array(
- 'Revert a file to an old version'
- );
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- array(
- array( 'mustbeloggedin', 'upload' ),
- array( 'badaccess-groups' ),
- array( 'invalidtitle', 'title' ),
- array( 'notanarticle' ),
- array( 'filerevert-badversion' ),
- )
+ 'Revert a file to an old version.'
);
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=123ABC'
+ 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&' .
+ 'archivename=20110305152740!Wiki.png&token=123ABC'
=> 'Revert Wiki.png to the version of 20110305152740',
);
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 70495439..9165ce88 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -30,22 +30,20 @@
* @ingroup API
*/
abstract class ApiFormatBase extends ApiBase {
-
private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared;
private $mBufferResult = false, $mBuffer, $mDisabled = false;
/**
- * Constructor
* If $format ends with 'fm', pretty-print the output in HTML.
- * @param $main ApiMain
+ * @param ApiMain $main
* @param string $format Format name
*/
- public function __construct( $main, $format ) {
+ public function __construct( ApiMain $main, $format ) {
parent::__construct( $main, $format );
- $this->mIsHtml = ( substr( $format, - 2, 2 ) === 'fm' ); // ends with 'fm'
+ $this->mIsHtml = ( substr( $format, -2, 2 ) === 'fm' ); // ends with 'fm'
if ( $this->mIsHtml ) {
- $this->mFormat = substr( $format, 0, - 2 ); // remove ending 'fm'
+ $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
} else {
$this->mFormat = $format;
}
@@ -54,7 +52,7 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
- * Overriding class returns the mime type that should be sent to the client.
+ * Overriding class returns the MIME type that should be sent to the client.
* This method is not called if getIsHtml() returns true.
* @return string
*/
@@ -122,6 +120,16 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
+ * Whether this formatter can handle printing API errors. If this returns
+ * false, then on API errors the default printer will be instantiated.
+ * @since 1.23
+ * @return bool
+ */
+ public function canPrintErrors() {
+ return true;
+ }
+
+ /**
* Initialize the printer function and prepare the output headers, etc.
* This method must be the first outputting method during execution.
* A human-targeted notice about available formats is printed for the HTML-based output,
@@ -146,9 +154,9 @@ abstract class ApiFormatBase extends ApiBase {
$this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
//Set X-Frame-Options API results (bug 39180)
- global $wgApiFrameOptions;
- if ( $wgApiFrameOptions ) {
- $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $wgApiFrameOptions" );
+ $apiFrameOptions = $this->getConfig()->get( 'ApiFrameOptions' );
+ if ( $apiFrameOptions ) {
+ $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
}
if ( $isHtml ) {
@@ -156,17 +164,20 @@ abstract class ApiFormatBase extends ApiBase {
<!DOCTYPE HTML>
<html>
<head>
-<?php if ( $this->mUnescapeAmps ) {
+<?php
+ if ( $this->mUnescapeAmps ) {
?> <title>MediaWiki API</title>
-<?php } else {
+<?php
+ } else {
?> <title>MediaWiki API Result</title>
-<?php } ?>
+<?php
+ }
+?>
</head>
<body>
<?php
-
-
if ( !$isHelpScreen ) {
+// @codingStandardsIgnoreStart Exclude long line from CodeSniffer checks
?>
<br />
<small>
@@ -179,15 +190,14 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
</small>
<pre style='white-space: pre-wrap;'>
<?php
-
-
- } else { // don't wrap the contents of the <pre> for help screens
- // because these are actually formatted to rely on
- // the monospaced font for layout purposes
+// @codingStandardsIgnoreEnd
+ // don't wrap the contents of the <pre> for help screens
+ // because these are actually formatted to rely on
+ // the monospaced font for layout purposes
+ } else {
?>
<pre>
<?php
-
}
}
}
@@ -206,8 +216,6 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
</body>
</html>
<?php
-
-
}
}
@@ -215,7 +223,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
* The main format printing function. Call it to output the result
* string to the user. This function will automatically output HTML
* when format name ends in 'fm'.
- * @param $text string
+ * @param string $text
*/
public function printText( $text ) {
if ( $this->mDisabled ) {
@@ -239,6 +247,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Get the contents of the buffer.
+ * @return string
*/
public function getBuffer() {
return $this->mBuffer;
@@ -246,7 +255,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Set the flag to buffer the result instead of printing it.
- * @param $value bool
+ * @param bool $value
*/
public function setBufferResult( $value ) {
$this->mBufferResult = $value;
@@ -254,7 +263,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Sets whether the pretty-printer should format *bold*
- * @param $help bool
+ * @param bool $help
*/
public function setHelp( $help = true ) {
$this->mHelp = $help;
@@ -263,7 +272,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
/**
* Pretty-print various elements in HTML format, such as xml tags and
* URLs. This method also escapes characters like <
- * @param $text string
+ * @param string $text
* @return string
*/
protected function formatHTML( $text ) {
@@ -276,8 +285,8 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
// identify requests to api.php
$text = preg_replace( '#^(\s*)(api\.php\?[^ <\n\t]+)$#m', '\1<a href="\2">\2</a>', $text );
if ( $this->mHelp ) {
- // make strings inside * bold
- $text = preg_replace( "#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text );
+ // make lines inside * bold
+ $text = preg_replace( '#^(\s*)(\*[^<>\n]+\*)(\s*)$#m', '$1<b>$2</b>$3', $text );
}
// Armor links (bug 61362)
@@ -291,7 +300,11 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
// identify URLs
$protos = wfUrlProtocolsWithoutProtRel();
// This regex hacks around bug 13218 (&quot; included in the URL)
- $text = preg_replace( "#(((?i)$protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#", '<a href="\\1">\\1</a>\\3\\4', $text );
+ $text = preg_replace(
+ "#(((?i)$protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#",
+ '<a href="\\1">\\1</a>\\3\\4',
+ $text
+ );
// Unarmor links
$text = preg_replace_callback( '#<([0-9a-f]{40})>#', function ( $matches ) use ( &$masked ) {
@@ -326,73 +339,16 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
public function getDescription() {
return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
}
-}
-
-/**
- * This printer is used to wrap an instance of the Feed class
- * @ingroup API
- */
-class ApiFormatFeedWrapper extends ApiFormatBase {
-
- public function __construct( $main ) {
- parent::__construct( $main, 'feed' );
- }
-
- /**
- * Call this method to initialize output data. See execute()
- * @param $result ApiResult
- * @param $feed object an instance of one of the $wgFeedClasses classes
- * @param array $feedItems of FeedItem objects
- */
- public static function setResult( $result, $feed, $feedItems ) {
- // Store output in the Result data.
- // This way we can check during execution if any error has occurred
- // Disable size checking for this because we can't continue
- // cleanly; size checking would cause more problems than it'd
- // solve
- $result->disableSizeCheck();
- $result->addValue( null, '_feed', $feed );
- $result->addValue( null, '_feeditems', $feedItems );
- $result->enableSizeCheck();
- }
-
- /**
- * Feed does its own headers
- *
- * @return null
- */
- public function getMimeType() {
- return null;
- }
-
- /**
- * Optimization - no need to sanitize data that will not be needed
- *
- * @return bool
- */
- public function getNeedsRawData() {
- return true;
- }
/**
- * This class expects the result data to be in a custom format set by self::setResult()
- * $result['_feed'] - an instance of one of the $wgFeedClasses classes
- * $result['_feeditems'] - an array of FeedItem instances
+ * To avoid code duplication with the deprecation of dbg, dump, txt, wddx,
+ * and yaml, this method is added to do the necessary work. It should be
+ * removed when those deprecated formats are removed.
*/
- public function execute() {
- $data = $this->getResultData();
- if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
- $feed = $data['_feed'];
- $items = $data['_feeditems'];
-
- $feed->outHeader();
- foreach ( $items as & $item ) {
- $feed->outItem( $item );
- }
- $feed->outFooter();
- } else {
- // Error has occurred, print something useful
- ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
- }
+ protected function markDeprecated() {
+ $fm = $this->getIsHtml() ? 'fm' : '';
+ $name = $this->getModuleName();
+ $this->logFeatureUsage( "format=$name" );
+ $this->setWarning( "format=$name has been deprecated. Please use format=json$fm instead." );
}
}
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index 1b2e02c9..5ec518b3 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -26,6 +26,7 @@
/**
* API PHP's var_export() output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatDbg extends ApiFormatBase {
@@ -38,10 +39,11 @@ class ApiFormatDbg extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
$this->printText( var_export( $this->getResultData(), true ) );
}
public function getDescription() {
- return 'Output data in PHP\'s var_export() format' . parent::getDescription();
+ return 'DEPRECATED! Output data in PHP\'s var_export() format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
index 62253e14..d4c7cab4 100644
--- a/includes/api/ApiFormatDump.php
+++ b/includes/api/ApiFormatDump.php
@@ -26,6 +26,7 @@
/**
* API PHP's var_dump() output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatDump extends ApiFormatBase {
@@ -38,6 +39,7 @@ class ApiFormatDump extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
ob_start();
var_dump( $this->getResultData() );
$result = ob_get_contents();
@@ -46,6 +48,6 @@ class ApiFormatDump extends ApiFormatBase {
}
public function getDescription() {
- return 'Output data in PHP\'s var_dump() format' . parent::getDescription();
+ return 'DEPRECATED! Output data in PHP\'s var_dump() format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatFeedWrapper.php b/includes/api/ApiFormatFeedWrapper.php
new file mode 100644
index 00000000..92600067
--- /dev/null
+++ b/includes/api/ApiFormatFeedWrapper.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ *
+ *
+ * Created on Sep 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * This printer is used to wrap an instance of the Feed class
+ * @ingroup API
+ */
+class ApiFormatFeedWrapper extends ApiFormatBase {
+
+ public function __construct( ApiMain $main ) {
+ parent::__construct( $main, 'feed' );
+ }
+
+ /**
+ * Call this method to initialize output data. See execute()
+ * @param ApiResult $result
+ * @param object $feed An instance of one of the $wgFeedClasses classes
+ * @param array $feedItems Array of FeedItem objects
+ */
+ public static function setResult( $result, $feed, $feedItems ) {
+ // Store output in the Result data.
+ // This way we can check during execution if any error has occurred
+ // Disable size checking for this because we can't continue
+ // cleanly; size checking would cause more problems than it'd
+ // solve
+ $result->addValue( null, '_feed', $feed, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, '_feeditems', $feedItems, ApiResult::NO_SIZE_CHECK );
+ }
+
+ /**
+ * Feed does its own headers
+ *
+ * @return null
+ */
+ public function getMimeType() {
+ return null;
+ }
+
+ /**
+ * Optimization - no need to sanitize data that will not be needed
+ *
+ * @return bool
+ */
+ public function getNeedsRawData() {
+ return true;
+ }
+
+ /**
+ * ChannelFeed doesn't give us a method to print errors in a friendly
+ * manner, so just punt errors to the default printer.
+ * @return bool
+ */
+ public function canPrintErrors() {
+ return false;
+ }
+
+ /**
+ * This class expects the result data to be in a custom format set by self::setResult()
+ * $result['_feed'] - an instance of one of the $wgFeedClasses classes
+ * $result['_feeditems'] - an array of FeedItem instances
+ */
+ public function execute() {
+ $data = $this->getResultData();
+ if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
+ $feed = $data['_feed'];
+ $items = $data['_feeditems'];
+
+ $feed->outHeader();
+ foreach ( $items as & $item ) {
+ $feed->outItem( $item );
+ }
+ $feed->outFooter();
+ } else {
+ // Error has occurred, print something useful
+ ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
+ }
+ }
+}
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index 47d82124..d9f9d46a 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -32,7 +32,7 @@ class ApiFormatJson extends ApiFormatBase {
private $mIsRaw;
- public function __construct( $main, $format ) {
+ public function __construct( ApiMain $main, $format ) {
parent::__construct( $main, $format );
$this->mIsRaw = ( $format === 'rawfm' );
}
@@ -43,6 +43,7 @@ class ApiFormatJson extends ApiFormatBase {
if ( $params['callback'] ) {
return 'text/javascript';
}
+
return 'application/json';
}
@@ -92,16 +93,18 @@ class ApiFormatJson extends ApiFormatBase {
public function getParamDescription() {
return array(
- 'callback' => 'If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.',
- 'utf8' => 'If specified, encodes most (but not all) non-ASCII characters as UTF-8 instead of replacing them with hexadecimal escape sequences.',
+ 'callback' => 'If specified, wraps the output into a given function ' .
+ 'call. For safety, all user-specific data will be restricted.',
+ 'utf8' => 'If specified, encodes most (but not all) non-ASCII ' .
+ 'characters as UTF-8 instead of replacing them with hexadecimal escape sequences.',
);
}
public function getDescription() {
if ( $this->mIsRaw ) {
return 'Output data with the debugging elements in JSON format' . parent::getDescription();
- } else {
- return 'Output data in JSON format' . parent::getDescription();
}
+
+ return 'Output data in JSON format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index bda1c180..73ce80ef 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -35,14 +35,13 @@ class ApiFormatPhp extends ApiFormatBase {
}
public function execute() {
- global $wgMangleFlashPolicy;
$text = serialize( $this->getResultData() );
// Bug 66776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
// Flash, but what it does isn't friendly for the API. There's nothing
// we can do here that isn't actively broken in some manner, so let's
// just be broken in a useful manner.
- if ( $wgMangleFlashPolicy &&
+ if ( $this->getConfig()->get( 'MangleFlashPolicy' ) &&
in_array( 'wfOutputHandler', ob_list_handlers(), true ) &&
preg_match( '/\<\s*cross-domain-policy\s*\>/i', $text )
) {
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index d278efa0..3f5c8b73 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -31,11 +31,10 @@
class ApiFormatRaw extends ApiFormatBase {
/**
- * Constructor
- * @param $main ApiMain object
- * @param $errorFallback ApiFormatBase object to fall back on for errors
+ * @param ApiMain $main
+ * @param ApiFormatBase $errorFallback Object to fall back on for errors
*/
- public function __construct( $main, $errorFallback ) {
+ public function __construct( ApiMain $main, ApiFormatBase $errorFallback ) {
parent::__construct( $main, 'raw' );
$this->mErrorFallback = $errorFallback;
}
@@ -58,6 +57,7 @@ class ApiFormatRaw extends ApiFormatBase {
$data = $this->getResultData();
if ( isset( $data['error'] ) ) {
$this->mErrorFallback->execute();
+
return;
}
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index 4130e70c..c451ed77 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -26,6 +26,7 @@
/**
* API Text output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatTxt extends ApiFormatBase {
@@ -38,10 +39,11 @@ class ApiFormatTxt extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
$this->printText( print_r( $this->getResultData(), true ) );
}
public function getDescription() {
- return 'Output data in PHP\'s print_r() format' . parent::getDescription();
+ return 'DEPRECATED! Output data in PHP\'s print_r() format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index 5685d937..ba90c260 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -26,6 +26,7 @@
/**
* API WDDX output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatWddx extends ApiFormatBase {
@@ -35,13 +36,17 @@ class ApiFormatWddx extends ApiFormatBase {
}
public function execute() {
+ $this->markDeprecated();
+
// Some versions of PHP have a broken wddx_serialize_value, see
// PHP bug 45314. Test encoding an affected character (U+00A0)
// to avoid this.
- $expected = "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>";
+ $expected =
+ "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>";
if ( function_exists( 'wddx_serialize_value' )
- && !$this->getIsHtml()
- && wddx_serialize_value( "\xc2\xa0" ) == $expected ) {
+ && !$this->getIsHtml()
+ && wddx_serialize_value( "\xc2\xa0" ) == $expected
+ ) {
$this->printText( wddx_serialize_value( $this->getResultData() ) );
} else {
// Don't do newlines and indentation if we weren't asked
@@ -60,8 +65,8 @@ class ApiFormatWddx extends ApiFormatBase {
/**
* Recursively go through the object and output its data in WDDX format.
- * @param $elemValue
- * @param $indent int
+ * @param mixed $elemValue
+ * @param int $indent
*/
function slowWddxPrinter( $elemValue, $indent = 0 ) {
$indstr = ( $this->getIsHtml() ? str_repeat( ' ', $indent ) : '' );
@@ -105,6 +110,6 @@ class ApiFormatWddx extends ApiFormatBase {
}
public function getDescription() {
- return 'Output data in WDDX format' . parent::getDescription();
+ return 'DEPRECATED! Output data in WDDX format' . parent::getDescription();
}
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 4ec149c0..b3d59379 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -69,7 +69,7 @@ class ApiFormatXml extends ApiFormatBase {
$this->printText(
self::recXmlPrint( $this->mRootElemName,
$data,
- $this->getIsHtml() ? - 2 : null
+ $this->getIsHtml() ? -2 : null
)
);
}
@@ -111,9 +111,9 @@ class ApiFormatXml extends ApiFormatBase {
* @note The method is recursive, so the same rules apply to any
* sub-arrays.
*
- * @param $elemName
- * @param $elemValue
- * @param $indent
+ * @param string $elemName
+ * @param mixed $elemValue
+ * @param int $indent
*
* @return string
*/
@@ -147,6 +147,15 @@ class ApiFormatXml extends ApiFormatBase {
$subElemIndName = null;
}
+ if ( isset( $elemValue['_subelements'] ) ) {
+ foreach ( $elemValue['_subelements'] as $subElemId ) {
+ if ( isset( $elemValue[$subElemId] ) && !is_array( $elemValue[$subElemId] ) ) {
+ $elemValue[$subElemId] = array( '*' => $elemValue[$subElemId] );
+ }
+ }
+ unset( $elemValue['_subelements'] );
+ }
+
$indElements = array();
$subElements = array();
foreach ( $elemValue as $subElemId => & $subElemValue ) {
@@ -156,11 +165,19 @@ class ApiFormatXml extends ApiFormatBase {
} elseif ( is_array( $subElemValue ) ) {
$subElements[$subElemId] = $subElemValue;
unset( $elemValue[$subElemId] );
+ } elseif ( is_bool( $subElemValue ) ) {
+ // treat true as empty string, skip false in xml format
+ if ( $subElemValue === true ) {
+ $subElemValue = '';
+ } else {
+ unset( $elemValue[$subElemId] );
+ }
}
}
if ( is_null( $subElemIndName ) && count( $indElements ) ) {
- ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()." );
+ ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys " .
+ "without _element value. Use ApiResult::setIndexedTagName()." );
}
if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) {
@@ -193,6 +210,7 @@ class ApiFormatXml extends ApiFormatBase {
$retval .= $indstr . Xml::element( $elemName, null, $elemValue );
}
}
+
return $retval;
}
@@ -200,17 +218,21 @@ class ApiFormatXml extends ApiFormatBase {
$nt = Title::newFromText( $this->mXslt );
if ( is_null( $nt ) || !$nt->exists() ) {
$this->setWarning( 'Invalid or non-existent stylesheet specified' );
+
return;
}
if ( $nt->getNamespace() != NS_MEDIAWIKI ) {
$this->setWarning( 'Stylesheet should be in the MediaWiki namespace.' );
+
return;
}
- if ( substr( $nt->getText(), - 4 ) !== '.xsl' ) {
+ if ( substr( $nt->getText(), -4 ) !== '.xsl' ) {
$this->setWarning( 'Stylesheet should have .xsl extension.' );
+
return;
}
- $this->printText( '<?xml-stylesheet href="' . htmlspecialchars( $nt->getLocalURL( 'action=raw' ) ) . '" type="text/xsl" ?>' );
+ $this->printText( '<?xml-stylesheet href="' .
+ htmlspecialchars( $nt->getLocalURL( 'action=raw' ) ) . '" type="text/xsl" ?>' );
}
public function getAllowedParams() {
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index 700d4a5e..3798f894 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -26,6 +26,7 @@
/**
* API YAML output formatter
+ * @deprecated since 1.24
* @ingroup API
*/
class ApiFormatYaml extends ApiFormatJson {
@@ -34,7 +35,12 @@ class ApiFormatYaml extends ApiFormatJson {
return 'application/yaml';
}
+ public function execute() {
+ $this->markDeprecated();
+ parent::execute();
+ }
+
public function getDescription() {
- return 'Output data in YAML format' . ApiFormatBase::getDescription();
+ return 'DEPRECATED! Output data in YAML format' . ApiFormatBase::getDescription();
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index 9cafc5bb..bcd6c12e 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -30,7 +30,6 @@
* @ingroup API
*/
class ApiHelp extends ApiBase {
-
/**
* Module for displaying help
*/
@@ -52,6 +51,7 @@ class ApiHelp extends ApiBase {
}
if ( is_array( $params['querymodules'] ) ) {
+ $this->logFeatureUsage( 'action=help&querymodules' );
$queryModules = $params['querymodules'];
foreach ( $queryModules as $m ) {
$modules[] = 'query+' . $m;
@@ -68,19 +68,22 @@ class ApiHelp extends ApiBase {
// In case the '+' was typed into URL, it resolves as a space
$subNames = explode( ' ', $m );
}
+
$module = $this->getMain();
- for ( $i = 0; $i < count( $subNames ); $i++ ) {
+ $subNamesCount = count( $subNames );
+ for ( $i = 0; $i < $subNamesCount; $i++ ) {
$subs = $module->getModuleManager();
if ( $subs === null ) {
$module = null;
} else {
$module = $subs->getModule( $subNames[$i] );
}
+
if ( $module === null ) {
if ( count( $subNames ) === 2
- && $i === 1
- && $subNames[0] === 'query'
- && in_array( $subNames[1], $queryModules )
+ && $i === 1
+ && $subNames[0] === 'query'
+ && in_array( $subNames[1], $queryModules )
) {
// Legacy: This is one of the renamed 'querymodule=...' parameters,
// do not use '+' notation in the output, use submodule's name instead.
@@ -94,6 +97,7 @@ class ApiHelp extends ApiBase {
$type = $subs->getModuleGroup( $subNames[$i] );
}
}
+
if ( $module !== null ) {
$r[] = $this->buildModuleHelp( $module, $type );
}
@@ -104,8 +108,8 @@ class ApiHelp extends ApiBase {
}
/**
- * @param $module ApiBase
- * @param $type String What type of request is this? e.g. action, query, list, prop, meta, format
+ * @param ApiBase $module
+ * @param string $type What type of request is this? e.g. action, query, list, prop, meta, format
* @return string
*/
private function buildModuleHelp( $module, $type ) {
@@ -141,21 +145,25 @@ class ApiHelp extends ApiBase {
public function getParamDescription() {
return array(
- 'modules' => 'List of module names (value of the action= parameter). Can specify submodules with a \'+\'',
- 'querymodules' => 'Use modules=query+value instead. List of query module names (value of prop=, meta= or list= parameter)',
+ 'modules' => 'List of module names (value of the action= parameter). ' .
+ 'Can specify submodules with a \'+\'',
+ 'querymodules' => 'Use modules=query+value instead. List of query ' .
+ 'module names (value of prop=, meta= or list= parameter)',
);
}
public function getDescription() {
- return 'Display this help screen. Or the help screen for the specified module';
+ return 'Display this help screen. Or the help screen for the specified module.';
}
public function getExamples() {
return array(
'api.php?action=help' => 'Whole help page',
'api.php?action=help&modules=protect' => 'Module (action) help page',
- 'api.php?action=help&modules=query+categorymembers' => 'Help for the query/categorymembers module',
- 'api.php?action=help&modules=login|query+info' => 'Help for the login and query/info modules',
+ 'api.php?action=help&modules=query+categorymembers'
+ => 'Help for the query/categorymembers module',
+ 'api.php?action=help&modules=login|query+info'
+ => 'Help for the login and query/info modules',
);
}
diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php
index 7a60e831..20396dd7 100644
--- a/includes/api/ApiImageRotate.php
+++ b/includes/api/ApiImageRotate.php
@@ -24,16 +24,12 @@
class ApiImageRotate extends ApiBase {
private $mPageSet = null;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Add all items from $values into the result
- * @param array $result output
- * @param array $values values to add
- * @param string $flag the name of the boolean flag to mark this element
- * @param string $name if given, name of the value
+ * @param array $result Output
+ * @param array $values Values to add
+ * @param string $flag The name of the boolean flag to mark this element
+ * @param string $name If given, name of the value
*/
private static function addValues( array &$result, $values, $flag = null, $name = null ) {
foreach ( $values as $val ) {
@@ -56,6 +52,8 @@ class ApiImageRotate extends ApiBase {
$params = $this->extractRequestParams();
$rotation = $params['rotation'];
+ $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+
$pageSet = $this->getPageSet();
$pageSet->execute();
@@ -135,6 +133,7 @@ class ApiImageRotate extends ApiBase {
$apiResult = $this->getResult();
$apiResult->setIndexedTagName( $result, 'page' );
$apiResult->addValue( null, $this->getModuleName(), $result );
+ $apiResult->endContinuation();
}
/**
@@ -145,6 +144,7 @@ class ApiImageRotate extends ApiBase {
if ( $this->mPageSet === null ) {
$this->mPageSet = new ApiPageSet( $this, 0, NS_FILE );
}
+
return $this->mPageSet;
}
@@ -163,6 +163,7 @@ class ApiImageRotate extends ApiBase {
if ( $permissionErrors ) {
// Just return the first error
$msg = $this->parseMsg( $permissionErrors[0] );
+
return $msg['info'];
}
@@ -183,43 +184,30 @@ class ApiImageRotate extends ApiBase {
ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
+ 'continue' => '',
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
+
return $result;
}
public function getParamDescription() {
$pageSet = $this->getPageSet();
+
return $pageSet->getFinalParamDescription() + array(
'rotation' => 'Degrees to rotate image clockwise',
- 'token' => 'Edit token. You can get one of these through action=tokens',
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'Rotate one or more images';
+ return 'Rotate one or more images.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
- }
-
- public function getPossibleErrors() {
- $pageSet = $this->getPageSet();
- return array_merge(
- parent::getPossibleErrors(),
- $pageSet->getFinalPossibleErrors()
- );
+ return 'csrf';
}
public function getExamples() {
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index f48a822e..b11348e5 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -98,18 +98,13 @@ class ApiImport extends ApiBase {
}
public function getAllowedParams() {
- global $wgImportSources;
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => null,
'xml' => array(
ApiBase::PARAM_TYPE => 'upload',
),
'interwikisource' => array(
- ApiBase::PARAM_TYPE => $wgImportSources
+ ApiBase::PARAM_TYPE => $this->getConfig()->get( 'ImportSources' ),
),
'interwikipage' => null,
'fullhistory' => false,
@@ -123,7 +118,6 @@ class ApiImport extends ApiBase {
public function getParamDescription() {
return array(
- 'token' => 'Import token obtained through prop=info',
'summary' => 'Import summary',
'xml' => 'Uploaded XML file',
'interwikisource' => 'For interwiki imports: wiki to import from',
@@ -135,17 +129,6 @@ class ApiImport extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => true,
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string',
- 'revisions' => 'integer'
- )
- );
- }
-
public function getDescription() {
return array(
'Import a page from another wiki, or an XML file.',
@@ -154,29 +137,14 @@ class ApiImport extends ApiBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'cantimport' ),
- array( 'missingparam', 'interwikipage' ),
- array( 'cantimport-upload' ),
- array( 'import-unknownerror', 'source' ),
- array( 'import-unknownerror', 'result' ),
- array( 'import-rootpage-nosubpage', 'namespace' ),
- array( 'import-rootpage-invalid' ),
- ) );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory=&token=123ABC'
+ 'api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&' .
+ 'namespace=100&fullhistory=&token=123ABC'
=> 'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history',
);
}
@@ -194,11 +162,11 @@ class ApiImportReporter extends ImportReporter {
private $mResultArr = array();
/**
- * @param $title Title
- * @param $origTitle Title
- * @param $revisionCount int
- * @param $successCount int
- * @param $pageInfo
+ * @param Title $title
+ * @param Title $origTitle
+ * @param int $revisionCount
+ * @param int $successCount
+ * @param array $pageInfo
* @return void
*/
function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index b51d441d..976f4c12 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -32,7 +32,7 @@
*/
class ApiLogin extends ApiBase {
- public function __construct( $main, $action ) {
+ public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action, 'lg' );
}
@@ -52,6 +52,7 @@ class ApiLogin extends ApiBase {
'result' => 'Aborted',
'reason' => 'Cannot log in when using a callback',
) );
+
return;
}
@@ -78,15 +79,12 @@ class ApiLogin extends ApiBase {
$loginForm = new LoginForm();
$loginForm->setContext( $context );
- global $wgCookiePrefix, $wgPasswordAttemptThrottle;
-
$authRes = $loginForm->authenticateUserData();
switch ( $authRes ) {
case LoginForm::SUCCESS:
$user = $context->getUser();
$this->getContext()->setUser( $user );
- $user->setOption( 'rememberpassword', 1 );
- $user->setCookies( $this->getRequest() );
+ $user->setCookies( $this->getRequest(), null, true );
ApiQueryInfo::resetTokenCache();
@@ -100,14 +98,14 @@ class ApiLogin extends ApiBase {
$result['lguserid'] = intval( $user->getId() );
$result['lgusername'] = $user->getName();
$result['lgtoken'] = $user->getToken();
- $result['cookieprefix'] = $wgCookiePrefix;
+ $result['cookieprefix'] = $this->getConfig()->get( 'CookiePrefix' );
$result['sessionid'] = session_id();
break;
case LoginForm::NEED_TOKEN:
$result['result'] = 'NeedToken';
$result['token'] = $loginForm->getLoginToken();
- $result['cookieprefix'] = $wgCookiePrefix;
+ $result['cookieprefix'] = $this->getConfig()->get( 'CookiePrefix' );
$result['sessionid'] = session_id();
break;
@@ -131,7 +129,9 @@ class ApiLogin extends ApiBase {
$result['result'] = 'NotExists';
break;
- case LoginForm::RESET_PASS: // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin - "The e-mailed temporary password should not be used for actual logins;"
+ // bug 20223 - Treat a temporary password as wrong. Per SpecialUserLogin:
+ // The e-mailed temporary password should not be used for actual logins.
+ case LoginForm::RESET_PASS:
case LoginForm::WRONG_PASS:
$result['result'] = 'WrongPass';
break;
@@ -147,7 +147,8 @@ class ApiLogin extends ApiBase {
case LoginForm::THROTTLED:
$result['result'] = 'Throttled';
- $result['wait'] = intval( $wgPasswordAttemptThrottle['seconds'] );
+ $throttle = $this->getConfig()->get( 'PasswordAttemptThrottle' );
+ $result['wait'] = intval( $throttle['seconds'] );
break;
case LoginForm::USER_BLOCKED:
@@ -192,92 +193,16 @@ class ApiLogin extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'result' => array(
- ApiBase::PROP_TYPE => array(
- 'Success',
- 'NeedToken',
- 'WrongToken',
- 'NoName',
- 'Illegal',
- 'WrongPluginPass',
- 'NotExists',
- 'WrongPass',
- 'EmptyPass',
- 'CreateBlocked',
- 'Throttled',
- 'Blocked',
- 'Aborted'
- )
- ),
- 'lguserid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'lgusername' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'lgtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'cookieprefix' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'sessionid' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'token' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'details' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'wait' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'reason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
return array(
- 'Log in and get the authentication tokens. ',
- 'In the event of a successful log-in, a cookie will be attached',
- 'to your session. In the event of a failed log-in, you will not ',
- 'be able to attempt another log-in through this method for 5 seconds.',
- 'This is to prevent password guessing by automated password crackers'
+ 'Log in and get the authentication tokens.',
+ 'In the event of a successful log-in, a cookie will be attached to your session.',
+ 'In the event of a failed log-in, you will not be able to attempt another log-in',
+ 'through this method for 5 seconds. This is to prevent password guessing by',
+ 'automated password crackers.'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'NeedToken', 'info' => 'You need to resubmit your login with the specified token. See https://bugzilla.wikimedia.org/show_bug.cgi?id=23076' ),
- array( 'code' => 'WrongToken', 'info' => 'You specified an invalid token' ),
- array( 'code' => 'NoName', 'info' => 'You didn\'t set the lgname parameter' ),
- array( 'code' => 'Illegal', 'info' => ' You provided an illegal username' ),
- array( 'code' => 'NotExists', 'info' => ' The username you provided doesn\'t exist' ),
- array( 'code' => 'EmptyPass', 'info' => ' You didn\'t set the lgpassword parameter or you left it empty' ),
- array( 'code' => 'WrongPass', 'info' => ' The password you provided is incorrect' ),
- array( 'code' => 'WrongPluginPass', 'info' => 'Same as "WrongPass", returned when an authentication plugin rather than MediaWiki itself rejected the password' ),
- array( 'code' => 'CreateBlocked', 'info' => 'The wiki tried to automatically create a new account for you, but your IP address has been blocked from account creation' ),
- array( 'code' => 'Throttled', 'info' => 'You\'ve logged in too many times in a short time' ),
- array( 'code' => 'Blocked', 'info' => 'User is blocked' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=login&lgname=user&lgpassword=password'
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index 2ba92a63..324f4b2f 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -50,16 +50,12 @@ class ApiLogout extends ApiBase {
return array();
}
- public function getResultProperties() {
- return array();
- }
-
public function getParamDescription() {
return array();
}
public function getDescription() {
- return 'Log out and clear session data';
+ return 'Log out and clear session data.';
}
public function getExamples() {
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index ea2fcc78..119d7b48 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -39,7 +39,6 @@
* @ingroup API
*/
class ApiMain extends ApiBase {
-
/**
* When no format parameter is given, this format will be used
*/
@@ -57,6 +56,7 @@ class ApiMain extends ApiBase {
'parse' => 'ApiParse',
'opensearch' => 'ApiOpenSearch',
'feedcontributions' => 'ApiFeedContributions',
+ 'feedrecentchanges' => 'ApiFeedRecentChanges',
'feedwatchlist' => 'ApiFeedWatchlist',
'help' => 'ApiHelp',
'paraminfo' => 'ApiParamInfo',
@@ -81,9 +81,11 @@ class ApiMain extends ApiBase {
'watch' => 'ApiWatch',
'patrol' => 'ApiPatrol',
'import' => 'ApiImport',
+ 'clearhasmsg' => 'ApiClearHasMsg',
'userrights' => 'ApiUserrights',
'options' => 'ApiOptions',
'imagerotate' => 'ApiImageRotate',
+ 'revisiondelete' => 'ApiRevisionDelete',
);
/**
@@ -110,6 +112,7 @@ class ApiMain extends ApiBase {
'none' => 'ApiFormatNone',
);
+ // @codingStandardsIgnoreStart String contenation on "msg" not allowed to break long line
/**
* List of user roles that are specifically relevant to the API.
* array( 'right' => array ( 'msg' => 'Some message with a $1',
@@ -126,6 +129,7 @@ class ApiMain extends ApiBase {
'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
)
);
+ // @codingStandardsIgnoreEnd
/**
* @var ApiFormatBase
@@ -144,8 +148,9 @@ class ApiMain extends ApiBase {
/**
* Constructs an instance of ApiMain that utilizes the module and format specified by $request.
*
- * @param $context IContextSource|WebRequest - if this is an instance of FauxRequest, errors are thrown and no printing occurs
- * @param bool $enableWrite should be set to true if the api may modify data
+ * @param IContextSource|WebRequest $context If this is an instance of
+ * FauxRequest, errors are thrown and no printing occurs
+ * @param bool $enableWrite Should be set to true if the api may modify data
*/
public function __construct( $context = null, $enableWrite = false ) {
if ( $context === null ) {
@@ -182,16 +187,17 @@ class ApiMain extends ApiBase {
}
}
- global $wgAPIModules;
+ $config = $this->getConfig();
$this->mModuleMgr = new ApiModuleManager( $this );
$this->mModuleMgr->addModules( self::$Modules, 'action' );
- $this->mModuleMgr->addModules( $wgAPIModules, 'action' );
+ $this->mModuleMgr->addModules( $config->get( 'APIModules' ), 'action' );
$this->mModuleMgr->addModules( self::$Formats, 'format' );
+ $this->mModuleMgr->addModules( $config->get( 'APIFormatModules' ), 'format' );
$this->mResult = new ApiResult( $this );
$this->mEnableWrite = $enableWrite;
- $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
+ $this->mSquidMaxage = -1; // flag for executeActionWithErrorHandling()
$this->mCommit = false;
}
@@ -233,7 +239,7 @@ class ApiMain extends ApiBase {
/**
* Set how long the response should be cached.
*
- * @param $maxage
+ * @param int $maxage
*/
public function setCacheMaxAge( $maxage ) {
$this->setCacheControl( array(
@@ -270,6 +276,7 @@ class ApiMain extends ApiBase {
public function setCacheMode( $mode ) {
if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) {
wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" );
+
// Ignore for forwards-compatibility
return;
}
@@ -278,6 +285,7 @@ class ApiMain extends ApiBase {
// Private wiki, only private headers
if ( $mode !== 'private' ) {
wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
+
return;
}
}
@@ -287,16 +295,6 @@ class ApiMain extends ApiBase {
}
/**
- * @deprecated since 1.17 Private caching is now the default, so there is usually no
- * need to call this function. If there is a need, you can use
- * $this->setCacheMode('private')
- */
- public function setCachePrivate() {
- wfDeprecated( __METHOD__, '1.17' );
- $this->setCacheMode( 'private' );
- }
-
- /**
* Set directives (key/value pairs) for the Cache-Control header.
* Boolean values will be formatted as such, by including or omitting
* without an equals sign.
@@ -304,31 +302,16 @@ class ApiMain extends ApiBase {
* Cache control values set here will only be used if the cache mode is not
* private, see setCacheMode().
*
- * @param $directives array
+ * @param array $directives
*/
public function setCacheControl( $directives ) {
$this->mCacheControl = $directives + $this->mCacheControl;
}
/**
- * Make sure Vary: Cookie and friends are set. Use this when the output of a request
- * may be cached for anons but may not be cached for logged-in users.
- *
- * WARNING: This function must be called CONSISTENTLY for a given URL. This means that a
- * given URL must either always or never call this function; if it sometimes does and
- * sometimes doesn't, stuff will break.
- *
- * @deprecated since 1.17 Use setCacheMode( 'anon-public-user-private' )
- */
- public function setVaryCookie() {
- wfDeprecated( __METHOD__, '1.17' );
- $this->setCacheMode( 'anon-public-user-private' );
- }
-
- /**
* Create an instance of an output formatter by its name
*
- * @param $format string
+ * @param string $format
*
* @return ApiFormatBase
*/
@@ -337,6 +320,7 @@ class ApiMain extends ApiBase {
if ( $printer === null ) {
$this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
}
+
return $printer;
}
@@ -379,37 +363,7 @@ class ApiMain extends ApiBase {
try {
$this->executeAction();
} catch ( Exception $e ) {
- // Allow extra cleanup and logging
- wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
-
- // Log it
- if ( !( $e instanceof UsageException ) ) {
- MWExceptionHandler::logException( $e );
- }
-
- // Handle any kind of exception by outputting properly formatted error message.
- // If this fails, an unhandled exception should be thrown so that global error
- // handler will process and log it.
-
- $errCode = $this->substituteResultWithError( $e );
-
- // Error results should not be cached
- $this->setCacheMode( 'private' );
-
- $response = $this->getRequest()->response();
- $headerStr = 'MediaWiki-API-Error: ' . $errCode;
- if ( $e->getCode() === 0 ) {
- $response->header( $headerStr );
- } else {
- $response->header( $headerStr, true, $e->getCode() );
- }
-
- // Reset and print just the error message
- ob_clean();
-
- // If the error occurred during printing, do a printer->profileOut()
- $this->mPrinter->safeProfileOut();
- $this->printResult( true );
+ $this->handleException( $e );
}
// Log the request whether or not there was an error
@@ -427,6 +381,79 @@ class ApiMain extends ApiBase {
}
/**
+ * Handle an exception as an API response
+ *
+ * @since 1.23
+ * @param Exception $e
+ */
+ protected function handleException( Exception $e ) {
+ // Bug 63145: Rollback any open database transactions
+ if ( !( $e instanceof UsageException ) ) {
+ // UsageExceptions are intentional, so don't rollback if that's the case
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+ }
+
+ // Allow extra cleanup and logging
+ wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
+
+ // Log it
+ if ( !( $e instanceof UsageException ) ) {
+ MWExceptionHandler::logException( $e );
+ }
+
+ // Handle any kind of exception by outputting properly formatted error message.
+ // If this fails, an unhandled exception should be thrown so that global error
+ // handler will process and log it.
+
+ $errCode = $this->substituteResultWithError( $e );
+
+ // Error results should not be cached
+ $this->setCacheMode( 'private' );
+
+ $response = $this->getRequest()->response();
+ $headerStr = 'MediaWiki-API-Error: ' . $errCode;
+ if ( $e->getCode() === 0 ) {
+ $response->header( $headerStr );
+ } else {
+ $response->header( $headerStr, true, $e->getCode() );
+ }
+
+ // Reset and print just the error message
+ ob_clean();
+
+ // If the error occurred during printing, do a printer->profileOut()
+ $this->mPrinter->safeProfileOut();
+ $this->printResult( true );
+ }
+
+ /**
+ * Handle an exception from the ApiBeforeMain hook.
+ *
+ * This tries to print the exception as an API response, to be more
+ * friendly to clients. If it fails, it will rethrow the exception.
+ *
+ * @since 1.23
+ * @param Exception $e
+ */
+ public static function handleApiBeforeMainException( Exception $e ) {
+ ob_start();
+
+ try {
+ $main = new self( RequestContext::getMain(), false );
+ $main->handleException( $e );
+ } catch ( Exception $e2 ) {
+ // Nope, even that didn't work. Punt.
+ throw $e;
+ }
+
+ // Log the request and reset cache headers
+ $main->logRequest( 0 );
+ $main->sendCacheHeaders();
+
+ ob_end_flush();
+ }
+
+ /**
* Check the &origin= query parameter against the Origin: HTTP header and respond appropriately.
*
* If no origin parameter is present, nothing happens.
@@ -439,8 +466,6 @@ class ApiMain extends ApiBase {
* @return bool False if the caller should abort (403 case), true otherwise (all other cases)
*/
protected function handleCORS() {
- global $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions;
-
$originParam = $this->getParameter( 'origin' ); // defaults to null
if ( $originParam === null ) {
// No origin parameter, nothing to do
@@ -456,6 +481,7 @@ class ApiMain extends ApiBase {
} else {
$origins = explode( ' ', $originHeader );
}
+
if ( !in_array( $originParam, $origins ) ) {
// origin parameter set but incorrect
// Send a 403 response
@@ -463,13 +489,23 @@ class ApiMain extends ApiBase {
$response->header( "HTTP/1.1 403 $message", true, 403 );
$response->header( 'Cache-Control: no-cache' );
echo "'origin' parameter does not match Origin header\n";
+
return false;
}
- if ( self::matchOrigin( $originParam, $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions ) ) {
+
+ $config = $this->getConfig();
+ $matchOrigin = self::matchOrigin(
+ $originParam,
+ $config->get( 'CrossSiteAJAXdomains' ),
+ $config->get( 'CrossSiteAJAXdomainExceptions' )
+ );
+
+ if ( $matchOrigin ) {
$response->header( "Access-Control-Allow-Origin: $originParam" );
$response->header( 'Access-Control-Allow-Credentials: true' );
$this->getOutput()->addVaryHeader( 'Origin' );
}
+
return true;
}
@@ -478,7 +514,8 @@ class ApiMain extends ApiBase {
* @param string $value Origin header
* @param array $rules Set of wildcard rules
* @param array $exceptions Set of wildcard rules
- * @return bool True if $value matches a rule in $rules and doesn't match any rules in $exceptions, false otherwise
+ * @return bool True if $value matches a rule in $rules and doesn't match
+ * any rules in $exceptions, false otherwise
*/
protected static function matchOrigin( $value, $rules, $exceptions ) {
foreach ( $rules as $rule ) {
@@ -489,9 +526,11 @@ class ApiMain extends ApiBase {
return false;
}
}
+
return true;
}
}
+
return false;
}
@@ -510,15 +549,17 @@ class ApiMain extends ApiBase {
array( '.*?', '.' ),
$wildcard
);
+
return "/^https?:\/\/$wildcard$/";
}
protected function sendCacheHeaders() {
- global $wgUseXVO, $wgVaryOnXFP;
$response = $this->getRequest()->response();
$out = $this->getOutput();
- if ( $wgVaryOnXFP ) {
+ $config = $this->getConfig();
+
+ if ( $config->get( 'VaryOnXFP' ) ) {
$out->addVaryHeader( 'X-Forwarded-Proto' );
}
@@ -527,10 +568,11 @@ class ApiMain extends ApiBase {
return;
}
+ $useXVO = $config->get( 'UseXVO' );
if ( $this->mCacheMode == 'anon-public-user-private' ) {
$out->addVaryHeader( 'Cookie' );
$response->header( $out->getVaryHeader() );
- if ( $wgUseXVO ) {
+ if ( $useXVO ) {
$response->header( $out->getXVO() );
if ( $out->haveCacheVaryCookies() ) {
// Logged in, mark this request private
@@ -542,13 +584,14 @@ class ApiMain extends ApiBase {
// Logged in or otherwise has session (e.g. anonymous users who have edited)
// Mark request private
$response->header( 'Cache-Control: private' );
+
return;
} // else no XVO and anonymous, send public headers below
}
// Send public headers
$response->header( $out->getVaryHeader() );
- if ( $wgUseXVO ) {
+ if ( $useXVO ) {
$response->header( $out->getXVO() );
}
@@ -565,6 +608,7 @@ class ApiMain extends ApiBase {
// Sending a Vary header in this case is harmless, and protects us
// against conditional calls of setCacheMaxAge().
$response->header( 'Cache-Control: private' );
+
return;
}
@@ -596,13 +640,12 @@ class ApiMain extends ApiBase {
/**
* Replace the result data with the information about an exception.
* Returns the error code
- * @param $e Exception
+ * @param Exception $e
* @return string
*/
protected function substituteResultWithError( $e ) {
- global $wgShowHostnames;
-
$result = $this->getResult();
+
// Printer may not be initialized if the extractRequestParams() fails for the main module
if ( !isset( $this->mPrinter ) ) {
// The printer has not been created yet. Try to manually get formatter value.
@@ -612,11 +655,20 @@ class ApiMain extends ApiBase {
}
$this->mPrinter = $this->createPrinterByName( $value );
- if ( $this->mPrinter->getNeedsRawData() ) {
- $result->setRawMode();
- }
}
+ // Printer may not be able to handle errors. This is particularly
+ // likely if the module returns something for getCustomPrinter().
+ if ( !$this->mPrinter->canPrintErrors() ) {
+ $this->mPrinter->safeProfileOut();
+ $this->mPrinter = $this->createPrinterByName( self::API_DEFAULT_FORMAT );
+ }
+
+ // Update raw mode flag for the selected printer.
+ $result->setRawMode( $this->mPrinter->getNeedsRawData() );
+
+ $config = $this->getConfig();
+
if ( $e instanceof UsageException ) {
// User entered incorrect parameters - print usage screen
$errMessage = $e->getMessageArray();
@@ -626,9 +678,8 @@ class ApiMain extends ApiBase {
ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
}
} else {
- global $wgShowSQLErrors, $wgShowExceptionDetails;
// Something is seriously wrong
- if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
+ if ( ( $e instanceof DBQueryError ) && !$config->get( 'ShowSQLErrors' ) ) {
$info = 'Database query error';
} else {
$info = "Exception Caught: {$e->getMessage()}";
@@ -638,7 +689,10 @@ class ApiMain extends ApiBase {
'code' => 'internal_api_error_' . get_class( $e ),
'info' => $info,
);
- ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
+ ApiResult::setContent(
+ $errMessage,
+ $config->get( 'ShowExceptionDetails' ) ? "\n\n{$e->getTraceAsString()}\n\n" : ''
+ );
}
// Remember all the warnings to re-add them later
@@ -646,21 +700,20 @@ class ApiMain extends ApiBase {
$warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null;
$result->reset();
- $result->disableSizeCheck();
// Re-add the id
$requestid = $this->getParameter( 'requestid' );
if ( !is_null( $requestid ) ) {
- $result->addValue( null, 'requestid', $requestid );
+ $result->addValue( null, 'requestid', $requestid, ApiResult::NO_SIZE_CHECK );
}
- if ( $wgShowHostnames ) {
+ if ( $config->get( 'ShowHostnames' ) ) {
// servedby is especially useful when debugging errors
- $result->addValue( null, 'servedby', wfHostName() );
+ $result->addValue( null, 'servedby', wfHostName(), ApiResult::NO_SIZE_CHECK );
}
if ( $warnings !== null ) {
- $result->addValue( null, 'warnings', $warnings );
+ $result->addValue( null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK );
}
- $result->addValue( null, 'error', $errMessage );
+ $result->addValue( null, 'error', $errMessage, ApiResult::NO_SIZE_CHECK );
return $errMessage['code'];
}
@@ -670,8 +723,6 @@ class ApiMain extends ApiBase {
* @return array
*/
protected function setupExecuteAction() {
- global $wgShowHostnames;
-
// First add the id to the top element
$result = $this->getResult();
$requestid = $this->getParameter( 'requestid' );
@@ -679,13 +730,18 @@ class ApiMain extends ApiBase {
$result->addValue( null, 'requestid', $requestid );
}
- if ( $wgShowHostnames ) {
+ if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
$servedby = $this->getParameter( 'servedby' );
if ( $servedby ) {
$result->addValue( null, 'servedby', wfHostName() );
}
}
+ if ( $this->getParameter( 'curtimestamp' ) ) {
+ $result->addValue( null, 'curtimestamp', wfTimestamp( TS_ISO_8601, time() ),
+ ApiResult::NO_SIZE_CHECK );
+ }
+
$params = $this->extractRequestParams();
$this->mAction = $params['action'];
@@ -709,30 +765,53 @@ class ApiMain extends ApiBase {
}
$moduleParams = $module->extractRequestParams();
- // Die if token required, but not provided
- $salt = $module->getTokenSalt();
- if ( $salt !== false ) {
+ // Check token, if necessary
+ if ( $module->needsToken() === true ) {
+ throw new MWException(
+ "Module '{$module->getModuleName()}' must be updated for the new token handling. " .
+ "See documentation for ApiBase::needsToken for details."
+ );
+ }
+ if ( $module->needsToken() ) {
+ if ( !$module->mustBePosted() ) {
+ throw new MWException(
+ "Module '{$module->getModuleName()}' must require POST to use tokens."
+ );
+ }
+
if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
- } else {
- if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) {
- $this->dieUsageMsg( 'sessionfailure' );
- }
+ }
+
+ if ( !$this->getConfig()->get( 'DebugAPI' ) &&
+ array_key_exists(
+ $module->encodeParamName( 'token' ),
+ $this->getRequest()->getQueryValues()
+ )
+ ) {
+ $this->dieUsage(
+ "The '{$module->encodeParamName( 'token' )}' parameter was found in the query string, but must be in the POST body",
+ 'mustposttoken'
+ );
+ }
+
+ if ( !$module->validateToken( $moduleParams['token'], $moduleParams ) ) {
+ $this->dieUsageMsg( 'sessionfailure' );
}
}
+
return $module;
}
/**
* Check the max lag if necessary
- * @param $module ApiBase object: Api module being used
- * @param array $params an array containing the request parameters.
- * @return boolean True on success, false should exit immediately
+ * @param ApiBase $module Api module being used
+ * @param array $params Array an array containing the request parameters.
+ * @return bool True on success, false should exit immediately
*/
protected function checkMaxLag( $module, $params ) {
if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
// Check for maxlag
- global $wgShowHostnames;
$maxLag = $params['maxlag'];
list( $host, $lag ) = wfGetLB()->getMaxLag();
if ( $lag > $maxLag ) {
@@ -741,26 +820,26 @@ class ApiMain extends ApiBase {
$response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
$response->header( 'X-Database-Lag: ' . intval( $lag ) );
- if ( $wgShowHostnames ) {
+ if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
$this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
- } else {
- $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
}
- return false;
+
+ $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
}
}
+
return true;
}
/**
* Check for sufficient permissions to execute
- * @param $module ApiBase An Api module
+ * @param ApiBase $module An Api module
*/
protected function checkExecutePermissions( $module ) {
$user = $this->getUser();
if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) &&
- !$user->isAllowed( 'read' ) )
- {
+ !$user->isAllowed( 'read' )
+ ) {
$this->dieUsageMsg( 'readrequired' );
}
if ( $module->isWriteMode() ) {
@@ -783,9 +862,31 @@ class ApiMain extends ApiBase {
}
/**
+ * Check asserts of the user's rights
+ * @param array $params
+ */
+ protected function checkAsserts( $params ) {
+ if ( isset( $params['assert'] ) ) {
+ $user = $this->getUser();
+ switch ( $params['assert'] ) {
+ case 'user':
+ if ( $user->isAnon() ) {
+ $this->dieUsage( 'Assertion that the user is logged in failed', 'assertuserfailed' );
+ }
+ break;
+ case 'bot':
+ if ( !$user->isAllowed( 'bot' ) ) {
+ $this->dieUsage( 'Assertion that the user has the bot right failed', 'assertbotfailed' );
+ }
+ break;
+ }
+ }
+ }
+
+ /**
* Check POST for external response and setup result printer
- * @param $module ApiBase An Api module
- * @param array $params an array with the request parameters
+ * @param ApiBase $module An Api module
+ * @param array $params An array with the request parameters
*/
protected function setupExternalResponse( $module, $params ) {
if ( !$this->getRequest()->wasPosted() && $module->mustBePosted() ) {
@@ -824,6 +925,8 @@ class ApiMain extends ApiBase {
$this->setupExternalResponse( $module, $params );
}
+ $this->checkAsserts( $params );
+
// Execute
$module->profileIn();
$module->execute();
@@ -843,7 +946,7 @@ class ApiMain extends ApiBase {
/**
* Log the preceding request
- * @param $time Time in seconds
+ * @param int $time Time in seconds
*/
protected function logRequest( $time ) {
$request = $this->getRequest();
@@ -867,25 +970,30 @@ class ApiMain extends ApiBase {
}
}
$s .= "\n";
- wfDebugLog( 'api', $s, false );
+ wfDebugLog( 'api', $s, 'private' );
}
/**
* Encode a value in a format suitable for a space-separated log line.
+ * @param string $s
+ * @return string
*/
protected function encodeRequestLogValue( $s ) {
static $table;
if ( !$table ) {
$chars = ';@$!*(),/:';
- for ( $i = 0; $i < strlen( $chars ); $i++ ) {
+ $numChars = strlen( $chars );
+ for ( $i = 0; $i < $numChars; $i++ ) {
$table[rawurlencode( $chars[$i] )] = $chars[$i];
}
}
+
return strtr( rawurlencode( $s ), $table );
}
/**
* Get the request parameters used in the course of the preceding execute() request
+ * @return array
*/
protected function getParamsUsed() {
return array_keys( $this->mParamsUsed );
@@ -893,19 +1001,35 @@ class ApiMain extends ApiBase {
/**
* Get a request value, and register the fact that it was used, for logging.
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
*/
public function getVal( $name, $default = null ) {
$this->mParamsUsed[$name] = true;
- return $this->getRequest()->getVal( $name, $default );
+
+ $ret = $this->getRequest()->getVal( $name );
+ if ( $ret === null ) {
+ if ( $this->getRequest()->getArray( $name ) !== null ) {
+ // See bug 10262 for why we don't just join( '|', ... ) the
+ // array.
+ $this->setWarning(
+ "Parameter '$name' uses unsupported PHP array syntax"
+ );
+ }
+ $ret = $default;
+ }
+ return $ret;
}
/**
* Get a boolean request value, and register the fact that the parameter
* was used, for logging.
+ * @param string $name
+ * @return bool
*/
public function getCheck( $name ) {
- $this->mParamsUsed[$name] = true;
- return $this->getRequest()->getCheck( $name );
+ return $this->getVal( $name, null ) !== null;
}
/**
@@ -917,6 +1041,7 @@ class ApiMain extends ApiBase {
*/
public function getUpload( $name ) {
$this->mParamsUsed[$name] = true;
+
return $this->getRequest()->getUpload( $name );
}
@@ -948,11 +1073,10 @@ class ApiMain extends ApiBase {
/**
* Print results using the current printer
*
- * @param $isError bool
+ * @param bool $isError
*/
protected function printResult( $isError ) {
- global $wgDebugAPI;
- if ( $wgDebugAPI !== false ) {
+ if ( $this->getConfig()->get( 'DebugAPI' ) !== false ) {
$this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
}
@@ -991,11 +1115,11 @@ class ApiMain extends ApiBase {
return array(
'format' => array(
ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'action' => array(
ApiBase::PARAM_DFLT => 'help',
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'maxlag' => array(
ApiBase::PARAM_TYPE => 'integer'
@@ -1008,8 +1132,12 @@ class ApiMain extends ApiBase {
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_DFLT => 0
),
+ 'assert' => array(
+ ApiBase::PARAM_TYPE => array( 'user', 'bot' )
+ ),
'requestid' => null,
'servedby' => false,
+ 'curtimestamp' => false,
'origin' => null,
);
}
@@ -1033,14 +1161,20 @@ class ApiMain extends ApiBase {
),
'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
+ 'assert' => 'Verify the user is logged in if set to "user", or has the bot userright if "bot"',
'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
- 'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error',
+ 'servedby' => 'Include the hostname that served the request in the ' .
+ 'results. Unconditionally shown on error',
+ 'curtimestamp' => 'Include the current timestamp in the result.',
'origin' => array(
- 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.',
- 'This must be included in any pre-flight request, and therefore must be part of the request URI (not the POST body).',
- 'This must match one of the origins in the Origin: header exactly, so it has to be set to something like http://en.wikipedia.org or https://meta.wikimedia.org .',
- 'If this parameter does not match the Origin: header, a 403 response will be returned.',
- 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.',
+ 'When accessing the API using a cross-domain AJAX request (CORS), set this to the',
+ 'originating domain. This must be included in any pre-flight request, and',
+ 'therefore must be part of the request URI (not the POST body). This must match',
+ 'one of the origins in the Origin: header exactly, so it has to be set to ',
+ 'something like http://en.wikipedia.org or https://meta.wikimedia.org . If this',
+ 'parameter does not match the Origin: header, a 403 response will be returned. If',
+ 'this parameter matches the Origin: header and the origin is whitelisted, an',
+ 'Access-Control-Allow-Origin header will be set.',
),
);
}
@@ -1054,33 +1188,35 @@ class ApiMain extends ApiBase {
return array(
'',
'',
- '**********************************************************************************************************',
- '** **',
- '** This is an auto-generated MediaWiki API documentation page **',
- '** **',
- '** Documentation and Examples: **',
- '** https://www.mediawiki.org/wiki/API **',
- '** **',
- '**********************************************************************************************************',
+ '**********************************************************************************************',
+ '** **',
+ '** This is an auto-generated MediaWiki API documentation page **',
+ '** **',
+ '** Documentation and Examples: **',
+ '** https://www.mediawiki.org/wiki/API **',
+ '** **',
+ '**********************************************************************************************',
'',
'Status: All features shown on this page should be working, but the API',
' is still in active development, and may change at any time.',
- ' Make sure to monitor our mailing list for any updates',
+ ' Make sure to monitor our mailing list for any updates.',
'',
'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent',
' with the key "MediaWiki-API-Error" and then both the value of the',
- ' header and the error code sent back will be set to the same value',
+ ' header and the error code sent back will be set to the same value.',
'',
' In the case of an invalid action being passed, these will have a value',
- ' of "unknown_action"',
+ ' of "unknown_action".',
'',
- ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings',
+ ' For more information see https://www.mediawiki.org' .
+ '/wiki/API:Errors_and_warnings',
'',
'Documentation: https://www.mediawiki.org/wiki/API:Main_page',
'FAQ https://www.mediawiki.org/wiki/API:FAQ',
'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce',
- 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
+ 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&' .
+ 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
'',
'',
'',
@@ -1090,30 +1226,18 @@ class ApiMain extends ApiBase {
}
/**
- * @return array
- */
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'readonlytext' ),
- array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ),
- array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ),
- array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ),
- array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ),
- ) );
- }
-
- /**
* Returns an array of strings with credits for the API
* @return array
*/
protected function getCredits() {
return array(
'API developers:',
- ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)',
- ' Victor Vasiliev - vasilvv @ gmail . com',
- ' Bryan Tong Minh - bryan . tongminh @ gmail . com',
- ' Sam Reed - sam @ reedyboy . net',
- ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)',
+ ' Roan Kattouw (lead developer Sep 2007-2009)',
+ ' Victor Vasiliev',
+ ' Bryan Tong Minh',
+ ' Sam Reed',
+ ' Yuri Astrakhan (creator, lead developer Sep 2006-Sep 2007, 2012-2013)',
+ ' Brad Jorsch (lead developer 2013-now)',
'',
'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
'or file a bug report at https://bugzilla.wikimedia.org/'
@@ -1123,7 +1247,7 @@ class ApiMain extends ApiBase {
/**
* Sets whether the pretty-printer should format *bold* and $italics$
*
- * @param $help bool
+ * @param bool $help
*/
public function setHelp( $help = true ) {
$this->mPrinter->setHelp( $help );
@@ -1135,21 +1259,24 @@ class ApiMain extends ApiBase {
* @return string
*/
public function makeHelpMsg() {
- global $wgMemc, $wgAPICacheHelpTimeout;
+ global $wgMemc;
$this->setHelp();
// Get help text from cache if present
$key = wfMemcKey( 'apihelp', $this->getModuleName(),
str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) );
- if ( $wgAPICacheHelpTimeout > 0 ) {
+
+ $cacheHelpTimeout = $this->getConfig()->get( 'APICacheHelpTimeout' );
+ if ( $cacheHelpTimeout > 0 ) {
$cached = $wgMemc->get( $key );
if ( $cached ) {
return $cached;
}
}
$retval = $this->reallyMakeHelpMsg();
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout );
+ if ( $cacheHelpTimeout > 0 ) {
+ $wgMemc->set( $key, $retval, $cacheHelpTimeout );
}
+
return $retval;
}
@@ -1180,7 +1307,7 @@ class ApiMain extends ApiBase {
foreach ( self::$mRights as $right => $rightMsg ) {
$groups = User::getGroupsWithPermission( $right );
$msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) .
- "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
+ "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
}
$msg .= "\n$astriks Formats $astriks\n\n";
@@ -1200,8 +1327,9 @@ class ApiMain extends ApiBase {
}
/**
- * @param $module ApiBase
- * @param string $paramName What type of request is this? e.g. action, query, list, prop, meta, format
+ * @param ApiBase $module
+ * @param string $paramName What type of request is this? e.g. action,
+ * query, list, prop, meta, format
* @return string
*/
public static function makeHelpMsgHeader( $module, $paramName ) {
@@ -1234,6 +1362,7 @@ class ApiMain extends ApiBase {
*/
public function getShowVersions() {
wfDeprecated( __METHOD__, '1.21' );
+
return false;
}
@@ -1252,7 +1381,7 @@ class ApiMain extends ApiBase {
*
* @deprecated since 1.21, Use getModuleManager()->addModule() instead.
* @param string $name The identifier for this module.
- * @param $class ApiBase The class where this module is implemented.
+ * @param ApiBase $class The class where this module is implemented.
*/
protected function addModule( $name, $class ) {
$this->getModuleManager()->addModule( $name, 'action', $class );
@@ -1264,7 +1393,7 @@ class ApiMain extends ApiBase {
*
* @deprecated since 1.21, Use getModuleManager()->addModule() instead.
* @param string $name The identifier for this format.
- * @param $class ApiFormatBase The class implementing this format.
+ * @param ApiFormatBase $class The class implementing this format.
*/
protected function addFormat( $name, $class ) {
$this->getModuleManager()->addModule( $name, 'format', $class );
@@ -1307,10 +1436,10 @@ class UsageException extends MWException {
private $mExtraData;
/**
- * @param $message string
- * @param $codestr string
- * @param $code int
- * @param $extradata array|null
+ * @param string $message
+ * @param string $codestr
+ * @param int $code
+ * @param array|null $extradata
*/
public function __construct( $message, $codestr, $code = 0, $extradata = null ) {
parent::__construct( $message, $code );
@@ -1336,6 +1465,7 @@ class UsageException extends MWException {
if ( is_array( $this->mExtraData ) ) {
$result = array_merge( $result, $this->mExtraData );
}
+
return $result;
}
diff --git a/includes/api/ApiModuleManager.php b/includes/api/ApiModuleManager.php
index 100392bf..a0300ab5 100644
--- a/includes/api/ApiModuleManager.php
+++ b/includes/api/ApiModuleManager.php
@@ -33,9 +33,21 @@
*/
class ApiModuleManager extends ContextSource {
+ /**
+ * @var ApiBase
+ */
private $mParent;
+ /**
+ * @var ApiBase[]
+ */
private $mInstances = array();
+ /**
+ * @var null[]
+ */
private $mGroups = array();
+ /**
+ * @var array[]
+ */
private $mModules = array();
/**
@@ -47,13 +59,55 @@ class ApiModuleManager extends ContextSource {
}
/**
- * Add a list of modules to the manager
- * @param array $modules A map of ModuleName => ModuleClass
+ * Add a list of modules to the manager. Each module is described
+ * by a module spec.
+ *
+ * Each module spec is an associative array containing at least
+ * the 'class' key for the module's class, and optionally a
+ * 'factory' key for the factory function to use for the module.
+ *
+ * That factory function will be called with two parameters,
+ * the parent module (an instance of ApiBase, usually ApiMain)
+ * and the name the module was registered under. The return
+ * value must be an instance of the class given in the 'class'
+ * field.
+ *
+ * For backward compatibility, the module spec may also be a
+ * simple string containing the module's class name. In that
+ * case, the class' constructor will be called with the parent
+ * module and module name as parameters, as described above.
+ *
+ * Examples for defining module specs:
+ *
+ * @code
+ * $modules['foo'] = 'ApiFoo';
+ * $modules['bar'] = array(
+ * 'class' => 'ApiBar',
+ * 'factory' => function( $main, $name ) { ... }
+ * );
+ * $modules['xyzzy'] = array(
+ * 'class' => 'ApiXyzzy',
+ * 'factory' => array( 'XyzzyFactory', 'newApiModule' )
+ * );
+ * @endcode
+ *
+ * @param array $modules A map of ModuleName => ModuleSpec; The ModuleSpec
+ * is either a string containing the module's class name, or an associative
+ * array (see above for details).
* @param string $group Which group modules belong to (action,format,...)
*/
public function addModules( array $modules, $group ) {
- foreach ( $modules as $name => $class ) {
- $this->addModule( $name, $group, $class );
+
+ foreach ( $modules as $name => $moduleSpec ) {
+ if ( is_array( $moduleSpec ) ) {
+ $class = $moduleSpec['class'];
+ $factory = ( isset( $moduleSpec['factory'] ) ? $moduleSpec['factory'] : null );
+ } else {
+ $class = $moduleSpec;
+ $factory = null;
+ }
+
+ $this->addModule( $name, $group, $class, $factory );
}
}
@@ -62,49 +116,100 @@ class ApiModuleManager extends ContextSource {
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
*
- * @param string $group Name of the module group
* @param string $name The identifier for this module.
+ * @param string $group Name of the module group
* @param string $class The class where this module is implemented.
+ * @param callable|null $factory Callback for instantiating the module.
+ *
+ * @throws InvalidArgumentException
*/
- public function addModule( $name, $group, $class ) {
+ public function addModule( $name, $group, $class, $factory = null ) {
+ if ( !is_string( $name ) ) {
+ throw new InvalidArgumentException( '$name must be a string' );
+ }
+
+ if ( !is_string( $group ) ) {
+ throw new InvalidArgumentException( '$group must be a string' );
+ }
+
+ if ( !is_string( $class ) ) {
+ throw new InvalidArgumentException( '$class must be a string' );
+ }
+
+ if ( $factory !== null && !is_callable( $factory ) ) {
+ throw new InvalidArgumentException( '$factory must be a callable (or null)' );
+ }
+
$this->mGroups[$group] = null;
- $this->mModules[$name] = array( $group, $class );
+ $this->mModules[$name] = array( $group, $class, $factory );
}
/**
* Get module instance by name, or instantiate it if it does not exist
- * @param string $moduleName module name
- * @param string $group optionally validate that the module is in a specific group
- * @param bool $ignoreCache if true, force-creates a new instance and does not cache it
- * @return mixed the new module instance, or null if failed
+ *
+ * @param string $moduleName Module name
+ * @param string $group Optionally validate that the module is in a specific group
+ * @param bool $ignoreCache If true, force-creates a new instance and does not cache it
+ *
+ * @return ApiBase|null The new module instance, or null if failed
*/
public function getModule( $moduleName, $group = null, $ignoreCache = false ) {
if ( !isset( $this->mModules[$moduleName] ) ) {
return null;
}
- $grpCls = $this->mModules[$moduleName];
- if ( $group !== null && $grpCls[0] !== $group ) {
+
+ list( $moduleGroup, $moduleClass, $moduleFactory ) = $this->mModules[$moduleName];
+
+ if ( $group !== null && $moduleGroup !== $group ) {
return null;
}
+
if ( !$ignoreCache && isset( $this->mInstances[$moduleName] ) ) {
// already exists
return $this->mInstances[$moduleName];
} else {
// new instance
- $class = $grpCls[1];
- $instance = new $class ( $this->mParent, $moduleName );
+ $instance = $this->instantiateModule( $moduleName, $moduleClass, $moduleFactory );
+
if ( !$ignoreCache ) {
// cache this instance in case it is needed later
$this->mInstances[$moduleName] = $instance;
}
+
return $instance;
}
}
/**
+ * Instantiate the module using the given class or factory function.
+ *
+ * @param string $name The identifier for this module.
+ * @param string $class The class where this module is implemented.
+ * @param callable|null $factory Callback for instantiating the module.
+ *
+ * @throws MWException
+ * @return ApiBase
+ */
+ private function instantiateModule( $name, $class, $factory = null ) {
+ if ( $factory !== null ) {
+ // create instance from factory
+ $instance = call_user_func( $factory, $this->mParent, $name );
+
+ if ( !$instance instanceof $class ) {
+ throw new MWException( "The factory function for module $name did not return an instance of $class!" );
+ }
+ } else {
+ // create instance from class name
+ $instance = new $class( $this->mParent, $name );
+ }
+
+ return $instance;
+ }
+
+ /**
* Get an array of modules in a specific group or all if no group is set.
- * @param string $group optional group filter
- * @return array list of module names
+ * @param string $group Optional group filter
+ * @return array List of module names
*/
public function getNames( $group = null ) {
if ( $group === null ) {
@@ -116,13 +221,14 @@ class ApiModuleManager extends ContextSource {
$result[] = $name;
}
}
+
return $result;
}
/**
* Create an array of (moduleName => moduleClass) for a specific group or for all.
- * @param string $group name of the group to get or null for all
- * @return array name=>class map
+ * @param string $group Name of the group to get or null for all
+ * @return array Name=>class map
*/
public function getNamesWithClasses( $group = null ) {
$result = array();
@@ -131,34 +237,50 @@ class ApiModuleManager extends ContextSource {
$result[$name] = $grpCls[1];
}
}
+
return $result;
}
/**
+ * Returns the class name of the given module
+ *
+ * @param string $module Module name
+ * @return string|bool class name or false if the module does not exist
+ * @since 1.24
+ */
+ public function getClassName( $module ) {
+ if ( isset( $this->mModules[$module] ) ) {
+ return $this->mModules[$module][1];
+ }
+
+ return false;
+ }
+
+ /**
* Returns true if the specific module is defined at all or in a specific group.
- * @param string $moduleName module name
- * @param string $group group name to check against, or null to check all groups,
- * @return boolean true if defined
+ * @param string $moduleName Module name
+ * @param string $group Group name to check against, or null to check all groups,
+ * @return bool True if defined
*/
public function isDefined( $moduleName, $group = null ) {
if ( isset( $this->mModules[$moduleName] ) ) {
return $group === null || $this->mModules[$moduleName][0] === $group;
- } else {
- return false;
}
+
+ return false;
}
/**
* Returns the group name for the given module
* @param string $moduleName
- * @return string group name or null if missing
+ * @return string Group name or null if missing
*/
public function getModuleGroup( $moduleName ) {
if ( isset( $this->mModules[$moduleName] ) ) {
return $this->mModules[$moduleName][0];
- } else {
- return null;
}
+
+ return null;
}
/**
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index c18036cf..04e931d2 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -61,8 +61,8 @@ class ApiMove extends ApiBase {
if ( $toTitle->getNamespace() == NS_FILE
&& !RepoGroup::singleton()->getLocalRepo()->findFile( $toTitle )
- && wfFindFile( $toTitle ) )
- {
+ && wfFindFile( $toTitle )
+ ) {
if ( !$params['ignorewarnings'] && $user->isAllowed( 'reupload-shared' ) ) {
$this->dieUsageMsg( 'sharedfile-exists' );
} elseif ( !$user->isAllowed( 'reupload-shared' ) ) {
@@ -77,7 +77,11 @@ class ApiMove extends ApiBase {
$this->dieUsageMsg( reset( $retval ) );
}
- $r = array( 'from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason'] );
+ $r = array(
+ 'from' => $fromTitle->getPrefixedText(),
+ 'to' => $toTitle->getPrefixedText(),
+ 'reason' => $params['reason']
+ );
if ( $fromTitle->exists() ) {
//NOTE: we assume that if the old title exists, it's because it was re-created as
@@ -115,7 +119,7 @@ class ApiMove extends ApiBase {
// Move subpages
if ( $params['movesubpages'] ) {
$r['subpages'] = $this->moveSubpages( $fromTitle, $toTitle,
- $params['reason'], $params['noredirect'] );
+ $params['reason'], $params['noredirect'] );
$result->setIndexedTagName( $r['subpages'], 'subpage' );
if ( $params['movetalk'] ) {
@@ -130,8 +134,10 @@ class ApiMove extends ApiBase {
$watch = $params['watchlist'];
} elseif ( $params['watch'] ) {
$watch = 'watch';
+ $this->logFeatureUsage( 'action=move&watch' );
} elseif ( $params['unwatch'] ) {
$watch = 'unwatch';
+ $this->logFeatureUsage( 'action=move&unwatch' );
}
// Watch pages
@@ -144,8 +150,8 @@ class ApiMove extends ApiBase {
/**
* @param Title $fromTitle
* @param Title $toTitle
- * @param $reason
- * @param $noredirect
+ * @param string $reason
+ * @param bool $noredirect
* @return array
*/
public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect ) {
@@ -153,20 +159,21 @@ class ApiMove extends ApiBase {
$success = $fromTitle->moveSubpages( $toTitle, true, $reason, !$noredirect );
if ( isset( $success[0] ) ) {
return array( 'error' => $this->parseMsg( $success ) );
- } else {
- // At least some pages could be moved
- // Report each of them separately
- foreach ( $success as $oldTitle => $newTitle ) {
- $r = array( 'from' => $oldTitle );
- if ( is_array( $newTitle ) ) {
- $r['error'] = $this->parseMsg( reset( $newTitle ) );
- } else {
- // Success
- $r['to'] = $newTitle;
- }
- $retval[] = $r;
+ }
+
+ // At least some pages could be moved
+ // Report each of them separately
+ foreach ( $success as $oldTitle => $newTitle ) {
+ $r = array( 'from' => $oldTitle );
+ if ( is_array( $newTitle ) ) {
+ $r['error'] = $this->parseMsg( reset( $newTitle ) );
+ } else {
+ // Success
+ $r['to'] = $newTitle;
}
+ $retval[] = $r;
}
+
return $retval;
}
@@ -188,10 +195,6 @@ class ApiMove extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => '',
'movetalk' => false,
'movesubpages' => false,
@@ -219,79 +222,35 @@ class ApiMove extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'from' => "Title of the page you want to move. Cannot be used together with {$p}fromid",
'fromid' => "Page ID of the page you want to move. Cannot be used together with {$p}from",
'to' => 'Title you want to rename the page to',
- 'token' => 'A move token previously retrieved through prop=info',
'reason' => 'Reason for the move',
'movetalk' => 'Move the talk page, if it exists',
'movesubpages' => 'Move subpages, if applicable',
'noredirect' => 'Don\'t create a redirect',
'watch' => 'Add the page and the redirect to your watchlist',
'unwatch' => 'Remove the page and the redirect from your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
'ignorewarnings' => 'Ignore any warnings'
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'from' => 'string',
- 'to' => 'string',
- 'reason' => 'string',
- 'redirectcreated' => 'boolean',
- 'moveoverredirect' => 'boolean',
- 'talkfrom' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'talkto' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'talkmoveoverredirect' => 'boolean',
- 'talkmove-error-code' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'talkmove-error-info' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
- return 'Move a page';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'from', 'fromid' ) ),
- array(
- array( 'invalidtitle', 'from' ),
- array( 'nosuchpageid', 'fromid' ),
- array( 'notanarticle' ),
- array( 'invalidtitle', 'to' ),
- array( 'sharedfile-exists' ),
- )
- );
+ return 'Move a page.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=move&from=Badtitle&to=Goodtitle&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
+ 'api.php?action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
+ 'reason=Misspelled%20title&movetalk=&noredirect='
);
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index 315ace37..7fb045e3 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -40,11 +40,11 @@ class ApiOpenSearch extends ApiBase {
if ( in_array( $format, $allowed ) ) {
return $this->getMain()->createPrinterByName( $format );
}
+
return $this->getMain()->createPrinterByName( $allowed[0] );
}
public function execute() {
- global $wgEnableOpenSearchSuggest, $wgSearchSuggestCacheExpiry;
$params = $this->extractRequestParams();
$search = $params['search'];
$limit = $params['limit'];
@@ -52,35 +52,15 @@ class ApiOpenSearch extends ApiBase {
$suggest = $params['suggest'];
// Some script that was loaded regardless of wgEnableOpenSearchSuggest, likely cached.
- if ( $suggest && !$wgEnableOpenSearchSuggest ) {
+ if ( $suggest && !$this->getConfig()->get( 'EnableOpenSearchSuggest' ) ) {
$searches = array();
} else {
// Open search results may be stored for a very long time
- $this->getMain()->setCacheMaxAge( $wgSearchSuggestCacheExpiry );
+ $this->getMain()->setCacheMaxAge( $this->getConfig()->get( 'SearchSuggestCacheExpiry' ) );
$this->getMain()->setCacheMode( 'public' );
- $searches = PrefixSearch::titleSearch( $search, $limit,
- $namespaces );
-
- // if the content language has variants, try to retrieve fallback results
- $fallbackLimit = $limit - count( $searches );
- if ( $fallbackLimit > 0 ) {
- global $wgContLang;
-
- $fallbackSearches = $wgContLang->autoConvertToAllVariants( $search );
- $fallbackSearches = array_diff( array_unique( $fallbackSearches ), array( $search ) );
-
- foreach ( $fallbackSearches as $fbs ) {
- $fallbackSearchResult = PrefixSearch::titleSearch( $fbs, $fallbackLimit,
- $namespaces );
- $searches = array_merge( $searches, $fallbackSearchResult );
- $fallbackLimit -= count( $fallbackSearchResult );
-
- if ( $fallbackLimit == 0 ) {
- break;
- }
- }
- }
+ $searcher = new StringPrefixSearch;
+ $searches = $searcher->searchWithVariants( $search, $limit, $namespaces );
}
// Set top level elements
$result = $this->getResult();
@@ -92,7 +72,7 @@ class ApiOpenSearch extends ApiBase {
return array(
'search' => null,
'limit' => array(
- ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_DFLT => $this->getConfig()->get( 'OpenSearchDefaultLimit' ),
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => 100,
@@ -122,7 +102,7 @@ class ApiOpenSearch extends ApiBase {
}
public function getDescription() {
- return 'Search the wiki using the OpenSearch protocol';
+ return 'Search the wiki using the OpenSearch protocol.';
}
public function getExamples() {
diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php
index 7256066d..b01dc3e2 100644
--- a/includes/api/ApiOptions.php
+++ b/includes/api/ApiOptions.php
@@ -31,7 +31,6 @@
* @ingroup API
*/
class ApiOptions extends ApiBase {
-
/**
* Changes preferences of the current user.
*/
@@ -99,6 +98,9 @@ class ApiOptions extends ApiBase {
$validation = true;
}
break;
+ case 'special':
+ $validation = "cannot be set by this module";
+ break;
case 'unused':
default:
$validation = "not a valid preference";
@@ -133,10 +135,6 @@ class ApiOptions extends ApiBase {
$optionKinds[] = 'all';
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reset' => false,
'resetkinds' => array(
ApiBase::PARAM_TYPE => $optionKinds,
@@ -155,50 +153,32 @@ class ApiOptions extends ApiBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => array(
- ApiBase::PROP_TYPE => array(
- 'success'
- )
- )
- )
- );
- }
-
public function getParamDescription() {
return array(
- 'token' => 'An options token previously obtained through the action=tokens',
'reset' => 'Resets preferences to the site defaults',
'resetkinds' => 'List of types of options to reset when the "reset" option is set',
- 'change' => 'List of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters. If no value is given (not even an equals sign), e.g., optionname|otheroption|..., the option will be reset to its default value',
+ 'change' => array( 'List of changes, formatted name=value (e.g. skin=vector), ' .
+ 'value cannot contain pipe characters. If no value is given (not ',
+ 'even an equals sign), e.g., optionname|otheroption|..., the ' .
+ 'option will be reset to its default value'
+ ),
'optionname' => 'A name of a option which should have an optionvalue set',
- 'optionvalue' => 'A value of the option specified by the optionname, can contain pipe characters',
+ 'optionvalue' => 'A value of the option specified by the optionname, ' .
+ 'can contain pipe characters',
);
}
public function getDescription() {
return array(
- 'Change preferences of the current user',
+ 'Change preferences of the current user.',
'Only options which are registered in core or in one of installed extensions,',
- 'or as options with keys prefixed with \'userjs-\' (intended to be used by user scripts), can be set.'
+ 'or as options with keys prefixed with \'userjs-\' (intended to be used by user',
+ 'scripts), can be set.'
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'notloggedin', 'info' => 'Anonymous users cannot change preferences' ),
- array( 'code' => 'nochanges', 'info' => 'No changes were requested' ),
- ) );
- }
-
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getHelpUrls() {
@@ -209,7 +189,8 @@ class ApiOptions extends ApiBase {
return array(
'api.php?action=options&reset=&token=123ABC',
'api.php?action=options&change=skin=vector|hideminor=1&token=123ABC',
- 'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
+ 'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&' .
+ 'optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
);
}
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index b05cb2b6..0f264675 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -39,7 +39,6 @@
* @since 1.21 derives from ApiBase instead of ApiQueryBase
*/
class ApiPageSet extends ApiBase {
-
/**
* Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter
* @since 1.21
@@ -62,6 +61,7 @@ class ApiPageSet extends ApiBase {
private $mSpecialTitles = array();
private $mNormalizedTitles = array();
private $mInterwikiTitles = array();
+ /** @var Title[] */
private $mPendingRedirectIDs = array();
private $mConvertedTitles = array();
private $mGoodRevIDs = array();
@@ -69,17 +69,38 @@ class ApiPageSet extends ApiBase {
private $mFakePageId = -1;
private $mCacheMode = 'public';
private $mRequestedPageFields = array();
+ /** @var int */
+ private $mDefaultNamespace = NS_MAIN;
+
/**
- * @var int
+ * Add all items from $values into the result
+ * @param array $result Output
+ * @param array $values Values to add
+ * @param string $flag The name of the boolean flag to mark this element
+ * @param string $name If given, name of the value
*/
- private $mDefaultNamespace = NS_MAIN;
+ private static function addValues( array &$result, $values, $flag = null, $name = null ) {
+ foreach ( $values as $val ) {
+ if ( $val instanceof Title ) {
+ $v = array();
+ ApiQueryBase::addTitleInfo( $v, $val );
+ } elseif ( $name !== null ) {
+ $v = array( $name => $val );
+ } else {
+ $v = $val;
+ }
+ if ( $flag !== null ) {
+ $v[$flag] = '';
+ }
+ $result[] = $v;
+ }
+ }
/**
- * Constructor
- * @param $dbSource ApiBase Module implementing getDB().
+ * @param ApiBase $dbSource Module implementing getDB().
* Allows PageSet to reuse existing db connection from the shared state like ApiQuery.
* @param int $flags Zero or more flags like DISABLE_GENERATORS
- * @param int $defaultNamespace the namespace to use if none is specified by a prefix.
+ * @param int $defaultNamespace The namespace to use if none is specified by a prefix.
* @since 1.21 accepts $flags instead of two boolean values
*/
public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
@@ -112,7 +133,8 @@ class ApiPageSet extends ApiBase {
/**
* Populate the PageSet from the request parameters.
- * @param bool $isDryRun If true, instantiates generator, but only to mark relevant parameters as used
+ * @param bool $isDryRun If true, instantiates generator, but only to mark
+ * relevant parameters as used
*/
private function executeInternal( $isDryRun ) {
$this->profileIn();
@@ -200,8 +222,9 @@ class ApiPageSet extends ApiBase {
break;
case 'revids':
if ( $this->mResolveRedirects ) {
- $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' .
- 'Any redirects the revids= point to have not been resolved.' );
+ $this->setWarning( 'Redirect resolution cannot be used ' .
+ 'together with the revids= parameter. Any redirects ' .
+ 'the revids= point to have not been resolved.' );
}
$this->mResolveRedirects = false;
$this->initFromRevIDs( $this->mParams['revids'] );
@@ -244,6 +267,7 @@ class ApiPageSet extends ApiBase {
if ( isset( $this->mParams['revids'] ) ) {
return 'revids';
}
+
return null;
}
@@ -270,7 +294,7 @@ class ApiPageSet extends ApiBase {
* Get the fields that have to be queried from the page table:
* the ones requested through requestField() and a few basic ones
* we always need
- * @return array of field names
+ * @return array Array of field names
*/
public function getPageTableFields() {
// Ensure we get minimum required fields
@@ -289,6 +313,7 @@ class ApiPageSet extends ApiBase {
$this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds );
$pageFlds = array_merge( $pageFlds, $this->mRequestedPageFields );
+
return array_keys( $pageFlds );
}
@@ -304,7 +329,7 @@ class ApiPageSet extends ApiBase {
/**
* All Title objects provided.
- * @return array of Title objects
+ * @return Title[]
*/
public function getTitles() {
return $this->mTitles;
@@ -320,7 +345,7 @@ class ApiPageSet extends ApiBase {
/**
* Title objects that were found in the database.
- * @return array page_id (int) => Title (obj)
+ * @return Title[] Array page_id (int) => Title (obj)
*/
public function getGoodTitles() {
return $this->mGoodTitles;
@@ -337,7 +362,7 @@ class ApiPageSet extends ApiBase {
/**
* Title objects that were NOT found in the database.
* The array's index will be negative for each item
- * @return array of Title objects
+ * @return Title[]
*/
public function getMissingTitles() {
return $this->mMissingTitles;
@@ -346,7 +371,7 @@ class ApiPageSet extends ApiBase {
/**
* Titles that were deemed invalid by Title::newFromText()
* The array's index will be unique and negative for each item
- * @return array of strings (not Title objects)
+ * @return string[] Array of strings (not Title objects)
*/
public function getInvalidTitles() {
return $this->mInvalidTitles;
@@ -354,7 +379,7 @@ class ApiPageSet extends ApiBase {
/**
* Page IDs that were not found in the database
- * @return array of page IDs
+ * @return array Array of page IDs
*/
public function getMissingPageIDs() {
return $this->mMissingPageIDs;
@@ -363,7 +388,7 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of redirect resolutions - maps a title to its redirect
* target, as an array of output-ready arrays
- * @return array
+ * @return Title[]
*/
public function getRedirectTitles() {
return $this->mRedirectTitles;
@@ -372,8 +397,8 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of redirect resolutions - maps a title to its redirect
* target.
- * @param $result ApiResult
- * @return array of prefixed_title (string) => Title object
+ * @param ApiResult $result
+ * @return array Array of prefixed_title (string) => Title object
* @since 1.21
*/
public function getRedirectTitlesAsResult( $result = null ) {
@@ -383,7 +408,7 @@ class ApiPageSet extends ApiBase {
'from' => strval( $titleStrFrom ),
'to' => $titleTo->getPrefixedText(),
);
- if ( $titleTo->getFragment() !== '' ) {
+ if ( $titleTo->hasFragment() ) {
$r['tofragment'] = $titleTo->getFragment();
}
$values[] = $r;
@@ -391,13 +416,14 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'r' );
}
+
return $values;
}
/**
* Get a list of title normalizations - maps a title to its normalized
* version.
- * @return array raw_prefixed_title (string) => prefixed_title (string)
+ * @return array Array of raw_prefixed_title (string) => prefixed_title (string)
*/
public function getNormalizedTitles() {
return $this->mNormalizedTitles;
@@ -406,8 +432,8 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of title normalizations - maps a title to its normalized
* version in the form of result array.
- * @param $result ApiResult
- * @return array of raw_prefixed_title (string) => prefixed_title (string)
+ * @param ApiResult $result
+ * @return array Array of raw_prefixed_title (string) => prefixed_title (string)
* @since 1.21
*/
public function getNormalizedTitlesAsResult( $result = null ) {
@@ -421,13 +447,14 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'n' );
}
+
return $values;
}
/**
* Get a list of title conversions - maps a title to its converted
* version.
- * @return array raw_prefixed_title (string) => prefixed_title (string)
+ * @return array Array of raw_prefixed_title (string) => prefixed_title (string)
*/
public function getConvertedTitles() {
return $this->mConvertedTitles;
@@ -436,8 +463,8 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of title conversions - maps a title to its converted
* version as a result array.
- * @param $result ApiResult
- * @return array of (from, to) strings
+ * @param ApiResult $result
+ * @return array Array of (from, to) strings
* @since 1.21
*/
public function getConvertedTitlesAsResult( $result = null ) {
@@ -451,13 +478,14 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'c' );
}
+
return $values;
}
/**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix.
- * @return array raw_prefixed_title (string) => interwiki_prefix (string)
+ * @return array Array of raw_prefixed_title (string) => interwiki_prefix (string)
*/
public function getInterwikiTitles() {
return $this->mInterwikiTitles;
@@ -466,9 +494,9 @@ class ApiPageSet extends ApiBase {
/**
* Get a list of interwiki titles - maps a title to its interwiki
* prefix as result.
- * @param $result ApiResult
- * @param $iwUrl boolean
- * @return array raw_prefixed_title (string) => interwiki_prefix (string)
+ * @param ApiResult $result
+ * @param bool $iwUrl
+ * @return array Array of raw_prefixed_title (string) => interwiki_prefix (string)
* @since 1.21
*/
public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
@@ -487,12 +515,53 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'i' );
}
+
return $values;
}
/**
+ * Get an array of invalid/special/missing titles.
+ *
+ * @param array $invalidChecks List of types of invalid titles to include.
+ * Recognized values are:
+ * - invalidTitles: Titles from $this->getInvalidTitles()
+ * - special: Titles from $this->getSpecialTitles()
+ * - missingIds: ids from $this->getMissingPageIDs()
+ * - missingRevIds: ids from $this->getMissingRevisionIDs()
+ * - missingTitles: Titles from $this->getMissingTitles()
+ * - interwikiTitles: Titles from $this->getInterwikiTitlesAsResult()
+ * @return array Array suitable for inclusion in the response
+ * @since 1.23
+ */
+ public function getInvalidTitlesAndRevisions( $invalidChecks = array( 'invalidTitles',
+ 'special', 'missingIds', 'missingRevIds', 'missingTitles', 'interwikiTitles' )
+ ) {
+ $result = array();
+ if ( in_array( "invalidTitles", $invalidChecks ) ) {
+ self::addValues( $result, $this->getInvalidTitles(), 'invalid', 'title' );
+ }
+ if ( in_array( "special", $invalidChecks ) ) {
+ self::addValues( $result, $this->getSpecialTitles(), 'special', 'title' );
+ }
+ if ( in_array( "missingIds", $invalidChecks ) ) {
+ self::addValues( $result, $this->getMissingPageIDs(), 'missing', 'pageid' );
+ }
+ if ( in_array( "missingRevIds", $invalidChecks ) ) {
+ self::addValues( $result, $this->getMissingRevisionIDs(), 'missing', 'revid' );
+ }
+ if ( in_array( "missingTitles", $invalidChecks ) ) {
+ self::addValues( $result, $this->getMissingTitles(), 'missing' );
+ }
+ if ( in_array( "interwikiTitles", $invalidChecks ) ) {
+ self::addValues( $result, $this->getInterwikiTitlesAsResult() );
+ }
+
+ return $result;
+ }
+
+ /**
* Get the list of revision IDs (requested with the revids= parameter)
- * @return array revID (int) => pageID (int)
+ * @return array Array of revID (int) => pageID (int)
*/
public function getRevisionIDs() {
return $this->mGoodRevIDs;
@@ -500,7 +569,7 @@ class ApiPageSet extends ApiBase {
/**
* Revision IDs that were not found in the database
- * @return array of revision IDs
+ * @return array Array of revision IDs
*/
public function getMissingRevisionIDs() {
return $this->mMissingRevIDs;
@@ -508,8 +577,8 @@ class ApiPageSet extends ApiBase {
/**
* Revision IDs that were not found in the database as result array.
- * @param $result ApiResult
- * @return array of revision IDs
+ * @param ApiResult $result
+ * @return array Array of revision IDs
* @since 1.21
*/
public function getMissingRevisionIDsAsResult( $result = null ) {
@@ -522,12 +591,13 @@ class ApiPageSet extends ApiBase {
if ( !empty( $values ) && $result ) {
$result->setIndexedTagName( $values, 'rev' );
}
+
return $values;
}
/**
* Get the list of titles with negative namespace
- * @return array Title
+ * @return Title[]
*/
public function getSpecialTitles() {
return $this->mSpecialTitles;
@@ -543,7 +613,7 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a list of Titles
- * @param array $titles of Title objects
+ * @param array $titles Array of Title objects
*/
public function populateFromTitles( $titles ) {
$this->profileIn();
@@ -553,7 +623,7 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a list of page IDs
- * @param array $pageIDs of page IDs
+ * @param array $pageIDs Array of page IDs
*/
public function populateFromPageIDs( $pageIDs ) {
$this->profileIn();
@@ -563,8 +633,8 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a rowset returned from the database
- * @param $db DatabaseBase object
- * @param $queryResult ResultWrapper Query result object
+ * @param DatabaseBase $db
+ * @param ResultWrapper $queryResult Query result object
*/
public function populateFromQueryResult( $db, $queryResult ) {
$this->profileIn();
@@ -574,7 +644,7 @@ class ApiPageSet extends ApiBase {
/**
* Populate this PageSet from a list of revision IDs
- * @param array $revIDs of revision IDs
+ * @param array $revIDs Array of revision IDs
*/
public function populateFromRevisionIDs( $revIDs ) {
$this->profileIn();
@@ -584,7 +654,7 @@ class ApiPageSet extends ApiBase {
/**
* Extract all requested fields from the row received from the database
- * @param $row Result row
+ * @param stdClass $row Result row
*/
public function processDbRow( $row ) {
// Store Title object in various data structures
@@ -627,7 +697,7 @@ class ApiPageSet extends ApiBase {
* #5 Substitute the original LinkBatch object with the new list
* #6 Repeat from step #1
*
- * @param array $titles of Title objects or strings
+ * @param array $titles Array of Title objects or strings
*/
private function initFromTitles( $titles ) {
// Get validated and normalized title objects
@@ -642,7 +712,7 @@ class ApiPageSet extends ApiBase {
// Get pageIDs data from the `page` table
$this->profileDBIn();
$res = $db->select( 'page', $this->getPageTableFields(), $set,
- __METHOD__ );
+ __METHOD__ );
$this->profileDBOut();
// Hack: get the ns:titles stored in array(ns => array(titles)) format
@@ -654,7 +724,7 @@ class ApiPageSet extends ApiBase {
/**
* Does the same as initFromTitles(), but is based on page IDs instead
- * @param array $pageids of page IDs
+ * @param array $pageids Array of page IDs
*/
private function initFromPageIds( $pageids ) {
if ( !$pageids ) {
@@ -676,7 +746,7 @@ class ApiPageSet extends ApiBase {
// Get pageIDs data from the `page` table
$this->profileDBIn();
$res = $db->select( 'page', $this->getPageTableFields(), $set,
- __METHOD__ );
+ __METHOD__ );
$this->profileDBOut();
}
@@ -689,8 +759,8 @@ class ApiPageSet extends ApiBase {
/**
* Iterate through the result of the query on 'page' table,
* and for each row create and store title object and save any extra fields requested.
- * @param $res ResultWrapper DB Query result
- * @param array $remaining of either pageID or ns/title elements (optional).
+ * @param ResultWrapper $res DB Query result
+ * @param array $remaining Array of either pageID or ns/title elements (optional).
* If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
* @param bool $processTitles Must be provided together with $remaining.
* If true, treat $remaining as an array of [ns][title]
@@ -761,7 +831,7 @@ class ApiPageSet extends ApiBase {
/**
* Does the same as initFromTitles(), but is based on revision IDs
* instead
- * @param array $revids of revision IDs
+ * @param array $revids Array of revision IDs
*/
private function initFromRevIDs( $revids ) {
if ( !$revids ) {
@@ -863,7 +933,12 @@ class ApiPageSet extends ApiBase {
foreach ( $res as $row ) {
$rdfrom = intval( $row->rd_from );
$from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
- $to = Title::makeTitle( $row->rd_namespace, $row->rd_title, $row->rd_fragment, $row->rd_interwiki );
+ $to = Title::makeTitle(
+ $row->rd_namespace,
+ $row->rd_title,
+ $row->rd_fragment,
+ $row->rd_interwiki
+ );
unset( $this->mPendingRedirectIDs[$rdfrom] );
if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
$lb->add( $row->rd_namespace, $row->rd_title );
@@ -886,6 +961,7 @@ class ApiPageSet extends ApiBase {
unset( $this->mPendingRedirectIDs[$id] );
}
}
+
return $lb;
}
@@ -898,7 +974,7 @@ class ApiPageSet 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|null $params
* @return string
* @since 1.21
*/
@@ -912,7 +988,7 @@ class ApiPageSet extends ApiBase {
* This method validates access rights for the title,
* and appends normalization values to the output.
*
- * @param array $titles of Title objects or strings
+ * @param array $titles Array of Title objects or strings
* @return LinkBatch
*/
private function processTitlesArray( $titles ) {
@@ -941,8 +1017,9 @@ class ApiPageSet extends ApiBase {
// Variants checking
global $wgContLang;
if ( $this->mConvertTitles &&
- count( $wgContLang->getVariants() ) > 1 &&
- !$titleObj->exists() ) {
+ count( $wgContLang->getVariants() ) > 1 &&
+ !$titleObj->exists()
+ ) {
// Language::findVariantLink will modify titleText and titleObj into
// the canonical variant if possible
$titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
@@ -999,7 +1076,7 @@ class ApiPageSet extends ApiBase {
/**
* Returns the input array of integers with all values < 0 removed
*
- * @param $array array
+ * @param array $array
* @return array
*/
private static function getPositiveIntegers( $array ) {
@@ -1040,6 +1117,7 @@ class ApiPageSet extends ApiBase {
$result['generator'] = null;
}
}
+
return $result;
}
@@ -1067,6 +1145,7 @@ class ApiPageSet extends ApiBase {
sort( $gens );
self::$generators = $gens;
}
+
return self::$generators;
}
@@ -1075,19 +1154,17 @@ class ApiPageSet extends ApiBase {
'titles' => 'A list of titles to work on',
'pageids' => 'A list of page IDs to work on',
'revids' => 'A list of revision IDs to work on',
- 'generator' => array( 'Get the list of pages to work on by executing the specified query module.',
- 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ),
+ 'generator' => array(
+ 'Get the list of pages to work on by executing the specified query module.',
+ 'NOTE: generator parameter names must be prefixed with a \'g\', see examples'
+ ),
'redirects' => 'Automatically resolve redirects',
- 'converttitles' => array( 'Convert titles to other variants if necessary. Only works if the wiki\'s content language supports variant conversion.',
- 'Languages that support variant conversion include ' . implode( ', ', LanguageConverter::$languagesWithVariants ) ),
+ 'converttitles' => array(
+ 'Convert titles to other variants if necessary. Only works if ' .
+ 'the wiki\'s content language supports variant conversion.',
+ 'Languages that support variant conversion include ' .
+ implode( ', ', LanguageConverter::$languagesWithVariants )
+ ),
);
}
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'multisource', 'info' => "Cannot use 'pageids' at the same time as 'dataSource'" ),
- array( 'code' => 'multisource', 'info' => "Cannot use 'revids' at the same time as 'dataSource'" ),
- array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
- ) );
- }
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 3e1a7531..067b2f59 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -34,7 +34,7 @@ class ApiParamInfo extends ApiBase {
*/
protected $queryObj;
- public function __construct( $main, $action ) {
+ public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action );
$this->queryObj = new ApiQuery( $this->getMain(), 'query' );
}
@@ -66,10 +66,10 @@ class ApiParamInfo extends ApiBase {
/**
* If the type is requested in parameters, adds a section to res with module info.
- * @param array $params user parameters array
- * @param string $type parameter name
- * @param array $res store results in this array
- * @param ApiResult $resultObj results object to set indexed tag.
+ * @param array $params User parameters array
+ * @param string $type Parameter name
+ * @param array $res Store results in this array
+ * @param ApiResult $resultObj Results object to set indexed tag.
*/
private function addModulesInfo( $params, $type, &$res, $resultObj ) {
if ( !is_array( $params[$type] ) ) {
@@ -99,7 +99,7 @@ class ApiParamInfo extends ApiBase {
}
/**
- * @param $obj ApiBase
+ * @param ApiBase $obj
* @return ApiResult
*/
private function getClassInfo( $obj ) {
@@ -198,6 +198,10 @@ class ApiParamInfo extends ApiBase {
$a['required'] = '';
}
+ if ( $n === 'token' && $obj->needsToken() ) {
+ $a['tokentype'] = $obj->needsToken();
+ }
+
if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
$type = $p[ApiBase::PARAM_TYPE];
if ( $type === 'boolean' ) {
@@ -224,9 +228,16 @@ class ApiParamInfo extends ApiBase {
}
if ( isset( $p[ApiBase::PARAM_TYPE] ) ) {
- $a['type'] = $p[ApiBase::PARAM_TYPE];
+ if ( $p[ApiBase::PARAM_TYPE] === 'submodule' ) {
+ $a['type'] = $obj->getModuleManager()->getNames( $n );
+ sort( $a['type'] );
+ $a['submodules'] = '';
+ } else {
+ $a['type'] = $p[ApiBase::PARAM_TYPE];
+ }
if ( is_array( $a['type'] ) ) {
- $a['type'] = array_values( $a['type'] ); // to prevent sparse arrays from being serialized to JSON as objects
+ // To prevent sparse arrays from being serialized to JSON as objects
+ $a['type'] = array_values( $a['type'] );
$result->setIndexedTagName( $a['type'], 't' );
}
}
@@ -243,66 +254,6 @@ class ApiParamInfo extends ApiBase {
}
$result->setIndexedTagName( $retval['parameters'], 'param' );
- $props = $obj->getFinalResultProperties();
- $listResult = null;
- if ( $props !== false ) {
- $retval['props'] = array();
-
- foreach ( $props as $prop => $properties ) {
- $propResult = array();
- if ( $prop == ApiBase::PROP_LIST ) {
- $listResult = $properties;
- continue;
- }
- if ( $prop != ApiBase::PROP_ROOT ) {
- $propResult['name'] = $prop;
- }
- $propResult['properties'] = array();
-
- foreach ( $properties as $name => $p ) {
- $propertyResult = array();
-
- $propertyResult['name'] = $name;
-
- if ( !is_array( $p ) ) {
- $p = array( ApiBase::PROP_TYPE => $p );
- }
-
- $propertyResult['type'] = $p[ApiBase::PROP_TYPE];
-
- if ( is_array( $propertyResult['type'] ) ) {
- $propertyResult['type'] = array_values( $propertyResult['type'] );
- $result->setIndexedTagName( $propertyResult['type'], 't' );
- }
-
- $nullable = null;
- if ( isset( $p[ApiBase::PROP_NULLABLE] ) ) {
- $nullable = $p[ApiBase::PROP_NULLABLE];
- }
-
- if ( $nullable === true ) {
- $propertyResult['nullable'] = '';
- }
-
- $propResult['properties'][] = $propertyResult;
- }
-
- $result->setIndexedTagName( $propResult['properties'], 'property' );
- $retval['props'][] = $propResult;
- }
-
- // default is true for query modules, false for other modules, overridden by ApiBase::PROP_LIST
- if ( $listResult === true || ( $listResult !== false && $obj instanceof ApiQueryBase ) ) {
- $retval['listresult'] = '';
- }
-
- $result->setIndexedTagName( $retval['props'], 'prop' );
- }
-
- // Errors
- $retval['errors'] = $this->parseErrors( $obj->getFinalPossibleErrors() );
- $result->setIndexedTagName( $retval['errors'], 'error' );
-
return $retval;
}
@@ -317,6 +268,7 @@ class ApiParamInfo extends ApiBase {
sort( $querymodules );
$formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
sort( $formatmodules );
+
return array(
'modules' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -340,13 +292,14 @@ class ApiParamInfo extends ApiBase {
'modules' => 'List of module names (value of the action= parameter)',
'querymodules' => 'List of query module names (value of prop=, meta= or list= parameter)',
'mainmodule' => 'Get information about the main (top-level) module as well',
- 'pagesetmodule' => 'Get information about the pageset module (providing titles= and friends) as well',
+ 'pagesetmodule' => 'Get information about the pageset module ' .
+ '(providing titles= and friends) as well',
'formatmodules' => 'List of format module names (value of format= parameter)',
);
}
public function getDescription() {
- return 'Obtain information about certain API parameters and errors';
+ return 'Obtain information about certain API parameters and errors.';
}
public function getExamples() {
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index a369994b..06fdf85b 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -27,7 +27,7 @@
*/
class ApiParse extends ApiBase {
- /** @var String $section */
+ /** @var string $section */
private $section = null;
/** @var Content $content */
@@ -60,7 +60,10 @@ class ApiParse extends ApiBase {
$format = $params['contentformat'];
if ( !is_null( $page ) && ( !is_null( $text ) || $titleProvided ) ) {
- $this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
+ $this->dieUsage(
+ 'The page parameter cannot be used together with the text and title parameters',
+ 'params'
+ );
}
$prop = array_flip( $params['prop'] );
@@ -76,9 +79,12 @@ class ApiParse extends ApiBase {
// TODO: Does this still need $wgTitle?
global $wgParser, $wgTitle;
- // Currently unnecessary, code to act as a safeguard against any change in current behavior of uselang
+ // Currently unnecessary, code to act as a safeguard against any change
+ // in current behavior of uselang
$oldLang = null;
- if ( isset( $params['uselang'] ) && $params['uselang'] != $this->getContext()->getLanguage()->getCode() ) {
+ if ( isset( $params['uselang'] )
+ && $params['uselang'] != $this->getContext()->getLanguage()->getCode()
+ ) {
$oldLang = $this->getContext()->getLanguage(); // Backup language
$this->getContext()->setLanguage( Language::factory( $params['uselang'] ) );
}
@@ -125,7 +131,7 @@ class ApiParse extends ApiBase {
'action' => 'query',
'redirects' => '',
);
- if ( !is_null ( $pageid ) ) {
+ if ( !is_null( $pageid ) ) {
$reqParams['pageids'] = $pageid;
} else { // $page
$reqParams['titles'] = $page;
@@ -170,15 +176,19 @@ class ApiParse extends ApiBase {
if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
- if ( !$titleObj->canExist() ) {
- $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
- }
$wgTitle = $titleObj;
- $pageObj = WikiPage::factory( $titleObj );
+ if ( $titleObj->canExist() ) {
+ $pageObj = WikiPage::factory( $titleObj );
+ } else {
+ // Do like MediaWiki::initializeArticle()
+ $article = Article::newFromTitle( $titleObj, $this->getContext() );
+ $pageObj = $article->getPage();
+ }
$popts = $this->makeParserOptions( $pageObj, $params );
+ $textProvided = !is_null( $text );
- if ( is_null( $text ) ) {
+ if ( !$textProvided ) {
if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
$this->setWarning(
"'title' used without 'text', and parsed page properties were requested " .
@@ -191,7 +201,7 @@ class ApiParse extends ApiBase {
// If we are parsing text, do not use the content model of the default
// API title, but default to wikitext to keep BC.
- if ( !$titleProvided && is_null( $model ) ) {
+ if ( $textProvided && !$titleProvided && is_null( $model ) ) {
$model = CONTENT_MODEL_WIKITEXT;
$this->setWarning( "No 'title' or 'contentmodel' was given, assuming $model." );
}
@@ -203,7 +213,7 @@ class ApiParse extends ApiBase {
}
if ( $this->section !== false ) {
- $this->content = $this->getSectionContent( $this->content, $titleObj->getText() );
+ $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
}
if ( $params['pst'] || $params['onlypst'] ) {
@@ -219,6 +229,7 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
}
$result->addValue( null, $this->getModuleName(), $result_array );
+
return;
}
@@ -242,6 +253,10 @@ class ApiParse extends ApiBase {
$result_array['redirects'] = $redirValues;
}
+ if ( $params['disabletoc'] ) {
+ $p_result->setTOCEnabled( false );
+ }
+
if ( isset( $prop['text'] ) ) {
$result_array['text'] = array();
ApiResult::setContent( $result_array['text'], $p_result->getText() );
@@ -249,10 +264,13 @@ class ApiParse extends ApiBase {
if ( !is_null( $params['summary'] ) ) {
$result_array['parsedsummary'] = array();
- ApiResult::setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) );
+ ApiResult::setContent(
+ $result_array['parsedsummary'],
+ Linker::formatComment( $params['summary'], $titleObj )
+ );
}
- if ( isset( $prop['langlinks'] ) || isset( $prop['languageshtml'] ) ) {
+ if ( isset( $prop['langlinks'] ) ) {
$langlinks = $p_result->getLanguageLinks();
if ( $params['effectivelanglinks'] ) {
@@ -268,12 +286,6 @@ class ApiParse extends ApiBase {
if ( isset( $prop['langlinks'] ) ) {
$result_array['langlinks'] = $this->formatLangLinks( $langlinks );
}
- if ( isset( $prop['languageshtml'] ) ) {
- $languagesHtml = $this->languagesHtml( $langlinks );
-
- $result_array['languageshtml'] = array();
- ApiResult::setContent( $result_array['languageshtml'], $languagesHtml );
- }
if ( isset( $prop['categories'] ) ) {
$result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
}
@@ -300,14 +312,14 @@ class ApiParse extends ApiBase {
if ( isset( $prop['displaytitle'] ) ) {
$result_array['displaytitle'] = $p_result->getDisplayTitle() ?
- $p_result->getDisplayTitle() :
- $titleObj->getPrefixedText();
+ $p_result->getDisplayTitle() :
+ $titleObj->getPrefixedText();
}
if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
$context = $this->getContext();
$context->setTitle( $titleObj );
- $context->getOutput()->addParserOutputNoText( $p_result );
+ $context->getOutput()->addParserOutputMetadata( $p_result );
if ( isset( $prop['headitems'] ) ) {
$headItems = $this->formatHeadItems( $p_result->getHeadItems() );
@@ -321,10 +333,20 @@ class ApiParse extends ApiBase {
if ( isset( $prop['headhtml'] ) ) {
$result_array['headhtml'] = array();
- ApiResult::setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) );
+ ApiResult::setContent(
+ $result_array['headhtml'],
+ $context->getOutput()->headElement( $context->getSkin() )
+ );
}
}
+ if ( isset( $prop['modules'] ) ) {
+ $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
+ $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
+ $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
+ $result_array['modulemessages'] = array_values( array_unique( $p_result->getModuleMessages() ) );
+ }
+
if ( isset( $prop['iwlinks'] ) ) {
$result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
}
@@ -341,6 +363,16 @@ class ApiParse extends ApiBase {
$result_array['properties'] = $this->formatProperties( $p_result->getProperties() );
}
+ if ( isset( $prop['limitreportdata'] ) ) {
+ $result_array['limitreportdata'] =
+ $this->formatLimitReportData( $p_result->getLimitReportData() );
+ }
+ if ( isset( $prop['limitreporthtml'] ) ) {
+ $limitreportHtml = EditPage::getPreviewLimitReport( $p_result );
+ $result_array['limitreporthtml'] = array();
+ ApiResult::setContent( $result_array['limitreporthtml'], $limitreportHtml );
+ }
+
if ( $params['generatexml'] ) {
if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
$this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
@@ -368,7 +400,12 @@ class ApiParse extends ApiBase {
'iwlinks' => 'iw',
'sections' => 's',
'headitems' => 'hi',
+ 'modules' => 'm',
+ 'modulescripts' => 'm',
+ 'modulestyles' => 'm',
+ 'modulemessages' => 'm',
'properties' => 'pp',
+ 'limitreportdata' => 'lr',
);
$this->setIndexedTagNames( $result_array, $result_mapping );
$result->addValue( null, $this->getModuleName(), $result_array );
@@ -393,16 +430,18 @@ class ApiParse extends ApiBase {
$popts->enableLimitReport( !$params['disablepp'] );
$popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
$popts->setIsSectionPreview( $params['sectionpreview'] );
+ $popts->setEditSection( !$params['disableeditsection'] );
wfProfileOut( __METHOD__ );
+
return $popts;
}
/**
- * @param $page WikiPage
- * @param $popts ParserOptions
- * @param $pageId Int
- * @param $getWikitext Bool
+ * @param WikiPage $page
+ * @param ParserOptions $popts
+ * @param int $pageId
+ * @param bool $getWikitext
* @return ParserOutput
*/
private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
@@ -411,24 +450,31 @@ class ApiParse extends ApiBase {
if ( $this->section !== false && $this->content !== null ) {
$this->content = $this->getSectionContent(
$this->content,
- !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getText() );
+ !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText()
+ );
// Not cached (save or load)
return $this->content->getParserOutput( $page->getTitle(), null, $popts );
- } else {
- // Try the parser cache first
- // getParserOutput will save to Parser cache if able
- $pout = $page->getParserOutput( $popts );
- if ( !$pout ) {
- $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
- }
- if ( $getWikitext ) {
- $this->content = $page->getContent( Revision::RAW );
- }
- return $pout;
}
+
+ // Try the parser cache first
+ // getParserOutput will save to Parser cache if able
+ $pout = $page->getParserOutput( $popts );
+ if ( !$pout ) {
+ $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
+ }
+ if ( $getWikitext ) {
+ $this->content = $page->getContent( Revision::RAW );
+ }
+
+ return $pout;
}
+ /**
+ * @param Content $content
+ * @param string $what Identifies the content in error messages, e.g. page title.
+ * @return Content|bool
+ */
private function getSectionContent( Content $content, $what ) {
// Not cached (save or load)
$section = $content->getSection( $this->section );
@@ -439,6 +485,7 @@ class ApiParse extends ApiBase {
$this->dieUsage( "Sections are not supported by " . $what, 'nosuchsection' );
$section = false;
}
+
return $section;
}
@@ -452,64 +499,67 @@ class ApiParse extends ApiBase {
$entry['lang'] = $bits[0];
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
+ // localised language name in user language (maybe set by uselang=)
+ $entry['langname'] = Language::fetchLanguageName(
+ $title->getInterwiki(),
+ $this->getLanguage()->getCode()
+ );
+
+ // native language name
+ $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
}
ApiResult::setContent( $entry, $bits[1] );
$result[] = $entry;
}
+
return $result;
}
private function formatCategoryLinks( $links ) {
$result = array();
+
+ if ( !$links ) {
+ return $result;
+ }
+
+ // Fetch hiddencat property
+ $lb = new LinkBatch;
+ $lb->setArray( array( NS_CATEGORY => $links ) );
+ $db = $this->getDB();
+ $res = $db->select( array( 'page', 'page_props' ),
+ array( 'page_title', 'pp_propname' ),
+ $lb->constructSet( 'page', $db ),
+ __METHOD__,
+ array(),
+ array( 'page_props' => array(
+ 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' )
+ ) )
+ );
+ $hiddencats = array();
+ foreach ( $res as $row ) {
+ $hiddencats[$row->page_title] = isset( $row->pp_propname );
+ }
+
foreach ( $links as $link => $sortkey ) {
$entry = array();
$entry['sortkey'] = $sortkey;
ApiResult::setContent( $entry, $link );
+ if ( !isset( $hiddencats[$link] ) ) {
+ $entry['missing'] = '';
+ } elseif ( $hiddencats[$link] ) {
+ $entry['hidden'] = '';
+ }
$result[] = $entry;
}
+
return $result;
}
private function categoriesHtml( $categories ) {
$context = $this->getContext();
$context->getOutput()->addCategoryLinks( $categories );
- return $context->getSkin()->getCategories();
- }
- /**
- * @deprecated since 1.18 No modern skin generates language links this way, please use language links
- * data to generate your own HTML.
- * @param $languages array
- * @return string
- */
- private function languagesHtml( $languages ) {
- wfDeprecated( __METHOD__, '1.18' );
-
- global $wgContLang, $wgHideInterlanguageLinks;
-
- if ( $wgHideInterlanguageLinks || count( $languages ) == 0 ) {
- return '';
- }
-
- $s = htmlspecialchars( wfMessage( 'otherlanguages' )->text() . wfMessage( 'colon-separator' )->text() );
-
- $langs = array();
- foreach ( $languages as $l ) {
- $nt = Title::newFromText( $l );
- $text = Language::fetchLanguageName( $nt->getInterwiki() );
-
- $langs[] = Html::element( 'a',
- array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => 'external' ),
- $text == '' ? $l : $text );
- }
-
- $s .= implode( wfMessage( 'pipe-separator' )->escaped(), $langs );
-
- if ( $wgContLang->isRTL() ) {
- $s = Html::rawElement( 'span', array( 'dir' => 'LTR' ), $s );
- }
-
- return $s;
+ return $context->getSkin()->getCategories();
}
private function formatLinks( $links ) {
@@ -525,6 +575,7 @@ class ApiParse extends ApiBase {
$result[] = $entry;
}
}
+
return $result;
}
@@ -544,6 +595,7 @@ class ApiParse extends ApiBase {
$result[] = $entry;
}
}
+
return $result;
}
@@ -555,6 +607,7 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $entry, $content );
$result[] = $entry;
}
+
return $result;
}
@@ -566,6 +619,7 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $entry, $value );
$result[] = $entry;
}
+
return $result;
}
@@ -577,6 +631,26 @@ class ApiParse extends ApiBase {
ApiResult::setContent( $entry, $link );
$result[] = $entry;
}
+
+ return $result;
+ }
+
+ private function formatLimitReportData( $limitReportData ) {
+ $result = array();
+ $apiResult = $this->getResult();
+
+ foreach ( $limitReportData as $name => $value ) {
+ $entry = array();
+ $entry['name'] = $name;
+ if ( !is_array( $value ) ) {
+ $value = array( $value );
+ }
+ $apiResult->setIndexedTagName( $value, 'param' );
+ $apiResult->setIndexedTagName_recursive( $value, 'param' );
+ $entry = array_merge( $entry, $value );
+ $result[] = $entry;
+ }
+
return $result;
}
@@ -602,12 +676,12 @@ class ApiParse extends ApiBase {
ApiBase::PARAM_TYPE => 'integer',
),
'prop' => array(
- ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle|iwlinks|properties',
+ ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
+ 'images|externallinks|sections|revid|displaytitle|iwlinks|properties',
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
'text',
'langlinks',
- 'languageshtml',
'categories',
'categorieshtml',
'links',
@@ -619,9 +693,12 @@ class ApiParse extends ApiBase {
'displaytitle',
'headitems',
'headhtml',
+ 'modules',
'iwlinks',
'wikitext',
'properties',
+ 'limitreportdata',
+ 'limitreporthtml',
)
),
'pst' => false,
@@ -630,9 +707,11 @@ class ApiParse extends ApiBase {
'uselang' => null,
'section' => null,
'disablepp' => false,
+ 'disableeditsection' => false,
'generatexml' => false,
'preview' => false,
'sectionpreview' => false,
+ 'disabletoc' => false,
'contentformat' => array(
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
),
@@ -645,12 +724,13 @@ class ApiParse extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
$wikitext = CONTENT_MODEL_WIKITEXT;
+
return array(
'text' => "Text to parse. Use {$p}title or {$p}contentmodel to control the content model",
'summary' => 'Summary to parse',
'redirects' => "If the {$p}page or the {$p}pageid parameter is set to a redirect, resolve it",
'title' => "Title of page the text belongs to. " .
- "If omitted, \"API\" is used as the title with content model $wikitext",
+ "If omitted, {$p}contentmodel must be specified, and \"API\" will be used as the title",
'page' => "Parse the content of this page. Cannot be used together with {$p}text and {$p}title",
'pageid' => "Parse the content of this page. Overrides {$p}page",
'oldid' => "Parse the content of this revision. Overrides {$p}page and {$p}pageid",
@@ -660,7 +740,6 @@ class ApiParse extends ApiBase {
' langlinks - Gives the language links in the parsed wikitext',
' categories - Gives the categories in the parsed wikitext',
' categorieshtml - Gives the HTML version of the categories',
- ' languageshtml - Gives the HTML version of the language links',
' links - Gives the internal links in the parsed wikitext',
' templates - Gives the templates in the parsed wikitext',
' images - Gives the images in the parsed wikitext',
@@ -670,13 +749,18 @@ class ApiParse extends ApiBase {
' displaytitle - Adds the title of the parsed wikitext',
' headitems - Gives items to put in the <head> of the page',
' headhtml - Gives parsed <head> of the page',
+ ' modules - Gives the ResourceLoader modules used on the page',
' iwlinks - Gives interwiki links in the parsed wikitext',
' wikitext - Gives the original wikitext that was parsed',
' properties - Gives various properties defined in the parsed wikitext',
+ ' limitreportdata - Gives the limit report in a structured way.',
+ " Gives no data, when {$p}disablepp is set.",
+ ' limitreporthtml - Gives the HTML version of the limit report.',
+ " Gives no data, when {$p}disablepp is set.",
),
'effectivelanglinks' => array(
'Includes language links supplied by extensions',
- '(for use with prop=langlinks|languageshtml)',
+ '(for use with prop=langlinks)',
),
'pst' => array(
'Do a pre-save transform on the input before parsing it',
@@ -690,16 +774,18 @@ class ApiParse extends ApiBase {
'uselang' => 'Which language to parse the request in',
'section' => 'Only retrieve the content of this section number',
'disablepp' => 'Disable the PP Report from the parser output',
+ 'disableeditsection' => 'Disable edit section links from the parser output',
'generatexml' => "Generate XML parse tree (requires contentmodel=$wikitext)",
'preview' => 'Parse in preview mode',
'sectionpreview' => 'Parse in section preview mode (enables preview mode too)',
+ 'disabletoc' => 'Disable table of contents in output',
'contentformat' => array(
'Content serialization format used for the input text',
"Only valid when used with {$p}text",
),
'contentmodel' => array(
- "Content model of the input text. Default is the model of the " .
- "specified ${p}title, or $wikitext if ${p}title is not specified",
+ "Content model of the input text. If omitted, ${p}title must be specified, " .
+ "and default will be the model of the specified ${p}title",
"Only valid when used with {$p}text",
),
);
@@ -707,9 +793,11 @@ class ApiParse extends ApiBase {
public function getDescription() {
$p = $this->getModulePrefix();
+
return array(
- 'Parses content and returns parser output',
- 'See the various prop-Modules of action=query to get information from the current version of a page',
+ 'Parses content and returns parser output.',
+ 'See the various prop-Modules of action=query to get information from the current' .
+ 'version of a page.',
'There are several ways to specify the text to parse:',
"1) Specify a page or revision, using {$p}page, {$p}pageid, or {$p}oldid.",
"2) Specify content explicitly, using {$p}text, {$p}title, and {$p}contentmodel.",
@@ -717,26 +805,12 @@ class ApiParse extends ApiBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ),
- array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ),
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ),
- array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
- array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
- array( 'nosuchpageid' ),
- array( 'invalidtitle', 'title' ),
- array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
- array( 'code' => 'notwikitext', 'info' => 'The requested operation is only supported on wikitext content.' ),
- array( 'code' => 'pagecannotexist', 'info' => "Namespace doesn't allow actual pages" ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=parse&page=Project:Sandbox' => 'Parse a page',
- 'api.php?action=parse&text={{Project:Sandbox}}' => 'Parse wikitext',
- 'api.php?action=parse&text={{PAGENAME}}&title=Test' => 'Parse wikitext, specifying the page title',
+ 'api.php?action=parse&text={{Project:Sandbox}}&contentmodel=wikitext' => 'Parse wikitext',
+ 'api.php?action=parse&text={{PAGENAME}}&title=Test'
+ => 'Parse wikitext, specifying the page title',
'api.php?action=parse&summary=Some+[[link]]&prop=' => 'Parse a summary',
);
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index bd2fde2b..8b66781a 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -77,10 +77,6 @@ class ApiPatrol extends ApiBase {
public function getAllowedParams() {
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'rcid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
@@ -92,52 +88,23 @@ class ApiPatrol extends ApiBase {
public function getParamDescription() {
return array(
- 'token' => 'Patrol token obtained from list=recentchanges',
'rcid' => 'Recentchanges ID to patrol',
'revid' => 'Revision ID to patrol',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'rcid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Patrol a page or revision';
- }
-
- public function getPossibleErrors() {
- return array_merge(
- parent::getPossibleErrors(),
- parent::getRequireOnlyOneParameterErrorMessages( array( 'rcid', 'revid' ) ),
- array(
- array( 'nosuchrcid', 'rcid' ),
- array( 'nosuchrevid', 'revid' ),
- array(
- 'code' => 'notpatrollable',
- 'info' => "The revision can't be patrolled as it's too old"
- )
- ) );
+ return 'Patrol a page or revision.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
return 'patrol';
}
public function getExamples() {
return array(
- 'api.php?action=patrol&token=123abc&rcid=230672766',
- 'api.php?action=patrol&token=123abc&revid=230672766'
+ 'api.php?action=patrol&token=123ABC&rcid=230672766',
+ 'api.php?action=patrol&token=123ABC&revid=230672766'
);
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index 7830c8b4..a3d12b7f 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -28,9 +28,7 @@
* @ingroup API
*/
class ApiProtect extends ApiBase {
-
public function execute() {
- global $wgRestrictionLevels;
$params = $this->extractRequestParams();
$pageObj = $this->getTitleOrPageId( $params, 'fromdbmaster' );
@@ -47,7 +45,11 @@ class ApiProtect extends ApiBase {
if ( count( $expiry ) == 1 ) {
$expiry = array_fill( 0, count( $params['protections'] ), $expiry[0] );
} else {
- $this->dieUsageMsg( array( 'toofewexpiries', count( $expiry ), count( $params['protections'] ) ) );
+ $this->dieUsageMsg( array(
+ 'toofewexpiries',
+ count( $expiry ),
+ count( $params['protections'] )
+ ) );
}
}
@@ -71,11 +73,11 @@ class ApiProtect extends ApiBase {
if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' ) {
$this->dieUsageMsg( array( 'protect-invalidaction', $p[0] ) );
}
- if ( !in_array( $p[1], $wgRestrictionLevels ) && $p[1] != 'all' ) {
+ if ( !in_array( $p[1], $this->getConfig()->get( 'RestrictionLevels' ) ) && $p[1] != 'all' ) {
$this->dieUsageMsg( array( 'protect-invalidlevel', $p[1] ) );
}
- if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'never' ) ) ) {
+ if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'infinity', 'never' ) ) ) {
$expiryarray[$p[0]] = $db->getInfinity();
} else {
$exp = strtotime( $expiry[$i] );
@@ -89,18 +91,30 @@ class ApiProtect extends ApiBase {
}
$expiryarray[$p[0]] = $exp;
}
- $resultProtections[] = array( $p[0] => $protections[$p[0]],
- 'expiry' => ( $expiryarray[$p[0]] == $db->getInfinity() ?
- 'infinite' :
- wfTimestamp( TS_ISO_8601, $expiryarray[$p[0]] ) ) );
+ $resultProtections[] = array(
+ $p[0] => $protections[$p[0]],
+ 'expiry' => ( $expiryarray[$p[0]] == $db->getInfinity()
+ ? 'infinite'
+ : wfTimestamp( TS_ISO_8601, $expiryarray[$p[0]] )
+ )
+ );
}
$cascade = $params['cascade'];
+ if ( $params['watch'] ) {
+ $this->logFeatureUsage( 'action=protect&watch' );
+ }
$watch = $params['watch'] ? 'watch' : $params['watchlist'];
- $this->setWatch( $watch, $titleObj );
-
- $status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() );
+ $this->setWatch( $watch, $titleObj, 'watchdefault' );
+
+ $status = $pageObj->doUpdateRestrictions(
+ $protections,
+ $expiryarray,
+ $cascade,
+ $params['reason'],
+ $this->getUser()
+ );
if ( !$status->isOK() ) {
$this->dieStatus( $status );
@@ -134,10 +148,6 @@ class ApiProtect extends ApiBase {
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer',
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'protections' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_REQUIRED => true,
@@ -167,62 +177,41 @@ class ApiProtect extends ApiBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'title' => "Title of the page you want to (un)protect. Cannot be used together with {$p}pageid",
'pageid' => "ID of the page you want to (un)protect. Cannot be used together with {$p}title",
- 'token' => 'A protect token previously retrieved through prop=info',
'protections' => 'List of protection levels, formatted action=group (e.g. edit=sysop)',
- 'expiry' => array( 'Expiry timestamps. If only one timestamp is set, it\'ll be used for all protections.',
- 'Use \'infinite\', \'indefinite\' or \'never\', for a never-expiring protection.' ),
+ 'expiry' => array(
+ 'Expiry timestamps. If only one timestamp is ' .
+ 'set, it\'ll be used for all protections.',
+ 'Use \'infinite\', \'indefinite\', \'infinity\' or \'never\', for a never-expiring protection.'
+ ),
'reason' => 'Reason for (un)protecting',
- 'cascade' => array( 'Enable cascading protection (i.e. protect pages included in this page)',
- 'Ignored if not all protection levels are \'sysop\' or \'protect\'' ),
+ 'cascade' => array(
+ 'Enable cascading protection (i.e. protect pages included in this page)',
+ 'Ignored if not all protection levels are \'sysop\' or \'protect\''
+ ),
'watch' => 'If set, add the page being (un)protected to your watchlist',
- 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'title' => 'string',
- 'reason' => 'string',
- 'cascade' => 'boolean'
- )
+ 'watchlist' => 'Unconditionally add or remove the page from your ' .
+ 'watchlist, use preferences or do not change watch',
);
}
public function getDescription() {
- return 'Change the protection level of a page';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'toofewexpiries', 'noofexpiries', 'noofprotections' ),
- array( 'create-titleexists' ),
- array( 'missingtitle-createonly' ),
- array( 'protect-invalidaction', 'action' ),
- array( 'protect-invalidlevel', 'level' ),
- array( 'invalidexpiry', 'expiry' ),
- array( 'pastexpiry', 'expiry' ),
- )
- );
+ return 'Change the protection level of a page.';
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
return array(
- 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never',
- 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=all|move=all&reason=Lifting%20restrictions'
+ 'api.php?action=protect&title=Main%20Page&token=123ABC&' .
+ 'protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never',
+ 'api.php?action=protect&title=Main%20Page&token=123ABC&' .
+ 'protections=edit=all|move=all&reason=Lifting%20restrictions'
);
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index 0812ba51..7667b235 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -30,51 +30,22 @@
* @ingroup API
*/
class ApiPurge extends ApiBase {
-
private $mPageSet;
/**
- * Add all items from $values into the result
- * @param array $result output
- * @param array $values values to add
- * @param string $flag the name of the boolean flag to mark this element
- * @param string $name if given, name of the value
- */
- private static function addValues( array &$result, $values, $flag = null, $name = null ) {
- foreach ( $values as $val ) {
- if ( $val instanceof Title ) {
- $v = array();
- ApiQueryBase::addTitleInfo( $v, $val );
- } elseif ( $name !== null ) {
- $v = array( $name => $val );
- } else {
- $v = $val;
- }
- if ( $flag !== null ) {
- $v[$flag] = '';
- }
- $result[] = $v;
- }
- }
-
- /**
* Purges the cache of a page
*/
public function execute() {
$params = $this->extractRequestParams();
+ $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+
$forceLinkUpdate = $params['forcelinkupdate'];
$forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate'];
$pageSet = $this->getPageSet();
$pageSet->execute();
- $result = array();
- self::addValues( $result, $pageSet->getInvalidTitles(), 'invalid', 'title' );
- self::addValues( $result, $pageSet->getSpecialTitles(), 'special', 'title' );
- self::addValues( $result, $pageSet->getMissingPageIDs(), 'missing', 'pageid' );
- self::addValues( $result, $pageSet->getMissingRevisionIDs(), 'missing', 'revid' );
- self::addValues( $result, $pageSet->getMissingTitles(), 'missing' );
- self::addValues( $result, $pageSet->getInterwikiTitlesAsResult() );
+ $result = $pageSet->getInvalidTitlesAndRevisions();
foreach ( $pageSet->getGoodTitles() as $title ) {
$r = array();
@@ -85,13 +56,17 @@ class ApiPurge extends ApiBase {
if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) {
if ( !$this->getUser()->pingLimiter( 'linkpurge' ) ) {
- global $wgEnableParserCache;
-
$popts = $page->makeParserOptions( 'canonical' );
# Parse content; note that HTML generation is only needed if we want to cache the result.
$content = $page->getContent( Revision::RAW );
- $p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache );
+ $enableParserCache = $this->getConfig()->get( 'EnableParserCache' );
+ $p_result = $content->getParserOutput(
+ $title,
+ $page->getLatest(),
+ $popts,
+ $enableParserCache
+ );
# Update the links tables
$updates = $content->getSecondaryDataUpdates(
@@ -100,7 +75,7 @@ class ApiPurge extends ApiBase {
$r['linkupdate'] = '';
- if ( $wgEnableParserCache ) {
+ if ( $enableParserCache ) {
$pcache = ParserCache::singleton();
$pcache->save( $p_result, $page, $popts );
}
@@ -129,6 +104,8 @@ class ApiPurge extends ApiBase {
if ( $values ) {
$apiResult->addValue( null, 'redirects', $values );
}
+
+ $apiResult->endContinuation();
}
/**
@@ -139,6 +116,7 @@ class ApiPurge extends ApiBase {
if ( !isset( $this->mPageSet ) ) {
$this->mPageSet = new ApiPageSet( $this );
}
+
return $this->mPageSet;
}
@@ -154,11 +132,13 @@ class ApiPurge extends ApiBase {
public function getAllowedParams( $flags = 0 ) {
$result = array(
'forcelinkupdate' => false,
- 'forcerecursivelinkupdate' => false
+ 'forcerecursivelinkupdate' => false,
+ 'continue' => '',
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
+
return $result;
}
@@ -168,55 +148,16 @@ class ApiPurge extends ApiBase {
'forcelinkupdate' => 'Update the links tables',
'forcerecursivelinkupdate' => 'Update the links table, and update ' .
'the links tables for any page that uses this page as a template',
+ 'continue' => 'When more results are available, use this to continue',
);
}
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => true,
- '' => array(
- 'ns' => array(
- ApiBase::PROP_TYPE => 'namespace',
- ApiBase::PROP_NULLABLE => true
- ),
- 'title' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'pageid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'revid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'invalid' => 'boolean',
- 'special' => 'boolean',
- 'missing' => 'boolean',
- 'purged' => 'boolean',
- 'linkupdate' => 'boolean',
- 'iw' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- )
- );
- }
-
public function getDescription() {
return array( 'Purge the cache for the given titles.',
'Requires a POST request if the user is not logged in.'
);
}
- public function getPossibleErrors() {
- return array_merge(
- parent::getPossibleErrors(),
- $this->getPageSet()->getFinalPossibleErrors()
- );
- }
-
public function getExamples() {
return array(
'api.php?action=purge&titles=Main_Page|API' => 'Purge the "Main Page" and the "API" page',
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index e03837fc..7c750e41 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -44,18 +44,23 @@ class ApiQuery extends ApiBase {
private static $QueryPropModules = array(
'categories' => 'ApiQueryCategories',
'categoryinfo' => 'ApiQueryCategoryInfo',
+ 'contributors' => 'ApiQueryContributors',
'duplicatefiles' => 'ApiQueryDuplicateFiles',
'extlinks' => 'ApiQueryExternalLinks',
+ 'fileusage' => 'ApiQueryBacklinksprop',
'images' => 'ApiQueryImages',
'imageinfo' => 'ApiQueryImageInfo',
'info' => 'ApiQueryInfo',
'links' => 'ApiQueryLinks',
+ 'linkshere' => 'ApiQueryBacklinksprop',
'iwlinks' => 'ApiQueryIWLinks',
'langlinks' => 'ApiQueryLangLinks',
'pageprops' => 'ApiQueryPageProps',
+ 'redirects' => 'ApiQueryBacklinksprop',
'revisions' => 'ApiQueryRevisions',
'stashimageinfo' => 'ApiQueryStashImageInfo',
'templates' => 'ApiQueryLinks',
+ 'transcludedin' => 'ApiQueryBacklinksprop',
);
/**
@@ -68,6 +73,7 @@ class ApiQuery extends ApiBase {
'allimages' => 'ApiQueryAllImages',
'alllinks' => 'ApiQueryAllLinks',
'allpages' => 'ApiQueryAllPages',
+ 'allredirects' => 'ApiQueryAllLinks',
'alltransclusions' => 'ApiQueryAllLinks',
'allusers' => 'ApiQueryAllUsers',
'backlinks' => 'ApiQueryBacklinks',
@@ -83,6 +89,7 @@ class ApiQuery extends ApiBase {
'logevents' => 'ApiQueryLogEvents',
'pageswithprop' => 'ApiQueryPagesWithProp',
'pagepropnames' => 'ApiQueryPagePropNames',
+ 'prefixsearch' => 'ApiQueryPrefixSearch',
'protectedtitles' => 'ApiQueryProtectedTitles',
'querypage' => 'ApiQueryQueryPage',
'random' => 'ApiQueryRandom',
@@ -104,6 +111,7 @@ class ApiQuery extends ApiBase {
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
'filerepoinfo' => 'ApiQueryFileRepoInfo',
+ 'tokens' => 'ApiQueryTokens',
);
/**
@@ -114,26 +122,24 @@ class ApiQuery extends ApiBase {
private $mParams;
private $mNamedDB = array();
private $mModuleMgr;
- private $mGeneratorContinue;
- private $mUseLegacyContinue;
/**
- * @param $main ApiMain
- * @param $action string
+ * @param ApiMain $main
+ * @param string $action
*/
- public function __construct( $main, $action ) {
+ public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action );
$this->mModuleMgr = new ApiModuleManager( $this );
// Allow custom modules to be added in LocalSettings.php
- global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
+ $config = $this->getConfig();
$this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
- $this->mModuleMgr->addModules( $wgAPIPropModules, 'prop' );
+ $this->mModuleMgr->addModules( $config->get( 'APIPropModules' ), 'prop' );
$this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
- $this->mModuleMgr->addModules( $wgAPIListModules, 'list' );
+ $this->mModuleMgr->addModules( $config->get( 'APIListModules' ), 'list' );
$this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
- $this->mModuleMgr->addModules( $wgAPIMetaModules, 'meta' );
+ $this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' );
// Create PageSet that will process titles/pageids/revids/generator
$this->mPageSet = new ApiPageSet( $this );
@@ -163,6 +169,7 @@ class ApiQuery extends ApiBase {
$this->mNamedDB[$name] = wfGetDB( $db, $groups );
$this->profileDBOut();
}
+
return $this->mNamedDB[$name];
}
@@ -177,17 +184,18 @@ class ApiQuery extends ApiBase {
/**
* Get the array mapping module names to class names
* @deprecated since 1.21, use getModuleManager()'s methods instead
- * @return array array(modulename => classname)
+ * @return array Array(modulename => classname)
*/
public function getModules() {
wfDeprecated( __METHOD__, '1.21' );
+
return $this->getModuleManager()->getNamesWithClasses();
}
/**
* Get the generators array mapping module names to class names
* @deprecated since 1.21, list of generators is maintained by ApiPageSet
- * @return array array(modulename => classname)
+ * @return array Array(modulename => classname)
*/
public function getGenerators() {
wfDeprecated( __METHOD__, '1.21' );
@@ -197,6 +205,7 @@ class ApiQuery extends ApiBase {
$gens[$name] = $class;
}
}
+
return $gens;
}
@@ -204,7 +213,7 @@ class ApiQuery extends ApiBase {
* Get whether the specified module is a prop, list or a meta query module
* @deprecated since 1.21, use getModuleManager()->getModuleGroup()
* @param string $moduleName Name of the module to find type for
- * @return mixed string or null
+ * @return string|null
*/
function getModuleType( $moduleName ) {
return $this->getModuleManager()->getModuleGroup( $moduleName );
@@ -216,8 +225,8 @@ class ApiQuery extends ApiBase {
public function getCustomPrinter() {
// If &exportnowrap is set, use the raw formatter
if ( $this->getParameter( 'export' ) &&
- $this->getParameter( 'exportnowrap' ) )
- {
+ $this->getParameter( 'exportnowrap' )
+ ) {
return new ApiFormatRaw( $this->getMain(),
$this->getMain()->createPrinterByName( 'xml' ) );
} else {
@@ -238,23 +247,24 @@ class ApiQuery extends ApiBase {
public function execute() {
$this->mParams = $this->extractRequestParams();
- // $pagesetParams is a array of parameter names used by the pageset generator
- // or null if pageset has already finished and is no longer needed
- // $completeModules is a set of complete modules with the name as key
- $this->initContinue( $pagesetParams, $completeModules );
-
// Instantiate requested modules
$allModules = array();
$this->instantiateModules( $allModules, 'prop' );
- $propModules = $allModules; // Keep a copy
+ $propModules = array_keys( $allModules );
$this->instantiateModules( $allModules, 'list' );
$this->instantiateModules( $allModules, 'meta' );
// Filter modules based on continue parameter
- $modules = $this->initModules( $allModules, $completeModules, $pagesetParams !== null );
+ list( $generatorDone, $modules ) = $this->getResult()->beginContinuation(
+ $this->mParams['continue'], $allModules, $propModules
+ );
- // Execute pageset if in legacy mode or if pageset is not done
- if ( $completeModules === null || $pagesetParams !== null ) {
+ if ( !$generatorDone ) {
+ // Query modules may optimize data requests through the $this->getPageSet()
+ // object by adding extra fields from the page table.
+ foreach ( $modules as $module ) {
+ $module->requestExtraData( $this->mPageSet );
+ }
// Populate page/revision information
$this->mPageSet->execute();
// Record page information (title, namespace, if exists, etc)
@@ -280,134 +290,10 @@ class ApiQuery extends ApiBase {
// Set the cache mode
$this->getMain()->setCacheMode( $cacheMode );
- if ( $completeModules === null ) {
- return; // Legacy continue, we are done
- }
-
- // Reformat query-continue result section
- $result = $this->getResult();
- $qc = $result->getData();
- if ( isset( $qc['query-continue'] ) ) {
- $qc = $qc['query-continue'];
- $result->unsetValue( null, 'query-continue' );
- } elseif ( $this->mGeneratorContinue !== null ) {
- $qc = array();
- } else {
- // no more "continue"s, we are done!
- return;
- }
-
- // we are done with all the modules that do not have result in query-continue
- $completeModules = array_merge( $completeModules, array_diff_key( $modules, $qc ) );
- if ( $pagesetParams !== null ) {
- // The pageset is still in use, check if all props have finished
- $incompleteProps = array_intersect_key( $propModules, $qc );
- if ( count( $incompleteProps ) > 0 ) {
- // Properties are not done, continue with the same pageset state - copy current parameters
- $main = $this->getMain();
- $contValues = array();
- foreach ( $pagesetParams as $param ) {
- // The param name is already prefix-encoded
- $contValues[$param] = $main->getVal( $param );
- }
- } elseif ( $this->mGeneratorContinue !== null ) {
- // Move to the next set of pages produced by pageset, properties need to be restarted
- $contValues = $this->mGeneratorContinue;
- $pagesetParams = array_keys( $contValues );
- $completeModules = array_diff_key( $completeModules, $propModules );
- } else {
- // Done with the pageset, finish up with the the lists and meta modules
- $pagesetParams = null;
- }
- }
-
- $continue = '||' . implode( '|', array_keys( $completeModules ) );
- if ( $pagesetParams !== null ) {
- // list of all pageset parameters to use in the next request
- $continue = implode( '|', $pagesetParams ) . $continue;
- } else {
- // we are done with the pageset
- $contValues = array();
- $continue = '-' . $continue;
- }
- $contValues['continue'] = $continue;
- foreach ( $qc as $qcModule ) {
- foreach ( $qcModule as $qcKey => $qcValue ) {
- $contValues[$qcKey] = $qcValue;
- }
- }
- $this->getResult()->addValue( null, 'continue', $contValues );
- }
-
- /**
- * Parse 'continue' parameter into the list of complete modules and a list of generator parameters
- * @param array|null $pagesetParams returns list of generator params or null if pageset is done
- * @param array|null $completeModules returns list of finished modules (as keys), or null if legacy
- */
- private function initContinue( &$pagesetParams, &$completeModules ) {
- $pagesetParams = array();
- $continue = $this->mParams['continue'];
- if ( $continue !== null ) {
- $this->mUseLegacyContinue = false;
- if ( $continue !== '' ) {
- // Format: ' pagesetParam1 | pagesetParam2 || module1 | module2 | module3 | ...
- // If pageset is done, use '-'
- $continue = explode( '||', $continue );
- $this->dieContinueUsageIf( count( $continue ) !== 2 );
- if ( $continue[0] === '-' ) {
- $pagesetParams = null; // No need to execute pageset
- } elseif ( $continue[0] !== '' ) {
- // list of pageset params that might need to be repeated
- $pagesetParams = explode( '|', $continue[0] );
- }
- $continue = $continue[1];
- }
- if ( $continue !== '' ) {
- $completeModules = array_flip( explode( '|', $continue ) );
- } else {
- $completeModules = array();
- }
- } else {
- $this->mUseLegacyContinue = true;
- $completeModules = null;
- }
- }
-
- /**
- * Validate sub-modules, filter out completed ones, and do requestExtraData()
- * @param array $allModules An dict of name=>instance of all modules requested by the client
- * @param array|null $completeModules list of finished modules, or null if legacy continue
- * @param bool $usePageset True if pageset will be executed
- * @return array of modules to be processed during this execution
- */
- private function initModules( $allModules, $completeModules, $usePageset ) {
- $modules = $allModules;
- $tmp = $completeModules;
- $wasPosted = $this->getRequest()->wasPosted();
-
- /** @var $module ApiQueryBase */
- foreach ( $allModules as $moduleName => $module ) {
- if ( !$wasPosted && $module->mustBePosted() ) {
- $this->dieUsageMsgOrDebug( array( 'mustbeposted', $moduleName ) );
- }
- if ( $completeModules !== null && array_key_exists( $moduleName, $completeModules ) ) {
- // If this module is done, mark all its params as used
- $module->extractRequestParams();
- // Make sure this module is not used during execution
- unset( $modules[$moduleName] );
- unset( $tmp[$moduleName] );
- } elseif ( $completeModules === null || $usePageset ) {
- // Query modules may optimize data requests through the $this->getPageSet()
- // object by adding extra fields from the page table.
- // This function will gather all the extra request fields from the modules.
- $module->requestExtraData( $this->mPageSet );
- } else {
- // Error - this prop module must have finished before generator is done
- $this->dieContinueUsageIf( $this->mModuleMgr->getModuleGroup( $moduleName ) === 'prop' );
- }
- }
- $this->dieContinueUsageIf( $completeModules !== null && count( $tmp ) !== 0 );
- return $modules;
+ // Write the continuation data into the result
+ $this->getResult()->endContinuation(
+ $this->mParams['continue'] === null ? 'raw' : 'standard'
+ );
}
/**
@@ -415,8 +301,8 @@ class ApiQuery extends ApiBase {
* The cache mode may increase in the level of privacy, but public modules
* added to private data do not decrease the level of privacy.
*
- * @param $cacheMode string
- * @param $modCacheMode string
+ * @param string $cacheMode
+ * @param string $modCacheMode
* @return string
*/
protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
@@ -429,21 +315,26 @@ class ApiQuery extends ApiBase {
} else { // private
$cacheMode = 'private';
}
+
return $cacheMode;
}
/**
* Create instances of all modules requested by the client
- * @param array $modules to append instantiated modules to
+ * @param array $modules To append instantiated modules to
* @param string $param Parameter name to read modules from
*/
private function instantiateModules( &$modules, $param ) {
+ $wasPosted = $this->getRequest()->wasPosted();
if ( isset( $this->mParams[$param] ) ) {
foreach ( $this->mParams[$param] as $moduleName ) {
$instance = $this->mModuleMgr->getModule( $moduleName, $param );
if ( $instance === null ) {
ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
}
+ if ( !$wasPosted && $instance->mustBePosted() ) {
+ $this->dieUsageMsgOrDebug( array( 'mustbeposted', $moduleName ) );
+ }
// Ignore duplicates. TODO 2.0: die()?
if ( !array_key_exists( $moduleName, $modules ) ) {
$modules[$moduleName] = $instance;
@@ -461,29 +352,29 @@ class ApiQuery extends ApiBase {
$pageSet = $this->getPageSet();
$result = $this->getResult();
- // We don't check for a full result set here because we can't be adding
- // more than 380K. The maximum revision size is in the megabyte range,
- // and the maximum result size must be even higher than that.
+ // We can't really handle max-result-size failure here, but we need to
+ // check anyway in case someone set the limit stupidly low.
+ $fit = true;
$values = $pageSet->getNormalizedTitlesAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'normalized', $values );
+ $fit = $fit && $result->addValue( 'query', 'normalized', $values );
}
$values = $pageSet->getConvertedTitlesAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'converted', $values );
+ $fit = $fit && $result->addValue( 'query', 'converted', $values );
}
$values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
if ( $values ) {
- $result->addValue( 'query', 'interwiki', $values );
+ $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
}
$values = $pageSet->getRedirectTitlesAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'redirects', $values );
+ $fit = $fit && $result->addValue( 'query', 'redirects', $values );
}
$values = $pageSet->getMissingRevisionIDsAsResult( $result );
if ( $values ) {
- $result->addValue( 'query', 'badrevids', $values );
+ $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
}
// Page elements
@@ -514,10 +405,12 @@ class ApiQuery extends ApiBase {
ApiQueryBase::addTitleInfo( $vals, $title );
$vals['special'] = '';
if ( $title->isSpecialPage() &&
- !SpecialPageFactory::exists( $title->getDBkey() ) ) {
+ !SpecialPageFactory::exists( $title->getDBkey() )
+ ) {
$vals['missing'] = '';
} elseif ( $title->getNamespace() == NS_MEDIA &&
- !wfFindFile( $title ) ) {
+ !wfFindFile( $title )
+ ) {
$vals['missing'] = '';
}
$pages[$fakeId] = $vals;
@@ -537,12 +430,21 @@ class ApiQuery extends ApiBase {
// json treats all map keys as strings - converting to match
$pageIDs = array_map( 'strval', $pageIDs );
$result->setIndexedTagName( $pageIDs, 'id' );
- $result->addValue( 'query', 'pageids', $pageIDs );
+ $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
}
$result->setIndexedTagName( $pages, 'page' );
- $result->addValue( 'query', 'pages', $pages );
+ $fit = $fit && $result->addValue( 'query', 'pages', $pages );
}
+
+ if ( !$fit ) {
+ $this->dieUsage(
+ 'The value of $wgAPIMaxResultSize on this wiki is ' .
+ 'too small to hold basic result information',
+ 'badconfig'
+ );
+ }
+
if ( $this->mParams['export'] ) {
$this->doExport( $pageSet, $result );
}
@@ -552,26 +454,21 @@ class ApiQuery extends ApiBase {
* This method is called by the generator base when generator in the smart-continue
* mode tries to set 'query-continue' value. ApiQuery stores those values separately
* until the post-processing when it is known if the generation should continue or repeat.
- * @param ApiQueryGeneratorBase $module generator module
+ * @deprecated since 1.24
+ * @param ApiQueryGeneratorBase $module Generator module
* @param string $paramName
* @param mixed $paramValue
- * @return bool true if processed, false if this is a legacy continue
+ * @return bool True if processed, false if this is a legacy continue
*/
public function setGeneratorContinue( $module, $paramName, $paramValue ) {
- if ( $this->mUseLegacyContinue ) {
- return false;
- }
- $paramName = $module->encodeParamName( $paramName );
- if ( $this->mGeneratorContinue === null ) {
- $this->mGeneratorContinue = array();
- }
- $this->mGeneratorContinue[$paramName] = $paramValue;
- return true;
+ wfDeprecated( __METHOD__, '1.24' );
+ $this->getResult()->setGeneratorContinueParam( $module, $paramName, $paramValue );
+ return $this->getParameter( 'continue' ) !== null;
}
/**
- * @param $pageSet ApiPageSet Pages to be exported
- * @param $result ApiResult Result to output to
+ * @param ApiPageSet $pageSet Pages to be exported
+ * @param ApiResult $result Result to output to
*/
private function doExport( $pageSet, $result ) {
$exportTitles = array();
@@ -601,43 +498,43 @@ class ApiQuery extends ApiBase {
// Don't check the size of exported stuff
// It's not continuable, so it would cause more
// problems than it'd solve
- $result->disableSizeCheck();
if ( $this->mParams['exportnowrap'] ) {
$result->reset();
// Raw formatter will handle this
- $result->addValue( null, 'text', $exportxml );
- $result->addValue( null, 'mime', 'text/xml' );
+ $result->addValue( null, 'text', $exportxml, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
} else {
$r = array();
ApiResult::setContent( $r, $exportxml );
- $result->addValue( 'query', 'export', $r );
+ $result->addValue( 'query', 'export', $r, ApiResult::NO_SIZE_CHECK );
}
- $result->enableSizeCheck();
}
public function getAllowedParams( $flags = 0 ) {
$result = array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'prop' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'list' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'list' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'meta' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'meta' )
+ ApiBase::PARAM_TYPE => 'submodule',
),
'indexpageids' => false,
'export' => false,
'exportnowrap' => false,
'iwurl' => false,
'continue' => null,
+ 'rawcontinue' => false,
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
}
+
return $result;
}
@@ -699,38 +596,41 @@ class ApiQuery extends ApiBase {
public function getParamDescription() {
return $this->getPageSet()->getFinalParamDescription() + array(
- 'prop' => 'Which properties to get for the titles/revisions/pageids. Module help is available below',
+ 'prop' => 'Which properties to get for the titles/revisions/pageids. ' .
+ 'Module help is available below',
'list' => 'Which lists to get. Module help is available below',
'meta' => 'Which metadata to get about the site. Module help is available below',
'indexpageids' => 'Include an additional pageids section listing all returned page IDs',
'export' => 'Export the current revisions of all given or generated pages',
- 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
+ 'exportnowrap' => 'Return the export XML without wrapping it in an ' .
+ 'XML result (same format as Special:Export). Can only be used with export',
'iwurl' => 'Whether to get the full URL if the title is an interwiki link',
'continue' => array(
- 'When present, formats query-continue as key-value pairs that should simply be merged into the original request.',
+ 'When present, formats query-continue as key-value pairs that ' .
+ 'should simply be merged into the original request.',
'This parameter must be set to an empty string in the initial query.',
- 'This parameter is recommended for all new development, and will be made default in the next API version.' ),
+ 'This parameter is recommended for all new development, and ' .
+ 'will be made default in the next API version.'
+ ),
+ 'rawcontinue' => 'Currently ignored. In the future, \'continue=\' will become the ' .
+ 'default and this will be needed to receive the raw query-continue data.',
);
}
public function getDescription() {
return array(
- 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
+ 'Query API module allows applications to get needed pieces of data ' .
+ 'from the MediaWiki databases,',
'and is loosely based on the old query.php interface.',
- 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites'
- );
- }
-
- public function getPossibleErrors() {
- return array_merge(
- parent::getPossibleErrors(),
- $this->getPageSet()->getFinalPossibleErrors()
+ 'All data modifications will first have to use query to acquire a ' .
+ 'token to prevent abuse from malicious sites.'
);
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment&continue=',
+ 'api.php?action=query&prop=revisions&meta=siteinfo&' .
+ 'titles=Main%20Page&rvprop=user|comment&continue=',
'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions&continue=',
);
}
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index 3f5c6ee7..79fab727 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -32,7 +32,7 @@
*/
class ApiQueryAllCategories extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ac' );
}
@@ -49,7 +49,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
$db = $this->getDB();
@@ -67,8 +67,12 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null
+ ? null
+ : $this->titlePartToKey( $params['from'], NS_CATEGORY ) );
+ $to = ( $params['to'] === null
+ ? null
+ : $this->titlePartToKey( $params['to'], NS_CATEGORY ) );
$this->addWhereRange( 'cat_title', $dir, $from, $to );
$min = $params['min'];
@@ -80,7 +84,9 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'cat_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], NS_CATEGORY ),
+ $db->anyString() ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -109,8 +115,9 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$result = $this->getResult();
$count = 0;
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional cats to be had. Stop here...
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional cats to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->cat_title );
break;
}
@@ -200,25 +207,8 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => 'string'
- ),
- 'size' => array(
- 'size' => 'integer',
- 'pages' => 'integer',
- 'files' => 'integer',
- 'subcats' => 'integer'
- ),
- 'hidden' => array(
- 'hidden' => 'boolean'
- )
- );
- }
-
public function getDescription() {
- return 'Enumerate all categories';
+ return 'Enumerate all categories.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php
index ccc7a3a2..9dc5f69a 100644
--- a/includes/api/ApiQueryAllImages.php
+++ b/includes/api/ApiQueryAllImages.php
@@ -32,10 +32,9 @@
* @ingroup API
*/
class ApiQueryAllImages extends ApiQueryGeneratorBase {
-
protected $mRepo;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ai' );
$this->mRepo = RepoGroup::singleton()->getLocalRepo();
}
@@ -60,25 +59,32 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function executeGenerator( $resultPageSet ) {
if ( $resultPageSet->isResolvingRedirects() ) {
- $this->dieUsage( 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator', 'params' );
+ $this->dieUsage(
+ 'Use "gaifilterredir=nonredirects" option instead of "redirects" ' .
+ 'when using allimages as a generator',
+ 'params'
+ );
}
$this->run( $resultPageSet );
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
$repo = $this->mRepo;
if ( !$repo instanceof LocalRepo ) {
- $this->dieUsage( 'Local file repository does not support querying all images', 'unsupportedrepo' );
+ $this->dieUsage(
+ 'Local file repository does not support querying all images',
+ 'unsupportedrepo'
+ );
}
$prefix = $this->getModulePrefix();
@@ -103,11 +109,17 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$disallowed = array( 'start', 'end', 'user' );
foreach ( $disallowed as $pname ) {
if ( isset( $params[$pname] ) ) {
- $this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp", 'badparams' );
+ $this->dieUsage(
+ "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp",
+ 'badparams'
+ );
}
}
if ( $params['filterbots'] != 'all' ) {
- $this->dieUsage( "Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp", 'badparams' );
+ $this->dieUsage(
+ "Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp",
+ 'badparams'
+ );
}
// Pagination
@@ -120,28 +132,56 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
// Image filters
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null ? null : $this->titlePartToKey( $params['from'], NS_FILE ) );
+ $to = ( $params['to'] === null ? null : $this->titlePartToKey( $params['to'], NS_FILE ) );
$this->addWhereRange( 'img_name', ( $ascendingOrder ? 'newer' : 'older' ), $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'img_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'img_name' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], NS_FILE ),
+ $db->anyString() ) );
}
} else {
// Check mutually exclusive params
$disallowed = array( 'from', 'to', 'prefix' );
foreach ( $disallowed as $pname ) {
if ( isset( $params[$pname] ) ) {
- $this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name", 'badparams' );
+ $this->dieUsage(
+ "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name",
+ 'badparams'
+ );
}
}
if ( !is_null( $params['user'] ) && $params['filterbots'] != 'all' ) {
- // Since filterbots checks if each user has the bot right, it doesn't make sense to use it with user
- $this->dieUsage( "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together", 'badparams' );
+ // Since filterbots checks if each user has the bot right, it
+ // doesn't make sense to use it with user
+ $this->dieUsage(
+ "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together",
+ 'badparams'
+ );
}
// Pagination
- $this->addTimestampWhereRange( 'img_timestamp', ( $ascendingOrder ? 'newer' : 'older' ), $params['start'], $params['end'] );
+ $this->addTimestampWhereRange(
+ 'img_timestamp',
+ $ascendingOrder ? 'newer' : 'older',
+ $params['start'],
+ $params['end']
+ );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'img_name', $ascendingOrder ? 'newer' : 'older', null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $ascendingOrder ? '>' : '<' );
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueName = $db->addQuotes( $cont[1] );
+ $this->addWhere( "img_timestamp $op $continueTimestamp OR " .
+ "(img_timestamp = $continueTimestamp AND " .
+ "img_name $op= $continueName)"
+ );
+ }
// Image filters
if ( !is_null( $params['user'] ) ) {
@@ -156,7 +196,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'ug_user = img_user'
)
) ) );
- $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL': 'NOT NULL' );
+ $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL' : 'NOT NULL' );
$this->addWhere( "ug_group IS $groupCond" );
}
}
@@ -188,8 +228,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
if ( !is_null( $params['mime'] ) ) {
- global $wgMiserMode;
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' );
}
@@ -222,12 +261,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
if ( $params['sort'] == 'name' ) {
$this->setContinueEnumParameter( 'continue', $row->img_name );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->img_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->img_timestamp|$row->img_name" );
}
break;
}
@@ -243,7 +283,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
if ( $params['sort'] == 'name' ) {
$this->setContinueEnumParameter( 'continue', $row->img_name );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->img_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->img_timestamp|$row->img_name" );
}
break;
}
@@ -326,6 +366,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'sort' => 'Property to sort by',
'dir' => 'The direction in which to list',
@@ -335,54 +376,25 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'start' => "The timestamp to start enumerating from. Can only be used with {$p}sort=timestamp",
'end' => "The timestamp to end enumerating. Can only be used with {$p}sort=timestamp",
'prop' => ApiQueryImageInfo::getPropertyDescriptions( $this->propertyFilter ),
- 'prefix' => "Search for all image titles that begin with this value. Can only be used with {$p}sort=name",
+ 'prefix' => "Search for all image titles that begin with this " .
+ "value. Can only be used with {$p}sort=name",
'minsize' => 'Limit to images with at least this many bytes',
'maxsize' => 'Limit to images with at most this many bytes',
'sha1' => "SHA1 hash of image. Overrides {$p}sha1base36",
'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
- 'user' => "Only return files uploaded by this user. Can only be used with {$p}sort=timestamp. Cannot be used together with {$p}filterbots",
- 'filterbots' => "How to filter files uploaded by bots. Can only be used with {$p}sort=timestamp. Cannot be used together with {$p}user",
+ 'user' => "Only return files uploaded by this user. Can only be used " .
+ "with {$p}sort=timestamp. Cannot be used together with {$p}filterbots",
+ 'filterbots' => "How to filter files uploaded by bots. Can only be " .
+ "used with {$p}sort=timestamp. Cannot be used together with {$p}user",
'mime' => 'What MIME type to search for. e.g. image/jpeg. Disabled in Miser Mode',
'limit' => 'How many images in total to return',
);
}
- private $propertyFilter = array( 'archivename', 'thumbmime' );
-
- public function getResultProperties() {
- return array_merge(
- array(
- '' => array(
- 'name' => 'string',
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- ),
- ApiQueryImageInfo::getResultPropertiesFiltered( $this->propertyFilter )
- );
- }
+ private $propertyFilter = array( 'archivename', 'thumbmime', 'uploadwarning' );
public function getDescription() {
- return 'Enumerate all images sequentially';
- }
-
- public function getPossibleErrors() {
- $p = $this->getModulePrefix();
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator' ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}start' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}end' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}user' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}filterbots' can only be used with {$p}sort=timestamp" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}from' can only be used with {$p}sort=name" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}to' can only be used with {$p}sort=name" ),
- array( 'code' => 'badparams', 'info' => "Parameter'{$p}prefix' can only be used with {$p}sort=name" ),
- array( 'code' => 'badparams', 'info' => "Parameters '{$p}user' and '{$p}filterbots' cannot be used together" ),
- array( 'code' => 'unsupportedrepo', 'info' => 'Local file repository does not support querying all images' ),
- array( 'code' => 'mimesearchdisabled', 'info' => 'MIME search disabled in Miser Mode' ),
- array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
- array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
- ) );
+ return 'Enumerate all images sequentially.';
}
public function getExamples() {
@@ -391,11 +403,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'Simple Use',
'Show a list of files starting at the letter "B"',
),
- 'api.php?action=query&list=allimages&aiprop=user|timestamp|url&aisort=timestamp&aidir=older' => array(
+ 'api.php?action=query&list=allimages&aiprop=user|timestamp|url&' .
+ 'aisort=timestamp&aidir=older' => array(
'Simple Use',
'Show a list of recently uploaded files similar to Special:NewFiles',
),
- 'api.php?action=query&generator=allimages&gailimit=4&gaifrom=T&prop=imageinfo' => array(
+ 'api.php?action=query&generator=allimages&gailimit=4&' .
+ 'gaifrom=T&prop=imageinfo' => array(
'Using as Generator',
'Show info about 4 files starting at the letter "T"',
),
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index 47d1bcef..903dee42 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -31,15 +31,21 @@
*/
class ApiQueryAllLinks extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ private $table, $tablePrefix, $indexTag,
+ $description, $descriptionWhat, $descriptionTargets, $descriptionLinking;
+ private $fieldTitle = 'title';
+ private $dfltNamespace = NS_MAIN;
+ private $hasNamespace = true;
+ private $useIndex = null;
+ private $props = array(), $propHelp = array();
+
+ public function __construct( ApiQuery $query, $moduleName ) {
switch ( $moduleName ) {
case 'alllinks':
$prefix = 'al';
$this->table = 'pagelinks';
$this->tablePrefix = 'pl_';
- $this->fieldTitle = 'title';
- $this->dfltNamespace = NS_MAIN;
- $this->hasNamespace = true;
+ $this->useIndex = 'pl_namespace';
$this->indexTag = 'l';
$this->description = 'Enumerate all links that point to a given namespace';
$this->descriptionWhat = 'link';
@@ -50,11 +56,11 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$prefix = 'at';
$this->table = 'templatelinks';
$this->tablePrefix = 'tl_';
- $this->fieldTitle = 'title';
$this->dfltNamespace = NS_TEMPLATE;
- $this->hasNamespace = true;
+ $this->useIndex = 'tl_namespace';
$this->indexTag = 't';
- $this->description = 'List all transclusions (pages embedded using {{x}}), including non-existing';
+ $this->description =
+ 'List all transclusions (pages embedded using {{x}}), including non-existing';
$this->descriptionWhat = 'transclusion';
$this->descriptionTargets = 'transcluded titles';
$this->descriptionLinking = 'transcluding';
@@ -72,6 +78,24 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$this->descriptionTargets = 'file titles';
$this->descriptionLinking = 'using';
break;
+ case 'allredirects':
+ $prefix = 'ar';
+ $this->table = 'redirect';
+ $this->tablePrefix = 'rd_';
+ $this->indexTag = 'r';
+ $this->description = 'List all redirects to a namespace';
+ $this->descriptionWhat = 'redirect';
+ $this->descriptionTargets = 'target pages';
+ $this->descriptionLinking = 'redirecting';
+ $this->props = array(
+ 'fragment' => 'rd_fragment',
+ 'interwiki' => 'rd_interwiki',
+ );
+ $this->propHelp = array(
+ ' fragment - Adds the fragment from the redirect, if any',
+ ' interwiki - Adds the interwiki prefix from the redirect, if any',
+ );
+ break;
default:
ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
}
@@ -92,7 +116,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -111,10 +135,13 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
if ( $params['unique'] ) {
- if ( $fld_ids ) {
+ $matches = array_intersect_key( $prop, $this->props + array( 'ids' => 1 ) );
+ if ( $matches ) {
+ $p = $this->getModulePrefix();
$this->dieUsage(
- "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionWhat}s mode",
- 'params' );
+ "Cannot use {$p}prop=" . join( '|', array_keys( $matches ) ) . " with {$p}unique",
+ 'params'
+ );
}
$this->addOption( 'DISTINCT' );
}
@@ -145,19 +172,25 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
// 'continue' always overrides 'from'
- $from = ( $continue || is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $continue || $params['from'] === null ? null :
+ $this->titlePartToKey( $params['from'], $namespace ) );
+ $to = ( $params['to'] === null ? null :
+ $this->titlePartToKey( $params['to'], $namespace ) );
$this->addWhereRange( $pfx . $fieldTitle, 'newer', $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( $pfx . $fieldTitle . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( $pfx . $fieldTitle . $db->buildLike( $this->titlePartToKey(
+ $params['prefix'], $namespace ), $db->anyString() ) );
}
$this->addFields( array( 'pl_title' => $pfx . $fieldTitle ) );
$this->addFieldsIf( array( 'pl_from' => $pfx . 'from' ), !$params['unique'] );
+ foreach ( $this->props as $name => $field ) {
+ $this->addFieldsIf( $field, isset( $prop[$name] ) );
+ }
- if ( $this->hasNamespace ) {
- $this->addOption( 'USE INDEX', $pfx . 'namespace' );
+ if ( $this->useIndex ) {
+ $this->addOption( 'USE INDEX', $this->useIndex );
}
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
@@ -177,8 +210,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
@@ -196,6 +230,11 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$title = Title::makeTitle( $namespace, $row->pl_title );
ApiQueryBase::addTitleInfo( $vals, $title );
}
+ foreach ( $this->props as $name => $field ) {
+ if ( isset( $prop[$name] ) && $row->$field !== null && $row->$field !== '' ) {
+ $vals[$name] = $row->$field;
+ }
+ }
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
if ( $params['unique'] ) {
@@ -231,10 +270,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'title',
- ApiBase::PARAM_TYPE => array(
- 'ids',
- 'title'
- )
+ ApiBase::PARAM_TYPE => array_merge(
+ array( 'ids', 'title' ), array_keys( $this->props )
+ ),
),
'namespace' => array(
ApiBase::PARAM_DFLT => $this->dfltNamespace,
@@ -258,6 +296,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if ( !$this->hasNamespace ) {
unset( $allowedParams['namespace'] );
}
+
return $allowedParams;
}
@@ -271,68 +310,55 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
'to' => "The title of the $what to stop enumerating at",
'prefix' => "Search for all $targets that begin with this value",
'unique' => array(
- "Only show distinct $targets. Cannot be used with {$p}prop=ids.",
+ "Only show distinct $targets. Cannot be used with {$p}prop=" .
+ join( '|', array_keys( array( 'ids' => 1 ) + $this->props ) ) . '.',
'When used as a generator, yields target pages instead of source pages.',
),
'prop' => array(
'What pieces of information to include',
- " ids - Adds the pageid of the $linking page (Cannot be used with {$p}unique)",
- " title - Adds the title of the $what",
+ " ids - Adds the pageid of the $linking page (Cannot be used with {$p}unique)",
+ " title - Adds the title of the $what",
),
'namespace' => 'The namespace to enumerate',
'limit' => 'How many total items to return',
'continue' => 'When more results are available, use this to continue',
'dir' => 'The direction in which to list',
);
+ foreach ( $this->propHelp as $help ) {
+ $paramDescription['prop'][] = "$help (Cannot be used with {$p}unique)";
+ }
if ( !$this->hasNamespace ) {
unset( $paramDescription['namespace'] );
}
- return $paramDescription;
- }
- public function getResultProperties() {
- return array(
- 'ids' => array(
- 'fromid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
+ return $paramDescription;
}
public function getDescription() {
return $this->description;
}
- public function getPossibleErrors() {
- $m = $this->getModuleName();
- $what = $this->descriptionWhat;
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$what}s mode" ),
- ) );
- }
-
public function getExamples() {
$p = $this->getModulePrefix();
$name = $this->getModuleName();
$what = $this->descriptionWhat;
$targets = $this->descriptionTargets;
+
return array(
"api.php?action=query&list={$name}&{$p}from=B&{$p}prop=ids|title"
- => "List $targets with page ids they are from, including missing ones. Start at B",
+ => "List $targets with page ids they are from, including missing ones. Start at B",
"api.php?action=query&list={$name}&{$p}unique=&{$p}from=B"
- => "List unique $targets",
+ => "List unique $targets",
"api.php?action=query&generator={$name}&g{$p}unique=&g{$p}from=B"
- => "Gets all $targets, marking the missing ones",
+ => "Gets all $targets, marking the missing ones",
"api.php?action=query&generator={$name}&g{$p}from=B"
- => "Gets pages containing the {$what}s",
+ => "Gets pages containing the {$what}s",
);
}
public function getHelpUrls() {
$name = ucfirst( $this->getModuleName() );
+
return "https://www.mediawiki.org/wiki/API:{$name}";
}
}
diff --git a/includes/api/ApiQueryAllMessages.php b/includes/api/ApiQueryAllMessages.php
index d47c7b76..a75a16fc 100644
--- a/includes/api/ApiQueryAllMessages.php
+++ b/includes/api/ApiQueryAllMessages.php
@@ -31,7 +31,7 @@
*/
class ApiQueryAllMessages extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'am' );
}
@@ -63,14 +63,13 @@ class ApiQueryAllMessages extends ApiQueryBase {
if ( in_array( '*', $params['messages'] ) ) {
$message_names = Language::getMessageKeysFor( $langObj->getCode() );
if ( $params['includelocal'] ) {
- global $wgLanguageCode;
$message_names = array_unique( array_merge(
$message_names,
// Pass in the content language code so we get local messages that have a
// MediaWiki:msgkey page. We might theoretically miss messages that have no
// MediaWiki:msgkey page but do have a MediaWiki:msgkey/lang page, but that's
// just a stupid case.
- MessageCache::singleton()->getAllMessageKeys( $wgLanguageCode )
+ MessageCache::singleton()->getAllMessageKeys( $this->getConfig()->get( 'LanguageCode' ) )
) );
}
sort( $message_names );
@@ -116,7 +115,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
global $wgContLang;
$lang = $langObj->getCode();
- $customisedMessages = AllmessagesTablePager::getCustomisedStatuses(
+ $customisedMessages = AllMessagesTablePager::getCustomisedStatuses(
array_map( array( $langObj, 'ucfirst' ), $messages_target ), $lang, $lang != $wgContLang->getCode() );
$customised = $params['customised'] === 'modified';
@@ -241,10 +240,10 @@ class ApiQueryAllMessages extends ApiQueryBase {
'messages' => 'Which messages to output. "*" (default) means all messages',
'prop' => 'Which properties to get',
'enableparser' => array( 'Set to enable parser, will preprocess the wikitext of message',
- 'Will substitute magic words, handle templates etc.' ),
+ 'Will substitute magic words, handle templates etc.' ),
'nocontent' => 'If set, do not include the content of the messages in the output.',
'includelocal' => array( "Also include local messages, i.e. messages that don't exist in the software but do exist as a MediaWiki: page.",
- "This lists all MediaWiki: pages, so it will also list those that aren't 'really' messages such as Common.js",
+ "This lists all MediaWiki: pages, so it will also list those that aren't 'really' messages such as Common.js",
),
'title' => 'Page name to use as context when parsing message (for enableparser option)',
'args' => 'Arguments to be substituted into message',
@@ -257,35 +256,8 @@ class ApiQueryAllMessages extends ApiQueryBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'invalidlang', 'info' => 'Invalid language code for parameter lang' ),
- ) );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'name' => 'string',
- 'customised' => 'boolean',
- 'missing' => 'boolean',
- '*' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'default' => array(
- 'defaultmissing' => 'boolean',
- 'default' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
- );
- }
-
public function getDescription() {
- return 'Return messages from this site';
+ return 'Return messages from this site.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryAllPages.php b/includes/api/ApiQueryAllPages.php
index d95980c2..b7bd65a5 100644
--- a/includes/api/ApiQueryAllPages.php
+++ b/includes/api/ApiQueryAllPages.php
@@ -31,7 +31,7 @@
*/
class ApiQueryAllPages extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ap' );
}
@@ -44,19 +44,23 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function executeGenerator( $resultPageSet ) {
if ( $resultPageSet->isResolvingRedirects() ) {
- $this->dieUsage( 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params' );
+ $this->dieUsage(
+ 'Use "gapfilterredir=nonredirects" option instead of "redirects" ' .
+ 'when using allpages as a generator',
+ 'params'
+ );
}
$this->run( $resultPageSet );
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -83,12 +87,18 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$this->addWhereFld( 'page_namespace', $params['namespace'] );
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null
+ ? null
+ : $this->titlePartToKey( $params['from'], $params['namespace'] ) );
+ $to = ( $params['to'] === null
+ ? null
+ : $this->titlePartToKey( $params['to'], $params['namespace'] ) );
$this->addWhereRange( 'page_title', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'page_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'page_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
+ $db->anyString() ) );
}
if ( is_null( $resultPageSet ) ) {
@@ -145,7 +155,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
}
$this->addOption( 'DISTINCT' );
-
} elseif ( isset( $params['prlevel'] ) ) {
$this->dieUsage( 'prlevel may not be used without prtype', 'params' );
}
@@ -186,8 +195,9 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'continue', $row->page_title );
break;
}
@@ -215,8 +225,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- global $wgRestrictionLevels;
-
return array(
'from' => null,
'continue' => null,
@@ -245,7 +253,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true
),
'prlevel' => array(
- ApiBase::PARAM_TYPE => $wgRestrictionLevels,
+ ApiBase::PARAM_TYPE => $this->getConfig()->get( 'RestrictionLevels' ),
ApiBase::PARAM_ISMULTI => true
),
'prfiltercascade' => array(
@@ -291,6 +299,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'from' => 'The page title to start enumerating from',
'continue' => 'When more results are available, use this to continue',
@@ -303,7 +312,8 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'maxsize' => 'Limit to pages with at most this many bytes',
'prtype' => 'Limit to protected pages only',
'prlevel' => "The protection level (must be used with {$p}prtype= parameter)",
- 'prfiltercascade' => "Filter protections based on cascadingness (ignored when {$p}prtype isn't set)",
+ 'prfiltercascade'
+ => "Filter protections based on cascadingness (ignored when {$p}prtype isn't set)",
'filterlanglinks' => array(
'Filter based on whether a page has langlinks',
'Note that this may not consider langlinks added by extensions.',
@@ -318,25 +328,8 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Enumerate all pages sequentially in a given namespace';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator' ),
- array( 'code' => 'params', 'info' => 'prlevel may not be used without prtype' ),
- ) );
+ return 'Enumerate all pages sequentially in a given namespace.';
}
public function getExamples() {
@@ -349,9 +342,9 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'Using as Generator',
'Show info about 4 pages starting at the letter "T"',
),
- 'api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content' => array(
- 'Show content of first 2 non-redirect pages beginning at "Re"',
- )
+ 'api.php?action=query&generator=allpages&gaplimit=2&' .
+ 'gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content'
+ => array( 'Show content of first 2 non-redirect pages beginning at "Re"' )
);
}
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 1948a51a..affddda7 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -30,7 +30,7 @@
* @ingroup API
*/
class ApiQueryAllUsers extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'au' );
}
@@ -38,15 +38,22 @@ class ApiQueryAllUsers extends ApiQueryBase {
* This function converts the user name to a canonical form
* which is stored in the database.
* @param string $name
- * @return String
+ * @return string
*/
private function getCanonicalUserName( $name ) {
return str_replace( '_', ' ', $name );
}
public function execute() {
- $db = $this->getDB();
$params = $this->extractRequestParams();
+ $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
+
+ if ( $params['activeusers'] ) {
+ // Update active user cache
+ SpecialActiveUsers::mergeActiveUsers( 600, $activeUserDays );
+ }
+
+ $db = $this->getDB();
$prop = $params['prop'];
if ( !is_null( $prop ) ) {
@@ -58,7 +65,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
$fld_registration = isset( $prop['registration'] );
$fld_implicitgroups = isset( $prop['implicitgroups'] );
} else {
- $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration = $fld_rights = $fld_implicitgroups = false;
+ $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration =
+ $fld_rights = $fld_implicitgroups = false;
}
$limit = $params['limit'];
@@ -70,9 +78,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
$from = is_null( $params['from'] ) ? null : $this->getCanonicalUserName( $params['from'] );
$to = is_null( $params['to'] ) ? null : $this->getCanonicalUserName( $params['to'] );
- # MySQL doesn't seem to use 'equality propagation' here, so like the
- # ActiveUsers special page, we have to use rc_user_text for some cases.
- $userFieldToSort = $params['activeusers'] ? 'rc_user_text' : 'user_name';
+ # MySQL can't figure out that 'user_name' and 'qcc_title' are the same
+ # despite the JOIN condition, so manually sort on the correct one.
+ $userFieldToSort = $params['activeusers'] ? 'qcc_title' : 'user_name';
$this->addWhereRange( $userFieldToSort, $dir, $from, $to );
@@ -90,6 +98,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
// no group with the given right(s) exists, no need for a query
if ( !count( $groups ) ) {
$this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' );
+
return;
}
@@ -111,7 +120,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
// Filter only users that belong to a given group
$this->addTables( 'user_groups', 'ug1' );
$this->addJoinConds( array( 'ug1' => array( 'INNER JOIN', array( 'ug1.ug_user=user_id',
- 'ug1.ug_group' => $params['group'] ) ) ) );
+ 'ug1.ug_group' => $params['group'] ) ) ) );
}
if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) {
@@ -122,12 +131,14 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( count( $params['excludegroup'] ) == 1 ) {
$exclude = array( 'ug1.ug_group' => $params['excludegroup'][0] );
} else {
- $exclude = array( $db->makeList( array( 'ug1.ug_group' => $params['excludegroup'] ), LIST_OR ) );
+ $exclude = array( $db->makeList(
+ array( 'ug1.ug_group' => $params['excludegroup'] ),
+ LIST_OR
+ ) );
}
$this->addJoinConds( array( 'ug1' => array( 'LEFT OUTER JOIN',
array_merge( array( 'ug1.ug_user=user_id' ), $exclude )
- )
- ) );
+ ) ) );
$this->addWhere( 'ug1.ug_user IS NULL' );
}
@@ -145,26 +156,38 @@ class ApiQueryAllUsers extends ApiQueryBase {
$this->addTables( 'user_groups', 'ug2' );
$this->addJoinConds( array( 'ug2' => array( 'LEFT JOIN', 'ug2.ug_user=user_id' ) ) );
- $this->addFields( 'ug2.ug_group ug_group2' );
+ $this->addFields( array( 'ug_group2' => 'ug2.ug_group' ) );
} else {
$sqlLimit = $limit + 1;
}
if ( $params['activeusers'] ) {
- global $wgActiveUserDays;
- $this->addTables( 'recentchanges' );
-
- $this->addJoinConds( array( 'recentchanges' => array(
- 'INNER JOIN', 'rc_user_text=user_name'
+ $activeUserSeconds = $activeUserDays * 86400;
+
+ // Filter query to only include users in the active users cache
+ $this->addTables( 'querycachetwo' );
+ $this->addJoinConds( array( 'querycachetwo' => array(
+ 'INNER JOIN', array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'qcc_title=user_name',
+ ),
) ) );
- $this->addFields( array( 'recentedits' => 'COUNT(*)' ) );
-
- $this->addWhere( 'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ) );
- $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 );
- $this->addWhere( 'rc_timestamp >= ' . $db->addQuotes( $timestamp ) );
-
- $this->addOption( 'GROUP BY', $userFieldToSort );
+ // Actually count the actions using a subquery (bug 64505 and bug 64507)
+ $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
+ $this->addFields( array(
+ 'recentactions' => '(' . $db->selectSQLText(
+ 'recentchanges',
+ 'COUNT(*)',
+ array(
+ 'rc_user_text = user_name',
+ 'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
+ 'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
+ 'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
+ )
+ ) . ')'
+ ) );
}
$this->addOption( 'LIMIT', $sqlLimit );
@@ -187,12 +210,12 @@ class ApiQueryAllUsers extends ApiQueryBase {
$lastUser = false;
$result = $this->getResult();
- //
- // This loop keeps track of the last entry.
- // For each new row, if the new row is for different user then the last, the last entry is added to results.
- // Otherwise, the group of the new row is appended to the last entry.
- // The setContinue... is more complex because of this, and takes into account the higher sql limit
- // to make sure all rows that belong to the same user are received.
+ // This loop keeps track of the last entry. For each new row, if the
+ // new row is for different user then the last, the last entry is added
+ // to results. Otherwise, the group of the new row is appended to the
+ // last entry. The setContinue... is more complex because of this, and
+ // takes into account the higher sql limit to make sure all rows that
+ // belong to the same user are received.
foreach ( $res as $row ) {
$count++;
@@ -200,8 +223,13 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( $lastUser !== $row->user_name ) {
// Save the last pass's user data
if ( is_array( $lastUserData ) ) {
- $fit = $result->addValue( array( 'query', $this->getModuleName() ),
+ if ( $params['activeusers'] && $lastUserData['recentactions'] === 0 ) {
+ // activeusers cache was out of date
+ $fit = true;
+ } else {
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $lastUserData );
+ }
$lastUserData = null;
@@ -212,7 +240,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
if ( $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'from', $row->user_name );
break;
}
@@ -227,6 +256,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$lastUserData['blockid'] = $row->ipb_id;
$lastUserData['blockedby'] = $row->ipb_by_text;
$lastUserData['blockedbyid'] = $row->ipb_by;
+ $lastUserData['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
$lastUserData['blockreason'] = $row->ipb_reason;
$lastUserData['blockexpiry'] = $row->ipb_expiry;
}
@@ -237,7 +267,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
$lastUserData['editcount'] = intval( $row->user_editcount );
}
if ( $params['activeusers'] ) {
- $lastUserData['recenteditcount'] = intval( $row->recentedits );
+ $lastUserData['recentactions'] = intval( $row->recentactions );
+ // @todo 'recenteditcount' is set for BC, remove in 1.25
+ $lastUserData['recenteditcount'] = $lastUserData['recentactions'];
}
if ( $fld_registration ) {
$lastUserData['registration'] = $row->user_registration ?
@@ -246,10 +278,13 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
if ( $sqlLimit == $count ) {
- // BUG! database contains group name that User::getAllGroups() does not return
- // TODO: should handle this more gracefully
- ApiBase::dieDebug( __METHOD__,
- 'MediaWiki configuration error: the database contains more user groups than known to User::getAllGroups() function' );
+ // @todo BUG! database contains group name that User::getAllGroups() does not return
+ // Should handle this more gracefully
+ ApiBase::dieDebug(
+ __METHOD__,
+ 'MediaWiki configuration error: The database contains more ' .
+ 'user groups than known to User::getAllGroups() function'
+ );
}
$lastUserObj = User::newFromId( $row->user_id );
@@ -295,7 +330,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
}
- if ( is_array( $lastUserData ) ) {
+ if ( is_array( $lastUserData ) &&
+ !( $params['activeusers'] && $lastUserData['recentactions'] === 0 )
+ ) {
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $lastUserData );
if ( !$fit ) {
@@ -312,6 +349,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
public function getAllowedParams() {
$userGroups = User::getAllGroups();
+
return array(
'from' => null,
'to' => null,
@@ -359,7 +397,6 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
public function getParamDescription() {
- global $wgActiveUserDays;
return array(
'from' => 'The user name to start enumerating from',
'to' => 'The user name to stop enumerating at',
@@ -367,72 +404,26 @@ class ApiQueryAllUsers extends ApiQueryBase {
'dir' => 'Direction to sort in',
'group' => 'Limit users to given group name(s)',
'excludegroup' => 'Exclude users in given group name(s)',
- 'rights' => 'Limit users to given right(s) (does not include rights granted by implicit or auto-promoted groups like *, user, or autoconfirmed)',
+ 'rights' => 'Limit users to given right(s) (does not include rights ' .
+ 'granted by implicit or auto-promoted groups like *, user, or autoconfirmed)',
'prop' => array(
'What pieces of information to include.',
' blockinfo - Adds the information about a current block on the user',
- ' groups - Lists groups that the user is in. This uses more server resources and may return fewer results than the limit',
+ ' groups - Lists groups that the user is in. This uses ' .
+ 'more server resources and may return fewer results than the limit',
' implicitgroups - Lists all the groups the user is automatically in',
' rights - Lists rights that the user has',
' editcount - Adds the edit count of the user',
' registration - Adds the timestamp of when the user registered if available (may be blank)',
- ),
+ ),
'limit' => 'How many total user names to return',
'witheditsonly' => 'Only list users who have made edits',
- 'activeusers' => "Only list users active in the last {$wgActiveUserDays} days(s)"
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'userid' => 'integer',
- 'name' => 'string',
- 'recenteditcount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'blockinfo' => array(
- 'blockid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedby' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedbyid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedreason' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blockedexpiry' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'hidden' => 'boolean'
- ),
- 'editcount' => array(
- 'editcount' => 'integer'
- ),
- 'registration' => array(
- 'registration' => 'string'
- )
+ 'activeusers' => "Only list users active in the last {$this->getConfig()->get( 'ActiveUserDays' )} days(s)"
);
}
public function getDescription() {
- return 'Enumerate all registered users';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'group-excludegroup', 'info' => 'group and excludegroup cannot be used together' ),
- ) );
+ return 'Enumerate all registered users.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index 2d1089a7..c141246d 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -75,7 +75,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
)
);
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
$settings = $this->backlinksSettings[$moduleName];
$prefix = $settings['prefix'];
$code = $settings['code'];
@@ -116,7 +116,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function prepareFirstQuery( $resultPageSet = null ) {
@@ -149,7 +149,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
if ( $this->params['filterredir'] == 'redirects' ) {
$this->addWhereFld( 'page_is_redirect', 1 );
} elseif ( $this->params['filterredir'] == 'nonredirects' && !$this->redirect ) {
- // bug 22245 - Check for !redirect, as filtering nonredirects, when getting what links to them is contradictory
+ // bug 22245 - Check for !redirect, as filtering nonredirects, when
+ // getting what links to them is contradictory
$this->addWhereFld( 'page_is_redirect', 0 );
}
@@ -160,7 +161,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function prepareSecondQuery( $resultPageSet = null ) {
@@ -193,7 +194,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$redirNs = $t->getNamespace();
$redirDBkey = $t->getDBkey();
$titleWhere[] = "{$this->bl_title} = " . $db->addQuotes( $redirDBkey ) .
- ( $this->hasNS ? " AND {$this->bl_ns} = {$redirNs}" : '' );
+ ( $this->hasNS ? " AND {$this->bl_ns} = {$redirNs}" : '' );
$allRedirNs[] = $redirNs;
$allRedirDBkey[] = $redirDBkey;
}
@@ -209,14 +210,14 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$from = $this->redirID;
if ( $this->hasNS ) {
$this->addWhere( "{$this->bl_ns} $op $ns OR " .
- "({$this->bl_ns} = $ns AND " .
- "({$this->bl_title} $op $title OR " .
- "({$this->bl_title} = $title AND " .
- "{$this->bl_from} $op= $from)))" );
+ "({$this->bl_ns} = $ns AND " .
+ "({$this->bl_title} $op $title OR " .
+ "({$this->bl_title} = $title AND " .
+ "{$this->bl_from} $op= $from)))" );
} else {
$this->addWhere( "{$this->bl_title} $op $title OR " .
- "({$this->bl_title} = $title AND " .
- "{$this->bl_from} $op= $from)" );
+ "({$this->bl_title} = $title AND " .
+ "{$this->bl_from} $op= $from)" );
}
}
if ( $this->params['filterredir'] == 'redirects' ) {
@@ -241,7 +242,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -268,8 +269,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$count = 0;
foreach ( $res as $row ) {
- if ( ++ $count > $this->params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $this->params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
// Continue string preserved in case the redirect query doesn't pass the limit
$this->continueStr = $this->getContinueStr( $row->page_id );
break;
@@ -294,7 +296,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$count = 0;
foreach ( $res as $row ) {
if ( ++$count > $this->params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
// We need to keep the parent page of this redir in
if ( $this->hasNS ) {
$parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}];
@@ -384,7 +387,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$parentID = $this->pageMap[$ns][$row->{$this->bl_title}];
// Put all the results in an array first
$this->resultArr[$parentID]['redirlinks'][] = $a;
- $this->getResult()->setIndexedTagName( $this->resultArr[$parentID]['redirlinks'], $this->bl_code );
+ $this->getResult()->setIndexedTagName(
+ $this->resultArr[$parentID]['redirlinks'],
+ $this->bl_code
+ );
}
protected function processContinue() {
@@ -396,7 +402,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// only image titles are allowed for the root in imageinfo mode
if ( !$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE ) {
- $this->dieUsage( "The title for {$this->getModuleName()} query must be an image", 'bad_image_title' );
+ $this->dieUsage(
+ "The title for {$this->getModuleName()} query must be an image",
+ 'bad_image_title'
+ );
}
}
@@ -428,7 +437,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
return;
}
$this->redirID = $redirID;
-
}
protected function getContinueStr( $lastPageID ) {
@@ -481,6 +489,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
return $retval;
}
$retval['redirect'] = false;
+
return $retval;
}
@@ -494,50 +503,36 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
);
if ( $this->getModuleName() != 'embeddedin' ) {
return array_merge( $retval, array(
- 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.',
- 'filterredir' => "How to filter for redirects. If set to nonredirects when {$this->bl_code}redirect is enabled, this is only applied to the second level",
- 'limit' => "How many total pages to return. If {$this->bl_code}redirect is enabled, limit applies to each level separately (which means you may get up to 2 * limit results)."
+ 'redirect' => 'If linking page is a redirect, find all pages ' .
+ 'that link to that redirect as well. Maximum limit is halved.',
+ 'filterredir' => 'How to filter for redirects. If set to ' .
+ "nonredirects when {$this->bl_code}redirect is enabled, " .
+ 'this is only applied to the second level',
+ 'limit' => 'How many total pages to return. If ' .
+ "{$this->bl_code}redirect is enabled, limit applies to each " .
+ 'level separately (which means you may get up to 2 * limit results).'
) );
}
+
return array_merge( $retval, array(
'filterredir' => 'How to filter for redirects',
'limit' => 'How many total pages to return'
) );
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'redirect' => 'boolean'
- )
- );
- }
-
public function getDescription() {
switch ( $this->getModuleName() ) {
case 'backlinks':
- return 'Find all pages that link to the given page';
+ return 'Find all pages that link to the given page.';
case 'embeddedin':
- return 'Find all pages that embed (transclude) the given title';
+ return 'Find all pages that embed (transclude) the given title.';
case 'imageusage':
return 'Find all pages that use the given image title.';
default:
- ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
+ ApiBase::dieDebug( __METHOD__, 'Unknown module name.' );
}
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'code' => 'bad_image_title', 'info' => "The title for {$this->getModuleName()} query must be an image" ),
- )
- );
- }
-
public function getExamples() {
static $examples = array(
'backlinks' => array(
diff --git a/includes/api/ApiQueryBacklinksprop.php b/includes/api/ApiQueryBacklinksprop.php
new file mode 100644
index 00000000..cd682612
--- /dev/null
+++ b/includes/api/ApiQueryBacklinksprop.php
@@ -0,0 +1,472 @@
+<?php
+/**
+ * API module to handle links table back-queries
+ *
+ * Created on Aug 19, 2014
+ *
+ * Copyright © 2014 Brad Jorsch <bjorsch@wikimedia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.24
+ */
+
+/**
+ * This implements prop=redirects, prop=linkshere, prop=catmembers,
+ * prop=transcludedin, and prop=fileusage
+ *
+ * @ingroup API
+ * @since 1.24
+ */
+class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
+
+ // Data for the various modules implemented by this class
+ private static $settings = array(
+ 'redirects' => array(
+ 'code' => 'rd',
+ 'prefix' => 'rd',
+ 'linktable' => 'redirect',
+ 'what' => 'redirects to',
+ 'description' => 'Returns all redirects to the given pages.',
+ 'props' => array(
+ 'fragment' => 'Fragment of each redirect, if any',
+ ),
+ 'showredirects' => false,
+ 'show' => array(
+ 'fragment' => 'Only show redirects with a fragment',
+ '!fragment' => 'Only show redirects without a fragment',
+ ),
+ ),
+ 'linkshere' => array(
+ 'code' => 'lh',
+ 'prefix' => 'pl',
+ 'linktable' => 'pagelinks',
+ 'from_namespace' => true,
+ 'what' => 'pages linking to',
+ 'description' => 'Find all pages that link to the given pages.',
+ 'showredirects' => true,
+ ),
+ 'transcludedin' => array(
+ 'code' => 'ti',
+ 'prefix' => 'tl',
+ 'linktable' => 'templatelinks',
+ 'from_namespace' => true,
+ 'what' => 'pages transcluding',
+ 'description' => 'Find all pages that transclude the given pages.',
+ 'showredirects' => true,
+ ),
+ 'fileusage' => array(
+ 'code' => 'fu',
+ 'prefix' => 'il',
+ 'linktable' => 'imagelinks',
+ 'from_namespace' => true,
+ 'to_namespace' => NS_FILE,
+ 'what' => 'pages using',
+ 'exampletitle' => 'File:Example.jpg',
+ 'description' => 'Find all pages that use the given files.',
+ 'showredirects' => true,
+ ),
+ );
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, self::$settings[$moduleName]['code'] );
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param ApiPageSet $resultPageSet
+ */
+ private function run( ApiPageSet $resultPageSet = null ) {
+ $settings = self::$settings[$this->getModuleName()];
+
+ $db = $this->getDB();
+ $params = $this->extractRequestParams();
+ $prop = array_flip( $params['prop'] );
+ $emptyString = $db->addQuotes( '' );
+
+ $pageSet = $this->getPageSet();
+ $titles = $pageSet->getGoodTitles() + $pageSet->getMissingTitles();
+ $map = $pageSet->getAllTitlesByNamespace();
+
+ // Determine our fields to query on
+ $p = $settings['prefix'];
+ $hasNS = !isset( $settings['to_namespace'] );
+ if ( $hasNS ) {
+ $bl_namespace = "{$p}_namespace";
+ $bl_title = "{$p}_title";
+ } else {
+ $bl_namespace = $settings['to_namespace'];
+ $bl_title = "{$p}_to";
+
+ $titles = array_filter( $titles, function ( $t ) use ( $bl_namespace ) {
+ return $t->getNamespace() === $bl_namespace;
+ } );
+ $map = array_intersect_key( $map, array( $bl_namespace => true ) );
+ }
+ $bl_from = "{$p}_from";
+
+ if ( !$titles ) {
+ return; // nothing to do
+ }
+
+ // Figure out what we're sorting by, and add associated WHERE clauses.
+ // MySQL's query planner screws up if we include a field in ORDER BY
+ // when it's constant in WHERE, so we have to test that for each field.
+ $sortby = array();
+ if ( $hasNS && count( $map ) > 1 ) {
+ $sortby[$bl_namespace] = 'ns';
+ }
+ $theTitle = null;
+ foreach ( $map as $nsTitles ) {
+ reset( $nsTitles );
+ $key = key( $nsTitles );
+ if ( $theTitle === null ) {
+ $theTitle = $key;
+ }
+ if ( count( $nsTitles ) > 1 || $key !== $theTitle ) {
+ $sortby[$bl_title] = 'title';
+ break;
+ }
+ }
+ $miser_ns = null;
+ if ( $params['namespace'] !== null ) {
+ if ( empty( $settings['from_namespace'] ) && $this->getConfig()->get( 'MiserMode' ) ) {
+ $miser_ns = $params['namespace'];
+ } else {
+ $this->addWhereFld( "{$p}_from_namespace", $params['namespace'] );
+ if ( !empty( $settings['from_namespace'] ) && count( $params['namespace'] ) > 1 ) {
+ $sortby["{$p}_from_namespace"] = 'int';
+ }
+ }
+ }
+ $sortby[$bl_from] = 'int';
+
+ // Now use the $sortby to figure out the continuation
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != count( $sortby ) );
+ $where = '';
+ $i = count( $sortby ) - 1;
+ $cont_ns = 0;
+ $cont_title = '';
+ foreach ( array_reverse( $sortby, true ) as $field => $type ) {
+ $v = $cont[$i];
+ switch ( $type ) {
+ case 'ns':
+ $cont_ns = (int)$v;
+ /* fall through */
+ case 'int':
+ $v = (int)$v;
+ $this->dieContinueUsageIf( $v != $cont[$i] );
+ break;
+
+ case 'title':
+ $cont_title = $v;
+ /* fall through */
+ default:
+ $v = $db->addQuotes( $v );
+ break;
+ }
+
+ if ( $where === '' ) {
+ $where = "$field >= $v";
+ } else {
+ $where = "$field > $v OR ($field = $v AND ($where))";
+ }
+
+ $i--;
+ }
+ $this->addWhere( $where );
+ }
+
+ // Populate the rest of the query
+ $this->addTables( array( $settings['linktable'], 'page' ) );
+ $this->addWhere( "$bl_from = page_id" );
+
+ if ( $this->getModuleName() === 'redirects' ) {
+ $this->addWhere( "rd_interwiki = $emptyString OR rd_interwiki IS NULL" );
+ }
+
+ $this->addFields( array_keys( $sortby ) );
+ $this->addFields( array( 'bl_namespace' => $bl_namespace, 'bl_title' => $bl_title ) );
+ if ( is_null( $resultPageSet ) ) {
+ $fld_pageid = isset( $prop['pageid'] );
+ $fld_title = isset( $prop['title'] );
+ $fld_redirect = isset( $prop['redirect'] );
+
+ $this->addFieldsIf( 'page_id', $fld_pageid );
+ $this->addFieldsIf( array( 'page_title', 'page_namespace' ), $fld_title );
+ $this->addFieldsIf( 'page_is_redirect', $fld_redirect );
+
+ // prop=redirects
+ $fld_fragment = isset( $prop['fragment'] );
+ $this->addFieldsIf( 'rd_fragment', $fld_fragment );
+ } else {
+ $this->addFields( $resultPageSet->getPageTableFields() );
+ }
+
+ $this->addFieldsIf( 'page_namespace', $miser_ns !== null );
+
+ if ( $hasNS ) {
+ $lb = new LinkBatch( $titles );
+ $this->addWhere( $lb->constructSet( $p, $db ) );
+ } else {
+ $where = array();
+ foreach ( $titles as $t ) {
+ if ( $t->getNamespace() == $bl_namespace ) {
+ $where[] = "$bl_title = " . $db->addQuotes( $t->getDBkey() );
+ }
+ }
+ $this->addWhere( $db->makeList( $where, LIST_OR ) );
+ }
+
+ if ( $params['show'] !== null ) {
+ // prop=redirects only
+ $show = array_flip( $params['show'] );
+ if ( isset( $show['fragment'] ) && isset( $show['!fragment'] ) ||
+ isset( $show['redirect'] ) && isset( $show['!redirect'] )
+ ) {
+ $this->dieUsageMsg( 'show' );
+ }
+ $this->addWhereIf( "rd_fragment != $emptyString", isset( $show['fragment'] ) );
+ $this->addWhereIf(
+ "rd_fragment = $emptyString OR rd_fragment IS NULL",
+ isset( $show['!fragment'] )
+ );
+ $this->addWhereIf( array( 'page_is_redirect' => 1 ), isset( $show['redirect'] ) );
+ $this->addWhereIf( array( 'page_is_redirect' => 0 ), isset( $show['!redirect'] ) );
+ }
+
+ // Override any ORDER BY from above with what we calculated earlier.
+ $this->addOption( 'ORDER BY', array_keys( $sortby ) );
+
+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
+
+ $res = $this->select( __METHOD__ );
+
+ if ( is_null( $resultPageSet ) ) {
+ $count = 0;
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinue( $row, $sortby );
+ break;
+ }
+
+ if ( $miser_ns !== null && !in_array( $row->page_namespace, $miser_ns ) ) {
+ // Miser mode namespace check
+ continue;
+ }
+
+ // Get the ID of the current page
+ $id = $map[$row->bl_namespace][$row->bl_title];
+
+ $vals = array();
+ if ( $fld_pageid ) {
+ $vals['pageid'] = $row->page_id;
+ }
+ if ( $fld_title ) {
+ ApiQueryBase::addTitleInfo( $vals,
+ Title::makeTitle( $row->page_namespace, $row->page_title )
+ );
+ }
+ if ( $fld_fragment && $row->rd_fragment !== null && $row->rd_fragment !== '' ) {
+ $vals['fragment'] = $row->rd_fragment;
+ }
+ if ( $fld_redirect && $row->page_is_redirect ) {
+ $vals['redirect'] = '';
+ }
+ $fit = $this->addPageSubItem( $id, $vals );
+ if ( !$fit ) {
+ $this->setContinue( $row, $sortby );
+ break;
+ }
+ }
+ } else {
+ $titles = array();
+ $count = 0;
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinue( $row, $sortby );
+ break;
+ }
+ $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
+ }
+ $resultPageSet->populateFromTitles( $titles );
+ }
+ }
+
+ private function setContinue( $row, $sortby ) {
+ $cont = array();
+ foreach ( $sortby as $field => $v ) {
+ $cont[] = $row->$field;
+ }
+ $this->setContinueEnumParameter( 'continue', join( '|', $cont ) );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ $settings = self::$settings[$this->getModuleName()];
+
+ $ret = array(
+ 'prop' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'pageid',
+ 'title',
+ ),
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'pageid|title',
+ ),
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ );
+
+ if ( !empty( $settings['showredirects'] ) ) {
+ $ret['prop'][ApiBase::PARAM_TYPE][] = 'redirect';
+ $ret['prop'][ApiBase::PARAM_DFLT] .= '|redirect';
+ }
+ if ( isset( $settings['props'] ) ) {
+ $ret['prop'][ApiBase::PARAM_TYPE] = array_merge(
+ $ret['prop'][ApiBase::PARAM_TYPE], array_keys( $settings['props'] )
+ );
+ }
+
+ $show = array();
+ if ( !empty( $settings['showredirects'] ) ) {
+ $show[] = 'redirect';
+ $show[] = '!redirect';
+ }
+ if ( isset( $settings['show'] ) ) {
+ $show = array_merge( $show, array_keys( $settings['show'] ) );
+ }
+ if ( $show ) {
+ $ret['show'] = array(
+ ApiBase::PARAM_TYPE => $show,
+ ApiBase::PARAM_ISMULTI => true,
+ );
+ }
+
+ return $ret;
+ }
+
+ public function getParamDescription() {
+ $settings = self::$settings[$this->getModuleName()];
+ $p = $this->getModulePrefix();
+
+ $ret = array(
+ 'prop' => array(
+ 'Which properties to get:',
+ ),
+ 'show' => array(
+ 'Show only items that meet this criteria.',
+ ),
+ 'namespace' => 'Only include pages in these namespaces',
+ 'limit' => 'How many to return',
+ 'continue' => 'When more results are available, use this to continue',
+ );
+
+ if ( empty( $settings['from_namespace'] ) && $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['namespace'] = array(
+ $ret['namespace'],
+ "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
+ 'returned before continuing; in extreme cases, zero results may be returned.',
+ );
+ if ( isset( $ret['type'] ) ) {
+ $ret['namespace'][] = "Note that you can use {$p}type=subcat or {$p}type=file " .
+ "instead of {$p}namespace=14 or 6.";
+ }
+ }
+
+ $props = array(
+ 'pageid' => 'Adds the ID of page',
+ 'title' => 'Adds the title and namespace ID of the page',
+ );
+ if ( !empty( $settings['showredirects'] ) ) {
+ $props['redirect'] = 'Indicate if the page is a redirect';
+ }
+ if ( isset( $settings['props'] ) ) {
+ $props += $settings['props'];
+ }
+ foreach ( $props as $k => $v ) {
+ $ret['props'][] = sprintf( "%-9s - %s", $k, $v );
+ }
+
+ $show = array();
+ if ( !empty( $settings['showredirects'] ) ) {
+ $show += array(
+ 'redirect' => 'Only show redirects',
+ '!redirect' => 'Only show non-redirects',
+ );
+ }
+ if ( isset( $settings['show'] ) ) {
+ $show += $settings['show'];
+ }
+ foreach ( $show as $k => $v ) {
+ $ret['show'][] = sprintf( "%-9s - %s", $k, $v );
+ }
+
+ return $ret;
+ }
+
+ public function getDescription() {
+ return self::$settings[$this->getModuleName()]['description'];
+ }
+
+ public function getExamples() {
+ $settings = self::$settings[$this->getModuleName()];
+ $name = $this->getModuleName();
+ $what = $settings['what'];
+ $title = isset( $settings['exampletitle'] ) ? $settings['exampletitle'] : 'Main Page';
+ $etitle = rawurlencode( $title );
+
+ return array(
+ "api.php?action=query&prop={$name}&titles={$etitle}"
+ => "Get a list of $what [[$title]]",
+ "api.php?action=query&generator={$name}&titles={$etitle}&prop=info"
+ => "Get information about $what [[$title]]",
+ );
+ }
+
+ public function getHelpUrls() {
+ $name = $this->getModuleName();
+ $prefix = $this->getModulePrefix();
+ return "https://www.mediawiki.org/wiki/API:Properties#{$name}_.2F_{$prefix}";
+ }
+}
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 );
}
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index e3c27f5e..33b25fd9 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -32,17 +32,18 @@
class ApiQueryBlocks extends ApiQueryBase {
/**
- * @var Array
+ * @var array
*/
protected $usernames;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'bk' );
}
public function execute() {
global $wgContLang;
+ $db = $this->getDB();
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'users', 'ip' );
@@ -61,9 +62,8 @@ class ApiQueryBlocks extends ApiQueryBase {
$result = $this->getResult();
$this->addTables( 'ipblocks' );
- $this->addFields( 'ipb_auto' );
+ $this->addFields( array( 'ipb_auto', 'ipb_id' ) );
- $this->addFieldsIf( 'ipb_id', $fld_id );
$this->addFieldsIf( array( 'ipb_address', 'ipb_user' ), $fld_user || $fld_userid );
$this->addFieldsIf( 'ipb_by_text', $fld_by );
$this->addFieldsIf( 'ipb_by', $fld_byid );
@@ -72,13 +72,31 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addFieldsIf( 'ipb_reason', $fld_reason );
$this->addFieldsIf( array( 'ipb_range_start', 'ipb_range_end' ), $fld_range );
$this->addFieldsIf( array( 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock',
- 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ),
- $fld_flags );
+ 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ),
+ $fld_flags );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- $this->addTimestampWhereRange( 'ipb_timestamp', $params['dir'], $params['start'], $params['end'] );
-
- $db = $this->getDB();
+ $this->addTimestampWhereRange(
+ 'ipb_timestamp',
+ $params['dir'],
+ $params['start'],
+ $params['end']
+ );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'ipb_id', $params['dir'], null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $params['dir'] == 'newer' ? '>' : '<' );
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueId = (int)$cont[1];
+ $this->dieContinueUsageIf( $continueId != $cont[1] );
+ $this->addWhere( "ipb_timestamp $op $continueTimestamp OR " .
+ "(ipb_timestamp = $continueTimestamp AND " .
+ "ipb_id $op= $continueId)"
+ );
+ }
if ( isset( $params['ids'] ) ) {
$this->addWhereFld( 'ipb_id', $params['ids'] );
@@ -91,14 +109,14 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereFld( 'ipb_auto', 0 );
}
if ( isset( $params['ip'] ) ) {
- global $wgBlockCIDRLimit;
+ $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
if ( IP::isIPv4( $params['ip'] ) ) {
$type = 'IPv4';
- $cidrLimit = $wgBlockCIDRLimit['IPv4'];
+ $cidrLimit = $blockCIDRLimit['IPv4'];
$prefixLen = 0;
} elseif ( IP::isIPv6( $params['ip'] ) ) {
$type = 'IPv6';
- $cidrLimit = $wgBlockCIDRLimit['IPv6'];
+ $cidrLimit = $blockCIDRLimit['IPv6'];
$prefixLen = 3; // IP::toHex output is prefixed with "v6-"
} else {
$this->dieUsage( 'IP parameter is not valid', 'param_ip' );
@@ -107,7 +125,10 @@ class ApiQueryBlocks extends ApiQueryBase {
# Check range validity, if it's a CIDR
list( $ip, $range ) = IP::parseCIDR( $params['ip'] );
if ( $ip !== false && $range !== false && $range < $cidrLimit ) {
- $this->dieUsage( "$type CIDR ranges broader than /$cidrLimit are not accepted", 'cidrtoobroad' );
+ $this->dieUsage(
+ "$type CIDR ranges broader than /$cidrLimit are not accepted",
+ 'cidrtoobroad'
+ );
}
# Let IP::parseRange handle calculating $upper, instead of duplicating the logic here.
@@ -134,9 +155,9 @@ class ApiQueryBlocks extends ApiQueryBase {
/* Check for conflicting parameters. */
if ( ( isset( $show['account'] ) && isset( $show['!account'] ) )
- || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
- || ( isset( $show['range'] ) && isset( $show['!range'] ) )
- || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
+ || ( isset( $show['ip'] ) && isset( $show['!ip'] ) )
+ || ( isset( $show['range'] ) && isset( $show['!range'] ) )
+ || ( isset( $show['temp'] ) && isset( $show['!temp'] ) )
) {
$this->dieUsageMsg( 'show' );
}
@@ -145,8 +166,10 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereIf( 'ipb_user != 0', isset( $show['account'] ) );
$this->addWhereIf( 'ipb_user != 0 OR ipb_range_end > ipb_range_start', isset( $show['!ip'] ) );
$this->addWhereIf( 'ipb_user = 0 AND ipb_range_end = ipb_range_start', isset( $show['ip'] ) );
- $this->addWhereIf( 'ipb_expiry = ' . $db->addQuotes( $db->getInfinity() ), isset( $show['!temp'] ) );
- $this->addWhereIf( 'ipb_expiry != ' . $db->addQuotes( $db->getInfinity() ), isset( $show['temp'] ) );
+ $this->addWhereIf( 'ipb_expiry = ' .
+ $db->addQuotes( $db->getInfinity() ), isset( $show['!temp'] ) );
+ $this->addWhereIf( 'ipb_expiry != ' .
+ $db->addQuotes( $db->getInfinity() ), isset( $show['temp'] ) );
$this->addWhereIf( 'ipb_range_end = ipb_range_start', isset( $show['!range'] ) );
$this->addWhereIf( 'ipb_range_end > ipb_range_start', isset( $show['range'] ) );
}
@@ -166,7 +189,7 @@ class ApiQueryBlocks extends ApiQueryBase {
foreach ( $res as $row ) {
if ( ++$count > $params['limit'] ) {
// We've had enough
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
break;
}
$block = array();
@@ -224,7 +247,7 @@ class ApiQueryBlocks extends ApiQueryBase {
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $block );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
break;
}
}
@@ -303,12 +326,14 @@ class ApiQueryBlocks extends ApiQueryBase {
),
ApiBase::PARAM_ISMULTI => true
),
+ 'continue' => null,
);
}
public function getParamDescription() {
- global $wgBlockCIDRLimit;
+ $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
$p = $this->getModulePrefix();
+
return array(
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to stop enumerating at',
@@ -318,7 +343,7 @@ class ApiQueryBlocks extends ApiQueryBase {
'ip' => array(
'Get all blocks applying to this IP or CIDR range, including range blocks.',
"Cannot be used together with bkusers. CIDR ranges broader than " .
- "IPv4/{$wgBlockCIDRLimit['IPv4']} or IPv6/{$wgBlockCIDRLimit['IPv6']} " .
+ "IPv4/{$blockCIDRLimit['IPv4']} or IPv6/{$blockCIDRLimit['IPv6']} " .
"are not accepted"
),
'limit' => 'The maximum amount of blocks to list',
@@ -339,86 +364,12 @@ class ApiQueryBlocks extends ApiQueryBase {
'Show only items that meet this criteria.',
"For example, to see only indefinite blocks on IPs, set {$p}show=ip|!temp"
),
- );
- }
-
- public function getResultProperties() {
- return array(
- 'id' => array(
- 'id' => 'integer'
- ),
- 'user' => array(
- 'user' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'userid' => array(
- 'userid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'by' => array(
- 'by' => 'string'
- ),
- 'byid' => array(
- 'byid' => 'integer'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'expiry' => array(
- 'expiry' => 'timestamp'
- ),
- 'reason' => array(
- 'reason' => 'string'
- ),
- 'range' => array(
- 'rangestart' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'rangeend' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'flags' => array(
- 'automatic' => 'boolean',
- 'anononly' => 'boolean',
- 'nocreate' => 'boolean',
- 'autoblock' => 'boolean',
- 'noemail' => 'boolean',
- 'hidden' => 'boolean',
- 'allowusertalk' => 'boolean'
- )
+ 'continue' => 'When more results are available, use this to continue',
);
}
public function getDescription() {
- return 'List all blocked users and IP addresses';
- }
-
- public function getPossibleErrors() {
- global $wgBlockCIDRLimit;
- return array_merge( parent::getPossibleErrors(),
- $this->getRequireOnlyOneParameterErrorMessages( array( 'users', 'ip' ) ),
- array(
- array(
- 'code' => 'cidrtoobroad',
- 'info' => "IPv4 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv4']} are not accepted"
- ),
- array(
- 'code' => 'cidrtoobroad',
- 'info' => "IPv6 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv6']} are not accepted"
- ),
- array( 'code' => 'param_ip', 'info' => 'IP parameter is not valid' ),
- array( 'code' => 'param_user', 'info' => 'User parameter may not be empty' ),
- array( 'code' => 'param_user', 'info' => 'User name user is not valid' ),
- array( 'show' ),
- )
- );
+ return 'List all blocked users and IP addresses.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 5d714f57..1926dd09 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -31,7 +31,7 @@
*/
class ApiQueryCategories extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'cl' );
}
@@ -48,7 +48,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
@@ -98,8 +98,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
if ( isset( $show['hidden'] ) && isset( $show['!hidden'] ) ) {
$this->dieUsageMsg( 'show' );
}
- if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) )
- {
+ if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) ) {
$this->addOption( 'STRAIGHT_JOIN' );
$this->addTables( array( 'page', 'page_props' ) );
$this->addFieldsIf( 'pp_propname', isset( $prop['hidden'] ) );
@@ -126,9 +125,9 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', 'cl_to' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'cl_from' . $sort,
- 'cl_to' . $sort
- ));
+ 'cl_from' . $sort,
+ 'cl_to' . $sort
+ ) );
}
$res = $this->select( __METHOD__ );
@@ -221,51 +220,30 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
return array(
'prop' => array(
'Which additional properties to get for each category',
- ' sortkey - Adds the sortkey (hexadecimal string) and sortkey prefix (human-readable part) for the category',
+ ' sortkey - Adds the sortkey (hexadecimal string) and sortkey prefix',
+ ' (human-readable part) for the category',
' timestamp - Adds timestamp of when the category was added',
' hidden - Tags categories that are hidden with __HIDDENCAT__',
),
'limit' => 'How many categories to return',
'show' => 'Which kind of categories to show',
'continue' => 'When more results are available, use this to continue',
- 'categories' => 'Only list these categories. Useful for checking whether a certain page is in a certain category',
+ 'categories' => 'Only list these categories. Useful for checking ' .
+ 'whether a certain page is in a certain category',
'dir' => 'The direction in which to list',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'sortkey' => array(
- 'sortkey' => 'string',
- 'sortkeyprefix' => 'string'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'hidden' => array(
- 'hidden' => 'boolean'
- )
- );
- }
-
public function getDescription() {
- return 'List all categories the page(s) belong to';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'show' ),
- ) );
+ return 'List all categories the page(s) belong to.';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=categories&titles=Albert%20Einstein' => 'Get a list of categories [[Albert Einstein]] belongs to',
- 'api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info' => 'Get information about all categories used in the [[Albert Einstein]]',
+ 'api.php?action=query&prop=categories&titles=Albert%20Einstein'
+ => 'Get a list of categories [[Albert Einstein]] belongs to',
+ 'api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info'
+ => 'Get information about all categories used in the [[Albert Einstein]]',
);
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index a889272e..6e9f33c1 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -32,7 +32,7 @@
*/
class ApiQueryCategoryInfo extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'ci' );
}
@@ -45,7 +45,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
$categories = $alltitles[NS_CATEGORY];
$titles = $this->getPageSet()->getGoodTitles() +
- $this->getPageSet()->getMissingTitles();
+ $this->getPageSet()->getMissingTitles();
$cattitles = array();
foreach ( $categories as $c ) {
/** @var $t Title */
@@ -63,7 +63,13 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
'pp_propname' => 'hiddencat' ) ),
) );
- $this->addFields( array( 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'cat_hidden' => 'pp_propname' ) );
+ $this->addFields( array(
+ 'cat_title',
+ 'cat_pages',
+ 'cat_subcats',
+ 'cat_files',
+ 'cat_hidden' => 'pp_propname'
+ ) );
$this->addWhere( array( 'cat_title' => $cattitles ) );
if ( !is_null( $params['continue'] ) ) {
@@ -108,36 +114,8 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
);
}
- public function getResultProperties() {
- return array(
- ApiBase::PROP_LIST => false,
- '' => array(
- 'size' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'pages' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'files' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'subcats' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => false
- ),
- 'hidden' => array(
- ApiBase::PROP_TYPE => 'boolean',
- ApiBase::PROP_NULLABLE => false
- )
- )
- );
- }
-
public function getDescription() {
- return 'Returns information about the given categories';
+ return 'Returns information about the given categories.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index 704d108a..a88a9cb1 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -31,7 +31,7 @@
*/
class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'cm' );
}
@@ -48,7 +48,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -86,9 +86,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
// Scanning large datasets for rare categories sucks, and I already told
// how to have efficient subcategory access :-) ~~~~ (oh well, domas)
- global $wgMiserMode;
$miser_ns = array();
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$miser_ns = $params['namespace'];
} else {
$this->addWhereFld( 'page_namespace', $params['namespace'] );
@@ -101,6 +100,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$dir,
$params['start'],
$params['end'] );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'cl_from', $dir, null, null );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $op = ( $dir === 'newer' ? '>' : '<' );
+ $db = $this->getDB();
+ $continueTimestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $continueFrom = (int)$cont[1];
+ $this->dieContinueUsageIf( $continueFrom != $cont[1] );
+ $this->addWhere( "cl_timestamp $op $continueTimestamp OR " .
+ "(cl_timestamp = $continueTimestamp AND " .
+ "cl_from $op= $continueFrom)"
+ );
+ }
$this->addOption( 'USE INDEX', 'cl_timestamp' );
} else {
@@ -125,12 +140,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->addWhereRange( 'cl_sortkey', $dir, null, null );
$this->addWhereRange( 'cl_from', $dir, null, null );
} else {
- $startsortkey = $params['startsortkeyprefix'] !== null ?
- Collation::singleton()->getSortkey( $params['startsortkeyprefix'] ) :
- $params['startsortkey'];
- $endsortkey = $params['endsortkeyprefix'] !== null ?
- Collation::singleton()->getSortkey( $params['endsortkeyprefix'] ) :
- $params['endsortkey'];
+ if ( $params['startsortkeyprefix'] !== null ) {
+ $startsortkey = Collation::singleton()->getSortkey( $params['startsortkeyprefix'] );
+ } elseif ( $params['starthexsortkey'] !== null ) {
+ $startsortkey = pack( 'H*', $params['starthexsortkey'] );
+ } else {
+ $this->logFeatureUsage( 'list=categorymembers&cmstartsortkey' );
+ $startsortkey = $params['startsortkey'];
+ }
+ if ( $params['endsortkeyprefix'] !== null ) {
+ $endsortkey = Collation::singleton()->getSortkey( $params['endsortkeyprefix'] );
+ } elseif ( $params['endhexsortkey'] !== null ) {
+ $endsortkey = pack( 'H*', $params['endhexsortkey'] );
+ } else {
+ $this->logFeatureUsage( 'list=categorymembers&cmendsortkey' );
+ $endsortkey = $params['endsortkey'];
+ }
// The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them
$this->addWhereRange( 'cl_sortkey',
@@ -180,11 +205,13 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$result = $this->getResult();
$count = 0;
foreach ( $rows as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // TODO: Security issue - if the user has no right to view next title, it will still be shown
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ // @todo Security issue - if the user has no right to view next
+ // title, it will still be shown
if ( $params['sort'] == 'timestamp' ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->cl_timestamp|$row->cl_from" );
} else {
$sortkey = bin2hex( $row->cl_sortkey );
$this->setContinueEnumParameter( 'continue',
@@ -224,10 +251,10 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp );
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
- null, $vals );
+ null, $vals );
if ( !$fit ) {
if ( $params['sort'] == 'timestamp' ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->cl_timestamp|$row->cl_from" );
} else {
$sortkey = bin2hex( $row->cl_sortkey );
$this->setContinueEnumParameter( 'continue',
@@ -313,25 +340,32 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'end' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
- 'startsortkey' => null,
- 'endsortkey' => null,
+ 'starthexsortkey' => null,
+ 'endhexsortkey' => null,
'startsortkeyprefix' => null,
'endsortkeyprefix' => null,
+ 'startsortkey' => array(
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'endsortkey' => array(
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
);
}
public function getParamDescription() {
- global $wgMiserMode;
$p = $this->getModulePrefix();
$desc = array(
- 'title' => "Which category to enumerate (required). Must include Category: prefix. Cannot be used together with {$p}pageid",
+ 'title' => "Which category to enumerate (required). Must include " .
+ "'Category:' prefix. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the category to enumerate. Cannot be used together with {$p}title",
'prop' => array(
'What pieces of information to include',
' ids - Adds the page ID',
' title - Adds the title and namespace ID of the page',
' sortkey - Adds the sortkey used for sorting in the category (hexadecimal string)',
- ' sortkeyprefix - Adds the sortkey prefix used for sorting in the category (human-readable part of the sortkey)',
+ ' sortkeyprefix - Adds the sortkey prefix used for sorting in the ' .
+ 'category (human-readable part of the sortkey)',
' type - Adds the type that the page has been categorised as (page, subcat or file)',
' timestamp - Adds the timestamp of when the page was included',
),
@@ -341,15 +375,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'dir' => 'In which direction to sort',
'start' => "Timestamp to start listing from. Can only be used with {$p}sort=timestamp",
'end' => "Timestamp to end listing at. Can only be used with {$p}sort=timestamp",
- 'startsortkey' => "Sortkey to start listing from. Must be given in binary format. Can only be used with {$p}sort=sortkey",
- 'endsortkey' => "Sortkey to end listing at. Must be given in binary format. Can only be used with {$p}sort=sortkey",
- 'startsortkeyprefix' => "Sortkey prefix to start listing from. Can only be used with {$p}sort=sortkey. Overrides {$p}startsortkey",
- 'endsortkeyprefix' => "Sortkey prefix to end listing BEFORE (not at, if this value occurs it will not be included!). Can only be used with {$p}sort=sortkey. Overrides {$p}endsortkey",
+ 'starthexsortkey' => "Sortkey to start listing from, as returned by prop=sortkey. " .
+ "Can only be used with {$p}sort=sortkey",
+ 'endhexsortkey' => "Sortkey to end listing from, as returned by prop=sortkey. " .
+ "Can only be used with {$p}sort=sortkey",
+ 'startsortkeyprefix' => "Sortkey prefix to start listing from. Can " .
+ "only be used with {$p}sort=sortkey. Overrides {$p}starthexsortkey",
+ 'endsortkeyprefix' => "Sortkey prefix to end listing BEFORE (not at, " .
+ "if this value occurs it will not be included!). Can only be used with " .
+ "{$p}sort=sortkey. Overrides {$p}endhexsortkey",
+ 'startsortkey' => "Use starthexsortkey instead",
+ 'endsortkey' => "Use endhexsortkey instead",
'continue' => 'For large categories, give the value returned from previous query',
'limit' => 'The maximum number of pages to return.',
);
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$desc['namespace'] = array(
$desc['namespace'],
"NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
@@ -357,56 +398,20 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
"Note that you can use {$p}type=subcat or {$p}type=file instead of {$p}namespace=14 or 6.",
);
}
- return $desc;
- }
- public function getResultProperties() {
- return array(
- 'ids' => array(
- 'pageid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'sortkey' => array(
- 'sortkey' => 'string'
- ),
- 'sortkeyprefix' => array(
- 'sortkeyprefix' => 'string'
- ),
- 'type' => array(
- 'type' => array(
- ApiBase::PROP_TYPE => array(
- 'page',
- 'subcat',
- 'file'
- )
- )
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- )
- );
+ return $desc;
}
public function getDescription() {
- return 'List all pages in a given category';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(),
- $this->getTitleOrPageIdErrorMessage(),
- array(
- array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ),
- )
- );
+ return 'List all pages in a given category.';
}
public function getExamples() {
return array(
- 'api.php?action=query&list=categorymembers&cmtitle=Category:Physics' => 'Get first 10 pages in [[Category:Physics]]',
- 'api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info' => 'Get page info about first 10 pages in [[Category:Physics]]',
+ 'api.php?action=query&list=categorymembers&cmtitle=Category:Physics'
+ => 'Get first 10 pages in [[Category:Physics]]',
+ 'api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info'
+ => 'Get page info about first 10 pages in [[Category:Physics]]',
);
}
diff --git a/includes/api/ApiQueryContributors.php b/includes/api/ApiQueryContributors.php
new file mode 100644
index 00000000..55ea4702
--- /dev/null
+++ b/includes/api/ApiQueryContributors.php
@@ -0,0 +1,282 @@
+<?php
+/**
+ * Query the list of contributors to a page
+ *
+ * Created on Nov 14, 2013
+ *
+ * Copyright © 2013 Brad Jorsch
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.23
+ */
+
+/**
+ * A query module to show contributors to a page
+ *
+ * @ingroup API
+ * @since 1.23
+ */
+class ApiQueryContributors extends ApiQueryBase {
+ /** We don't want to process too many pages at once (it hits cold
+ * database pages too heavily), so only do the first MAX_PAGES input pages
+ * in each API call (leaving the rest for continuation).
+ */
+ const MAX_PAGES = 100;
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ // "pc" is short for "page contributors", "co" was already taken by the
+ // GeoData extension's prop=coordinates.
+ parent::__construct( $query, $moduleName, 'pc' );
+ }
+
+ public function execute() {
+ $db = $this->getDB();
+ $params = $this->extractRequestParams();
+ $this->requireMaxOneParameter( $params, 'group', 'excludegroup', 'rights', 'excluderights' );
+
+ // Only operate on existing pages
+ $pages = array_keys( $this->getPageSet()->getGoodTitles() );
+
+ // Filter out already-processed pages
+ if ( $params['continue'] !== null ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $cont_page = (int)$cont[0];
+ $pages = array_filter( $pages, function ( $v ) use ( $cont_page ) {
+ return $v >= $cont_page;
+ } );
+ }
+ if ( !count( $pages ) ) {
+ // Nothing to do
+ return;
+ }
+
+ // Apply MAX_PAGES, leaving any over the limit for a continue.
+ sort( $pages );
+ $continuePages = null;
+ if ( count( $pages ) > self::MAX_PAGES ) {
+ $continuePages = $pages[self::MAX_PAGES] . '|0';
+ $pages = array_slice( $pages, 0, self::MAX_PAGES );
+ }
+
+ $result = $this->getResult();
+
+ // First, count anons
+ $this->addTables( 'revision' );
+ $this->addFields( array(
+ 'page' => 'rev_page',
+ 'anons' => 'COUNT(DISTINCT rev_user_text)',
+ ) );
+ $this->addWhereFld( 'rev_page', $pages );
+ $this->addWhere( 'rev_user = 0' );
+ $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ $this->addOption( 'GROUP BY', 'rev_page' );
+ $res = $this->select( __METHOD__ );
+ foreach ( $res as $row ) {
+ $fit = $result->addValue( array( 'query', 'pages', $row->page ),
+ 'anoncontributors', $row->anons
+ );
+ if ( !$fit ) {
+ // This not fitting isn't reasonable, so it probably means that
+ // some other module used up all the space. Just set a dummy
+ // continue and hope it works next time.
+ $this->setContinueEnumParameter( 'continue',
+ $params['continue'] !== null ? $params['continue'] : '0|0'
+ );
+
+ return;
+ }
+ }
+
+ // Next, add logged-in users
+ $this->resetQueryParams();
+ $this->addTables( 'revision' );
+ $this->addFields( array(
+ 'page' => 'rev_page',
+ 'user' => 'rev_user',
+ 'username' => 'MAX(rev_user_text)', // Non-MySQL databases don't like partial group-by
+ ) );
+ $this->addWhereFld( 'rev_page', $pages );
+ $this->addWhere( 'rev_user != 0' );
+ $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
+ $this->addOption( 'GROUP BY', 'rev_page, rev_user' );
+ $this->addOption( 'LIMIT', $params['limit'] + 1 );
+
+ // Force a sort order to ensure that properties are grouped by page
+ // But only if pp_page is not constant in the WHERE clause.
+ if ( count( $pages ) > 1 ) {
+ $this->addOption( 'ORDER BY', 'rev_page, rev_user' );
+ } else {
+ $this->addOption( 'ORDER BY', 'rev_user' );
+ }
+
+ $limitGroups = array();
+ if ( $params['group'] ) {
+ $excludeGroups = false;
+ $limitGroups = $params['group'];
+ } elseif ( $params['excludegroup'] ) {
+ $excludeGroups = true;
+ $limitGroups = $params['excludegroup'];
+ } elseif ( $params['rights'] ) {
+ $excludeGroups = false;
+ foreach ( $params['rights'] as $r ) {
+ $limitGroups = array_merge( $limitGroups, User::getGroupsWithPermission( $r ) );
+ }
+
+ // If no group has the rights requested, no need to query
+ if ( !$limitGroups ) {
+ if ( $continuePages !== null ) {
+ // But we still need to continue for the next page's worth
+ // of anoncontributors
+ $this->setContinueEnumParameter( 'continue', $continuePages );
+ }
+
+ return;
+ }
+ } elseif ( $params['excluderights'] ) {
+ $excludeGroups = true;
+ foreach ( $params['excluderights'] as $r ) {
+ $limitGroups = array_merge( $limitGroups, User::getGroupsWithPermission( $r ) );
+ }
+ }
+
+ if ( $limitGroups ) {
+ $limitGroups = array_unique( $limitGroups );
+ $this->addTables( 'user_groups' );
+ $this->addJoinConds( array( 'user_groups' => array(
+ $excludeGroups ? 'LEFT OUTER JOIN' : 'INNER JOIN',
+ array( 'ug_user=rev_user', 'ug_group' => $limitGroups )
+ ) ) );
+ $this->addWhereIf( 'ug_user IS NULL', $excludeGroups );
+ }
+
+ if ( $params['continue'] !== null ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $cont_page = (int)$cont[0];
+ $cont_user = (int)$cont[1];
+ $this->addWhere(
+ "rev_page > $cont_page OR " .
+ "(rev_page = $cont_page AND " .
+ "rev_user >= $cont_user)"
+ );
+ }
+
+ $res = $this->select( __METHOD__ );
+ $count = 0;
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that
+ // there are additional pages to be had. Stop here...
+ $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->user );
+
+ return;
+ }
+
+ $fit = $this->addPageSubItem( $row->page,
+ array( 'userid' => $row->user, 'name' => $row->username ),
+ 'user'
+ );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->user );
+
+ return;
+ }
+ }
+
+ if ( $continuePages !== null ) {
+ $this->setContinueEnumParameter( 'continue', $continuePages );
+ }
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ $userGroups = User::getAllGroups();
+ $userRights = User::getAllRights();
+
+ return array(
+ 'group' => array(
+ ApiBase::PARAM_TYPE => $userGroups,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'excludegroup' => array(
+ ApiBase::PARAM_TYPE => $userGroups,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'rights' => array(
+ ApiBase::PARAM_TYPE => $userRights,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'excluderights' => array(
+ ApiBase::PARAM_TYPE => $userRights,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'group' => array(
+ 'Limit users to given group name(s)',
+ 'Does not include implicit or auto-promoted groups like *, user, or autoconfirmed'
+ ),
+ 'excludegroup' => array(
+ 'Exclude users in given group name(s)',
+ 'Does not include implicit or auto-promoted groups like *, user, or autoconfirmed'
+ ),
+ 'rights' => array(
+ 'Limit users to those having given right(s)',
+ 'Does not include rights granted by implicit or auto-promoted groups ' .
+ 'like *, user, or autoconfirmed'
+ ),
+ 'excluderights' => array(
+ 'Limit users to those not having given right(s)',
+ 'Does not include rights granted by implicit or auto-promoted groups ' .
+ 'like *, user, or autoconfirmed'
+ ),
+ 'limit' => 'How many contributors to return',
+ 'continue' => 'When more results are available, use this to continue',
+ );
+ }
+
+ public function getDescription() {
+ return 'Get the list of logged-in contributors and ' .
+ 'the count of anonymous contributors to a page.';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&prop=contributors&titles=Main_Page',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#contributors_.2F_pc';
+ }
+}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index 82733133..9042696b 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -31,7 +31,7 @@
*/
class ApiQueryDeletedrevs extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'dr' );
}
@@ -39,7 +39,10 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$user = $this->getUser();
// Before doing anything at all, let's check permissions
if ( !$user->isAllowed( 'deletedhistory' ) ) {
- $this->dieUsage( 'You don\'t have permission to view deleted revision information', 'permissiondenied' );
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision information',
+ 'permissiondenied'
+ );
}
$db = $this->getDB();
@@ -56,12 +59,25 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$fld_sha1 = isset( $prop['sha1'] );
$fld_content = isset( $prop['content'] );
$fld_token = isset( $prop['token'] );
+ $fld_tags = isset( $prop['tags'] );
+
+ if ( isset( $prop['token'] ) ) {
+ $p = $this->getModulePrefix();
+ $this->setWarning(
+ "{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead."
+ );
+ }
// If we're in JSON callback mode, no tokens can be obtained
if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
$fld_token = false;
}
+ // If user can't undelete, no tokens
+ if ( !$user->isAllowed( 'undelete' ) ) {
+ $fld_token = false;
+ }
+
$result = $this->getResult();
$pageSet = $this->getPageSet();
$titles = $pageSet->getTitles();
@@ -97,8 +113,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
$this->addTables( 'archive' );
- $this->addWhere( 'ar_deleted = 0' );
- $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp' ) );
+ $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_deleted', 'ar_id' ) );
$this->addFieldsIf( 'ar_parent_id', $fld_parentid );
$this->addFieldsIf( 'ar_rev_id', $fld_revid );
@@ -109,14 +124,41 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addFieldsIf( 'ar_len', $fld_len );
$this->addFieldsIf( 'ar_sha1', $fld_sha1 );
+ if ( $fld_tags ) {
+ $this->addTables( 'tag_summary' );
+ $this->addJoinConds(
+ array( 'tag_summary' => array( 'LEFT JOIN', array( 'ar_rev_id=ts_rev_id' ) ) )
+ );
+ $this->addFields( 'ts_tags' );
+ }
+
+ if ( !is_null( $params['tag'] ) ) {
+ $this->addTables( 'change_tag' );
+ $this->addJoinConds(
+ array( 'change_tag' => array( 'INNER JOIN', array( 'ar_rev_id=ct_rev_id' ) ) )
+ );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
+ }
+
if ( $fld_content ) {
+ // Modern MediaWiki has the content for deleted revs in the 'text'
+ // table using fields old_text and old_flags. But revisions deleted
+ // pre-1.5 store the content in the 'archive' table directly using
+ // fields ar_text and ar_flags, and no corresponding 'text' row. So
+ // we have to LEFT JOIN and fetch all four fields, plus ar_text_id
+ // to be able to tell the difference.
$this->addTables( 'text' );
- $this->addFields( array( 'ar_text', 'ar_text_id', 'old_text', 'old_flags' ) );
- $this->addWhere( 'ar_text_id = old_id' );
+ $this->addJoinConds(
+ array( 'text' => array( 'LEFT JOIN', array( 'ar_text_id=old_id' ) ) )
+ );
+ $this->addFields( array( 'ar_text', 'ar_flags', 'ar_text_id', 'old_text', 'old_flags' ) );
// This also means stricter restrictions
- if ( !$user->isAllowed( 'undelete' ) ) {
- $this->dieUsage( 'You don\'t have permission to view deleted revision content', 'permissiondenied' );
+ if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision content',
+ 'permissiondenied'
+ );
}
}
// Check limits
@@ -147,12 +189,18 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
} elseif ( $mode == 'all' ) {
$this->addWhereFld( 'ar_namespace', $params['namespace'] );
- $from = is_null( $params['from'] ) ? null : $this->titleToKey( $params['from'] );
- $to = is_null( $params['to'] ) ? null : $this->titleToKey( $params['to'] );
+ $from = $params['from'] === null
+ ? null
+ : $this->titlePartToKey( $params['from'], $params['namespace'] );
+ $to = $params['to'] === null
+ ? null
+ : $this->titlePartToKey( $params['to'], $params['namespace'] );
$this->addWhereRange( 'ar_title', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'ar_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'ar_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
+ $db->anyString() ) );
}
}
@@ -163,32 +211,67 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$db->addQuotes( $params['excludeuser'] ) );
}
- if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) ) {
+ if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
+ // Paranoia: avoid brute force searches (bug 17342)
+ // (shouldn't be able to get here without 'deletedhistory', but
+ // check it again just in case)
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $bitmask = Revision::DELETED_USER;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
+ }
+ }
+
+ if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- $this->dieContinueUsageIf( count( $cont ) != 3 );
- $ns = intval( $cont[0] );
- $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
- $title = $db->addQuotes( $cont[1] );
- $ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
$op = ( $dir == 'newer' ? '>' : '<' );
- $this->addWhere( "ar_namespace $op $ns OR " .
+ if ( $mode == 'all' || $mode == 'revs' ) {
+ $this->dieContinueUsageIf( count( $cont ) != 4 );
+ $ns = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
+ $title = $db->addQuotes( $cont[1] );
+ $ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
+ $ar_id = (int)$cont[3];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[3] );
+ $this->addWhere( "ar_namespace $op $ns OR " .
"(ar_namespace = $ns AND " .
"(ar_title $op $title OR " .
"(ar_title = $title AND " .
- "ar_timestamp $op= $ts)))" );
+ "(ar_timestamp $op $ts OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)))))" );
+ } else {
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $ts = $db->addQuotes( $db->timestamp( $cont[0] ) );
+ $ar_id = (int)$cont[1];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[1] );
+ $this->addWhere( "ar_timestamp $op $ts OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)" );
+ }
}
$this->addOption( 'LIMIT', $limit + 1 );
- $this->addOption( 'USE INDEX', array( 'archive' => ( $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp' ) ) );
+ $this->addOption(
+ 'USE INDEX',
+ array( 'archive' => ( $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp' ) )
+ );
if ( $mode == 'all' ) {
if ( $params['unique'] ) {
+ // @todo Does this work on non-MySQL?
$this->addOption( 'GROUP BY', 'ar_title' );
} else {
$sort = ( $dir == 'newer' ? '' : ' DESC' );
$this->addOption( 'ORDER BY', array(
'ar_title' . $sort,
- 'ar_timestamp' . $sort
- ));
+ 'ar_timestamp' . $sort,
+ 'ar_id' . $sort,
+ ) );
}
} else {
if ( $mode == 'revs' ) {
@@ -197,6 +280,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addWhereRange( 'ar_title', $dir, null, null );
}
$this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
+ // Include in ORDER BY for uniqueness
+ $this->addWhereRange( 'ar_id', $dir, null, null );
}
$res = $this->select( __METHOD__ );
$pageMap = array(); // Maps ns&title to (fake) pageid
@@ -206,15 +291,18 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( ++$count > $limit ) {
// We've had enough
if ( $mode == 'all' || $mode == 'revs' ) {
- $this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
- $row->ar_title . '|' . $row->ar_timestamp );
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
}
break;
}
$rev = array();
+ $anyHidden = false;
+
$rev['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ar_timestamp );
if ( $fld_revid ) {
$rev['revid'] = intval( $row->ar_rev_id );
@@ -222,21 +310,37 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_parentid && !is_null( $row->ar_parent_id ) ) {
$rev['parentid'] = intval( $row->ar_parent_id );
}
- if ( $fld_user ) {
- $rev['user'] = $row->ar_user_text;
- }
- if ( $fld_userid ) {
- $rev['userid'] = $row->ar_user;
- }
- if ( $fld_comment ) {
- $rev['comment'] = $row->ar_comment;
+ if ( $fld_user || $fld_userid ) {
+ if ( $row->ar_deleted & Revision::DELETED_USER ) {
+ $rev['userhidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_USER, $user ) ) {
+ if ( $fld_user ) {
+ $rev['user'] = $row->ar_user_text;
+ }
+ if ( $fld_userid ) {
+ $rev['userid'] = $row->ar_user;
+ }
+ }
}
- $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
-
- if ( $fld_parsedcomment ) {
- $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
+ if ( $fld_comment || $fld_parsedcomment ) {
+ if ( $row->ar_deleted & Revision::DELETED_COMMENT ) {
+ $rev['commenthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_COMMENT, $user ) ) {
+ if ( $fld_comment ) {
+ $rev['comment'] = $row->ar_comment;
+ }
+ if ( $fld_parsedcomment ) {
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
+ }
+ }
}
+
if ( $fld_minor && $row->ar_minor_edit == 1 ) {
$rev['minor'] = '';
}
@@ -244,14 +348,45 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$rev['len'] = $row->ar_len;
}
if ( $fld_sha1 ) {
- if ( $row->ar_sha1 != '' ) {
- $rev['sha1'] = wfBaseConvert( $row->ar_sha1, 36, 16, 40 );
- } else {
- $rev['sha1'] = '';
+ if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
+ $rev['sha1hidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
+ if ( $row->ar_sha1 != '' ) {
+ $rev['sha1'] = wfBaseConvert( $row->ar_sha1, 36, 16, 40 );
+ } else {
+ $rev['sha1'] = '';
+ }
}
}
if ( $fld_content ) {
- ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
+ $rev['texthidden'] = '';
+ $anyHidden = true;
+ }
+ if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
+ if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
+ // Pre-1.5 ar_text row (if condition from Revision::newFromArchiveRow)
+ ApiResult::setContent( $rev, Revision::getRevisionText( $row, 'ar_' ) );
+ } else {
+ ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ }
+ }
+ }
+
+ if ( $fld_tags ) {
+ if ( $row->ts_tags ) {
+ $tags = explode( ',', $row->ts_tags );
+ $this->getResult()->setIndexedTagName( $tags, 'tag' );
+ $rev['tags'] = $tags;
+ } else {
+ $rev['tags'] = array();
+ }
+ }
+
+ if ( $anyHidden && ( $row->ar_deleted & Revision::DELETED_RESTRICTED ) ) {
+ $rev['suppressed'] = '';
}
if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
@@ -259,6 +394,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
$a['revisions'] = array( $rev );
$result->setIndexedTagName( $a['revisions'], 'rev' );
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
ApiQueryBase::addTitleInfo( $a, $title );
if ( $fld_token ) {
$a['token'] = $token;
@@ -272,10 +408,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
if ( !$fit ) {
if ( $mode == 'all' || $mode == 'revs' ) {
- $this->setContinueEnumParameter( 'continue', intval( $row->ar_namespace ) . '|' .
- $row->ar_title . '|' . $row->ar_timestamp );
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
} else {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->ar_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
}
break;
}
@@ -303,6 +440,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'prefix' => null,
'continue' => null,
'unique' => false,
+ 'tag' => null,
'user' => array(
ApiBase::PARAM_TYPE => 'user'
),
@@ -333,7 +471,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'len',
'sha1',
'content',
- 'token'
+ 'token',
+ 'tags'
),
ApiBase::PARAM_ISMULTI => true
),
@@ -361,57 +500,37 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
' len - Adds the length (bytes) of the revision',
' sha1 - Adds the SHA-1 (base 16) of the revision',
' content - Adds the content of the revision',
- ' token - Gives the edit token',
+ ' token - DEPRECATED! Gives the edit token',
+ ' tags - Tags for the revision',
),
'namespace' => 'Only list pages in this namespace (3)',
'user' => 'Only list revisions by this user',
'excludeuser' => 'Don\'t list revisions by this user',
- 'continue' => 'When more results are available, use this to continue (1, 3)',
+ 'continue' => 'When more results are available, use this to continue',
'unique' => 'List only one revision for each page (3)',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'token' => array(
- 'token' => 'string'
- )
+ 'tag' => 'Only list revisions tagged with this tag',
);
}
public function getDescription() {
$p = $this->getModulePrefix();
+
return array(
'List deleted revisions.',
'Operates in three modes:',
- ' 1) List deleted revisions for the given title(s), sorted by timestamp',
- ' 2) List deleted contributions for the given user, sorted by timestamp (no titles specified)',
- " 3) List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, {$p}user not set)",
+ ' 1) List deleted revisions for the given title(s), sorted by timestamp.',
+ ' 2) List deleted contributions for the given user, sorted by timestamp (no titles specified).',
+ ' 3) List all deleted revisions in the given namespace, sorted by title and timestamp',
+ " (no titles specified, {$p}user not set).",
'Certain parameters only apply to some modes and are ignored in others.',
- 'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3',
+ 'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3.',
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision information' ),
- array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision content' ),
- array( 'code' => 'badparams', 'info' => "The 'from' parameter cannot be used in modes 1 or 2" ),
- array( 'code' => 'badparams', 'info' => "The 'to' parameter cannot be used in modes 1 or 2" ),
- array( 'code' => 'badparams', 'info' => "The 'prefix' parameter cannot be used in modes 1 or 2" ),
- array( 'code' => 'badparams', 'info' => "The 'start' parameter cannot be used in mode 3" ),
- array( 'code' => 'badparams', 'info' => "The 'end' parameter cannot be used in mode 3" ),
- ) );
- }
-
public function getExamples() {
return array(
- 'api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content'
+ 'api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&' .
+ 'drprop=user|comment|content'
=> 'List the last deleted revisions of Main Page and Talk:Main Page, with content (mode 1)',
'api.php?action=query&list=deletedrevs&druser=Bob&drlimit=50'
=> 'List the last 50 deleted contributions by Bob (mode 2)',
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index 0311fa7f..6d836cd5 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -31,7 +31,7 @@
*/
class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'df' );
}
@@ -95,7 +95,8 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$sha1s[$file->getName()] = $file->getSha1();
}
- // find all files with the hashes, result format is: array( hash => array( dup1, dup2 ), hash1 => ... )
+ // find all files with the hashes, result format is:
+ // array( hash => array( dup1, dup2 ), hash1 => ... )
$filesToFindBySha1s = array_unique( array_values( $sha1s ) );
if ( $params['localonly'] ) {
$filesBySha1s = RepoGroup::singleton()->getLocalRepo()->findBySha1s( $filesToFindBySha1s );
@@ -187,19 +188,8 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'name' => 'string',
- 'user' => 'string',
- 'timestamp' => 'timestamp',
- 'shared' => 'boolean',
- )
- );
- }
-
public function getDescription() {
- return 'List all files that are duplicates of the given file(s) based on hash values';
+ return 'List all files that are duplicates of the given file(s) based on hash values.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index 456e87ba..faabb920 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -29,7 +29,7 @@
*/
class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'eu' );
}
@@ -46,7 +46,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
private function run( $resultPageSet = null ) {
@@ -59,9 +59,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$this->addOption( 'USE INDEX', 'el_index' );
$this->addWhere( 'page_id=el_from' );
- global $wgMiserMode;
$miser_ns = array();
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$miser_ns = $params['namespace'];
} else {
$this->addWhereFld( 'page_namespace', $params['namespace'] );
@@ -101,8 +100,9 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$result = $this->getResult();
$count = 0;
foreach ( $res as $row ) {
- if ( ++ $count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
+ if ( ++$count > $limit ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
$this->setContinueEnumParameter( 'offset', $offset + $limit );
break;
}
@@ -140,7 +140,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ),
- $this->getModulePrefix() );
+ $this->getModulePrefix() );
}
}
@@ -186,6 +186,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$protocols[] = substr( $p, 0, strpos( $p, ':' ) );
}
}
+
return $protocols;
}
@@ -207,7 +208,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
public function getParamDescription() {
- global $wgMiserMode;
$p = $this->getModulePrefix();
$desc = array(
'prop' => array(
@@ -221,13 +221,14 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
"Protocol of the URL. If empty and {$p}query set, the protocol is http.",
"Leave both this and {$p}query empty to list all external links"
),
- 'query' => 'Search string without protocol. See [[Special:LinkSearch]]. Leave empty to list all external links',
+ 'query' => 'Search string without protocol. See [[Special:LinkSearch]]. ' .
+ 'Leave empty to list all external links',
'namespace' => 'The page namespace(s) to enumerate.',
'limit' => 'How many pages to return.',
'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
);
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
$desc['namespace'] = array(
$desc['namespace'],
"NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
@@ -238,29 +239,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
return $desc;
}
- public function getResultProperties() {
- return array(
- 'ids' => array(
- 'pageid' => 'integer'
- ),
- 'title' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- ),
- 'url' => array(
- 'url' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Enumerate pages that contain a given URL';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'bad_query', 'info' => 'Invalid query' ),
- ) );
+ return 'Enumerate pages that contain a given URL.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index 583ef697..95666354 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryExternalLinks extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'el' );
}
@@ -127,6 +127,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'limit' => 'How many links to return',
'offset' => 'When more results are available, use this to continue',
@@ -134,32 +135,20 @@ class ApiQueryExternalLinks extends ApiQueryBase {
"Protocol of the URL. If empty and {$p}query set, the protocol is http.",
"Leave both this and {$p}query empty to list all external links"
),
- 'query' => 'Search string without protocol. Useful for checking whether a certain page contains a certain external url',
+ 'query' => 'Search string without protocol. Useful for checking ' .
+ 'whether a certain page contains a certain external url',
'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- '*' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Returns all external URLs (not interwikis) from the given page(s)';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'bad_query', 'info' => 'Invalid query' ),
- ) );
+ return 'Returns all external URLs (not interwikis) from the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=extlinks&titles=Main%20Page' => 'Get a list of external links on the [[Main Page]]',
+ 'api.php?action=query&prop=extlinks&titles=Main%20Page'
+ => 'Get a list of external links on the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryFileRepoInfo.php b/includes/api/ApiQueryFileRepoInfo.php
index 3a353533..d1600efe 100644
--- a/includes/api/ApiQueryFileRepoInfo.php
+++ b/includes/api/ApiQueryFileRepoInfo.php
@@ -29,16 +29,13 @@
*/
class ApiQueryFileRepoInfo extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'fri' );
}
protected function getInitialisedRepoGroup() {
$repoGroup = RepoGroup::singleton();
-
- if ( !$repoGroup->reposInitialised ) {
- $repoGroup->initialiseRepos();
- }
+ $repoGroup->initialiseRepos();
return $repoGroup;
}
@@ -55,7 +52,7 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
$repos[] = array_intersect_key( $repo->getInfo(), $props );
} );
- $repos[] = array_intersect_key( $repoGroup->localRepo->getInfo(), $props );
+ $repos[] = array_intersect_key( $repoGroup->getLocalRepo()->getInfo(), $props );
$result = $this->getResult();
$result->setIndexedTagName( $repos, 'repo' );
@@ -86,16 +83,19 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
$props = array_merge( $props, array_keys( $repo->getInfo() ) );
} );
- return array_values( array_unique( array_merge( $props, array_keys( $repoGroup->localRepo->getInfo() ) ) ) );
+ return array_values( array_unique( array_merge(
+ $props,
+ array_keys( $repoGroup->getLocalRepo()->getInfo() )
+ ) ) );
}
public function getParamDescription() {
- $p = $this->getModulePrefix();
return array(
'prop' => array(
'Which repository properties to get (there may be more available on some wikis):',
' apiurl - URL to the repository API - helpful for getting image info from the host.',
- ' name - The key of the repository - used in e.g. $wgForeignFileRepos and imageinfo return values.',
+ ' name - The key of the repository - used in e.g. ' .
+ '$wgForeignFileRepos and imageinfo return values.',
' displayname - The human-readable name of the repository wiki.',
' rooturl - Root URL for image paths.',
' local - Whether that repository is the local one or not.',
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index f53cd386..f047d8d4 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -33,7 +33,7 @@
*/
class ApiQueryFilearchive extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'fa' );
}
@@ -41,7 +41,10 @@ class ApiQueryFilearchive extends ApiQueryBase {
$user = $this->getUser();
// Before doing anything at all, let's check permissions
if ( !$user->isAllowed( 'deletedhistory' ) ) {
- $this->dieUsage( 'You don\'t have permission to view deleted file information', 'permissiondenied' );
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted file information',
+ 'permissiondenied'
+ );
}
$db = $this->getDB();
@@ -63,9 +66,9 @@ class ApiQueryFilearchive extends ApiQueryBase {
$this->addTables( 'filearchive' );
- $this->addFields( array( 'fa_name', 'fa_deleted' ) );
+ $this->addFields( ArchivedFile::selectFields() );
+ $this->addFields( array( 'fa_id', 'fa_name', 'fa_timestamp', 'fa_deleted' ) );
$this->addFieldsIf( 'fa_sha1', $fld_sha1 );
- $this->addFieldsIf( 'fa_timestamp', $fld_timestamp );
$this->addFieldsIf( array( 'fa_user', 'fa_user_text' ), $fld_user );
$this->addFieldsIf( array( 'fa_height', 'fa_width', 'fa_size' ), $fld_dimensions || $fld_size );
$this->addFieldsIf( 'fa_description', $fld_description );
@@ -77,22 +80,29 @@ class ApiQueryFilearchive extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- $this->dieContinueUsageIf( count( $cont ) != 1 );
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
- $this->addWhere( "fa_name $op= $cont_from" );
+ $cont_timestamp = $db->addQuotes( $db->timestamp( $cont[1] ) );
+ $cont_id = (int)$cont[2];
+ $this->dieContinueUsageIf( $cont[2] !== (string)$cont_id );
+ $this->addWhere( "fa_name $op $cont_from OR " .
+ "(fa_name = $cont_from AND " .
+ "(fa_timestamp $op $cont_timestamp OR " .
+ "(fa_timestamp = $cont_timestamp AND " .
+ "fa_id $op= $cont_id )))"
+ );
}
// Image filters
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
- if ( !is_null( $params['continue'] ) ) {
- $from = $params['continue'];
- }
- $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $from = ( $params['from'] === null ? null : $this->titlePartToKey( $params['from'], NS_FILE ) );
+ $to = ( $params['to'] === null ? null : $this->titlePartToKey( $params['to'], NS_FILE ) );
$this->addWhereRange( 'fa_name', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'fa_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( 'fa_name' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], NS_FILE ),
+ $db->anyString() ) );
}
$sha1Set = isset( $params['sha1'] );
@@ -116,20 +126,26 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
}
- if ( !$user->isAllowed( 'suppressrevision' ) ) {
- // Filter out revisions that the user is not allowed to see. There
- // is no way to indicate that we have skipped stuff because the
- // continuation parameter is fa_name
-
- // Note that this field is unindexed. This should however not be
- // a big problem as files with fa_deleted are rare
- $this->addWhereFld( 'fa_deleted', 0 );
+ // Exclude files this user can't view.
+ if ( !$user->isAllowed( 'deletedtext' ) ) {
+ $bitmask = File::DELETED_FILE;
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
+ $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
+ } else {
+ $bitmask = 0;
+ }
+ if ( $bitmask ) {
+ $this->addWhere( $this->getDB()->bitAnd( 'fa_deleted', $bitmask ) . " != $bitmask" );
}
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
- $this->addOption( 'ORDER BY', 'fa_name' . $sort );
+ $this->addOption( 'ORDER BY', array(
+ 'fa_name' . $sort,
+ 'fa_timestamp' . $sort,
+ 'fa_id' . $sort,
+ ) );
$res = $this->select( __METHOD__ );
@@ -137,26 +153,41 @@ class ApiQueryFilearchive extends ApiQueryBase {
$result = $this->getResult();
foreach ( $res as $row ) {
if ( ++$count > $limit ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', $row->fa_name );
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ $this->setContinueEnumParameter(
+ 'continue', "$row->fa_name|$row->fa_timestamp|$row->fa_id"
+ );
break;
}
$file = array();
+ $file['id'] = $row->fa_id;
$file['name'] = $row->fa_name;
$title = Title::makeTitle( NS_FILE, $row->fa_name );
self::addTitleInfo( $file, $title );
+ if ( $fld_description &&
+ Revision::userCanBitfield( $row->fa_deleted, File::DELETED_COMMENT, $user )
+ ) {
+ $file['description'] = $row->fa_description;
+ if ( isset( $prop['parseddescription'] ) ) {
+ $file['parseddescription'] = Linker::formatComment(
+ $row->fa_description, $title );
+ }
+ }
+ if ( $fld_user &&
+ Revision::userCanBitfield( $row->fa_deleted, File::DELETED_USER, $user )
+ ) {
+ $file['userid'] = $row->fa_user;
+ $file['user'] = $row->fa_user_text;
+ }
if ( $fld_sha1 ) {
$file['sha1'] = wfBaseConvert( $row->fa_sha1, 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
}
- if ( $fld_user ) {
- $file['userid'] = $row->fa_user;
- $file['user'] = $row->fa_user_text;
- }
if ( $fld_size || $fld_dimensions ) {
$file['size'] = $row->fa_size;
@@ -168,20 +199,13 @@ class ApiQueryFilearchive extends ApiQueryBase {
$file['height'] = $row->fa_height;
$file['width'] = $row->fa_width;
}
- if ( $fld_description ) {
- $file['description'] = $row->fa_description;
- if ( isset( $prop['parseddescription'] ) ) {
- $file['parseddescription'] = Linker::formatComment(
- $row->fa_description, $title );
- }
- }
if ( $fld_mediatype ) {
$file['mediatype'] = $row->fa_media_type;
}
if ( $fld_metadata ) {
$file['metadata'] = $row->fa_metadata
- ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
- : null;
+ ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
+ : null;
}
if ( $fld_bitdepth ) {
$file['bitdepth'] = $row->fa_bits;
@@ -209,7 +233,9 @@ class ApiQueryFilearchive extends ApiQueryBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', $row->fa_name );
+ $this->setContinueEnumParameter(
+ 'continue', "$row->fa_name|$row->fa_timestamp|$row->fa_id"
+ );
break;
}
}
@@ -275,7 +301,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
' sha1 - Adds SHA-1 hash for the image',
' timestamp - Adds timestamp for the uploaded version',
' user - Adds user who uploaded the image version',
- ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)',
+ ' size - Adds the size of the image in bytes and the height, ' .
+ 'width and page count (if applicable)',
' dimensions - Alias for size',
' description - Adds description the image version',
' parseddescription - Parse the description on the version',
@@ -288,81 +315,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'name' => 'string',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'filehidden' => 'boolean',
- 'commenthidden' => 'boolean',
- 'userhidden' => 'boolean',
- 'suppressed' => 'boolean'
- ),
- 'sha1' => array(
- 'sha1' => 'string'
- ),
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'user' => array(
- 'userid' => 'integer',
- 'user' => 'string'
- ),
- 'size' => array(
- 'size' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'height' => 'integer',
- 'width' => 'integer'
- ),
- 'dimensions' => array(
- 'size' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'height' => 'integer',
- 'width' => 'integer'
- ),
- 'description' => array(
- 'description' => 'string'
- ),
- 'parseddescription' => array(
- 'description' => 'string',
- 'parseddescription' => 'string'
- ),
- 'metadata' => array(
- 'metadata' => 'string'
- ),
- 'bitdepth' => array(
- 'bitdepth' => 'integer'
- ),
- 'mime' => array(
- 'mime' => 'string'
- ),
- 'mediatype' => array(
- 'mediatype' => 'string'
- ),
- 'archivename' => array(
- 'archivename' => 'string'
- ),
- );
- }
-
public function getDescription() {
- return 'Enumerate all deleted files sequentially';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted file information' ),
- array( 'code' => 'hashsearchdisabled', 'info' => 'Search by hash disabled in Miser Mode' ),
- array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
- array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
- ) );
+ return 'Enumerate all deleted files sequentially.';
}
public function getExamples() {
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index ebae3e76..b5aa45bf 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'iwbl' );
}
@@ -44,7 +44,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function run( $resultPageSet = null ) {
@@ -92,14 +92,14 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', array(
'iwl_title' . $sort,
'iwl_from' . $sort
- ));
+ ) );
}
} else {
$this->addOption( 'ORDER BY', array(
'iwl_prefix' . $sort,
'iwl_title' . $sort,
'iwl_from' . $sort
- ));
+ ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -111,10 +111,15 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // Continue string preserved in case the redirect query doesn't pass the limit
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}" );
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ // Continue string preserved in case the redirect query doesn't
+ // pass the limit
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}"
+ );
break;
}
@@ -140,7 +145,10 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $entry );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_prefix}|{$row->iwl_title}|{$row->iwl_from}"
+ );
break;
}
}
@@ -202,37 +210,14 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'redirect' => 'boolean'
- ),
- 'iwprefix' => array(
- 'iwprefix' => 'string'
- ),
- 'iwtitle' => array(
- 'iwtitle' => 'string'
- )
- );
- }
-
public function getDescription() {
return array( 'Find all pages that link to the given interwiki link.',
'Can be used to find all links with a prefix, or',
'all links to a title (with a given prefix).',
- 'Using neither parameter is effectively "All IW Links"',
+ 'Using neither parameter is effectively "All IW Links".',
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'prefix' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&list=iwbacklinks&iwbltitle=Test&iwblprefix=wikibooks',
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index be539311..a185ee24 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -32,7 +32,7 @@
*/
class ApiQueryIWLinks extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'iw' );
}
@@ -42,11 +42,19 @@ class ApiQueryIWLinks extends ApiQueryBase {
}
$params = $this->extractRequestParams();
+ $prop = array_flip( (array)$params['prop'] );
if ( isset( $params['title'] ) && !isset( $params['prefix'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'prefix' ) );
}
+ // Handle deprecated param
+ $this->requireMaxOneParameter( $params, 'url', 'prop' );
+ if ( $params['url'] ) {
+ $this->logFeatureUsage( 'prop=iwlinks&iwurl' );
+ $prop = array( 'url' => 1 );
+ }
+
$this->addFields( array(
'iwl_from',
'iwl_prefix',
@@ -81,9 +89,9 @@ class ApiQueryIWLinks extends ApiQueryBase {
$this->addOption( 'ORDER BY', 'iwl_from' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'iwl_from' . $sort,
- 'iwl_title' . $sort
- ));
+ 'iwl_from' . $sort,
+ 'iwl_title' . $sort
+ ) );
}
} else {
// Don't order by iwl_from if it's constant in the WHERE clause
@@ -91,10 +99,10 @@ class ApiQueryIWLinks extends ApiQueryBase {
$this->addOption( 'ORDER BY', 'iwl_prefix' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'iwl_from' . $sort,
- 'iwl_prefix' . $sort,
- 'iwl_title' . $sort
- ));
+ 'iwl_from' . $sort,
+ 'iwl_prefix' . $sort,
+ 'iwl_title' . $sort
+ ) );
}
}
@@ -106,12 +114,15 @@ class ApiQueryIWLinks extends ApiQueryBase {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}"
+ );
break;
}
$entry = array( 'prefix' => $row->iwl_prefix );
- if ( $params['url'] ) {
+ if ( isset( $prop['url'] ) ) {
$title = Title::newFromText( "{$row->iwl_prefix}:{$row->iwl_title}" );
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
@@ -121,7 +132,10 @@ class ApiQueryIWLinks extends ApiQueryBase {
ApiResult::setContent( $entry, $row->iwl_title );
$fit = $this->addPageSubItem( $row->iwl_from, $entry );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->iwl_from}|{$row->iwl_prefix}|{$row->iwl_title}"
+ );
break;
}
}
@@ -133,7 +147,16 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getAllowedParams() {
return array(
- 'url' => false,
+ 'url' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
+ 'url',
+ )
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -156,7 +179,11 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getParamDescription() {
return array(
- 'url' => 'Whether to get the full URL',
+ 'prop' => array(
+ 'Which additional properties to get for each interlanguage link',
+ ' url - Adds the full URL',
+ ),
+ 'url' => "Whether to get the full URL (Cannot be used with {$this->getModulePrefix()}prop)",
'limit' => 'How many interwiki links to return',
'continue' => 'When more results are available, use this to continue',
'prefix' => 'Prefix for the interwiki',
@@ -165,32 +192,14 @@ class ApiQueryIWLinks extends ApiQueryBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'prefix' => 'string',
- 'url' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- '*' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Returns all interwiki links from the given page(s)';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'prefix' ),
- ) );
+ return 'Returns all interwiki links from the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=iwlinks&titles=Main%20Page' => 'Get interwiki links from the [[Main Page]]',
+ 'api.php?action=query&prop=iwlinks&titles=Main%20Page'
+ => 'Get interwiki links from the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index 0ea28684..945374b1 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -33,9 +33,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
const TRANSFORM_LIMIT = 50;
private static $transformCount = 0;
- public function __construct( $query, $moduleName, $prefix = 'ii' ) {
- // We allow a subclass to override the prefix, to create a related API module.
- // Some other parts of MediaWiki construct this with a null $prefix, which used to be ignored when this only took two arguments
+ public function __construct( ApiQuery $query, $moduleName, $prefix = 'ii' ) {
+ // We allow a subclass to override the prefix, to create a related API
+ // module. Some other parts of MediaWiki construct this with a null
+ // $prefix, which used to be ignored when this only took two arguments
if ( is_null( $prefix ) ) {
$prefix = 'ii';
}
@@ -49,6 +50,14 @@ class ApiQueryImageInfo extends ApiQueryBase {
$scale = $this->getScale( $params );
+ $opts = array(
+ 'version' => $params['metadataversion'],
+ 'language' => $params['extmetadatalanguage'],
+ 'multilang' => $params['extmetadatamultilang'],
+ 'extmetadatafilter' => $params['extmetadatafilter'],
+ 'revdelUser' => $this->getUser(),
+ );
+
$pageIds = $this->getPageSet()->getAllTitlesByNamespace();
if ( !empty( $pageIds[NS_FILE] ) ) {
$titles = array_keys( $pageIds[NS_FILE] );
@@ -70,13 +79,21 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
}
- $result = $this->getResult();
- //search only inside the local repo
+ $user = $this->getUser();
+ $findTitles = array_map( function ( $title ) use ( $user ) {
+ return array(
+ 'title' => $title,
+ 'private' => $user,
+ );
+ }, $titles );
+
if ( $params['localonly'] ) {
- $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $titles );
+ $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $findTitles );
} else {
- $images = RepoGroup::singleton()->findFiles( $titles );
+ $images = RepoGroup::singleton()->findFiles( $findTitles );
}
+
+ $result = $this->getResult();
foreach ( $titles as $title ) {
$pageId = $pageIds[NS_FILE][$title];
$start = $title === $fromTitle ? $fromTimestamp : $params['start'];
@@ -146,7 +163,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
$fit = $this->addPageSubItem( $pageId,
self::getInfo( $img, $prop, $result,
- $finalThumbParams, $params['metadataversion'] ) );
+ $finalThumbParams, $opts
+ )
+ );
if ( !$fit ) {
if ( count( $pageIds[NS_FILE] ) == 1 ) {
// See the 'the user is screwed' comment above
@@ -167,7 +186,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
/** @var $oldie File */
foreach ( $oldies as $oldie ) {
if ( ++$count > $params['limit'] ) {
- // We've reached the extra one which shows that there are additional pages to be had. Stop here...
+ // We've reached the extra one which shows that there are
+ // additional pages to be had. Stop here...
// Only set a query-continue if there was only one title
if ( count( $pageIds[NS_FILE] ) == 1 ) {
$this->setContinueEnumParameter( 'start',
@@ -178,7 +198,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$fit = self::getTransformCount() < self::TRANSFORM_LIMIT &&
$this->addPageSubItem( $pageId,
self::getInfo( $oldie, $prop, $result,
- $finalThumbParams, $params['metadataversion']
+ $finalThumbParams, $opts
)
);
if ( !$fit ) {
@@ -202,7 +222,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* From parameters, construct a 'scale' array
* @param array $params Parameters passed to api.
- * @return Array or Null: key-val array of 'width' and 'height', or null
+ * @return array|null Key-val array of 'width' and 'height', or null
*/
public function getScale( $params ) {
$p = $this->getModulePrefix();
@@ -217,9 +237,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
$scale = array();
$scale['height'] = $params['urlheight'];
} else {
- $scale = null;
if ( $params['urlparam'] ) {
- $this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" );
+ // Audio files might not have a width/height.
+ $scale = array();
+ } else {
+ $scale = null;
}
}
@@ -231,26 +253,29 @@ class ApiQueryImageInfo extends ApiQueryBase {
* We do this later than getScale, since we need the image
* to know which handler, since handlers can make their own parameters.
* @param File $image Image that params are for.
- * @param array $thumbParams thumbnail parameters from getScale
- * @param string $otherParams of otherParams (iiurlparam).
- * @return Array of parameters for transform.
+ * @param array $thumbParams Thumbnail parameters from getScale
+ * @param string $otherParams String of otherParams (iiurlparam).
+ * @return array Array of parameters for transform.
*/
protected function mergeThumbParams( $image, $thumbParams, $otherParams ) {
- global $wgThumbLimits;
-
+ if ( $thumbParams === null ) {
+ // No scaling requested
+ return null;
+ }
if ( !isset( $thumbParams['width'] ) && isset( $thumbParams['height'] ) ) {
// We want to limit only by height in this situation, so pass the
// image's full width as the limiting width. But some file types
// don't have a width of their own, so pick something arbitrary so
// thumbnailing the default icon works.
if ( $image->getWidth() <= 0 ) {
- $thumbParams['width'] = max( $wgThumbLimits );
+ $thumbParams['width'] = max( $this->getConfig()->get( 'ThumbLimits' ) );
} else {
$thumbParams['width'] = $image->getWidth();
}
}
if ( !$otherParams ) {
+ $this->checkParameterNormalise( $image, $thumbParams );
return $thumbParams;
}
$p = $this->getModulePrefix();
@@ -259,6 +284,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( !$h ) {
$this->setWarning( 'Could not create thumbnail because ' .
$image->getName() . ' does not have an associated image handler' );
+
return $thumbParams;
}
@@ -270,13 +296,15 @@ class ApiQueryImageInfo extends ApiQueryBase {
// handlers.
$this->setWarning( "Could not parse {$p}urlparam for " . $image->getName()
. '. Using only width and height' );
+ $this->checkParameterNormalise( $image, $thumbParams );
return $thumbParams;
}
- if ( isset( $paramList['width'] ) ) {
+ if ( isset( $paramList['width'] ) && isset( $thumbParams['width'] ) ) {
if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) {
$this->setWarning( "Ignoring width value set in {$p}urlparam ({$paramList['width']}) "
- . "in favor of width value derived from {$p}urlwidth/{$p}urlheight ({$thumbParams['width']})" );
+ . "in favor of width value derived from {$p}urlwidth/{$p}urlheight "
+ . "({$thumbParams['width']})" );
}
}
@@ -286,20 +314,65 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
}
- return $thumbParams + $paramList;
+ $finalParams = $thumbParams + $paramList;
+ $this->checkParameterNormalise( $image, $finalParams );
+ return $finalParams;
+ }
+
+ /**
+ * Verify that the final image parameters can be normalised.
+ *
+ * This doesn't use the normalised parameters, since $file->transform
+ * expects the pre-normalised parameters, but doing the normalisation
+ * allows us to catch certain error conditions early (such as missing
+ * required parameter).
+ *
+ * @param $image File
+ * @param $finalParams array List of parameters to transform image with
+ */
+ protected function checkParameterNormalise( $image, $finalParams ) {
+ $h = $image->getHandler();
+ if ( !$h ) {
+ return;
+ }
+ // Note: normaliseParams modifies the array in place, but we aren't interested
+ // in the actual normalised version, only if we can actually normalise them,
+ // so we use the functions scope to throw away the normalisations.
+ if ( !$h->normaliseParams( $image, $finalParams ) ) {
+ $this->dieUsage( "Could not normalise image parameters for " . $image->getName(), "urlparamnormal" );
+ }
}
/**
* Get result information for an image revision
*
- * @param $file File object
- * @param array $prop of properties to get (in the keys)
- * @param $result ApiResult object
- * @param array $thumbParams containing 'width' and 'height' items, or null
- * @param string $version Version of image metadata (for things like jpeg which have different versions).
- * @return Array: result array
+ * @param File $file
+ * @param array $prop Array of properties to get (in the keys)
+ * @param ApiResult $result
+ * @param array $thumbParams Containing 'width' and 'height' items, or null
+ * @param array|bool|string $opts Options for data fetching.
+ * This is an array consisting of the keys:
+ * 'version': The metadata version for the metadata option
+ * 'language': The language for extmetadata property
+ * 'multilang': Return all translations in extmetadata property
+ * 'revdelUser': User to use when checking whether to show revision-deleted fields.
+ * @return array Result array
*/
- static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
+ static function getInfo( $file, $prop, $result, $thumbParams = null, $opts = false ) {
+ global $wgContLang;
+
+ $anyHidden = false;
+
+ if ( !$opts || is_string( $opts ) ) {
+ $opts = array(
+ 'version' => $opts ?: 'latest',
+ 'language' => $wgContLang,
+ 'multilang' => false,
+ 'extmetadatafilter' => array(),
+ 'revdelUser' => null,
+ );
+ }
+ $version = $opts['version'];
$vals = array();
// Timestamp is shown even if the file is revdelete'd in interface
// so do same here.
@@ -307,13 +380,27 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
}
+ // Handle external callers who don't pass revdelUser
+ if ( isset( $opts['revdelUser'] ) && $opts['revdelUser'] ) {
+ $revdelUser = $opts['revdelUser'];
+ $canShowField = function ( $field ) use ( $file, $revdelUser ) {
+ return $file->userCan( $field, $revdelUser );
+ };
+ } else {
+ $canShowField = function ( $field ) use ( $file ) {
+ return !$file->isDeleted( $field );
+ };
+ }
+
$user = isset( $prop['user'] );
$userid = isset( $prop['userid'] );
if ( $user || $userid ) {
if ( $file->isDeleted( File::DELETED_USER ) ) {
$vals['userhidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $canShowField( File::DELETED_USER ) ) {
if ( $user ) {
$vals['user'] = $file->getUser();
}
@@ -337,6 +424,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $pageCount !== false ) {
$vals['pagecount'] = $pageCount;
}
+
+ // length as in how many seconds long a video is.
+ $length = $file->getLength();
+ if ( $length ) {
+ // Call it duration, because "length" can be ambiguous.
+ $vals['duration'] = (float)$length;
+ }
}
$pcomment = isset( $prop['parsedcomment'] );
@@ -345,34 +439,53 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $pcomment || $comment ) {
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
$vals['commenthidden'] = '';
- } else {
+ $anyHidden = true;
+ }
+ if ( $canShowField( File::DELETED_COMMENT ) ) {
if ( $pcomment ) {
$vals['parsedcomment'] = Linker::formatComment(
- $file->getDescription(), $file->getTitle() );
+ $file->getDescription( File::RAW ), $file->getTitle() );
}
if ( $comment ) {
- $vals['comment'] = $file->getDescription();
+ $vals['comment'] = $file->getDescription( File::RAW );
}
}
}
+ $canonicaltitle = isset( $prop['canonicaltitle'] );
$url = isset( $prop['url'] );
$sha1 = isset( $prop['sha1'] );
$meta = isset( $prop['metadata'] );
+ $extmetadata = isset( $prop['extmetadata'] );
+ $commonmeta = isset( $prop['commonmetadata'] );
$mime = isset( $prop['mime'] );
$mediatype = isset( $prop['mediatype'] );
$archive = isset( $prop['archivename'] );
$bitdepth = isset( $prop['bitdepth'] );
$uploadwarning = isset( $prop['uploadwarning'] );
- if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth )
- && $file->isDeleted( File::DELETED_FILE ) ) {
+ if ( $uploadwarning ) {
+ $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
+ }
+
+ if ( $file->isDeleted( File::DELETED_FILE ) ) {
$vals['filehidden'] = '';
+ $anyHidden = true;
+ }
+
+ if ( $anyHidden && $file->isDeleted( File::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = true;
+ }
+ if ( !$canShowField( File::DELETED_FILE ) ) {
//Early return, tidier than indenting all following things one level
return $vals;
}
+ if ( $canonicaltitle ) {
+ $vals['canonicaltitle'] = $file->getTitle()->getPrefixedText();
+ }
+
if ( $url ) {
if ( !is_null( $thumbParams ) ) {
$mto = $file->transform( $thumbParams );
@@ -416,6 +529,26 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
$vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
}
+ if ( $commonmeta ) {
+ $metaArray = $file->getCommonMetaArray();
+ $vals['commonmetadata'] = $metaArray ? self::processMetaData( $metaArray, $result ) : array();
+ }
+
+ if ( $extmetadata ) {
+ // Note, this should return an array where all the keys
+ // start with a letter, and all the values are strings.
+ // Thus there should be no issue with format=xml.
+ $format = new FormatMetadata;
+ $format->setSingleLanguage( !$opts['multilang'] );
+ $format->getContext()->setLanguage( $opts['language'] );
+ $extmetaArray = $format->fetchExtendedMetadata( $file );
+ if ( $opts['extmetadatafilter'] ) {
+ $extmetaArray = array_intersect_key(
+ $extmetaArray, array_flip( $opts['extmetadatafilter'] )
+ );
+ }
+ $vals['extmetadata'] = $extmetaArray;
+ }
if ( $mime ) {
$vals['mime'] = $file->getMimeType();
@@ -433,10 +566,6 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['bitdepth'] = $file->getBitDepth();
}
- if ( $uploadwarning ) {
- $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
- }
-
return $vals;
}
@@ -445,7 +574,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
*
* If this is >= TRANSFORM_LIMIT, you should probably stop processing images.
*
- * @return integer count
+ * @return int Count
*/
static function getTransformCount() {
return self::$transformCount;
@@ -453,9 +582,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
*
- * @param $metadata Array
- * @param $result ApiResult
- * @return Array
+ * @param array $metadata
+ * @param ApiResult $result
+ * @return array
*/
public static function processMetaData( $metadata, $result ) {
$retval = array();
@@ -471,15 +600,20 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
}
$result->setIndexedTagName( $retval, 'metadata' );
+
return $retval;
}
public function getCacheMode( $params ) {
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
+
return 'public';
}
/**
- * @param $img File
+ * @param File $img
* @param null|string $start
* @return string
*/
@@ -487,10 +621,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $start === null ) {
$start = $img->getTimestamp();
}
+
return $img->getOriginalTitle()->getDBkey() . '|' . $start;
}
public function getAllowedParams() {
+ global $wgContLang;
+
return array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -522,6 +659,18 @@ class ApiQueryImageInfo extends ApiQueryBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_DFLT => '1',
),
+ 'extmetadatalanguage' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => $wgContLang->getCode(),
+ ),
+ 'extmetadatamultilang' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false,
+ ),
+ 'extmetadatafilter' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'urlparam' => array(
ApiBase::PARAM_DFLT => '',
ApiBase::PARAM_TYPE => 'string',
@@ -536,7 +685,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
*
* @param array $filter List of properties to filter out
*
- * @return Array
+ * @return array
*/
public static function getPropertyNames( $filter = array() ) {
return array_diff( array_keys( self::getProperties() ), $filter );
@@ -555,18 +704,26 @@ class ApiQueryImageInfo extends ApiQueryBase {
'userid' => ' userid - Add the user ID that uploaded the image version',
'comment' => ' comment - Comment on the version',
'parsedcomment' => ' parsedcomment - Parse the comment on the version',
+ 'canonicaltitle' => ' canonicaltitle - Adds the canonical title of the image file',
'url' => ' url - Gives URL to the image and the description page',
- 'size' => ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)',
- 'dimensions' => ' dimensions - Alias for size', // For backwards compatibility with Allimages
+ 'size' => ' size - Adds the size of the image in bytes, ' .
+ 'its height and its width. Page count and duration are added if applicable',
+ 'dimensions' => ' dimensions - Alias for size', // B/C with Allimages
'sha1' => ' sha1 - Adds SHA-1 hash for the image',
'mime' => ' mime - Adds MIME type of the image',
'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail' .
' (requires url and param ' . $modulePrefix . 'urlwidth)',
'mediatype' => ' mediatype - Adds the media type of the image',
'metadata' => ' metadata - Lists Exif metadata for the version of the image',
- 'archivename' => ' archivename - Adds the file name of the archive version for non-latest versions',
+ 'commonmetadata' => ' commonmetadata - Lists file format generic metadata ' .
+ 'for the version of the image',
+ 'extmetadata' => ' extmetadata - Lists formatted metadata combined ' .
+ 'from multiple sources. Results are HTML formatted.',
+ 'archivename' => ' archivename - Adds the file name of the archive ' .
+ 'version for non-latest versions',
'bitdepth' => ' bitdepth - Adds the bit depth of the version',
- 'uploadwarning' => ' uploadwarning - Used by the Special:Upload page to get information about an existing file. Not intended for use outside MediaWiki core',
+ 'uploadwarning' => ' uploadwarning - Used by the Special:Upload page to ' .
+ 'get information about an existing file. Not intended for use outside MediaWiki core',
);
}
@@ -586,169 +743,53 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* Return the API documentation for the parameters.
- * @return Array parameter documentation.
+ * @return array Parameter documentation.
*/
public function getParamDescription() {
$p = $this->getModulePrefix();
+
return array(
'prop' => self::getPropertyDescriptions( array(), $p ),
- 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
+ 'urlwidth' => array(
+ "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
'For performance reasons if this option is used, ' .
- 'no more than ' . self::TRANSFORM_LIMIT . ' scaled images will be returned.' ),
+ 'no more than ' . self::TRANSFORM_LIMIT . ' scaled images will be returned.'
+ ),
'urlheight' => "Similar to {$p}urlwidth.",
- 'urlparam' => array( "A handler specific parameter string. For example, pdf's ",
- "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ),
+ 'urlparam' => array(
+ "A handler specific parameter string. For example, pdf's ",
+ "might use 'page15-100px'."
+ ),
'limit' => 'How many image revisions to return per image',
'start' => 'Timestamp to start listing from',
'end' => 'Timestamp to stop listing at',
- 'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
- "Defaults to '1' for backwards compatibility" ),
- 'continue' => 'If the query response includes a continue value, use it here to get another page of results',
- 'localonly' => 'Look only for files in the local repository',
- );
- }
-
- public static function getResultPropertiesFiltered( $filter = array() ) {
- $props = array(
- 'timestamp' => array(
- 'timestamp' => 'timestamp'
- ),
- 'user' => array(
- 'userhidden' => 'boolean',
- 'user' => 'string',
- 'anon' => 'boolean'
- ),
- 'userid' => array(
- 'userhidden' => 'boolean',
- 'userid' => 'integer',
- 'anon' => 'boolean'
- ),
- 'size' => array(
- 'size' => 'integer',
- 'width' => 'integer',
- 'height' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'dimensions' => array(
- 'size' => 'integer',
- 'width' => 'integer',
- 'height' => 'integer',
- 'pagecount' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'comment' => array(
- 'commenthidden' => 'boolean',
- 'comment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'parsedcomment' => array(
- 'commenthidden' => 'boolean',
- 'parsedcomment' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'url' => array(
- 'filehidden' => 'boolean',
- 'thumburl' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'thumbwidth' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'thumbheight' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'thumberror' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'url' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'descriptionurl' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'sha1' => array(
- 'filehidden' => 'boolean',
- 'sha1' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'mime' => array(
- 'filehidden' => 'boolean',
- 'mime' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'thumbmime' => array(
- 'filehidden' => 'boolean',
- 'thumbmime' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'mediatype' => array(
- 'filehidden' => 'boolean',
- 'mediatype' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'archivename' => array(
- 'filehidden' => 'boolean',
- 'archivename' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'bitdepth' => array(
- 'filehidden' => 'boolean',
- 'bitdepth' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
+ 'metadataversion'
+ => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
+ "Defaults to '1' for backwards compatibility" ),
+ 'extmetadatalanguage' => array(
+ 'What language to fetch extmetadata in. This affects both which',
+ 'translation to fetch, if multiple are available, as well as how things',
+ 'like numbers and various values are formatted.'
),
+ 'extmetadatamultilang'
+ =>'If translations for extmetadata property are available, fetch all of them.',
+ 'extmetadatafilter'
+ => "If specified and non-empty, only these keys will be returned for {$p}prop=extmetadata",
+ 'continue' => 'If the query response includes a continue value, ' .
+ 'use it here to get another page of results',
+ 'localonly' => 'Look only for files in the local repository',
);
- return array_diff_key( $props, array_flip( $filter ) );
- }
-
- public function getResultProperties() {
- return self::getResultPropertiesFiltered();
}
public function getDescription() {
- return 'Returns image information and upload history';
- }
-
- public function getPossibleErrors() {
- $p = $this->getModulePrefix();
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => "{$p}urlwidth", 'info' => "{$p}urlheight cannot be used without {$p}urlwidth" ),
- array( 'code' => 'urlparam', 'info' => "Invalid value for {$p}urlparam" ),
- array( 'code' => 'urlparam_no_width', 'info' => "{$p}urlparam requires {$p}urlwidth" ),
- ) );
+ return 'Returns image information and upload history.';
}
public function getExamples() {
return array(
'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo',
- 'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url',
+ 'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&' .
+ 'iiend=20071231235959&iiprop=timestamp|user|url',
);
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index f2bf0a7b..9bc3abed 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -32,7 +32,7 @@
*/
class ApiQueryImages extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'im' );
}
@@ -45,7 +45,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
@@ -79,9 +79,9 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', 'il_to' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'il_from' . $sort,
- 'il_to' . $sort
- ));
+ 'il_from' . $sort,
+ 'il_to' . $sort
+ ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -164,28 +164,22 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
return array(
'limit' => 'How many images to return',
'continue' => 'When more results are available, use this to continue',
- 'images' => 'Only list these images. Useful for checking whether a certain page has a certain Image.',
+ 'images' => 'Only list these images. Useful for checking whether a ' .
+ 'certain page has a certain Image.',
'dir' => 'The direction in which to list',
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return 'Returns all images contained on the given page(s)';
+ return 'Returns all images contained on the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=images&titles=Main%20Page' => 'Get a list of images used in the [[Main Page]]',
- 'api.php?action=query&generator=images&titles=Main%20Page&prop=info' => 'Get information about all images used in the [[Main Page]]',
+ 'api.php?action=query&prop=images&titles=Main%20Page'
+ => 'Get a list of images used in the [[Main Page]]',
+ 'api.php?action=query&generator=images&titles=Main%20Page&prop=info'
+ => 'Get information about all images used in the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 017684ed..d7037e3a 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -42,35 +42,35 @@ class ApiQueryInfo extends ApiQueryBase {
private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched,
$pageLatest, $pageLength;
- private $protections, $watched, $watchers, $notificationtimestamps, $talkids, $subjectids, $displaytitles;
+ private $protections, $watched, $watchers, $notificationtimestamps,
+ $talkids, $subjectids, $displaytitles;
private $showZeroWatchers = false;
private $tokenFunctions;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'in' );
}
/**
- * @param $pageSet ApiPageSet
+ * @param ApiPageSet $pageSet
* @return void
*/
public function requestExtraData( $pageSet ) {
- global $wgDisableCounters, $wgContentHandlerUseDB;
-
$pageSet->requestField( 'page_restrictions' );
// when resolving redirects, no page will have this field
if ( !$pageSet->isResolvingRedirects() ) {
$pageSet->requestField( 'page_is_redirect' );
}
$pageSet->requestField( 'page_is_new' );
- if ( !$wgDisableCounters ) {
+ $config = $this->getConfig();
+ if ( !$config->get( 'DisableCounters' ) ) {
$pageSet->requestField( 'page_counter' );
}
$pageSet->requestField( 'page_touched' );
$pageSet->requestField( 'page_latest' );
$pageSet->requestField( 'page_len' );
- if ( $wgContentHandlerUseDB ) {
+ if ( $config->get( 'ContentHandlerUseDB' ) ) {
$pageSet->requestField( 'page_content_model' );
}
}
@@ -79,7 +79,8 @@ class ApiQueryInfo extends ApiQueryBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title)
* it should return a token or false (permission denied)
- * @return array array(tokenname => function)
+ * @deprecated since 1.24
+ * @return array Array(tokenname => function)
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
@@ -104,15 +105,22 @@ class ApiQueryInfo extends ApiQueryBase {
'watch' => array( 'ApiQueryInfo', 'getWatchToken' ),
);
wfRunHooks( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) );
+
return $this->tokenFunctions;
}
- static $cachedTokens = array();
+ static protected $cachedTokens = array();
+ /**
+ * @deprecated since 1.24
+ */
public static function resetTokenCache() {
ApiQueryInfo::$cachedTokens = array();
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getEditToken( $pageid, $title ) {
// We could check for $title->userCan('edit') here,
// but that's too expensive for this purpose
@@ -130,6 +138,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['edit'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getDeleteToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'delete' ) ) {
@@ -144,6 +155,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['delete'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getProtectToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'protect' ) ) {
@@ -158,6 +172,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['protect'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getMoveToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'move' ) ) {
@@ -172,6 +189,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['move'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getBlockToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'block' ) ) {
@@ -186,11 +206,17 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['block'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getUnblockToken( $pageid, $title ) {
// Currently, this is exactly the same as the block token
return self::getBlockToken( $pageid, $title );
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getEmailToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() ) {
@@ -205,6 +231,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['email'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getImportToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
@@ -219,6 +248,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['import'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getWatchToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
@@ -233,6 +265,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['watch'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getOptionsToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
@@ -293,9 +328,7 @@ class ApiQueryInfo extends ApiQueryBase {
: array();
$this->pageIsNew = $pageSet->getCustomField( 'page_is_new' );
- global $wgDisableCounters;
-
- if ( !$wgDisableCounters ) {
+ if ( !$this->getConfig()->get( 'DisableCounters' ) ) {
$this->pageCounter = $pageSet->getCustomField( 'page_counter' );
}
$this->pageTouched = $pageSet->getCustomField( 'page_touched' );
@@ -333,8 +366,8 @@ class ApiQueryInfo extends ApiQueryBase {
), $pageid, $pageInfo );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue',
- $title->getNamespace() . '|' .
- $title->getText() );
+ $title->getNamespace() . '|' .
+ $title->getText() );
break;
}
}
@@ -343,12 +376,13 @@ class ApiQueryInfo extends ApiQueryBase {
/**
* Get a result array with information about a title
* @param int $pageid Page ID (negative for missing titles)
- * @param $title Title object
+ * @param Title $title
* @return array
*/
private function extractPageInfo( $pageid, $title ) {
$pageInfo = array();
- $titleExists = $pageid > 0; //$title->exists() needs pageid, which is not set for all title objects
+ // $title->exists() needs pageid, which is not set for all title objects
+ $titleExists = $pageid > 0;
$ns = $title->getNamespace();
$dbkey = $title->getDBkey();
@@ -356,11 +390,9 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['pagelanguage'] = $title->getPageLanguage()->getCode();
if ( $titleExists ) {
- global $wgDisableCounters;
-
$pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
$pageInfo['lastrevid'] = intval( $this->pageLatest[$pageid] );
- $pageInfo['counter'] = $wgDisableCounters
+ $pageInfo['counter'] = $this->getConfig()->get( 'DisableCounters' )
? ''
: intval( $this->pageCounter[$pageid] );
$pageInfo['length'] = intval( $this->pageLength[$pageid] );
@@ -410,7 +442,8 @@ class ApiQueryInfo extends ApiQueryBase {
if ( $this->fld_notificationtimestamp ) {
$pageInfo['notificationtimestamp'] = '';
if ( isset( $this->notificationtimestamps[$ns][$dbkey] ) ) {
- $pageInfo['notificationtimestamp'] = wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
+ $pageInfo['notificationtimestamp'] =
+ wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
}
}
@@ -425,6 +458,7 @@ class ApiQueryInfo extends ApiQueryBase {
if ( $this->fld_url ) {
$pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
$pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
+ $pageInfo['canonicalurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CANONICAL );
}
if ( $this->fld_readable && $title->userCan( 'read', $this->getUser() ) ) {
$pageInfo['readable'] = '';
@@ -465,7 +499,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->resetQueryParams();
$this->addTables( 'page_restrictions' );
$this->addFields( array( 'pr_page', 'pr_type', 'pr_level',
- 'pr_expiry', 'pr_cascade' ) );
+ 'pr_expiry', 'pr_cascade' ) );
$this->addWhereFld( 'pr_page', array_keys( $this->titles ) );
$res = $this->select( __METHOD__ );
@@ -556,8 +590,8 @@ class ApiQueryInfo extends ApiQueryBase {
$this->resetQueryParams();
$this->addTables( array( 'page_restrictions', 'page', 'templatelinks' ) );
$this->addFields( array( 'pr_type', 'pr_level', 'pr_expiry',
- 'page_title', 'page_namespace',
- 'tl_title', 'tl_namespace' ) );
+ 'page_title', 'page_namespace',
+ 'tl_title', 'tl_namespace' ) );
$this->addWhere( $lb->constructSet( 'tl', $db ) );
$this->addWhere( 'pr_page = page_id' );
$this->addWhere( 'pr_page = tl_from' );
@@ -580,7 +614,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->resetQueryParams();
$this->addTables( array( 'page_restrictions', 'page', 'imagelinks' ) );
$this->addFields( array( 'pr_type', 'pr_level', 'pr_expiry',
- 'page_title', 'page_namespace', 'il_to' ) );
+ 'page_title', 'page_namespace', 'il_to' ) );
$this->addWhere( 'pr_page = page_id' );
$this->addWhere( 'pr_page = il_from' );
$this->addWhereFld( 'pr_cascade', 1 );
@@ -633,10 +667,10 @@ class ApiQueryInfo extends ApiQueryBase {
foreach ( $res as $row ) {
if ( MWNamespace::isTalk( $row->page_namespace ) ) {
$this->talkids[MWNamespace::getSubject( $row->page_namespace )][$row->page_title] =
- intval( $row->page_id );
+ intval( $row->page_id );
} else {
$this->subjectids[MWNamespace::getTalk( $row->page_namespace )][$row->page_title] =
- intval( $row->page_id );
+ intval( $row->page_id );
}
}
}
@@ -697,7 +731,8 @@ class ApiQueryInfo extends ApiQueryBase {
$this->watched[$row->wl_namespace][$row->wl_title] = true;
}
if ( $this->fld_notificationtimestamp ) {
- $this->notificationtimestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
+ $this->notificationtimestamps[$row->wl_namespace][$row->wl_title] =
+ $row->wl_notificationtimestamp;
}
}
}
@@ -706,15 +741,14 @@ class ApiQueryInfo extends ApiQueryBase {
* Get the count of watchers and put it in $this->watchers
*/
private function getWatcherInfo() {
- global $wgUnwatchedPageThreshold;
-
if ( count( $this->everything ) == 0 ) {
return;
}
$user = $this->getUser();
$canUnwatchedpages = $user->isAllowed( 'unwatchedpages' );
- if ( !$canUnwatchedpages && !is_int( $wgUnwatchedPageThreshold ) ) {
+ $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
+ if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
return;
}
@@ -732,7 +766,7 @@ class ApiQueryInfo extends ApiQueryBase {
) );
$this->addOption( 'GROUP BY', array( 'wl_namespace', 'wl_title' ) );
if ( !$canUnwatchedpages ) {
- $this->addOption( 'HAVING', "COUNT(*) >= $wgUnwatchedPageThreshold" );
+ $this->addOption( 'HAVING', "COUNT(*) >= $unwatchedPageThreshold" );
}
$res = $this->select( __METHOD__ );
@@ -761,6 +795,7 @@ class ApiQueryInfo extends ApiQueryBase {
if ( !is_null( $params['token'] ) ) {
return 'private';
}
+
return 'public';
}
@@ -784,6 +819,7 @@ class ApiQueryInfo extends ApiQueryBase {
// need to be added to getCacheMode()
) ),
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_DFLT => null,
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() )
@@ -802,7 +838,7 @@ class ApiQueryInfo extends ApiQueryBase {
' watchers - The number of watchers, if allowed',
' notificationtimestamp - The watchlist notification timestamp of each page',
' subjectid - The page ID of the parent page for each talk page',
- ' url - Gives a full URL to the page, and also an edit URL',
+ ' url - Gives a full URL, an edit URL, and the canonical URL for each page',
' readable - Whether the user can read this page',
' preload - Gives the text returned by EditFormPreloadText',
' displaytitle - Gives the way the page title is actually displayed',
@@ -812,72 +848,6 @@ class ApiQueryInfo extends ApiQueryBase {
);
}
- public function getResultProperties() {
- $props = array(
- ApiBase::PROP_LIST => false,
- '' => array(
- 'touched' => 'timestamp',
- 'lastrevid' => 'integer',
- 'counter' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- ),
- 'length' => 'integer',
- 'redirect' => 'boolean',
- 'new' => 'boolean',
- 'starttimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- ),
- 'contentmodel' => 'string',
- ),
- 'watched' => array(
- 'watched' => 'boolean'
- ),
- 'watchers' => array(
- 'watchers' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'notificationtimestamp' => array(
- 'notificationtimestamp' => array(
- ApiBase::PROP_TYPE => 'timestamp',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'talkid' => array(
- 'talkid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'subjectid' => array(
- 'subjectid' => array(
- ApiBase::PROP_TYPE => 'integer',
- ApiBase::PROP_NULLABLE => true
- )
- ),
- 'url' => array(
- 'fullurl' => 'string',
- 'editurl' => 'string'
- ),
- 'readable' => array(
- 'readable' => 'boolean'
- ),
- 'preload' => array(
- 'preload' => 'string'
- ),
- 'displaytitle' => array(
- 'displaytitle' => 'string'
- )
- );
-
- self::addTokenProperties( $props, $this->getTokenFunctions() );
-
- return $props;
- }
-
public function getDescription() {
return 'Get basic page information such as namespace, title, last touched date, ...';
}
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
index 5bd451b6..34842c63 100644
--- a/includes/api/ApiQueryLangBacklinks.php
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'lbl' );
}
@@ -44,7 +44,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
+ * @param ApiPageSet $resultPageSet
* @return void
*/
public function run( $resultPageSet = null ) {
@@ -92,14 +92,14 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'ORDER BY', array(
'll_title' . $sort,
'll_from' . $sort
- ));
+ ) );
}
} else {
$this->addOption( 'ORDER BY', array(
'll_lang' . $sort,
'll_title' . $sort,
'll_from' . $sort
- ));
+ ) );
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -111,10 +111,14 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
- if ( ++ $count > $params['limit'] ) {
- // We've reached the one extra which shows that there are additional pages to be had. Stop here...
- // Continue string preserved in case the redirect query doesn't pass the limit
- $this->setContinueEnumParameter( 'continue', "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}" );
+ if ( ++$count > $params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here... Continue string
+ // preserved in case the redirect query doesn't pass the limit.
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}"
+ );
break;
}
@@ -140,7 +144,10 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $entry );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}" );
+ $this->setContinueEnumParameter(
+ 'continue',
+ "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}"
+ );
break;
}
}
@@ -202,23 +209,6 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'pageid' => 'integer',
- 'ns' => 'namespace',
- 'title' => 'string',
- 'redirect' => 'boolean'
- ),
- 'lllang' => array(
- 'lllang' => 'string'
- ),
- 'lltitle' => array(
- 'lltitle' => 'string'
- )
- );
- }
-
public function getDescription() {
return array( 'Find all pages that link to the given language link.',
'Can be used to find all links with a language code, or',
@@ -228,12 +218,6 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'lang' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&list=langbacklinks&lbltitle=Test&lbllang=fr',
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index aa796e31..da05f273 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -31,7 +31,7 @@
*/
class ApiQueryLangLinks extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'll' );
}
@@ -41,11 +41,19 @@ class ApiQueryLangLinks extends ApiQueryBase {
}
$params = $this->extractRequestParams();
+ $prop = array_flip( (array)$params['prop'] );
if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'lang' ) );
}
+ // Handle deprecated param
+ $this->requireMaxOneParameter( $params, 'url', 'prop' );
+ if ( $params['url'] ) {
+ $this->logFeatureUsage( 'prop=langlinks&llurl' );
+ $prop = array( 'url' => 1 );
+ }
+
$this->addFields( array(
'll_from',
'll_lang',
@@ -86,9 +94,9 @@ class ApiQueryLangLinks extends ApiQueryBase {
$this->addOption( 'ORDER BY', 'll_lang' . $sort );
} else {
$this->addOption( 'ORDER BY', array(
- 'll_from' . $sort,
- 'll_lang' . $sort
- ));
+ 'll_from' . $sort,
+ 'll_lang' . $sort
+ ) );
}
}
@@ -104,12 +112,18 @@ class ApiQueryLangLinks extends ApiQueryBase {
break;
}
$entry = array( 'lang' => $row->ll_lang );
- if ( $params['url'] ) {
+ if ( isset( $prop['url'] ) ) {
$title = Title::newFromText( "{$row->ll_lang}:{$row->ll_title}" );
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
}
+ if ( isset( $prop['langname'] ) ) {
+ $entry['langname'] = Language::fetchLanguageName( $row->ll_lang, $params['inlanguagecode'] );
+ }
+ if ( isset( $prop['autonym'] ) ) {
+ $entry['autonym'] = Language::fetchLanguageName( $row->ll_lang );
+ }
ApiResult::setContent( $entry, $row->ll_title );
$fit = $this->addPageSubItem( $row->ll_from, $entry );
if ( !$fit ) {
@@ -124,6 +138,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
}
public function getAllowedParams() {
+ global $wgContLang;
return array(
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -133,7 +148,18 @@ class ApiQueryLangLinks extends ApiQueryBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'continue' => null,
- 'url' => false,
+ 'url' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array(
+ 'url',
+ 'langname',
+ 'autonym',
+ )
+ ),
'lang' => null,
'title' => null,
'dir' => array(
@@ -143,6 +169,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
'descending'
)
),
+ 'inlanguagecode' => $wgContLang->getCode(),
);
}
@@ -150,39 +177,29 @@ class ApiQueryLangLinks extends ApiQueryBase {
return array(
'limit' => 'How many langlinks to return',
'continue' => 'When more results are available, use this to continue',
- 'url' => 'Whether to get the full URL',
+ 'url' => "Whether to get the full URL (Cannot be used with {$this->getModulePrefix()}prop)",
+ 'prop' => array(
+ 'Which additional properties to get for each interlanguage link',
+ ' url - Adds the full URL',
+ ' langname - Adds the localised language name (best effort, use CLDR extension)',
+ " Use {$this->getModulePrefix()}inlanguagecode to control the language",
+ ' autonym - Adds the native language name',
+ ),
'lang' => 'Language code',
'title' => "Link to search for. Must be used with {$this->getModulePrefix()}lang",
'dir' => 'The direction in which to list',
- );
- }
-
- public function getResultProperties() {
- return array(
- '' => array(
- 'lang' => 'string',
- 'url' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- '*' => 'string'
- )
+ 'inlanguagecode' => 'Language code for localised language names',
);
}
public function getDescription() {
- return 'Returns all interlanguage links from the given page(s)';
- }
-
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'missingparam', 'lang' ),
- ) );
+ return 'Returns all interlanguage links from the given page(s).';
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=langlinks&titles=Main%20Page&redirects=' => 'Get interlanguage links from the [[Main Page]]',
+ 'api.php?action=query&prop=langlinks&titles=Main%20Page&redirects='
+ => 'Get interlanguage links from the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 937f4f13..71329c4d 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -36,14 +36,15 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
private $table, $prefix, $description, $helpUrl;
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
switch ( $moduleName ) {
case self::LINKS:
$this->table = 'pagelinks';
$this->prefix = 'pl';
$this->description = 'link';
$this->titlesParam = 'titles';
- $this->titlesParamDescription = 'Only list links to these titles. Useful for checking whether a certain page links to a certain title.';
+ $this->titlesParamDescription = 'Only list links to these titles. Useful ' .
+ 'for checking whether a certain page links to a certain title.';
$this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#links_.2F_pl';
break;
case self::TEMPLATES:
@@ -51,7 +52,8 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->prefix = 'tl';
$this->description = 'template';
$this->titlesParam = 'templates';
- $this->titlesParamDescription = 'Only list these templates. Useful for checking whether a certain page uses a certain template.';
+ $this->titlesParamDescription = 'Only list these templates. Useful ' .
+ 'for checking whether a certain page uses a certain template.';
$this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#templates_.2F_tl';
break;
default:
@@ -74,8 +76,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
}
/**
- * @param $resultPageSet ApiPageSet
- * @return
+ * @param ApiPageSet $resultPageSet
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
@@ -212,6 +213,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
public function getParamDescription() {
$desc = $this->description;
+
return array(
'namespace' => "Show {$desc}s in this namespace(s) only",
'limit' => "How many {$desc}s to return",
@@ -221,26 +223,20 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
);
}
- public function getResultProperties() {
- return array(
- '' => array(
- 'ns' => 'namespace',
- 'title' => 'string'
- )
- );
- }
-
public function getDescription() {
- return "Returns all {$this->description}s from the given page(s)";
+ return "Returns all {$this->description}s from the given page(s).";
}
public function getExamples() {
$desc = $this->description;
$name = $this->getModuleName();
+
return array(
"api.php?action=query&prop={$name}&titles=Main%20Page" => "Get {$desc}s from the [[Main Page]]",
- "api.php?action=query&generator={$name}&titles=Main%20Page&prop=info" => "Get information about the {$desc} pages in the [[Main Page]]",
- "api.php?action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10" => "Get {$desc}s from the Main Page in the User and Template namespaces",
+ "api.php?action=query&generator={$name}&titles=Main%20Page&prop=info"
+ => "Get information about the {$desc} pages in the [[Main Page]]",
+ "api.php?action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10"
+ => "Get {$desc}s from the Main Page in the User and Template namespaces",
);
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index ecd117e4..d9dbb5e6 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -31,7 +31,7 @@
*/
class ApiQueryLogEvents extends ApiQueryBase {
- public function __construct( $query, $moduleName ) {
+ public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'le' );
}
@@ -43,6 +43,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
$db = $this->getDB();
+ $this->requireMaxOneParameter( $params, 'title', 'prefix', 'namespace' );
$prop = array_flip( $params['prop'] );
@@ -64,26 +65,32 @@ class ApiQueryLogEvents extends ApiQueryBase {
// Order is significant here
$this->addTables( array( 'logging', 'user', 'page' ) );
- $this->addOption( 'STRAIGHT_JOIN' );
$this->addJoinConds( array(
'user' => array( 'LEFT JOIN',
'user_id=log_user' ),
'page' => array( 'LEFT JOIN',
array( 'log_namespace=page_namespace',
'log_title=page_title' ) ) ) );
- $index = array( 'logging' => 'times' ); // default, may change
$this->addFields( array(
+ 'log_id',
'log_type',
'log_action',
'log_timestamp',
'log_deleted',
) );
- $this->addFieldsIf( array( 'log_id', 'page_id' ), $this->fld_ids );
+ $this->addFieldsIf( 'page_id', $this->fld_ids );
+ // log_page is the page_id saved at log time, whereas page_id is from a
+ // join at query time. This leads to different results in various
+ // scenarios, e.g. deletion, recreation.
+ $this->addFieldsIf( 'log_page', $this->fld_ids );
$this->addFieldsIf( array( 'log_user', 'log_user_text', 'user_name' ), $this->fld_user );
$this->addFieldsIf( 'log_user', $this->fld_userid );
- $this->addFieldsIf( array( 'log_namespace', 'log_title' ), $this->fld_title || $this->fld_parsedcomment );
+ $this->addFieldsIf(
+ array( 'log_namespace', 'log_title' ),
+ $this->fld_title || $this->fld_parsedcomment
+ );
$this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_params', $this->fld_details );
@@ -95,21 +102,59 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
- $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'log_id=ct_log_id' ) ) ) );
+ $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN',
+ array( 'log_id=ct_log_id' ) ) ) );
$this->addWhereFld( 'ct_tag', $params['tag'] );
- $index['change_tag'] = 'change_tag_tag_id';
}
if ( !is_null( $params['action'] ) ) {
- list( $type, $action ) = explode( '/', $params['action'] );
+ // Do validation of action param, list of allowed actions can contains wildcards
+ // Allow the param, when the actions is in the list or a wildcard version is listed.
+ $logAction = $params['action'];
+ if ( strpos( $logAction, '/' ) === false ) {
+ // all items in the list have a slash
+ $valid = false;
+ } else {
+ $logActions = array_flip( $this->getAllowedLogActions() );
+ list( $type, $action ) = explode( '/', $logAction, 2 );
+ $valid = isset( $logActions[$logAction] ) || isset( $logActions[$type . '/*'] );
+ }
+
+ if ( !$valid ) {
+ $valueName = $this->encodeParamName( 'action' );
+ $this->dieUsage(
+ "Unrecognized value for parameter '$valueName': {$logAction}",
+ "unknown_$valueName"
+ );
+ }
+
$this->addWhereFld( 'log_type', $type );
$this->addWhereFld( 'log_action', $action );
} elseif ( !is_null( $params['type'] ) ) {
$this->addWhereFld( 'log_type', $params['type'] );
- $index['logging'] = 'type_time';
}
- $this->addTimestampWhereRange( 'log_timestamp', $params['dir'], $params['start'], $params