summaryrefslogtreecommitdiff
path: root/includes/api
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2013-08-12 09:28:15 +0200
committerPierre Schmitz <pierre@archlinux.de>2013-08-12 09:28:15 +0200
commit08aa4418c30cfc18ccc69a0f0f9cb9e17be6c196 (patch)
tree577a29fb579188d16003a209ce2a2e9c5b0aa2bd /includes/api
parentcacc939b34e315b85e2d72997811eb6677996cc1 (diff)
Update to MediaWiki 1.21.1
Diffstat (limited to 'includes/api')
-rw-r--r--includes/api/ApiBase.php386
-rw-r--r--includes/api/ApiBlock.php29
-rw-r--r--includes/api/ApiComparePages.php22
-rw-r--r--includes/api/ApiCreateAccount.php298
-rw-r--r--includes/api/ApiDelete.php25
-rw-r--r--includes/api/ApiDisabled.php8
-rw-r--r--includes/api/ApiEditPage.php181
-rw-r--r--includes/api/ApiEmailUser.php10
-rw-r--r--includes/api/ApiExpandTemplates.php10
-rw-r--r--includes/api/ApiFeedContributions.php26
-rw-r--r--includes/api/ApiFeedWatchlist.php20
-rw-r--r--includes/api/ApiFileRevert.php16
-rw-r--r--includes/api/ApiFormatBase.php48
-rw-r--r--includes/api/ApiFormatDbg.php8
-rw-r--r--includes/api/ApiFormatDump.php8
-rw-r--r--includes/api/ApiFormatJson.php6
-rw-r--r--includes/api/ApiFormatNone.php43
-rw-r--r--includes/api/ApiFormatPhp.php8
-rw-r--r--includes/api/ApiFormatRaw.php4
-rw-r--r--includes/api/ApiFormatTxt.php8
-rw-r--r--includes/api/ApiFormatWddx.php8
-rw-r--r--includes/api/ApiFormatXml.php20
-rw-r--r--includes/api/ApiFormatYaml.php6
-rw-r--r--includes/api/ApiHelp.php91
-rw-r--r--includes/api/ApiImageRotate.php232
-rw-r--r--includes/api/ApiImport.php14
-rw-r--r--includes/api/ApiLogin.php8
-rw-r--r--includes/api/ApiLogout.php8
-rw-r--r--includes/api/ApiMain.php289
-rw-r--r--includes/api/ApiModuleManager.php171
-rw-r--r--includes/api/ApiMove.php25
-rw-r--r--includes/api/ApiOpenSearch.php8
-rw-r--r--includes/api/ApiOptions.php68
-rw-r--r--includes/api/ApiPageSet.php560
-rw-r--r--includes/api/ApiParamInfo.php114
-rw-r--r--includes/api/ApiParse.php132
-rw-r--r--includes/api/ApiPatrol.php8
-rw-r--r--includes/api/ApiProtect.php10
-rw-r--r--includes/api/ApiPurge.php142
-rw-r--r--includes/api/ApiQuery.php649
-rw-r--r--includes/api/ApiQueryAllCategories.php16
-rw-r--r--includes/api/ApiQueryAllImages.php63
-rw-r--r--includes/api/ApiQueryAllLinks.php138
-rw-r--r--includes/api/ApiQueryAllMessages.php21
-rw-r--r--includes/api/ApiQueryAllPages.php20
-rw-r--r--includes/api/ApiQueryAllUsers.php18
-rw-r--r--includes/api/ApiQueryBacklinks.php35
-rw-r--r--includes/api/ApiQueryBase.php138
-rw-r--r--includes/api/ApiQueryBlocks.php18
-rw-r--r--includes/api/ApiQueryCategories.php11
-rw-r--r--includes/api/ApiQueryCategoryInfo.php5
-rw-r--r--includes/api/ApiQueryCategoryMembers.php17
-rw-r--r--includes/api/ApiQueryDeletedrevs.php23
-rw-r--r--includes/api/ApiQueryDisabled.php8
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php19
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php20
-rw-r--r--includes/api/ApiQueryExternalLinks.php16
-rw-r--r--includes/api/ApiQueryFilearchive.php35
-rw-r--r--includes/api/ApiQueryIWBacklinks.php10
-rw-r--r--includes/api/ApiQueryIWLinks.php10
-rw-r--r--includes/api/ApiQueryImageInfo.php122
-rw-r--r--includes/api/ApiQueryImages.php17
-rw-r--r--includes/api/ApiQueryInfo.php149
-rw-r--r--includes/api/ApiQueryLangBacklinks.php10
-rw-r--r--includes/api/ApiQueryLangLinks.php12
-rw-r--r--includes/api/ApiQueryLinks.php17
-rw-r--r--includes/api/ApiQueryLogEvents.php51
-rw-r--r--includes/api/ApiQueryORM.php264
-rw-r--r--includes/api/ApiQueryPagePropNames.php116
-rw-r--r--includes/api/ApiQueryPageProps.php17
-rw-r--r--includes/api/ApiQueryPagesWithProp.php189
-rw-r--r--includes/api/ApiQueryProtectedTitles.php9
-rw-r--r--includes/api/ApiQueryQueryPage.php8
-rw-r--r--includes/api/ApiQueryRandom.php6
-rw-r--r--includes/api/ApiQueryRecentChanges.php63
-rw-r--r--includes/api/ApiQueryRevisions.php180
-rw-r--r--includes/api/ApiQuerySearch.php8
-rw-r--r--includes/api/ApiQuerySiteinfo.php57
-rw-r--r--includes/api/ApiQueryStashImageInfo.php7
-rw-r--r--includes/api/ApiQueryTags.php6
-rw-r--r--includes/api/ApiQueryUserContributions.php13
-rw-r--r--includes/api/ApiQueryUserInfo.php10
-rw-r--r--includes/api/ApiQueryUsers.php76
-rw-r--r--includes/api/ApiQueryWatchlist.php18
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php10
-rw-r--r--includes/api/ApiResult.php99
-rw-r--r--includes/api/ApiRollback.php10
-rw-r--r--includes/api/ApiRsd.php12
-rw-r--r--includes/api/ApiSetNotificationTimestamp.php66
-rw-r--r--includes/api/ApiTokens.php70
-rw-r--r--includes/api/ApiUnblock.php8
-rw-r--r--includes/api/ApiUndelete.php18
-rw-r--r--includes/api/ApiUpload.php275
-rw-r--r--includes/api/ApiUserrights.php8
-rw-r--r--includes/api/ApiWatch.php25
95 files changed, 4197 insertions, 2197 deletions
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 875a3814..9351a8d8 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -66,14 +66,22 @@ abstract class ApiBase extends ContextSource {
const LIMIT_SML1 = 50; // Slow query, std user limit
const LIMIT_SML2 = 500; // Slow query, bot/sysop limit
+ /**
+ * getAllowedParams() flag: When set, the result could take longer to generate,
+ * but should be more thorough. E.g. get the list of generators for ApiSandBox extension
+ * @since 1.21
+ */
+ const GET_VALUES_FOR_HELP = 1;
+
private $mMainModule, $mModuleName, $mModulePrefix;
+ private $mSlaveDB = null;
private $mParamCache = array();
/**
* Constructor
* @param $mainModule ApiMain object
- * @param $moduleName string Name of this module
- * @param $modulePrefix string Prefix to use for parameter names
+ * @param string $moduleName Name of this module
+ * @param string $modulePrefix Prefix to use for parameter names
*/
public function __construct( $mainModule, $moduleName, $modulePrefix = '' ) {
$this->mMainModule = $mainModule;
@@ -105,15 +113,19 @@ abstract class ApiBase extends ContextSource {
* The result data should be stored in the ApiResult object available
* through getResult().
*/
- public abstract function execute();
+ 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
*/
- public abstract function getVersion();
+ public function getVersion() {
+ wfDeprecated( __METHOD__, '1.21' );
+ return '';
+ }
/**
* Get the name of the module being executed by this instance
@@ -124,6 +136,15 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Get the module manager, or null if this module has no sub-modules
+ * @since 1.21
+ * @return ApiModuleManager
+ */
+ public function getModuleManager() {
+ return null;
+ }
+
+ /**
* Get parameter prefix (usually two letters or an empty string).
* @return string
*/
@@ -168,7 +189,7 @@ abstract class ApiBase extends ContextSource {
* @return ApiResult
*/
public function getResult() {
- // Main module has getResult() method overriden
+ // Main module has getResult() method overridden
// Safety - avoid infinite loop:
if ( $this->isMain() ) {
ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
@@ -203,26 +224,32 @@ abstract class ApiBase extends ContextSource {
* section to notice any changes in API. Multiple calls to this
* function will result in the warning messages being separated by
* newlines
- * @param $warning string Warning message
+ * @param string $warning Warning message
*/
public function setWarning( $warning ) {
$result = $this->getResult();
$data = $result->getData();
- if ( isset( $data['warnings'][$this->getModuleName()] ) ) {
+ $moduleName = $this->getModuleName();
+ if ( isset( $data['warnings'][$moduleName] ) ) {
// Don't add duplicate warnings
- $warn_regex = preg_quote( $warning, '/' );
- if ( preg_match( "/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'] ) ) {
- return;
+ $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;
+ }
}
- $oldwarning = $data['warnings'][$this->getModuleName()]['*'];
// If there is a warning already, append it to the existing one
- $warning = "$oldwarning\n$warning";
- $result->unsetValue( 'warnings', $this->getModuleName() );
+ $warning = "$oldWarning\n$warning";
}
$msg = array();
ApiResult::setContent( $msg, $warning );
$result->disableSizeCheck();
- $result->addValue( 'warnings', $this->getModuleName(), $msg );
+ $result->addValue( 'warnings', $moduleName,
+ $msg, ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP );
$result->enableSizeCheck();
}
@@ -254,6 +281,8 @@ abstract class ApiBase extends ContextSource {
}
$msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+ $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
+
if ( $this->isReadMode() ) {
$msg .= "\nThis module requires read rights";
}
@@ -297,25 +326,6 @@ abstract class ApiBase extends ContextSource {
}
}
}
-
- $msg .= $this->makeHelpArrayToString( $lnPrfx, "Help page", $this->getHelpUrls() );
-
- if ( $this->getMain()->getShowVersions() ) {
- $versions = $this->getVersion();
- $pattern = '/(\$.*) ([0-9a-z_]+\.php) (.*\$)/i';
- $callback = array( $this, 'makeHelpMsg_callback' );
-
- if ( is_array( $versions ) ) {
- foreach ( $versions as &$v ) {
- $v = preg_replace_callback( $pattern, $callback, $v );
- }
- $versions = implode( "\n ", $versions );
- } else {
- $versions = preg_replace_callback( $pattern, $callback, $versions );
- }
-
- $msg .= "Version:\n $versions\n";
- }
}
return $msg;
@@ -330,8 +340,8 @@ abstract class ApiBase extends ContextSource {
}
/**
- * @param $prefix string Text to split output items
- * @param $title string What is being output
+ * @param string $prefix Text to split output items
+ * @param string $title What is being output
* @param $input string|array
* @return string
*/
@@ -340,13 +350,15 @@ abstract class ApiBase extends ContextSource {
return '';
}
if ( !is_array( $input ) ) {
- $input = array(
- $input
- );
+ $input = array( $input );
}
if ( count( $input ) > 0 ) {
- $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
+ if ( $title ) {
+ $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
+ } else {
+ $msg = ' ';
+ }
$msg .= implode( $prefix, $input ) . "\n";
return $msg;
}
@@ -359,7 +371,7 @@ abstract class ApiBase extends ContextSource {
* @return string or false
*/
public function makeHelpMsgParameters() {
- $params = $this->getFinalParams();
+ $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
if ( $params ) {
$paramsDescription = $this->getFinalParamDescription();
@@ -416,7 +428,7 @@ abstract class ApiBase extends ContextSource {
if ( $t === '' ) {
$nothingPrompt = 'Can be empty, or ';
} else {
- $choices[] = $t;
+ $choices[] = $t;
}
}
$desc .= $paramPrefix . $nothingPrompt . $prompt;
@@ -455,6 +467,9 @@ abstract class ApiBase extends ContextSource {
$desc .= $paramPrefix . $intRangeStr;
}
break;
+ case 'upload':
+ $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
+ break;
}
}
@@ -487,44 +502,6 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Callback for preg_replace_callback() call in makeHelpMsg().
- * Replaces a source file name with a link to ViewVC
- *
- * @param $matches array
- * @return string
- */
- public function makeHelpMsg_callback( $matches ) {
- global $wgAutoloadClasses, $wgAutoloadLocalClasses;
-
- $file = '';
- if ( isset( $wgAutoloadLocalClasses[get_class( $this )] ) ) {
- $file = $wgAutoloadLocalClasses[get_class( $this )];
- } elseif ( isset( $wgAutoloadClasses[get_class( $this )] ) ) {
- $file = $wgAutoloadClasses[get_class( $this )];
- }
-
- // Do some guesswork here
- $path = strstr( $file, 'includes/api/' );
- if ( $path === false ) {
- $path = strstr( $file, 'extensions/' );
- } else {
- $path = 'phase3/' . $path;
- }
-
- // Get the filename from $matches[2] instead of $file
- // If they're not the same file, they're assumed to be in the
- // same directory
- // This is necessary to make stuff like ApiMain::getVersion()
- // returning the version string for ApiBase work
- if ( $path ) {
- return "{$matches[0]}\n https://svn.wikimedia.org/" .
- "viewvc/mediawiki/trunk/" . dirname( $path ) .
- "/{$matches[2]}";
- }
- return $matches[0];
- }
-
- /**
* Returns the description string for this module
* @return mixed string or array of strings
*/
@@ -545,15 +522,22 @@ abstract class ApiBase extends ContextSource {
* 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
*/
- protected function getAllowedParams() {
+ 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;
}
/**
* Returns an array of parameter descriptions.
- * Don't call this functon directly: use getFinalParamDescription() to
+ * Don't call this function directly: use getFinalParamDescription() to
* allow hooks to modify descriptions as needed.
* @return array|bool False on no parameter descriptions
*/
@@ -565,11 +549,13 @@ abstract class ApiBase extends ContextSource {
* Get final list of parameters, after hooks have had a chance to
* tweak it as needed.
*
+ * @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
* @return array|Bool False on no parameters
+ * @since 1.21 $flags param added
*/
- public function getFinalParams() {
- $params = $this->getAllowedParams();
- wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params ) );
+ public function getFinalParams( $flags = 0 ) {
+ $params = $this->getAllowedParams( $flags );
+ wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
return $params;
}
@@ -596,7 +582,7 @@ abstract class ApiBase extends ContextSource {
* The array can also contain a boolean under the key PROP_LIST,
* indicating whether the result is a list.
*
- * Don't call this functon directly: use getFinalResultProperties() to
+ * Don't call this function directly: use getFinalResultProperties() to
* allow hooks to modify descriptions as needed.
*
* @return array|bool False on no properties
@@ -645,7 +631,7 @@ abstract class ApiBase extends ContextSource {
/**
* This method mangles parameter name based on the prefix supplied to the constructor.
* Override this method to change parameter name during runtime
- * @param $paramName string Parameter name
+ * @param string $paramName Parameter name
* @return string Prefixed parameter name
*/
public function encodeParamName( $paramName ) {
@@ -680,8 +666,8 @@ abstract class ApiBase extends ContextSource {
/**
* Get a value for the given parameter
- * @param $paramName string Parameter name
- * @param $parseLimit bool see extractRequestParams()
+ * @param string $paramName Parameter name
+ * @param bool $parseLimit see extractRequestParams()
* @return mixed Parameter value
*/
protected function getParameter( $paramName, $parseLimit = true ) {
@@ -692,7 +678,7 @@ abstract class ApiBase extends ContextSource {
/**
* Die if none or more than one of a certain set of parameters is set and not false.
- * @param $params array of parameter names
+ * @param array $params of parameter names
*/
public function requireOnlyOneParameter( $params ) {
$required = func_get_args();
@@ -703,7 +689,7 @@ 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', "{$p}invalidparammix" );
+ $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', "{$p}invalidparammix" );
} elseif ( count( $intersection ) == 0 ) {
$this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
}
@@ -760,7 +746,7 @@ abstract class ApiBase extends ContextSource {
/**
* @param $params array
- * @param $load bool|string Whether load the object's state from the database:
+ * @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
* - 'fromdbmaster': load from the master database
@@ -772,9 +758,12 @@ abstract class ApiBase extends ContextSource {
$pageObj = null;
if ( isset( $params['title'] ) ) {
$titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj ) {
+ if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
+ if ( !$titleObj->canExist() ) {
+ $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
+ }
$pageObj = WikiPage::factory( $titleObj );
if ( $load !== false ) {
$pageObj->loadPageData( $load );
@@ -806,7 +795,7 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Callback function used in requireOnlyOneParameter to check whether reequired parameters are set
+ * Callback function used in requireOnlyOneParameter to check whether required parameters are set
*
* @param $x object Parameter to check is not null/false
* @return bool
@@ -827,9 +816,9 @@ abstract class ApiBase extends ContextSource {
/**
* Return true if we're to watch the page, false if not, null if no change.
- * @param $watchlist String Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param $titleObj Title the page under consideration
- * @param $userOption String The user option to consider when $watchlist=preferences.
+ * @param string $userOption The user option to consider when $watchlist=preferences.
* If not set will magically default to either watchdefault or watchcreations
* @return bool
*/
@@ -849,13 +838,13 @@ abstract class ApiBase extends ContextSource {
if ( $userWatching ) {
return true;
}
- # If no user option was passed, use watchdefault or watchcreation
+ # If no user option was passed, use watchdefault or watchcreations
if ( is_null( $userOption ) ) {
$userOption = $titleObj->exists()
? 'watchdefault' : 'watchcreations';
}
# Watch the article based on the user preference
- return (bool)$this->getUser()->getOption( $userOption );
+ return $this->getUser()->getBoolOption( $userOption );
case 'nochange':
return $userWatching;
@@ -867,9 +856,9 @@ abstract class ApiBase extends ContextSource {
/**
* Set a watch (or unwatch) based the based on a watchlist parameter.
- * @param $watch String Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
+ * @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param $titleObj Title the article's title to change
- * @param $userOption String The user option to consider when $watch=preferences
+ * @param string $userOption The user option to consider when $watch=preferences
*/
protected function setWatch( $watch, $titleObj, $userOption = null ) {
$value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
@@ -888,8 +877,8 @@ abstract class ApiBase extends ContextSource {
/**
* Using the settings determine the value for the given parameter
*
- * @param $paramName String: parameter name
- * @param $paramSettings array|mixed 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?
* @return mixed Parameter value
@@ -929,9 +918,32 @@ abstract class ApiBase extends ContextSource {
ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'. Boolean parameters must default to false." );
}
- $value = $this->getRequest()->getCheck( $encParamName );
+ $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." );
+ }
+ if ( $multi ) {
+ ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
+ }
+ $value = $this->getMain()->getUpload( $encParamName );
+ if ( !$value->exists() ) {
+ // This will get the value without trying to normalize it
+ // (because trying to normalize a large binary file
+ // accidentally uploaded as a field fails spectacularly)
+ $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
+ 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.",
+ "badupload_{$encParamName}"
+ );
+ }
+ }
} else {
- $value = $this->getRequest()->getVal( $encParamName, $default );
+ $value = $this->getMain()->getVal( $encParamName, $default );
if ( isset( $value ) && $type == 'namespace' ) {
$type = MWNamespace::getValidNamespaces();
@@ -953,7 +965,6 @@ abstract class ApiBase extends ContextSource {
if ( $required && $value === '' ) {
$this->dieUsageMsg( array( 'missingparam', $paramName ) );
}
-
break;
case 'integer': // Force everything using intval() and optionally validate limits
$min = isset ( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
@@ -1010,29 +1021,23 @@ abstract class ApiBase extends ContextSource {
}
break;
case 'user':
- if ( !is_array( $value ) ) {
- $value = array( $value );
- }
-
- foreach ( $value as $key => $val ) {
- $title = Title::makeTitleSafe( NS_USER, $val );
- if ( is_null( $title ) ) {
- $this->dieUsage( "Invalid value for user parameter $encParamName", "baduser_{$encParamName}" );
+ if ( is_array( $value ) ) {
+ foreach ( $value as $key => $val ) {
+ $value[$key] = $this->validateUser( $val, $encParamName );
}
- $value[$key] = $title->getText();
- }
-
- if ( !$multi ) {
- $value = $value[0];
+ } else {
+ $value = $this->validateUser( $value, $encParamName );
}
break;
+ case 'upload': // nothing to do
+ break;
default:
ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
}
}
// Throw out duplicates if requested
- if ( is_array( $value ) && !$dupes ) {
+ if ( !$dupes && is_array( $value ) ) {
$value = array_unique( $value );
}
@@ -1051,10 +1056,10 @@ abstract class ApiBase extends ContextSource {
* Return an array of values that were given in a 'a|b|c' notation,
* after it optionally validates them against the list allowed values.
*
- * @param $valueName string The name of the parameter (for error
+ * @param string $valueName The name of the parameter (for error
* reporting)
* @param $value mixed The value being parsed
- * @param $allowMultiple bool Can $value contain more than one value
+ * @param bool $allowMultiple Can $value contain more than one value
* separated by '|'?
* @param $allowedValues mixed An array of values to check against. If
* null, all values are accepted.
@@ -1106,11 +1111,11 @@ abstract class ApiBase extends ContextSource {
/**
* Validate the value against the minimum and user/bot maximum limits.
* Prints usage info on failure.
- * @param $paramName string Parameter name
- * @param $value int Parameter value
- * @param $min int|null Minimum value
- * @param $max int|null Maximum value for users
- * @param $botMax int Maximum value for sysops/bots
+ * @param string $paramName Parameter name
+ * @param int $value Parameter value
+ * @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
*/
function validateLimit( $paramName, &$value, $min, $max, $botMax = null, $enforceLimits = false ) {
@@ -1144,16 +1149,31 @@ abstract class ApiBase extends ContextSource {
}
/**
- * @param $value string
- * @param $paramName string
- * @return string
+ * Validate and normalize of parameters of type 'timestamp'
+ * @param string $value Parameter value
+ * @param string $encParamName Parameter name
+ * @return string Validated and normalized parameter
+ */
+ function validateTimestamp( $value, $encParamName ) {
+ $unixTimestamp = wfTimestamp( TS_UNIX, $value );
+ if ( $unixTimestamp === false ) {
+ $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" );
+ }
+ return wfTimestamp( TS_MW, $unixTimestamp );
+ }
+
+ /**
+ * Validate and normalize of parameters of type 'user'
+ * @param string $value Parameter value
+ * @param string $encParamName Parameter value
+ * @return string Validated and normalized parameter
*/
- function validateTimestamp( $value, $paramName ) {
- $value = wfTimestamp( TS_UNIX, $value );
- if ( $value === 0 ) {
- $this->dieUsage( "Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$paramName}" );
+ 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}" );
}
- return wfTimestamp( TS_MW, $value );
+ return $title->getText();
}
/**
@@ -1172,8 +1192,8 @@ abstract class ApiBase extends ContextSource {
/**
* Truncate an array to a certain length.
- * @param $arr array Array to truncate
- * @param $limit int Maximum length
+ * @param array $arr Array to truncate
+ * @param int $limit Maximum length
* @return bool True if the array was truncated, false otherwise
*/
public static function truncateArray( &$arr, $limit ) {
@@ -1189,12 +1209,12 @@ abstract class ApiBase extends ContextSource {
* Throw a UsageException, which will (if uncaught) call the main module's
* error handler and die with an error message.
*
- * @param $description string One-line human-readable description of the
+ * @param string $description One-line human-readable description of the
* error condition, e.g., "The API requires a valid action parameter"
- * @param $errorCode string Brief, arbitrary, stable string to allow easy
+ * @param string $errorCode Brief, arbitrary, stable string to allow easy
* automated identification of the error, e.g., 'unknown_action'
- * @param $httpRespCode int HTTP response code
- * @param $extradata array Data to add to the "<error>" element; array in ApiResult format
+ * @param int $httpRespCode HTTP response code
+ * @param array $extradata Data to add to the "<error>" element; array in ApiResult format
* @throws UsageException
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
@@ -1226,7 +1246,7 @@ abstract class ApiBase extends ContextSource {
'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 e-mail address before you can edit" ),
+ '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" ),
@@ -1254,15 +1274,15 @@ abstract class ApiBase extends ContextSource {
'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 invidually, but you can unblock the range as a whole." ),
+ '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 e-mail address, or you are not allowed to send e-mail to other users, so you cannot send e-mail" ),
+ '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 e-mail" ),
+ '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 e-mail address, or has chosen not to receive e-mail from other users" ),
+ '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" ),
@@ -1291,7 +1311,7 @@ abstract class ApiBase extends ContextSource {
'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 e-mail through the wiki" ),
+ '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" ),
@@ -1349,8 +1369,8 @@ abstract class ApiBase extends ContextSource {
// 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.' ),
+ '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.' ),
'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
@@ -1385,8 +1405,41 @@ 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
+ * @since 1.21
+ */
+ public function dieUsageMsgOrDebug( $error ) {
+ global $wgDebugAPI;
+ if( $wgDebugAPI !== true ) {
+ $this->dieUsageMsg( $error );
+ } else {
+ 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
+ * @since 1.21
+ */
+ protected function dieContinueUsageIf( $condition ) {
+ if ( $condition ) {
+ $this->dieUsage(
+ 'Invalid continue param. You should pass the original value returned by the previous query',
+ 'badcontinue' );
+ }
+ }
+
+ /**
* Return the error message related to a certain array
- * @param $error array Element of a getUserPermissionsErrors()-style array
+ * @param array $error Element of a getUserPermissionsErrors()-style array
* @return array('code' => code, 'info' => info)
*/
public function parseMsg( $error ) {
@@ -1395,7 +1448,7 @@ abstract class ApiBase extends ContextSource {
// Check whether the error array was nested
// array( array( <code>, <params> ), array( <another_code>, <params> ) )
- if( is_array( $key ) ){
+ if( is_array( $key ) ) {
$error = $key;
$key = array_shift( $error );
}
@@ -1413,8 +1466,8 @@ abstract class ApiBase extends ContextSource {
/**
* Internal code errors should be reported with this method
- * @param $method string Method or function name
- * @param $message string Error message
+ * @param string $method Method or function name
+ * @param string $message Error message
*/
protected static function dieDebug( $method, $message ) {
wfDebugDieBacktrace( "Internal error in $method: $message" );
@@ -1515,10 +1568,17 @@ abstract class ApiBase extends ContextSource {
$params = $this->getFinalParams();
if ( $params ) {
foreach ( $params as $paramName => $paramSettings ) {
- if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) ) {
+ if ( isset( $paramSettings[ApiBase::PARAM_REQUIRED] ) && $paramSettings[ApiBase::PARAM_REQUIRED] ) {
$ret[] = array( 'missingparam', $paramName );
}
}
+ if ( array_key_exists( 'continue', $params ) ) {
+ $ret[] = array(
+ array(
+ 'code' => 'badcontinue',
+ 'info' => 'Invalid continue param. You should pass the original value returned by the previous query'
+ ) );
+ }
}
if ( $this->mustBePosted() ) {
@@ -1544,7 +1604,7 @@ abstract class ApiBase extends ContextSource {
/**
* Parses a list of errors into a standardised format
- * @param $errors array List of errors. Items can be in the for array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... )
+ * @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 ) {
@@ -1666,17 +1726,23 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Gets a default slave database connection object
* @return DatabaseBase
*/
protected function getDB() {
- return wfGetDB( DB_SLAVE, 'api' );
+ if ( !isset( $this->mSlaveDB ) ) {
+ $this->profileDBIn();
+ $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
+ $this->profileDBOut();
+ }
+ return $this->mSlaveDB;
}
/**
* Debugging function that prints a value and an optional backtrace
* @param $value mixed Value to print
- * @param $name string Description of the printed value
- * @param $backtrace bool If true, print a backtrace
+ * @param string $name Description of the printed value
+ * @param bool $backtrace If true, print a backtrace
*/
public static function debugPrint( $value, $name = 'unknown', $backtrace = false ) {
print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
@@ -1686,12 +1752,4 @@ abstract class ApiBase extends ContextSource {
}
print "\n</pre>\n";
}
-
- /**
- * Returns a string that identifies the version of this class.
- * @return string
- */
- public static function getBaseVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index c879b35d..90432b95 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -25,17 +25,13 @@
*/
/**
-* API module that facilitates the blocking of users. Requires API write mode
-* to be enabled.
-*
+ * API module that facilitates the blocking of users. Requires API write mode
+ * to be enabled.
+ *
* @ingroup API
*/
class ApiBlock extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Blocks the user specified in the parameters for the given expiry, with the
* given reason, and with all other settings provided in the params. If the block
@@ -55,6 +51,7 @@ class ApiBlock extends ApiBase {
if ( !$user->isAllowed( 'block' ) ) {
$this->dieUsageMsg( 'cantblock' );
}
+
# bug 15810: blocked admins should have limited access here
if ( $user->isBlocked() ) {
$status = SpecialBlock::checkUnblockSelf( $params['user'], $user );
@@ -62,6 +59,13 @@ class ApiBlock extends ApiBase {
$this->dieUsageMsg( array( $status ) );
}
}
+
+ $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() ) ) ) {
+ $this->dieUsageMsg( array( 'nosuchuser', $params['user'] ) );
+ }
+
if ( $params['hidename'] && !$user->isAllowed( 'hideuser' ) ) {
$this->dieUsageMsg( 'canthide' );
}
@@ -70,6 +74,7 @@ class ApiBlock extends ApiBase {
}
$data = array(
+ 'PreviousTarget' => $params['user'],
'Target' => $params['user'],
'Reason' => array(
$params['reason'],
@@ -83,7 +88,7 @@ class ApiBlock extends ApiBase {
'DisableEmail' => $params['noemail'],
'HideUser' => $params['hidename'],
'DisableUTEdit' => !$params['allowusertalk'],
- 'AlreadyBlocked' => $params['reblock'],
+ 'Reblock' => $params['reblock'],
'Watch' => $params['watchuser'],
'Confirm' => true,
);
@@ -99,7 +104,7 @@ class ApiBlock extends ApiBase {
$res['userID'] = $target instanceof User ? $target->getId() : 0;
$block = Block::newFromTarget( $target );
- if( $block instanceof Block ){
+ if( $block instanceof Block ) {
$res['expiry'] = $block->mExpiry == $this->getDB()->getInfinity()
? 'infinite'
: wfTimestamp( TS_ISO_8601, $block->mExpiry );
@@ -178,7 +183,7 @@ class ApiBlock extends ApiBase {
'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 e-mail through the wiki. (Requires the "blockemail" right.)',
+ '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)',
'reblock' => 'If the user is already blocked, overwrite the existing block',
@@ -256,8 +261,4 @@ class ApiBlock extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Block';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
index ed72b29b..79ffcb0a 100644
--- a/includes/api/ApiComparePages.php
+++ b/includes/api/ApiComparePages.php
@@ -25,17 +25,21 @@
class ApiComparePages extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$params = $this->extractRequestParams();
$rev1 = $this->revisionOrTitleOrId( $params['fromrev'], $params['fromtitle'], $params['fromid'] );
$rev2 = $this->revisionOrTitleOrId( $params['torev'], $params['totitle'], $params['toid'] );
- $de = new DifferenceEngine( $this->getContext(),
+ $revision = Revision::newFromId( $rev1 );
+
+ if ( !$revision ) {
+ $this->dieUsage( 'The diff cannot be retrieved, ' .
+ 'one revision does not exist or you do not have permission to view it.', 'baddiff' );
+ }
+
+ $contentHandler = $revision->getContentHandler();
+ $de = $contentHandler->createDifferenceEngine( $this->getContext(),
$rev1,
$rev2,
null, // rcid
@@ -77,11 +81,11 @@ class ApiComparePages extends ApiBase {
* @return int
*/
private function revisionOrTitleOrId( $revision, $titleText, $titleId ) {
- if( $revision ){
+ if( $revision ) {
return $revision;
} elseif( $titleText ) {
$title = Title::newFromText( $titleText );
- if( !$title ){
+ if( !$title || $title->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
}
return $title->getLatestRevID();
@@ -164,8 +168,4 @@ class ApiComparePages extends ApiBase {
'api.php?action=compare&fromrev=1&torev=2' => 'Create a diff between revision 1 and 2',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiCreateAccount.php b/includes/api/ApiCreateAccount.php
new file mode 100644
index 00000000..55c60cce
--- /dev/null
+++ b/includes/api/ApiCreateAccount.php
@@ -0,0 +1,298 @@
+<?php
+/**
+ * Created on August 7, 2012
+ *
+ * Copyright © 2012 Tyler Romeo <tylerromeo@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
+ */
+
+/**
+ * Unit to authenticate account registration attempts to the current wiki.
+ *
+ * @ingroup API
+ */
+class ApiCreateAccount extends ApiBase {
+ public function execute() {
+
+ // $loginForm->addNewaccountInternal will throw exceptions
+ // if wiki is read only (already handled by api), user is blocked or does not have rights.
+ // 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' );
+ }
+ if ( $this->getUser()->isBlockedFromCreateAccount() ) {
+ $this->dieUsage( 'You cannot create a new account because you are blocked', 'blocked' );
+ }
+
+ $params = $this->extractRequestParams();
+
+ $result = array();
+
+ // Init session if necessary
+ if ( session_id() == '' ) {
+ wfSetupSession();
+ }
+
+ if( $params['mailpassword'] && !$params['email'] ) {
+ $this->dieUsageMsg( 'noemail' );
+ }
+
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setRequest( new DerivativeRequest(
+ $this->getContext()->getRequest(),
+ array(
+ 'type' => 'signup',
+ 'uselang' => $params['language'],
+ 'wpName' => $params['name'],
+ 'wpPassword' => $params['password'],
+ 'wpRetype' => $params['password'],
+ 'wpDomain' => $params['domain'],
+ 'wpEmail' => $params['email'],
+ 'wpRealName' => $params['realname'],
+ 'wpCreateaccountToken' => $params['token'],
+ 'wpCreateaccount' => $params['mailpassword'] ? null : '1',
+ 'wpCreateaccountMail' => $params['mailpassword'] ? '1' : null
+ )
+ ) );
+
+ $loginForm = new LoginForm();
+ $loginForm->setContext( $context );
+ $loginForm->load();
+
+ $status = $loginForm->addNewaccountInternal();
+ $result = array();
+ if( $status->isGood() ) {
+ // Success!
+ $user = $status->getValue();
+
+ // If we showed up language selection links, and one was in use, be
+ // smart (and sensible) and save that language as the user's preference
+ global $wgLoginLanguageSelector, $wgEmailAuthentication;
+ if( $wgLoginLanguageSelector && $params['language'] ) {
+ $user->setOption( 'language', $params['language'] );
+ }
+
+ 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() ) ) {
+ // Send out an email authentication message if needed
+ $status->merge( $user->sendConfirmationMail() );
+ }
+
+ // Save settings (including confirmation token)
+ $user->saveSettings();
+
+ wfRunHooks( 'AddNewAccount', array( $user, $params['mailpassword'] ) );
+
+ if ( $params['mailpassword'] ) {
+ $logAction = 'byemail';
+ } elseif ( $this->getUser()->isLoggedIn() ) {
+ $logAction = 'create2';
+ } else {
+ $logAction = 'create';
+ }
+ $user->addNewUserLogEntry( $logAction, (string)$params['reason'] );
+
+ // Add username, id, and token to result.
+ $result['username'] = $user->getName();
+ $result['userid'] = $user->getId();
+ $result['token'] = $user->getToken();
+ }
+
+ $apiResult = $this->getResult();
+
+ if( $status->hasMessage( 'sessionfailure' ) || $status->hasMessage( 'nocookiesfornew' ) ) {
+ // Token was incorrect, so add it to result, but don't throw an exception
+ // since not having the correct token is part of the normal
+ // flow of events.
+ $result['token'] = LoginForm::getCreateaccountToken();
+ $result['result'] = 'needtoken';
+ } elseif( !$status->isOK() ) {
+ // There was an error. Die now.
+ // Cannot use dieUsageMsg() directly because extensions
+ // might return custom error messages.
+ $errors = $status->getErrorsArray();
+ if( $errors[0] instanceof Message ) {
+ $code = 'aborted';
+ $desc = $errors[0];
+ } else {
+ $code = array_shift( $errors[0] );
+ $desc = wfMessage( $code, $errors[0] );
+ }
+ $this->dieUsage( $desc, $code );
+ } elseif( !$status->isGood() ) {
+ // Status is not good, but OK. This means warnings.
+ $result['result'] = 'warning';
+
+ // Add any warnings to the result
+ $warnings = $status->getErrorsByType( 'warning' );
+ if( $warnings ) {
+ foreach( $warnings as &$warning ) {
+ $apiResult->setIndexedTagName( $warning['params'], 'param' );
+ }
+ $apiResult->setIndexedTagName( $warnings, 'warning' );
+ $result['warnings'] = $warnings;
+ }
+ } else {
+ // Everything was fine.
+ $result['result'] = 'success';
+ }
+
+ $apiResult->addValue( null, 'createaccount', $result );
+ }
+
+ public function getDescription() {
+ return 'Create a new user account.';
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isReadMode() {
+ return false;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ global $wgEmailConfirmToEdit;
+ return array(
+ 'name' => array(
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'password' => null,
+ 'domain' => null,
+ 'token' => null,
+ 'email' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => $wgEmailConfirmToEdit
+ ),
+ 'realname' => null,
+ 'mailpassword' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false
+ ),
+ 'reason' => null,
+ 'language' => null
+ );
+ }
+
+ public function getParamDescription() {
+ $p = $this->getModulePrefix();
+ return array(
+ 'name' => 'Username',
+ 'password' => "Password (ignored if {$p}mailpassword is set)",
+ 'domain' => 'Domain for external authentication (optional)',
+ 'token' => 'Account creation token obtained in first request',
+ 'email' => 'Email address of user (optional)',
+ '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 )->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)'
+ );
+
+ // 'passwordtooshort' has parameters. :(
+ global $wgMinimalPasswordLength;
+ $errors[] = array(
+ 'code' => 'passwordtooshort',
+ 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->parse()
+ );
+ return $errors;
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=createaccount&name=testuser&password=test123',
+ 'api.php?action=createaccount&name=testmailuser&mailpassword=true&reason=MyReason',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Account_creation';
+ }
+}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index 2d36f19a..d1f0806e 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -32,10 +32,6 @@
*/
class ApiDelete extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* 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
@@ -61,6 +57,9 @@ class ApiDelete extends ApiBase {
$status = self::delete( $pageObj, $user, $params['token'], $reason );
}
+ if ( is_array( $status ) ) {
+ $this->dieUsageMsg( $status[0] );
+ }
if ( !$status->isGood() ) {
$errors = $status->getErrorsArray();
$this->dieUsageMsg( $errors[0] ); // We don't care about multiple errors, just report one of them
@@ -98,11 +97,11 @@ class ApiDelete extends ApiBase {
/**
* We have our own delete() function, since Article.php's implementation is split in two phases
*
- * @param $page WikiPage object to work on
+ * @param $page Page|WikiPage object to work on
* @param $user User doing the action
- * @param $token String: delete token (same as edit token)
- * @param $reason String: reason for the deletion. Autogenerated if NULL
- * @return Status
+ * @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 ) {
$title = $page->getTitle();
@@ -128,13 +127,13 @@ class ApiDelete extends ApiBase {
}
/**
- * @param $page WikiPage object to work on
+ * @param $page WikiPage|Page object to work on
* @param $user User doing the action
* @param $token
* @param $oldimage
* @param $reason
* @param $suppress bool
- * @return Status
+ * @return Status|array
*/
public static function deleteFile( Page $page, User $user, $token, $oldimage, &$reason = null, $suppress = false ) {
$title = $page->getTitle();
@@ -161,7 +160,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 );
+ return FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress, $user );
}
public function mustBePosted() {
@@ -264,8 +263,4 @@ class ApiDelete extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Delete';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index 13975aec..e5ef3b7e 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -36,10 +36,6 @@
*/
class ApiDisabled extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$this->dieUsage( "The \"{$this->getModuleName()}\" module has been disabled.", 'moduledisabled' );
}
@@ -63,8 +59,4 @@ class ApiDisabled extends ApiBase {
public function getExamples() {
return array();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index 0963fe7c..4916145b 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -33,10 +33,6 @@
*/
class ApiEditPage extends ApiBase {
- public function __construct( $query, $moduleName ) {
- parent::__construct( $query, $moduleName );
- }
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
@@ -50,32 +46,28 @@ class ApiEditPage extends ApiBase {
$pageObj = $this->getTitleOrPageId( $params );
$titleObj = $pageObj->getTitle();
- if ( $titleObj->isExternal() ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
- }
-
$apiResult = $this->getResult();
if ( $params['redirect'] ) {
if ( $titleObj->isRedirect() ) {
$oldTitle = $titleObj;
- $titles = Title::newFromRedirectArray(
- Revision::newFromTitle(
- $oldTitle, false, Revision::READ_LATEST
- )->getText( Revision::FOR_THIS_USER )
- );
+ $titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
+ ->getContent( Revision::FOR_THIS_USER, $user )
+ ->getRedirectChain();
// array_shift( $titles );
$redirValues = array();
+
+ /** @var $newTitle Title */
foreach ( $titles as $id => $newTitle ) {
- if ( !isset( $titles[ $id - 1 ] ) ) {
- $titles[ $id - 1 ] = $oldTitle;
+ if ( !isset( $titles[$id - 1] ) ) {
+ $titles[$id - 1] = $oldTitle;
}
$redirValues[] = array(
- 'from' => $titles[ $id - 1 ]->getPrefixedText(),
+ 'from' => $titles[$id - 1]->getPrefixedText(),
'to' => $newTitle->getPrefixedText()
);
@@ -84,9 +76,34 @@ class ApiEditPage extends ApiBase {
$apiResult->setIndexedTagName( $redirValues, 'r' );
$apiResult->addValue( null, 'redirects', $redirValues );
+
+ // Since the page changed, update $pageObj
+ $pageObj = WikiPage::factory( $titleObj );
}
}
+ if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
+ $contentHandler = $pageObj->getContentHandler();
+ } else {
+ $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
+ }
+
+ // @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();
+ }
+
+ $contentFormat = $params['contentformat'];
+
+ if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
+ $name = $titleObj->getPrefixedDBkey();
+ $model = $contentHandler->getModelID();
+
+ $this->dieUsage( "The requested format $contentFormat is not supported for content model ".
+ " $model used by $name", 'badformat' );
+ }
+
if ( $params['createonly'] && $titleObj->exists() ) {
$this->dieUsageMsg( 'createonly-exists' );
}
@@ -103,31 +120,61 @@ class ApiEditPage extends ApiBase {
$this->dieUsageMsg( $errors[0] );
}
- $articleObj = Article::newFromTitle( $titleObj, $this->getContext() );
-
$toMD5 = $params['text'];
if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
{
- // For non-existent pages, Article::getContent()
- // returns an interface message rather than ''
- // We do want getContent()'s behavior for non-existent
- // MediaWiki: pages, though
- if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
- $content = '';
- } else {
- $content = $articleObj->getContent();
+ $content = $pageObj->getContent();
+
+ if ( !$content ) {
+ if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
+ # If this is a MediaWiki:x message, then load the messages
+ # and return the message value for x.
+ $text = $titleObj->getDefaultMessageText();
+ if ( $text === false ) {
+ $text = '';
+ }
+
+ try {
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ return;
+ }
+ } else {
+ # Otherwise, make a new empty content.
+ $content = $contentHandler->makeEmptyContent();
+ }
+ }
+
+ // @todo: Add support for appending/prepending to the Content interface
+
+ if ( !( $content instanceof TextContent ) ) {
+ $mode = $contentHandler->getModelID();
+ $this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
}
if ( !is_null( $params['section'] ) ) {
+ if ( !$contentHandler->supportsSections() ) {
+ $modelName = $contentHandler->getModelID();
+ $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' );
+ }
+
// Process the content for section edits
- global $wgParser;
$section = intval( $params['section'] );
- $content = $wgParser->getSection( $content, $section, false );
- if ( $content === false ) {
+ $content = $content->getSection( $section );
+
+ if ( !$content ) {
$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
}
}
- $params['text'] = $params['prependtext'] . $content . $params['appendtext'];
+
+ if ( !$content ) {
+ $text = '';
+ } else {
+ $text = $content->serialize( $contentFormat );
+ }
+
+ $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
$toMD5 = $params['prependtext'] . $params['appendtext'];
}
@@ -151,18 +198,21 @@ class ApiEditPage extends ApiBase {
$this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
}
- if ( $undoRev->getPage() != $articleObj->getID() ) {
+ if ( $undoRev->getPage() != $pageObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
}
- if ( $undoafterRev->getPage() != $articleObj->getID() ) {
+ if ( $undoafterRev->getPage() != $pageObj->getID() ) {
$this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
}
- $newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
- if ( $newtext === false ) {
+ $newContent = $contentHandler->getUndoContent( $pageObj->getRevision(), $undoRev, $undoafterRev );
+
+ if ( !$newContent ) {
$this->dieUsageMsg( 'undo-failure' );
}
- $params['text'] = $newtext;
+
+ $params['text'] = $newContent->serialize( $params['contentformat'] );
+
// 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'] ) {
@@ -179,6 +229,8 @@ class ApiEditPage extends ApiBase {
// That interface kind of sucks, but it's workable
$requestArray = array(
'wpTextbox1' => $params['text'],
+ 'format' => $contentFormat,
+ 'model' => $contentHandler->getModelID(),
'wpEditToken' => $params['token'],
'wpIgnoreBlankSummary' => ''
);
@@ -191,18 +243,23 @@ class ApiEditPage extends ApiBase {
$requestArray['wpSectionTitle'] = $params['sectiontitle'];
}
+ // TODO: Pass along information from 'undoafter' as well
+ if ( $params['undo'] > 0 ) {
+ $requestArray['wpUndidRevision'] = $params['undo'];
+ }
+
// Watch out for basetimestamp == ''
// wfTimestamp() treats it as NOW, almost certainly causing an edit conflict
if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
$requestArray['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
} else {
- $requestArray['wpEdittime'] = $articleObj->getTimestamp();
+ $requestArray['wpEdittime'] = $pageObj->getTimestamp();
}
if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
$requestArray['wpStarttime'] = wfTimestamp( TS_MW, $params['starttimestamp'] );
} else {
- $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
+ $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
}
if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
@@ -244,7 +301,19 @@ class ApiEditPage extends ApiBase {
// TODO: Make them not or check if they still do
$wgTitle = $titleObj;
- $ep = new EditPage( $articleObj );
+ $articleContext = new RequestContext;
+ $articleContext->setRequest( $req );
+ $articleContext->setWikiPage( $pageObj );
+ $articleContext->setUser( $this->getUser() );
+
+ /** @var $articleObject Article */
+ $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
+
+ $ep = new EditPage( $articleObject );
+
+ // allow editing of non-textual content.
+ $ep->allowNonTextContent = true;
+
$ep->setContextTitle( $titleObj );
$ep->importFormData( $req );
@@ -262,7 +331,7 @@ class ApiEditPage extends ApiBase {
}
// Do the actual save
- $oldRevId = $articleObj->getRevIdFetched();
+ $oldRevId = $articleObject->getRevIdFetched();
$result = null;
// Fake $wgRequest for some hooks inside EditPage
// @todo FIXME: This interface SUCKS
@@ -278,6 +347,9 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_HOOK_ERROR_EXPECTED:
$this->dieUsageMsg( 'hookaborted' );
+ case EditPage::AS_PARSE_ERROR:
+ $this->dieUsage( $status->getMessage(), 'parseerror' );
+
case EditPage::AS_IMAGE_REDIRECT_ANON:
$this->dieUsageMsg( 'noimageredirect-anon' );
@@ -324,19 +396,21 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_SUCCESS_NEW_ARTICLE:
$r['new'] = '';
+ // fall-through
case EditPage::AS_SUCCESS_UPDATE:
$r['result'] = 'Success';
$r['pageid'] = intval( $titleObj->getArticleID() );
$r['title'] = $titleObj->getPrefixedText();
- $newRevId = $articleObj->getLatest();
+ $r['contentmodel'] = $titleObj->getContentModel();
+ $newRevId = $articleObject->getLatest();
if ( $newRevId == $oldRevId ) {
$r['nochange'] = '';
} else {
$r['oldrevid'] = intval( $oldRevId );
$r['newrevid'] = intval( $newRevId );
$r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
- $articleObj->getTimestamp() );
+ $pageObj->getTimestamp() );
}
break;
@@ -380,6 +454,7 @@ class ApiEditPage extends ApiBase {
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' ),
@@ -397,6 +472,13 @@ class ApiEditPage extends ApiBase {
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' ),
)
@@ -414,7 +496,6 @@ class ApiEditPage extends ApiBase {
'section' => null,
'sectiontitle' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => false,
),
'text' => null,
'token' => array(
@@ -460,6 +541,12 @@ class ApiEditPage extends ApiBase {
ApiBase::PARAM_TYPE => 'boolean',
ApiBase::PARAM_DFLT => false,
),
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
@@ -490,7 +577,7 @@ class ApiEditPage extends ApiBase {
'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.",
+ '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.",
@@ -498,6 +585,8 @@ class ApiEditPage extends ApiBase {
'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',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
@@ -546,10 +635,8 @@ class ApiEditPage extends ApiBase {
public function getExamples() {
return array(
-
'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\\'
=> 'Prepend __NOTOC__ to a page (anonymous user)',
'api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\'
@@ -560,8 +647,4 @@ class ApiEditPage extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Edit';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index 4fa03434..cd0d0cba 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -30,10 +30,6 @@
*/
class ApiEmailUser extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$params = $this->extractRequestParams();
@@ -158,10 +154,6 @@ class ApiEmailUser extends ApiBase {
}
public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:E-mail';
- }
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
+ return 'https://www.mediawiki.org/wiki/API:Email';
}
}
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index 160f5b91..f5898fb3 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -33,10 +33,6 @@
*/
class ApiExpandTemplates extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
// Cache may vary on $wgUser because ParserOptions gets data from it
$this->getMain()->setCacheMode( 'anon-public-user-private' );
@@ -46,7 +42,7 @@ class ApiExpandTemplates extends ApiBase {
// Create title for parser
$title_obj = Title::newFromText( $params['title'] );
- if ( !$title_obj ) {
+ if ( !$title_obj || $title_obj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
@@ -130,8 +126,4 @@ class ApiExpandTemplates extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#expandtemplates';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
index 1cf760ae..015a9922 100644
--- a/includes/api/ApiFeedContributions.php
+++ b/includes/api/ApiFeedContributions.php
@@ -29,10 +29,6 @@
*/
class ApiFeedContributions extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* This module uses a custom feed wrapper printer.
*
@@ -51,7 +47,7 @@ class ApiFeedContributions extends ApiBase {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) {
+ if( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
@@ -130,10 +126,22 @@ class ApiFeedContributions extends ApiBase {
protected function feedItemDesc( $revision ) {
if( $revision ) {
$msg = wfMessage( 'colon-separator' )->inContentLanguage()->text();
+ $content = $revision->getContent();
+
+ if ( $content instanceof TextContent ) {
+ // only textual content has a "source view".
+ $html = nl2br( htmlspecialchars( $content->getNativeData() ) );
+ } else {
+ //XXX: we could get an HTML representation of the content via getParserOutput, but that may
+ // contain JS magic and generally may not be suitable for inclusion in a feed.
+ // Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
+ //Compare also FeedUtils::formatDiffRow.
+ $html = '';
+ }
+
return '<p>' . htmlspecialchars( $revision->getUserText() ) . $msg .
htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
- "</p>\n<hr />\n<div>" .
- nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+ "</p>\n<hr />\n<div>" . $html . "</div>";
}
return '';
}
@@ -201,8 +209,4 @@ class ApiFeedContributions extends ApiBase {
'api.php?action=feedcontributions&user=Reedy',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index 6ccb02fe..6c793b36 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -33,10 +33,6 @@
*/
class ApiFeedWatchlist extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* This module uses a custom feed wrapper printer.
*
@@ -62,12 +58,9 @@ class ApiFeedWatchlist extends ApiBase {
$this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
}
- if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) {
+ if( !isset( $wgFeedClasses[$params['feedformat']] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
- if ( !is_null( $params['wlexcludeuser'] ) ) {
- $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
- }
// limit to the number of hours going from now back
$endTime = wfTimestamp( TS_MW, time() - intval( $params['hours'] * 60 * 60 ) );
@@ -84,12 +77,15 @@ class ApiFeedWatchlist extends ApiBase {
'wllimit' => ( 50 > $wgFeedLimit ) ? $wgFeedLimit : 50
);
- if ( !is_null( $params['wlowner'] ) ) {
+ if ( $params['wlowner'] !== null ) {
$fauxReqArr['wlowner'] = $params['wlowner'];
}
- if ( !is_null( $params['wltoken'] ) ) {
+ if ( $params['wltoken'] !== null ) {
$fauxReqArr['wltoken'] = $params['wltoken'];
}
+ if ( $params['wlexcludeuser'] !== null ) {
+ $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
+ }
// Support linking to diffs instead of article
if ( $params['linktodiffs'] ) {
@@ -233,8 +229,4 @@ class ApiFeedWatchlist extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Watchlist_feed';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php
index 83d078d2..cbb2ba6a 100644
--- a/includes/api/ApiFileRevert.php
+++ b/includes/api/ApiFileRevert.php
@@ -37,10 +37,6 @@ class ApiFileRevert extends ApiBase {
protected $params;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$this->params = $this->extractRequestParams();
// Extract the file and archiveName from the request parameters
@@ -50,7 +46,7 @@ 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'] );
+ $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'], 0, false, false, $this->getUser() );
if ( $status->isGood() ) {
$result = array( 'result' => 'Success' );
@@ -73,8 +69,8 @@ class ApiFileRevert extends ApiBase {
protected function checkPermissions( $user ) {
$title = $this->file->getTitle();
$permissionErrors = array_merge(
- $title->getUserPermissionsErrors( 'edit' , $user ),
- $title->getUserPermissionsErrors( 'upload' , $user )
+ $title->getUserPermissionsErrors( 'edit', $user ),
+ $title->getUserPermissionsErrors( 'upload', $user )
);
if ( $permissionErrors ) {
@@ -191,12 +187,8 @@ class ApiFileRevert extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=+\\'
+ 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=123ABC'
=> 'Revert Wiki.png to the version of 20110305152740',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 8ad9b8ca..d8aa1634 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -38,7 +38,7 @@ abstract class ApiFormatBase extends ApiBase {
* Constructor
* If $format ends with 'fm', pretty-print the output in HTML.
* @param $main ApiMain
- * @param $format string Format name
+ * @param string $format Format name
*/
public function __construct( $main, $format ) {
parent::__construct( $main, $format );
@@ -58,7 +58,7 @@ abstract class ApiFormatBase extends ApiBase {
* This method is not called if getIsHtml() returns true.
* @return string
*/
- public abstract function getMimeType();
+ abstract public function getMimeType();
/**
* Whether this formatter needs raw data such as _element tags
@@ -83,7 +83,7 @@ abstract class ApiFormatBase extends ApiBase {
* special-case fix that should be removed once the help has been
* reworked to use a fully HTML interface.
*
- * @param $b bool Whether or not ampersands should be escaped.
+ * @param bool $b Whether or not ampersands should be escaped.
*/
public function setUnescapeAmps ( $b ) {
$this->mUnescapeAmps = $b;
@@ -123,11 +123,13 @@ abstract class ApiFormatBase extends ApiBase {
/**
* Initialize the printer function and prepare the output headers, etc.
- * This method must be the first outputing method during execution.
- * A help screen's header is printed for the HTML-based output
- * @param $isError bool Whether an error message is printed
+ * This method must be the first outputting method during execution.
+ * A human-targeted notice about available formats is printed for the HTML-based output,
+ * except for help screens (caused by either an error in the API parameters,
+ * the calling of action=help, or requesting the root script api.php).
+ * @param bool $isHelpScreen Whether a help screen is going to be shown
*/
- function initPrinter( $isError ) {
+ function initPrinter( $isHelpScreen ) {
if ( $this->mDisabled ) {
return;
}
@@ -164,7 +166,7 @@ abstract class ApiFormatBase extends ApiBase {
<?php
- if ( !$isError ) {
+ if ( !$isHelpScreen ) {
?>
<br />
<small>
@@ -175,15 +177,18 @@ To see the non HTML representation of the <?php echo( $this->mFormat ); ?> forma
See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or
<a href='<?php echo( $script ); ?>'>API help</a> for more information.
</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
?>
<pre>
<?php
-
+ }
}
}
@@ -248,7 +253,7 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
}
/**
- * Sets whether the pretty-printer should format *bold* and $italics$
+ * Sets whether the pretty-printer should format *bold*
* @param $help bool
*/
public function setHelp( $help = true ) {
@@ -264,22 +269,19 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
protected function formatHTML( $text ) {
// Escape everything first for full coverage
$text = htmlspecialchars( $text );
-
// encode all comments or tags as safe blue strings
$text = str_replace( '&lt;', '<span style="color:blue;">&lt;', $text );
$text = str_replace( '&gt;', '&gt;</span>', $text );
- // 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 );
// identify requests to api.php
$text = preg_replace( "#api\\.php\\?[^ <\n\t]+#", '<a href="\\0">\\0</a>', $text );
if ( $this->mHelp ) {
// make strings inside * bold
$text = preg_replace( "#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text );
- // make strings inside $ italic
- $text = preg_replace( "#\\$[^<>\n]+\\$#", '<b><i>\\0</i></b>', $text );
}
+ // 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 );
/**
* Temporary fix for bad links in help messages. As a special case,
@@ -308,10 +310,6 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
public function getDescription() {
return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
}
-
- public static function getBaseVersion() {
- return __CLASS__ . ': $Id$';
- }
}
/**
@@ -328,7 +326,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
* Call this method to initialize output data. See execute()
* @param $result ApiResult
* @param $feed object an instance of one of the $wgFeedClasses classes
- * @param $feedItems array of FeedItem objects
+ * @param array $feedItems of FeedItem objects
*/
public static function setResult( $result, $feed, $feedItems ) {
// Store output in the Result data.
@@ -381,8 +379,4 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
}
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index 3d2a39ca..1b2e02c9 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -30,10 +30,6 @@
*/
class ApiFormatDbg extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
// This looks like it should be text/plain, but IE7 is so
// brain-damaged it tries to parse text/plain as HTML if it
@@ -48,8 +44,4 @@ class ApiFormatDbg extends ApiFormatBase {
public function getDescription() {
return 'Output data in PHP\'s var_export() format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
index 0f055e13..62253e14 100644
--- a/includes/api/ApiFormatDump.php
+++ b/includes/api/ApiFormatDump.php
@@ -30,10 +30,6 @@
*/
class ApiFormatDump extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
// This looks like it should be text/plain, but IE7 is so
// brain-damaged it tries to parse text/plain as HTML if it
@@ -52,8 +48,4 @@ class ApiFormatDump extends ApiFormatBase {
public function getDescription() {
return 'Output data in PHP\'s var_dump() format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index acbc7d3b..abb63480 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -85,13 +85,9 @@ class ApiFormatJson extends ApiFormatBase {
public function getDescription() {
if ( $this->mIsRaw ) {
- return 'Output data with the debuging elements in JSON format' . parent::getDescription();
+ return 'Output data with the debugging elements in JSON format' . parent::getDescription();
} else {
return 'Output data in JSON format' . parent::getDescription();
}
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatNone.php b/includes/api/ApiFormatNone.php
new file mode 100644
index 00000000..78023af3
--- /dev/null
+++ b/includes/api/ApiFormatNone.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ *
+ *
+ * Created on Oct 22, 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
+ */
+
+/**
+ * API Serialized PHP output formatter
+ * @ingroup API
+ */
+class ApiFormatNone extends ApiFormatBase {
+
+ public function getMimeType() {
+ return 'text/plain';
+ }
+
+ public function execute() {
+ }
+
+ public function getDescription() {
+ return 'Output nothing' . parent::getDescription();
+ }
+}
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index fac2ca58..b2d1f044 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -30,10 +30,6 @@
*/
class ApiFormatPhp extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
return 'application/vnd.php.serialized';
}
@@ -45,8 +41,4 @@ class ApiFormatPhp extends ApiFormatBase {
public function getDescription() {
return 'Output data in serialized PHP format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index 184f0a34..d278efa0 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -66,8 +66,4 @@ class ApiFormatRaw extends ApiFormatBase {
}
$this->printText( $data['text'] );
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index 71414593..4130e70c 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -30,10 +30,6 @@
*/
class ApiFormatTxt extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
// This looks like it should be text/plain, but IE7 is so
// brain-damaged it tries to parse text/plain as HTML if it
@@ -48,8 +44,4 @@ class ApiFormatTxt extends ApiFormatBase {
public function getDescription() {
return 'Output data in PHP\'s print_r() format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index 65056e44..62b69bb6 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -30,10 +30,6 @@
*/
class ApiFormatWddx extends ApiFormatBase {
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
return 'text/xml';
}
@@ -112,8 +108,4 @@ class ApiFormatWddx extends ApiFormatBase {
public function getDescription() {
return 'Output data in WDDX format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 5ccf1859..b4e8e330 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -36,10 +36,6 @@ class ApiFormatXml extends ApiFormatBase {
private $mIncludeNamespace = false;
private $mXslt = null;
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
-
public function getMimeType() {
return 'text/xml';
}
@@ -92,7 +88,7 @@ class ApiFormatXml extends ApiFormatBase {
*
* @par Example:
* @verbatim
- * name='root', value = array( '_element'=>'page', 'x', 'y', 'z')
+ * name='root', value = array( '_element'=>'page', 'x', 'y', 'z')
* @endverbatim
* creates:
* @verbatim
@@ -105,7 +101,7 @@ class ApiFormatXml extends ApiFormatBase {
*
* @par Example:
* @verbatim
- * name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10)
+ * name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10)
* @endverbatim
* creates:
* @verbatim
@@ -205,7 +201,13 @@ class ApiFormatXml extends ApiFormatBase {
// ignore
break;
default:
- $retval .= $indstr . Xml::element( $elemName, null, $elemValue );
+ // to make sure null value doesn't produce unclosed element,
+ // which is what Xml::element( $elemName, null, null ) returns
+ if ( $elemValue === null ) {
+ $retval .= $indstr . Xml::element( $elemName );
+ } else {
+ $retval .= $indstr . Xml::element( $elemName, null, $elemValue );
+ }
break;
}
return $retval;
@@ -248,8 +250,4 @@ class ApiFormatXml extends ApiFormatBase {
public function getDescription() {
return 'Output data in XML format' . parent::getDescription();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index 730ad8ea..700d4a5e 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -35,10 +35,6 @@ class ApiFormatYaml extends ApiFormatJson {
}
public function getDescription() {
- return 'Output data in YAML format' . parent::getDescription();
- }
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
+ return 'Output data in YAML format' . ApiFormatBase::getDescription();
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index 2b5de21a..9cafc5bb 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -31,10 +31,6 @@
*/
class ApiHelp extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Module for displaying help
*/
@@ -47,43 +43,62 @@ class ApiHelp extends ApiBase {
}
$this->getMain()->setHelp();
-
$result = $this->getResult();
- $queryObj = new ApiQuery( $this->getMain(), 'query' );
- $r = array();
- if ( is_array( $params['modules'] ) ) {
- $modArr = $this->getMain()->getModules();
- foreach ( $params['modules'] as $m ) {
- if ( !isset( $modArr[$m] ) ) {
- $r[] = array( 'name' => $m, 'missing' => '' );
- continue;
- }
- $module = new $modArr[$m]( $this->getMain(), $m );
-
- $r[] = $this->buildModuleHelp( $module, 'action' );
- }
+ if ( is_array( $params['modules'] ) ) {
+ $modules = $params['modules'];
+ } else {
+ $modules = array();
}
if ( is_array( $params['querymodules'] ) ) {
- $qmodArr = $queryObj->getModules();
+ $queryModules = $params['querymodules'];
+ foreach ( $queryModules as $m ) {
+ $modules[] = 'query+' . $m;
+ }
+ } else {
+ $queryModules = array();
+ }
- foreach ( $params['querymodules'] as $qm ) {
- if ( !isset( $qmodArr[$qm] ) ) {
- $r[] = array( 'name' => $qm, 'missing' => '' );
- continue;
+ $r = array();
+ foreach ( $modules as $m ) {
+ // sub-modules could be given in the form of "name[+name[+name...]]"
+ $subNames = explode( '+', $m );
+ if ( count( $subNames ) === 1 ) {
+ // 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++ ) {
+ $subs = $module->getModuleManager();
+ if ( $subs === null ) {
+ $module = null;
+ } else {
+ $module = $subs->getModule( $subNames[$i] );
}
- $module = new $qmodArr[$qm]( $this, $qm );
- $type = $queryObj->getModuleType( $qm );
-
- if ( $type === null ) {
- $r[] = array( 'name' => $qm, 'missing' => '' );
- continue;
+ if ( $module === null ) {
+ if ( count( $subNames ) === 2
+ && $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.
+ $name = $subNames[1];
+ } else {
+ $name = implode( '+', array_slice( $subNames, 0, $i + 1 ) );
+ }
+ $r[] = array( 'name' => $name, 'missing' => '' );
+ break;
+ } else {
+ $type = $subs->getModuleGroup( $subNames[$i] );
}
-
+ }
+ if ( $module !== null ) {
$r[] = $this->buildModuleHelp( $module, $type );
}
}
+
$result->setIndexedTagName( $r, 'module' );
$result->addValue( null, $this->getModuleName(), $r );
}
@@ -118,15 +133,16 @@ class ApiHelp extends ApiBase {
ApiBase::PARAM_ISMULTI => true
),
'querymodules' => array(
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DEPRECATED => true
),
);
}
public function getParamDescription() {
return array(
- 'modules' => 'List of module names (value of the action= parameter)',
- 'querymodules' => '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)',
);
}
@@ -138,9 +154,8 @@ class ApiHelp extends ApiBase {
return array(
'api.php?action=help' => 'Whole help page',
'api.php?action=help&modules=protect' => 'Module (action) help page',
- 'api.php?action=help&querymodules=categorymembers' => 'Query (list) modules help page',
- 'api.php?action=help&querymodules=info' => 'Query (prop) modules help page',
- 'api.php?action=help&querymodules=siteinfo' => 'Query (meta) modules 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',
);
}
@@ -151,8 +166,4 @@ class ApiHelp extends ApiBase {
'https://www.mediawiki.org/wiki/API:Quick_start_guide',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php
new file mode 100644
index 00000000..b2d75825
--- /dev/null
+++ b/includes/api/ApiImageRotate.php
@@ -0,0 +1,232 @@
+<?php
+/**
+ *
+ * Created on January 3rd, 2013
+ *
+ * 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
+ */
+
+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
+ */
+ 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;
+ }
+ }
+
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $rotation = $params[ 'rotation' ];
+ $user = $this->getUser();
+
+ $pageSet = $this->getPageSet();
+ $pageSet->execute();
+
+ $result = array();
+ $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->getInterwikiTitlesAsResult() );
+
+ foreach ( $pageSet->getTitles() as $title ) {
+ $r = array();
+ $r['id'] = $title->getArticleID();
+ ApiQueryBase::addTitleInfo( $r, $title );
+ if ( !$title->exists() ) {
+ $r['missing'] = '';
+ }
+
+ $file = wfFindFile( $title );
+ if ( !$file ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = 'File does not exist';
+ $result[] = $r;
+ continue;
+ }
+ $handler = $file->getHandler();
+ if ( !$handler || !$handler->canRotate() ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = 'File type cannot be rotated';
+ $result[] = $r;
+ continue;
+ }
+
+ // Check whether we're allowed to rotate this file
+ $permError = $this->checkPermissions( $this->getUser(), $file->getTitle() );
+ if ( $permError !== null ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = $permError;
+ $result[] = $r;
+ continue;
+ }
+
+ $srcPath = $file->getLocalRefPath();
+ if ( $srcPath === false ) {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = 'Cannot get local file path';
+ $result[] = $r;
+ continue;
+ }
+ $ext = strtolower( pathinfo( "$srcPath", PATHINFO_EXTENSION ) );
+ $tmpFile = TempFSFile::factory( 'rotate_', $ext);
+ $dstPath = $tmpFile->getPath();
+ $err = $handler->rotate( $file, array(
+ "srcPath" => $srcPath,
+ "dstPath" => $dstPath,
+ "rotation"=> $rotation
+ ) );
+ if ( !$err ) {
+ $comment = wfMessage( 'rotate-comment' )->numParams( $rotation )->text();
+ $status = $file->upload( $dstPath,
+ $comment, $comment, 0, false, false, $this->getUser() );
+ if ( $status->isGood() ) {
+ $r['result'] = 'Success';
+ } else {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = $this->getResult()->convertStatusToArray( $status );
+ }
+ } else {
+ $r['result'] = 'Failure';
+ $r['errormessage'] = $err->toText();
+ }
+ $result[] = $r;
+ }
+ $apiResult = $this->getResult();
+ $apiResult->setIndexedTagName( $result, 'page' );
+ $apiResult->addValue( null, $this->getModuleName(), $result );
+ }
+
+ /**
+ * Get a cached instance of an ApiPageSet object
+ * @return ApiPageSet
+ */
+ private function getPageSet() {
+ if ( $this->mPageSet === null ) {
+ $this->mPageSet = new ApiPageSet( $this, 0, NS_FILE );
+ }
+ return $this->mPageSet;
+ }
+
+ /**
+ * Checks that the user has permissions to perform rotations.
+ * @param $user User The user to check.
+ * @return string|null Permission error message, or null if there is no error
+ */
+ protected function checkPermissions( $user, $title ) {
+ $permissionErrors = array_merge(
+ $title->getUserPermissionsErrors( 'edit' , $user ),
+ $title->getUserPermissionsErrors( 'upload' , $user )
+ );
+
+ if ( $permissionErrors ) {
+ // Just return the first error
+ $msg = $this->parseMsg( $permissionErrors[0] );
+ return $msg['info'];
+ }
+
+ return null;
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams( $flags = 0 ) {
+ $pageSet = $this->getPageSet();
+ $result = array(
+ 'rotation' => array(
+ ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'token' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ );
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
+ }
+
+ public function getParamDescription() {
+ $pageSet = $this->getPageSet();
+ return $pageSet->getParamDescription() + array(
+ 'rotation' => 'Degrees to rotate image clockwise',
+ 'token' => 'Edit token. You can get one of these through action=tokens',
+ );
+ }
+
+ public function getDescription() {
+ 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->getPossibleErrors()
+ );
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=imagerotate&titles=Example.jpg&rotation=90&token=123ABC',
+ );
+ }
+}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index 637c1fff..1f0a5fab 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -31,10 +31,6 @@
*/
class ApiImport extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
@@ -109,7 +105,9 @@ class ApiImport extends ApiBase {
ApiBase::PARAM_REQUIRED => true
),
'summary' => null,
- 'xml' => null,
+ 'xml' => array(
+ ApiBase::PARAM_TYPE => 'upload',
+ ),
'interwikisource' => array(
ApiBase::PARAM_TYPE => $wgImportSources
),
@@ -150,7 +148,7 @@ class ApiImport extends ApiBase {
public function getDescription() {
return array(
- 'Import a page from another wiki, or an XML file.' ,
+ 'Import a page from another wiki, or an XML file.',
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
'sending a file for the "xml" parameter.'
);
@@ -186,10 +184,6 @@ class ApiImport extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Import';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
/**
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 1f91fe92..b936d3be 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -38,7 +38,7 @@ class ApiLogin extends ApiBase {
/**
* Executes the log-in attempt using the parameters passed. If
- * the log-in succeeeds, it attaches a cookie to the session
+ * the log-in succeeds, it attaches a cookie to the session
* and outputs the user id, username, and session token. If a
* log-in fails, as the result of a bad password, a nonexistent
* user, or any other reason, the host is cached with an expiry
@@ -147,7 +147,7 @@ class ApiLogin extends ApiBase {
case LoginForm::ABORTED:
$result['result'] = 'Aborted';
- $result['reason'] = $loginForm->mAbortLoginErrorMsg;
+ $result['reason'] = $loginForm->mAbortLoginErrorMsg;
break;
default:
@@ -278,8 +278,4 @@ class ApiLogin extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Login';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index b2f634d0..2ba92a63 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -32,10 +32,6 @@
*/
class ApiLogout extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
$oldName = $user->getName();
@@ -75,8 +71,4 @@ class ApiLogout extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Logout';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index 35febd95..80bca2f6 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -51,6 +51,7 @@ class ApiMain extends ApiBase {
private static $Modules = array(
'login' => 'ApiLogin',
'logout' => 'ApiLogout',
+ 'createaccount' => 'ApiCreateAccount',
'query' => 'ApiQuery',
'expandtemplates' => 'ApiExpandTemplates',
'parse' => 'ApiParse',
@@ -82,6 +83,7 @@ class ApiMain extends ApiBase {
'import' => 'ApiImport',
'userrights' => 'ApiUserrights',
'options' => 'ApiOptions',
+ 'imagerotate' =>'ApiImageRotate',
);
/**
@@ -105,6 +107,7 @@ class ApiMain extends ApiBase {
'dbgfm' => 'ApiFormatDbg',
'dump' => 'ApiFormatDump',
'dumpfm' => 'ApiFormatDump',
+ 'none' => 'ApiFormatNone',
);
/**
@@ -118,7 +121,7 @@ class ApiMain extends ApiBase {
'msg' => 'Use of the write API',
'params' => array()
),
- 'apihighlimits' => array(
+ 'apihighlimits' => array(
'msg' => 'Use higher limits in API queries (Slow queries: $1 results; Fast queries: $2 results). The limits for slow queries also apply to multivalue parameters.',
'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
)
@@ -129,18 +132,20 @@ class ApiMain extends ApiBase {
*/
private $mPrinter;
- private $mModules, $mModuleNames, $mFormats, $mFormatNames;
- private $mResult, $mAction, $mShowVersions, $mEnableWrite;
+ private $mModuleMgr, $mResult;
+ private $mAction;
+ private $mEnableWrite;
private $mInternalMode, $mSquidMaxage, $mModule;
private $mCacheMode = 'private';
private $mCacheControl = array();
+ private $mParamsUsed = array();
/**
* 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 $enableWrite bool should be set to true if the api may modify data
+ * @param bool $enableWrite should be set to true if the api may modify data
*/
public function __construct( $context = null, $enableWrite = false ) {
if ( $context === null ) {
@@ -168,7 +173,7 @@ class ApiMain extends ApiBase {
// Remove all modules other than login
global $wgUser;
- if ( $this->getRequest()->getVal( 'callback' ) !== null ) {
+ if ( $this->getVal( 'callback' ) !== null ) {
// JSON callback allows cross-site reads.
// For safety, strip user credentials.
wfDebug( "API: stripping user credentials for JSON callback\n" );
@@ -177,15 +182,13 @@ class ApiMain extends ApiBase {
}
}
- global $wgAPIModules; // extension modules
- $this->mModules = $wgAPIModules + self::$Modules;
-
- $this->mModuleNames = array_keys( $this->mModules );
- $this->mFormats = self::$Formats;
- $this->mFormatNames = array_keys( $this->mFormats );
+ global $wgAPIModules;
+ $this->mModuleMgr = new ApiModuleManager( $this );
+ $this->mModuleMgr->addModules( self::$Modules, 'action' );
+ $this->mModuleMgr->addModules( $wgAPIModules, 'action' );
+ $this->mModuleMgr->addModules( self::$Formats, 'format' );
$this->mResult = new ApiResult( $this );
- $this->mShowVersions = false;
$this->mEnableWrite = $enableWrite;
$this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
@@ -242,7 +245,7 @@ class ApiMain extends ApiBase {
/**
* Set the type of caching headers which will be sent.
*
- * @param $mode String One of:
+ * @param string $mode One of:
* - 'public': Cache this object in public caches, if the maxage or smaxage
* parameter is set, or if setCacheMaxAge() was called. If a maximum age is
* not provided by any of these means, the object will be private.
@@ -271,7 +274,7 @@ class ApiMain extends ApiBase {
return;
}
- if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
+ if ( !User::groupHasPermission( '*', 'read' ) ) {
// Private wiki, only private headers
if ( $mode !== 'private' ) {
wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" );
@@ -330,10 +333,11 @@ class ApiMain extends ApiBase {
* @return ApiFormatBase
*/
public function createPrinterByName( $format ) {
- if ( !isset( $this->mFormats[$format] ) ) {
+ $printer = $this->mModuleMgr->getModule( $format, 'format' );
+ if ( $printer === null ) {
$this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' );
}
- return new $this->mFormats[$format] ( $this, $format );
+ return $printer;
}
/**
@@ -361,10 +365,17 @@ class ApiMain extends ApiBase {
return;
}
+ // Exit here if the request method was OPTIONS
+ // (assume there will be a followup GET or POST)
+ if ( $this->getRequest()->getMethod() === 'OPTIONS' ) {
+ return;
+ }
+
// In case an error occurs during data output,
// clear the output buffer and print just the error information
ob_start();
+ $t = microtime( true );
try {
$this->executeAction();
} catch ( Exception $e ) {
@@ -372,11 +383,16 @@ class ApiMain extends ApiBase {
wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
// Log it
- if ( !( $e instanceof UsageException ) ) {
- wfDebugLog( 'exception', $e->getLogMessage() );
+ if ( $e instanceof MWException && !( $e instanceof UsageException ) ) {
+ global $wgLogExceptionBacktrace;
+ if ( $wgLogExceptionBacktrace ) {
+ wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" );
+ } else {
+ wfDebugLog( 'exception', $e->getLogMessage() );
+ }
}
- // Handle any kind of exception by outputing properly formatted error message.
+ // 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.
@@ -401,6 +417,9 @@ class ApiMain extends ApiBase {
$this->printResult( true );
}
+ // Log the request whether or not there was an error
+ $this->logRequest( microtime( true ) - $t);
+
// Send cache headers after any code which might generate an error, to
// avoid sending public cache headers for errors.
$this->sendCacheHeaders();
@@ -461,9 +480,9 @@ class ApiMain extends ApiBase {
/**
* Attempt to match an Origin header against a set of rules and a set of exceptions
- * @param $value string Origin header
- * @param $rules array Set of wildcard rules
- * @param $exceptions array Set of wildcard rules
+ * @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
*/
protected static function matchOrigin( $value, $rules, $exceptions ) {
@@ -486,7 +505,7 @@ class ApiMain extends ApiBase {
* '*' => '.*?'
* '?' => '.'
*
- * @param $wildcard string String with wildcards
+ * @param string $wildcard String with wildcards
* @return string Regular expression
*/
protected static function wildcardToRegex( $wildcard ) {
@@ -593,7 +612,7 @@ class ApiMain extends ApiBase {
if ( !isset ( $this->mPrinter ) ) {
// The printer has not been created yet. Try to manually get formatter value.
$value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
- if ( !in_array( $value, $this->mFormatNames ) ) {
+ if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) {
$value = self::API_DEFAULT_FORMAT;
}
@@ -611,7 +630,6 @@ class ApiMain extends ApiBase {
if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) {
ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
}
-
} else {
global $wgShowSQLErrors, $wgShowExceptionDetails;
// Something is seriously wrong
@@ -628,6 +646,10 @@ class ApiMain extends ApiBase {
ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
}
+ // Remember all the warnings to re-add them later
+ $oldResult = $result->getData();
+ $warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null;
+
$result->reset();
$result->disableSizeCheck();
// Re-add the id
@@ -635,11 +657,13 @@ class ApiMain extends ApiBase {
if ( !is_null( $requestid ) ) {
$result->addValue( null, 'requestid', $requestid );
}
-
if ( $wgShowHostnames ) {
// servedby is especially useful when debugging errors
$result->addValue( null, 'servedby', wfHostName() );
}
+ if ( $warnings !== null ) {
+ $result->addValue( null, 'warnings', $warnings );
+ }
$result->addValue( null, 'error', $errMessage );
@@ -669,7 +693,6 @@ class ApiMain extends ApiBase {
$params = $this->extractRequestParams();
- $this->mShowVersions = $params['version'];
$this->mAction = $params['action'];
if ( !is_string( $this->mAction ) ) {
@@ -685,9 +708,10 @@ class ApiMain extends ApiBase {
*/
protected function setupModule() {
// Instantiate the module requested by the user
- $module = new $this->mModules[$this->mAction] ( $this, $this->mAction );
- $this->mModule = $module;
-
+ $module = $this->mModuleMgr->getModule( $this->mAction, 'action' );
+ if ( $module === null ) {
+ $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' );
+ }
$moduleParams = $module->extractRequestParams();
// Die if token required, but not provided (unless there is a gettoken parameter)
@@ -713,7 +737,7 @@ class ApiMain extends ApiBase {
/**
* Check the max lag if necessary
* @param $module ApiBase object: Api module being used
- * @param $params Array an array containing the request parameters.
+ * @param array $params an array containing the request parameters.
* @return boolean True on success, false should exit immediately
*/
protected function checkMaxLag( $module, $params ) {
@@ -745,7 +769,7 @@ class ApiMain extends ApiBase {
*/
protected function checkExecutePermissions( $module ) {
$user = $this->getUser();
- if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) &&
+ if ( $module->isReadMode() && !User::groupHasPermission( '*', 'read' ) &&
!$user->isAllowed( 'read' ) )
{
$this->dieUsageMsg( 'readrequired' );
@@ -772,12 +796,13 @@ class ApiMain extends ApiBase {
/**
* Check POST for external response and setup result printer
* @param $module ApiBase An Api module
- * @param $params Array an array with the request parameters
+ * @param array $params an array with the request parameters
*/
protected function setupExternalResponse( $module, $params ) {
- // Ignore mustBePosted() for internal calls
- if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) {
- $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) );
+ if ( !$this->getRequest()->wasPosted() && $module->mustBePosted() ) {
+ // Module requires POST. GET request might still be allowed
+ // if $wgDebugApi is true, otherwise fail.
+ $this->dieUsageMsgOrDebug( array( 'mustbeposted', $this->mAction ) );
}
// See if custom printer is used
@@ -798,6 +823,7 @@ class ApiMain extends ApiBase {
protected function executeAction() {
$params = $this->setupExecuteAction();
$module = $this->setupModule();
+ $this->mModule = $module;
$this->checkExecutePermissions( $module );
@@ -815,6 +841,8 @@ class ApiMain extends ApiBase {
wfRunHooks( 'APIAfterExecute', array( &$module ) );
$module->profileOut();
+ $this->reportUnusedParams();
+
if ( !$this->mInternalMode ) {
//append Debug information
MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() );
@@ -825,11 +853,120 @@ class ApiMain extends ApiBase {
}
/**
+ * Log the preceding request
+ * @param $time Time in seconds
+ */
+ protected function logRequest( $time ) {
+ $request = $this->getRequest();
+ $milliseconds = $time === null ? '?' : round( $time * 1000 );
+ $s = 'API' .
+ ' ' . $request->getMethod() .
+ ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
+ ' ' . $request->getIP() .
+ ' T=' . $milliseconds .'ms';
+ foreach ( $this->getParamsUsed() as $name ) {
+ $value = $request->getVal( $name );
+ if ( $value === null ) {
+ continue;
+ }
+ $s .= ' ' . $name . '=';
+ if ( strlen( $value ) > 256 ) {
+ $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) );
+ $s .= $encValue . '[...]';
+ } else {
+ $s .= $this->encodeRequestLogValue( $value );
+ }
+ }
+ $s .= "\n";
+ wfDebugLog( 'api', $s, false );
+ }
+
+ /**
+ * Encode a value in a format suitable for a space-separated log line.
+ */
+ protected function encodeRequestLogValue( $s ) {
+ static $table;
+ if ( !$table ) {
+ $chars = ';@$!*(),/:';
+ for ( $i = 0; $i < strlen( $chars ); $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
+ */
+ protected function getParamsUsed() {
+ return array_keys( $this->mParamsUsed );
+ }
+
+ /**
+ * Get a request value, and register the fact that it was used, for logging.
+ */
+ public function getVal( $name, $default = null ) {
+ $this->mParamsUsed[$name] = true;
+ return $this->getRequest()->getVal( $name, $default );
+ }
+
+ /**
+ * Get a boolean request value, and register the fact that the parameter
+ * was used, for logging.
+ */
+ public function getCheck( $name ) {
+ $this->mParamsUsed[$name] = true;
+ return $this->getRequest()->getCheck( $name );
+ }
+
+ /**
+ * Get a request upload, and register the fact that it was used, for logging.
+ *
+ * @since 1.21
+ * @param string $name Parameter name
+ * @return WebRequestUpload
+ */
+ public function getUpload( $name ) {
+ $this->mParamsUsed[$name] = true;
+ return $this->getRequest()->getUpload( $name );
+ }
+
+ /**
+ * Report unused parameters, so the client gets a hint in case it gave us parameters we don't know,
+ * for example in case of spelling mistakes or a missing 'g' prefix for generators.
+ */
+ protected function reportUnusedParams() {
+ $paramsUsed = $this->getParamsUsed();
+ $allParams = $this->getRequest()->getValueNames();
+
+ if ( !$this->mInternalMode ) {
+ // Printer has not yet executed; don't warn that its parameters are unused
+ $printerParams = array_map(
+ array( $this->mPrinter, 'encodeParamName' ),
+ array_keys( $this->mPrinter->getFinalParams() ?: array() )
+ );
+ $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams );
+ } else {
+ $unusedParams = array_diff( $allParams, $paramsUsed );
+ }
+
+ if( count( $unusedParams ) ) {
+ $s = count( $unusedParams ) > 1 ? 's' : '';
+ $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" );
+ }
+ }
+
+ /**
* Print results using the current printer
*
* @param $isError bool
*/
protected function printResult( $isError ) {
+ global $wgDebugAPI;
+ if( $wgDebugAPI !== false ) {
+ $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
+ }
+
$this->getResult()->cleanUpUTF8();
$printer = $this->mPrinter;
$printer->profileIn();
@@ -839,10 +976,10 @@ class ApiMain extends ApiBase {
* tell the printer not to escape ampersands so that our links do
* not break.
*/
- $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError )
- && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
+ $isHelp = $isError || $this->mAction == 'help';
+ $printer->setUnescapeAmps( $isHelp && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
- $printer->initPrinter( $isError );
+ $printer->initPrinter( $isHelp );
$printer->execute();
$printer->closePrinter();
@@ -865,13 +1002,12 @@ class ApiMain extends ApiBase {
return array(
'format' => array(
ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
- ApiBase::PARAM_TYPE => $this->mFormatNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' )
),
'action' => array(
ApiBase::PARAM_DFLT => 'help',
- ApiBase::PARAM_TYPE => $this->mModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' )
),
- 'version' => false,
'maxlag' => array(
ApiBase::PARAM_TYPE => 'integer'
),
@@ -898,12 +1034,11 @@ class ApiMain extends ApiBase {
return array(
'format' => 'The format of the output',
'action' => 'What action you would like to perform. See below for module help',
- 'version' => 'When showing help, include version for each module',
'maxlag' => array(
'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.',
'To save actions causing any more site replication lag, this parameter can make the client',
'wait until the replication lag is less than the specified value.',
- 'In case of a replag error, a HTTP 503 error is returned, with the message like',
+ 'In case of a replag error, error code "maxlag" is returned, with the message like',
'"Waiting for $host: $lag seconds lagged\n".',
'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information',
),
@@ -984,11 +1119,11 @@ class ApiMain extends ApiBase {
protected function getCredits() {
return array(
'API developers:',
- ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-present)',
+ ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)',
' Victor Vasiliev - vasilvv at gee mail dot 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)',
+ ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)',
'',
'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
'or file a bug report at https://bugzilla.wikimedia.org/'
@@ -1014,8 +1149,7 @@ class ApiMain extends ApiBase {
$this->setHelp();
// Get help text from cache if present
$key = wfMemcKey( 'apihelp', $this->getModuleName(),
- SpecialVersion::getVersion( 'nodb' ) .
- $this->getShowVersions() );
+ SpecialVersion::getVersion( 'nodb' ) );
if ( $wgAPICacheHelpTimeout > 0 ) {
$cached = $wgMemc->get( $key );
if ( $cached ) {
@@ -1040,9 +1174,11 @@ class ApiMain extends ApiBase {
$astriks = str_repeat( '*** ', 14 );
$msg .= "\n\n$astriks Modules $astriks\n\n";
- foreach ( array_keys( $this->mModules ) as $moduleName ) {
- $module = new $this->mModules[$moduleName] ( $this, $moduleName );
+
+ foreach ( $this->mModuleMgr->getNames( 'action' ) as $name ) {
+ $module = $this->mModuleMgr->getModule( $name );
$msg .= self::makeHelpMsgHeader( $module, 'action' );
+
$msg2 = $module->makeHelpMsg();
if ( $msg2 !== false ) {
$msg .= $msg2;
@@ -1053,14 +1189,13 @@ class ApiMain extends ApiBase {
$msg .= "\n$astriks Permissions $astriks\n\n";
foreach ( self::$mRights as $right => $rightMsg ) {
$groups = User::getGroupsWithPermission( $right );
- $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) .
+ $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) .
"\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
-
}
$msg .= "\n$astriks Formats $astriks\n\n";
- foreach ( array_keys( $this->mFormats ) as $formatName ) {
- $module = $this->createPrinterByName( $formatName );
+ foreach ( $this->mModuleMgr->getNames( 'format' ) as $name ) {
+ $module = $this->mModuleMgr->getModule( $name );
$msg .= self::makeHelpMsgHeader( $module, 'format' );
$msg2 = $module->makeHelpMsg();
if ( $msg2 !== false ) {
@@ -1076,7 +1211,7 @@ class ApiMain extends ApiBase {
/**
* @param $module ApiBase
- * @param $paramName String What type of request is this? e.g. action, query, list, prop, meta, format
+ * @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 ) {
@@ -1105,25 +1240,19 @@ class ApiMain extends ApiBase {
/**
* Check whether the user wants us to show version information in the API help
* @return bool
+ * @deprecated since 1.21, always returns false
*/
public function getShowVersions() {
- return $this->mShowVersions;
+ wfDeprecated( __METHOD__, '1.21' );
+ return false;
}
/**
- * Returns the version information of this file, plus it includes
- * the versions for all files that are not callable proper API modules
- *
- * @return array
+ * Overrides to return this instance's module manager.
+ * @return ApiModuleManager
*/
- public function getVersion() {
- $vers = array();
- $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
- $vers[] = __CLASS__ . ': $Id$';
- $vers[] = ApiBase::getBaseVersion();
- $vers[] = ApiFormatBase::getBaseVersion();
- $vers[] = ApiQueryBase::getBaseVersion();
- return $vers;
+ public function getModuleManager() {
+ return $this->mModuleMgr;
}
/**
@@ -1131,40 +1260,44 @@ class ApiMain extends ApiBase {
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
*
- * @param $mdlName String The identifier for this module.
- * @param $mdlClass String The class where this module is implemented.
+ * @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.
*/
- protected function addModule( $mdlName, $mdlClass ) {
- $this->mModules[$mdlName] = $mdlClass;
+ protected function addModule( $name, $class ) {
+ $this->getModuleManager()->addModule( $name, 'action', $class );
}
/**
* Add or overwrite an output format for this ApiMain. Intended for use by extending
* classes who wish to add to or modify current formatters.
*
- * @param $fmtName string The identifier for this format.
- * @param $fmtClass ApiFormatBase The class implementing this format.
+ * @deprecated since 1.21, Use getModuleManager()->addModule() instead.
+ * @param string $name The identifier for this format.
+ * @param $class ApiFormatBase The class implementing this format.
*/
- protected function addFormat( $fmtName, $fmtClass ) {
- $this->mFormats[$fmtName] = $fmtClass;
+ protected function addFormat( $name, $class ) {
+ $this->getModuleManager()->addModule( $name, 'format', $class );
}
/**
* Get the array mapping module names to class names
+ * @deprecated since 1.21, Use getModuleManager()'s methods instead.
* @return array
*/
function getModules() {
- return $this->mModules;
+ return $this->getModuleManager()->getNamesWithClasses( 'action' );
}
/**
* Returns the list of supported formats in form ( 'format' => 'ClassName' )
*
* @since 1.18
+ * @deprecated since 1.21, Use getModuleManager()'s methods instead.
* @return array
*/
public function getFormats() {
- return $this->mFormats;
+ return $this->getModuleManager()->getNamesWithClasses( 'format' );
}
}
diff --git a/includes/api/ApiModuleManager.php b/includes/api/ApiModuleManager.php
new file mode 100644
index 00000000..100392bf
--- /dev/null
+++ b/includes/api/ApiModuleManager.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ *
+ *
+ * Created on Dec 27, 2012
+ *
+ * Copyright © 2012 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
+ * @since 1.21
+ */
+
+/**
+ * This class holds a list of modules and handles instantiation
+ *
+ * @since 1.21
+ * @ingroup API
+ */
+class ApiModuleManager extends ContextSource {
+
+ private $mParent;
+ private $mInstances = array();
+ private $mGroups = array();
+ private $mModules = array();
+
+ /**
+ * Construct new module manager
+ * @param ApiBase $parentModule Parent module instance will be used during instantiation
+ */
+ public function __construct( ApiBase $parentModule ) {
+ $this->mParent = $parentModule;
+ }
+
+ /**
+ * Add a list of modules to the manager
+ * @param array $modules A map of ModuleName => ModuleClass
+ * @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 );
+ }
+ }
+
+ /**
+ * Add or overwrite a module in this ApiMain instance. Intended for use by extending
+ * 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 $class The class where this module is implemented.
+ */
+ public function addModule( $name, $group, $class ) {
+ $this->mGroups[$group] = null;
+ $this->mModules[$name] = array( $group, $class );
+ }
+
+ /**
+ * 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
+ */
+ 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 ) {
+ 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 );
+ if ( !$ignoreCache ) {
+ // cache this instance in case it is needed later
+ $this->mInstances[$moduleName] = $instance;
+ }
+ 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
+ */
+ public function getNames( $group = null ) {
+ if ( $group === null ) {
+ return array_keys( $this->mModules );
+ }
+ $result = array();
+ foreach ( $this->mModules as $name => $grpCls ) {
+ if ( $grpCls[0] === $group ) {
+ $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
+ */
+ public function getNamesWithClasses( $group = null ) {
+ $result = array();
+ foreach ( $this->mModules as $name => $grpCls ) {
+ if ( $group === null || $grpCls[0] === $group ) {
+ $result[$name] = $grpCls[1];
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 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
+ */
+ public function isDefined( $moduleName, $group = null ) {
+ if ( isset( $this->mModules[$moduleName] ) ) {
+ return $group === null || $this->mModules[$moduleName][0] === $group;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the group name for the given module
+ * @param string $moduleName
+ * @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;
+ }
+ }
+
+ /**
+ * Get a list of groups this manager contains.
+ * @return array
+ */
+ public function getGroups() {
+ return array_keys( $this->mGroups );
+ }
+}
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index 9d73562b..3e846e3b 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -30,10 +30,6 @@
*/
class ApiMove extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
@@ -42,7 +38,7 @@ class ApiMove extends ApiBase {
if ( isset( $params['from'] ) ) {
$fromTitle = Title::newFromText( $params['from'] );
- if ( !$fromTitle ) {
+ if ( !$fromTitle || $fromTitle->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['from'] ) );
}
} elseif ( isset( $params['fromid'] ) ) {
@@ -58,7 +54,7 @@ class ApiMove extends ApiBase {
$fromTalk = $fromTitle->getTalkPage();
$toTitle = Title::newFromText( $params['to'] );
- if ( !$toTitle ) {
+ if ( !$toTitle || $toTitle->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['to'] ) );
}
$toTalk = $toTitle->getTalkPage();
@@ -82,9 +78,16 @@ class ApiMove extends ApiBase {
}
$r = array( 'from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason'] );
- if ( !$params['noredirect'] || !$user->isAllowed( 'suppressredirect' ) ) {
+
+ if ( $fromTitle->exists() ) {
+ //NOTE: we assume that if the old title exists, it's because it was re-created as
+ // a redirect to the new title. This is not safe, but what we did before was
+ // even worse: we just determined whether a redirect should have been created,
+ // and reported that it was created if it should have, without any checks.
+ // Also note that isRedirect() is unreliable because of bug 37209.
$r['redirectcreated'] = '';
}
+
if( $toTitleExists ) {
$r['moveoverredirect'] = '';
}
@@ -122,7 +125,7 @@ class ApiMove extends ApiBase {
}
}
- $watch = "preferences";
+ $watch = 'preferences';
if ( isset( $params['watchlist'] ) ) {
$watch = $params['watchlist'];
} elseif ( $params['watch'] ) {
@@ -288,15 +291,11 @@ class ApiMove extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=move&from=Exampel&to=Example&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
+ 'api.php?action=move&from=Badtitle&to=Goodtitle&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
);
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Move';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index ef562741..caf361ac 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -29,10 +29,6 @@
*/
class ApiOpenSearch extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function getCustomPrinter() {
return $this->getMain()->createPrinterByName( 'json' );
}
@@ -123,8 +119,4 @@ class ApiOpenSearch extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Opensearch';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php
index 265c2ccb..8c996a26 100644
--- a/includes/api/ApiOptions.php
+++ b/includes/api/ApiOptions.php
@@ -25,17 +25,13 @@
*/
/**
-* API module that facilitates the changing of user's preferences.
-* Requires API write mode to be enabled.
-*
+ * API module that facilitates the changing of user's preferences.
+ * Requires API write mode to be enabled.
+ *
* @ingroup API
*/
class ApiOptions extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Changes preferences of the current user.
*/
@@ -54,7 +50,7 @@ class ApiOptions extends ApiBase {
}
if ( $params['reset'] ) {
- $user->resetOptions();
+ $user->resetOptions( $params['resetkinds'] );
$changed = true;
}
@@ -74,13 +70,36 @@ class ApiOptions extends ApiBase {
}
$prefs = Preferences::getPreferences( $user, $this->getContext() );
+ $prefsKinds = $user->getOptionKinds( $this->getContext(), $changes );
+
foreach ( $changes as $key => $value ) {
- if ( !isset( $prefs[$key] ) ) {
- $this->setWarning( "Not a valid preference: $key" );
- continue;
+ switch ( $prefsKinds[$key] ) {
+ case 'registered':
+ // Regular option.
+ $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key] );
+ $validation = $field->validate( $value, $user->getOptions() );
+ break;
+ case 'registered-multiselect':
+ case 'registered-checkmatrix':
+ // A key for a multiselect or checkmatrix option.
+ $validation = true;
+ $value = $value !== null ? (bool) $value : null;
+ break;
+ case 'userjs':
+ // Allow non-default preferences prefixed with 'userjs-', to be set by user scripts
+ if ( strlen( $key ) > 255 ) {
+ $validation = "key too long (no more than 255 bytes allowed)";
+ } elseif ( preg_match( "/[^a-zA-Z0-9_-]/", $key ) !== 0 ) {
+ $validation = "invalid key (only a-z, A-Z, 0-9, _, - allowed)";
+ } else {
+ $validation = true;
+ }
+ break;
+ case 'unused':
+ default:
+ $validation = "not a valid preference";
+ break;
}
- $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key] );
- $validation = $field->validate( $value, $user->getOptions() );
if ( $validation === true ) {
$user->setOption( $key, $value );
$changed = true;
@@ -106,12 +125,20 @@ class ApiOptions extends ApiBase {
}
public function getAllowedParams() {
+ $optionKinds = User::listOptionKinds();
+ $optionKinds[] = 'all';
+
return array(
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reset' => false,
+ 'resetkinds' => array(
+ ApiBase::PARAM_TYPE => $optionKinds,
+ ApiBase::PARAM_DFLT => 'all',
+ ApiBase::PARAM_ISMULTI => true
+ ),
'change' => array(
ApiBase::PARAM_ISMULTI => true,
),
@@ -139,15 +166,20 @@ class ApiOptions extends ApiBase {
public function getParamDescription() {
return array(
'token' => 'An options token previously obtained through the action=tokens',
- 'reset' => 'Resets all preferences to the site defaults',
- 'change' => 'List of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters',
+ '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',
'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',
);
}
public function getDescription() {
- return 'Change preferences of the current user';
+ return array(
+ '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.'
+ );
}
public function getPossibleErrors() {
@@ -176,8 +208,4 @@ class ApiOptions extends ApiBase {
'api.php?action=options&reset=&change=skin=monobook&optionname=nickname&optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 0f5be6b2..074efe4b 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -4,7 +4,7 @@
*
* Created on Sep 24, 2006
*
- * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ * Copyright © 2006, 2013 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
@@ -36,52 +36,177 @@
* the second instance for all their work.
*
* @ingroup API
+ * @since 1.21 derives from ApiBase instead of ApiQueryBase
*/
-class ApiPageSet extends ApiQueryBase {
+class ApiPageSet extends ApiBase {
- private $mAllPages; // [ns][dbkey] => page_id or negative when missing
- private $mTitles, $mGoodTitles, $mMissingTitles, $mInvalidTitles;
- private $mMissingPageIDs, $mRedirectTitles, $mSpecialTitles;
- private $mNormalizedTitles, $mInterwikiTitles;
- private $mResolveRedirects, $mPendingRedirectIDs;
- private $mConvertTitles, $mConvertedTitles;
- private $mGoodRevIDs, $mMissingRevIDs;
- private $mFakePageId;
-
- private $mRequestedPageFields;
+ /**
+ * Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter
+ * @since 1.21
+ */
+ const DISABLE_GENERATORS = 1;
+
+ private $mDbSource;
+ private $mParams;
+ private $mResolveRedirects;
+ private $mConvertTitles;
+ private $mAllowGenerator;
+
+ private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing
+ private $mTitles = array();
+ private $mGoodTitles = array();
+ private $mMissingTitles = array();
+ private $mInvalidTitles = array();
+ private $mMissingPageIDs = array();
+ private $mRedirectTitles = array();
+ private $mSpecialTitles = array();
+ private $mNormalizedTitles = array();
+ private $mInterwikiTitles = array();
+ private $mPendingRedirectIDs = array();
+ private $mConvertedTitles = array();
+ private $mGoodRevIDs = array();
+ private $mMissingRevIDs = array();
+ private $mFakePageId = -1;
+ private $mCacheMode = 'public';
+ private $mRequestedPageFields = array();
+ private $mDefaultNamespace = NS_MAIN;
/**
* Constructor
- * @param $query ApiBase
- * @param $resolveRedirects bool Whether redirects should be resolved
- * @param $convertTitles bool
+ * @param $dbSource ApiBase 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.
+ * @since 1.21 accepts $flags instead of two boolean values
*/
- public function __construct( $query, $resolveRedirects = false, $convertTitles = false ) {
- parent::__construct( $query, 'query' );
+ public function __construct( ApiBase $dbSource, $flags = 0, $defaultNamespace = NS_MAIN ) {
+ parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() );
+ $this->mDbSource = $dbSource;
+ $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0;
+ $this->mDefaultNamespace = $defaultNamespace;
- $this->mAllPages = array();
- $this->mTitles = array();
- $this->mGoodTitles = array();
- $this->mMissingTitles = array();
- $this->mInvalidTitles = array();
- $this->mMissingPageIDs = array();
- $this->mRedirectTitles = array();
- $this->mNormalizedTitles = array();
- $this->mInterwikiTitles = array();
- $this->mGoodRevIDs = array();
- $this->mMissingRevIDs = array();
- $this->mSpecialTitles = array();
+ $this->profileIn();
+ $this->mParams = $this->extractRequestParams();
+ $this->mResolveRedirects = $this->mParams['redirects'];
+ $this->mConvertTitles = $this->mParams['converttitles'];
+ $this->profileOut();
+ }
- $this->mRequestedPageFields = array();
- $this->mResolveRedirects = $resolveRedirects;
- if ( $resolveRedirects ) {
- $this->mPendingRedirectIDs = array();
- }
+ /**
+ * In case execute() is not called, call this method to mark all relevant parameters as used
+ * This prevents unused parameters from being reported as warnings
+ */
+ public function executeDryRun() {
+ $this->executeInternal( true );
+ }
- $this->mConvertTitles = $convertTitles;
- $this->mConvertedTitles = array();
+ /**
+ * Populate the PageSet from the request parameters.
+ */
+ public function execute() {
+ $this->executeInternal( false );
+ }
+
+ /**
+ * Populate the PageSet from the request parameters.
+ * @param bool $isDryRun If true, instantiates generator, but only to mark relevant parameters as used
+ */
+ private function executeInternal( $isDryRun ) {
+ $this->profileIn();
- $this->mFakePageId = - 1;
+ $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null;
+ if ( isset( $generatorName ) ) {
+ $dbSource = $this->mDbSource;
+ $isQuery = $dbSource instanceof ApiQuery;
+ if ( !$isQuery ) {
+ // If the parent container of this pageset is not ApiQuery, we must create it to run generator
+ $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' );
+ // Enable profiling for query module because it will be used for db sql profiling
+ $dbSource->profileIn();
+ }
+ $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true );
+ if ( $generator === null ) {
+ $this->dieUsage( 'Unknown generator=' . $generatorName, 'badgenerator' );
+ }
+ if ( !$generator instanceof ApiQueryGeneratorBase ) {
+ $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
+ }
+ // Create a temporary pageset to store generator's output,
+ // add any additional fields generator may need, and execute pageset to populate titles/pageids
+ $tmpPageSet = new ApiPageSet( $dbSource, ApiPageSet::DISABLE_GENERATORS );
+ $generator->setGeneratorMode( $tmpPageSet );
+ $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() );
+
+ if ( !$isDryRun ) {
+ $generator->requestExtraData( $tmpPageSet );
+ }
+ $tmpPageSet->executeInternal( $isDryRun );
+
+ // populate this pageset with the generator output
+ $this->profileOut();
+ $generator->profileIn();
+
+ if ( !$isDryRun ) {
+ $generator->executeGenerator( $this );
+ wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) );
+ $this->resolvePendingRedirects();
+ } else {
+ // Prevent warnings from being reported on these parameters
+ $main = $this->getMain();
+ foreach ( $generator->extractRequestParams() as $paramName => $param ) {
+ $main->getVal( $generator->encodeParamName( $paramName ) );
+ }
+ }
+ $generator->profileOut();
+ $this->profileIn();
+
+ if ( !$isQuery ) {
+ // If this pageset is not part of the query, we called profileIn() above
+ $dbSource->profileOut();
+ }
+ } else {
+ // Only one of the titles/pageids/revids is allowed at the same time
+ $dataSource = null;
+ if ( isset( $this->mParams['titles'] ) ) {
+ $dataSource = 'titles';
+ }
+ if ( isset( $this->mParams['pageids'] ) ) {
+ if ( isset( $dataSource ) ) {
+ $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
+ }
+ $dataSource = 'pageids';
+ }
+ if ( isset( $this->mParams['revids'] ) ) {
+ if ( isset( $dataSource ) ) {
+ $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
+ }
+ $dataSource = 'revids';
+ }
+
+ if ( !$isDryRun ) {
+ // Populate page information with the original user input
+ switch( $dataSource ) {
+ case 'titles':
+ $this->initFromTitles( $this->mParams['titles'] );
+ break;
+ case 'pageids':
+ $this->initFromPageIds( $this->mParams['pageids'] );
+ 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->mResolveRedirects = false;
+ $this->initFromRevIDs( $this->mParams['revids'] );
+ break;
+ default:
+ // Do nothing - some queries do not need any of the data sources.
+ break;
+ }
+ }
+ }
+ $this->profileOut();
}
/**
@@ -93,9 +218,33 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Request an additional field from the page table. Must be called
- * before execute()
- * @param $fieldName string Field name
+ * Return the parameter name that is the source of data for this PageSet
+ *
+ * If multiple source parameters are specified (e.g. titles and pageids),
+ * one will be named arbitrarily.
+ *
+ * @return string|null
+ */
+ public function getDataSource() {
+ if ( $this->mAllowGenerator && isset( $this->mParams['generator'] ) ) {
+ return 'generator';
+ }
+ if ( isset( $this->mParams['titles'] ) ) {
+ return 'titles';
+ }
+ if ( isset( $this->mParams['pageids'] ) ) {
+ return 'pageids';
+ }
+ if ( isset( $this->mParams['revids'] ) ) {
+ return 'revids';
+ }
+ return null;
+ }
+
+ /**
+ * Request an additional field from the page table.
+ * Must be called before execute()
+ * @param string $fieldName Field name
*/
public function requestField( $fieldName ) {
$this->mRequestedPageFields[$fieldName] = null;
@@ -104,7 +253,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Get the value of a custom field previously requested through
* requestField()
- * @param $fieldName string Field name
+ * @param string $fieldName Field name
* @return mixed Field value
*/
public function getCustomField( $fieldName ) {
@@ -207,14 +356,39 @@ class ApiPageSet extends ApiQueryBase {
/**
* Get a list of redirect resolutions - maps a title to its redirect
- * target.
- * @return array prefixed_title (string) => Title object
+ * target, as an array of output-ready arrays
+ * @return array
*/
public function getRedirectTitles() {
return $this->mRedirectTitles;
}
/**
+ * Get a list of redirect resolutions - maps a title to its redirect
+ * target.
+ * @param $result ApiResult
+ * @return array of prefixed_title (string) => Title object
+ * @since 1.21
+ */
+ public function getRedirectTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) {
+ $r = array(
+ 'from' => strval( $titleStrFrom ),
+ 'to' => $titleTo->getPrefixedText(),
+ );
+ if ( $titleTo->getFragment() !== '' ) {
+ $r['tofragment'] = $titleTo->getFragment();
+ }
+ $values[] = $r;
+ }
+ 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)
@@ -224,6 +398,27 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * 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)
+ * @since 1.21
+ */
+ public function getNormalizedTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
+ $values[] = array(
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+ 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)
@@ -233,6 +428,27 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * 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
+ * @since 1.21
+ */
+ public function getConvertedTitlesAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) {
+ $values[] = array(
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+ 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)
@@ -242,6 +458,33 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * 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)
+ * @since 1.21
+ */
+ public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) {
+ $values = array();
+ foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
+ $item = array(
+ 'title' => $rawTitleStr,
+ 'iw' => $interwikiStr,
+ );
+ if ( $iwUrl ) {
+ $title = Title::newFromText( $rawTitleStr );
+ $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT );
+ }
+ $values[] = $item;
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'i' );
+ }
+ return $values;
+ }
+
+ /**
* Get the list of revision IDs (requested with the revids= parameter)
* @return array revID (int) => pageID (int)
*/
@@ -258,6 +501,25 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Revision IDs that were not found in the database as result array.
+ * @param $result ApiResult
+ * @return array of revision IDs
+ * @since 1.21
+ */
+ public function getMissingRevisionIDsAsResult( $result = null ) {
+ $values = array();
+ foreach ( $this->getMissingRevisionIDs() as $revid ) {
+ $values[$revid] = array(
+ 'revid' => $revid
+ );
+ }
+ if ( !empty( $values ) && $result ) {
+ $result->setIndexedTagName( $values, 'rev' );
+ }
+ return $values;
+ }
+
+ /**
* Get the list of titles with negative namespace
* @return array Title
*/
@@ -274,55 +536,8 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Populate the PageSet from the request parameters.
- */
- public function execute() {
- $this->profileIn();
- $params = $this->extractRequestParams();
-
- // Only one of the titles/pageids/revids is allowed at the same time
- $dataSource = null;
- if ( isset( $params['titles'] ) ) {
- $dataSource = 'titles';
- }
- if ( isset( $params['pageids'] ) ) {
- if ( isset( $dataSource ) ) {
- $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' );
- }
- $dataSource = 'pageids';
- }
- if ( isset( $params['revids'] ) ) {
- if ( isset( $dataSource ) ) {
- $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' );
- }
- $dataSource = 'revids';
- }
-
- switch ( $dataSource ) {
- case 'titles':
- $this->initFromTitles( $params['titles'] );
- break;
- case 'pageids':
- $this->initFromPageIds( $params['pageids'] );
- 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->mResolveRedirects = false;
- $this->initFromRevIDs( $params['revids'] );
- break;
- default:
- // Do nothing - some queries do not need any of the data sources.
- break;
- }
- $this->profileOut();
- }
-
- /**
* Populate this PageSet from a list of Titles
- * @param $titles array of Title objects
+ * @param array $titles of Title objects
*/
public function populateFromTitles( $titles ) {
$this->profileIn();
@@ -332,7 +547,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Populate this PageSet from a list of page IDs
- * @param $pageIDs array of page IDs
+ * @param array $pageIDs of page IDs
*/
public function populateFromPageIDs( $pageIDs ) {
$this->profileIn();
@@ -353,7 +568,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Populate this PageSet from a list of revision IDs
- * @param $revIDs array of revision IDs
+ * @param array $revIDs of revision IDs
*/
public function populateFromRevisionIDs( $revIDs ) {
$this->profileIn();
@@ -385,12 +600,11 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Resolve redirects, if applicable
+ * Do not use, does nothing, will be removed
+ * @deprecated 1.21
*/
public function finishPageSetGeneration() {
- $this->profileIn();
- $this->resolvePendingRedirects();
- $this->profileOut();
+ wfDeprecated( __METHOD__, '1.21' );
}
/**
@@ -407,7 +621,7 @@ class ApiPageSet extends ApiQueryBase {
* #5 Substitute the original LinkBatch object with the new list
* #6 Repeat from step #1
*
- * @param $titles array of Title objects or strings
+ * @param array $titles of Title objects or strings
*/
private function initFromTitles( $titles ) {
// Get validated and normalized title objects
@@ -434,10 +648,10 @@ class ApiPageSet extends ApiQueryBase {
/**
* Does the same as initFromTitles(), but is based on page IDs instead
- * @param $pageids array of page IDs
+ * @param array $pageids of page IDs
*/
private function initFromPageIds( $pageids ) {
- if ( !count( $pageids ) ) {
+ if ( !$pageids ) {
return;
}
@@ -447,7 +661,7 @@ class ApiPageSet extends ApiQueryBase {
$pageids = self::getPositiveIntegers( $pageids );
$res = null;
- if ( count( $pageids ) ) {
+ if ( !empty( $pageids ) ) {
$set = array(
'page_id' => $pageids
);
@@ -460,7 +674,7 @@ class ApiPageSet extends ApiQueryBase {
$this->profileDBOut();
}
- $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
+ $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
// Resolve any found redirects
$this->resolvePendingRedirects();
@@ -470,9 +684,9 @@ class ApiPageSet extends ApiQueryBase {
* 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 $remaining array of either pageID or ns/title elements (optional).
+ * @param array $remaining of either pageID or ns/title elements (optional).
* If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
- * @param $processTitles bool Must be provided together with $remaining.
+ * @param bool $processTitles Must be provided together with $remaining.
* If true, treat $remaining as an array of [ns][title]
* If false, treat it as an array of [pageIDs]
*/
@@ -499,7 +713,7 @@ class ApiPageSet extends ApiQueryBase {
$this->processDbRow( $row );
// Need gender information
- if( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) {
+ if ( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) {
$usernames[] = $row->page_title;
}
}
@@ -518,7 +732,7 @@ class ApiPageSet extends ApiQueryBase {
$this->mTitles[] = $title;
// need gender information
- if( MWNamespace::hasGenderDistinction( $ns ) ) {
+ if ( MWNamespace::hasGenderDistinction( $ns ) ) {
$usernames[] = $dbkey;
}
}
@@ -541,10 +755,10 @@ class ApiPageSet extends ApiQueryBase {
/**
* Does the same as initFromTitles(), but is based on revision IDs
* instead
- * @param $revids array of revision IDs
+ * @param array $revids of revision IDs
*/
private function initFromRevIDs( $revids ) {
- if ( !count( $revids ) ) {
+ if ( !$revids ) {
return;
}
@@ -555,14 +769,14 @@ class ApiPageSet extends ApiQueryBase {
$revids = self::getPositiveIntegers( $revids );
- if ( count( $revids ) ) {
+ if ( !empty( $revids ) ) {
$tables = array( 'revision', 'page' );
$fields = array( 'rev_id', 'rev_page' );
$where = array( 'rev_id' => $revids, 'rev_page = page_id' );
// Get pageIDs data from the `page` table
$this->profileDBIn();
- $res = $db->select( $tables, $fields, $where, __METHOD__ );
+ $res = $db->select( $tables, $fields, $where, __METHOD__ );
foreach ( $res as $row ) {
$revid = intval( $row->rev_id );
$pageid = intval( $row->rev_page );
@@ -670,44 +884,63 @@ class ApiPageSet extends ApiQueryBase {
}
/**
+ * Get the cache mode for the data generated by this module.
+ * All PageSet users should take into account whether this returns a more-restrictive
+ * cache mode than the using module itself. For possible return values and other
+ * details about cache modes, see ApiMain::setCacheMode()
+ *
+ * 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
+ * @return string
+ * @since 1.21
+ */
+ public function getCacheMode( $params = null ) {
+ return $this->mCacheMode;
+ }
+
+ /**
* Given an array of title strings, convert them into Title objects.
- * Alternativelly, an array of Title objects may be given.
+ * Alternatively, an array of Title objects may be given.
* This method validates access rights for the title,
* and appends normalization values to the output.
*
- * @param $titles array of Title objects or strings
+ * @param array $titles of Title objects or strings
* @return LinkBatch
*/
private function processTitlesArray( $titles ) {
- $genderCache = GenderCache::singleton();
- $genderCache->doTitlesArray( $titles, __METHOD__ );
-
+ $usernames = array();
$linkBatch = new LinkBatch();
foreach ( $titles as $title ) {
- $titleObj = is_string( $title ) ? Title::newFromText( $title ) : $title;
+ if ( is_string( $title ) ) {
+ $titleObj = Title::newFromText( $title, $this->mDefaultNamespace );
+ } else {
+ $titleObj = $title;
+ }
if ( !$titleObj ) {
// Handle invalid titles gracefully
- $this->mAllpages[0][$title] = $this->mFakePageId;
+ $this->mAllPages[0][$title] = $this->mFakePageId;
$this->mInvalidTitles[$this->mFakePageId] = $title;
$this->mFakePageId--;
continue; // There's nothing else we can do
}
$unconvertedTitle = $titleObj->getPrefixedText();
$titleWasConverted = false;
- $iw = $titleObj->getInterwiki();
- if ( strval( $iw ) !== '' ) {
+ if ( $titleObj->isExternal() ) {
// This title is an interwiki link.
- $this->mInterwikiTitles[$titleObj->getPrefixedText()] = $iw;
+ $this->mInterwikiTitles[$unconvertedTitle] = $titleObj->getInterwiki();
} else {
// Variants checking
global $wgContLang;
if ( $this->mConvertTitles &&
- count( $wgContLang->getVariants() ) > 1 &&
+ count( $wgContLang->getVariants() ) > 1 &&
!$titleObj->exists() ) {
- // Language::findVariantLink will modify titleObj into
+ // Language::findVariantLink will modify titleText and titleObj into
// the canonical variant if possible
- $wgContLang->findVariantLink( $title, $titleObj );
+ $titleText = is_string( $title ) ? $title : $titleObj->getPrefixedText();
+ $wgContLang->findVariantLink( $titleText, $titleObj );
$titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
}
@@ -728,16 +961,36 @@ class ApiPageSet extends ApiQueryBase {
// namespace is localized or the capitalization is
// different
if ( $titleWasConverted ) {
- $this->mConvertedTitles[$title] = $titleObj->getPrefixedText();
+ $this->mConvertedTitles[$unconvertedTitle] = $titleObj->getPrefixedText();
+ // In this case the page can't be Special.
+ if ( is_string( $title ) && $title !== $unconvertedTitle ) {
+ $this->mNormalizedTitles[$title] = $unconvertedTitle;
+ }
} elseif ( is_string( $title ) && $title !== $titleObj->getPrefixedText() ) {
$this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
}
+
+ // Need gender information
+ if ( MWNamespace::hasGenderDistinction( $titleObj->getNamespace() ) ) {
+ $usernames[] = $titleObj->getText();
+ }
}
+ // Get gender information
+ $genderCache = GenderCache::singleton();
+ $genderCache->doQuery( $usernames, __METHOD__ );
return $linkBatch;
}
/**
+ * Get the database connection (read-only)
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ return $this->mDbSource->getDB();
+ }
+
+ /**
* Returns the input array of integers with all values < 0 removed
*
* @param $array array
@@ -747,7 +1000,7 @@ class ApiPageSet extends ApiQueryBase {
// bug 25734 API: possible issue with revids validation
// It seems with a load of revision rows, MySQL gets upset
// Remove any < 0 integers, as they can't be valid
- foreach( $array as $i => $int ) {
+ foreach ( $array as $i => $int ) {
if ( $int < 0 ) {
unset( $array[$i] );
}
@@ -756,8 +1009,8 @@ class ApiPageSet extends ApiQueryBase {
return $array;
}
- public function getAllowedParams() {
- return array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'titles' => array(
ApiBase::PARAM_ISMULTI => true
),
@@ -768,15 +1021,59 @@ class ApiPageSet extends ApiQueryBase {
'revids' => array(
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_ISMULTI => true
- )
+ ),
+ 'redirects' => false,
+ 'converttitles' => false,
);
+ if ( $this->mAllowGenerator ) {
+ if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
+ $result['generator'] = array(
+ ApiBase::PARAM_TYPE => $this->getGenerators()
+ );
+ } else {
+ $result['generator'] = null;
+ }
+ }
+ return $result;
+ }
+
+ private static $generators = null;
+
+ /**
+ * Get an array of all available generators
+ * @return array
+ */
+ private function getGenerators() {
+ if ( self::$generators === null ) {
+ $query = $this->mDbSource;
+ if ( !( $query instanceof ApiQuery ) ) {
+ // If the parent container of this pageset is not ApiQuery,
+ // we must create it to get module manager
+ $query = $this->getMain()->getModuleManager()->getModule( 'query' );
+ }
+ $gens = array();
+ $mgr = $query->getModuleManager();
+ foreach ( $mgr->getNamesWithClasses() as $name => $class ) {
+ if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) {
+ $gens[] = $name;
+ }
+ }
+ sort( $gens );
+ self::$generators = $gens;
+ }
+ return self::$generators;
}
public function getParamDescription() {
return array(
'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'
+ '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' ),
+ '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 ) ),
);
}
@@ -784,10 +1081,7 @@ class ApiPageSet extends ApiQueryBase {
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' ),
) );
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 343a2625..27f8cefd 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -42,42 +42,13 @@ class ApiParamInfo extends ApiBase {
public function execute() {
// Get parameters
$params = $this->extractRequestParams();
- $result = $this->getResult();
+ $resultObj = $this->getResult();
$res = array();
- if ( is_array( $params['modules'] ) ) {
- $modules = $this->getMain()->getModules();
- $res['modules'] = array();
- foreach ( $params['modules'] as $mod ) {
- if ( !isset( $modules[$mod] ) ) {
- $res['modules'][] = array( 'name' => $mod, 'missing' => '' );
- continue;
- }
- $obj = new $modules[$mod]( $this->getMain(), $mod );
- $item = $this->getClassInfo( $obj );
- $item['name'] = $mod;
- $res['modules'][] = $item;
- }
- $result->setIndexedTagName( $res['modules'], 'module' );
- }
+ $this->addModulesInfo( $params, 'modules', $res, $resultObj );
- if ( is_array( $params['querymodules'] ) ) {
- $queryModules = $this->queryObj->getModules();
- $res['querymodules'] = array();
- foreach ( $params['querymodules'] as $qm ) {
- if ( !isset( $queryModules[$qm] ) ) {
- $res['querymodules'][] = array( 'name' => $qm, 'missing' => '' );
- continue;
- }
- $obj = new $queryModules[$qm]( $this, $qm );
- $item = $this->getClassInfo( $obj );
- $item['name'] = $qm;
- $item['querytype'] = $this->queryObj->getModuleType( $qm );
- $res['querymodules'][] = $item;
- }
- $result->setIndexedTagName( $res['querymodules'], 'module' );
- }
+ $this->addModulesInfo( $params, 'querymodules', $res, $resultObj );
if ( $params['mainmodule'] ) {
$res['mainmodule'] = $this->getClassInfo( $this->getMain() );
@@ -88,36 +59,57 @@ class ApiParamInfo extends ApiBase {
$res['pagesetmodule'] = $this->getClassInfo( $pageSet );
}
- if ( is_array( $params['formatmodules'] ) ) {
- $formats = $this->getMain()->getFormats();
- $res['formatmodules'] = array();
- foreach ( $params['formatmodules'] as $f ) {
- if ( !isset( $formats[$f] ) ) {
- $res['formatmodules'][] = array( 'name' => $f, 'missing' => '' );
- continue;
- }
- $obj = new $formats[$f]( $this, $f );
- $item = $this->getClassInfo( $obj );
- $item['name'] = $f;
- $res['formatmodules'][] = $item;
+ $this->addModulesInfo( $params, 'formatmodules', $res, $resultObj );
+
+ $resultObj->addValue( null, $this->getModuleName(), $res );
+ }
+
+ /**
+ * 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.
+ */
+ private function addModulesInfo( $params, $type, &$res, $resultObj ) {
+ if ( !is_array( $params[$type] ) ) {
+ return;
+ }
+ $isQuery = ( $type === 'querymodules' );
+ if ( $isQuery ) {
+ $mgr = $this->queryObj->getModuleManager();
+ } else {
+ $mgr = $this->getMain()->getModuleManager();
+ }
+ $res[$type] = array();
+ foreach ( $params[$type] as $mod ) {
+ if ( !$mgr->isDefined( $mod ) ) {
+ $res[$type][] = array( 'name' => $mod, 'missing' => '' );
+ continue;
+ }
+ $obj = $mgr->getModule( $mod );
+ $item = $this->getClassInfo( $obj );
+ $item['name'] = $mod;
+ if ( $isQuery ) {
+ $item['querytype'] = $mgr->getModuleGroup( $mod );
}
- $result->setIndexedTagName( $res['formatmodules'], 'module' );
+ $res[$type][] = $item;
}
- $result->addValue( null, $this->getModuleName(), $res );
+ $resultObj->setIndexedTagName( $res[$type], 'module' );
}
/**
* @param $obj ApiBase
* @return ApiResult
*/
- function getClassInfo( $obj ) {
+ private function getClassInfo( $obj ) {
$result = $this->getResult();
$retval['classname'] = get_class( $obj );
$retval['description'] = implode( "\n", (array)$obj->getFinalDescription() );
-
$retval['examples'] = '';
- $retval['version'] = implode( "\n", (array)$obj->getVersion() );
+ // version is deprecated since 1.21, but needs to be returned for v1
+ $retval['version'] = '';
$retval['prefix'] = $obj->getModulePrefix();
if ( $obj->isReadMode() ) {
@@ -133,7 +125,7 @@ class ApiParamInfo extends ApiBase {
$retval['generator'] = '';
}
- $allowedParams = $obj->getFinalParams();
+ $allowedParams = $obj->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
if ( !is_array( $allowedParams ) ) {
return $retval;
}
@@ -150,7 +142,7 @@ class ApiParamInfo extends ApiBase {
if ( is_string( $examples ) ) {
$examples = array( $examples );
}
- foreach( $examples as $k => $v ) {
+ foreach ( $examples as $k => $v ) {
if ( strlen( $retval['examples'] ) ) {
$retval['examples'] .= ' ';
}
@@ -181,7 +173,7 @@ class ApiParamInfo extends ApiBase {
}
//handle shorthand
- if( !is_array( $p ) ) {
+ if ( !is_array( $p ) ) {
$p = array(
ApiBase::PARAM_DFLT => $p,
);
@@ -208,11 +200,11 @@ class ApiParamInfo extends ApiBase {
if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
$type = $p[ApiBase::PARAM_TYPE];
- if( $type === 'boolean' ) {
+ if ( $type === 'boolean' ) {
$a['default'] = ( $p[ApiBase::PARAM_DFLT] ? 'true' : 'false' );
- } elseif( $type === 'string' ) {
+ } elseif ( $type === 'string' ) {
$a['default'] = strval( $p[ApiBase::PARAM_DFLT] );
- } elseif( $type === 'integer' ) {
+ } elseif ( $type === 'integer' ) {
$a['default'] = intval( $p[ApiBase::PARAM_DFLT] );
} else {
$a['default'] = $p[ApiBase::PARAM_DFLT];
@@ -299,7 +291,7 @@ class ApiParamInfo extends ApiBase {
$retval['props'][] = $propResult;
}
- // default is true for query modules, false for other modules, overriden by ApiBase::PROP_LIST
+ // 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'] = '';
}
@@ -319,11 +311,11 @@ class ApiParamInfo extends ApiBase {
}
public function getAllowedParams() {
- $modules = array_keys( $this->getMain()->getModules() );
+ $modules = $this->getMain()->getModuleManager()->getNames( 'action' );
sort( $modules );
- $querymodules = array_keys( $this->queryObj->getModules() );
+ $querymodules = $this->queryObj->getModuleManager()->getNames();
sort( $querymodules );
- $formatmodules = array_keys( $this->getMain()->getFormats() );
+ $formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
sort( $formatmodules );
return array(
'modules' => array(
@@ -366,8 +358,4 @@ class ApiParamInfo extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Parameter_information';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index db6e2bb8..09b7a882 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -26,11 +26,15 @@
* @ingroup API
*/
class ApiParse extends ApiBase {
- private $section, $text, $pstText = null;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
+ /** @var String $section */
+ private $section = null;
+
+ /** @var Content $content */
+ private $content = null;
+
+ /** @var Content $pstContent */
+ private $pstContent = null;
public function execute() {
// The data is hot but user-dependent, like page views, so we set vary cookies
@@ -44,6 +48,9 @@ class ApiParse extends ApiBase {
$pageid = $params['pageid'];
$oldid = $params['oldid'];
+ $model = $params['contentmodel'];
+ $format = $params['contentformat'];
+
if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
$this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
}
@@ -61,7 +68,7 @@ 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 behaviour of uselang breaks
+ // 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() ) {
$oldLang = $this->getContext()->getLanguage(); // Backup language
@@ -91,19 +98,19 @@ class ApiParse extends ApiBase {
$popts->enableLimitReport( !$params['disablepp'] );
// If for some reason the "oldid" is actually the current revision, it may be cached
- if ( $titleObj->getLatestRevID() === intval( $oldid ) ) {
+ if ( $rev->isCurrent() ) {
// May get from/save to parser cache
- $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $p_result = $this->getParsedContent( $pageObj, $popts,
+ $pageid, isset( $prop['wikitext'] ) );
} else { // This is an old revision, so get the text differently
- $this->text = $rev->getText( Revision::FOR_THIS_USER, $this->getUser() );
+ $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
+ $this->content = $this->getSectionContent( $this->content, 'r' . $rev->getId() );
}
// Should we save old revision parses to the parser cache?
- $p_result = $wgParser->parse( $this->text, $titleObj, $popts );
+ $p_result = $this->content->getParserOutput( $titleObj, $rev->getId(), $popts );
}
} else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
@@ -136,6 +143,9 @@ class ApiParse extends ApiBase {
$pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' );
$titleObj = $pageObj->getTitle();
+ if ( !$titleObj || !$titleObj->exists() ) {
+ $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
+ }
$wgTitle = $titleObj;
if ( isset( $prop['revid'] ) ) {
@@ -146,46 +156,59 @@ class ApiParse extends ApiBase {
$popts->enableLimitReport( !$params['disablepp'] );
// Potentially cached
- $p_result = $this->getParsedSectionOrText( $pageObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
+ isset( $prop['wikitext'] ) );
}
} else { // Not $oldid, $pageid, $page. Hence based on $text
-
- if ( is_null( $text ) ) {
- $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
- }
- $this->text = $text;
$titleObj = Title::newFromText( $title );
- if ( !$titleObj ) {
+ 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 );
$popts = $pageObj->makeParserOptions( $this->getContext() );
$popts->enableLimitReport( !$params['disablepp'] );
+ if ( is_null( $text ) ) {
+ $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
+ }
+
+ try {
+ $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ }
+
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, $titleObj->getText() );
+ $this->content = $this->getSectionContent( $this->content, $titleObj->getText() );
}
if ( $params['pst'] || $params['onlypst'] ) {
- $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $this->getUser(), $popts );
+ $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
}
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array = array();
$result_array['text'] = array();
- $result->setContent( $result_array['text'], $this->pstText );
+ $result->setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
}
$result->addValue( null, $this->getModuleName(), $result_array );
return;
}
+
// Not cached (save or load)
- $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
+ if ( $params['pst'] ) {
+ $p_result = $this->pstContent->getParserOutput( $titleObj, null, $popts );
+ } else {
+ $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
+ }
}
$result_array = array();
@@ -275,10 +298,10 @@ class ApiParse extends ApiBase {
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
- if ( !is_null( $this->pstText ) ) {
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ if ( !is_null( $this->pstContent ) ) {
$result_array['psttext'] = array();
- $result->setContent( $result_array['psttext'], $this->pstText );
+ $result->setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
}
}
if ( isset( $prop['properties'] ) ) {
@@ -286,8 +309,12 @@ class ApiParse extends ApiBase {
}
if ( $params['generatexml'] ) {
+ if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
+ $this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
+ }
+
$wgParser->startExternalParse( $titleObj, $popts, OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $this->text );
+ $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
} else {
@@ -325,15 +352,16 @@ class ApiParse extends ApiBase {
* @param $getWikitext Bool
* @return ParserOutput
*/
- private function getParsedSectionOrText( $page, $popts, $pageId = null, $getWikitext = false ) {
- global $wgParser;
+ private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
+ $this->content = $page->getContent( Revision::RAW ); //XXX: really raw?
- if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
- ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText() );
+ if ( $this->section !== false && $this->content !== null ) {
+ $this->content = $this->getSectionContent(
+ $this->content,
+ !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getText() );
// Not cached (save or load)
- return $wgParser->parse( $this->text, $page->getTitle(), $popts );
+ return $this->content->getParserOutput( $page->getTitle(), null, $popts );
} else {
// Try the parser cache first
// getParserOutput will save to Parser cache if able
@@ -342,20 +370,23 @@ class ApiParse extends ApiBase {
$this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
}
if ( $getWikitext ) {
- $this->text = $page->getRawText();
+ $this->content = $page->getContent( Revision::RAW );
}
return $pout;
}
}
- private function getSectionText( $text, $what ) {
- global $wgParser;
+ private function getSectionContent( Content $content, $what ) {
// Not cached (save or load)
- $text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false ) {
+ $section = $content->getSection( $this->section );
+ if ( $section === false ) {
$this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
}
- return $text;
+ if ( $section === null ) {
+ $this->dieUsage( "Sections are not supported by " . $what, 'nosuchsection' );
+ $section = false;
+ }
+ return $section;
}
private function formatLangLinks( $links ) {
@@ -415,14 +446,14 @@ class ApiParse extends ApiBase {
$text = Language::fetchLanguageName( $nt->getInterwiki() );
$langs[] = Html::element( 'a',
- array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
+ 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 );
+ $s = Html::rawElement( 'span', array( 'dir' => 'LTR' ), $s );
}
return $s;
@@ -548,6 +579,12 @@ class ApiParse extends ApiBase {
'section' => null,
'disablepp' => false,
'generatexml' => false,
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
@@ -592,7 +629,9 @@ 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',
- 'generatexml' => 'Generate XML parse tree',
+ 'generatexml' => 'Generate XML parse tree (requires prop=wikitext)',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
@@ -613,6 +652,9 @@ class ApiParse extends ApiBase {
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" ),
) );
}
@@ -625,8 +667,4 @@ class ApiParse extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index cb5e081a..4d4fbba9 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -30,10 +30,6 @@
*/
class ApiPatrol extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Patrols the article or provides the reason the patrol failed.
*/
@@ -120,8 +116,4 @@ class ApiPatrol extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Patrol';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index b3ca67e6..503c6920 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -29,10 +29,6 @@
*/
class ApiProtect extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
global $wgRestrictionLevels;
$params = $this->extractRequestParams();
@@ -178,7 +174,7 @@ class ApiProtect extends ApiBase {
'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 neverexpiring protection.' ),
+ 'Use \'infinite\', \'indefinite\' 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\'' ),
@@ -234,8 +230,4 @@ class ApiProtect extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Protect';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index 9fedaf1b..134f4a0d 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -31,69 +31,69 @@
*/
class ApiPurge extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
+ 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() {
- $user = $this->getUser();
$params = $this->extractRequestParams();
- if ( !$user->isAllowed( 'purge' ) && !$this->getMain()->isInternalMode() &&
- !$this->getRequest()->wasPosted() ) {
- $this->dieUsageMsg( array( 'mustbeposted', $this->getModuleName() ) );
- }
$forceLinkUpdate = $params['forcelinkupdate'];
- $pageSet = new ApiPageSet( $this );
+ $pageSet = $this->getPageSet();
$pageSet->execute();
$result = array();
- foreach( $pageSet->getInvalidTitles() as $title ) {
- $r = array();
- $r['title'] = $title;
- $r['invalid'] = '';
- $result[] = $r;
- }
- foreach( $pageSet->getMissingPageIDs() as $p ) {
- $page = array();
- $page['pageid'] = $p;
- $page['missing'] = '';
- $result[] = $page;
- }
- foreach( $pageSet->getMissingRevisionIDs() as $r ) {
- $rev = array();
- $rev['revid'] = $r;
- $rev['missing'] = '';
- $result[] = $rev;
- }
-
- foreach ( $pageSet->getTitles() as $title ) {
+ 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() );
+
+ foreach ( $pageSet->getGoodTitles() as $title ) {
$r = array();
-
ApiQueryBase::addTitleInfo( $r, $title );
- if ( !$title->exists() ) {
- $r['missing'] = '';
- $result[] = $r;
- continue;
- }
-
$page = WikiPage::factory( $title );
$page->doPurge(); // Directly purge and skip the UI part of purge().
$r['purged'] = '';
- if( $forceLinkUpdate ) {
- if ( !$user->pingLimiter() ) {
- global $wgParser, $wgEnableParserCache;
+ if ( $forceLinkUpdate ) {
+ if ( !$this->getUser()->pingLimiter() ) {
+ global $wgEnableParserCache;
$popts = $page->makeParserOptions( 'canonical' );
- $p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
- true, true, $page->getLatest() );
+
+ # 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 );
# Update the links tables
- $updates = $p_result->getSecondaryDataUpdates( $title );
+ $updates = $content->getSecondaryDataUpdates( $title, null, true, $p_result );
DataUpdate::runUpdates( $updates );
$r['linkupdate'] = '';
@@ -114,24 +114,52 @@ class ApiPurge extends ApiBase {
$apiResult = $this->getResult();
$apiResult->setIndexedTagName( $result, 'page' );
$apiResult->addValue( null, $this->getModuleName(), $result );
+
+ $values = $pageSet->getNormalizedTitlesAsResult( $apiResult );
+ if ( $values ) {
+ $apiResult->addValue( null, 'normalized', $values );
+ }
+ $values = $pageSet->getConvertedTitlesAsResult( $apiResult );
+ if ( $values ) {
+ $apiResult->addValue( null, 'converted', $values );
+ }
+ $values = $pageSet->getRedirectTitlesAsResult( $apiResult );
+ if ( $values ) {
+ $apiResult->addValue( null, 'redirects', $values );
+ }
+ }
+
+ /**
+ * Get a cached instance of an ApiPageSet object
+ * @return ApiPageSet
+ */
+ private function getPageSet() {
+ if ( !isset( $this->mPageSet ) ) {
+ $this->mPageSet = new ApiPageSet( $this );
+ }
+ return $this->mPageSet;
}
public function isWriteMode() {
return true;
}
- public function getAllowedParams() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getAllowedParams() + array(
- 'forcelinkupdate' => false,
- );
+ public function mustBePosted() {
+ // Anonymous users are not allowed a non-POST request
+ return !$this->getUser()->isAllowed( 'purge' );
+ }
+
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array( 'forcelinkupdate' => false );
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
}
public function getParamDescription() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getParamDescription() + array(
- 'forcelinkupdate' => 'Update the links tables',
- );
+ return $this->getPageSet()->getParamDescription()
+ + array( 'forcelinkupdate' => 'Update the links tables' );
}
public function getResultProperties() {
@@ -155,9 +183,14 @@ class ApiPurge extends ApiBase {
ApiBase::PROP_NULLABLE => true
),
'invalid' => 'boolean',
+ 'special' => 'boolean',
'missing' => 'boolean',
'purged' => 'boolean',
- 'linkupdate' => 'boolean'
+ 'linkupdate' => 'boolean',
+ 'iw' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ ),
)
);
}
@@ -169,10 +202,9 @@ class ApiPurge extends ApiBase {
}
public function getPossibleErrors() {
- $psModule = new ApiPageSet( $this );
return array_merge(
parent::getPossibleErrors(),
- $psModule->getPossibleErrors()
+ $this->getPageSet()->getPossibleErrors()
);
}
@@ -185,8 +217,4 @@ class ApiPurge extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Purge';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index 554aae5a..f69ad234 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -37,16 +37,11 @@
*/
class ApiQuery extends ApiBase {
- private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
-
/**
- * @var ApiPageSet
+ * List of Api Query prop modules
+ * @var array
*/
- private $mPageSet;
-
- private $params, $redirects, $convertTitles, $iwUrl;
-
- private $mQueryPropModules = array(
+ private static $QueryPropModules = array(
'categories' => 'ApiQueryCategories',
'categoryinfo' => 'ApiQueryCategoryInfo',
'duplicatefiles' => 'ApiQueryDuplicateFiles',
@@ -63,11 +58,16 @@ class ApiQuery extends ApiBase {
'templates' => 'ApiQueryLinks',
);
- private $mQueryListModules = array(
+ /**
+ * List of Api Query list modules
+ * @var array
+ */
+ private static $QueryListModules = array(
'allcategories' => 'ApiQueryAllCategories',
'allimages' => 'ApiQueryAllImages',
'alllinks' => 'ApiQueryAllLinks',
'allpages' => 'ApiQueryAllPages',
+ 'alltransclusions' => 'ApiQueryAllLinks',
'allusers' => 'ApiQueryAllUsers',
'backlinks' => 'ApiQueryBacklinks',
'blocks' => 'ApiQueryBlocks',
@@ -80,6 +80,8 @@ class ApiQuery extends ApiBase {
'iwbacklinks' => 'ApiQueryIWBacklinks',
'langbacklinks' => 'ApiQueryLangBacklinks',
'logevents' => 'ApiQueryLogEvents',
+ 'pageswithprop' => 'ApiQueryPagesWithProp',
+ 'pagepropnames' => 'ApiQueryPagePropNames',
'protectedtitles' => 'ApiQueryProtectedTitles',
'querypage' => 'ApiQueryQueryPage',
'random' => 'ApiQueryRandom',
@@ -92,16 +94,26 @@ class ApiQuery extends ApiBase {
'watchlistraw' => 'ApiQueryWatchlistRaw',
);
- private $mQueryMetaModules = array(
+ /**
+ * List of Api Query meta modules
+ * @var array
+ */
+ private static $QueryMetaModules = array(
'allmessages' => 'ApiQueryAllMessages',
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
);
- private $mSlaveDB = null;
- private $mNamedDB = array();
+ /**
+ * @var ApiPageSet
+ */
+ private $mPageSet;
- protected $mAllowedGenerators = array();
+ private $mParams;
+ private $mNamedDB = array();
+ private $mModuleMgr;
+ private $mGeneratorContinue;
+ private $mUseLegacyContinue;
/**
* @param $main ApiMain
@@ -110,59 +122,27 @@ class ApiQuery extends ApiBase {
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
- // Allow custom modules to be added in LocalSettings.php
- global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules,
- $wgMemc, $wgAPICacheHelpTimeout;
- self::appendUserModules( $this->mQueryPropModules, $wgAPIPropModules );
- self::appendUserModules( $this->mQueryListModules, $wgAPIListModules );
- self::appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules );
-
- $this->mPropModuleNames = array_keys( $this->mQueryPropModules );
- $this->mListModuleNames = array_keys( $this->mQueryListModules );
- $this->mMetaModuleNames = array_keys( $this->mQueryMetaModules );
-
- // Get array of query generators from cache if present
- $key = wfMemcKey( 'apiquerygenerators', SpecialVersion::getVersion( 'nodb' ) );
-
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $cached = $wgMemc->get( $key );
- if ( $cached ) {
- $this->mAllowedGenerators = $cached;
- return;
- }
- }
- $this->makeGeneratorList( $this->mQueryPropModules );
- $this->makeGeneratorList( $this->mQueryListModules );
+ $this->mModuleMgr = new ApiModuleManager( $this );
- if ( $wgAPICacheHelpTimeout > 0 ) {
- $wgMemc->set( $key, $this->mAllowedGenerators, $wgAPICacheHelpTimeout );
- }
+ // Allow custom modules to be added in LocalSettings.php
+ global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
+ $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
+ $this->mModuleMgr->addModules( $wgAPIPropModules, 'prop' );
+ $this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
+ $this->mModuleMgr->addModules( $wgAPIListModules, 'list' );
+ $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
+ $this->mModuleMgr->addModules( $wgAPIMetaModules, 'meta' );
+
+ // Create PageSet that will process titles/pageids/revids/generator
+ $this->mPageSet = new ApiPageSet( $this );
}
/**
- * Helper function to append any add-in modules to the list
- * @param $modules array Module array
- * @param $newModules array Module array to add to $modules
+ * Overrides to return this instance's module manager.
+ * @return ApiModuleManager
*/
- private static function appendUserModules( &$modules, $newModules ) {
- if ( is_array( $newModules ) ) {
- foreach ( $newModules as $moduleName => $moduleClass ) {
- $modules[$moduleName] = $moduleClass;
- }
- }
- }
-
- /**
- * Gets a default slave database connection object
- * @return DatabaseBase
- */
- public function getDB() {
- if ( !isset( $this->mSlaveDB ) ) {
- $this->profileDBIn();
- $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
- $this->profileDBOut();
- }
- return $this->mSlaveDB;
+ public function getModuleManager() {
+ return $this->mModuleMgr;
}
/**
@@ -170,9 +150,9 @@ class ApiQuery extends ApiBase {
* If no such connection has been requested before, it will be created.
* Subsequent calls with the same $name will return the same connection
* as the first, regardless of the values of $db and $groups
- * @param $name string Name to assign to the database connection
- * @param $db int One of the DB_* constants
- * @param $groups array Query groups
+ * @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 getNamedDB( $name, $db, $groups ) {
@@ -194,31 +174,38 @@ 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)
+ */
+ 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)
*/
- function getModules() {
- return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules );
+ public function getGenerators() {
+ wfDeprecated( __METHOD__, '1.21' );
+ $gens = array();
+ foreach ( $this->mModuleMgr->getNamesWithClasses() as $name => $class ) {
+ if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) {
+ $gens[$name] = $class;
+ }
+ }
+ return $gens;
}
/**
* Get whether the specified module is a prop, list or a meta query module
- * @param $moduleName string Name of the module to find type for
+ * @deprecated since 1.21, use getModuleManager()->getModuleGroup()
+ * @param string $moduleName Name of the module to find type for
* @return mixed string or null
*/
function getModuleType( $moduleName ) {
- if ( isset( $this->mQueryPropModules[$moduleName] ) ) {
- return 'prop';
- }
-
- if ( isset( $this->mQueryListModules[$moduleName] ) ) {
- return 'list';
- }
-
- if ( isset( $this->mQueryMetaModules[$moduleName] ) ) {
- return 'meta';
- }
-
- return null;
+ return $this->getModuleManager()->getModuleGroup( $moduleName );
}
/**
@@ -247,42 +234,37 @@ class ApiQuery extends ApiBase {
* #5 Execute all requested modules
*/
public function execute() {
- $this->params = $this->extractRequestParams();
- $this->redirects = $this->params['redirects'];
- $this->convertTitles = $this->params['converttitles'];
- $this->iwUrl = $this->params['iwurl'];
+ $this->mParams = $this->extractRequestParams();
- // Create PageSet
- $this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
+ // $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
- $modules = array();
- $this->instantiateModules( $modules, 'prop', $this->mQueryPropModules );
- $this->instantiateModules( $modules, 'list', $this->mQueryListModules );
- $this->instantiateModules( $modules, 'meta', $this->mQueryMetaModules );
-
- $cacheMode = 'public';
-
- // If given, execute generator to substitute user supplied data with generated data.
- if ( isset( $this->params['generator'] ) ) {
- $generator = $this->newGenerator( $this->params['generator'] );
- $params = $generator->extractRequestParams();
- $cacheMode = $this->mergeCacheMode( $cacheMode,
- $generator->getCacheMode( $params ) );
- $this->executeGeneratorModule( $generator, $modules );
- } else {
- // Append custom fields and populate page/revision information
- $this->addCustomFldsToPageSet( $modules, $this->mPageSet );
+ $allModules = array();
+ $this->instantiateModules( $allModules, 'prop' );
+ $propModules = $allModules; // Keep a copy
+ $this->instantiateModules( $allModules, 'list' );
+ $this->instantiateModules( $allModules, 'meta' );
+
+ // Filter modules based on continue parameter
+ $modules = $this->initModules( $allModules, $completeModules, $pagesetParams !== null );
+
+ // Execute pageset if in legacy mode or if pageset is not done
+ if ( $completeModules === null || $pagesetParams !== null ) {
+ // Populate page/revision information
$this->mPageSet->execute();
+ // Record page information (title, namespace, if exists, etc)
+ $this->outputGeneralPageInfo();
+ } else {
+ $this->mPageSet->executeDryRun();
}
- // Record page information (title, namespace, if exists, etc)
- $this->outputGeneralPageInfo();
+ $cacheMode = $this->mPageSet->getCacheMode();
- // Execute all requested modules.
- /**
- * @var $module ApiQueryBase
- */
+ // Execute all unfinished modules
+ /** @var $module ApiQueryBase */
foreach ( $modules as $module ) {
$params = $module->extractRequestParams();
$cacheMode = $this->mergeCacheMode(
@@ -295,6 +277,136 @@ 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();
+ $main = $this->getMain();
+
+ /** @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;
}
/**
@@ -320,32 +432,21 @@ class ApiQuery extends ApiBase {
}
/**
- * 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.
- * @param $modules array of module objects
- * @param $pageSet ApiPageSet
- */
- private function addCustomFldsToPageSet( $modules, $pageSet ) {
- // Query all requested modules.
- /**
- * @var $module ApiQueryBase
- */
- foreach ( $modules as $module ) {
- $module->requestExtraData( $pageSet );
- }
- }
-
- /**
* Create instances of all modules requested by the client
- * @param $modules Array to append instantiated modules to
- * @param $param string Parameter name to read modules from
- * @param $moduleList Array array(modulename => classname)
+ * @param array $modules to append instantiated modules to
+ * @param string $param Parameter name to read modules from
*/
- private function instantiateModules( &$modules, $param, $moduleList ) {
- if ( isset( $this->params[$param] ) ) {
- foreach ( $this->params[$param] as $moduleName ) {
- $modules[] = new $moduleList[$moduleName] ( $this, $moduleName );
+ private function instantiateModules( &$modules, $param ) {
+ 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' );
+ }
+ // Ignore duplicates. TODO 2.0: die()?
+ if ( !array_key_exists( $moduleName, $modules ) ) {
+ $modules[$moduleName] = $instance;
+ }
}
}
}
@@ -363,85 +464,25 @@ class ApiQuery extends ApiBase {
// more than 380K. The maximum revision size is in the megabyte range,
// and the maximum result size must be even higher than that.
- // Title normalizations
- $normValues = array();
- foreach ( $pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
- $normValues[] = array(
- 'from' => $rawTitleStr,
- 'to' => $titleStr
- );
+ $values = $pageSet->getNormalizedTitlesAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'normalized', $values );
}
-
- if ( count( $normValues ) ) {
- $result->setIndexedTagName( $normValues, 'n' );
- $result->addValue( 'query', 'normalized', $normValues );
+ $values = $pageSet->getConvertedTitlesAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'converted', $values );
}
-
- // Title conversions
- $convValues = array();
- foreach ( $pageSet->getConvertedTitles() as $rawTitleStr => $titleStr ) {
- $convValues[] = array(
- 'from' => $rawTitleStr,
- 'to' => $titleStr
- );
+ $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
+ if ( $values ) {
+ $result->addValue( 'query', 'interwiki', $values );
}
-
- if ( count( $convValues ) ) {
- $result->setIndexedTagName( $convValues, 'c' );
- $result->addValue( 'query', 'converted', $convValues );
+ $values = $pageSet->getRedirectTitlesAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'redirects', $values );
}
-
- // Interwiki titles
- $intrwValues = array();
- foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
- $item = array(
- 'title' => $rawTitleStr,
- 'iw' => $interwikiStr,
- );
- if ( $this->iwUrl ) {
- $title = Title::newFromText( $rawTitleStr );
- $item['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
- }
- $intrwValues[] = $item;
- }
-
- if ( count( $intrwValues ) ) {
- $result->setIndexedTagName( $intrwValues, 'i' );
- $result->addValue( 'query', 'interwiki', $intrwValues );
- }
-
- // Show redirect information
- $redirValues = array();
- /**
- * @var $titleTo Title
- */
- foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleTo ) {
- $r = array(
- 'from' => strval( $titleStrFrom ),
- 'to' => $titleTo->getPrefixedText(),
- );
- if ( $titleTo->getFragment() !== '' ) {
- $r['tofragment'] = $titleTo->getFragment();
- }
- $redirValues[] = $r;
- }
-
- if ( count( $redirValues ) ) {
- $result->setIndexedTagName( $redirValues, 'r' );
- $result->addValue( 'query', 'redirects', $redirValues );
- }
-
- // Missing revision elements
- $missingRevIDs = $pageSet->getMissingRevisionIDs();
- if ( count( $missingRevIDs ) ) {
- $revids = array();
- foreach ( $missingRevIDs as $revid ) {
- $revids[$revid] = array(
- 'revid' => $revid
- );
- }
- $result->setIndexedTagName( $revids, 'rev' );
- $result->addValue( 'query', 'badrevids', $revids );
+ $values = $pageSet->getMissingRevisionIDsAsResult( $result );
+ if ( $values ) {
+ $result->addValue( 'query', 'badrevids', $values );
}
// Page elements
@@ -466,6 +507,7 @@ class ApiQuery extends ApiBase {
);
}
// Report special pages
+ /** @var $title Title */
foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
@@ -489,7 +531,7 @@ class ApiQuery extends ApiBase {
}
if ( count( $pages ) ) {
- if ( $this->params['indexpageids'] ) {
+ if ( $this->mParams['indexpageids'] ) {
$pageIDs = array_keys( $pages );
// json treats all map keys as strings - converting to match
$pageIDs = array_map( 'strval', $pageIDs );
@@ -500,21 +542,44 @@ class ApiQuery extends ApiBase {
$result->setIndexedTagName( $pages, 'page' );
$result->addValue( 'query', 'pages', $pages );
}
- if ( $this->params['export'] ) {
+ if ( $this->mParams['export'] ) {
$this->doExport( $pageSet, $result );
}
}
/**
- * @param $pageSet ApiPageSet Pages to be exported
- * @param $result ApiResult Result to output to
+ * 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
+ * @param string $paramName
+ * @param mixed $paramValue
+ * @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;
+ }
+
+ /**
+ * @param $pageSet ApiPageSet Pages to be exported
+ * @param $result ApiResult Result to output to
*/
- private function doExport( $pageSet, $result ) {
+ private function doExport( $pageSet, $result ) {
$exportTitles = array();
$titles = $pageSet->getGoodTitles();
if ( count( $titles ) ) {
+ $user = $this->getUser();
+ /** @var $title Title */
foreach ( $titles as $title ) {
- if ( $title->userCan( 'read' ) ) {
+ if ( $title->userCan( 'read', $user ) ) {
$exportTitles[] = $title;
}
}
@@ -536,7 +601,7 @@ class ApiQuery extends ApiBase {
// It's not continuable, so it would cause more
// problems than it'd solve
$result->disableSizeCheck();
- if ( $this->params['exportnowrap'] ) {
+ if ( $this->mParams['exportnowrap'] ) {
$result->reset();
// Raw formatter will handle this
$result->addValue( null, 'text', $exportxml );
@@ -549,80 +614,30 @@ class ApiQuery extends ApiBase {
$result->enableSizeCheck();
}
- /**
- * Create a generator object of the given type and return it
- * @param $generatorName string Module name
- * @return ApiQueryGeneratorBase
- */
- public function newGenerator( $generatorName ) {
- // Find class that implements requested generator
- if ( isset( $this->mQueryListModules[$generatorName] ) ) {
- $className = $this->mQueryListModules[$generatorName];
- } elseif ( isset( $this->mQueryPropModules[$generatorName] ) ) {
- $className = $this->mQueryPropModules[$generatorName];
- } else {
- ApiBase::dieDebug( __METHOD__, "Unknown generator=$generatorName" );
- }
- $generator = new $className ( $this, $generatorName );
- if ( !$generator instanceof ApiQueryGeneratorBase ) {
- $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
- }
- $generator->setGeneratorMode();
- return $generator;
- }
-
- /**
- * For generator mode, execute generator, and use its output as new
- * ApiPageSet
- * @param $generator ApiQueryGeneratorBase Generator Module
- * @param $modules array of module objects
- */
- protected function executeGeneratorModule( $generator, $modules ) {
- // Generator results
- $resultPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
-
- // Add any additional fields modules may need
- $generator->requestExtraData( $this->mPageSet );
- $this->addCustomFldsToPageSet( $modules, $resultPageSet );
-
- // Populate page information with the original user input
- $this->mPageSet->execute();
-
- // populate resultPageSet with the generator output
- $generator->profileIn();
- $generator->executeGenerator( $resultPageSet );
- wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$resultPageSet ) );
- $resultPageSet->finishPageSetGeneration();
- $generator->profileOut();
-
- // Swap the resulting pageset back in
- $this->mPageSet = $resultPageSet;
- }
-
- public function getAllowedParams() {
- return array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mPropModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'prop' )
),
'list' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mListModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'list' )
),
'meta' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mMetaModuleNames
+ ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'meta' )
),
- 'generator' => array(
- ApiBase::PARAM_TYPE => $this->mAllowedGenerators
- ),
- 'redirects' => false,
- 'converttitles' => false,
'indexpageids' => false,
'export' => false,
'exportnowrap' => false,
'iwurl' => false,
+ 'continue' => null,
);
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
}
/**
@@ -630,42 +645,40 @@ class ApiQuery extends ApiBase {
* @return string
*/
public function makeHelpMsg() {
- // Make sure the internal object is empty
- // (just in case a sub-module decides to optimize during instantiation)
- $this->mPageSet = null;
+
+ // Use parent to make default message for the query module
+ $msg = parent::makeHelpMsg();
$querySeparator = str_repeat( '--- ', 12 );
$moduleSeparator = str_repeat( '*** ', 14 );
- $msg = "\n$querySeparator Query: Prop $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' );
+ $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n";
+ $msg .= $this->makeHelpMsgHelper( 'prop' );
$msg .= "\n$querySeparator Query: List $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' );
+ $msg .= $this->makeHelpMsgHelper( 'list' );
$msg .= "\n$querySeparator Query: Meta $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryMetaModules, 'meta' );
+ $msg .= $this->makeHelpMsgHelper( 'meta' );
$msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n";
- // Use parent to make default message for the query module
- $msg = parent::makeHelpMsg() . $msg;
-
return $msg;
}
/**
- * For all modules in $moduleList, generate help messages and join them together
- * @param $moduleList Array array(modulename => classname)
- * @param $paramName string Parameter name
+ * For all modules of a given group, generate help messages and join them together
+ * @param string $group Module group
* @return string
*/
- private function makeHelpMsgHelper( $moduleList, $paramName ) {
+ private function makeHelpMsgHelper( $group ) {
$moduleDescriptions = array();
- foreach ( $moduleList as $moduleName => $moduleClass ) {
+ $moduleNames = $this->mModuleMgr->getNames( $group );
+ sort( $moduleNames );
+ foreach ( $moduleNames as $name ) {
/**
* @var $module ApiQueryBase
*/
- $module = new $moduleClass( $this, $moduleName, null );
+ $module = $this->mModuleMgr->getModule( $name );
- $msg = ApiMain::makeHelpMsgHeader( $module, $paramName );
+ $msg = ApiMain::makeHelpMsgHeader( $module, $group );
$msg2 = $module->makeHelpMsg();
if ( $msg2 !== false ) {
$msg .= $msg2;
@@ -679,46 +692,23 @@ class ApiQuery extends ApiBase {
return implode( "\n", $moduleDescriptions );
}
- /**
- * Adds any classes that are a subclass of ApiQueryGeneratorBase
- * to the allowed generator list
- * @param $moduleList array()
- */
- private function makeGeneratorList( $moduleList ) {
- foreach( $moduleList as $moduleName => $moduleClass ) {
- if ( is_subclass_of( $moduleClass, 'ApiQueryGeneratorBase' ) ) {
- $this->mAllowedGenerators[] = $moduleName;
- }
- }
- }
-
- /**
- * Override to add extra parameters from PageSet
- * @return string
- */
- public function makeHelpMsgParameters() {
- $psModule = new ApiPageSet( $this );
- return $psModule->makeHelpMsgParameters() . parent::makeHelpMsgParameters();
- }
-
public function shouldCheckMaxlag() {
return true;
}
public function getParamDescription() {
- return array(
+ return $this->getPageSet()->getParamDescription() + array(
'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',
- 'generator' => array( 'Use the output of a list as the input for other prop/list/meta items',
- '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 ) ),
'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',
'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.',
+ '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.' ),
);
}
@@ -731,15 +721,16 @@ class ApiQuery extends ApiBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
- ) );
+ return array_merge(
+ parent::getPossibleErrors(),
+ $this->getPageSet()->getPossibleErrors()
+ );
}
public function getExamples() {
return array(
- 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
- 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
+ '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=',
);
}
@@ -750,12 +741,4 @@ class ApiQuery extends ApiBase {
'https://www.mediawiki.org/wiki/API:Lists',
);
}
-
- public function getVersion() {
- $psModule = new ApiPageSet( $this );
- $vers = array();
- $vers[] = __CLASS__ . ': $Id$';
- $vers[] = $psModule->getVersion();
- return $vers;
- }
}
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index 4f4c77f0..496a0eb8 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -60,10 +60,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
$this->addWhere( "cat_title $op= $cont_from" );
@@ -81,7 +78,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
} else {
$this->addWhereRange( 'cat_pages', 'older', $max, $min);
}
-
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
@@ -225,12 +221,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
return 'Enumerate all categories';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&list=allcategories&acprop=size',
@@ -241,8 +231,4 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allcategories';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php
index b562da8e..e24b162c 100644
--- a/includes/api/ApiQueryAllImages.php
+++ b/includes/api/ApiQueryAllImages.php
@@ -41,8 +41,8 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
/**
- * Override parent method to make sure to make sure the repo's DB is used
- * which may not necesarilly be the same as the local DB.
+ * Override parent method to make sure the repo's DB is used
+ * which may not necessarily be the same as the local DB.
*
* TODO: allow querying non-local repos.
* @return DatabaseBase
@@ -93,7 +93,10 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$prop = array_flip( $params['prop'] );
$this->addFields( LocalFile::selectFields() );
- $dir = ( in_array( $params['dir'], array( 'descending', 'older' ) ) ? 'older' : 'newer' );
+ $ascendingOrder = true;
+ if ( $params['dir'] == 'descending' || $params['dir'] == 'older' ) {
+ $ascendingOrder = false;
+ }
if ( $params['sort'] == 'name' ) {
// Check mutually exclusive params
@@ -110,19 +113,16 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
// Pagination
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
- $op = ( $dir == 'older' ? '<' : '>' );
- $cont_from = $db->addQuotes( $cont[0] );
- $this->addWhere( "img_name $op= $cont_from" );
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
+ $op = ( $ascendingOrder ? '>' : '<' );
+ $continueFrom = $db->addQuotes( $cont[0] );
+ $this->addWhere( "img_name $op= $continueFrom" );
}
// Image filters
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
$to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
- $this->addWhereRange( 'img_name', $dir, $from, $to );
+ $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() ) );
@@ -135,13 +135,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$this->dieUsage( "Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name", 'badparams' );
}
}
- if (!is_null( $params['user'] ) && $params['filterbots'] != 'all') {
+ 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 'user' and 'filterbots' cannot be used together", 'badparams' );
+ $this->dieUsage( "Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together", 'badparams' );
}
// Pagination
- $this->addTimestampWhereRange( 'img_timestamp', $dir, $params['start'], $params['end'] );
+ $this->addTimestampWhereRange( 'img_timestamp', ( $ascendingOrder ? 'newer' : 'older' ), $params['start'], $params['end'] );
// Image filters
if ( !is_null( $params['user'] ) ) {
@@ -149,8 +149,6 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
if ( $params['filterbots'] != 'all' ) {
$this->addTables( 'user_groups' );
- $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL': 'NOT NULL' );
- $this->addWhere( "ug_group IS $groupCond" );
$this->addJoinConds( array( 'user_groups' => array(
'LEFT JOIN',
array(
@@ -158,6 +156,8 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'ug_user = img_user'
)
) ) );
+ $groupCond = ( $params['filterbots'] == 'nobots' ? 'NULL': 'NOT NULL' );
+ $this->addWhere( "ug_group IS $groupCond" );
}
}
@@ -172,12 +172,13 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$sha1 = false;
if ( isset( $params['sha1'] ) ) {
- if ( !$this->validateSha1Hash( $params['sha1'] ) ) {
+ $sha1 = strtolower( $params['sha1'] );
+ if ( !$this->validateSha1Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
}
- $sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 );
+ $sha1 = wfBaseConvert( $sha1, 16, 36, 31 );
} elseif ( isset( $params['sha1base36'] ) ) {
- $sha1 = $params['sha1base36'];
+ $sha1 = strtolower( $params['sha1base36'] );
if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
}
@@ -200,16 +201,19 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
- $sort = ( $dir == 'older' ? ' DESC' : '' );
+ $sortFlag = '';
+ if ( !$ascendingOrder ) {
+ $sortFlag = ' DESC';
+ }
if ( $params['sort'] == 'timestamp' ) {
- $this->addOption( 'ORDER BY', 'img_timestamp' . $sort );
- if ( $params['filterbots'] == 'all' ) {
- $this->addOption( 'USE INDEX', array( 'image' => 'img_timestamp' ) );
- } else {
+ $this->addOption( 'ORDER BY', 'img_timestamp' . $sortFlag );
+ if ( !is_null( $params['user'] ) ) {
$this->addOption( 'USE INDEX', array( 'image' => 'img_usertext_timestamp' ) );
+ } else {
+ $this->addOption( 'USE INDEX', array( 'image' => 'img_timestamp' ) );
}
} else {
- $this->addOption( 'ORDER BY', 'img_name' . $sort );
+ $this->addOption( 'ORDER BY', 'img_name' . $sortFlag );
}
$res = $this->select( __METHOD__ );
@@ -272,7 +276,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'descending',
// sort=timestamp
'newer',
- 'older',
+ 'older'
)
),
'from' => null,
@@ -373,12 +377,11 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
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 'user' and 'filterbots' cannot be used together" ),
+ 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' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -402,8 +405,4 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allimages';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index da4840f0..e355f8b0 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -32,7 +32,34 @@
class ApiQueryAllLinks extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
- parent::__construct( $query, $moduleName, 'al' );
+ switch ( $moduleName ) {
+ case 'alllinks':
+ $prefix = 'al';
+ $this->table = 'pagelinks';
+ $this->tablePrefix = 'pl_';
+ $this->dfltNamespace = NS_MAIN;
+ $this->indexTag = 'l';
+ $this->description = 'Enumerate all links that point to a given namespace';
+ $this->descriptionLink = 'link';
+ $this->descriptionLinked = 'linked';
+ $this->descriptionLinking = 'linking';
+ break;
+ case 'alltransclusions':
+ $prefix = 'at';
+ $this->table = 'templatelinks';
+ $this->tablePrefix = 'tl_';
+ $this->dfltNamespace = NS_TEMPLATE;
+ $this->indexTag = 't';
+ $this->description = 'List all transclusions (pages embedded using {{x}}), including non-existing';
+ $this->descriptionLink = 'transclusion';
+ $this->descriptionLinked = 'transcluded';
+ $this->descriptionLinking = 'transcluding';
+ break;
+ default:
+ ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
+ }
+
+ parent::__construct( $query, $moduleName, $prefix );
}
public function execute() {
@@ -55,75 +82,71 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$db = $this->getDB();
$params = $this->extractRequestParams();
+ $pfx = $this->tablePrefix;
$prop = array_flip( $params['prop'] );
$fld_ids = isset( $prop['ids'] );
$fld_title = isset( $prop['title'] );
if ( $params['unique'] ) {
- if ( !is_null( $resultPageSet ) ) {
- $this->dieUsage( $this->getModuleName() . ' cannot be used as a generator in unique links mode', 'params' );
- }
if ( $fld_ids ) {
- $this->dieUsage( $this->getModuleName() . ' cannot return corresponding page ids in unique links mode', 'params' );
+ $this->dieUsage(
+ "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionLink}s mode",
+ 'params' );
}
$this->addOption( 'DISTINCT' );
}
- $this->addTables( 'pagelinks' );
- $this->addWhereFld( 'pl_namespace', $params['namespace'] );
+ $this->addTables( $this->table );
+ $this->addWhereFld( $pfx . 'namespace', $params['namespace'] );
- if ( !is_null( $params['from'] ) && !is_null( $params['continue'] ) ) {
- $this->dieUsage( 'alcontinue and alfrom cannot be used together', 'params' );
- }
- if ( !is_null( $params['continue'] ) ) {
+ $continue = !is_null( $params['continue'] );
+ if ( $continue ) {
$continueArr = explode( '|', $params['continue'] );
$op = $params['dir'] == 'descending' ? '<' : '>';
if ( $params['unique'] ) {
- if ( count( $continueArr ) != 1 ) {
- $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $continueArr ) != 1 );
$continueTitle = $db->addQuotes( $continueArr[0] );
- $this->addWhere( "pl_title $op= $continueTitle" );
+ $this->addWhere( "{$pfx}title $op= $continueTitle" );
} else {
- if ( count( $continueArr ) != 2 ) {
- $this->dieUsage( 'Invalid continue parameter', 'badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $continueArr ) != 2 );
$continueTitle = $db->addQuotes( $continueArr[0] );
$continueFrom = intval( $continueArr[1] );
$this->addWhere(
- "pl_title $op $continueTitle OR " .
- "(pl_title = $continueTitle AND " .
- "pl_from $op= $continueFrom)"
+ "{$pfx}title $op $continueTitle OR " .
+ "({$pfx}title = $continueTitle AND " .
+ "{$pfx}from $op= $continueFrom)"
);
}
}
- $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
+ // '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'] ) );
- $this->addWhereRange( 'pl_title', 'newer', $from, $to );
+ $this->addWhereRange( $pfx . 'title', 'newer', $from, $to );
if ( isset( $params['prefix'] ) ) {
- $this->addWhere( 'pl_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( $pfx . 'title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
- $this->addFields( 'pl_title' );
- $this->addFieldsIf( 'pl_from', !$params['unique'] );
+ $this->addFields( array( 'pl_title' => $pfx . 'title' ) );
+ $this->addFieldsIf( array( 'pl_from' => $pfx . 'from' ), !$params['unique'] );
- $this->addOption( 'USE INDEX', 'pl_namespace' );
+ $this->addOption( 'USE INDEX', $pfx . 'namespace' );
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
$orderBy = array();
- $orderBy[] = 'pl_title' . $sort;
+ $orderBy[] = $pfx . 'title' . $sort;
if ( !$params['unique'] ) {
- $orderBy[] = 'pl_from' . $sort;
+ $orderBy[] = $pfx . 'from' . $sort;
}
$this->addOption( 'ORDER BY', $orderBy );
$res = $this->select( __METHOD__ );
$pageids = array();
+ $titles = array();
$count = 0;
$result = $this->getResult();
foreach ( $res as $row ) {
@@ -132,7 +155,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
- $this->setContinueEnumParameter( 'continue', $row->pl_title . "|" . $row->pl_from );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title . '|' . $row->pl_from );
}
break;
}
@@ -151,17 +174,21 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
if ( $params['unique'] ) {
$this->setContinueEnumParameter( 'continue', $row->pl_title );
} else {
- $this->setContinueEnumParameter( 'continue', $row->pl_title . "|" . $row->pl_from );
+ $this->setContinueEnumParameter( 'continue', $row->pl_title . '|' . $row->pl_from );
}
break;
}
+ } elseif ( $params['unique'] ) {
+ $titles[] = Title::makeTitle( $params['namespace'], $row->pl_title );
} else {
$pageids[] = $row->pl_from;
}
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'l' );
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $this->indexTag );
+ } elseif ( $params['unique'] ) {
+ $resultPageSet->populateFromTitles( $titles );
} else {
$resultPageSet->populateFromPageIDs( $pageids );
}
@@ -183,7 +210,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
)
),
'namespace' => array(
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => $this->dfltNamespace,
ApiBase::PARAM_TYPE => 'namespace'
),
'limit' => array(
@@ -205,18 +232,23 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
public function getParamDescription() {
$p = $this->getModulePrefix();
+ $link = $this->descriptionLink;
+ $linking = $this->descriptionLinking;
return array(
- 'from' => 'The page title to start enumerating from',
- 'to' => 'The page title to stop enumerating at',
- 'prefix' => 'Search for all page titles that begin with this value',
- 'unique' => "Only show unique links. Cannot be used with generator or {$p}prop=ids",
+ 'from' => "The title of the $link to start enumerating from",
+ 'to' => "The title of the $link to stop enumerating at",
+ 'prefix' => "Search for all $link titles that begin with this value",
+ 'unique' => array(
+ "Only show distinct $link titles. Cannot be used with {$p}prop=ids.",
+ 'When used as a generator, yields target pages instead of source pages.',
+ ),
'prop' => array(
'What pieces of information to include',
- " ids - Adds pageid of where the link is from (Cannot be used with {$p}unique)",
- ' title - Adds the title of the link',
+ " ids - Adds the pageid of the $linking page (Cannot be used with {$p}unique)",
+ " title - Adds the title of the $link",
),
'namespace' => 'The namespace to enumerate',
- 'limit' => 'How many total links to return',
+ 'limit' => "How many total items to return",
'continue' => 'When more results are available, use this to continue',
'dir' => 'The direction in which to list',
);
@@ -235,30 +267,34 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
public function getDescription() {
- return 'Enumerate all links that point to a given namespace';
+ return $this->description;
}
public function getPossibleErrors() {
$m = $this->getModuleName();
+ $link = $this->descriptionLink;
return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'params', 'info' => "{$m} cannot be used as a generator in unique links mode" ),
- array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique links mode" ),
- array( 'code' => 'params', 'info' => 'alcontinue and alfrom cannot be used together' ),
- array( 'code' => 'badcontinue', 'info' => 'Invalid continue parameter' ),
+ array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$link}s mode" ),
) );
}
public function getExamples() {
+ $p = $this->getModulePrefix();
+ $link = $this->descriptionLink;
+ $linked = $this->descriptionLinked;
return array(
- 'api.php?action=query&list=alllinks&alunique=&alfrom=B',
+ "api.php?action=query&list=all{$link}s&{$p}from=B&{$p}prop=ids|title"
+ => "List $linked titles with page ids they are from, including missing ones. Start at B",
+ "api.php?action=query&list=all{$link}s&{$p}unique=&{$p}from=B"
+ => "List unique $linked titles",
+ "api.php?action=query&generator=all{$link}s&g{$p}unique=&g{$p}from=B"
+ => "Gets all $link targets, marking the missing ones",
+ "api.php?action=query&generator=all{$link}s&g{$p}from=B"
+ => "Gets pages containing the {$link}s",
);
}
public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:Alllinks';
- }
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
+ return "https://www.mediawiki.org/wiki/API:All{$this->descriptionLink}s";
}
}
diff --git a/includes/api/ApiQueryAllMessages.php b/includes/api/ApiQueryAllMessages.php
index f5e1146b..c9811b0d 100644
--- a/includes/api/ApiQueryAllMessages.php
+++ b/includes/api/ApiQueryAllMessages.php
@@ -39,8 +39,9 @@ class ApiQueryAllMessages extends ApiQueryBase {
$params = $this->extractRequestParams();
if ( is_null( $params['lang'] ) ) {
- global $wgLang;
- $langObj = $wgLang;
+ $langObj = $this->getLanguage();
+ } elseif ( !Language::isValidCode( $params['lang'] ) ) {
+ $this->dieUsage( 'Invalid language code for parameter lang', 'invalidlang' );
} else {
$langObj = Language::factory( $params['lang'] );
}
@@ -48,7 +49,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
if ( $params['enableparser'] ) {
if ( !is_null( $params['title'] ) ) {
$title = Title::newFromText( $params['title'] );
- if ( !$title ) {
+ if ( !$title || $title->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
} else {
@@ -116,7 +117,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
$lang = $langObj->getCode();
$customisedMessages = AllmessagesTablePager::getCustomisedStatuses(
- array_map( array( $langObj, 'ucfirst'), $messages_target ), $lang, $lang != $wgContLang->getCode() );
+ array_map( array( $langObj, 'ucfirst' ), $messages_target ), $lang, $lang != $wgContLang->getCode() );
$customised = $params['customised'] === 'modified';
}
@@ -143,7 +144,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
}
if ( $customiseFilterEnabled ) {
- $messageIsCustomised = isset( $customisedMessages['pages'][ $langObj->ucfirst( $message ) ] );
+ $messageIsCustomised = isset( $customisedMessages['pages'][$langObj->ucfirst( $message )] );
if ( $customised === $messageIsCustomised ) {
if ( $customised ) {
$a['customised'] = '';
@@ -256,6 +257,12 @@ 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(
@@ -291,8 +298,4 @@ class ApiQueryAllMessages extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Meta#allmessages_.2F_am';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllPages.php b/includes/api/ApiQueryAllPages.php
index 16cc31d2..d718b967 100644
--- a/includes/api/ApiQueryAllPages.php
+++ b/includes/api/ApiQueryAllPages.php
@@ -69,10 +69,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
$this->addWhere( "page_title $op= $cont_from" );
@@ -120,7 +117,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
if ( count( $params['prtype'] ) || $params['prexpiry'] != 'all' ) {
$this->addTables( 'page_restrictions' );
$this->addWhere( 'page_id=pr_page' );
- $this->addWhere( 'pr_expiry>' . $db->addQuotes( $db->timestamp() ) );
+ $this->addWhere( "pr_expiry > {$db->addQuotes( $db->timestamp() )} OR pr_expiry IS NULL" );
if ( count( $params['prtype'] ) ) {
$this->addWhereFld( 'pr_type', $params['prtype'] );
@@ -138,8 +135,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
} elseif ( $params['prfiltercascade'] == 'noncascading' ) {
$this->addWhereFld( 'pr_cascade', 0 );
}
-
- $this->addOption( 'DISTINCT' );
}
$forceNameTitleIndex = false;
@@ -149,6 +144,8 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$this->addWhere( "pr_expiry != {$db->addQuotes( $db->getInfinity() )}" );
}
+ $this->addOption( 'DISTINCT' );
+
} elseif ( isset( $params['prlevel'] ) ) {
$this->dieUsage( 'prlevel may not be used without prtype', 'params' );
}
@@ -226,7 +223,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'to' => null,
'prefix' => null,
'namespace' => array(
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => NS_MAIN,
ApiBase::PARAM_TYPE => 'namespace',
),
'filterredir' => array(
@@ -336,7 +333,6 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
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' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -351,7 +347,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
'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 begining at "Re"',
+ 'Show content of first 2 non-redirect pages beginning at "Re"',
)
);
}
@@ -359,8 +355,4 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allpages';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 7f50cbad..7283aa00 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -37,7 +37,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
/**
* This function converts the user name to a canonical form
* which is stored in the database.
- * @param String $name
+ * @param string $name
* @return String
*/
private function getCanonicalUserName( $name ) {
@@ -81,12 +81,18 @@ class ApiQueryAllUsers extends ApiQueryBase {
$db->buildLike( $this->getCanonicalUserName( $params['prefix'] ), $db->anyString() ) );
}
- if ( !is_null( $params['rights'] ) ) {
+ if ( !is_null( $params['rights'] ) && count( $params['rights'] ) ) {
$groups = array();
foreach( $params['rights'] as $r ) {
$groups = array_merge( $groups, User::getGroupsWithPermission( $r ) );
}
+ // 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;
+ }
+
$groups = array_unique( $groups );
if ( is_null( $params['group'] ) ) {
@@ -155,7 +161,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$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 );
+ $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 );
$this->addWhere( 'rc_timestamp >= ' . $db->addQuotes( $timestamp ) );
$this->addOption( 'GROUP BY', $userFieldToSort );
@@ -273,7 +279,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( $fld_rights ) {
if ( !isset( $lastUserData['rights'] ) ) {
if ( $lastUserObj ) {
- $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
+ $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
} else {
// This should not normally happen
$lastUserData['rights'] = array();
@@ -438,8 +444,4 @@ class ApiQueryAllUsers extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Allusers';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index 06db87bf..3ef6b840 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -188,6 +188,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$titleWhere = array();
$allRedirNs = array();
$allRedirDBkey = array();
+ /** @var $t Title */
foreach ( $this->redirTitles as $t ) {
$redirNs = $t->getNamespace();
$redirDBkey = $t->getDBkey();
@@ -201,6 +202,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
if ( !is_null( $this->redirID ) ) {
$op = $this->params['dir'] == 'descending' ? '<' : '>';
+ /** @var $first Title */
$first = $this->redirTitles[0];
$title = $db->addQuotes( $first->getDBkey() );
$ns = $first->getNamespace();
@@ -246,7 +248,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->params = $this->extractRequestParams( false );
$this->redirect = isset( $this->params['redirect'] ) && $this->params['redirect'];
$userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1 / 2 : ApiBase::LIMIT_BIG1 );
- $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 );
+ $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 );
$result = $this->getResult();
@@ -406,20 +408,14 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// null stuff out now so we know what's set and what isn't
$this->rootTitle = $this->contID = $this->redirID = null;
$rootNs = intval( $continueList[0] );
- if ( $rootNs === 0 && $continueList[0] !== '0' ) {
- // Illegal continue parameter
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( $rootNs === 0 && $continueList[0] !== '0' );
+
$this->rootTitle = Title::makeTitleSafe( $rootNs, $continueList[1] );
+ $this->dieContinueUsageIf( !$this->rootTitle );
- if ( !$this->rootTitle ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
- }
$contID = intval( $continueList[2] );
+ $this->dieContinueUsageIf( $contID === 0 && $continueList[2] !== '0' );
- if ( $contID === 0 && $continueList[2] !== '0' ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
- }
$this->contID = $contID;
$id2 = isset( $continueList[3] ) ? $continueList[3] : null;
$redirID = intval( $id2 );
@@ -455,12 +451,12 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => 'namespace'
),
- 'dir' => array(
- ApiBase::PARAM_DFLT => 'ascending',
- ApiBase::PARAM_TYPE => array(
- 'ascending',
- 'descending'
- )
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
),
'filterredir' => array(
ApiBase::PARAM_DFLT => 'all',
@@ -535,7 +531,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->getTitleOrPageIdErrorMessage(),
array(
array( 'code' => 'bad_image_title', 'info' => "The title for {$this->getModuleName()} query must be an image" ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
)
);
}
@@ -562,8 +557,4 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return $this->helpUrl;
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 2c48aca0..7819ead4 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -112,7 +112,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of fields to select to the internal array
- * @param $value array|string Field name or array of field names
+ * @param array|string $value Field name or array of field names
*/
protected function addFields( $value ) {
if ( is_array( $value ) ) {
@@ -124,8 +124,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addFields(), but add the fields only if a condition is met
- * @param $value array|string See addFields()
- * @param $condition bool If false, do nothing
+ * @param array|string $value See addFields()
+ * @param bool $condition If false, do nothing
* @return bool $condition
*/
protected function addFieldsIf( $value, $condition ) {
@@ -162,7 +162,7 @@ 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 $condition bool If false, do nothing
+ * @param bool $condition If false, do nothing
* @return bool $condition
*/
protected function addWhereIf( $value, $condition ) {
@@ -175,8 +175,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Equivalent to addWhere(array($field => $value))
- * @param $field string Field name
- * @param $value string Value; ignored if null or empty array;
+ * @param string $field Field name
+ * @param string $value Value; ignored if null or empty array;
*/
protected function addWhereFld( $field, $value ) {
// Use count() to its full documented capabilities to simultaneously
@@ -189,14 +189,14 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a WHERE clause corresponding to a range, and an ORDER BY
* clause to sort in the right direction
- * @param $field string Field name
- * @param $dir string If 'newer', sort in ascending order, otherwise
+ * @param string $field Field name
+ * @param string $dir If 'newer', sort in ascending order, otherwise
* sort in descending order
- * @param $start string Value to start the list at. If $dir == 'newer'
+ * @param string $start Value to start the list at. If $dir == 'newer'
* this is the lower boundary, otherwise it's the upper boundary
- * @param $end string Value to end the list at. If $dir == 'newer' this
+ * @param string $end Value to end the list at. If $dir == 'newer' this
* is the upper boundary, otherwise it's the lower boundary
- * @param $sort bool If false, don't add an ORDER BY clause
+ * @param bool $sort If false, don't add an ORDER BY clause
*/
protected function addWhereRange( $field, $dir, $start, $end, $sort = true ) {
$isDirNewer = ( $dir === 'newer' );
@@ -240,8 +240,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add an option such as LIMIT or USE INDEX. If an option was set
* before, the old value will be overwritten
- * @param $name string Option name
- * @param $value string Option value
+ * @param string $name Option name
+ * @param string $value Option value
*/
protected function addOption( $name, $value = null ) {
if ( is_null( $value ) ) {
@@ -253,9 +253,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Execute a SELECT query based on the values in the internal arrays
- * @param $method string Function the query should be attributed to.
+ * @param string $method Function the query should be attributed to.
* You should usually use __METHOD__ here
- * @param $extraQuery array Query data to add but not store in the object
+ * @param array $extraQuery Query data to add but not store in the object
* Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... )
* @return ResultWrapper
*/
@@ -298,9 +298,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add information (title and namespace) about a Title object to a
* result array
- * @param $arr array Result array à la ApiResult
+ * @param array $arr Result array à la ApiResult
* @param $title Title
- * @param $prefix string Module prefix
+ * @param string $prefix Module prefix
*/
public static function addTitleInfo( &$arr, $title, $prefix = '' ) {
$arr[$prefix . 'ns'] = intval( $title->getNamespace() );
@@ -325,8 +325,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a sub-element under the page element with the given page ID
- * @param $pageId int Page ID
- * @param $data array Data array à la ApiResult
+ * @param int $pageId Page ID
+ * @param array $data Data array à la ApiResult
* @return bool Whether the element fit in the result
*/
protected function addPageSubItems( $pageId, $data ) {
@@ -339,9 +339,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addPageSubItems(), but one element of $data at a time
- * @param $pageId int Page ID
- * @param $item array Data array à la ApiResult
- * @param $elemname string XML element name. If null, getModuleName()
+ * @param int $pageId Page ID
+ * @param array $item Data array à la ApiResult
+ * @param string $elemname XML element name. If null, getModuleName()
* is used
* @return bool Whether the element fit in the result
*/
@@ -351,7 +351,7 @@ abstract class ApiQueryBase extends ApiBase {
}
$result = $this->getResult();
$fit = $result->addValue( array( 'query', 'pages', $pageId,
- $this->getModuleName() ), null, $item );
+ $this->getModuleName() ), null, $item );
if ( !$fit ) {
return false;
}
@@ -362,15 +362,15 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Set a query-continue value
- * @param $paramName string Parameter name
- * @param $paramValue string Parameter value
+ * @param string $paramName Parameter name
+ * @param string $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 );
+ $result->addValue( 'query-continue', $this->getModuleName(), $msg, ApiResult::ADD_ON_TOP );
$result->enableSizeCheck();
}
@@ -380,8 +380,7 @@ abstract class ApiQueryBase extends ApiBase {
*/
protected function getDB() {
if ( is_null( $this->mDb ) ) {
- $apiQuery = $this->getQuery();
- $this->mDb = $apiQuery->getDB();
+ $this->mDb = $this->getQuery()->getDB();
}
return $this->mDb;
}
@@ -389,9 +388,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Selects the query database connection with the given name.
* See ApiQuery::getNamedDB() for more information
- * @param $name string Name to assign to the database connection
- * @param $db int One of the DB_* constants
- * @param $groups array Query groups
+ * @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 ) {
@@ -408,7 +407,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Convert a title to a DB key
- * @param $title string Page title with spaces
+ * @param string $title Page title with spaces
* @return string Page title with underscores
*/
public function titleToKey( $title ) {
@@ -420,12 +419,12 @@ abstract class ApiQueryBase extends ApiBase {
if ( !$t ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
- return $t->getPrefixedDbKey();
+ return $t->getPrefixedDBkey();
}
/**
* The inverse of titleToKey()
- * @param $key string Page title with underscores
+ * @param string $key Page title with underscores
* @return string Page title with spaces
*/
public function keyToTitle( $key ) {
@@ -443,7 +442,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* An alternative to titleToKey() that doesn't trim trailing spaces
- * @param $titlePart string Title part with spaces
+ * @param string $titlePart Title part with spaces
* @return string Title part with underscores
*/
public function titlePartToKey( $titlePart ) {
@@ -452,7 +451,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* An alternative to keyToTitle() that doesn't trim trailing spaces
- * @param $keyPart string Key part with spaces
+ * @param string $keyPart Key part with spaces
* @return string Key part with underscores
*/
public function keyPartToTitle( $keyPart ) {
@@ -534,7 +533,7 @@ abstract class ApiQueryBase extends ApiBase {
* @return bool
*/
public function validateSha1Hash( $hash ) {
- return preg_match( '/[a-fA-F0-9]{40}/', $hash );
+ return preg_match( '/^[a-f0-9]{40}$/', $hash );
}
/**
@@ -542,25 +541,19 @@ abstract class ApiQueryBase extends ApiBase {
* @return bool
*/
public function validateSha1Base36Hash( $hash ) {
- return preg_match( '/[a-zA-Z0-9]{31}/', $hash );
+ return preg_match( '/^[a-z0-9]{31}$/', $hash );
}
/**
* @return array
*/
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
+ $errors = parent::getPossibleErrors();
+ $errors = array_merge( $errors, array(
array( 'invalidtitle', 'title' ),
array( 'invalidtitle', 'key' ),
) );
- }
-
- /**
- * Get version string for use in the API help output
- * @return string
- */
- public static function getBaseVersion() {
- return __CLASS__ . ': $Id$';
+ return $errors;
}
}
@@ -569,33 +562,41 @@ abstract class ApiQueryBase extends ApiBase {
*/
abstract class ApiQueryGeneratorBase extends ApiQueryBase {
- private $mIsGenerator;
+ private $mGeneratorPageSet = null;
/**
- * @param $query ApiBase
- * @param $moduleName string
- * @param $paramPrefix string
+ * 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
+ * by calling getPageSet() when in generator mode.
*/
- public function __construct( $query, $moduleName, $paramPrefix = '' ) {
- parent::__construct( $query, $moduleName, $paramPrefix );
- $this->mIsGenerator = false;
+ public function setGeneratorMode( ApiPageSet $generatorPageSet ) {
+ if ( $generatorPageSet === null ) {
+ ApiBase::dieDebug( __METHOD__, 'Required parameter missing - $generatorPageSet' );
+ }
+ $this->mGeneratorPageSet = $generatorPageSet;
}
/**
- * Switch this module to generator mode. By default, generator mode is
- * switched off and the module acts like a normal query module.
+ * Get the PageSet object to work on.
+ * If this module is generator, the pageSet object is different from other module's
+ * @return ApiPageSet
*/
- public function setGeneratorMode() {
- $this->mIsGenerator = true;
+ protected function getPageSet() {
+ if ( $this->mGeneratorPageSet !== null ) {
+ return $this->mGeneratorPageSet;
+ }
+ return parent::getPageSet();
}
/**
* Overrides base class to prepend 'g' to every generator parameter
- * @param $paramName string Parameter name
+ * @param string $paramName Parameter name
* @return string Prefixed parameter name
*/
public function encodeParamName( $paramName ) {
- if ( $this->mIsGenerator ) {
+ if ( $this->mGeneratorPageSet !== null ) {
return 'g' . parent::encodeParamName( $paramName );
} else {
return parent::encodeParamName( $paramName );
@@ -603,9 +604,24 @@ 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.
+ * @param string $paramName Parameter name
+ * @param string $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 )
+ ) {
+ parent::setContinueEnumParameter( $paramName, $paramValue );
+ }
+ }
+
+ /**
* Execute this module as a generator
* @param $resultPageSet ApiPageSet: All output should be appended to
* this object
*/
- public abstract function executeGenerator( $resultPageSet );
+ abstract public function executeGenerator( $resultPageSet );
}
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index 96b86962..d9be9f28 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -132,10 +132,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_range_end = ipb_range_start", isset( $show['!range'] ) );
- $this->addWhereIf( "ipb_range_end > ipb_range_start", isset( $show['range'] ) );
+ $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'] ) );
}
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
@@ -182,8 +182,8 @@ class ApiQueryBlocks extends ApiQueryBase {
$block['reason'] = $row->ipb_reason;
}
if ( $fld_range && !$row->ipb_auto ) {
- $block['rangestart'] = IP::hexToQuad( $row->ipb_range_start );
- $block['rangeend'] = IP::hexToQuad( $row->ipb_range_end );
+ $block['rangestart'] = IP::formatHex( $row->ipb_range_start );
+ $block['rangeend'] = IP::formatHex( $row->ipb_range_end );
}
if ( $fld_flags ) {
// For clarity, these flags use the same names as their action=block counterparts
@@ -301,7 +301,7 @@ class ApiQueryBlocks extends ApiQueryBase {
'dir' => $this->getDirectionDescription( $p ),
'ids' => 'List of block IDs to list (optional)',
'users' => 'List of users to search for (optional)',
- 'ip' => array( 'Get all blocks applying to this IP or CIDR range, including range blocks.',
+ '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 /16 are not accepted' ),
'limit' => 'The maximum amount of blocks to list',
'prop' => array(
@@ -404,8 +404,4 @@ class ApiQueryBlocks extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Blocks';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 309c2ce9..69a64415 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -53,7 +53,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
- return; // nothing to do
+ return; // nothing to do
}
$params = $this->extractRequestParams();
@@ -85,10 +85,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$clfrom = intval( $cont[0] );
$clto = $this->getDB()->addQuotes( $cont[1] );
@@ -276,8 +273,4 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#categories_.2F_cl';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index 31517fab..a889272e 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -48,6 +48,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
$this->getPageSet()->getMissingTitles();
$cattitles = array();
foreach ( $categories as $c ) {
+ /** @var $t Title */
$t = $titles[$c];
$cattitles[$c] = $t->getDBkey();
}
@@ -146,8 +147,4 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#categoryinfo_.2F_ci';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index 55ce0234..9dbd8593 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -78,7 +78,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->addFieldsIf( 'cl_timestamp', $fld_timestamp || $params['sort'] == 'timestamp' );
- $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX'
+ $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX'
$this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() );
$queryTypes = $params['type'];
@@ -106,11 +106,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
} else {
if ( $params['continue'] ) {
$cont = explode( '|', $params['continue'], 3 );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned '.
- 'by the previous query', '_badcontinue'
- );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
// Remove the types to skip from $queryTypes
$contTypeIndex = array_search( $cont[0], $queryTypes );
@@ -118,7 +114,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
// Add a WHERE clause for sortkey and from
// pack( "H*", $foo ) is used to convert hex back to binary
- $escSortkey = $this->getDB()->addQuotes( pack( "H*", $cont[1] ) );
+ $escSortkey = $this->getDB()->addQuotes( pack( 'H*', $cont[1] ) );
$from = intval( $cont[2] );
$op = $dir == 'newer' ? '>' : '<';
// $contWhere is used further down
@@ -247,7 +243,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
$result->setIndexedTagName_internal(
- array( 'query', $this->getModuleName() ), 'cm' );
+ array( 'query', $this->getModuleName() ), 'cm' );
}
}
@@ -403,7 +399,6 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->getTitleOrPageIdErrorMessage(),
array(
array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ),
- array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
)
);
}
@@ -418,8 +413,4 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Categorymembers';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index e69ccbd6..31ca1ef5 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -74,15 +74,15 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $mode == 'revs' || $mode == 'user' ) {
// Ignore namespace and unique due to inability to know whether they were purposely set
- foreach( array( 'from', 'to', 'prefix', /*'namespace',*/ 'continue', /*'unique'*/ ) as $p ) {
+ foreach( array( 'from', 'to', 'prefix', /*'namespace', 'unique'*/ ) as $p ) {
if ( !is_null( $params[$p] ) ) {
- $this->dieUsage( "The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams');
+ $this->dieUsage( "The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams' );
}
}
} else {
foreach( array( 'start', 'end' ) as $p ) {
if ( !is_null( $params[$p] ) ) {
- $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams');
+ $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams' );
}
}
}
@@ -116,7 +116,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
// Check limits
$userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1;
- $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
+ $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
$limit = $params['limit'];
@@ -160,10 +160,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', 'badcontinue' );
- }
+ $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' ? '>' : '<' );
@@ -307,7 +306,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
),
'namespace' => array(
ApiBase::PARAM_TYPE => 'namespace',
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => NS_MAIN,
),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -362,7 +361,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'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 (3)',
+ 'continue' => 'When more results are available, use this to continue (1, 3)',
'unique' => 'List only one revision for each page (3)',
);
}
@@ -397,11 +396,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
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' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
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 'continue' 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" ),
) );
@@ -423,8 +420,4 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Deletedrevs';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php
index 6715969a..cf0d841e 100644
--- a/includes/api/ApiQueryDisabled.php
+++ b/includes/api/ApiQueryDisabled.php
@@ -36,10 +36,6 @@
*/
class ApiQueryDisabled extends ApiQueryBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$this->setWarning( "The \"{$this->getModuleName()}\" module has been disabled." );
}
@@ -61,8 +57,4 @@ class ApiQueryDisabled extends ApiQueryBase {
public function getExamples() {
return array();
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index 8f0fd3be..18dcba85 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -66,10 +66,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$skipUntilThisDup = false;
if ( isset( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$fromImage = $cont[0];
$skipUntilThisDup = $cont[1];
// Filter out any images before $fromImage
@@ -95,6 +92,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$sha1s = array();
foreach ( $files as $file ) {
+ /** @var $file File */
$sha1s[$file->getName()] = $file->getSha1();
}
@@ -116,6 +114,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
if( $params['dir'] == 'descending' ) {
$dupFiles = array_reverse( $dupFiles );
}
+ /** @var $dupFile File */
foreach ( $dupFiles as $dupFile ) {
$dupName = $dupFile->getName();
if( $image == $dupName && $dupFile->isLocal() ) {
@@ -133,7 +132,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
break;
}
if ( !is_null( $resultPageSet ) ) {
- $titles[] = $file->getTitle();
+ $titles[] = $dupFile->getTitle();
} else {
$r = array(
'name' => $dupName,
@@ -204,12 +203,6 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
return 'List all files that are duplicates of the given file(s) based on hash values';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles',
@@ -220,8 +213,4 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#duplicatefiles_.2F_df';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index 42b398ba..eb9cdf9e 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -55,7 +55,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$query = $params['query'];
$protocol = self::getProtocolPrefix( $params['protocol'] );
- $this->addTables( array( 'page', 'externallinks' ) ); // must be in this order for 'USE INDEX'
+ $this->addTables( array( 'page', 'externallinks' ) ); // must be in this order for 'USE INDEX'
$this->addOption( 'USE INDEX', 'el_index' );
$this->addWhere( 'page_id=el_from' );
@@ -121,8 +121,12 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
ApiQueryBase::addTitleInfo( $vals, $title );
}
if ( $fld_url ) {
- // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan
- $vals['url'] = $row->el_to;
+ $to = $row->el_to;
+ // expand protocol-relative urls
+ if( $params['expandurl'] ) {
+ $to = wfExpandUrl( $to, PROTO_CANONICAL );
+ }
+ $vals['url'] = $to;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
@@ -169,7 +173,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
- )
+ ),
+ 'expandurl' => false,
);
}
@@ -218,7 +223,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
),
'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.'
+ 'limit' => 'How many pages to return.',
+ 'expandurl' => 'Expand protocol-relative urls with the canonical protocol',
);
if ( $wgMiserMode ) {
@@ -266,8 +272,4 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Exturlusage';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index 9365a9b8..761b49ea 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -86,8 +86,12 @@ class ApiQueryExternalLinks extends ApiQueryBase {
break;
}
$entry = array();
- // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan
- ApiResult::setContent( $entry, $row->el_to );
+ $to = $row->el_to;
+ // expand protocol-relative urls
+ if( $params['expandurl'] ) {
+ $to = wfExpandUrl( $to, PROTO_CANONICAL );
+ }
+ ApiResult::setContent( $entry, $to );
$fit = $this->addPageSubItem( $row->el_from, $entry );
if ( !$fit ) {
$this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
@@ -117,6 +121,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
ApiBase::PARAM_DFLT => '',
),
'query' => null,
+ 'expandurl' => false,
);
}
@@ -130,6 +135,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
"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',
+ 'expandurl' => 'Expand protocol-relative urls with the canonical protocol',
);
}
@@ -142,7 +148,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
}
public function getDescription() {
- return 'Returns all external urls (not interwikies) from the given page(s)';
+ return 'Returns all external urls (not interwikis) from the given page(s)';
}
public function getPossibleErrors() {
@@ -160,8 +166,4 @@ class ApiQueryExternalLinks extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#extlinks_.2F_el';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index a5486ef4..021074a9 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -64,7 +64,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
$this->addTables( 'filearchive' );
$this->addFields( array( 'fa_name', 'fa_deleted' ) );
- $this->addFieldsIf( 'fa_storage_key', $fld_sha1 );
+ $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 );
@@ -77,10 +77,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 1 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$cont_from = $db->addQuotes( $cont[0] );
$this->addWhere( "fa_name $op= $cont_from" );
@@ -101,25 +98,21 @@ class ApiQueryFilearchive extends ApiQueryBase {
$sha1Set = isset( $params['sha1'] );
$sha1base36Set = isset( $params['sha1base36'] );
if ( $sha1Set || $sha1base36Set ) {
- global $wgMiserMode;
- if ( $wgMiserMode ) {
- $this->dieUsage( 'Search by hash disabled in Miser Mode', 'hashsearchdisabled' );
- }
-
$sha1 = false;
if ( $sha1Set ) {
- if ( !$this->validateSha1Hash( $params['sha1'] ) ) {
+ $sha1 = strtolower( $params['sha1'] );
+ if ( !$this->validateSha1Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
}
- $sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 );
+ $sha1 = wfBaseConvert( $sha1, 16, 36, 31 );
} elseif ( $sha1base36Set ) {
- if ( !$this->validateSha1Base36Hash( $params['sha1base36'] ) ) {
+ $sha1 = strtolower( $params['sha1base36'] );
+ if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
$this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
}
- $sha1 = $params['sha1base36'];
}
if ( $sha1 ) {
- $this->addWhere( 'fa_storage_key ' . $db->buildLike( "{$sha1}.", $db->anyString() ) );
+ $this->addWhereFld( 'fa_sha1', $sha1 );
}
}
@@ -155,7 +148,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
self::addTitleInfo( $file, $title );
if ( $fld_sha1 ) {
- $file['sha1'] = wfBaseConvert( LocalRepo::getHashFromKey( $row->fa_storage_key ), 36, 16, 40 );
+ $file['sha1'] = wfBaseConvert( $row->fa_sha1, 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
@@ -214,7 +207,6 @@ class ApiQueryFilearchive extends ApiQueryBase {
$file['suppressed'] = '';
}
-
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $row->fa_name );
@@ -276,8 +268,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
'prefix' => 'Search for all image titles that begin with this value',
'dir' => 'The direction in which to list',
'limit' => 'How many images to return in total',
- 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36. Disabled in Miser Mode",
- 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki). Disabled in Miser Mode',
+ 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36",
+ 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
'prop' => array(
'What image information to get:',
' sha1 - Adds SHA-1 hash for the image',
@@ -370,7 +362,6 @@ class ApiQueryFilearchive extends ApiQueryBase {
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' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -382,8 +373,4 @@ class ApiQueryFilearchive extends ApiQueryBase {
),
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index c5012f08..b47d31f2 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -56,10 +56,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$db = $this->getDB();
$op = $params['dir'] == 'descending' ? '<' : '>';
@@ -233,7 +230,6 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'prefix' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -243,8 +239,4 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
'api.php?action=query&generator=iwbacklinks&giwbltitle=Test&giwblprefix=wikibooks&prop=info'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index 30c7f5a8..fc77b4e6 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -58,10 +58,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$db = $this->getDB();
$iwlfrom = intval( $cont[0] );
@@ -187,7 +184,6 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'prefix' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -196,8 +192,4 @@ class ApiQueryIWLinks extends ApiQueryBase {
'api.php?action=query&prop=iwlinks&titles=Main%20Page' => 'Get interwiki links from the [[Main Page]]',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index d822eed5..95c2745a 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -30,6 +30,8 @@
* @ingroup API
*/
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.
@@ -52,14 +54,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
$titles = array_keys( $pageIds[NS_FILE] );
asort( $titles ); // Ensure the order is always the same
- $skip = false;
+ $fromTitle = null;
if ( !is_null( $params['continue'] ) ) {
- $skip = true;
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$fromTitle = strval( $cont[0] );
$fromTimestamp = $cont[1];
// Filter out any titles before $fromTitle
@@ -79,14 +77,34 @@ class ApiQueryImageInfo extends ApiQueryBase {
} else {
$images = RepoGroup::singleton()->findFiles( $titles );
}
- foreach ( $images as $img ) {
- // Skip redirects
- if ( $img->getOriginalTitle()->isRedirect() ) {
+ foreach ( $titles as $title ) {
+ $pageId = $pageIds[NS_FILE][$title];
+ $start = $title === $fromTitle ? $fromTimestamp : $params['start'];
+
+ if ( !isset( $images[$title] ) ) {
+ $result->addValue(
+ array( 'query', 'pages', intval( $pageId ) ),
+ 'imagerepository', ''
+ );
+ // The above can't fail because it doesn't increase the result size
continue;
}
- $start = $skip ? $fromTimestamp : $params['start'];
- $pageId = $pageIds[NS_FILE][ $img->getOriginalTitle()->getDBkey() ];
+ /** @var $img File */
+ $img = $images[$title];
+
+ if ( self::getTransformCount() >= self::TRANSFORM_LIMIT ) {
+ if ( count( $pageIds[NS_FILE] ) == 1 ) {
+ // See the 'the user is screwed' comment below
+ $this->setContinueEnumParameter( 'start',
+ $start !== null ? $start : wfTimestamp( TS_ISO_8601, $img->getTimestamp() )
+ );
+ } else {
+ $this->setContinueEnumParameter( 'continue',
+ $this->getContinueStr( $img, $start ) );
+ }
+ break;
+ }
$fit = $result->addValue(
array( 'query', 'pages', intval( $pageId ) ),
@@ -100,10 +118,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
// thing again. When the violating queries have been
// out-continued, the result will get through
$this->setContinueEnumParameter( 'start',
- wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
+ $start !== null ? $start : wfTimestamp( TS_ISO_8601, $img->getTimestamp() )
+ );
} else {
$this->setContinueEnumParameter( 'continue',
- $this->getContinueStr( $img ) );
+ $this->getContinueStr( $img, $start ) );
}
break;
}
@@ -140,6 +159,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
// Get one more to facilitate query-continue functionality
$count = ( $gotOne ? 1 : 0 );
$oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] );
+ /** @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...
@@ -167,25 +187,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( !$fit ) {
break;
}
- $skip = false;
- }
-
- $data = $this->getResultData();
- foreach ( $data['query']['pages'] as $pageid => $arr ) {
- if ( !isset( $arr['imagerepository'] ) ) {
- $result->addValue(
- array( 'query', 'pages', $pageid ),
- 'imagerepository', ''
- );
- }
- // The above can't fail because it doesn't increase the result size
}
}
}
/**
* From parameters, construct a 'scale' array
- * @param $params Array: Parameters passed to api.
+ * @param array $params Parameters passed to api.
* @return Array or Null: key-val array of 'width' and 'height', or null
*/
public function getScale( $params ) {
@@ -216,8 +224,8 @@ 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).
+ * @param array $thumbParams thumbnail parameters from getScale
+ * @param string $otherParams of otherParams (iiurlparam).
* @return Array of parameters for transform.
*/
protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) {
@@ -264,10 +272,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
* Get result information for an image revision
*
* @param $file File object
- * @param $prop Array of properties to get (in the keys)
+ * @param array $prop of properties to get (in the keys)
* @param $result ApiResult object
- * @param $thumbParams Array containing 'width' and 'height' items, or null
- * @param $version string Version of image metadata (for things like jpeg which have different versions).
+ * @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
*/
static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
@@ -346,6 +354,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $url ) {
if ( !is_null( $thumbParams ) ) {
$mto = $file->transform( $thumbParams );
+ self::$transformCount++;
if ( $mto && !$mto->isError() ) {
$vals['thumburl'] = wfExpandUrl( $mto->getUrl(), PROTO_CURRENT );
@@ -360,7 +369,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) {
- list( $ext, $mime ) = $file->getHandler()->getThumbType(
+ list( , $mime ) = $file->getHandler()->getThumbType(
$mto->getExtension(), $file->getMimeType(), $thumbParams );
$vals['thumbmime'] = $mime;
}
@@ -377,8 +386,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
if ( $meta ) {
+ wfSuppressWarnings();
$metadata = unserialize( $file->getMetadata() );
- if ( $version !== 'latest' ) {
+ wfRestoreWarnings();
+ if ( $metadata && $version !== 'latest' ) {
$metadata = $file->convertMetadataVersion( $metadata, $version );
}
$vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
@@ -404,6 +415,17 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
/**
+ * Get the count of image transformations performed
+ *
+ * If this is >= TRANSFORM_LIMIT, you should probably stop processing images.
+ *
+ * @return integer count
+ */
+ static function getTransformCount() {
+ return self::$transformCount;
+ }
+
+ /**
*
* @param $metadata Array
* @param $result ApiResult
@@ -432,11 +454,14 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* @param $img File
+ * @param null|string $start
* @return string
*/
- protected function getContinueStr( $img ) {
- return $img->getOriginalTitle()->getText() .
- '|' . $img->getTimestamp();
+ protected function getContinueStr( $img, $start = null ) {
+ if ( $start === null ) {
+ $start = $img->getTimestamp();
+ }
+ return $img->getOriginalTitle()->getText() . '|' . $start;
}
public function getAllowedParams() {
@@ -494,6 +519,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* Returns array key value pairs of properties and their descriptions
*
+ * @param string $modulePrefix
* @return array
*/
private static function getProperties( $modulePrefix = '' ) {
@@ -540,7 +566,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
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.",
- 'Only the current version of the image can be scaled' ),
+ 'Only the current version of the image can be scaled' ),
'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$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" ),
@@ -578,6 +604,15 @@ class ApiQueryImageInfo extends ApiQueryBase {
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(
@@ -633,6 +668,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
ApiBase::PROP_NULLABLE => true
)
),
+ 'thumbmime' => array(
+ 'filehidden' => 'boolean',
+ 'thumbmime' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ )
+ ),
'mediatype' => array(
'filehidden' => 'boolean',
'mediatype' => array(
@@ -672,7 +714,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
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" ),
- array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesnt't " .
+ array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesn't " .
"match the one in {$p}urlwidth" ),
) );
}
@@ -687,8 +729,4 @@ class ApiQueryImageInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index 6052a75f..f2bf0a7b 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -49,7 +49,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
- return; // nothing to do
+ return; // nothing to do
}
$params = $this->extractRequestParams();
@@ -62,10 +62,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
$this->addWhereFld( 'il_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$ilfrom = intval( $cont[0] );
$ilto = $this->getDB()->addQuotes( $cont[1] );
@@ -185,12 +182,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
return 'Returns all images contained on the given page(s)';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&prop=images&titles=Main%20Page' => 'Get a list of images used in the [[Main Page]]',
@@ -201,8 +192,4 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#images_.2F_im';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 5d4f0346..37cd9159 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -33,7 +33,8 @@ class ApiQueryInfo extends ApiQueryBase {
private $fld_protection = false, $fld_talkid = false,
$fld_subjectid = false, $fld_url = false,
- $fld_readable = false, $fld_watched = false, $fld_notificationtimestamp = false,
+ $fld_readable = false, $fld_watched = false, $fld_watchers = false,
+ $fld_notificationtimestamp = false,
$fld_preload = false, $fld_displaytitle = false;
private $params, $titles, $missing, $everything, $pageCounter;
@@ -41,7 +42,8 @@ class ApiQueryInfo extends ApiQueryBase {
private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched,
$pageLatest, $pageLength;
- private $protections, $watched, $notificationtimestamps, $talkids, $subjectids, $displaytitles;
+ private $protections, $watched, $watchers, $notificationtimestamps, $talkids, $subjectids, $displaytitles;
+ private $showZeroWatchers = false;
private $tokenFunctions;
@@ -96,7 +98,7 @@ class ApiQueryInfo extends ApiQueryBase {
'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ),
'email' => array( 'ApiQueryInfo', 'getEmailToken' ),
'import' => array( 'ApiQueryInfo', 'getImportToken' ),
- 'watch' => array( 'ApiQueryInfo', 'getWatchToken'),
+ 'watch' => array( 'ApiQueryInfo', 'getWatchToken' ),
);
wfRunHooks( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
@@ -118,11 +120,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'edit' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'edit' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['edit'] ) ) {
+ ApiQueryInfo::$cachedTokens['edit'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'edit' ];
+ return ApiQueryInfo::$cachedTokens['edit'];
}
public static function getDeleteToken( $pageid, $title ) {
@@ -132,11 +134,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'delete' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'delete' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['delete'] ) ) {
+ ApiQueryInfo::$cachedTokens['delete'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'delete' ];
+ return ApiQueryInfo::$cachedTokens['delete'];
}
public static function getProtectToken( $pageid, $title ) {
@@ -146,11 +148,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'protect' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'protect' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['protect'] ) ) {
+ ApiQueryInfo::$cachedTokens['protect'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'protect' ];
+ return ApiQueryInfo::$cachedTokens['protect'];
}
public static function getMoveToken( $pageid, $title ) {
@@ -160,11 +162,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'move' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'move' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['move'] ) ) {
+ ApiQueryInfo::$cachedTokens['move'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'move' ];
+ return ApiQueryInfo::$cachedTokens['move'];
}
public static function getBlockToken( $pageid, $title ) {
@@ -174,11 +176,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'block' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'block' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['block'] ) ) {
+ ApiQueryInfo::$cachedTokens['block'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'block' ];
+ return ApiQueryInfo::$cachedTokens['block'];
}
public static function getUnblockToken( $pageid, $title ) {
@@ -193,11 +195,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'email' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'email' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['email'] ) ) {
+ ApiQueryInfo::$cachedTokens['email'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'email' ];
+ return ApiQueryInfo::$cachedTokens['email'];
}
public static function getImportToken( $pageid, $title ) {
@@ -207,11 +209,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'import' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'import' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['import'] ) ) {
+ ApiQueryInfo::$cachedTokens['import'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'import' ];
+ return ApiQueryInfo::$cachedTokens['import'];
}
public static function getWatchToken( $pageid, $title ) {
@@ -221,11 +223,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'watch' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'watch' ] = $wgUser->getEditToken( 'watch' );
+ if ( !isset( ApiQueryInfo::$cachedTokens['watch'] ) ) {
+ ApiQueryInfo::$cachedTokens['watch'] = $wgUser->getEditToken( 'watch' );
}
- return ApiQueryInfo::$cachedTokens[ 'watch' ];
+ return ApiQueryInfo::$cachedTokens['watch'];
}
public static function getOptionsToken( $pageid, $title ) {
@@ -235,11 +237,11 @@ class ApiQueryInfo extends ApiQueryBase {
}
// The token is always the same, let's exploit that
- if ( !isset( ApiQueryInfo::$cachedTokens[ 'options' ] ) ) {
- ApiQueryInfo::$cachedTokens[ 'options' ] = $wgUser->getEditToken();
+ if ( !isset( ApiQueryInfo::$cachedTokens['options'] ) ) {
+ ApiQueryInfo::$cachedTokens['options'] = $wgUser->getEditToken();
}
- return ApiQueryInfo::$cachedTokens[ 'options' ];
+ return ApiQueryInfo::$cachedTokens['options'];
}
public function execute() {
@@ -248,6 +250,7 @@ class ApiQueryInfo extends ApiQueryBase {
$prop = array_flip( $this->params['prop'] );
$this->fld_protection = isset( $prop['protection'] );
$this->fld_watched = isset( $prop['watched'] );
+ $this->fld_watchers = isset( $prop['watchers'] );
$this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
$this->fld_talkid = isset( $prop['talkid'] );
$this->fld_subjectid = isset( $prop['subjectid'] );
@@ -268,10 +271,7 @@ class ApiQueryInfo extends ApiQueryBase {
// Throw away any titles we're gonna skip so they don't
// clutter queries
$cont = explode( '|', $this->params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$conttitle = Title::makeTitleSafe( $cont[0], $cont[1] );
foreach ( $this->everything as $pageid => $title ) {
if ( Title::compare( $title, $conttitle ) >= 0 ) {
@@ -308,6 +308,10 @@ class ApiQueryInfo extends ApiQueryBase {
$this->getWatchedInfo();
}
+ if ( $this->fld_watchers ) {
+ $this->getWatcherInfo();
+ }
+
// Run the talkid/subjectid query if requested
if ( $this->fld_talkid || $this->fld_subjectid ) {
$this->getTSIDs();
@@ -317,6 +321,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->getDisplayTitle();
}
+ /** @var $title Title */
foreach ( $this->everything as $pageid => $title ) {
$pageInfo = $this->extractPageInfo( $pageid, $title );
$fit = $result->addValue( array(
@@ -334,7 +339,7 @@ class ApiQueryInfo extends ApiQueryBase {
/**
* Get a result array with information about a title
- * @param $pageid int Page ID (negative for missing titles)
+ * @param int $pageid Page ID (negative for missing titles)
* @param $title Title object
* @return array
*/
@@ -349,7 +354,7 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
$pageInfo['lastrevid'] = intval( $this->pageLatest[$pageid] );
$pageInfo['counter'] = $wgDisableCounters
- ? ""
+ ? ''
: intval( $this->pageCounter[$pageid] );
$pageInfo['length'] = intval( $this->pageLength[$pageid] );
@@ -387,6 +392,14 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['watched'] = '';
}
+ if ( $this->fld_watchers ) {
+ if ( isset( $this->watchers[$ns][$dbkey] ) ) {
+ $pageInfo['watchers'] = $this->watchers[$ns][$dbkey];
+ } elseif ( $this->showZeroWatchers ) {
+ $pageInfo['watchers'] = 0;
+ }
+ }
+
if ( $this->fld_notificationtimestamp ) {
$pageInfo['notificationtimestamp'] = '';
if ( isset( $this->notificationtimestamps[$ns][$dbkey] ) ) {
@@ -394,7 +407,7 @@ class ApiQueryInfo extends ApiQueryBase {
}
}
- if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
+ if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
$pageInfo['talkid'] = $this->talkids[$ns][$dbkey];
}
@@ -406,7 +419,7 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
$pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
}
- if ( $this->fld_readable && $title->userCan( 'read' ) ) {
+ if ( $this->fld_readable && $title->userCan( 'read', $this->getUser() ) ) {
$pageInfo['readable'] = '';
}
@@ -450,6 +463,7 @@ class ApiQueryInfo extends ApiQueryBase {
$res = $this->select( __METHOD__ );
foreach ( $res as $row ) {
+ /** @var $title Title */
$title = $this->titles[$row->pr_page];
$a = array(
'type' => $row->pr_type,
@@ -585,6 +599,7 @@ class ApiQueryInfo extends ApiQueryBase {
private function getTSIDs() {
$getTitles = $this->talkids = $this->subjectids = array();
+ /** @var $t Title */
foreach ( $this->everything as $t ) {
if ( MWNamespace::isTalk( $t->getNamespace() ) ) {
if ( $this->fld_subjectid ) {
@@ -678,6 +693,46 @@ 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 ) ) {
+ return;
+ }
+
+ $this->watchers = array();
+ $this->showZeroWatchers = $canUnwatchedpages;
+ $db = $this->getDB();
+
+ $lb = new LinkBatch( $this->everything );
+
+ $this->resetQueryParams();
+ $this->addTables( array( 'watchlist' ) );
+ $this->addFields( array( 'wl_title', 'wl_namespace', 'count' => 'COUNT(*)' ) );
+ $this->addWhere( array(
+ $lb->constructSet( 'wl', $db )
+ ) );
+ $this->addOption( 'GROUP BY', array( 'wl_namespace', 'wl_title' ) );
+ if ( !$canUnwatchedpages ) {
+ $this->addOption( 'HAVING', "COUNT(*) >= $wgUnwatchedPageThreshold" );
+ }
+
+ $res = $this->select( __METHOD__ );
+
+ foreach ( $res as $row ) {
+ $this->watchers[$row->wl_namespace][$row->wl_title] = (int)$row->count;
+ }
+ }
+
public function getCacheMode( $params ) {
$publicProps = array(
'protection',
@@ -709,6 +764,7 @@ class ApiQueryInfo extends ApiQueryBase {
'protection',
'talkid',
'watched', # private
+ 'watchers', # private
'notificationtimestamp', # private
'subjectid',
'url',
@@ -734,6 +790,7 @@ class ApiQueryInfo extends ApiQueryBase {
' protection - List the protection level of each page',
' talkid - The page ID of the talk page for each non-talk page',
' watched - List the watched status of each page',
+ ' 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',
@@ -767,6 +824,12 @@ class ApiQueryInfo extends ApiQueryBase {
'watched' => array(
'watched' => 'boolean'
),
+ 'watchers' => array(
+ 'watchers' => array(
+ ApiBase::PROP_TYPE => 'integer',
+ ApiBase::PROP_NULLABLE => true
+ )
+ ),
'notificationtimestamp' => array(
'notificationtimestamp' => array(
ApiBase::PROP_TYPE => 'timestamp',
@@ -809,12 +872,6 @@ class ApiQueryInfo extends ApiQueryBase {
return 'Get basic page information such as namespace, title, last touched date, ...';
}
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
- }
-
public function getExamples() {
return array(
'api.php?action=query&prop=info&titles=Main%20Page',
@@ -825,8 +882,4 @@ class ApiQueryInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#info_.2F_in';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
index 3920407b..7a4880a4 100644
--- a/includes/api/ApiQueryLangBacklinks.php
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -56,10 +56,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$db = $this->getDB();
$op = $params['dir'] == 'descending' ? '<' : '>';
@@ -233,7 +230,6 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'lang' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -243,8 +239,4 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
'api.php?action=query&generator=langbacklinks&glbltitle=Test&glbllang=fr&prop=info'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index 3109a090..ac65d2d2 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -25,7 +25,7 @@
*/
/**
- * A query module to list all langlinks (links to correspanding foreign language pages).
+ * A query module to list all langlinks (links to corresponding foreign language pages).
*
* @ingroup API
*/
@@ -56,10 +56,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
$this->addWhereFld( 'll_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$llfrom = intval( $cont[0] );
$lllang = $this->getDB()->addQuotes( $cont[1] );
@@ -179,7 +176,6 @@ class ApiQueryLangLinks extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'missingparam', 'lang' ),
- array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -192,8 +188,4 @@ class ApiQueryLangLinks extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#langlinks_.2F_ll';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 9e4b7ebb..937f4f13 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -79,7 +79,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
*/
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
- return; // nothing to do
+ return; // nothing to do
}
$params = $this->extractRequestParams();
@@ -112,10 +112,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 3 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the ' .
- 'original value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 3 );
$op = $params['dir'] == 'descending' ? '<' : '>';
$plfrom = intval( $cont[0] );
$plns = intval( $cont[1] );
@@ -241,17 +238,13 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$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&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",
);
}
public function getHelpUrls() {
return $this->helpUrl;
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index 5d85c221..73dcea49 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -58,7 +58,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->fld_details = isset( $prop['details'] );
$this->fld_tags = isset( $prop['tags'] );
- $hideLogs = LogEventsList::getExcludeClause( $db );
+ $hideLogs = LogEventsList::getExcludeClause( $db, 'user', $this->getUser() );
if ( $hideLogs !== false ) {
$this->addWhere( $hideLogs );
}
@@ -70,7 +70,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
'user' => array( 'JOIN',
'user_id=log_user' ),
'page' => array( 'LEFT JOIN',
- array( 'log_namespace=page_namespace',
+ array( 'log_namespace=page_namespace',
'log_title=page_title' ) ) ) );
$index = array( 'logging' => 'times' ); // default, may change
@@ -151,7 +151,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( is_null( $title ) ) {
$this->dieUsage( "Bad title value '$prefix'", 'param_prefix' );
}
- $this->addWhereFld( 'log_namespace', $title->getNamespace() );
+ $this->addWhereFld( 'log_namespace', $title->getNamespace() );
$this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) );
}
@@ -201,7 +201,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
public static function addLogParams( $result, &$vals, $params, $type, $action, $ts, $legacy = false ) {
switch ( $type ) {
case 'move':
- if ( $legacy ){
+ if ( $legacy ) {
$targetKey = 0;
$noredirKey = 1;
} else {
@@ -209,21 +209,21 @@ class ApiQueryLogEvents extends ApiQueryBase {
$noredirKey = '5::noredir';
}
- if ( isset( $params[ $targetKey ] ) ) {
- $title = Title::newFromText( $params[ $targetKey ] );
+ if ( isset( $params[$targetKey] ) ) {
+ $title = Title::newFromText( $params[$targetKey] );
if ( $title ) {
$vals2 = array();
ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' );
$vals[$type] = $vals2;
}
}
- if ( isset( $params[ $noredirKey ] ) && $params[ $noredirKey ] ) {
+ if ( isset( $params[$noredirKey] ) && $params[$noredirKey] ) {
$vals[$type]['suppressedredirect'] = '';
}
$params = null;
break;
case 'patrol':
- if ( $legacy ){
+ if ( $legacy ) {
$cur = 0;
$prev = 1;
$auto = 2;
@@ -241,7 +241,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
break;
case 'rights':
$vals2 = array();
- list( $vals2['old'], $vals2['new'] ) = $params;
+ if( $legacy ) {
+ list( $vals2['old'], $vals2['new'] ) = $params;
+ } else {
+ $vals2['new'] = implode( ', ', $params['5::newgroups'] );
+ $vals2['old'] = implode( ', ', $params['4::oldgroups'] );
+ }
$vals[$type] = $vals2;
$params = null;
break;
@@ -262,9 +267,19 @@ class ApiQueryLogEvents extends ApiQueryBase {
break;
}
if ( !is_null( $params ) ) {
- $result->setIndexedTagName( $params, 'param' );
- $result->setIndexedTagName_recursive( $params, 'param' );
- $vals = array_merge( $vals, $params );
+ $logParams = array();
+ // Keys like "4::paramname" can't be used for output so we change them to "paramname"
+ foreach ( $params as $key => $value ) {
+ if ( strpos( $key, ':' ) === false ) {
+ $logParams[$key] = $value;
+ continue;
+ }
+ $logParam = explode( ':', $key, 3 );
+ $logParams[$logParam[2]] = $value;
+ }
+ $result->setIndexedTagName( $logParams, 'param' );
+ $result->setIndexedTagName_recursive( $logParams, 'param' );
+ $vals = array_merge( $vals, $logParams );
}
return $vals;
}
@@ -362,8 +377,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( !is_null( $params['prop'] ) && in_array( 'parsedcomment', $params['prop'] ) ) {
// formatComment() calls wfMessage() among other things
return 'anon-public-user-private';
- } else {
+ } elseif ( LogEventsList::getExcludeClause( $this->getDB(), 'user', $this->getUser() )
+ === LogEventsList::getExcludeClause( $this->getDB(), 'public' )
+ ) { // Output can only contain public data.
return 'public';
+ } else {
+ return 'anon-public-user-private';
}
}
@@ -432,7 +451,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
' timestamp - Adds the timestamp for the event',
' comment - Adds the comment of the event',
' parsedcomment - Adds the parsed comment of the event',
- ' details - Lists addtional details about the event',
+ ' details - Lists additional details about the event',
' tags - Lists tags for the event',
),
'type' => 'Filter log entries to only this type',
@@ -526,8 +545,4 @@ class ApiQueryLogEvents extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Logevents';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryORM.php b/includes/api/ApiQueryORM.php
new file mode 100644
index 00000000..41d8f11c
--- /dev/null
+++ b/includes/api/ApiQueryORM.php
@@ -0,0 +1,264 @@
+<?php
+
+/**
+ * Base query module for querying results from ORMTables.
+ *
+ * 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
+ *
+ * @since 1.21
+ *
+ * @file
+ * @ingroup API
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+abstract class ApiQueryORM extends ApiQueryBase {
+
+ /**
+ * Returns an instance of the IORMTable table being queried.
+ *
+ * @since 1.21
+ *
+ * @return IORMTable
+ */
+ abstract protected function getTable();
+
+ /**
+ * Returns the name of the individual rows.
+ * For example: page, user, contest, campaign, etc.
+ * This is used to appropriately name elements in XML.
+ * Deriving classes typically override this method.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getRowName() {
+ return 'item';
+ }
+
+ /**
+ * Returns the name of the list of rows.
+ * For example: pages, users, contests, campaigns, etc.
+ * This is used to appropriately name nodes in the output.
+ * Deriving classes typically override this method.
+ *
+ * @since 1.21
+ *
+ * @return string
+ */
+ protected function getListName() {
+ return 'items';
+ }
+
+ /**
+ * Returns the path to where the items results should be added in the result.
+ *
+ * @since 1.21
+ *
+ * @return null|string|array
+ */
+ protected function getResultPath() {
+ return null;
+ }
+
+ /**
+ * Get the parameters, find out what the conditions for the query are,
+ * run it, and add the results.
+ *
+ * @since 1.21
+ */
+ public function execute() {
+ $params = $this->getParams();
+
+ if ( !in_array( 'id', $params['props'] ) ) {
+ $params['props'][] = 'id';
+ }
+
+ $results = $this->getResults( $params, $this->getConditions( $params ) );
+ $this->addResults( $params, $results );
+ }
+
+ /**
+ * Get the request parameters and remove all params set
+ * to null (ie those that are not actually provided).
+ *
+ * @since 1.21
+ *
+ * @return array
+ */
+ protected function getParams() {
+ return array_filter(
+ $this->extractRequestParams(),
+ function( $prop ) {
+ return isset( $prop );
+ }
+ );
+ }
+
+ /**
+ * Get the conditions for the query. These will be provided as
+ * regular parameters, together with limit, props, continue,
+ * and possibly others which we need to get rid off.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ protected function getConditions( array $params ) {
+ $conditions = array();
+ $fields = $this->getTable()->getFields();
+
+ foreach ( $params as $name => $value ) {
+ if ( array_key_exists( $name, $fields ) ) {
+ $conditions[$name] = $value;
+ }
+ }
+
+ return $conditions;
+ }
+
+ /**
+ * Get the actual results.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ * @param array $conditions
+ *
+ * @return ORMResult
+ */
+ protected function getResults( array $params, array $conditions ) {
+ return $this->getTable()->select(
+ $params['props'],
+ $conditions,
+ array(
+ 'LIMIT' => $params['limit'] + 1,
+ 'ORDER BY' => $this->getTable()->getPrefixedField( 'id' ) . ' ASC',
+ ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * Serialize the results and add them to the result object.
+ *
+ * @since 1.21
+ *
+ * @param array $params
+ * @param ORMResult $results
+ */
+ protected function addResults( array $params, ORMResult $results ) {
+ $serializedResults = array();
+ $count = 0;
+
+ foreach ( $results as /* IORMRow */ $result ) {
+ 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', $result->getId() );
+ break;
+ }
+
+ $serializedResults[] = $this->formatRow( $result, $params );
+ }
+
+ $this->setIndexedTagNames( $serializedResults );
+ $this->addSerializedResults( $serializedResults );
+ }
+
+ /**
+ * Formats a row to it's desired output format.
+ *
+ * @since 1.21
+ *
+ * @param IORMRow $result
+ * @param array $params
+ *
+ * @return mixed
+ */
+ protected function formatRow( IORMRow $result, array $params ) {
+ return $result->toArray( $params['props'] );
+ }
+
+ /**
+ * Set the tag names for formats such as XML.
+ *
+ * @since 1.21
+ *
+ * @param array $serializedResults
+ */
+ protected function setIndexedTagNames( array &$serializedResults ) {
+ $this->getResult()->setIndexedTagName( $serializedResults, $this->getRowName() );
+ }
+
+ /**
+ * Add the serialized results to the result object.
+ *
+ * @since 1.21
+ *
+ * @param array $serializedResults
+ */
+ protected function addSerializedResults( array $serializedResults ) {
+ $this->getResult()->addValue(
+ $this->getResultPath(),
+ $this->getListName(),
+ $serializedResults
+ );
+ }
+
+ /**
+ * @see ApiBase::getAllowedParams()
+ * @return array
+ */
+ public function getAllowedParams() {
+ $params = array (
+ 'props' => array(
+ ApiBase::PARAM_TYPE => $this->getTable()->getFieldNames(),
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_DFLT => 20,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'continue' => null,
+ );
+
+ return array_merge( $this->getTable()->getAPIParams(), $params );
+ }
+
+ /**
+ * @see ApiBase::getParamDescription()
+ * @return array
+ */
+ public function getParamDescription() {
+ $descriptions = array (
+ 'props' => 'Fields to query',
+ 'continue' => 'Offset number from where to continue the query',
+ 'limit' => 'Max amount of rows to return',
+ );
+
+ return array_merge( $this->getTable()->getFieldDescriptions(), $descriptions );
+ }
+
+}
diff --git a/includes/api/ApiQueryPagePropNames.php b/includes/api/ApiQueryPagePropNames.php
new file mode 100644
index 00000000..08c883d8
--- /dev/null
+++ b/includes/api/ApiQueryPagePropNames.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Created on January 21, 2013
+ *
+ * Copyright © 2013 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.21
+ * @author Brad Jorsch
+ */
+
+/**
+ * A query module to list used page props
+ *
+ * @ingroup API
+ * @since 1.21
+ */
+class ApiQueryPagePropNames extends ApiQueryBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'ppn' );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ $this->addTables( 'page_props' );
+ $this->addFields( 'pp_propname' );
+ $this->addOption( 'DISTINCT' );
+ $this->addOption( 'ORDER BY', 'pp_propname' );
+
+ if ( $params['continue'] ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
+
+ // Add a WHERE clause
+ $this->addWhereRange( 'pp_propname', 'newer', $cont[0], null );
+ }
+
+ $limit = $params['limit'];
+ $this->addOption( 'LIMIT', $limit + 1 );
+
+ $result = $this->getResult();
+ $count = 0;
+ foreach ( $this->select( __METHOD__ ) 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->pp_propname );
+ break;
+ }
+
+ $vals = array();
+ $vals['propname'] = $row->pp_propname;
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $row->pp_propname );
+ break;
+ }
+ }
+
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'continue' => null,
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'continue' => 'When more results are available, use this to continue',
+ 'limit' => 'The maximum number of pages to return',
+ );
+ }
+
+ public function getDescription() {
+ return 'List all page prop names in use on the wiki';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&list=pagepropnames' => 'Get first 10 prop names',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Pagepropnames';
+ }
+}
diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php
index 1eef67e6..2de57106 100644
--- a/includes/api/ApiQueryPageProps.php
+++ b/includes/api/ApiQueryPageProps.php
@@ -49,7 +49,7 @@ class ApiQueryPageProps extends ApiQueryBase {
$this->addTables( 'page_props' );
$this->addFields( array( 'pp_page', 'pp_propname', 'pp_value' ) );
- $this->addWhereFld( 'pp_page', array_keys( $pages ) );
+ $this->addWhereFld( 'pp_page', array_keys( $pages ) );
if ( $this->params['continue'] ) {
$this->addWhere( 'pp_page >=' . intval( $this->params['continue'] ) );
@@ -60,7 +60,10 @@ class ApiQueryPageProps extends ApiQueryBase {
}
# Force a sort order to ensure that properties are grouped by page
- $this->addOption( 'ORDER BY', 'pp_page' );
+ # But only if pp_page is not constant in the WHERE clause.
+ if ( count( $pages ) > 1 ) {
+ $this->addOption( 'ORDER BY', 'pp_page' );
+ }
$res = $this->select( __METHOD__ );
$currentPage = 0; # Id of the page currently processed
@@ -122,14 +125,16 @@ class ApiQueryPageProps extends ApiQueryBase {
public function getAllowedParams() {
return array(
'continue' => null,
- 'prop' => null,
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ),
);
}
public function getParamDescription() {
return array(
'continue' => 'When more results are available, use this to continue',
- 'prop' => 'Page prop to look on the page for. Useful for checking whether a certain page uses a certain page prop.'
+ 'prop' => 'Only list these props. Useful for checking whether a certain page uses a certain page prop',
);
}
@@ -146,8 +151,4 @@ class ApiQueryPageProps extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#pageprops_.2F_pp';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryPagesWithProp.php b/includes/api/ApiQueryPagesWithProp.php
new file mode 100644
index 00000000..0132fc3e
--- /dev/null
+++ b/includes/api/ApiQueryPagesWithProp.php
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Created on December 31, 2012
+ *
+ * Copyright © 2012 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.21
+ * @author Brad Jorsch
+ */
+
+/**
+ * A query module to enumerate pages that use a particular prop
+ *
+ * @ingroup API
+ * @since 1.21
+ */
+class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'pwp' );
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
+ private function run( $resultPageSet = null ) {
+ $params = $this->extractRequestParams();
+
+ $prop = array_flip( $params['prop'] );
+ $fld_ids = isset( $prop['ids'] );
+ $fld_title = isset( $prop['title'] );
+ $fld_value = isset( $prop['value'] );
+
+ if ( $resultPageSet === null ) {
+ $this->addFields( array( 'page_id' ) );
+ $this->addFieldsIf( array( 'page_title', 'page_namespace' ), $fld_title );
+ $this->addFieldsIf( 'pp_value', $fld_value );
+ } else {
+ $this->addFields( $resultPageSet->getPageTableFields() );
+ }
+ $this->addTables( array( 'page_props', 'page' ) );
+ $this->addWhere( 'pp_page=page_id' );
+ $this->addWhereFld( 'pp_propname', $params['propname'] );
+
+ $dir = ( $params['dir'] == 'ascending' ) ? 'newer' : 'older';
+
+ if ( $params['continue'] ) {
+ $cont = explode( '|', $params['continue'] );
+ $this->dieContinueUsageIf( count( $cont ) != 1 );
+
+ // Add a WHERE clause
+ $from = (int)$cont[0];
+ $this->addWhereRange( 'pp_page', $dir, $from, null );
+ }
+
+ $sort = ( $params['dir'] === 'descending' ? ' DESC' : '' );
+ $this->addOption( 'ORDER BY', 'pp_page' . $sort );
+
+ $limit = $params['limit'];
+ $this->addOption( 'LIMIT', $limit + 1 );
+
+ $result = $this->getResult();
+ $count = 0;
+ foreach ( $this->select( __METHOD__ ) 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->page_id );
+ break;
+ }
+
+ if ( $resultPageSet === null ) {
+ $vals = array();
+ if ( $fld_ids ) {
+ $vals['pageid'] = (int)$row->page_id;
+ }
+ if ( $fld_title ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ ApiQueryBase::addTitleInfo( $vals, $title );
+ }
+ if ( $fld_value ) {
+ $vals['value'] = $row->pp_value;
+ }
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $row->page_id );
+ break;
+ }
+ } else {
+ $resultPageSet->processDbRow( $row );
+ }
+ }
+
+ if ( $resultPageSet === null ) {
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ }
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'propname' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => 'ids|title',
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array (
+ 'ids',
+ 'title',
+ 'value',
+ )
+ ),
+ 'continue' => null,
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_DFLT => 10,
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ),
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending',
+ )
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'propname' => 'Page prop for which to enumerate pages',
+ 'prop' => array(
+ 'What pieces of information to include',
+ ' ids - Adds the page ID',
+ ' title - Adds the title and namespace ID of the page',
+ ' value - Adds the value of the page prop',
+ ),
+ 'dir' => 'In which direction to sort',
+ 'continue' => 'When more results are available, use this to continue',
+ 'limit' => 'The maximum number of pages to return',
+ );
+ }
+
+ public function getDescription() {
+ return 'List all pages using a given page prop';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&list=pageswithprop&pwppropname=displaytitle&pwpprop=ids|title|value' => 'Get first 10 pages using {{DISPLAYTITLE:}}',
+ 'api.php?action=query&generator=pageswithprop&gpwppropname=notoc&prop=info' => 'Get page info about first 10 pages using __NOTOC__',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Pageswithprop';
+ }
+}
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index 14aed28d..4aa00007 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -98,7 +98,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$vals['user'] = $row->user_name;
}
- if ( isset( $prop['user'] ) ) {
+ if ( isset( $prop['userid'] ) || /*B/C*/isset( $prop['user'] ) ) {
$vals['userid'] = $row->pt_user;
}
@@ -231,6 +231,9 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
),
'userid' => 'integer'
),
+ 'userid' => array(
+ 'userid' => 'integer'
+ ),
'comment' => array(
'comment' => 'string'
),
@@ -261,8 +264,4 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Protectedtitles';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php
index a8be26d3..b03bdfb8 100644
--- a/includes/api/ApiQueryQueryPage.php
+++ b/includes/api/ApiQueryQueryPage.php
@@ -75,6 +75,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
$params = $this->extractRequestParams();
$result = $this->getResult();
+ /** @var $qp QueryPage */
$qp = new $this->qpMap[$params['page']]();
if ( !$qp->userCanExecute( $this->getUser() ) ) {
$this->dieUsageMsg( 'specialpage-cantexecute' );
@@ -141,6 +142,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
}
public function getCacheMode( $params ) {
+ /** @var $qp QueryPage */
$qp = new $this->qpMap[$params['page']]();
if ( $qp->getRestriction() != '' ) {
return 'private';
@@ -211,7 +213,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'specialpage-cantexecute' )
+ array( 'specialpage-cantexecute' )
) );
}
@@ -220,8 +222,4 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
'api.php?action=query&list=querypage&qppage=Ancientpages'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index ddf5841b..ae3bb893 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -33,6 +33,8 @@
class ApiQueryRandom extends ApiQueryGeneratorBase {
+ private $pageIDs;
+
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rn' );
}
@@ -183,8 +185,4 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
public function getExamples() {
return 'api.php?action=query&list=random&rnnamespace=0&rnlimit=2';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRandom.php overlordq$';
- }
}
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 7ae4f371..8aceab22 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -105,7 +105,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/**
* Sets internal state to include the desired properties in the output.
- * @param $prop Array associative array of properties, only keys are used here
+ * @param array $prop associative array of properties, only keys are used here
*/
public function initProperties( $prop ) {
$this->fld_comment = isset( $prop['comment'] );
@@ -149,6 +149,31 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->addTables( 'recentchanges' );
$index = array( 'recentchanges' => 'rc_timestamp' ); // May change
$this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
+
+ if ( !is_null( $params['continue'] ) ) {
+ $cont = explode( '|', $params['continue'] );
+ if ( count( $cont ) != 2 ) {
+ $this->dieUsage( 'Invalid continue param. You should pass the ' .
+ 'original value returned by the previous query', '_badcontinue' );
+ }
+
+ $timestamp = $this->getDB()->addQuotes( wfTimestamp( TS_MW, $cont[0] ) );
+ $id = intval( $cont[1] );
+ $op = $params['dir'] === 'older' ? '<' : '>';
+
+ $this->addWhere(
+ "rc_timestamp $op $timestamp OR " .
+ "(rc_timestamp = $timestamp AND " .
+ "rc_id $op= $id)"
+ );
+ }
+
+ $order = $params['dir'] === 'older' ? 'DESC' : 'ASC';
+ $this->addOption( 'ORDER BY', array(
+ "rc_timestamp $order",
+ "rc_id $order",
+ ) );
+
$this->addWhereFld( 'rc_namespace', $params['namespace'] );
$this->addWhereFld( 'rc_deleted', 0 );
@@ -214,8 +239,6 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
'rc_title',
'rc_cur_id',
'rc_type',
- 'rc_moved_to_ns',
- 'rc_moved_to_title',
'rc_deleted'
) );
@@ -231,12 +254,13 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
+ $this->addFields( 'rc_id' );
/* Add fields to our query if they are specified as a needed parameter. */
- $this->addFieldsIf( array( 'rc_id', 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
+ $this->addFieldsIf( array( 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
$this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'rc_user', $this->fld_user );
$this->addFieldsIf( 'rc_user_text', $this->fld_user || $this->fld_userid );
- $this->addFieldsIf( array( 'rc_minor', 'rc_type', 'rc_bot' ) , $this->fld_flags );
+ $this->addFieldsIf( array( 'rc_minor', 'rc_type', 'rc_bot' ), $this->fld_flags );
$this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
$this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo );
@@ -262,7 +286,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
- $this->addWhereFld( 'ct_tag' , $params['tag'] );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
global $wgOldChangeTagsIndex;
$index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
@@ -283,7 +307,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
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( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
break;
}
@@ -297,7 +321,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
+ $this->setContinueEnumParameter( 'continue', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) . '|' . $row->rc_id );
break;
}
} else {
@@ -316,17 +340,11 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/**
* Extracts from a single sql row the data needed to describe one recent change.
*
- * @param $row The row from which to extract the data.
+ * @param mixed $row The row from which to extract the data.
* @return array An array mapping strings (descriptors) to their respective string values.
* @access public
*/
public function extractRowInfo( $row ) {
- /* If page was moved somewhere, get the title of the move target. */
- $movedToTitle = false;
- if ( isset( $row->rc_moved_to_title ) && $row->rc_moved_to_title !== '' ) {
- $movedToTitle = Title::makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title );
- }
-
/* Determine the title of the page that has been changed. */
$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
@@ -349,6 +367,9 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
case RC_LOG:
$vals['type'] = 'log';
break;
+ case RC_EXTERNAL:
+ $vals['type'] = 'external';
+ break;
case RC_MOVE_OVER_REDIRECT:
$vals['type'] = 'move over redirect';
break;
@@ -359,9 +380,6 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Create a new entry in the result for the title. */
if ( $this->fld_title ) {
ApiQueryBase::addTitleInfo( $vals, $title );
- if ( $movedToTitle ) {
- ApiQueryBase::addTitleInfo( $vals, $movedToTitle, 'new_' );
- }
}
/* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */
@@ -488,6 +506,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
return RC_NEW;
case 'log':
return RC_LOG;
+ case 'external':
+ return RC_EXTERNAL;
}
}
@@ -584,11 +604,13 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
'edit',
+ 'external',
'new',
'log'
)
),
'toponly' => false,
+ 'continue' => null,
);
}
@@ -626,6 +648,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
'limit' => 'How many total changes to return',
'tag' => 'Only list changes tagged with this tag',
'toponly' => 'Only list changes which are the latest revision',
+ 'continue' => 'When more results are available, use this to continue',
);
}
@@ -741,8 +764,4 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Recentchanges';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index b89a8ea9..192fe873 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -34,15 +34,15 @@
class ApiQueryRevisions extends ApiQueryBase {
private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
- $token, $parseContent;
+ $token, $parseContent, $contentFormat;
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rv' );
}
- private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
+ private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, $fld_sha1 = false,
$fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
- $fld_content = false, $fld_tags = false;
+ $fld_content = false, $fld_tags = false, $fld_contentmodel = false;
private $tokenFunctions;
@@ -95,7 +95,6 @@ class ApiQueryRevisions extends ApiQueryBase {
!is_null( $params['endid'] ) || $params['dir'] === 'newer' ||
!is_null( $params['start'] ) || !is_null( $params['end'] ) );
-
$pageSet = $this->getPageSet();
$pageCount = $pageSet->getGoodTitleCount();
$revCount = $pageSet->getRevisionCount();
@@ -155,15 +154,20 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
$this->fld_size = isset ( $prop['size'] );
$this->fld_sha1 = isset ( $prop['sha1'] );
+ $this->fld_contentmodel = isset ( $prop['contentmodel'] );
$this->fld_userid = isset( $prop['userid'] );
$this->fld_user = isset ( $prop['user'] );
$this->token = $params['token'];
+ if ( !empty( $params['contentformat'] ) ) {
+ $this->contentFormat = $params['contentformat'];
+ }
+
// Possible indexes used
$index = array();
$userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
- $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
+ $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
$limit = $params['limit'];
if ( $limit == 'max' ) {
$limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
@@ -184,15 +188,17 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) );
- $this->addWhereFld( 'ct_tag' , $params['tag'] );
+ $this->addWhereFld( 'ct_tag', $params['tag'] );
global $wgOldChangeTagsIndex;
$index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
// For each page we will request, the user must have read rights for that page
+ $user = $this->getUser();
+ /** @var $title Title */
foreach ( $pageSet->getGoodTitles() as $title ) {
- if ( !$title->userCan( 'read' ) ) {
+ if ( !$title->userCan( 'read', $user ) ) {
$this->dieUsage(
'The current user is not allowed to read ' . $title->getPrefixedText(),
'accessdenied' );
@@ -255,7 +261,7 @@ class ApiQueryRevisions extends ApiQueryBase {
// rvstart and rvstartid when that is supplied.
if ( !is_null( $params['continue'] ) ) {
$params['startid'] = $params['continue'];
- unset( $params['start'] );
+ $params['start'] = null;
}
// This code makes an assumption that sorting by rev_id and rev_timestamp produces
@@ -332,10 +338,7 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !is_null( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$pageid = intval( $cont[0] );
$revid = intval( $cont[1] );
$this->addWhere(
@@ -433,12 +436,18 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
- if ( $this->fld_sha1 ) {
+ if ( $this->fld_sha1 && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
if ( $revision->getSha1() != '' ) {
$vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
} else {
$vals['sha1'] = '';
}
+ } elseif ( $this->fld_sha1 ) {
+ $vals['sha1hidden'] = '';
+ }
+
+ if ( $this->fld_contentmodel ) {
+ $vals['contentmodel'] = $revision->getContentModel();
}
if ( $this->fld_comment || $this->fld_parsedcomment ) {
@@ -479,55 +488,121 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
- $text = null;
+ $content = null;
global $wgParser;
- if ( $this->fld_content || !is_null( $this->difftotext ) ) {
- $text = $revision->getText();
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ $content = $revision->getContent();
// Expand templates after getting section content because
// template-added sections don't count and Parser::preprocess()
// will have less input
- if ( $this->section !== false ) {
- $text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false ) {
+ if ( $content && $this->section !== false ) {
+ $content = $content->getSection( $this->section, false );
+ if ( !$content ) {
$this->dieUsage( "There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection' );
}
}
}
- if ( $this->fld_content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $this->fld_content && $content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $text = null;
+
if ( $this->generateXML ) {
- $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $text );
- if ( is_callable( array( $dom, 'saveXML' ) ) ) {
- $xml = $dom->saveXML();
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $t = $content->getNativeData(); # note: don't set $text
+
+ $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
+ $dom = $wgParser->preprocessToDom( $t );
+ if ( is_callable( array( $dom, 'saveXML' ) ) ) {
+ $xml = $dom->saveXML();
+ } else {
+ $xml = $dom->__toString();
+ }
+ $vals['parsetree'] = $xml;
} else {
- $xml = $dom->__toString();
+ $this->setWarning( "Conversion to XML is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() . ")" );
}
- $vals['parsetree'] = $xml;
-
}
+
if ( $this->expandTemplates && !$this->parseContent ) {
- $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ #XXX: implement template expansion for all content types in ContentHandler?
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $text = $content->getNativeData();
+
+ $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ } else {
+ $this->setWarning( "Template expansion is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() . ")" );
+
+ $text = false;
+ }
}
if ( $this->parseContent ) {
- $text = $wgParser->parse( $text, $title, ParserOptions::newFromContext( $this->getContext() ) )->getText();
+ $po = $content->getParserOutput( $title, $revision->getId(), ParserOptions::newFromContext( $this->getContext() ) );
+ $text = $po->getText();
+ }
+
+ if ( $text === null ) {
+ $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
+ $model = $content->getModel();
+
+ if ( !$content->isSupportedFormat( $format ) ) {
+ $name = $title->getPrefixedDBkey();
+
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported ".
+ "for content model $model used by $name", 'badformat' );
+ }
+
+ $text = $content->serialize( $format );
+
+ // always include format and model.
+ // Format is needed to deserialize, model is needed to interpret.
+ $vals['contentformat'] = $format;
+ $vals['contentmodel'] = $model;
+ }
+
+ if ( $text !== false ) {
+ ApiResult::setContent( $vals, $text );
}
- ApiResult::setContent( $vals, $text );
} elseif ( $this->fld_content ) {
- $vals['texthidden'] = '';
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['texthidden'] = '';
+ } else {
+ $vals['textmissing'] = '';
+ }
}
if ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
global $wgAPIMaxUncachedDiffs;
static $n = 0; // Number of uncached diffs we've had
- if ( $n < $wgAPIMaxUncachedDiffs ) {
+
+ if ( is_null( $content ) ) {
+ $vals['textmissing'] = '';
+ } elseif ( $n < $wgAPIMaxUncachedDiffs ) {
$vals['diff'] = array();
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $title );
+ $handler = $revision->getContentHandler();
+
if ( !is_null( $this->difftotext ) ) {
- $engine = new DifferenceEngine( $context );
- $engine->setText( $text, $this->difftotext );
+ $model = $title->getContentModel();
+
+ if ( $this->contentFormat
+ && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat ) ) {
+
+ $name = $title->getPrefixedDBkey();
+
+ $this->dieUsage( "The requested format {$this->contentFormat} is not supported for ".
+ "content model $model used by $name", 'badformat' );
+ }
+
+ $difftocontent = ContentHandler::makeContent( $this->difftotext, $title, $model, $this->contentFormat );
+
+ $engine = $handler->createDifferenceEngine( $context );
+ $engine->setContent( $content, $difftocontent );
} else {
- $engine = new DifferenceEngine( $context, $revision->getID(), $this->diffto );
+ $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
$vals['diff']['from'] = $engine->getOldid();
$vals['diff']['to'] = $engine->getNewid();
}
@@ -567,6 +642,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'userid',
'size',
'sha1',
+ 'contentmodel',
'comment',
'parsedcomment',
'content',
@@ -616,6 +692,10 @@ class ApiQueryRevisions extends ApiQueryBase {
'continue' => null,
'diffto' => null,
'difftotext' => null,
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_DFLT => null
+ ),
);
}
@@ -631,6 +711,7 @@ class ApiQueryRevisions extends ApiQueryBase {
' userid - User id of revision creator',
' size - Length (bytes) of the revision',
' sha1 - SHA-1 (base 16) of the revision',
+ ' contentmodel - Content model id',
' comment - Comment by the user for revision',
' parsedcomment - Parsed comment by the user for the revision',
' content - Text of the revision',
@@ -644,9 +725,10 @@ class ApiQueryRevisions extends ApiQueryBase {
'dir' => $this->getDirectionDescription( $p, ' (enum)' ),
'user' => 'Only include revisions made by user (enum)',
'excludeuser' => 'Exclude revisions made by user (enum)',
- 'expandtemplates' => 'Expand templates in revision content',
- 'generatexml' => 'Generate XML parse tree for revision content',
- 'parse' => 'Parse revision content. For performance reasons if this option is used, rvlimit is enforced to 1.',
+ 'expandtemplates' => "Expand templates in revision content (requires {$p}prop=content)",
+ 'generatexml' => "Generate XML parse tree for revision content (requires {$p}prop=content)",
+ 'parse' => array( "Parse revision content (requires {$p}prop=content).",
+ 'For performance reasons if this option is used, rvlimit is enforced to 1.' ),
'section' => 'Only retrieve the content of this section number',
'token' => 'Which tokens to obtain for each revision',
'continue' => 'When more results are available, use this to continue',
@@ -655,6 +737,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'difftotext' => array( 'Text to diff each revision to. Only diffs a limited number of revisions.',
"Overrides {$p}diffto. If {$p}section is set, only that section will be diffed against this text" ),
'tag' => 'Only list revisions tagged with this tag',
+ 'contentformat' => 'Serialization format used for difftotext and expected for output of content',
);
}
@@ -709,8 +792,12 @@ class ApiQueryRevisions extends ApiQueryBase {
ApiBase::PROP_TYPE => 'string',
ApiBase::PROP_NULLABLE => true
),
- 'texthidden' => 'boolean'
- )
+ 'texthidden' => 'boolean',
+ 'textmissing' => 'boolean',
+ ),
+ 'contentmodel' => array(
+ 'contentmodel' => 'string'
+ ),
);
self::addTokenProperties( $props, $this->getTokenFunctions() );
@@ -732,13 +819,18 @@ class ApiQueryRevisions extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'nosuchrevid', 'diffto' ),
- array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).' ),
- array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.' ),
+ array( 'code' => 'revids', 'info' => 'The revids= parameter may not be used with the list options '
+ . '(limit, startid, endid, dirNewer, start, end).' ),
+ array( 'code' => 'multpages', 'info' => 'titles, pageids or a generator was used to supply multiple pages, '
+ . ' but the limit, startid, endid, dirNewer, user, excludeuser, '
+ . 'start and end parameters may only be used on a single page.' ),
array( 'code' => 'diffto', 'info' => 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"' ),
array( 'code' => 'badparams', 'info' => 'start and startid cannot be used together' ),
array( 'code' => 'badparams', 'info' => 'end and endid cannot be used together' ),
array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
array( 'code' => 'nosuchsection', 'info' => 'There is no section section in rID' ),
+ array( 'code' => 'badformat', 'info' => 'The requested serialization format can not be applied '
+ . ' to the page\'s content model' ),
) );
}
@@ -762,8 +854,4 @@ class ApiQueryRevisions extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#revisions_.2F_rv';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index 364433d5..86183391 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -168,7 +168,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
}
if ( isset( $prop['hasrelated'] ) && $result->hasRelated() ) {
- $vals['hasrelated'] = "";
+ $vals['hasrelated'] = '';
}
// Add item to results and see whether it fits
@@ -205,7 +205,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
ApiBase::PARAM_REQUIRED => true
),
'namespace' => array(
- ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_DFLT => NS_MAIN,
ApiBase::PARAM_TYPE => 'namespace',
ApiBase::PARAM_ISMULTI => true,
),
@@ -359,8 +359,4 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Search';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index ec503d64..810e1d6b 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -96,6 +96,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'variables':
$fit = $this->appendVariables( $p );
break;
+ case 'protocols':
+ $fit = $this->appendProtocols( $p );
+ break;
default:
ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
}
@@ -111,7 +114,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
protected function appendGeneralInfo( $property ) {
- global $wgContLang;
+ global $wgContLang,
+ $wgDisableLangConversion,
+ $wgDisableTitleConversion;
$data = array();
$mainPage = Title::newMainPage();
@@ -120,10 +125,31 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['sitename'] = $GLOBALS['wgSitename'];
$data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
$data['phpversion'] = phpversion();
- $data['phpsapi'] = php_sapi_name();
+ $data['phpsapi'] = PHP_SAPI;
$data['dbtype'] = $GLOBALS['wgDBtype'];
$data['dbversion'] = $this->getDB()->getServerVersion();
+ if ( !$wgDisableLangConversion ) {
+ $data['langconversion'] = '';
+ }
+
+ if ( !$wgDisableTitleConversion ) {
+ $data['titleconversion'] = '';
+ }
+
+ if ( $wgContLang->linkPrefixExtension() ) {
+ $data['linkprefix'] = wfMessage( 'linkprefix' )->inContentLanguage()->text();
+ } else {
+ $data['linkprefix'] = '';
+ }
+
+ $linktrail = $wgContLang->linkTrail();
+ if ( $linktrail ) {
+ $data['linktrail'] = $linktrail;
+ } else {
+ $data['linktrail'] = '';
+ }
+
$git = SpecialVersion::getGitHeadSha1( $GLOBALS['IP'] );
if ( $git ) {
$data['git-hash'] = $git;
@@ -227,6 +253,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
if ( MWNamespace::isNonincludable( $ns ) ) {
$data[$ns]['nonincludable'] = '';
}
+
+ $contentmodel = MWNamespace::getNamespaceContentModel( $ns );
+ if ( $contentmodel ) {
+ $data[$ns]['defaultcontentmodel'] = $contentmodel;
+ }
}
$this->getResult()->setIndexedTagName( $data, 'ns' );
@@ -345,7 +376,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
);
}
} else {
- list( $host, $lag, $index ) = $lb->getMaxLag();
+ list( , $lag, $index ) = $lb->getMaxLag();
$data[] = array(
'host' => $wgShowHostnames
? $lb->getServerName( $index )
@@ -457,7 +488,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
if ( isset( $ext['author'] ) ) {
$ret['author'] = is_array( $ext['author'] ) ?
- implode( ', ', $ext['author' ] ) : $ext['author'];
+ implode( ', ', $ext['author'] ) : $ext['author'];
}
if ( isset( $ext['url'] ) ) {
$ret['url'] = $ext['url'];
@@ -525,7 +556,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function appendExtensionTags( $property ) {
global $wgParser;
$wgParser->firstCallInit();
- $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() );
+ $tags = array_map( array( $this, 'formatParserTags' ), $wgParser->getTags() );
$this->getResult()->setIndexedTagName( $tags, 't' );
return $this->getResult()->addValue( 'query', $property, $tags );
}
@@ -544,6 +575,14 @@ class ApiQuerySiteinfo extends ApiQueryBase {
return $this->getResult()->addValue( 'query', $property, $variables );
}
+ public function appendProtocols( $property ) {
+ global $wgUrlProtocols;
+ // Make a copy of the global so we don't try to set the _element key of it - bug 45130
+ $protocols = array_values( $wgUrlProtocols );
+ $this->getResult()->setIndexedTagName( $protocols, 'p' );
+ return $this->getResult()->addValue( 'query', $property, $protocols );
+ }
+
private function formatParserTags( $item ) {
return "<{$item}>";
}
@@ -554,7 +593,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
ksort( $myWgHooks );
$data = array();
- foreach ( $myWgHooks as $hook => $hooks ) {
+ foreach ( $myWgHooks as $hook => $hooks ) {
$arr = array(
'name' => $hook,
'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
@@ -596,6 +635,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'functionhooks',
'showhooks',
'variables',
+ 'protocols',
)
),
'filteriw' => array(
@@ -633,6 +673,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
' functionhooks - Returns a list of parser function hooks',
' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)',
' variables - Returns a list of variable IDs',
+ ' protocols - Returns a list of protocols that are allowed in external links.',
),
'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
@@ -662,8 +703,4 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php
index a310d109..6899375a 100644
--- a/includes/api/ApiQueryStashImageInfo.php
+++ b/includes/api/ApiQueryStashImageInfo.php
@@ -42,7 +42,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
$result = $this->getResult();
if ( !$params['filekey'] && !$params['sessionkey'] ) {
- $this->dieUsage( "One of filekey or sessionkey must be supplied", 'nofilekey');
+ $this->dieUsage( "One of filekey or sessionkey must be supplied", 'nofilekey' );
}
// Alias sessionkey to filekey, but give an existing filekey precedence.
@@ -138,9 +138,4 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
);
}
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
-
}
-
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index f97c1b2a..e0637ff7 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -162,7 +162,7 @@ class ApiQueryTags extends ApiQueryBase {
'prop' => array(
'Which properties to get',
' name - Adds name of tag',
- ' displayname - Adds system messsage for the tag',
+ ' displayname - Adds system message for the tag',
' description - Adds description of the tag',
' hitcount - Adds the amount of revisions that have this tag',
),
@@ -195,8 +195,4 @@ class ApiQueryTags extends ApiQueryBase {
'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index f30b1325..597c412d 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -160,10 +160,7 @@ class ApiQueryContributions extends ApiQueryBase {
// Handle continue parameter
if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
$continue = explode( '|', $this->params['continue'] );
- if ( count( $continue ) != 2 ) {
- $this->dieUsage( 'Invalid continue param. You should pass the original ' .
- 'value returned by the previous query', '_badcontinue' );
- }
+ $this->dieContinueUsageIf( count( $continue ) != 2 );
$db = $this->getDB();
$encUser = $db->addQuotes( $continue[0] );
$encTS = $db->addQuotes( $db->timestamp( $continue[1] ) );
@@ -223,7 +220,7 @@ class ApiQueryContributions extends ApiQueryBase {
) );
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
- $this->fld_patrolled ) {
+ $this->fld_patrolled ) {
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
@@ -445,7 +442,7 @@ class ApiQueryContributions extends ApiQueryBase {
'end' => 'The end timestamp to return to',
'continue' => 'When more results are available, use this to continue',
'user' => 'The users to retrieve contributions for',
- 'userprefix' => "Retrieve contibutions for all users whose names begin with this value. Overrides {$p}user",
+ 'userprefix' => "Retrieve contributions for all users whose names begin with this value. Overrides {$p}user",
'dir' => $this->getDirectionDescription( $p ),
'namespace' => 'Only list contributions in these namespaces',
'prop' => array(
@@ -546,8 +543,4 @@ class ApiQueryContributions extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Usercontribs';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index 66906659..1a491eca 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -77,18 +77,18 @@ class ApiQueryUserInfo extends ApiQueryBase {
if ( isset( $this->prop['groups'] ) ) {
$vals['groups'] = $user->getEffectiveGroups();
- $result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
+ $result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
}
if ( isset( $this->prop['implicitgroups'] ) ) {
$vals['implicitgroups'] = $user->getAutomaticGroups();
- $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
+ $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
}
if ( isset( $this->prop['rights'] ) ) {
// User::getRights() may return duplicate values, strip them
$vals['rights'] = array_values( array_unique( $user->getRights() ) );
- $result->setIndexedTagName( $vals['rights'], 'r' ); // even if empty
+ $result->setIndexedTagName( $vals['rights'], 'r' ); // even if empty
}
if ( isset( $this->prop['changeablegroups'] ) ) {
@@ -303,8 +303,4 @@ class ApiQueryUserInfo extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Meta#userinfo_.2F_ui';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index bf438d1d..72ab7866 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -110,19 +110,39 @@ class ApiQueryUsers extends ApiQueryBase {
$this->addFields( User::selectFields() );
$this->addWhereFld( 'user_name', $goodNames );
- if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
- $this->addTables( 'user_groups' );
- $this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=user_id' ) ) );
- $this->addFields( 'ug_group' );
- }
-
$this->showHiddenUsersAddBlockInfo( isset( $this->prop['blockinfo'] ) );
$data = array();
$res = $this->select( __METHOD__ );
+ $this->resetQueryParams();
+
+ // get user groups if needed
+ if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
+ $userGroups = array();
+
+ $this->addTables( 'user' );
+ $this->addWhereFld( 'user_name', $goodNames );
+ $this->addTables( 'user_groups' );
+ $this->addJoinConds( array( 'user_groups' => array( 'INNER JOIN', 'ug_user=user_id' ) ) );
+ $this->addFields( array( 'user_name', 'ug_group' ) );
+ $userGroupsRes = $this->select( __METHOD__ );
+
+ foreach( $userGroupsRes as $row ) {
+ $userGroups[$row->user_name][] = $row->ug_group;
+ }
+ }
foreach ( $res as $row ) {
- $user = User::newFromRow( $row );
+ // create user object and pass along $userGroups if set
+ // that reduces the number of database queries needed in User dramatically
+ if ( !isset( $userGroups ) ) {
+ $user = User::newFromRow( $row );
+ } else {
+ if ( !isset( $userGroups[$row->user_name] ) || !is_array( $userGroups[$row->user_name] ) ) {
+ $userGroups[$row->user_name] = array();
+ }
+ $user = User::newFromRow( $row, array( 'user_groups' => $userGroups[$row->user_name] ) );
+ }
$name = $user->getName();
$data[$name]['userid'] = $user->getId();
@@ -137,29 +157,15 @@ class ApiQueryUsers extends ApiQueryBase {
}
if ( isset( $this->prop['groups'] ) ) {
- if ( !isset( $data[$name]['groups'] ) ) {
- $data[$name]['groups'] = $user->getAutomaticGroups();
- }
-
- if ( !is_null( $row->ug_group ) ) {
- // This row contains only one group, others will be added from other rows
- $data[$name]['groups'][] = $row->ug_group;
- }
+ $data[$name]['groups'] = $user->getEffectiveGroups();
}
- if ( isset( $this->prop['implicitgroups'] ) && !isset( $data[$name]['implicitgroups'] ) ) {
- $data[$name]['implicitgroups'] = $user->getAutomaticGroups();
+ if ( isset( $this->prop['implicitgroups'] ) ) {
+ $data[$name]['implicitgroups'] = $user->getAutomaticGroups();
}
if ( isset( $this->prop['rights'] ) ) {
- if ( !isset( $data[$name]['rights'] ) ) {
- $data[$name]['rights'] = User::getGroupPermissions( $user->getAutomaticGroups() );
- }
-
- if ( !is_null( $row->ug_group ) ) {
- $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'],
- User::getGroupPermissions( array( $row->ug_group ) ) ) );
- }
+ $data[$name]['rights'] = $user->getRights();
}
if ( $row->ipb_deleted ) {
$data[$name]['hidden'] = '';
@@ -244,16 +250,16 @@ class ApiQueryUsers extends ApiQueryBase {
}
$done[] = $u;
}
- return $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
}
/**
- * Gets all the groups that a user is automatically a member of (implicit groups)
- *
- * @deprecated since 1.20; call User::getAutomaticGroups() directly.
- * @param $user User
- * @return array
- */
+ * Gets all the groups that a user is automatically a member of (implicit groups)
+ *
+ * @deprecated since 1.20; call User::getAutomaticGroups() directly.
+ * @param $user User
+ * @return array
+ */
public static function getAutoGroups( $user ) {
wfDeprecated( __METHOD__, '1.20' );
@@ -304,7 +310,7 @@ class ApiQueryUsers extends ApiQueryBase {
' rights - Lists all the rights the user(s) has',
' editcount - Adds the user\'s edit count',
' registration - Adds the user\'s registration timestamp',
- ' emailable - Tags if the user can and wants to receive e-mail through [[Special:Emailuser]]',
+ ' emailable - Tags if the user can and wants to receive email through [[Special:Emailuser]]',
' gender - Tags the gender of the user. Returns "male", "female", or "unknown"',
),
'users' => 'A list of users to obtain the same information for',
@@ -390,8 +396,4 @@ class ApiQueryUsers extends ApiQueryBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Users';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index a1a33728..90b12c14 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -116,7 +116,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
) );
$userId = $user->getId();
- $this->addJoinConds( array( 'watchlist' => array('INNER JOIN',
+ $this->addJoinConds( array( 'watchlist' => array( 'INNER JOIN',
array(
'wl_user' => $userId,
'wl_namespace=rc_namespace',
@@ -240,14 +240,16 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ( $this->fld_user || $this->fld_userid ) {
- if ( $this->fld_user ) {
- $vals['user'] = $row->rc_user_text;
- }
-
if ( $this->fld_userid ) {
+ $vals['userid'] = $row->rc_user;
+ // for backwards compatibility
$vals['user'] = $row->rc_user;
}
+ if ( $this->fld_user ) {
+ $vals['user'] = $row->rc_user_text;
+ }
+
if ( !$row->rc_user ) {
$vals['anon'] = '';
}
@@ -511,15 +513,11 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'api.php?action=query&list=watchlist&wlallrev=&wlprop=ids|title|timestamp|user|comment',
'api.php?action=query&generator=watchlist&prop=info',
'api.php?action=query&generator=watchlist&gwlallrev=&prop=revisions&rvprop=timestamp|user',
- 'api.php?action=query&list=watchlist&wlowner=Bob_Smith&wltoken=d8d562e9725ea1512894cdab28e5ceebc7f20237'
+ 'api.php?action=query&list=watchlist&wlowner=Bob_Smith&wltoken=123ABC'
);
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Watchlist';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index 6b24aef3..2cb4d9eb 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -71,11 +71,9 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
if ( isset( $params['continue'] ) ) {
$cont = explode( '|', $params['continue'] );
- if ( count( $cont ) != 2 ) {
- $this->dieUsage( "Invalid continue param. You should pass the " .
- "original value returned by the previous query", "_badcontinue" );
- }
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
$ns = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
$title = $this->getDB()->addQuotes( $cont[1] );
$op = $params['dir'] == 'ascending' ? '>' : '<';
$this->addWhere(
@@ -224,8 +222,4 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions',
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 91e20812..39c114b8 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -36,13 +36,26 @@
* There are two special key values that change how XML output is generated:
* '_element' This key sets the tag name for the rest of the elements in the current array.
* It is only inserted if the formatter returned true for getNeedsRawData()
- * '*' This key has special meaning only to the XML formatter, and is outputed as is
- * for all others. In XML it becomes the content of the current element.
+ * '*' This key has special meaning only to the XML formatter, and is outputted as is
+ * for all others. In XML it becomes the content of the current element.
*
* @ingroup API
*/
class ApiResult extends ApiBase {
+ /**
+ * override existing value in addValue() and setElement()
+ * @since 1.21
+ */
+ const OVERRIDE = 1;
+
+ /**
+ * For addValue() and setElement(), if the value does not exist, add it as the first element.
+ * In case the new value has no name (numerical index), all indexes will be renumbered.
+ * @since 1.21
+ */
+ const ADD_ON_TOP = 2;
+
private $mData, $mIsRawMode, $mSize, $mCheckingSize;
/**
@@ -134,18 +147,27 @@ class ApiResult extends ApiBase {
/**
* Add an output value to the array by name.
* Verifies that value with the same name has not been added before.
- * @param $arr array to add $value to
- * @param $name string Index of $arr to add $value at
+ * @param array $arr to add $value to
+ * @param string $name Index of $arr to add $value at
* @param $value mixed
- * @param $overwrite bool Whether overwriting an existing element is allowed
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. This parameter used to be
+ * boolean, and the value of OVERRIDE=1 was specifically chosen so that it would be backwards
+ * compatible with the new method signature.
+ *
+ * @since 1.21 int $flags replaced boolean $override
*/
- public static function setElement( &$arr, $name, $value, $overwrite = false ) {
+ public static function setElement( &$arr, $name, $value, $flags = 0 ) {
if ( $arr === null || $name === null || $value === null || !is_array( $arr ) || is_array( $name ) ) {
ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
}
- if ( !isset ( $arr[$name] ) || $overwrite ) {
- $arr[$name] = $value;
+ $exists = isset( $arr[$name] );
+ if ( !$exists || ( $flags & ApiResult::OVERRIDE ) ) {
+ if ( !$exists && ( $flags & ApiResult::ADD_ON_TOP ) ) {
+ $arr = array( $name => $value ) + $arr;
+ } else {
+ $arr[$name] = $value;
+ }
} elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
$merged = array_intersect_key( $arr[$name], $value );
if ( !count( $merged ) ) {
@@ -161,9 +183,9 @@ class ApiResult extends ApiBase {
/**
* Adds a content element to an array.
* Use this function instead of hardcoding the '*' element.
- * @param $arr array to add the content element to
+ * @param array $arr to add the content element to
* @param $value Mixed
- * @param $subElemName string when present, content element is created
+ * @param string $subElemName when present, content element is created
* as a sub item of $arr. Use this parameter to create elements in
* format "<elem>text</elem>" without attributes.
*/
@@ -186,7 +208,7 @@ class ApiResult extends ApiBase {
* give all indexed values the given tag name. This function MUST be
* called on every array that has numerical indexes.
* @param $arr array
- * @param $tag string Tag name
+ * @param string $tag Tag name
*/
public function setIndexedTagName( &$arr, $tag ) {
// In raw mode, add the '_element', otherwise just ignore
@@ -203,7 +225,7 @@ class ApiResult extends ApiBase {
/**
* Calls setIndexedTagName() on each sub-array of $arr
* @param $arr array
- * @param $tag string Tag name
+ * @param string $tag Tag name
*/
public function setIndexedTagName_recursive( &$arr, $tag ) {
if ( !is_array( $arr ) ) {
@@ -222,7 +244,7 @@ class ApiResult extends ApiBase {
* Calls setIndexedTagName() on an array already in the result.
* Don't specify a path to a value that's not in the result, or
* you'll get nasty errors.
- * @param $path array Path to the array, like addValue()'s $path
+ * @param array $path Path to the array, like addValue()'s $path
* @param $tag string
*/
public function setIndexedTagName_internal( $path, $tag ) {
@@ -249,11 +271,14 @@ class ApiResult extends ApiBase {
* @param $path array|string|null
* @param $name string
* @param $value mixed
- * @param $overwrite bool
- *
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP. This parameter used to be
+ * boolean, and the value of OVERRIDE=1 was specifically chosen so that it would be backwards
+ * compatible with the new method signature.
* @return bool True if $value fits in the result, false if not
+ *
+ * @since 1.21 int $flags replaced boolean $override
*/
- public function addValue( $path, $name, $value, $overwrite = false ) {
+ public function addValue( $path, $name, $value, $flags = 0 ) {
global $wgAPIMaxResultSize;
$data = &$this->mData;
@@ -268,26 +293,34 @@ class ApiResult extends ApiBase {
$this->mSize = $newsize;
}
- if ( !is_null( $path ) ) {
- if ( is_array( $path ) ) {
- foreach ( $path as $p ) {
- if ( !isset( $data[$p] ) ) {
+ $addOnTop = $flags & ApiResult::ADD_ON_TOP;
+ if ( $path !== null ) {
+ foreach ( (array)$path as $p ) {
+ if ( !isset( $data[$p] ) ) {
+ if ( $addOnTop ) {
+ $data = array( $p => array() ) + $data;
+ $addOnTop = false;
+ } else {
$data[$p] = array();
}
- $data = &$data[$p];
- }
- } else {
- if ( !isset( $data[$path] ) ) {
- $data[$path] = array();
}
- $data = &$data[$path];
+ $data = &$data[$p];
}
}
if ( !$name ) {
- $data[] = $value; // Add list element
+ // Add list element
+ if ( $addOnTop ) {
+ // This element needs to be inserted in the beginning
+ // Numerical indexes will be renumbered
+ array_unshift( $data, $value );
+ } else {
+ // Add new value at the end
+ $data[] = $value;
+ }
} else {
- self::setElement( $data, $name, $value, $overwrite ); // Add named element
+ // Add named element
+ self::setElement( $data, $name, $value, $flags );
}
return true;
}
@@ -300,19 +333,19 @@ class ApiResult extends ApiBase {
*/
public function setParsedLimit( $moduleName, $limit ) {
// Add value, allowing overwriting
- $this->addValue( 'limits', $moduleName, $limit, true );
+ $this->addValue( 'limits', $moduleName, $limit, ApiResult::OVERRIDE );
}
/**
* Unset a value previously added to the result set.
* Fails silently if the value isn't found.
* For parameters, see addValue()
- * @param $path array
+ * @param $path array|null
* @param $name string
*/
public function unsetValue( $path, $name ) {
$data = &$this->mData;
- if ( !is_null( $path ) ) {
+ if ( $path !== null ) {
foreach ( (array)$path as $p ) {
if ( !isset( $data[$p] ) ) {
return;
@@ -367,8 +400,4 @@ class ApiResult extends ApiBase {
public function execute() {
ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index 677df16a..b9873f49 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -29,10 +29,6 @@
*/
class ApiRollback extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* @var Title
*/
@@ -185,7 +181,7 @@ class ApiRollback extends ApiBase {
$this->mTitleObj = Title::newFromText( $params['title'] );
- if ( !$this->mTitleObj ) {
+ if ( !$this->mTitleObj || $this->mTitleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
if ( !$this->mTitleObj->exists() ) {
@@ -205,8 +201,4 @@ class ApiRollback extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Rollback';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
index f0e1fad6..c4a1328c 100644
--- a/includes/api/ApiRsd.php
+++ b/includes/api/ApiRsd.php
@@ -31,10 +31,6 @@
*/
class ApiRsd extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$result = $this->getResult();
@@ -155,10 +151,6 @@ class ApiRsd extends ApiBase {
}
return $outputData;
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
class ApiFormatXmlRsd extends ApiFormatXml {
@@ -170,8 +162,4 @@ class ApiFormatXmlRsd extends ApiFormatXml {
public function getMimeType() {
return 'application/rsd+xml';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php
index 098b1a66..58d5d9ab 100644
--- a/includes/api/ApiSetNotificationTimestamp.php
+++ b/includes/api/ApiSetNotificationTimestamp.php
@@ -31,9 +31,7 @@
*/
class ApiSetNotificationTimestamp extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
+ private $mPageSet;
public function execute() {
$user = $this->getUser();
@@ -45,11 +43,12 @@ class ApiSetNotificationTimestamp extends ApiBase {
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );
- $pageSet = new ApiPageSet( $this );
- $args = array_merge( array( $params, 'entirewatchlist' ), array_keys( $pageSet->getAllowedParams() ) );
- call_user_func_array( array( $this, 'requireOnlyOneParameter' ), $args );
+ $pageSet = $this->getPageSet();
+ if ( $params['entirewatchlist'] && $pageSet->getDataSource() !== null ) {
+ $this->dieUsage( "Cannot use 'entirewatchlist' at the same time as '{$pageSet->getDataSource()}'", 'multisource' );
+ }
- $dbw = $this->getDB( DB_MASTER );
+ $dbw = wfGetDB( DB_MASTER, 'api' );
$timestamp = null;
if ( isset( $params['timestamp'] ) ) {
@@ -96,20 +95,20 @@ class ApiSetNotificationTimestamp extends ApiBase {
$result['notificationtimestamp'] = ( is_null( $timestamp ) ? '' : wfTimestamp( TS_ISO_8601, $timestamp ) );
} else {
// First, log the invalid titles
- foreach( $pageSet->getInvalidTitles() as $title ) {
+ foreach ( $pageSet->getInvalidTitles() as $title ) {
$r = array();
$r['title'] = $title;
$r['invalid'] = '';
$result[] = $r;
}
- foreach( $pageSet->getMissingPageIDs() as $p ) {
+ foreach ( $pageSet->getMissingPageIDs() as $p ) {
$page = array();
$page['pageid'] = $p;
$page['missing'] = '';
$page['notwatched'] = '';
$result[] = $page;
}
- foreach( $pageSet->getMissingRevisionIDs() as $r ) {
+ foreach ( $pageSet->getMissingRevisionIDs() as $r ) {
$rev = array();
$rev['revid'] = $r;
$rev['missing'] = '';
@@ -135,6 +134,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
}
// Now, put the valid titles into the result
+ /** @var $title Title */
foreach ( $pageSet->getTitles() as $title ) {
$ns = $title->getNamespace();
$dbkey = $title->getDBkey();
@@ -161,6 +161,17 @@ class ApiSetNotificationTimestamp extends ApiBase {
$apiResult->addValue( null, $this->getModuleName(), $result );
}
+ /**
+ * Get a cached instance of an ApiPageSet object
+ * @return ApiPageSet
+ */
+ private function getPageSet() {
+ if ( !isset( $this->mPageSet ) ) {
+ $this->mPageSet = new ApiPageSet( $this );
+ }
+ return $this->mPageSet;
+ }
+
public function mustBePosted() {
return true;
}
@@ -177,9 +188,8 @@ class ApiSetNotificationTimestamp extends ApiBase {
return '';
}
- public function getAllowedParams() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getAllowedParams() + array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = array(
'entirewatchlist' => array(
ApiBase::PARAM_TYPE => 'boolean'
),
@@ -194,11 +204,15 @@ class ApiSetNotificationTimestamp extends ApiBase {
ApiBase::PARAM_TYPE => 'integer'
),
);
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
+ }
+ return $result;
+
}
public function getParamDescription() {
- $psModule = new ApiPageSet( $this );
- return $psModule->getParamDescription() + array(
+ return $this->getPageSet()->getParamDescription() + array(
'entirewatchlist' => 'Work on all watched pages',
'timestamp' => 'Timestamp to which to set the notification timestamp',
'torevid' => 'Revision to set the notification timestamp to (one page only)',
@@ -247,18 +261,20 @@ class ApiSetNotificationTimestamp extends ApiBase {
public function getDescription() {
return array( 'Update the notification timestamp for watched pages.',
'This affects the highlighting of changed pages in the watchlist and history,',
- 'and the sending of email when the "E-mail me when a page on my watchlist is',
+ 'and the sending of email when the "Email me when a page on my watchlist is',
'changed" preference is enabled.'
);
}
public function getPossibleErrors() {
- $psModule = new ApiPageSet( $this );
+ $ps = $this->getPageSet();
return array_merge(
parent::getPossibleErrors(),
- $psModule->getPossibleErrors(),
- $this->getRequireMaxOneParameterErrorMessages( array( 'timestamp', 'torevid', 'newerthanrevid' ) ),
- $this->getRequireOnlyOneParameterErrorMessages( array_merge( array( 'entirewatchlist' ), array_keys( $psModule->getAllowedParams() ) ) ),
+ $ps->getPossibleErrors(),
+ $this->getRequireMaxOneParameterErrorMessages(
+ array( 'timestamp', 'torevid', 'newerthanrevid' ) ),
+ $this->getRequireOnlyOneParameterErrorMessages(
+ array_merge( array( 'entirewatchlist' ), array_keys( $ps->getFinalParams() ) ) ),
array(
array( 'code' => 'notloggedin', 'info' => 'Anonymous users cannot use watchlist change notifications' ),
array( 'code' => 'multpages', 'info' => 'torevid may only be used with a single page' ),
@@ -269,17 +285,13 @@ class ApiSetNotificationTimestamp extends ApiBase {
public function getExamples() {
return array(
- 'api.php?action=setnotificationtimestamp&entirewatchlist=&token=ABC123' => 'Reset the notification status for the entire watchlist',
- 'api.php?action=setnotificationtimestamp&titles=Main_page&token=ABC123' => 'Reset the notification status for "Main page"',
- 'api.php?action=setnotificationtimestamp&titles=Main_page&timestamp=2012-01-01T00:00:00Z&token=ABC123' => 'Set the notification timestamp for "Main page" so all edits since 1 January 2012 are unviewed',
+ 'api.php?action=setnotificationtimestamp&entirewatchlist=&token=123ABC' => 'Reset the notification status for the entire watchlist',
+ 'api.php?action=setnotificationtimestamp&titles=Main_page&token=123ABC' => 'Reset the notification status for "Main page"',
+ 'api.php?action=setnotificationtimestamp&titles=Main_page&timestamp=2012-01-01T00:00:00Z&token=123ABC' => 'Set the notification timestamp for "Main page" so all edits since 1 January 2012 are unviewed',
);
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:SetNotificationTimestamp';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiTokens.php b/includes/api/ApiTokens.php
index 2c9b482c..7080f547 100644
--- a/includes/api/ApiTokens.php
+++ b/includes/api/ApiTokens.php
@@ -24,25 +24,17 @@
* @file
*/
-
/**
* @ingroup API
*/
class ApiTokens extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
- wfProfileIn( __METHOD__ );
$params = $this->extractRequestParams();
$res = array();
$types = $this->getTokenTypes();
foreach ( $params['type'] as $type ) {
- $type = strtolower( $type );
-
$val = call_user_func( $types[$type], null, null );
if ( $val === false ) {
@@ -53,7 +45,6 @@ class ApiTokens extends ApiBase {
}
$this->getResult()->addValue( null, $this->getModuleName(), $res );
- wfProfileOut( __METHOD__ );
}
private function getTokenTypes() {
@@ -62,11 +53,11 @@ class ApiTokens extends ApiBase {
return $types;
}
wfProfileIn( __METHOD__ );
- $types = array( 'patrol' => 'ApiQueryRecentChanges::getPatrolToken' );
+ $types = array( 'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' ) );
$names = array( 'edit', 'delete', 'protect', 'move', 'block', 'unblock',
'email', 'import', 'watch', 'options' );
foreach ( $names as $name ) {
- $types[$name] = 'ApiQueryInfo::get' . ucfirst( $name ) . 'Token';
+ $types[$name] = array( 'ApiQueryInfo', 'get' . ucfirst( $name ) . 'Token' );
}
wfRunHooks( 'ApiTokensGetTokenTypes', array( &$types ) );
ksort( $types );
@@ -85,54 +76,13 @@ class ApiTokens extends ApiBase {
}
public function getResultProperties() {
- return array(
- '' => array(
- 'patroltoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'edittoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'deletetoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'protecttoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'movetoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'blocktoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'unblocktoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'emailtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'importtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'watchtoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- ),
- 'optionstoken' => array(
- ApiBase::PROP_TYPE => 'string',
- ApiBase::PROP_NULLABLE => true
- )
- )
+ $props = array(
+ '' => array(),
);
+
+ self::addTokenProperties( $props, $this->getTokenTypes() );
+
+ return $props;
}
public function getParamDescription() {
@@ -151,8 +101,4 @@ class ApiTokens extends ApiBase {
'api.php?action=tokens&type=email|move' => 'Retrieve an email token and a move token'
);
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index ff9ac474..55e7331d 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -32,10 +32,6 @@
*/
class ApiUnblock extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
/**
* Unblocks the specified user or provides the reason the unblock failed.
*/
@@ -178,8 +174,4 @@ class ApiUnblock extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Block';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index c9962517..4bbe568d 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -29,10 +29,6 @@
*/
class ApiUndelete extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$params = $this->extractRequestParams();
@@ -45,7 +41,7 @@ class ApiUndelete extends ApiBase {
}
$titleObj = Title::newFromText( $params['title'] );
- if ( !$titleObj ) {
+ if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
@@ -61,7 +57,13 @@ class ApiUndelete extends ApiBase {
}
$pa = new PageArchive( $titleObj );
- $retval = $pa->undelete( ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ), $params['reason'] );
+ $retval = $pa->undelete(
+ ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ),
+ $params['reason'],
+ array(),
+ false,
+ $this->getUser()
+ );
if ( !is_array( $retval ) ) {
$this->dieUsageMsg( 'cannotundelete' );
}
@@ -170,8 +172,4 @@ class ApiUndelete extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Undelete';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index e7a7849b..7d67aa6e 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -36,11 +36,9 @@ class ApiUpload extends ApiBase {
protected $mParams;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
+ global $wgEnableAsyncUploads;
+
// Check whether upload is enabled
if ( !UploadBase::isEnabled() ) {
$this->dieUsageMsg( 'uploaddisabled' );
@@ -51,6 +49,8 @@ class ApiUpload extends ApiBase {
// Parameter handling
$this->mParams = $this->extractRequestParams();
$request = $this->getMain()->getRequest();
+ // Check if async mode is actually supported (jobs done in cli mode)
+ $this->mParams['async'] = ( $this->mParams['async'] && $wgEnableAsyncUploads );
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
$this->mParams['chunk'] = $request->getFileName( 'chunk' );
@@ -62,17 +62,16 @@ class ApiUpload extends ApiBase {
// Select an upload module
if ( !$this->selectUploadModule() ) {
- // This is not a true upload, but a status request or similar
- return;
- }
- if ( !isset( $this->mUpload ) ) {
+ return; // not a true upload, but a status request or similar
+ } elseif ( !isset( $this->mUpload ) ) {
$this->dieUsage( 'No upload module set', 'nomodule' );
}
// First check permission to upload
$this->checkPermissions( $user );
- // Fetch the file
+ // Fetch the file (usually a no-op)
+ /** @var $status Status */
$status = $this->mUpload->fetchFile();
if ( !$status->isGood() ) {
$errors = $status->getErrorsArray();
@@ -82,26 +81,32 @@ class ApiUpload extends ApiBase {
// Check if the uploaded file is sane
if ( $this->mParams['chunk'] ) {
- $maxSize = $this->mUpload->getMaxUploadSize( );
+ $maxSize = $this->mUpload->getMaxUploadSize();
if( $this->mParams['filesize'] > $maxSize ) {
$this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
}
+ if ( !$this->mUpload->getTitle() ) {
+ $this->dieUsage( 'Invalid file title supplied', 'internal-error' );
+ }
+ } elseif ( $this->mParams['async'] && $this->mParams['filekey'] ) {
+ // defer verification to background process
} else {
+ wfDebug( __METHOD__ . 'about to verify' );
$this->verifyUpload();
}
-
+
// Check if the user has the rights to modify or overwrite the requested title
// (This check is irrelevant if stashing is already requested, since the errors
// can always be fixed by changing the title)
- if ( ! $this->mParams['stash'] ) {
+ if ( !$this->mParams['stash'] ) {
$permErrors = $this->mUpload->verifyTitlePermissions( $user );
if ( $permErrors !== true ) {
$this->dieRecoverableError( $permErrors[0], 'filename' );
}
}
- // Get the result based on the current upload context:
- $result = $this->getContextResult();
+ // Get the result based on the current upload context:
+ $result = $this->getContextResult();
if ( $result['result'] === 'Success' ) {
$result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
}
@@ -111,14 +116,15 @@ class ApiUpload extends ApiBase {
// Cleanup any temporary mess
$this->mUpload->cleanupTempFile();
}
+
/**
- * Get an uplaod result based on upload context
+ * Get an upload result based on upload context
* @return array
*/
- private function getContextResult(){
+ private function getContextResult() {
$warnings = $this->getApiWarnings();
if ( $warnings && !$this->mParams['ignorewarnings'] ) {
- // Get warnings formated in result array format
+ // Get warnings formatted in result array format
return $this->getWarningsResult( $warnings );
} elseif ( $this->mParams['chunk'] ) {
// Add chunk, and get result
@@ -131,12 +137,13 @@ class ApiUpload extends ApiBase {
// performUpload will return a formatted properly for the API with status
return $this->performUpload( $warnings );
}
+
/**
- * Get Stash Result, throws an expetion if the file could not be stashed.
- * @param $warnings array Array of Api upload warnings
+ * Get Stash Result, throws an exception if the file could not be stashed.
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
- private function getStashResult( $warnings ){
+ private function getStashResult( $warnings ) {
$result = array ();
// Some uploads can request they be stashed, so as not to publish them immediately.
// In this case, a failure to stash ought to be fatal
@@ -152,12 +159,13 @@ class ApiUpload extends ApiBase {
}
return $result;
}
+
/**
* Get Warnings Result
- * @param $warnings array Array of Api upload warnings
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
- private function getWarningsResult( $warnings ){
+ private function getWarningsResult( $warnings ) {
$result = array();
$result['result'] = 'Warning';
$result['warnings'] = $warnings;
@@ -171,12 +179,13 @@ class ApiUpload extends ApiBase {
}
return $result;
}
+
/**
* Get the result of a chunk upload.
- * @param $warnings array Array of Api upload warnings
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
- private function getChunkResult( $warnings ){
+ private function getChunkResult( $warnings ) {
$result = array();
$result['result'] = 'Continue';
@@ -186,55 +195,78 @@ class ApiUpload extends ApiBase {
$request = $this->getMain()->getRequest();
$chunkPath = $request->getFileTempname( 'chunk' );
$chunkSize = $request->getUpload( 'chunk' )->getSize();
- if ($this->mParams['offset'] == 0) {
+ if ( $this->mParams['offset'] == 0 ) {
try {
- $result['filekey'] = $this->performStash();
+ $filekey = $this->performStash();
} catch ( MWException $e ) {
// FIXME: Error handling here is wrong/different from rest of this
$this->dieUsage( $e->getMessage(), 'stashfailed' );
}
} else {
- $status = $this->mUpload->addChunk($chunkPath, $chunkSize,
- $this->mParams['offset']);
+ $filekey = $this->mParams['filekey'];
+ /** @var $status Status */
+ $status = $this->mUpload->addChunk(
+ $chunkPath, $chunkSize, $this->mParams['offset'] );
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
return array();
}
+ }
- // Check we added the last chunk:
- if( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
+ // Check we added the last chunk:
+ if ( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
+ if ( $this->mParams['async'] ) {
+ $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ if ( $progress && $progress['result'] === 'Poll' ) {
+ $this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' );
+ }
+ UploadBase::setSessionStatus(
+ $this->mParams['filekey'],
+ array( 'result' => 'Poll',
+ 'stage' => 'queued', 'status' => Status::newGood() )
+ );
+ $ok = JobQueueGroup::singleton()->push( new AssembleUploadChunksJob(
+ Title::makeTitle( NS_FILE, $this->mParams['filekey'] ),
+ array(
+ 'filename' => $this->mParams['filename'],
+ 'filekey' => $this->mParams['filekey'],
+ 'session' => $this->getContext()->exportSession()
+ )
+ ) );
+ if ( $ok ) {
+ $result['result'] = 'Poll';
+ } else {
+ UploadBase::setSessionStatus( $this->mParams['filekey'], false );
+ $this->dieUsage(
+ "Failed to start AssembleUploadChunks.php", 'stashfailed' );
+ }
+ } else {
$status = $this->mUpload->concatenateChunks();
-
if ( !$status->isGood() ) {
$this->dieUsage( $status->getWikiText(), 'stashfailed' );
return array();
}
- // We have a new filekey for the fully concatenated file.
- $result['filekey'] = $this->mUpload->getLocalFile()->getFileKey();
-
- // Remove chunk from stash. (Checks against user ownership of chunks.)
- $this->mUpload->stash->removeFile( $this->mParams['filekey'] );
+ // The fully concatenated file has a new filekey. So remove
+ // the old filekey and fetch the new one.
+ $this->mUpload->stash->removeFile( $filekey );
+ $filekey = $this->mUpload->getLocalFile()->getFileKey();
$result['result'] = 'Success';
-
- } else {
-
- // Continue passing through the filekey for adding further chunks.
- $result['filekey'] = $this->mParams['filekey'];
}
}
+ $result['filekey'] = $filekey;
$result['offset'] = $this->mParams['offset'] + $chunkSize;
return $result;
}
-
+
/**
* Stash the file and return the file key
* Also re-raises exceptions with slightly more informative message strings (useful for API)
* @throws MWException
* @return String file key
*/
- function performStash() {
+ private function performStash() {
try {
$stashFile = $this->mUpload->stashFile();
@@ -244,7 +276,7 @@ class ApiUpload extends ApiBase {
$fileKey = $stashFile->getFileKey();
} catch ( MWException $e ) {
$message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
- wfDebug( __METHOD__ . ' ' . $message . "\n");
+ wfDebug( __METHOD__ . ' ' . $message . "\n" );
throw new MWException( $message );
}
return $fileKey;
@@ -254,12 +286,12 @@ class ApiUpload extends ApiBase {
* Throw an error that the user can recover from by providing a better
* value for $parameter
*
- * @param $error array Error array suitable for passing to dieUsageMsg()
- * @param $parameter string Parameter that needs revising
- * @param $data array Optional extra data to pass to the user
+ * @param array $error Error array suitable for passing to dieUsageMsg()
+ * @param string $parameter Parameter that needs revising
+ * @param array $data Optional extra data to pass to the user
* @throws UsageException
*/
- function dieRecoverableError( $error, $parameter, $data = array() ) {
+ private function dieRecoverableError( $error, $parameter, $data = array() ) {
try {
$data['filekey'] = $this->performStash();
$data['sessionkey'] = $data['filekey'];
@@ -283,11 +315,27 @@ class ApiUpload extends ApiBase {
$request = $this->getMain()->getRequest();
// chunk or one and only one of the following parameters is needed
- if( !$this->mParams['chunk'] ) {
+ if ( !$this->mParams['chunk'] ) {
$this->requireOnlyOneParameter( $this->mParams,
'filekey', 'file', 'url', 'statuskey' );
}
+ // Status report for "upload to stash"/"upload from stash"
+ if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
+ $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ if ( !$progress ) {
+ $this->dieUsage( 'No result in status data', 'missingresult' );
+ } elseif ( !$progress['status']->isGood() ) {
+ $this->dieUsage( $progress['status']->getWikiText(), 'stashfailed' );
+ }
+ if ( isset( $progress['status']->value['verification'] ) ) {
+ $this->checkVerification( $progress['status']->value['verification'] );
+ }
+ unset( $progress['status'] ); // remove Status object
+ $this->getResult()->addValue( null, $this->getModuleName(), $progress );
+ return false;
+ }
+
if ( $this->mParams['statuskey'] ) {
$this->checkAsyncDownloadEnabled();
@@ -302,7 +350,6 @@ class ApiUpload extends ApiBase {
}
$this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
return false;
-
}
// The following modules all require the filename parameter to be set
@@ -311,9 +358,11 @@ class ApiUpload extends ApiBase {
}
if ( $this->mParams['chunk'] ) {
+ $this->checkChunkedEnabled();
+
// Chunk upload
$this->mUpload = new UploadFromChunks();
- if( isset( $this->mParams['filekey'] ) ){
+ if( isset( $this->mParams['filekey'] ) ) {
// handle new chunk
$this->mUpload->continueChunks(
$this->mParams['filename'],
@@ -334,8 +383,11 @@ class ApiUpload extends ApiBase {
}
$this->mUpload = new UploadFromStash( $this->getUser() );
-
- $this->mUpload->initialize( $this->mParams['filekey'], $this->mParams['filename'] );
+ // This will not download the temp file in initialize() in async mode.
+ // We still have enough information to call checkWarnings() and such.
+ $this->mUpload->initialize(
+ $this->mParams['filekey'], $this->mParams['filename'], !$this->mParams['async']
+ );
} elseif ( isset( $this->mParams['file'] ) ) {
$this->mUpload = new UploadFromFile();
$this->mUpload->initialize(
@@ -396,13 +448,20 @@ class ApiUpload extends ApiBase {
/**
* Performs file verification, dies on error.
*/
- protected function verifyUpload( ) {
- global $wgFileExtensions;
-
- $verification = $this->mUpload->verifyUpload( );
+ protected function verifyUpload() {
+ $verification = $this->mUpload->verifyUpload();
if ( $verification['status'] === UploadBase::OK ) {
return;
+ } else {
+ return $this->checkVerification( $verification );
}
+ }
+
+ /**
+ * Performs file verification, dies on error.
+ */
+ protected function checkVerification( array $verification ) {
+ global $wgFileExtensions;
// TODO: Move them to ApiBase's message map
switch( $verification['status'] ) {
@@ -460,12 +519,11 @@ class ApiUpload extends ApiBase {
break;
default:
$this->dieUsage( 'An unknown error occurred', 'unknown-error',
- 0, array( 'code' => $verification['status'] ) );
+ 0, array( 'code' => $verification['status'] ) );
break;
}
}
-
/**
* Check warnings.
* Returns a suitable array for inclusion into API results if there were warnings
@@ -503,12 +561,11 @@ class ApiUpload extends ApiBase {
return $warnings;
}
-
/**
* Perform the actual upload. Returns a suitable result array on success;
* dies on failure.
*
- * @param $warnings array Array of Api upload warnings
+ * @param array $warnings Array of Api upload warnings
* @return array
*/
protected function performUpload( $warnings ) {
@@ -517,6 +574,7 @@ class ApiUpload extends ApiBase {
$this->mParams['text'] = $this->mParams['comment'];
}
+ /** @var $file File */
$file = $this->mUpload->getLocalFile();
$watch = $this->getWatchlistValue( $this->mParams['watchlist'], $file->getTitle() );
@@ -526,29 +584,57 @@ class ApiUpload extends ApiBase {
}
// No errors, no warnings: do the upload
- $status = $this->mUpload->performUpload( $this->mParams['comment'],
- $this->mParams['text'], $watch, $this->getUser() );
-
- if ( !$status->isGood() ) {
- $error = $status->getErrorsArray();
-
- if ( count( $error ) == 1 && $error[0][0] == 'async' ) {
- // The upload can not be performed right now, because the user
- // requested so
- return array(
- 'result' => 'Queued',
- 'statuskey' => $error[0][1],
- );
+ if ( $this->mParams['async'] ) {
+ $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ if ( $progress && $progress['result'] === 'Poll' ) {
+ $this->dieUsage( "Upload from stash already in progress.", 'publishfailed' );
+ }
+ UploadBase::setSessionStatus(
+ $this->mParams['filekey'],
+ array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
+ );
+ $ok = JobQueueGroup::singleton()->push( new PublishStashedFileJob(
+ Title::makeTitle( NS_FILE, $this->mParams['filename'] ),
+ array(
+ 'filename' => $this->mParams['filename'],
+ 'filekey' => $this->mParams['filekey'],
+ 'comment' => $this->mParams['comment'],
+ 'text' => $this->mParams['text'],
+ 'watch' => $watch,
+ 'session' => $this->getContext()->exportSession()
+ )
+ ) );
+ if ( $ok ) {
+ $result['result'] = 'Poll';
} else {
- $this->getResult()->setIndexedTagName( $error, 'error' );
+ UploadBase::setSessionStatus( $this->mParams['filekey'], false );
+ $this->dieUsage(
+ "Failed to start PublishStashedFile.php", 'publishfailed' );
+ }
+ } else {
+ /** @var $status Status */
+ $status = $this->mUpload->performUpload( $this->mParams['comment'],
+ $this->mParams['text'], $watch, $this->getUser() );
- $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+ if ( !$status->isGood() ) {
+ $error = $status->getErrorsArray();
+
+ if ( count( $error ) == 1 && $error[0][0] == 'async' ) {
+ // The upload can not be performed right now, because the user
+ // requested so
+ return array(
+ 'result' => 'Queued',
+ 'statuskey' => $error[0][1],
+ );
+ } else {
+ $this->getResult()->setIndexedTagName( $error, 'error' );
+
+ $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
+ }
}
+ $result['result'] = 'Success';
}
- $file = $this->mUpload->getLocalFile();
-
- $result['result'] = 'Success';
$result['filename'] = $file->getName();
if ( $warnings && count( $warnings ) > 0 ) {
$result['warnings'] = $warnings;
@@ -563,7 +649,14 @@ class ApiUpload extends ApiBase {
protected function checkAsyncDownloadEnabled() {
global $wgAllowAsyncCopyUploads;
if ( !$wgAllowAsyncCopyUploads ) {
- $this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled');
+ $this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled' );
+ }
+ }
+
+ protected function checkChunkedEnabled() {
+ global $wgAllowChunkedUploads;
+ if ( !$wgAllowChunkedUploads ) {
+ $this->dieUsage( 'Chunked uploads disabled', 'chunkeduploaddisabled' );
}
}
@@ -601,7 +694,9 @@ class ApiUpload extends ApiBase {
),
),
'ignorewarnings' => false,
- 'file' => null,
+ 'file' => array(
+ ApiBase::PARAM_TYPE => 'upload',
+ ),
'url' => null,
'filekey' => null,
'sessionkey' => array(
@@ -612,11 +707,15 @@ class ApiUpload extends ApiBase {
'filesize' => null,
'offset' => null,
- 'chunk' => null,
+ 'chunk' => array(
+ ApiBase::PARAM_TYPE => 'upload',
+ ),
+ 'async' => false,
'asyncdownload' => false,
'leavemessage' => false,
'statuskey' => null,
+ 'checkstatus' => false,
);
return $params;
@@ -641,9 +740,11 @@ class ApiUpload extends ApiBase {
'offset' => 'Offset of chunk in bytes',
'filesize' => 'Filesize of entire upload',
+ 'async' => 'Make potentially large file operations asynchronous when possible',
'asyncdownload' => 'Make fetching a URL asynchronous',
'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
- 'statuskey' => 'Fetch the upload status for this file key',
+ 'statuskey' => 'Fetch the upload status for this file key (upload by URL)',
+ 'checkstatus' => 'Only fetch the upload status for the given file key',
);
return $params;
@@ -692,7 +793,7 @@ class ApiUpload extends ApiBase {
' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter',
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
- 'sending the "file". Also you must get and send an edit token before doing any upload stuff'
+ 'sending the "file". Also you must get and send an edit token before doing any upload stuff'
);
}
@@ -712,8 +813,10 @@ class ApiUpload extends ApiBase {
array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
+ array( 'code' => 'publishfailed', 'info' => 'Publishing of stashed file failed' ),
array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
array( 'code' => 'asynccopyuploaddisabled', 'info' => 'Asynchronous copy uploads disabled' ),
+ array( 'code' => 'chunkeduploaddisabled', 'info' => 'Chunked uploads disabled' ),
array( 'fileexists-forbidden' ),
array( 'fileexists-shared-forbidden' ),
)
@@ -740,8 +843,4 @@ class ApiUpload extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Upload';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index cbb66a41..b9b1eeda 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -30,10 +30,6 @@
*/
class ApiUserrights extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
private $mUser = null;
public function execute() {
@@ -141,8 +137,4 @@ class ApiUserrights extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:User_group_membership';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index 0509f1f8..3e51299f 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -31,10 +31,6 @@
*/
class ApiWatch extends ApiBase {
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
- }
-
public function execute() {
$user = $this->getUser();
if ( !$user->isLoggedIn() ) {
@@ -44,12 +40,20 @@ class ApiWatch extends ApiBase {
$params = $this->extractRequestParams();
$title = Title::newFromText( $params['title'] );
- if ( !$title || $title->getNamespace() < 0 ) {
+ if ( !$title || $title->isExternal() || !$title->canExist() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
$res = array( 'title' => $title->getPrefixedText() );
+ // Currently unnecessary, code to act as a safeguard against any change in current behavior of uselang
+ // Copy from ApiParse
+ $oldLang = null;
+ if ( isset( $params['uselang'] ) && $params['uselang'] != $this->getContext()->getLanguage()->getCode() ) {
+ $oldLang = $this->getContext()->getLanguage(); // Backup language
+ $this->getContext()->setLanguage( Language::factory( $params['uselang'] ) );
+ }
+
if ( $params['unwatch'] ) {
$res['unwatched'] = '';
$res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
@@ -59,6 +63,11 @@ class ApiWatch extends ApiBase {
$res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
$success = WatchAction::doWatch( $title, $user );
}
+
+ if ( !is_null( $oldLang ) ) {
+ $this->getContext()->setLanguage( $oldLang ); // Reset language to $oldLang
+ }
+
if ( !$success ) {
$this->dieUsageMsg( 'hookaborted' );
}
@@ -88,6 +97,7 @@ class ApiWatch extends ApiBase {
ApiBase::PARAM_REQUIRED => true
),
'unwatch' => false,
+ 'uselang' => null,
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
@@ -99,6 +109,7 @@ class ApiWatch extends ApiBase {
return array(
'title' => 'The page to (un)watch',
'unwatch' => 'If set the page will be unwatched rather than watched',
+ 'uselang' => 'Language to show the message in',
'token' => 'A token previously acquired via prop=info',
);
}
@@ -136,8 +147,4 @@ class ApiWatch extends ApiBase {
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Watch';
}
-
- public function getVersion() {
- return __CLASS__ . ': $Id$';
- }
}