summaryrefslogtreecommitdiff
path: root/includes/api
diff options
context:
space:
mode:
Diffstat (limited to 'includes/api')
-rw-r--r--includes/api/ApiBase.php245
-rw-r--r--includes/api/ApiBlock.php9
-rw-r--r--includes/api/ApiDelete.php22
-rw-r--r--includes/api/ApiDisabled.php6
-rw-r--r--includes/api/ApiEditPage.php84
-rw-r--r--includes/api/ApiEmailUser.php11
-rw-r--r--includes/api/ApiFeedWatchlist.php4
-rw-r--r--includes/api/ApiFormatBase.php82
-rw-r--r--includes/api/ApiFormatJson.php4
-rw-r--r--includes/api/ApiFormatJson_json.php1480
-rw-r--r--includes/api/ApiFormatRaw.php71
-rw-r--r--includes/api/ApiFormatWddx.php14
-rw-r--r--includes/api/ApiFormatXml.php15
-rw-r--r--includes/api/ApiHelp.php6
-rw-r--r--includes/api/ApiImport.php179
-rw-r--r--includes/api/ApiLogin.php10
-rw-r--r--includes/api/ApiLogout.php6
-rw-r--r--includes/api/ApiMain.php71
-rw-r--r--includes/api/ApiMove.php51
-rw-r--r--includes/api/ApiOpenSearch.php10
-rw-r--r--includes/api/ApiPageSet.php146
-rw-r--r--includes/api/ApiParamInfo.php27
-rw-r--r--includes/api/ApiParse.php15
-rw-r--r--includes/api/ApiPatrol.php11
-rw-r--r--includes/api/ApiProtect.php21
-rw-r--r--includes/api/ApiPurge.php11
-rw-r--r--includes/api/ApiQuery.php118
-rw-r--r--includes/api/ApiQueryAllCategories.php20
-rw-r--r--includes/api/ApiQueryAllLinks.php24
-rw-r--r--includes/api/ApiQueryAllUsers.php33
-rw-r--r--includes/api/ApiQueryAllimages.php19
-rw-r--r--includes/api/ApiQueryAllmessages.php23
-rw-r--r--includes/api/ApiQueryAllpages.php18
-rw-r--r--includes/api/ApiQueryBacklinks.php108
-rw-r--r--includes/api/ApiQueryBase.php145
-rw-r--r--includes/api/ApiQueryBlocks.php16
-rw-r--r--includes/api/ApiQueryCategories.php43
-rw-r--r--includes/api/ApiQueryCategoryInfo.php43
-rw-r--r--includes/api/ApiQueryCategoryMembers.php25
-rw-r--r--includes/api/ApiQueryDeletedrevs.php183
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php28
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php21
-rw-r--r--includes/api/ApiQueryExternalLinks.php26
-rw-r--r--includes/api/ApiQueryImageInfo.php148
-rw-r--r--includes/api/ApiQueryImages.php28
-rw-r--r--includes/api/ApiQueryInfo.php555
-rw-r--r--includes/api/ApiQueryLangLinks.php26
-rw-r--r--includes/api/ApiQueryLinks.php29
-rw-r--r--includes/api/ApiQueryLogEvents.php85
-rw-r--r--includes/api/ApiQueryProtectedTitles.php191
-rw-r--r--includes/api/ApiQueryRandom.php26
-rw-r--r--includes/api/ApiQueryRecentChanges.php45
-rw-r--r--includes/api/ApiQueryRevisions.php155
-rw-r--r--includes/api/ApiQuerySearch.php25
-rw-r--r--includes/api/ApiQuerySiteinfo.php133
-rw-r--r--includes/api/ApiQueryUserContributions.php101
-rw-r--r--includes/api/ApiQueryUserInfo.php19
-rw-r--r--includes/api/ApiQueryUsers.php121
-rw-r--r--includes/api/ApiQueryWatchlist.php31
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php18
-rw-r--r--includes/api/ApiResult.php144
-rw-r--r--includes/api/ApiRollback.php17
-rw-r--r--includes/api/ApiUnblock.php9
-rw-r--r--includes/api/ApiUndelete.php13
-rw-r--r--includes/api/ApiWatch.php10
65 files changed, 3470 insertions, 1963 deletions
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 22144333..8cf8c096 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -24,15 +24,17 @@
*/
/**
- * This abstract class implements many basic API functions, and is the base of all API classes.
+ * This abstract class implements many basic API functions, and is the base of
+ * all API classes.
* The class functions are divided into several areas of functionality:
*
- * Module parameters: Derived classes can define getAllowedParams() to specify which parameters to expect,
- * how to parse and validate them.
+ * Module parameters: Derived classes can define getAllowedParams() to specify
+ * which parameters to expect,h ow to parse and validate them.
*
- * Profiling: various methods to allow keeping tabs on various tasks and their time costs
+ * Profiling: various methods to allow keeping tabs on various tasks and their
+ * time costs
*
- * Self-documentation: code to allow api to document its own state.
+ * Self-documentation: code to allow the API to document its own state
*
* @ingroup API
*/
@@ -56,8 +58,11 @@ abstract class ApiBase {
private $mMainModule, $mModuleName, $mModulePrefix;
/**
- * Constructor
- */
+ * Constructor
+ * @param $mainModule ApiMain object
+ * @param $moduleName string Name of this module
+ * @param $modulePrefix string Prefix to use for parameter names
+ */
public function __construct($mainModule, $moduleName, $modulePrefix = '') {
$this->mMainModule = $mainModule;
$this->mModuleName = $moduleName;
@@ -69,32 +74,34 @@ abstract class ApiBase {
*****************************************************************************/
/**
- * Evaluates the parameters, performs the requested query, and sets up the
- * result. Concrete implementations of ApiBase must override this method to
- * provide whatever functionality their module offers. Implementations must
- * not produce any output on their own and are not expected to handle any
- * errors.
+ * Evaluates the parameters, performs the requested query, and sets up
+ * the result. Concrete implementations of ApiBase must override this
+ * method to provide whatever functionality their module offers.
+ * Implementations must not produce any output on their own and are not
+ * expected to handle any errors.
*
- * The execute method will be invoked directly by ApiMain immediately before
- * the result of the module is output. Aside from the constructor, implementations
- * should assume that no other methods will be called externally on the module
- * before the result is processed.
+ * The execute() method will be invoked directly by ApiMain immediately
+ * before the result of the module is output. Aside from the
+ * constructor, implementations should assume that no other methods
+ * will be called externally on the module before the result is
+ * processed.
*
- * The result data should be stored in the result object referred to by
- * "getResult()". Refer to ApiResult.php for details on populating a result
- * object.
+ * The result data should be stored in the ApiResult object available
+ * through getResult().
*/
public abstract 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. May
- * be severely incorrect in many implementations!
+ * 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
*/
public abstract function getVersion();
/**
* Get the name of the module being executed by this instance
+ * @return string
*/
public function getModuleName() {
return $this->mModuleName;
@@ -102,6 +109,7 @@ abstract class ApiBase {
/**
* Get parameter prefix (usually two letters or an empty string).
+ * @return string
*/
public function getModulePrefix() {
return $this->mModulePrefix;
@@ -109,6 +117,7 @@ abstract class ApiBase {
/**
* Get the name of the module as shown in the profiler log
+ * @return string
*/
public function getModuleProfileName($db = false) {
if ($db)
@@ -118,7 +127,8 @@ abstract class ApiBase {
}
/**
- * Get main module
+ * Get the main module
+ * @return ApiMain object
*/
public function getMain() {
return $this->mMainModule;
@@ -127,14 +137,15 @@ abstract class ApiBase {
/**
* Returns true if this module is the main module ($this === $this->mMainModule),
* false otherwise.
+ * @return bool
*/
public function isMain() {
return $this === $this->mMainModule;
}
/**
- * Get the result object. Please refer to the documentation in ApiResult.php
- * for details on populating and accessing data in a result object.
+ * Get the result object
+ * @return ApiResult
*/
public function getResult() {
// Main module has getResult() method overriden
@@ -145,37 +156,45 @@ abstract class ApiBase {
}
/**
- * Get the result data array
+ * Get the result data array (read-only)
+ * @return array
*/
- public function & getResultData() {
+ public function getResultData() {
return $this->getResult()->getData();
}
/**
- * Set warning section for this module. Users should monitor this section to
- * notice any changes in API.
+ * Set warning section for this module. Users should monitor this
+ * section to notice any changes in API. Multiple calls to this
+ * function will result in the warning messages being separated by
+ * newlines
+ * @param $warning string Warning message
*/
public function setWarning($warning) {
- # If there is a warning already, append it to the existing one
- $data =& $this->getResult()->getData();
+ $data = $this->getResult()->getData();
if(isset($data['warnings'][$this->getModuleName()]))
{
# Don't add duplicate warnings
$warn_regex = preg_quote($warning, '/');
if(preg_match("/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*']))
return;
- $warning = "{$data['warnings'][$this->getModuleName()]['*']}\n$warning";
- unset($data['warnings'][$this->getModuleName()]);
+ $oldwarning = $data['warnings'][$this->getModuleName()]['*'];
+ # If there is a warning already, append it to the existing one
+ $warning = "$oldwarning\n$warning";
+ $this->getResult()->unsetValue('warnings', $this->getModuleName());
}
$msg = array();
ApiResult :: setContent($msg, $warning);
+ $this->getResult()->disableSizeCheck();
$this->getResult()->addValue('warnings', $this->getModuleName(), $msg);
+ $this->getResult()->enableSizeCheck();
}
/**
* If the module may only be used with a certain format module,
* it should override this method to return an instance of that formatter.
* A value of null means the default format will be used.
+ * @return mixed instance of a derived class of ApiFormatBase, or null
*/
public function getCustomPrinter() {
return null;
@@ -183,6 +202,7 @@ abstract class ApiBase {
/**
* Generates help message for this module, or false if there is no description
+ * @return mixed string or false
*/
public function makeHelpMsg() {
@@ -198,8 +218,15 @@ abstract class ApiBase {
);
$msg = $lnPrfx . implode($lnPrfx, $msg) . "\n";
+ if ($this->isReadMode())
+ $msg .= "\nThis module requires read rights.";
+ if ($this->isWriteMode())
+ $msg .= "\nThis module requires write rights.";
if ($this->mustBePosted())
- $msg .= "\nThis module only accepts POST requests.\n";
+ $msg .= "\nThis module only accepts POST requests.";
+ if ($this->isReadMode() || $this->isWriteMode() ||
+ $this->mustBePosted())
+ $msg .= "\n";
// Parameters
$paramsMsg = $this->makeHelpMsgParameters();
@@ -220,16 +247,16 @@ abstract class ApiBase {
if ($this->getMain()->getShowVersions()) {
$versions = $this->getVersion();
- $pattern = '(\$.*) ([0-9a-z_]+\.php) (.*\$)';
+ $pattern = '/(\$.*) ([0-9a-z_]+\.php) (.*\$)/i';
$replacement = '\\0' . "\n " . 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/api/\\2';
if (is_array($versions)) {
foreach ($versions as &$v)
- $v = eregi_replace($pattern, $replacement, $v);
+ $v = preg_replace($pattern, $replacement, $v);
$versions = implode("\n ", $versions);
}
else
- $versions = eregi_replace($pattern, $replacement, $versions);
+ $versions = preg_replace($pattern, $replacement, $versions);
$msg .= "Version:\n $versions\n";
}
@@ -241,6 +268,7 @@ abstract class ApiBase {
/**
* Generates the parameter descriptions for this module, to be displayed in the
* module's help.
+ * @return string
*/
public function makeHelpMsgParameters() {
$params = $this->getFinalParams();
@@ -311,6 +339,7 @@ abstract class ApiBase {
/**
* Returns the description string for this module
+ * @return mixed string or array of strings
*/
protected function getDescription() {
return false;
@@ -318,15 +347,18 @@ abstract class ApiBase {
/**
* Returns usage examples for this module. Return null if no examples are available.
+ * @return mixed string or array of strings
*/
protected function getExamples() {
return false;
}
/**
- * Returns an array of allowed parameters (keys) => default value for that parameter.
- * Don't call this function directly: use getFinalParams() to allow hooks
- * to modify parameters as needed.
+ * Returns an array of allowed parameters (parameter name) => (default
+ * value) or (parameter name) => (array with PARAM_* constants as keys)
+ * Don't call this function directly: use getFinalParams() to allow
+ * hooks to modify parameters as needed.
+ * @return array
*/
protected function getAllowedParams() {
return false;
@@ -334,24 +366,30 @@ abstract class ApiBase {
/**
* Returns an array of parameter descriptions.
- * Don't call this functon directly: use getFinalParamDescription() to allow
- * hooks to modify descriptions as needed.
+ * Don't call this functon directly: use getFinalParamDescription() to
+ * allow hooks to modify descriptions as needed.
+ * @return array
*/
protected function getParamDescription() {
return false;
}
/**
- * Get final list of parameters, after hooks have had
- * a chance to tweak it as needed.
+ * Get final list of parameters, after hooks have had a chance to
+ * tweak it as needed.
+ * @return array
*/
public function getFinalParams() {
$params = $this->getAllowedParams();
wfRunHooks('APIGetAllowedParams', array(&$this, &$params));
return $params;
}
-
-
+
+ /**
+ * Get final description, after hooks have had a chance to tweak it as
+ * needed.
+ * @return array
+ */
public function getFinalParamDescription() {
$desc = $this->getParamDescription();
wfRunHooks('APIGetParamDescription', array(&$this, &$desc));
@@ -361,16 +399,21 @@ abstract class ApiBase {
/**
* 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
+ * @return string Prefixed parameter name
*/
public function encodeParamName($paramName) {
return $this->mModulePrefix . $paramName;
}
/**
- * Using getAllowedParams(), makes an array of the values provided by the user,
- * with key being the name of the variable, and value - validated value from user or default.
- * limit=max will not be parsed if $parseMaxLimit is set to false; use this
- * when the max limit is not definite, e.g. when getting revisions.
+ * Using getAllowedParams(), this function makes an array of the values
+ * provided by the user, with key being the name of the variable, and
+ * value - validated value from user or default. limit=max will not be
+ * parsed if $parseMaxLimit is set to false; use this when the max
+ * limit is not definitive yet, e.g. when getting revisions.
+ * @param $parseMaxLimit bool
+ * @return array
*/
public function extractRequestParams($parseMaxLimit = true) {
$params = $this->getFinalParams();
@@ -384,6 +427,9 @@ abstract class ApiBase {
/**
* Get a value for the given parameter
+ * @param $paramName string Parameter name
+ * @param $parseMaxLimit bool see extractRequestParams()
+ * @return mixed Parameter value
*/
protected function getParameter($paramName, $parseMaxLimit = true) {
$params = $this->getFinalParams();
@@ -393,6 +439,7 @@ abstract class ApiBase {
/**
* Die if none or more than one of a certain set of parameters is set
+ * @param $params array of parameter names
*/
public function requireOnlyOneParameter($params) {
$required = func_get_args();
@@ -411,6 +458,7 @@ abstract class ApiBase {
/**
* Returns an array of the namespaces (by integer id) that exist on the
* wiki. Used primarily in help documentation.
+ * @return array
*/
public static function getValidNamespaces() {
static $mValidNamespaces = null;
@@ -430,8 +478,10 @@ abstract class ApiBase {
* Using the settings determine the value for the given parameter
*
* @param $paramName String: parameter name
- * @param $paramSettings Mixed: default value or an array of settings using PARAM_* constants.
+ * @param $paramSettings Mixed: default value or an array of settings
+ * using PARAM_* constants.
* @param $parseMaxLimit Boolean: parse limit when max is given?
+ * @return mixed Parameter value
*/
protected function getParameterFromSettings($paramName, $paramSettings, $parseMaxLimit) {
@@ -550,14 +600,17 @@ abstract class ApiBase {
* 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 - The name of the parameter (for error reporting)
- * @param value - The value being parsed
- * @param allowMultiple - Can $value contain more than one value separated by '|'?
- * @param allowedValues - An array of values to check against. If null, all values are accepted.
- * @return (allowMultiple ? an_array_of_values : a_single_value)
+ * @param $valueName string 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
+ * separated by '|'?
+ * @param $allowedValues mixed An array of values to check against. If
+ * null, all values are accepted.
+ * @return mixed (allowMultiple ? an_array_of_values : a_single_value)
*/
protected function parseMultiValue($valueName, $value, $allowMultiple, $allowedValues) {
- if( trim($value) === "" )
+ if( trim($value) === "" && $allowMultiple)
return array();
$sizeLimit = $this->mMainModule->canApiHighLimits() ? self::LIMIT_SML2 : self::LIMIT_SML1;
$valuesList = explode('|', $value, $sizeLimit + 1);
@@ -590,8 +643,14 @@ abstract class ApiBase {
}
/**
- * Validate the value against the minimum and user/bot maximum limits. Prints usage info on failure.
- */
+ * 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 Minimum value
+ * @param $max int Maximum value for users
+ * @param $botMax int Maximum value for sysops/bots
+ */
function validateLimit($paramName, $value, $min, $max, $botMax = null) {
if (!is_null($min) && $value < $min) {
$this->dieUsage($this->encodeParamName($paramName) . " may not be less than $min (set to $value)", $paramName);
@@ -632,9 +691,13 @@ abstract class ApiBase {
}
/**
- * Call main module's error handler
+ * Call the main module's error handler
+ * @param $description string Error text
+ * @param $errorCode string Error code
+ * @param $httpRespCode int HTTP response code
*/
public function dieUsage($description, $errorCode, $httpRespCode = 0) {
+ wfProfileClose();
throw new UsageException($description, $this->encodeParamName($errorCode), $httpRespCode);
}
@@ -698,11 +761,17 @@ abstract class ApiBase {
'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"),
'rcpatroldisabled' => array('code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki"),
'markedaspatrollederror-noautopatrol' => array('code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes"),
+ 'delete-toobig' => array('code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions"),
+ 'movenotallowedfile' => array('code' => 'cantmovefile', 'info' => "You don't have permission to move files"),
// API-specific messages
+ 'readrequired' => array('code' => 'readapidenied', 'info' => "You need read permission to use this module"),
+ 'writedisabled' => array('code' => 'noapiwrite', 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file"),
+ 'writerequired' => array('code' => 'writeapidenied', 'info' => "You're not allowed to edit this wiki through the API"),
'missingparam' => array('code' => 'no$1', 'info' => "The \$1 parameter must be set"),
'invalidtitle' => array('code' => 'invalidtitle', 'info' => "Bad title ``\$1''"),
'nosuchpageid' => array('code' => 'nosuchpageid', 'info' => "There is no page with ID \$1"),
+ 'nosuchrevid' => array('code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1"),
'invaliduser' => array('code' => 'invaliduser', 'info' => "Invalid username ``\$1''"),
'invalidexpiry' => array('code' => 'invalidexpiry', 'info' => "Invalid expiry time ``\$1''"),
'pastexpiry' => array('code' => 'pastexpiry', 'info' => "Expiry time ``\$1'' is in the past"),
@@ -723,36 +792,47 @@ abstract class ApiBase {
'protect-invalidaction' => array('code' => 'protect-invalidaction', 'info' => "Invalid protection type ``\$1''"),
'protect-invalidlevel' => array('code' => 'protect-invalidlevel', 'info' => "Invalid protection level ``\$1''"),
'toofewexpiries' => array('code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed"),
-
+ 'cantimport' => array('code' => 'cantimport', 'info' => "You don't have permission to import pages"),
+ 'cantimport-upload' => array('code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages"),
+ 'importnofile' => array('code' => 'nofile', 'info' => "You didn't upload a file"),
+ 'importuploaderrorsize' => array('code' => 'filetoobig', 'info' => 'The file you uploaded is bigger than the maximum upload size'),
+ 'importuploaderrorpartial' => array('code' => 'partialupload', 'info' => 'The file was only partially uploaded'),
+ 'importuploaderrortemp' => array('code' => 'notempdir', 'info' => 'The temporary upload directory is missing'),
+ 'importcantopen' => array('code' => 'cantopenfile', 'info' => "Couldn't open the uploaded file"),
+ 'import-noarticle' => array('code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified'),
+ 'importbadinterwiki' => array('code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified'),
+ 'import-unknownerror' => array('code' => 'import-unknownerror', 'info' => "Unknown error on import: ``\$1''"),
// ApiEditPage messages
'noimageredirect-anon' => array('code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects"),
'noimageredirect-logged' => array('code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects"),
'spamdetected' => array('code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: ``\$1''"),
'filtered' => array('code' => 'filtered', 'info' => "The filter callback function refused your edit"),
- 'contenttoobig' => array('code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 bytes"),
+ 'contenttoobig' => array('code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"),
'noedit-anon' => array('code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages"),
'noedit' => array('code' => 'noedit', 'info' => "You don't have permission to edit pages"),
'wasdeleted' => array('code' => 'pagedeleted', 'info' => "The page has been deleted since you fetched its timestamp"),
'blankpage' => array('code' => 'emptypage', 'info' => "Creating new, empty pages is not allowed"),
'editconflict' => array('code' => 'editconflict', 'info' => "Edit conflict detected"),
'hashcheckfailed' => array('code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect"),
- 'missingtext' => array('code' => 'notext', 'info' => "One of the text, appendtext and prependtext parameters must be set"),
+ 'missingtext' => array('code' => 'notext', 'info' => "One of the text, appendtext, prependtext and undo parameters must be set"),
'emptynewsection' => array('code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.'),
+ 'revwrongpage' => array('code' => 'revwrongpage', 'info' => "r\$1 is not a revision of ``\$2''"),
+ 'undo-failure' => array('code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits'),
);
/**
* Output the error message related to a certain array
- * @param array $error Element of a getUserPermissionsErrors()-style array
+ * @param $error array Element of a getUserPermissionsErrors()-style array
*/
public function dieUsageMsg($error) {
$parsed = $this->parseMsg($error);
- $this->dieUsage($parsed['code'], $parsed['info']);
+ $this->dieUsage($parsed['info'], $parsed['code']);
}
/**
* Return the error message related to a certain array
- * @param array $error Element of a getUserPermissionsErrors()-style array
+ * @param $error array Element of a getUserPermissionsErrors()-style array
* @return array('code' => code, 'info' => info)
*/
public function parseMsg($error) {
@@ -769,27 +849,39 @@ abstract class ApiBase {
/**
* Internal code errors should be reported with this method
+ * @param $method string Method or function name
+ * @param $message string Error message
*/
protected static function dieDebug($method, $message) {
wfDebugDieBacktrace("Internal error in $method: $message");
}
/**
- * Indicates if API needs to check maxlag
+ * Indicates if this module needs maxlag to be checked
+ * @return bool
*/
public function shouldCheckMaxlag() {
return true;
}
/**
- * Indicates if this module requires edit mode
+ * Indicates whether this module requires read rights
+ * @return bool
+ */
+ public function isReadMode() {
+ return true;
+ }
+ /**
+ * Indicates whether this module requires write mode
+ * @return bool
*/
- public function isEditMode() {
+ public function isWriteMode() {
return false;
}
/**
* Indicates whether this module must be called with a POST request
+ * @return bool
*/
public function mustBePosted() {
return false;
@@ -839,6 +931,7 @@ abstract class ApiBase {
/**
* Total time the module was executed
+ * @return float
*/
public function getProfileTime() {
if ($this->mTimeIn !== 0)
@@ -882,6 +975,7 @@ abstract class ApiBase {
/**
* Total time the module used the database
+ * @return float
*/
public function getProfileDBTime() {
if ($this->mDBTimeIn !== 0)
@@ -889,8 +983,14 @@ abstract class ApiBase {
return $this->mDBTime;
}
+ /**
+ * 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
+ */
public static function debugPrint($value, $name = 'unknown', $backtrace = false) {
- print "\n\n<pre><b>Debuging value '$name':</b>\n\n";
+ print "\n\n<pre><b>Debugging value '$name':</b>\n\n";
var_export($value);
if ($backtrace)
print "\n" . wfBacktrace();
@@ -899,9 +999,10 @@ abstract class ApiBase {
/**
- * Returns a String that identifies the version of this class.
+ * Returns a string that identifies the version of this class.
+ * @return string
*/
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiBase.php 47041 2009-02-09 14:39:41Z catrope $';
+ return __CLASS__ . ': $Id: ApiBase.php 50217 2009-05-05 13:12:16Z tstarling $';
}
}
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index dfb11061..1c0bd5ac 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -50,7 +50,6 @@ class ApiBlock extends ApiBase {
*/
public function execute() {
global $wgUser, $wgBlockAllowsUTEdit;
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
if($params['gettoken'])
@@ -94,7 +93,7 @@ class ApiBlock extends ApiBase {
$this->dieUsageMsg($retval);
$res['user'] = $params['user'];
- $res['userID'] = $userID;
+ $res['userID'] = intval($userID);
$res['expiry'] = ($expiry == Block::infinity() ? 'infinite' : wfTimestamp(TS_ISO_8601, $expiry));
$res['reason'] = $params['reason'];
if($params['anononly'])
@@ -115,6 +114,10 @@ class ApiBlock extends ApiBase {
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'user' => null,
@@ -163,6 +166,6 @@ class ApiBlock extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiBlock.php 43677 2008-11-18 15:21:04Z catrope $';
+ return __CLASS__ . ': $Id: ApiBlock.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index c0212924..9431ad78 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -49,7 +49,6 @@ class ApiDelete extends ApiBase {
*/
public function execute() {
global $wgUser;
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
$this->requireOnlyOneParameter($params, 'title', 'pageid');
@@ -76,14 +75,18 @@ class ApiDelete extends ApiBase {
$retval = self::deleteFile($params['token'], $titleObj, $params['oldimage'], $reason, false);
if(count($retval))
// We don't care about multiple errors, just report one of them
- $this->dieUsageMsg(current($retval));
+ $this->dieUsageMsg(reset($retval));
} else {
$articleObj = new Article($titleObj);
+ if($articleObj->isBigDeletion() && !$wgUser->isAllowed('bigdelete')) {
+ global $wgDeleteRevisionsLimit;
+ $this->dieUsageMsg(array('delete-toobig', $wgDeleteRevisionsLimit));
+ }
$retval = self::delete($articleObj, $params['token'], $reason);
if(count($retval))
// We don't care about multiple errors, just report one of them
- $this->dieUsageMsg(current($retval));
+ $this->dieUsageMsg(reset($retval));
if($params['watch'] || $wgUser->getOption('watchdeletion'))
$articleObj->doWatch();
@@ -133,9 +136,10 @@ class ApiDelete extends ApiBase {
if($reason === false)
return array(array('cannotdelete'));
}
-
- if (!wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason)))
- $this->dieUsageMsg(array('hookaborted'));
+
+ $error = '';
+ if (!wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason, $error)))
+ $this->dieUsageMsg(array('hookaborted', $error));
// Luckily, Article.php provides a reusable delete function that does the hard work for us
if($article->doDeleteArticle($reason)) {
@@ -173,6 +177,10 @@ class ApiDelete extends ApiBase {
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'title' => null,
@@ -213,6 +221,6 @@ class ApiDelete extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiDelete.php 44541 2008-12-13 21:07:18Z mrzman $';
+ return __CLASS__ . ': $Id: ApiDelete.php 48122 2009-03-07 12:58:41Z catrope $';
}
}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index 40e38a0f..9e0bf56e 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -48,6 +48,10 @@ class ApiDisabled extends ApiBase {
$this->dieUsage("The ``{$this->getModuleName()}'' module has been disabled.", 'moduledisabled');
}
+ public function isReadMode() {
+ return false;
+ }
+
public function getAllowedParams() {
return array ();
}
@@ -67,6 +71,6 @@ class ApiDisabled extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiDisabled.php 41268 2008-09-25 20:50:50Z catrope $';
+ return __CLASS__ . ': $Id: ApiDisabled.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index bc5dfa87..d4a57b83 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -43,12 +43,12 @@ class ApiEditPage extends ApiBase {
public function execute() {
global $wgUser;
- $this->getMain()->requestWriteMode();
-
$params = $this->extractRequestParams();
if(is_null($params['title']))
$this->dieUsageMsg(array('missingparam', 'title'));
- if(is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext']))
+ if(is_null($params['text']) && is_null($params['appendtext']) &&
+ is_null($params['prependtext']) &&
+ $params['undo'] == 0)
$this->dieUsageMsg(array('missingtext'));
if(is_null($params['token']))
$this->dieUsageMsg(array('missingparam', 'token'));
@@ -58,6 +58,9 @@ class ApiEditPage extends ApiBase {
$titleObj = Title::newFromText($params['title']);
if(!$titleObj)
$this->dieUsageMsg(array('invalidtitle', $params['title']));
+ // Some functions depend on $wgTitle == $ep->mTitle
+ global $wgTitle;
+ $wgTitle = $titleObj;
if($params['createonly'] && $titleObj->exists())
$this->dieUsageMsg(array('createonly-exists'));
@@ -75,13 +78,50 @@ class ApiEditPage extends ApiBase {
$toMD5 = $params['text'];
if(!is_null($params['appendtext']) || !is_null($params['prependtext']))
{
- $content = $articleObj->getContent();
+ // 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();
$params['text'] = $params['prependtext'] . $content . $params['appendtext'];
$toMD5 = $params['prependtext'] . $params['appendtext'];
}
+
+ if($params['undo'] > 0)
+ {
+ if($params['undoafter'] > 0)
+ {
+ if($params['undo'] < $params['undoafter'])
+ list($params['undo'], $params['undoafter']) =
+ array($params['undoafter'], $params['undo']);
+ $undoafterRev = Revision::newFromID($params['undoafter']);
+ }
+ $undoRev = Revision::newFromID($params['undo']);
+ if(is_null($undoRev) || $undoRev->isDeleted(Revision::DELETED_TEXT))
+ $this->dieUsageMsg(array('nosuchrevid', $params['undo']));
+ if($params['undoafter'] == 0)
+ $undoafterRev = $undoRev->getPrevious();
+ if(is_null($undoafterRev) || $undoafterRev->isDeleted(Revision::DELETED_TEXT))
+ $this->dieUsageMsg(array('nosuchrevid', $params['undoafter']));
+ if($undoRev->getPage() != $articleObj->getID())
+ $this->dieUsageMsg(array('revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText()));
+ if($undoafterRev->getPage() != $articleObj->getID())
+ $this->dieUsageMsg(array('revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText()));
+ $newtext = $articleObj->getUndoText($undoRev, $undoafterRev);
+ if($newtext === false)
+ $this->dieUsageMsg(array('undo-failure'));
+ $params['text'] = $newtext;
+ // If no summary was given and we only undid one rev,
+ // use an autosummary
+ if(is_null($params['summary']) && $titleObj->getNextRevisionID($undoafterRev->getID()) == $params['undo'])
+ $params['summary'] = wfMsgForContent('undo-summary', $params['undo'], $undoRev->getUserText());
+ }
# See if the MD5 hash checks out
- if(isset($params['md5']))
+ if(!is_null($params['md5']))
if(md5($toMD5) !== $params['md5'])
$this->dieUsageMsg(array('hashcheckfailed'));
@@ -140,9 +180,9 @@ class ApiEditPage extends ApiBase {
# Run hooks
# Handle CAPTCHA parameters
global $wgRequest;
- if(isset($params['captchaid']))
+ if(!is_null($params['captchaid']))
$wgRequest->setVal( 'wpCaptchaId', $params['captchaid'] );
- if(isset($params['captchaword']))
+ if(!is_null($params['captchaword']))
$wgRequest->setVal( 'wpCaptchaWord', $params['captchaword'] );
$r = array();
if(!wfRunHooks('APIEditBeforeSave', array(&$ep, $ep->textbox1, &$r)))
@@ -160,10 +200,6 @@ class ApiEditPage extends ApiBase {
# Do the actual save
$oldRevId = $articleObj->getRevIdFetched();
$result = null;
- # *Something* is setting $wgTitle to a title corresponding to "Msg",
- # but that breaks API mode detection through is_null($wgTitle)
- global $wgTitle;
- $wgTitle = null;
# Fake $wgRequest for some hooks inside EditPage
# FIXME: This interface SUCKS
$oldRequest = $wgRequest;
@@ -217,7 +253,7 @@ class ApiEditPage extends ApiBase {
$r['new'] = '';
case EditPage::AS_SUCCESS_UPDATE:
$r['result'] = "Success";
- $r['pageid'] = $titleObj->getArticleID();
+ $r['pageid'] = intval($titleObj->getArticleID());
$r['title'] = $titleObj->getPrefixedText();
# HACK: We create a new Article object here because getRevIdFetched()
# refuses to be run twice, and because Title::getLatestRevId()
@@ -229,8 +265,8 @@ class ApiEditPage extends ApiBase {
$r['nochange'] = '';
else
{
- $r['oldrevid'] = $oldRevId;
- $r['newrevid'] = $newRevId;
+ $r['oldrevid'] = intval($oldRevId);
+ $r['newrevid'] = intval($newRevId);
}
break;
default:
@@ -243,6 +279,10 @@ class ApiEditPage extends ApiBase {
return true;
}
+ public function isWriteMode() {
+ return true;
+ }
+
protected function getDescription() {
return 'Create and edit pages.';
}
@@ -269,6 +309,12 @@ class ApiEditPage extends ApiBase {
'md5' => null,
'prependtext' => null,
'appendtext' => null,
+ 'undo' => array(
+ ApiBase :: PARAM_TYPE => 'integer'
+ ),
+ 'undoafter' => array(
+ ApiBase :: PARAM_TYPE => 'integer'
+ ),
);
}
@@ -300,17 +346,23 @@ class ApiEditPage extends ApiBase {
'prependtext' => array( 'Add this text to the beginning of the page. Overrides text.',
'Don\'t use together with section: that won\'t do what you expect.'),
'appendtext' => 'Add this text to the end of the page. Overrides text',
+ 'undo' => 'Undo this revision. Overrides text, prependtext and appendtext',
+ 'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
);
}
protected function getExamples() {
return array (
"Edit a page (anonymous user):",
- " api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\"
+ " api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\",
+ "Prepend __NOTOC__ to a page (anonymous user):",
+ " api.php?action=edit&title=Test&summary=NOTOC&minor&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\",
+ "Undo r13579 through r13585 with autosummary(anonymous user):",
+ " api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\",
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiEditPage.php 44394 2008-12-10 14:12:54Z catrope $';
+ return __CLASS__ . ': $Id: ApiEditPage.php 50220 2009-05-05 14:07:59Z tstarling $';
}
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index fbdf495f..9bb504fb 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -39,14 +39,11 @@ class ApiEmailUser extends ApiBase {
public function execute() {
global $wgUser;
-
// Check whether email is enabled
if ( !EmailUserForm::userEmailEnabled() )
$this->dieUsageMsg( array( 'usermaildisabled' ) );
-
- $this->getMain()->requestWriteMode();
+
$params = $this->extractRequestParams();
-
// Check required parameters
if ( !isset( $params['target'] ) )
$this->dieUsageMsg( array( 'missingparam', 'target' ) );
@@ -79,6 +76,10 @@ class ApiEmailUser extends ApiBase {
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'target' => null,
@@ -112,7 +113,7 @@ class ApiEmailUser extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiEmailUser.php 41269 2008-09-25 21:39:36Z catrope $';
+ return __CLASS__ . ': $Id: ApiEmailUser.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
\ No newline at end of file
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index 109b6552..0859232e 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -89,7 +89,7 @@ class ApiFeedWatchlist extends ApiBase {
$data = $module->getResultData();
$feedItems = array ();
- foreach ($data['query']['watchlist'] as $info) {
+ foreach ((array)$data['query']['watchlist'] as $info) {
$feedItems[] = $this->createFeedItem($info);
}
@@ -175,6 +175,6 @@ class ApiFeedWatchlist extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFeedWatchlist.php 35098 2008-05-20 17:13:28Z ialex $';
+ return __CLASS__ . ': $Id: ApiFeedWatchlist.php 46848 2009-02-05 15:31:06Z catrope $';
}
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 9efbbbe0..cc7434c6 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -38,9 +38,11 @@ abstract class ApiFormatBase extends ApiBase {
private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared;
/**
- * Create a new instance of the formatter.
- * If the format name ends with 'fm', wrap its output in the proper HTML.
- */
+ * Constructor
+ * If $format ends with 'fm', pretty-print the output in HTML.
+ * @param $main ApiMain
+ * @param $format string Format name
+ */
public function __construct($main, $format) {
parent :: __construct($main, $format);
@@ -61,9 +63,8 @@ abstract class ApiFormatBase extends ApiBase {
public abstract function getMimeType();
/**
- * If formatter outputs data results as is, the results must first be sanitized.
- * An XML formatter on the other hand uses special tags, such as "_element" for special handling,
- * and thus needs to override this function to return true.
+ * Whether this formatter needs raw data such as _element tags
+ * @return bool
*/
public function getNeedsRawData() {
return false;
@@ -71,36 +72,40 @@ abstract class ApiFormatBase extends ApiBase {
/**
* Get the internal format name
+ * @return string
*/
public function getFormat() {
return $this->mFormat;
}
/**
- * Specify whether or not ampersands should be escaped to '&amp;' when rendering. This
- * should only be set to true for the help message when rendered in the default (xmlfm)
- * format. This is a temporary special-case fix that should be removed once the help
- * has been reworked to use a fully html interface.
+ * Specify whether or not sequences like &amp;quot; should be unescaped
+ * to &quot; . This should only be set to true for the help message
+ * when rendered in the default (xmlfm) format. This is a temporary
+ * special-case fix that should be removed once the help has been
+ * reworked to use a fully HTML interface.
*
- * @param boolean Whether or not ampersands should be escaped.
+ * @param $b bool Whether or not ampersands should be escaped.
*/
public function setUnescapeAmps ( $b ) {
$this->mUnescapeAmps = $b;
}
/**
- * Returns true when an HTML filtering printer should be used.
+ * Returns true when the HTML pretty-printer should be used.
* The default implementation assumes that formats ending with 'fm'
* should be formatted in HTML.
+ * @return bool
*/
public function getIsHtml() {
return $this->mIsHtml;
}
/**
- * Initialize the printer function and prepares the output headers, etc.
+ * 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
*/
function initPrinter($isError) {
$isHtml = $this->getIsHtml();
@@ -167,8 +172,10 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
}
/**
- * The main format printing function. Call it to output the result string to the user.
- * This function will automatically output HTML when format name ends in 'fm'.
+ * The main format printing function. Call it to output the result
+ * string to the user. This function will automatically output HTML
+ * when format name ends in 'fm'.
+ * @param $text string
*/
public function printText($text) {
if ($this->getIsHtml())
@@ -188,15 +195,18 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
}
/**
- * Says pretty-printer that it should use *bold* and $italics$ formatting
- */
+ * Sets whether the pretty-printer should format *bold* and $italics$
+ * @param $help bool
+ */
public function setHelp( $help = true ) {
$this->mHelp = true;
}
/**
- * Prety-print various elements in HTML format, such as xml tags and URLs.
- * This method also replaces any '<' with &lt;
+ * Prety-print various elements in HTML format, such as xml tags and
+ * URLs. This method also escapes characters like <
+ * @param $text string
+ * @return string
*/
protected function formatHTML($text) {
global $wgUrlProtocols;
@@ -209,14 +219,14 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
// identify URLs
$protos = implode("|", $wgUrlProtocols);
# This regex hacks around bug 13218 (&quot; included in the URL)
- $text = preg_replace("#(($protos).*?)(&quot;)?([ \\'\"()<\n])#", '<a href="\\1">\\1</a>\\3\\4', $text);
+ $text = preg_replace("#(($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 = ereg_replace("\\*[^<>\n]+\\*", '<b>\\0</b>', $text);
+ $text = preg_replace("#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text);
// make strings inside $ italic
- $text = ereg_replace("\\$[^<>\n]+\\$", '<b><i>\\0</i></b>', $text);
+ $text = preg_replace("#\\$[^<>\n]+\\$#", '<b><i>\\0</i></b>', $text);
}
/* Temporary fix for bad links in help messages. As a special case,
@@ -229,9 +239,6 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
return $text;
}
- /**
- * Returns usage examples for this format.
- */
protected function getExamples() {
return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName();
}
@@ -241,7 +248,7 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
}
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiFormatBase.php 43470 2008-11-14 00:30:34Z tstarling $';
+ return __CLASS__ . ': $Id: ApiFormatBase.php 48521 2009-03-18 19:25:29Z ialex $';
}
}
@@ -256,14 +263,21 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
}
/**
- * Call this method to initialize output data. See self::execute()
+ * 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
*/
public static function setResult($result, $feed, $feedItems) {
// Store output in the Result data.
// This way we can check during execution if any error has occured
- $data = & $result->getData();
- $data['_feed'] = $feed;
- $data['_feeditems'] = $feedItems;
+ // Disable size checking for this because we can't continue
+ // cleanly; size checking would cause more problems than it'd
+ // solve
+ $result->disableSizeCheck();
+ $result->addValue(null, '_feed', $feed);
+ $result->addValue(null, '_feeditems', $feedItems);
+ $result->enableSizeCheck();
}
/**
@@ -282,8 +296,8 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
/**
* This class expects the result data to be in a custom format set by self::setResult()
- * $result['_feed'] - an instance of one of the $wgFeedClasses classes
- * $result['_feeditems'] - an array of FeedItem instances
+ * $result['_feed'] - an instance of one of the $wgFeedClasses classes
+ * $result['_feeditems'] - an array of FeedItem instances
*/
public function execute() {
$data = $this->getResultData();
@@ -302,6 +316,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatBase.php 43470 2008-11-14 00:30:34Z tstarling $';
+ return __CLASS__ . ': $Id: ApiFormatBase.php 48521 2009-03-18 19:25:29Z ialex $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index 1d89eb18..7b5a02a4 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -61,7 +61,7 @@ class ApiFormatJson extends ApiFormatBase {
// Some versions of PHP have a broken json_encode, see PHP bug
// 46944. Test encoding an affected character (U+20000) to
// avoid this.
- if (!function_exists('json_encode') || $this->getIsHtml() || strtolower(json_encode("\xf0\xa0\x80\x80")) != '\ud840\udc00') {
+ if (!function_exists('json_encode') || $this->getIsHtml() || strtolower(json_encode("\xf0\xa0\x80\x80")) != '"\ud840\udc00"') {
$json = new Services_JSON();
$this->printText($prefix . $json->encode($this->getResultData(), $this->getIsHtml()) . $suffix);
} else {
@@ -89,6 +89,6 @@ class ApiFormatJson extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatJson.php 45682 2009-01-12 19:06:33Z raymond $';
+ return __CLASS__ . ': $Id: ApiFormatJson.php 48713 2009-03-23 19:58:07Z catrope $';
}
}
diff --git a/includes/api/ApiFormatJson_json.php b/includes/api/ApiFormatJson_json.php
index 4b29ff56..8cb3606d 100644
--- a/includes/api/ApiFormatJson_json.php
+++ b/includes/api/ApiFormatJson_json.php
@@ -45,14 +45,14 @@
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
-* @ingroup API
-* @author Michal Migurski <mike-json@teczno.com>
-* @author Matt Knapp <mdknapp[at]gmail[dot]com>
-* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
-* @copyright 2005 Michal Migurski
-* @version CVS: $Id: ApiFormatJson_json.php 45682 2009-01-12 19:06:33Z raymond $
-* @license http://www.opensource.org/licenses/bsd-license.php
-* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+* @ingroup API
+* @author Michal Migurski <mike-json@teczno.com>
+* @author Matt Knapp <mdknapp[at]gmail[dot]com>
+* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+* @copyright 2005 Michal Migurski
+* @version CVS: $Id: ApiFormatJson_json.php 45765 2009-01-15 10:18:44Z catrope $
+* @license http://www.opensource.org/licenses/bsd-license.php
+* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
/**
@@ -115,715 +115,715 @@ define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
*/
class Services_JSON
{
- /**
- * constructs a new JSON instance
- *
- * @param int $use object behavior flags; combine with boolean-OR
- *
- * possible values:
- * - SERVICES_JSON_LOOSE_TYPE: loose typing.
- * "{...}" syntax creates associative arrays
- * instead of objects in decode().
- * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
- * Values which can't be encoded (e.g. resources)
- * appear as NULL instead of throwing errors.
- * By default, a deeply-nested resource will
- * bubble up with an error, so all return values
- * from encode() should be checked with isError()
- */
- function Services_JSON($use = 0)
- {
- $this->use = $use;
- }
-
- /**
- * convert a string from one UTF-16 char to one UTF-8 char
- *
- * Normally should be handled by mb_convert_encoding, but
- * provides a slower PHP-only method for installations
- * that lack the multibye string extension.
- *
- * @param string $utf16 UTF-16 character
- * @return string UTF-8 character
- * @access private
- */
- function utf162utf8($utf16)
- {
- // oh please oh please oh please oh please oh please
- if(function_exists('mb_convert_encoding')) {
- return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
- }
-
- $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
-
- switch(true) {
- case ((0x7F & $bytes) == $bytes):
- // this case should never be reached, because we are in ASCII range
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0x7F & $bytes);
-
- case (0x07FF & $bytes) == $bytes:
- // return a 2-byte UTF-8 character
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0xC0 | (($bytes >> 6) & 0x1F))
- . chr(0x80 | ($bytes & 0x3F));
-
- case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16{2})) == 0xDC:
- // return a 4-byte UTF-8 character
- $char = ((($bytes & 0x03FF) << 10)
- | ((ord($utf16{2}) & 0x03) << 8)
- | ord($utf16{3}));
- $char += 0x10000;
- return chr(0xF0 | (($char >> 18) & 0x07))
- . chr(0x80 | (($char >> 12) & 0x3F))
- . chr(0x80 | (($char >> 6) & 0x3F))
- . chr(0x80 | ($char & 0x3F));
-
- case (0xFFFF & $bytes) == $bytes:
- // return a 3-byte UTF-8 character
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0xE0 | (($bytes >> 12) & 0x0F))
- . chr(0x80 | (($bytes >> 6) & 0x3F))
- . chr(0x80 | ($bytes & 0x3F));
- }
-
- // ignoring UTF-32 for now, sorry
- return '';
- }
-
- /**
- * convert a string from one UTF-8 char to one UTF-16 char
- *
- * Normally should be handled by mb_convert_encoding, but
- * provides a slower PHP-only method for installations
- * that lack the multibye string extension.
- *
- * @param string $utf8 UTF-8 character
- * @return string UTF-16 character
- * @access private
- */
- function utf82utf16($utf8)
- {
- // oh please oh please oh please oh please oh please
- if(function_exists('mb_convert_encoding')) {
- return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
- }
-
- switch(strlen($utf8)) {
- case 1:
- // this case should never be reached, because we are in ASCII range
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return $utf8;
-
- case 2:
- // return a UTF-16 character from a 2-byte UTF-8 char
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0x07 & (ord($utf8{0}) >> 2))
- . chr((0xC0 & (ord($utf8{0}) << 6))
- | (0x3F & ord($utf8{1})));
-
- case 3:
- // return a UTF-16 character from a 3-byte UTF-8 char
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr((0xF0 & (ord($utf8{0}) << 4))
- | (0x0F & (ord($utf8{1}) >> 2)))
- . chr((0xC0 & (ord($utf8{1}) << 6))
- | (0x7F & ord($utf8{2})));
-
- case 4:
- // return a UTF-16 surrogate pair from a 4-byte UTF-8 char
- if(ord($utf8{0}) > 0xF4) return ''; # invalid
- $char = ((0x1C0000 & (ord($utf8{0}) << 18))
- | (0x03F000 & (ord($utf8{1}) << 12))
- | (0x000FC0 & (ord($utf8{2}) << 6))
- | (0x00003F & ord($utf8{3})));
- if($char > 0x10FFFF) return ''; # invalid
- $char -= 0x10000;
- return chr(0xD8 | (($char >> 18) & 0x03))
- . chr(($char >> 10) & 0xFF)
- . chr(0xDC | (($char >> 8) & 0x03))
- . chr($char & 0xFF);
- }
-
- // ignoring UTF-32 for now, sorry
- return '';
- }
-
- /**
- * encodes an arbitrary variable into JSON format
- *
- * @param mixed $var any number, boolean, string, array, or object to be encoded.
- * see argument 1 to Services_JSON() above for array-parsing behavior.
- * if var is a strng, note that encode() always expects it
- * to be in ASCII or UTF-8 format!
- * @param bool $pretty pretty-print output with indents and newlines
- *
- * @return mixed JSON string representation of input var or an error if a problem occurs
- * @access public
- */
- function encode($var, $pretty=false)
- {
- $this->indent = 0;
- $this->pretty = $pretty;
- $this->nameValSeparator = $pretty ? ': ' : ':';
- return $this->encode2($var);
- }
-
- /**
- * encodes an arbitrary variable into JSON format
- *
- * @param mixed $var any number, boolean, string, array, or object to be encoded.
- * see argument 1 to Services_JSON() above for array-parsing behavior.
- * if var is a strng, note that encode() always expects it
- * to be in ASCII or UTF-8 format!
- *
- * @return mixed JSON string representation of input var or an error if a problem occurs
- * @access private
- */
- function encode2($var)
- {
- if ($this->pretty) {
- $close = "\n" . str_repeat("\t", $this->indent);
- $open = $close . "\t";
- $mid = ',' . $open;
- }
- else {
- $open = $close = '';
- $mid = ',';
- }
-
- switch (gettype($var)) {
- case 'boolean':
- return $var ? 'true' : 'false';
-
- case 'NULL':
- return 'null';
-
- case 'integer':
- return (int) $var;
-
- case 'double':
- case 'float':
- return (float) $var;
-
- case 'string':
- // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
- $ascii = '';
- $strlen_var = strlen($var);
-
- /*
- * Iterate over every character in the string,
- * escaping with a slash or encoding to UTF-8 where necessary
- */
- for ($c = 0; $c < $strlen_var; ++$c) {
-
- $ord_var_c = ord($var{$c});
-
- switch (true) {
- case $ord_var_c == 0x08:
- $ascii .= '\b';
- break;
- case $ord_var_c == 0x09:
- $ascii .= '\t';
- break;
- case $ord_var_c == 0x0A:
- $ascii .= '\n';
- break;
- case $ord_var_c == 0x0C:
- $ascii .= '\f';
- break;
- case $ord_var_c == 0x0D:
- $ascii .= '\r';
- break;
-
- case $ord_var_c == 0x22:
- case $ord_var_c == 0x2F:
- case $ord_var_c == 0x5C:
- // double quote, slash, slosh
- $ascii .= '\\'.$var{$c};
- break;
-
- case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
- // characters U-00000000 - U-0000007F (same as ASCII)
- $ascii .= $var{$c};
- break;
-
- case (($ord_var_c & 0xE0) == 0xC0):
- // characters U-00000080 - U-000007FF, mask 110XXXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
- $c += 1;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xF0) == 0xE0):
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}));
- $c += 2;
- $utf16 = $this->utf82utf16($char);
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
- break;
-
- case (($ord_var_c & 0xF8) == 0xF0):
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- // These will always return a surrogate pair
- $char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}),
- ord($var{$c + 3}));
- $c += 3;
- $utf16 = $this->utf82utf16($char);
- if($utf16 == '') {
- $ascii .= '\ufffd';
- } else {
- $utf16 = str_split($utf16, 2);
- $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1]));
- }
- break;
- }
- }
-
- return '"'.$ascii.'"';
-
- case 'array':
- /*
- * As per JSON spec if any array key is not an integer
- * we must treat the the whole array as an object. We
- * also try to catch a sparsely populated associative
- * array with numeric keys here because some JS engines
- * will create an array with empty indexes up to
- * max_index which can cause memory issues and because
- * the keys, which may be relevant, will be remapped
- * otherwise.
- *
- * As per the ECMA and JSON specification an object may
- * have any string as a property. Unfortunately due to
- * a hole in the ECMA specification if the key is a
- * ECMA reserved word or starts with a digit the
- * parameter is only accessible using ECMAScript's
- * bracket notation.
- */
-
- // treat as a JSON object
- if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
- $this->indent++;
- $properties = array_map(array($this, 'name_value'),
- array_keys($var),
- array_values($var));
- $this->indent--;
-
- foreach($properties as $property) {
- if(Services_JSON::isError($property)) {
- return $property;
- }
- }
-
- return '{' . $open . join($mid, $properties) . $close . '}';
- }
-
- // treat it like a regular array
- $this->indent++;
- $elements = array_map(array($this, 'encode2'), $var);
- $this->indent--;
-
- foreach($elements as $element) {
- if(Services_JSON::isError($element)) {
- return $element;
- }
- }
-
- return '[' . $open . join($mid, $elements) . $close . ']';
-
- case 'object':
- $vars = get_object_vars($var);
-
- $this->indent++;
- $properties = array_map(array($this, 'name_value'),
- array_keys($vars),
- array_values($vars));
- $this->indent--;
-
- foreach($properties as $property) {
- if(Services_JSON::isError($property)) {
- return $property;
- }
- }
-
- return '{' . $open . join($mid, $properties) . $close . '}';
-
- default:
- return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
- ? 'null'
- : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
- }
- }
-
- /**
- * array-walking function for use in generating JSON-formatted name-value pairs
- *
- * @param string $name name of key to use
- * @param mixed $value reference to an array element to be encoded
- *
- * @return string JSON-formatted name-value pair, like '"name":value'
- * @access private
- */
- function name_value($name, $value)
- {
- $encoded_value = $this->encode2($value);
-
- if(Services_JSON::isError($encoded_value)) {
- return $encoded_value;
- }
-
- return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value;
- }
-
- /**
- * reduce a string by removing leading and trailing comments and whitespace
- *
- * @param $str string string value to strip of comments and whitespace
- *
- * @return string string value stripped of comments and whitespace
- * @access private
- */
- function reduce_string($str)
- {
- $str = preg_replace(array(
-
- // eliminate single line comments in '// ...' form
- '#^\s*//(.+)$#m',
-
- // eliminate multi-line comments in '/* ... */' form, at start of string
- '#^\s*/\*(.+)\*/#Us',
-
- // eliminate multi-line comments in '/* ... */' form, at end of string
- '#/\*(.+)\*/\s*$#Us'
-
- ), '', $str);
-
- // eliminate extraneous space
- return trim($str);
- }
-
- /**
- * decodes a JSON string into appropriate variable
- *
- * @param string $str JSON-formatted string
- *
- * @return mixed number, boolean, string, array, or object
- * corresponding to given JSON input string.
- * See argument 1 to Services_JSON() above for object-output behavior.
- * Note that decode() always returns strings
- * in ASCII or UTF-8 format!
- * @access public
- */
- function decode($str)
- {
- $str = $this->reduce_string($str);
-
- switch (strtolower($str)) {
- case 'true':
- return true;
-
- case 'false':
- return false;
-
- case 'null':
- return null;
-
- default:
- $m = array();
-
- if (is_numeric($str)) {
- // Lookie-loo, it's a number
-
- // This would work on its own, but I'm trying to be
- // good about returning integers where appropriate:
- // return (float)$str;
-
- // Return float or int, as appropriate
- return ((float)$str == (integer)$str)
- ? (integer)$str
- : (float)$str;
-
- } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
- // STRINGS RETURNED IN UTF-8 FORMAT
- $delim = substr($str, 0, 1);
- $chrs = substr($str, 1, -1);
- $utf8 = '';
- $strlen_chrs = strlen($chrs);
-
- for ($c = 0; $c < $strlen_chrs; ++$c) {
-
- $substr_chrs_c_2 = substr($chrs, $c, 2);
- $ord_chrs_c = ord($chrs{$c});
-
- switch (true) {
- case $substr_chrs_c_2 == '\b':
- $utf8 .= chr(0x08);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\t':
- $utf8 .= chr(0x09);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\n':
- $utf8 .= chr(0x0A);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\f':
- $utf8 .= chr(0x0C);
- ++$c;
- break;
- case $substr_chrs_c_2 == '\r':
- $utf8 .= chr(0x0D);
- ++$c;
- break;
-
- case $substr_chrs_c_2 == '\\"':
- case $substr_chrs_c_2 == '\\\'':
- case $substr_chrs_c_2 == '\\\\':
- case $substr_chrs_c_2 == '\\/':
- if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
- ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
- $utf8 .= $chrs{++$c};
- }
- break;
-
- case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)):
- // escaped unicode surrogate pair
- $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
- . chr(hexdec(substr($chrs, ($c + 4), 2)))
- . chr(hexdec(substr($chrs, ($c + 8), 2)))
- . chr(hexdec(substr($chrs, ($c + 10), 2)));
- $utf8 .= $this->utf162utf8($utf16);
- $c += 11;
- break;
-
- case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
- // single, escaped unicode character
- $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
- . chr(hexdec(substr($chrs, ($c + 4), 2)));
- $utf8 .= $this->utf162utf8($utf16);
- $c += 5;
- break;
-
- case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
- $utf8 .= $chrs{$c};
- break;
-
- case ($ord_chrs_c & 0xE0) == 0xC0:
- // characters U-00000080 - U-000007FF, mask 110XXXXX
- //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 2);
- ++$c;
- break;
-
- case ($ord_chrs_c & 0xF0) == 0xE0:
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 3);
- $c += 2;
- break;
-
- case ($ord_chrs_c & 0xF8) == 0xF0:
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 4);
- $c += 3;
- break;
-
- case ($ord_chrs_c & 0xFC) == 0xF8:
- // characters U-00200000 - U-03FFFFFF, mask 111110XX
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 5);
- $c += 4;
- break;
-
- case ($ord_chrs_c & 0xFE) == 0xFC:
- // characters U-04000000 - U-7FFFFFFF, mask 1111110X
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $utf8 .= substr($chrs, $c, 6);
- $c += 5;
- break;
-
- }
-
- }
-
- return $utf8;
-
- } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
- // array, or object notation
-
- if ($str{0} == '[') {
- $stk = array(SERVICES_JSON_IN_ARR);
- $arr = array();
- } else {
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $stk = array(SERVICES_JSON_IN_OBJ);
- $obj = array();
- } else {
- $stk = array(SERVICES_JSON_IN_OBJ);
- $obj = new stdClass();
- }
- }
-
- array_push($stk, array('what' => SERVICES_JSON_SLICE,
- 'where' => 0,
- 'delim' => false));
-
- $chrs = substr($str, 1, -1);
- $chrs = $this->reduce_string($chrs);
-
- if ($chrs == '') {
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- return $arr;
-
- } else {
- return $obj;
-
- }
- }
-
- //print("\nparsing {$chrs}\n");
-
- $strlen_chrs = strlen($chrs);
-
- for ($c = 0; $c <= $strlen_chrs; ++$c) {
-
- $top = end($stk);
- $substr_chrs_c_2 = substr($chrs, $c, 2);
-
- if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
- // found a comma that is not inside a string, array, etc.,
- // OR we've reached the end of the character list
- $slice = substr($chrs, $top['where'], ($c - $top['where']));
- array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
- //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- // we are in an array, so just push an element onto the stack
- array_push($arr, $this->decode($slice));
-
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
- // we are in an object, so figure
- // out the property name and set an
- // element in an associative array,
- // for now
- $parts = array();
-
- if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
- // "name":value pair
- $key = $this->decode($parts[1]);
- $val = $this->decode($parts[2]);
-
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $obj[$key] = $val;
- } else {
- $obj->$key = $val;
- }
- } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
- // name:value pair, where name is unquoted
- $key = $parts[1];
- $val = $this->decode($parts[2]);
-
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
- $obj[$key] = $val;
- } else {
- $obj->$key = $val;
- }
- }
-
- }
-
- } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
- // found a quote, and we are not inside a string
- array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
- //print("Found start of string at {$c}\n");
-
- } elseif (($chrs{$c} == $top['delim']) &&
- ($top['what'] == SERVICES_JSON_IN_STR) &&
- (($chrs{$c - 1} != '\\') ||
- ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
- // found a quote, we're in a string, and it's not escaped
- array_pop($stk);
- //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
-
- } elseif (($chrs{$c} == '[') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a left-bracket, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
- //print("Found start of array at {$c}\n");
-
- } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
- // found a right-bracket, and we're in an array
- array_pop($stk);
- //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- } elseif (($chrs{$c} == '{') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a left-brace, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
- //print("Found start of object at {$c}\n");
-
- } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
- // found a right-brace, and we're in an object
- array_pop($stk);
- //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- } elseif (($substr_chrs_c_2 == '/*') &&
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
- // found a comment start, and we are in an array, object, or slice
- array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
- $c++;
- //print("Found start of comment at {$c}\n");
-
- } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
- // found a comment end, and we're in one now
- array_pop($stk);
- $c++;
-
- for ($i = $top['where']; $i <= $c; ++$i)
- $chrs = substr_replace($chrs, ' ', $i, 1);
-
- //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
- }
-
- }
-
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
- return $arr;
-
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
- return $obj;
-
- }
-
- }
- }
- }
-
- /**
- * @todo Ultimately, this should just call PEAR::isError()
- */
- function isError($data, $code = null)
- {
- if (class_exists('pear')) {
- return PEAR::isError($data, $code);
- } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
- is_subclass_of($data, 'services_json_error'))) {
- return true;
- }
-
- return false;
- }
+ /**
+ * constructs a new JSON instance
+ *
+ * @param int $use object behavior flags; combine with boolean-OR
+ *
+ * possible values:
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
+ * "{...}" syntax creates associative arrays
+ * instead of objects in decode().
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
+ * Values which can't be encoded (e.g. resources)
+ * appear as NULL instead of throwing errors.
+ * By default, a deeply-nested resource will
+ * bubble up with an error, so all return values
+ * from encode() should be checked with isError()
+ */
+ function Services_JSON($use = 0)
+ {
+ $this->use = $use;
+ }
+
+ /**
+ * convert a string from one UTF-16 char to one UTF-8 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf16 UTF-16 character
+ * @return string UTF-8 character
+ * @access private
+ */
+ function utf162utf8($utf16)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+ }
+
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+ switch(true) {
+ case ((0x7F & $bytes) == $bytes):
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x7F & $bytes);
+
+ case (0x07FF & $bytes) == $bytes:
+ // return a 2-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
+ . chr(0x80 | ($bytes & 0x3F));
+
+ case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16{2})) == 0xDC:
+ // return a 4-byte UTF-8 character
+ $char = ((($bytes & 0x03FF) << 10)
+ | ((ord($utf16{2}) & 0x03) << 8)
+ | ord($utf16{3}));
+ $char += 0x10000;
+ return chr(0xF0 | (($char >> 18) & 0x07))
+ . chr(0x80 | (($char >> 12) & 0x3F))
+ . chr(0x80 | (($char >> 6) & 0x3F))
+ . chr(0x80 | ($char & 0x3F));
+
+ case (0xFFFF & $bytes) == $bytes:
+ // return a 3-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
+ . chr(0x80 | ($bytes & 0x3F));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * convert a string from one UTF-8 char to one UTF-16 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ * @access private
+ */
+ function utf82utf16($utf8)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch(strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8{0}) >> 2))
+ . chr((0xC0 & (ord($utf8{0}) << 6))
+ | (0x3F & ord($utf8{1})));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8{0}) << 4))
+ | (0x0F & (ord($utf8{1}) >> 2)))
+ . chr((0xC0 & (ord($utf8{1}) << 6))
+ | (0x7F & ord($utf8{2})));
+
+ case 4:
+ // return a UTF-16 surrogate pair from a 4-byte UTF-8 char
+ if(ord($utf8{0}) > 0xF4) return ''; # invalid
+ $char = ((0x1C0000 & (ord($utf8{0}) << 18))
+ | (0x03F000 & (ord($utf8{1}) << 12))
+ | (0x000FC0 & (ord($utf8{2}) << 6))
+ | (0x00003F & ord($utf8{3})));
+ if($char > 0x10FFFF) return ''; # invalid
+ $char -= 0x10000;
+ return chr(0xD8 | (($char >> 18) & 0x03))
+ . chr(($char >> 10) & 0xFF)
+ . chr(0xDC | (($char >> 8) & 0x03))
+ . chr($char & 0xFF);
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ * @param bool $pretty pretty-print output with indents and newlines
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access public
+ */
+ function encode($var, $pretty=false)
+ {
+ $this->indent = 0;
+ $this->pretty = $pretty;
+ $this->nameValSeparator = $pretty ? ': ' : ':';
+ return $this->encode2($var);
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access private
+ */
+ function encode2($var)
+ {
+ if ($this->pretty) {
+ $close = "\n" . str_repeat("\t", $this->indent);
+ $open = $close . "\t";
+ $mid = ',' . $open;
+ }
+ else {
+ $open = $close = '';
+ $mid = ',';
+ }
+
+ switch (gettype($var)) {
+ case 'boolean':
+ return $var ? 'true' : 'false';
+
+ case 'NULL':
+ return 'null';
+
+ case 'integer':
+ return (int) $var;
+
+ case 'double':
+ case 'float':
+ return (float) $var;
+
+ case 'string':
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+ $ascii = '';
+ $strlen_var = strlen($var);
+
+ /*
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for ($c = 0; $c < $strlen_var; ++$c) {
+
+ $ord_var_c = ord($var{$c});
+
+ switch (true) {
+ case $ord_var_c == 0x08:
+ $ascii .= '\b';
+ break;
+ case $ord_var_c == 0x09:
+ $ascii .= '\t';
+ break;
+ case $ord_var_c == 0x0A:
+ $ascii .= '\n';
+ break;
+ case $ord_var_c == 0x0C:
+ $ascii .= '\f';
+ break;
+ case $ord_var_c == 0x0D:
+ $ascii .= '\r';
+ break;
+
+ case $ord_var_c == 0x22:
+ case $ord_var_c == 0x2F:
+ case $ord_var_c == 0x5C:
+ // double quote, slash, slosh
+ $ascii .= '\\'.$var{$c};
+ break;
+
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $var{$c};
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $c += 1;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}));
+ $c += 2;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ // These will always return a surrogate pair
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}));
+ $c += 3;
+ $utf16 = $this->utf82utf16($char);
+ if($utf16 == '') {
+ $ascii .= '\ufffd';
+ } else {
+ $utf16 = str_split($utf16, 2);
+ $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1]));
+ }
+ break;
+ }
+ }
+
+ return '"'.$ascii.'"';
+
+ case 'array':
+ /*
+ * As per JSON spec if any array key is not an integer
+ * we must treat the the whole array as an object. We
+ * also try to catch a sparsely populated associative
+ * array with numeric keys here because some JS engines
+ * will create an array with empty indexes up to
+ * max_index which can cause memory issues and because
+ * the keys, which may be relevant, will be remapped
+ * otherwise.
+ *
+ * As per the ECMA and JSON specification an object may
+ * have any string as a property. Unfortunately due to
+ * a hole in the ECMA specification if the key is a
+ * ECMA reserved word or starts with a digit the
+ * parameter is only accessible using ECMAScript's
+ * bracket notation.
+ */
+
+ // treat as a JSON object
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+ $this->indent++;
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($var),
+ array_values($var));
+ $this->indent--;
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . $open . join($mid, $properties) . $close . '}';
+ }
+
+ // treat it like a regular array
+ $this->indent++;
+ $elements = array_map(array($this, 'encode2'), $var);
+ $this->indent--;
+
+ foreach($elements as $element) {
+ if(Services_JSON::isError($element)) {
+ return $element;
+ }
+ }
+
+ return '[' . $open . join($mid, $elements) . $close . ']';
+
+ case 'object':
+ $vars = get_object_vars($var);
+
+ $this->indent++;
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($vars),
+ array_values($vars));
+ $this->indent--;
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . $open . join($mid, $properties) . $close . '}';
+
+ default:
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+ ? 'null'
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+ }
+ }
+
+ /**
+ * array-walking function for use in generating JSON-formatted name-value pairs
+ *
+ * @param string $name name of key to use
+ * @param mixed $value reference to an array element to be encoded
+ *
+ * @return string JSON-formatted name-value pair, like '"name":value'
+ * @access private
+ */
+ function name_value($name, $value)
+ {
+ $encoded_value = $this->encode2($value);
+
+ if(Services_JSON::isError($encoded_value)) {
+ return $encoded_value;
+ }
+
+ return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value;
+ }
+
+ /**
+ * reduce a string by removing leading and trailing comments and whitespace
+ *
+ * @param $str string string value to strip of comments and whitespace
+ *
+ * @return string string value stripped of comments and whitespace
+ * @access private
+ */
+ function reduce_string($str)
+ {
+ $str = preg_replace(array(
+
+ // eliminate single line comments in '// ...' form
+ '#^\s*//(.+)$#m',
+
+ // eliminate multi-line comments in '/* ... */' form, at start of string
+ '#^\s*/\*(.+)\*/#Us',
+
+ // eliminate multi-line comments in '/* ... */' form, at end of string
+ '#/\*(.+)\*/\s*$#Us'
+
+ ), '', $str);
+
+ // eliminate extraneous space
+ return trim($str);
+ }
+
+ /**
+ * decodes a JSON string into appropriate variable
+ *
+ * @param string $str JSON-formatted string
+ *
+ * @return mixed number, boolean, string, array, or object
+ * corresponding to given JSON input string.
+ * See argument 1 to Services_JSON() above for object-output behavior.
+ * Note that decode() always returns strings
+ * in ASCII or UTF-8 format!
+ * @access public
+ */
+ function decode($str)
+ {
+ $str = $this->reduce_string($str);
+
+ switch (strtolower($str)) {
+ case 'true':
+ return true;
+
+ case 'false':
+ return false;
+
+ case 'null':
+ return null;
+
+ default:
+ $m = array();
+
+ if (is_numeric($str)) {
+ // Lookie-loo, it's a number
+
+ // This would work on its own, but I'm trying to be
+ // good about returning integers where appropriate:
+ // return (float)$str;
+
+ // Return float or int, as appropriate
+ return ((float)$str == (integer)$str)
+ ? (integer)$str
+ : (float)$str;
+
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+ // STRINGS RETURNED IN UTF-8 FORMAT
+ $delim = substr($str, 0, 1);
+ $chrs = substr($str, 1, -1);
+ $utf8 = '';
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+ $ord_chrs_c = ord($chrs{$c});
+
+ switch (true) {
+ case $substr_chrs_c_2 == '\b':
+ $utf8 .= chr(0x08);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\t':
+ $utf8 .= chr(0x09);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\n':
+ $utf8 .= chr(0x0A);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\f':
+ $utf8 .= chr(0x0C);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\r':
+ $utf8 .= chr(0x0D);
+ ++$c;
+ break;
+
+ case $substr_chrs_c_2 == '\\"':
+ case $substr_chrs_c_2 == '\\\'':
+ case $substr_chrs_c_2 == '\\\\':
+ case $substr_chrs_c_2 == '\\/':
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+ $utf8 .= $chrs{++$c};
+ }
+ break;
+
+ case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)):
+ // escaped unicode surrogate pair
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+ . chr(hexdec(substr($chrs, ($c + 4), 2)))
+ . chr(hexdec(substr($chrs, ($c + 8), 2)))
+ . chr(hexdec(substr($chrs, ($c + 10), 2)));
+ $utf8 .= $this->utf162utf8($utf16);
+ $c += 11;
+ break;
+
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+ // single, escaped unicode character
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
+ $utf8 .= $this->utf162utf8($utf16);
+ $c += 5;
+ break;
+
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+ $utf8 .= $chrs{$c};
+ break;
+
+ case ($ord_chrs_c & 0xE0) == 0xC0:
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 2);
+ ++$c;
+ break;
+
+ case ($ord_chrs_c & 0xF0) == 0xE0:
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 3);
+ $c += 2;
+ break;
+
+ case ($ord_chrs_c & 0xF8) == 0xF0:
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 4);
+ $c += 3;
+ break;
+
+ case ($ord_chrs_c & 0xFC) == 0xF8:
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 5);
+ $c += 4;
+ break;
+
+ case ($ord_chrs_c & 0xFE) == 0xFC:
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 6);
+ $c += 5;
+ break;
+
+ }
+
+ }
+
+ return $utf8;
+
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+ // array, or object notation
+
+ if ($str{0} == '[') {
+ $stk = array(SERVICES_JSON_IN_ARR);
+ $arr = array();
+ } else {
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = array();
+ } else {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = new stdClass();
+ }
+ }
+
+ array_push($stk, array( 'what' => SERVICES_JSON_SLICE,
+ 'where' => 0,
+ 'delim' => false));
+
+ $chrs = substr($str, 1, -1);
+ $chrs = $this->reduce_string($chrs);
+
+ if ($chrs == '') {
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } else {
+ return $obj;
+
+ }
+ }
+
+ //print("\nparsing {$chrs}\n");
+
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+ $top = end($stk);
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+ // found a comma that is not inside a string, array, etc.,
+ // OR we've reached the end of the character list
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ // we are in an array, so just push an element onto the stack
+ array_push($arr, $this->decode($slice));
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ // we are in an object, so figure
+ // out the property name and set an
+ // element in an associative array,
+ // for now
+ $parts = array();
+
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // "name":value pair
+ $key = $this->decode($parts[1]);
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // name:value pair, where name is unquoted
+ $key = $parts[1];
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ }
+
+ }
+
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+ // found a quote, and we are not inside a string
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+ //print("Found start of string at {$c}\n");
+
+ } elseif (($chrs{$c} == $top['delim']) &&
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
+ (($chrs{$c - 1} != '\\') ||
+ ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
+ // found a quote, we're in a string, and it's not escaped
+ array_pop($stk);
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '[') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-bracket, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+ //print("Found start of array at {$c}\n");
+
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+ // found a right-bracket, and we're in an array
+ array_pop($stk);
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '{') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-brace, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+ //print("Found start of object at {$c}\n");
+
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+ // found a right-brace, and we're in an object
+ array_pop($stk);
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($substr_chrs_c_2 == '/*') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a comment start, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+ $c++;
+ //print("Found start of comment at {$c}\n");
+
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+ // found a comment end, and we're in one now
+ array_pop($stk);
+ $c++;
+
+ for ($i = $top['where']; $i <= $c; ++$i)
+ $chrs = substr_replace($chrs, ' ', $i, 1);
+
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ }
+
+ }
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ return $obj;
+
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @todo Ultimately, this should just call PEAR::isError()
+ */
+ function isError($data, $code = null)
+ {
+ if (class_exists('pear')) {
+ return PEAR::isError($data, $code);
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+ is_subclass_of($data, 'services_json_error'))) {
+ return true;
+ }
+
+ return false;
+ }
}
@@ -831,31 +831,31 @@ class Services_JSON
/// @cond
if (class_exists('PEAR_Error')) {
- /**
- * @ingroup API
- */
- class Services_JSON_Error extends PEAR_Error
- {
- function Services_JSON_Error($message = 'unknown error', $code = null,
- $mode = null, $options = null, $userinfo = null)
- {
- parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
- }
- }
+ /**
+ * @ingroup API
+ */
+ class Services_JSON_Error extends PEAR_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+ }
+ }
} else {
/// @endcond
- /**
- * @todo Ultimately, this class shall be descended from PEAR_Error
- * @ingroup API
- */
- class Services_JSON_Error
- {
- function Services_JSON_Error($message = 'unknown error', $code = null,
- $mode = null, $options = null, $userinfo = null)
- {
-
- }
- }
+ /**
+ * @todo Ultimately, this class shall be descended from PEAR_Error
+ * @ingroup API
+ */
+ class Services_JSON_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+
+ }
+ }
}
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
new file mode 100644
index 00000000..51025448
--- /dev/null
+++ b/includes/api/ApiFormatRaw.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * Created on Feb 2, 2009
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiFormatBase.php');
+}
+
+/**
+ * Formatter that spits out anything you like with any desired MIME type
+ * @ingroup API
+ */
+class ApiFormatRaw extends ApiFormatBase {
+
+ /**
+ * Constructor
+ * @param $main ApiMain object
+ * @param $errorFallback Formatter object to fall back on for errors
+ */
+ public function __construct($main, $errorFallback) {
+ parent :: __construct($main, 'raw');
+ $this->mErrorFallback = $errorFallback;
+ }
+
+ public function getMimeType() {
+ $data = $this->getResultData();
+ if(isset($data['error']))
+ return $this->mErrorFallback->getMimeType();
+ if(!isset($data['mime']))
+ ApiBase::dieDebug(__METHOD__, "No MIME type set for raw formatter");
+ return $data['mime'];
+ }
+
+ public function execute() {
+ $data = $this->getResultData();
+ if(isset($data['error']))
+ {
+ $this->mErrorFallback->execute();
+ return;
+ }
+ if(!isset($data['text']))
+ ApiBase::dieDebug(__METHOD__, "No text given for raw formatter");
+ $this->printText($data['text']);
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatRaw.php 48629 2009-03-20 11:40:54Z catrope $';
+ }
+}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index e741c16d..a716373d 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -42,7 +42,13 @@ class ApiFormatWddx extends ApiFormatBase {
}
public function execute() {
- if (function_exists('wddx_serialize_value') && !$this->getIsHtml()) {
+ // Some versions of PHP have a broken wddx_serialize_value, see
+ // PHP bug 45314. Test encoding an affected character (U+00A0)
+ // to avoid this.
+ $expected = "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>";
+ if (function_exists('wddx_serialize_value')
+ && !$this->getIsHtml()
+ && wddx_serialize_value("\xc2\xa0") == $expected) {
$this->printText(wddx_serialize_value($this->getResultData()));
} else {
// Don't do newlines and indentation if we weren't asked
@@ -60,8 +66,8 @@ class ApiFormatWddx extends ApiFormatBase {
}
/**
- * Recursivelly go through the object and output its data in WDDX format.
- */
+ * Recursively go through the object and output its data in WDDX format.
+ */
function slowWddxPrinter($elemValue, $indent = 0) {
$indstr = ($this->getIsHtml() ? "" : str_repeat(' ', $indent));
$indstr2 = ($this->getIsHtml() ? "" : str_repeat(' ', $indent + 2));
@@ -109,6 +115,6 @@ class ApiFormatWddx extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatWddx.php 44588 2008-12-14 19:14:21Z demon $';
+ return __CLASS__ . ': $Id: ApiFormatWddx.php 48716 2009-03-23 20:06:16Z catrope $';
}
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 7ff57324..35b412c9 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -89,6 +89,11 @@ class ApiFormatXml extends ApiFormatBase {
if ($this->mDoubleQuote)
$subElemContent = $this->doubleQuote($subElemContent);
unset ($elemValue['*']);
+
+ // Add xml:space="preserve" to the
+ // element so XML parsers will leave
+ // whitespace in the content alone
+ $elemValue['xml:space'] = 'preserve';
} else {
$subElemContent = null;
}
@@ -106,14 +111,6 @@ class ApiFormatXml extends ApiFormatBase {
if (is_string($subElemValue) && $this->mDoubleQuote)
$subElemValue = $this->doubleQuote($subElemValue);
- // Replace spaces with underscores
- $newSubElemId = str_replace(' ', '_', $subElemId);
- if($newSubElemId != $subElemId) {
- $elemValue[$newSubElemId] = $subElemValue;
- unset($elemValue[$subElemId]);
- $subElemId = $newSubElemId;
- }
-
if (gettype($subElemId) === 'integer') {
$indElements[] = $subElemValue;
unset ($elemValue[$subElemId]);
@@ -175,6 +172,6 @@ class ApiFormatXml extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatXml.php 44588 2008-12-14 19:14:21Z demon $';
+ return __CLASS__ . ': $Id: ApiFormatXml.php 50217 2009-05-05 13:12:16Z tstarling $';
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index 4ccb5acf..c001a7dc 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -50,6 +50,10 @@ class ApiHelp extends ApiBase {
return false;
}
+ public function isReadMode() {
+ return false;
+ }
+
public function getDescription() {
return array (
'Display this help screen.'
@@ -57,6 +61,6 @@ class ApiHelp extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiHelp.php 35098 2008-05-20 17:13:28Z ialex $';
+ return __CLASS__ . ': $Id: ApiHelp.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
new file mode 100644
index 00000000..4b1518bb
--- /dev/null
+++ b/includes/api/ApiImport.php
@@ -0,0 +1,179 @@
+<?php
+
+/*
+ * Created on Feb 4, 2009
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+/**
+ * API module that imports an XML file like Special:Import does
+ *
+ * @ingroup API
+ */
+class ApiImport extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ }
+
+ public function execute() {
+ global $wgUser;
+ if(!$wgUser->isAllowed('import'))
+ $this->dieUsageMsg(array('cantimport'));
+ $params = $this->extractRequestParams();
+ if(!isset($params['token']))
+ $this->dieUsageMsg(array('missingparam', 'token'));
+ if(!$wgUser->matchEditToken($params['token']))
+ $this->dieUsageMsg(array('sessionfailure'));
+
+ $source = null;
+ $isUpload = false;
+ if(isset($params['interwikisource']))
+ {
+ if(!isset($params['interwikipage']))
+ $this->dieUsageMsg(array('missingparam', 'interwikipage'));
+ $source = ImportStreamSource::newFromInterwiki(
+ $params['interwikisource'],
+ $params['interwikipage'],
+ $params['fullhistory'],
+ $params['templates']);
+ }
+ else
+ {
+ $isUpload = true;
+ if(!$wgUser->isAllowed('importupload'))
+ $this->dieUsageMsg(array('cantimport-upload'));
+ $source = ImportStreamSource::newFromUpload('xml');
+ }
+ if($source instanceof WikiErrorMsg)
+ $this->dieUsageMsg(array_merge(
+ array($source->getMessageKey()),
+ $source->getMessageArgs()));
+ else if(WikiError::isError($source))
+ // This shouldn't happen
+ $this->dieUsageMsg(array('import-unknownerror', $source->getMessage()));
+
+ $importer = new WikiImporter($source);
+ if(isset($params['namespace']))
+ $importer->setTargetNamespace($params['namespace']);
+ $reporter = new ApiImportReporter($importer, $isUpload,
+ $params['interwikisource'],
+ $params['summary']);
+
+ $result = $importer->doImport();
+ if($result instanceof WikiXmlError)
+ $this->dieUsageMsg(array('import-xml-error',
+ $result->mLine,
+ $result->mColumn,
+ $result->mByte . $result->mContext,
+ xml_error_string($result->mXmlError)));
+ else if(WikiError::isError($result))
+ // This shouldn't happen
+ $this->dieUsageMsg(array('import-unknownerror', $result->getMessage()));
+ $resultData = $reporter->getData();
+ $this->getResult()->setIndexedTagName($resultData, 'page');
+ $this->getResult()->addValue(null, $this->getModuleName(), $resultData);
+ }
+
+ public function mustBePosted() { return true; }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ global $wgImportSources;
+ return array (
+ 'token' => null,
+ 'summary' => null,
+ 'xml' => null,
+ 'interwikisource' => array(
+ ApiBase :: PARAM_TYPE => $wgImportSources
+ ),
+ 'interwikipage' => null,
+ 'fullhistory' => false,
+ 'templates' => false,
+ 'namespace' => array(
+ ApiBase :: PARAM_TYPE => 'namespace'
+ )
+ );
+ }
+
+ public function getParamDescription() {
+ return array (
+ 'token' => 'Import token obtained through prop=info',
+ 'summary' => 'Import summary',
+ 'xml' => 'Uploaded XML file',
+ 'interwikisource' => 'For interwiki imports: wiki to import from',
+ 'interwikipage' => 'For interwiki imports: page to import',
+ 'fullhistory' => 'For interwiki imports: import the full history, not just the current version',
+ 'templates' => 'For interwiki imports: import all included templates as well',
+ 'namespace' => 'For interwiki imports: import to this namespace',
+ );
+ }
+
+ public function getDescription() {
+ return array (
+ 'Import a page from another wiki, or an XML file'
+ );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history:',
+ ' api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory&token=123ABC',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiImport.php 48091 2009-03-06 13:49:44Z catrope $';
+ }
+}
+
+/**
+ * Import reporter for the API
+ * @ingroup API
+ */
+class ApiImportReporter extends ImportReporter {
+ private $mResultArr = array();
+
+ function reportPage($title, $origTitle, $revisionCount, $successCount)
+ {
+ // Add a result entry
+ $r = array();
+ ApiQueryBase::addTitleInfo($r, $title);
+ $r['revisions'] = intval($successCount);
+ $this->mResultArr[] = $r;
+
+ // Piggyback on the parent to do the logging
+ parent::reportPage($title, $origTitle, $revisionCount, $successCount);
+ }
+
+ function getData()
+ {
+ return $this->mResultArr;
+ }
+}
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 43b30f7c..bc477e1d 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -82,7 +82,7 @@ class ApiLogin extends ApiBase {
wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
$result['result'] = 'Success';
- $result['lguserid'] = $wgUser->getId();
+ $result['lguserid'] = intval($wgUser->getId());
$result['lgusername'] = $wgUser->getName();
$result['lgtoken'] = $wgUser->getToken();
$result['cookieprefix'] = $wgCookiePrefix;
@@ -114,7 +114,7 @@ class ApiLogin extends ApiBase {
case LoginForm :: THROTTLED :
global $wgPasswordAttemptThrottle;
$result['result'] = 'Throttled';
- $result['wait'] = $wgPasswordAttemptThrottle['seconds'];
+ $result['wait'] = intval($wgPasswordAttemptThrottle['seconds']);
break;
default :
ApiBase :: dieDebug(__METHOD__, "Unhandled case value: {$authRes}");
@@ -125,6 +125,10 @@ class ApiLogin extends ApiBase {
public function mustBePosted() { return true; }
+ public function isReadMode() {
+ return false;
+ }
+
public function getAllowedParams() {
return array (
'name' => null,
@@ -158,6 +162,6 @@ class ApiLogin extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiLogin.php 45275 2009-01-01 02:02:03Z simetrical $';
+ return __CLASS__ . ': $Id: ApiLogin.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index 8b178f6a..0af579ca 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -50,6 +50,10 @@ class ApiLogout extends ApiBase {
wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName) );
}
+ public function isReadMode() {
+ return false;
+ }
+
public function getAllowedParams() {
return array ();
}
@@ -71,6 +75,6 @@ class ApiLogout extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiLogout.php 43522 2008-11-15 01:23:39Z brion $';
+ return __CLASS__ . ': $Id: ApiLogout.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index 60d932be..ffdeb1e8 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -65,10 +65,9 @@ class ApiMain extends ApiBase {
'feedwatchlist' => 'ApiFeedWatchlist',
'help' => 'ApiHelp',
'paraminfo' => 'ApiParamInfo',
- 'purge' => 'ApiPurge',
- );
- private static $WriteModules = array (
+ // Write modules
+ 'purge' => 'ApiPurge',
'rollback' => 'ApiRollback',
'delete' => 'ApiDelete',
'undelete' => 'ApiUndelete',
@@ -80,6 +79,7 @@ class ApiMain extends ApiBase {
'emailuser' => 'ApiEmailUser',
'watch' => 'ApiWatch',
'patrol' => 'ApiPatrol',
+ 'import' => 'ApiImport',
);
/**
@@ -149,20 +149,10 @@ class ApiMain extends ApiBase {
wfDebug( "API: stripping user credentials for JSON callback\n" );
$wgUser = new User();
}
-
- if (!$wgUser->isAllowed('read')) {
- self::$Modules = array(
- 'login' => self::$Modules['login'],
- 'logout' => self::$Modules['logout'],
- 'help' => self::$Modules['help'],
- );
- }
}
- global $wgAPIModules, $wgEnableWriteAPI; // extension modules
+ global $wgAPIModules; // extension modules
$this->mModules = $wgAPIModules + self :: $Modules;
- if($wgEnableWriteAPI)
- $this->mModules += self::$WriteModules;
$this->mModuleNames = array_keys($this->mModules);
$this->mFormats = self :: $Formats;
@@ -200,22 +190,10 @@ class ApiMain extends ApiBase {
}
/**
- * This method will simply cause an error if the write mode was disabled
- * or if the current user doesn't have the right to use it
+ * Only kept for backwards compatibility
+ * @deprecated Use isWriteMode() instead
*/
- public function requestWriteMode() {
- global $wgUser;
- if (!$this->mEnableWrite)
- $this->dieUsage('Editing of this wiki through the API' .
- ' is disabled. Make sure the $wgEnableWriteAPI=true; ' .
- 'statement is included in the wiki\'s ' .
- 'LocalSettings.php file', 'noapiwrite');
- if (!$wgUser->isAllowed('writeapi'))
- $this->dieUsage('You\'re not allowed to edit this ' .
- 'wiki through the API', 'writeapidenied');
- if (wfReadOnly())
- $this->dieUsageMsg(array('readonlytext'));
- }
+ public function requestWriteMode() {}
/**
* Set how long the response should be cached.
@@ -360,9 +338,11 @@ class ApiMain extends ApiBase {
}
$this->getResult()->reset();
+ $this->getResult()->disableSizeCheck();
// Re-add the id
- if($this->mRequest->getCheck('requestid'))
- $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid'));
+ $requestid = $this->getParameter('requestid');
+ if(!is_null($requestid))
+ $this->getResult()->addValue(null, 'requestid', $requestid);
$this->getResult()->addValue(null, 'error', $errMessage);
return $errMessage['code'];
@@ -373,8 +353,9 @@ class ApiMain extends ApiBase {
*/
protected function executeAction() {
// First add the id to the top element
- if($this->mRequest->getCheck('requestid'))
- $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid'));
+ $requestid = $this->getParameter('requestid');
+ if(!is_null($requestid))
+ $this->getResult()->addValue(null, 'requestid', $requestid);
$params = $this->extractRequestParams();
@@ -398,14 +379,26 @@ class ApiMain extends ApiBase {
header( 'X-Database-Lag: ' . intval( $lag ) );
// XXX: should we return a 503 HTTP error code like wfMaxlagError() does?
if( $wgShowHostnames ) {
- ApiBase :: dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
+ $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
} else {
- ApiBase :: dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
+ $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' );
}
return;
}
}
+ global $wgUser;
+ if ($module->isReadMode() && !$wgUser->isAllowed('read'))
+ $this->dieUsageMsg(array('readrequired'));
+ if ($module->isWriteMode()) {
+ if (!$this->mEnableWrite)
+ $this->dieUsageMsg(array('writedisabled'));
+ if (!$wgUser->isAllowed('writeapi'))
+ $this->dieUsageMsg(array('writerequired'));
+ if (wfReadOnly())
+ $this->dieUsageMsg(array('readonlytext'));
+ }
+
if (!$this->mInternalMode) {
// Ignore mustBePosted() for internal calls
if($module->mustBePosted() && !$this->mRequest->wasPosted())
@@ -438,7 +431,7 @@ class ApiMain extends ApiBase {
* Print results using the current printer
*/
protected function printResult($isError) {
- $this->getResult()->cleanupUTF8();
+ $this->getResult()->cleanUpUTF8();
$printer = $this->mPrinter;
$printer->profileIn();
@@ -454,6 +447,10 @@ class ApiMain extends ApiBase {
$printer->closePrinter();
$printer->profileOut();
}
+
+ public function isReadMode() {
+ return false;
+ }
/**
* See ApiBase for description.
@@ -657,7 +654,7 @@ class ApiMain extends ApiBase {
public function getVersion() {
$vers = array ();
$vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
- $vers[] = __CLASS__ . ': $Id: ApiMain.php 45752 2009-01-14 21:36:57Z catrope $';
+ $vers[] = __CLASS__ . ': $Id: ApiMain.php 50834 2009-05-20 20:10:47Z catrope $';
$vers[] = ApiBase :: getBaseVersion();
$vers[] = ApiFormatBase :: getBaseVersion();
$vers[] = ApiQueryBase :: getBaseVersion();
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index 13b058c9..e22d0294 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -39,7 +39,6 @@ class ApiMove extends ApiBase {
public function execute() {
global $wgUser;
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
if(is_null($params['reason']))
$params['reason'] = '';
@@ -73,6 +72,7 @@ class ApiMove extends ApiBase {
$this->dieUsageMsg(array('invalidtitle', $params['to']));
$toTalk = $toTitle->getTalkPage();
+ # Move the page
$hookErr = null;
$retval = $fromTitle->moveTo($toTitle, true, $params['reason'], !$params['noredirect']);
if($retval !== true)
@@ -82,10 +82,9 @@ class ApiMove extends ApiBase {
if(!$params['noredirect'] || !$wgUser->isAllowed('suppressredirect'))
$r['redirectcreated'] = '';
+ # Move the talk page
if($params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage())
{
- // We need to move the talk page as well
- $toTalk = $toTitle->getTalkPage();
$retval = $fromTalk->moveTo($toTalk, true, $params['reason'], !$params['noredirect']);
if($retval === true)
{
@@ -101,6 +100,20 @@ class ApiMove extends ApiBase {
}
}
+ # Move subpages
+ if($params['movesubpages'])
+ {
+ $r['subpages'] = $this->moveSubpages($fromTitle, $toTitle,
+ $params['reason'], $params['noredirect']);
+ $this->getResult()->setIndexedTagName($r['subpages'], 'subpage');
+ if($params['movetalk'])
+ {
+ $r['subpages-talk'] = $this->moveSubpages($fromTalk, $toTalk,
+ $params['reason'], $params['noredirect']);
+ $this->getResult()->setIndexedTagName($r['subpages-talk'], 'subpage');
+ }
+ }
+
# Watch pages
if($params['watch'] || $wgUser->getOption('watchmoves'))
{
@@ -114,9 +127,37 @@ class ApiMove extends ApiBase {
}
$this->getResult()->addValue(null, $this->getModuleName(), $r);
}
+
+ public function moveSubpages($fromTitle, $toTitle, $reason, $noredirect)
+ {
+ $retval = array();
+ $success = $fromTitle->moveSubpages($toTitle, true, $reason, !$noredirect);
+ if(isset($success[0]))
+ return array('error' => $this->parseMsg($success));
+ else
+ {
+ // At least some pages could be moved
+ // Report each of them separately
+ foreach($success as $oldTitle => $newTitle)
+ {
+ $r = array('from' => $oldTitle);
+ if(is_array($newTitle))
+ $r['error'] = $this->parseMsg(reset($newTitle));
+ else
+ // Success
+ $r['to'] = $newTitle;
+ $retval[] = $r;
+ }
+ }
+ return $retval;
+ }
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'from' => null,
@@ -127,6 +168,7 @@ class ApiMove extends ApiBase {
'token' => null,
'reason' => null,
'movetalk' => false,
+ 'movesubpages' => false,
'noredirect' => false,
'watch' => false,
'unwatch' => false
@@ -141,6 +183,7 @@ class ApiMove extends ApiBase {
'token' => 'A move token previously retrieved through prop=info',
'reason' => 'Reason for the move (optional).',
'movetalk' => 'Move the talk page, if it exists.',
+ 'movesubpages' => 'Move subpages, if applicable',
'noredirect' => 'Don\'t create a redirect',
'watch' => 'Add the page and the redirect to your watchlist',
'unwatch' => 'Remove the page and the redirect from your watchlist'
@@ -160,6 +203,6 @@ class ApiMove extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiMove.php 47041 2009-02-09 14:39:41Z catrope $';
+ return __CLASS__ . ': $Id: ApiMove.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index 2da92059..8fc1f32b 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -42,10 +42,14 @@ class ApiOpenSearch extends ApiBase {
}
public function execute() {
+ global $wgEnableMWSuggest;
$params = $this->extractRequestParams();
$search = $params['search'];
$limit = $params['limit'];
$namespaces = $params['namespace'];
+ $suggest = $params['suggest'];
+ # $wgEnableMWSuggest hit incoming when $wgEnableMWSuggest is disabled
+ if( $suggest && !$wgEnableMWSuggest ) return;
// Open search results may be stored for a very long time
$this->getMain()->setCacheMaxAge(1200);
@@ -61,7 +65,7 @@ class ApiOpenSearch extends ApiBase {
public function getAllowedParams() {
return array (
'search' => null,
- 'limit' => array (
+ 'limit' => array(
ApiBase :: PARAM_DFLT => 10,
ApiBase :: PARAM_TYPE => 'limit',
ApiBase :: PARAM_MIN => 1,
@@ -73,6 +77,7 @@ class ApiOpenSearch extends ApiBase {
ApiBase :: PARAM_TYPE => 'namespace',
ApiBase :: PARAM_ISMULTI => true
),
+ 'suggest' => false,
);
}
@@ -81,6 +86,7 @@ class ApiOpenSearch extends ApiBase {
'search' => 'Search string',
'limit' => 'Maximum amount of results to return',
'namespace' => 'Namespaces to search',
+ 'suggest' => 'Do nothing if $wgEnableMWSuggest is false',
);
}
@@ -95,6 +101,6 @@ class ApiOpenSearch extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiOpenSearch.php 35098 2008-05-20 17:13:28Z ialex $';
+ return __CLASS__ . ': $Id: ApiOpenSearch.php 47188 2009-02-12 17:27:05Z catrope $';
}
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 54482e4b..6b9e90b8 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -30,13 +30,14 @@ if (!defined('MEDIAWIKI')) {
/**
* This class contains a list of pages that the client has requested.
- * Initially, when the client passes in titles=, pageids=, or revisions= parameter,
- * an instance of the ApiPageSet class will normalize titles,
- * determine if the pages/revisions exist, and prefetch any additional data page data requested.
+ * Initially, when the client passes in titles=, pageids=, or revisions=
+ * parameter, an instance of the ApiPageSet class will normalize titles,
+ * determine if the pages/revisions exist, and prefetch any additional page
+ * data requested.
*
- * When generator is used, the result of the generator will become the input for the
- * second instance of this class, and all subsequent actions will go use the second instance
- * for all their work.
+ * When a generator is used, the result of the generator will become the input
+ * for the second instance of this class, and all subsequent actions will use
+ * the second instance for all their work.
*
* @ingroup API
*/
@@ -52,6 +53,11 @@ class ApiPageSet extends ApiQueryBase {
private $mRequestedPageFields;
+ /**
+ * Constructor
+ * @param $query ApiQuery
+ * @param $resolveRedirects bool Whether redirects should be resolved
+ */
public function __construct($query, $resolveRedirects = false) {
parent :: __construct($query, 'query');
@@ -75,20 +81,38 @@ class ApiPageSet extends ApiQueryBase {
$this->mFakePageId = -1;
}
+ /**
+ * Check whether this PageSet is resolving redirects
+ * @return bool
+ */
public function isResolvingRedirects() {
return $this->mResolveRedirects;
}
+ /**
+ * Request an additional field from the page table. Must be called
+ * before execute()
+ * @param $fieldName string Field name
+ */
public function requestField($fieldName) {
$this->mRequestedPageFields[$fieldName] = null;
}
+ /**
+ * Get the value of a custom field previously requested through
+ * requestField()
+ * @param $fieldName string Field name
+ * @return mixed Field value
+ */
public function getCustomField($fieldName) {
return $this->mRequestedPageFields[$fieldName];
}
/**
- * Get fields that modules have requested from the page table
+ * Get the fields that have to be queried from the page table:
+ * the ones requested through requestField() and a few basic ones
+ * we always need
+ * @return array of field names
*/
public function getPageTableFields() {
// Ensure we get minimum required fields
@@ -99,12 +123,12 @@ class ApiPageSet extends ApiQueryBase {
'page_id' => null,
);
- // only store non-default fields
- $this->mRequestedPageFields = array_diff_key($this->mRequestedPageFields, $pageFlds);
-
if ($this->mResolveRedirects)
$pageFlds['page_is_redirect'] = null;
+ // only store non-default fields
+ $this->mRequestedPageFields = array_diff_key($this->mRequestedPageFields, $pageFlds);
+
$pageFlds = array_merge($pageFlds, $this->mRequestedPageFields);
return array_keys($pageFlds);
}
@@ -113,6 +137,7 @@ class ApiPageSet extends ApiQueryBase {
* Returns an array [ns][dbkey] => page_id for all requested titles.
* page_id is a unique negative number in case title was not found.
* Invalid titles will also have negative page IDs and will be in namespace 0
+ * @return array
*/
public function getAllTitlesByNamespace() {
return $this->mAllPages;
@@ -128,6 +153,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Returns the number of unique pages (not revisions) in the set.
+ * @return int
*/
public function getTitleCount() {
return count($this->mTitles);
@@ -143,6 +169,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Returns the number of found unique pages (not revisions) in the set.
+ * @return int
*/
public function getGoodTitleCount() {
return count($this->mGoodTitles);
@@ -175,7 +202,8 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Get a list of redirects when doing redirect resolution
+ * Get a list of redirect resolutions - maps a title to its redirect
+ * target.
* @return array prefixed_title (string) => prefixed_title (string)
*/
public function getRedirectTitles() {
@@ -183,8 +211,8 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Get a list of title normalizations - maps the title given
- * with its normalized version.
+ * Get a list of title normalizations - maps a title to its normalized
+ * version.
* @return array raw_prefixed_title (string) => prefixed_title (string)
*/
public function getNormalizedTitles() {
@@ -192,8 +220,8 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Get a list of interwiki titles - maps the title given
- * with to the interwiki prefix.
+ * Get a list of interwiki titles - maps a title to its interwiki
+ * prefix.
* @return array raw_prefixed_title (string) => interwiki_prefix (string)
*/
public function getInterwikiTitles() {
@@ -201,7 +229,7 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Get the list of revision IDs (requested with revids= parameter)
+ * Get the list of revision IDs (requested with the revids= parameter)
* @return array revID (int) => pageID (int)
*/
public function getRevisionIDs() {
@@ -217,14 +245,15 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Returns the number of revisions (requested with revids= parameter)
+ * Returns the number of revisions (requested with revids= parameter)\
+ * @return int
*/
public function getRevisionCount() {
return count($this->getRevisionIDs());
}
/**
- * Populate from the request parameters
+ * Populate the PageSet from the request parameters.
*/
public function execute() {
$this->profileIn();
@@ -267,7 +296,8 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Initialize PageSet from a list of Titles
+ * Populate this PageSet from a list of Titles
+ * @param $titles array of Title objects
*/
public function populateFromTitles($titles) {
$this->profileIn();
@@ -276,7 +306,8 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Initialize PageSet from a list of Page IDs
+ * Populate this PageSet from a list of page IDs
+ * @param $pageIDs array of page IDs
*/
public function populateFromPageIDs($pageIDs) {
$this->profileIn();
@@ -285,7 +316,9 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Initialize PageSet from a rowset returned from the database
+ * Populate this PageSet from a rowset returned from the database
+ * @param $db Database object
+ * @param $queryResult Query result object
*/
public function populateFromQueryResult($db, $queryResult) {
$this->profileIn();
@@ -294,17 +327,18 @@ class ApiPageSet extends ApiQueryBase {
}
/**
- * Initialize PageSet from a list of Revision IDs
+ * Populate this PageSet from a list of revision IDs
+ * @param $revIDs array of revision IDs
*/
public function populateFromRevisionIDs($revIDs) {
$this->profileIn();
- $revIDs = array_map('intval', $revIDs); // paranoia
$this->initFromRevIDs($revIDs);
$this->profileOut();
}
/**
* Extract all requested fields from the row received from the database
+ * @param $row Result row
*/
public function processDbRow($row) {
@@ -325,6 +359,9 @@ class ApiPageSet extends ApiQueryBase {
$fieldValues[$pageId] = $row-> $fieldName;
}
+ /**
+ * Resolve redirects, if applicable
+ */
public function finishPageSetGeneration() {
$this->profileIn();
$this->resolvePendingRedirects();
@@ -341,9 +378,11 @@ class ApiPageSet extends ApiQueryBase {
*
* Additionally, when resolving redirects:
* #3 If no more redirects left, stop.
- * #4 For each redirect, get its links from `pagelinks` table.
+ * #4 For each redirect, get its target from the `redirect` table.
* #5 Substitute the original LinkBatch object with the new list
* #6 Repeat from step #1
+ *
+ * @param $titles array of Title objects or strings
*/
private function initFromTitles($titles) {
@@ -357,7 +396,8 @@ class ApiPageSet extends ApiQueryBase {
// Get pageIDs data from the `page` table
$this->profileDBIn();
- $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
+ $res = $db->select('page', $this->getPageTableFields(), $set,
+ __METHOD__);
$this->profileDBOut();
// Hack: get the ns:titles stored in array(ns => array(titles)) format
@@ -367,6 +407,10 @@ class ApiPageSet extends ApiQueryBase {
$this->resolvePendingRedirects();
}
+ /**
+ * Does the same as initFromTitles(), but is based on page IDs instead
+ * @param $pageids array of page IDs
+ */
private function initFromPageIds($pageids) {
if(!count($pageids))
return;
@@ -375,12 +419,12 @@ class ApiPageSet extends ApiQueryBase {
$set = array (
'page_id' => $pageids
);
-
$db = $this->getDB();
// Get pageIDs data from the `page` table
$this->profileDBIn();
- $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
+ $res = $db->select('page', $this->getPageTableFields(), $set,
+ __METHOD__);
$this->profileDBOut();
$remaining = array_flip($pageids);
@@ -395,12 +439,11 @@ class ApiPageSet extends ApiQueryBase {
* and for each row create and store title object and save any extra fields requested.
* @param $db Database
* @param $res DB Query result
- * @param $remaining Array of either pageID or ns/title elements (optional).
+ * @param $remaining array 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.
* If true, treat $remaining as an array of [ns][title]
* If false, treat it as an array of [pageIDs]
- * @return Array of redirect IDs (only when resolving redirects)
*/
private function initFromQueryResult($db, $res, &$remaining = null, $processTitles = null) {
if (!is_null($remaining) && is_null($processTitles))
@@ -448,30 +491,36 @@ class ApiPageSet extends ApiQueryBase {
}
}
+ /**
+ * Does the same as initFromTitles(), but is based on revision IDs
+ * instead
+ * @param $revids array of revision IDs
+ */
private function initFromRevIDs($revids) {
if(!count($revids))
return;
+ $revids = array_map('intval', $revids); // paranoia
$db = $this->getDB();
$pageids = array();
$remaining = array_flip($revids);
- $tables = array('revision','page');
- $fields = array('rev_id','rev_page');
- $where = array('rev_deleted' => 0, 'rev_id' => $revids,'rev_page = page_id');
+ $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__ );
- while ( $row = $db->fetchObject( $res ) ) {
+ $res = $db->select($tables, $fields, $where, __METHOD__);
+ while ($row = $db->fetchObject($res)) {
$revid = intval($row->rev_id);
$pageid = intval($row->rev_page);
$this->mGoodRevIDs[$revid] = $pageid;
$pageids[$pageid] = '';
unset($remaining[$revid]);
}
- $db->freeResult( $res );
+ $db->freeResult($res);
$this->profileDBOut();
$this->mMissingRevIDs = array_keys($remaining);
@@ -480,6 +529,11 @@ class ApiPageSet extends ApiQueryBase {
$this->initFromPageIds(array_keys($pageids));
}
+ /**
+ * Resolve any redirects in the result if redirect resolution was
+ * requested. This function is called repeatedly until all redirects
+ * have been resolved.
+ */
private function resolvePendingRedirects() {
if($this->mResolveRedirects) {
@@ -498,7 +552,7 @@ class ApiPageSet extends ApiQueryBase {
break;
$set = $linkBatch->constructSet('page', $db);
- if(false === $set)
+ if($set === false)
break;
// Get pageIDs data from the `page` table
@@ -512,6 +566,13 @@ class ApiPageSet extends ApiQueryBase {
}
}
+ /**
+ * Get the targets of the pending redirects from the database
+ *
+ * Also creates entries in the redirect table for redirects that don't
+ * have one.
+ * @return LinkBatch
+ */
private function getRedirectTargets() {
$lb = new LinkBatch();
$db = $this->getDB();
@@ -562,7 +623,8 @@ class ApiPageSet extends ApiQueryBase {
* This method validates access rights for the title,
* and appends normalization values to the output.
*
- * @return LinkBatch of title objects.
+ * @param $titles array of Title objects or strings
+ * @return LinkBatch
*/
private function processTitlesArray($titles) {
@@ -592,9 +654,11 @@ class ApiPageSet extends ApiQueryBase {
$linkBatch->addObj($titleObj);
}
- // Make sure we remember the original title that was given to us
- // This way the caller can correlate new titles with the originally requested,
- // i.e. namespace is localized or capitalization is different
+ // Make sure we remember the original title that was
+ // given to us. This way the caller can correlate new
+ // titles with the originally requested when e.g. the
+ // namespace is localized or the capitalization is
+ // different
if (is_string($title) && $title !== $titleObj->getPrefixedText()) {
$this->mNormalizedTitles[$title] = $titleObj->getPrefixedText();
}
@@ -628,6 +692,6 @@ class ApiPageSet extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPageSet.php 45275 2009-01-01 02:02:03Z simetrical $';
+ return __CLASS__ . ': $Id: ApiPageSet.php 47424 2009-02-18 05:29:11Z werdna $';
}
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 2cf044cf..d710c206 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -41,6 +41,7 @@ class ApiParamInfo extends ApiBase {
// Get parameters
$params = $this->extractRequestParams();
$result = $this->getResult();
+ $queryObj = new ApiQuery($this->getMain(), 'query');
$r = array();
if(is_array($params['modules']))
{
@@ -61,7 +62,6 @@ class ApiParamInfo extends ApiBase {
}
if(is_array($params['querymodules']))
{
- $queryObj = new ApiQuery($this->getMain(), 'query');
$qmodArr = $queryObj->getModules();
foreach($params['querymodules'] as $qm)
{
@@ -77,6 +77,13 @@ class ApiParamInfo extends ApiBase {
}
$result->setIndexedTagName($r['querymodules'], 'module');
}
+ if($params['mainmodule'])
+ $r['mainmodule'] = $this->getClassInfo($this->getMain());
+ if($params['pagesetmodule'])
+ {
+ $pageSet = new ApiPageSet($queryObj);
+ $r['pagesetmodule'] = $this->getClassInfo($pageSet);
+ }
$result->addValue(null, $this->getModuleName(), $r);
}
@@ -86,6 +93,12 @@ class ApiParamInfo extends ApiBase {
$retval['classname'] = get_class($obj);
$retval['description'] = (is_array($obj->getDescription()) ? implode("\n", $obj->getDescription()) : $obj->getDescription());
$retval['prefix'] = $obj->getModulePrefix();
+ if($obj->isReadMode())
+ $retval['readrights'] = '';
+ if($obj->isWriteMode())
+ $retval['writerights'] = '';
+ if($obj->mustBePosted())
+ $retval['mustbeposted'] = '';
$allowedParams = $obj->getFinalParams();
if(!is_array($allowedParams))
return $retval;
@@ -140,6 +153,10 @@ class ApiParamInfo extends ApiBase {
return $retval;
}
+ public function isReadMode() {
+ return false;
+ }
+
public function getAllowedParams() {
return array (
'modules' => array(
@@ -147,7 +164,9 @@ class ApiParamInfo extends ApiBase {
),
'querymodules' => array(
ApiBase :: PARAM_ISMULTI => true
- )
+ ),
+ 'mainmodule' => false,
+ 'pagesetmodule' => false,
);
}
@@ -155,6 +174,8 @@ class ApiParamInfo extends ApiBase {
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)',
+ 'mainmodule' => 'Get information about the main (top-level) module as well',
+ 'pagesetmodule' => 'Get information about the pageset module (providing titles= and friends) as well',
);
}
@@ -169,6 +190,6 @@ class ApiParamInfo extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiParamInfo.php 41653 2008-10-04 15:03:03Z catrope $';
+ return __CLASS__ . ': $Id: ApiParamInfo.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index e221fb1d..8f4b70bf 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -105,7 +105,7 @@ class ApiParse extends ApiBase {
$p_result = $wgParser->parse($articleObj->getContent(), $titleObj, $popts);
global $wgUseParserCache;
if($wgUseParserCache)
- $pcache->save($p_result, $articleObj, $wgUser);
+ $pcache->save($p_result, $articleObj, $popts);
}
}
}
@@ -151,8 +151,12 @@ class ApiParse extends ApiBase {
$result_array['externallinks'] = array_keys($p_result->getExternalLinks());
if(isset($prop['sections']))
$result_array['sections'] = $p_result->getSections();
+ if(isset($prop['displaytitle']))
+ $result_array['displaytitle'] = $p_result->getDisplayTitle() ?
+ $p_result->getDisplayTitle() :
+ $titleObj->getPrefixedText();
if(!is_null($oldid))
- $result_array['revid'] = $oldid;
+ $result_array['revid'] = intval($oldid);
$result_mapping = array(
'redirects' => 'r',
@@ -223,7 +227,7 @@ class ApiParse extends ApiBase {
'redirects' => false,
'oldid' => null,
'prop' => array(
- ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid',
+ ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle',
ApiBase :: PARAM_ISMULTI => true,
ApiBase :: PARAM_TYPE => array(
'text',
@@ -234,7 +238,8 @@ class ApiParse extends ApiBase {
'images',
'externallinks',
'sections',
- 'revid'
+ 'revid',
+ 'displaytitle',
)
),
'pst' => false,
@@ -272,6 +277,6 @@ class ApiParse extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiParse.php 44858 2008-12-20 20:00:07Z catrope $';
+ return __CLASS__ . ': $Id: ApiParse.php 48544 2009-03-18 23:27:48Z aboostani $';
}
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index 08de87b0..2c9d1ecf 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -42,7 +42,6 @@ class ApiPatrol extends ApiBase {
*/
public function execute() {
global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol;
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
if(!isset($params['token']))
@@ -58,13 +57,17 @@ class ApiPatrol extends ApiBase {
$retval = RecentChange::markPatrolled($params['rcid']);
if($retval)
- $this->dieUsageMsg(current($retval));
+ $this->dieUsageMsg(reset($retval));
- $result = array('rcid' => $rc->getAttribute('rc_id'));
+ $result = array('rcid' => intval($rc->getAttribute('rc_id')));
ApiQueryBase::addTitleInfo($result, $rc->getTitle());
$this->getResult()->addValue(null, $this->getModuleName(), $result);
}
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'token' => null,
@@ -94,6 +97,6 @@ class ApiPatrol extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPatrol.php 42548 2008-10-25 14:04:43Z tstarling $';
+ return __CLASS__ . ': $Id: ApiPatrol.php 48122 2009-03-07 12:58:41Z catrope $';
}
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index 522d02b2..aad37066 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -38,7 +38,6 @@ class ApiProtect extends ApiBase {
public function execute() {
global $wgUser, $wgRestrictionTypes, $wgRestrictionLevels;
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
$titleObj = NULL;
@@ -59,7 +58,7 @@ class ApiProtect extends ApiBase {
$errors = $titleObj->getUserPermissionsErrors('protect', $wgUser);
if($errors)
// We don't care about multiple errors, just report one of them
- $this->dieUsageMsg(current($errors));
+ $this->dieUsageMsg(reset($errors));
$expiry = (array)$params['expiry'];
if(count($expiry) != count($params['protections']))
@@ -106,10 +105,12 @@ class ApiProtect extends ApiBase {
}
$cascade = $params['cascade'];
- if($titleObj->exists()) {
- $articleObj = new Article($titleObj);
+ $articleObj = new Article($titleObj);
+ if($params['watch'])
+ $articleObj->doWatch();
+ if($titleObj->exists())
$ok = $articleObj->updateRestrictions($protections, $params['reason'], $cascade, $expiryarray);
- } else
+ else
$ok = $titleObj->updateTitleProtection($protections['create'], $params['reason'], $expiryarray['create']);
if(!$ok)
// This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime?
@@ -125,6 +126,10 @@ class ApiProtect extends ApiBase {
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'title' => null,
@@ -138,7 +143,8 @@ class ApiProtect extends ApiBase {
ApiBase :: PARAM_DFLT => 'infinite',
),
'reason' => '',
- 'cascade' => false
+ 'cascade' => false,
+ 'watch' => false,
);
}
@@ -152,6 +158,7 @@ class ApiProtect extends ApiBase {
'reason' => 'Reason for (un)protecting (optional)',
'cascade' => array('Enable cascading protection (i.e. protect pages included in this page)',
'Ignored if not all protection levels are \'sysop\' or \'protect\''),
+ 'watch' => 'If set, add the page being (un)protected to your watchlist',
);
}
@@ -169,6 +176,6 @@ class ApiProtect extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiProtect.php 44426 2008-12-10 22:39:41Z catrope $';
+ return __CLASS__ . ': $Id: ApiProtect.php 48122 2009-03-07 12:58:41Z catrope $';
}
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index d7202a46..27d5cac6 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -74,6 +74,15 @@ class ApiPurge extends ApiBase {
$this->getResult()->addValue(null, $this->getModuleName(), $result);
}
+ public function mustBePosted() {
+ global $wgUser;
+ return $wgUser->isAnon();
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'titles' => array(
@@ -101,6 +110,6 @@ class ApiPurge extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPurge.php 41020 2008-09-19 00:21:03Z demon $';
+ return __CLASS__ . ': $Id: ApiPurge.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index 45a5667a..149e4082 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -29,13 +29,13 @@ if (!defined('MEDIAWIKI')) {
}
/**
- * This is the main query class. It behaves similar to ApiMain: based on the parameters given,
- * it will create a list of titles to work on (an instance of the ApiPageSet object)
- * instantiate and execute various property/list/meta modules,
- * and assemble all resulting data into a single ApiResult object.
+ * This is the main query class. It behaves similar to ApiMain: based on the
+ * parameters given, it will create a list of titles to work on (an ApiPageSet
+ * object), instantiate and execute various property/list/meta modules, and
+ * assemble all resulting data into a single ApiResult object.
*
- * In the generator mode, a generator will be first executed to populate a second ApiPageSet object,
- * and that object will be used for all subsequent modules.
+ * In generator mode, a generator will be executed first to populate a second
+ * ApiPageSet object, and that object will be used for all subsequent modules.
*
* @ingroup API
*/
@@ -80,6 +80,7 @@ class ApiQuery extends ApiBase {
'exturlusage' => 'ApiQueryExtLinksUsage',
'users' => 'ApiQueryUsers',
'random' => 'ApiQueryRandom',
+ 'protectedtitles' => 'ApiQueryProtectedTitles',
);
private $mQueryMetaModules = array (
@@ -111,6 +112,8 @@ class ApiQuery extends ApiBase {
/**
* 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
*/
private static function appendUserModules(&$modules, $newModules) {
if (is_array( $newModules )) {
@@ -122,6 +125,7 @@ class ApiQuery extends ApiBase {
/**
* Gets a default slave database connection object
+ * @return Database
*/
public function getDB() {
if (!isset ($this->mSlaveDB)) {
@@ -136,7 +140,11 @@ class ApiQuery extends ApiBase {
* Get the query database connection with the given name.
* 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 $db or $groups new values.
+ * 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
+ * @return Database
*/
public function getNamedDB($name, $db, $groups) {
if (!array_key_exists($name, $this->mNamedDB)) {
@@ -149,6 +157,7 @@ class ApiQuery extends ApiBase {
/**
* Gets the set of pages the user has requested (or generated)
+ * @return ApiPageSet
*/
public function getPageSet() {
return $this->mPageSet;
@@ -156,15 +165,26 @@ class ApiQuery extends ApiBase {
/**
* Get the array mapping module names to class names
+ * @return array(modulename => classname)
*/
function getModules() {
return array_merge($this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules);
}
+
+ public function getCustomPrinter() {
+ // If &exportnowrap is set, use the raw formatter
+ if ($this->getParameter('export') &&
+ $this->getParameter('exportnowrap'))
+ return new ApiFormatRaw($this->getMain(),
+ $this->getMain()->createPrinterByName('xml'));
+ else
+ return null;
+ }
/**
* Query execution happens in the following steps:
* #1 Create a PageSet object with any pages requested by the user
- * #2 If using generator, execute it to get a new PageSet object
+ * #2 If using a generator, execute it to get a new ApiPageSet object
* #3 Instantiate all requested modules.
* This way the PageSet object will know what shared data is required,
* and minimize DB calls.
@@ -220,6 +240,8 @@ 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.
@@ -230,6 +252,9 @@ class ApiQuery extends ApiBase {
/**
* Create instances of all modules requested by the client
+ * @param $modules array to append instatiated modules to
+ * @param $param string Parameter name to read modules from
+ * @param $moduleList array(modulename => classname)
*/
private function InstantiateModules(&$modules, $param, $moduleList) {
$list = @$this->params[$param];
@@ -239,14 +264,19 @@ class ApiQuery extends ApiBase {
}
/**
- * Appends an element for each page in the current pageSet with the most general
- * information (id, title), plus any title normalizations and missing or invalid title/pageids/revids.
+ * Appends an element for each page in the current pageSet with the
+ * most general information (id, title), plus any title normalizations
+ * and missing or invalid title/pageids/revids.
*/
private function outputGeneralPageInfo() {
$pageSet = $this->getPageSet();
$result = $this->getResult();
+ # We don't check for a full result set here because we can't be adding
+ # more than 380K. The maximum revision size is in the megabyte range,
+ # and the maximum result size must be even higher than that.
+
// Title normalizations
$normValues = array ();
foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) {
@@ -346,12 +376,43 @@ class ApiQuery extends ApiBase {
}
$result->setIndexedTagName($pages, 'page');
- $result->addValue('query', 'pages', $pages);
+ $result->addValue('query', 'pages', $pages);
+ }
+ if ($this->params['export']) {
+ $exporter = new WikiExporter($this->getDB());
+ // WikiExporter writes to stdout, so catch its
+ // output with an ob
+ ob_start();
+ $exporter->openStream();
+ foreach (@$pageSet->getGoodTitles() as $title)
+ if ($title->userCanRead())
+ $exporter->pageByTitle($title);
+ $exporter->closeStream();
+ $exportxml = ob_get_contents();
+ ob_end_clean();
+ // Don't check the size of exported stuff
+ // It's not continuable, so it would cause more
+ // problems than it'd solve
+ $result->disableSizeCheck();
+ if ($this->params['exportnowrap']) {
+ $result->reset();
+ // Raw formatter will handle this
+ $result->addValue(null, 'text', $exportxml);
+ $result->addValue(null, 'mime', 'text/xml');
+ } else {
+ $r = array();
+ ApiResult::setContent($r, $exportxml);
+ $result->addValue('query', 'export', $r);
+ }
+ $result->enableSizeCheck();
}
}
/**
- * For generator mode, execute generator, and use its output as new pageSet
+ * For generator mode, execute generator, and use its output as new
+ * ApiPageSet
+ * @param $generatorName string Module name
+ * @param $modules array of module objects
*/
protected function executeGeneratorModule($generatorName, $modules) {
@@ -392,10 +453,6 @@ class ApiQuery extends ApiBase {
$this->mPageSet = $resultPageSet;
}
- /**
- * Returns the list of allowed parameters for this module.
- * Qurey module also lists all ApiPageSet parameters as its own.
- */
public function getAllowedParams() {
return array (
'prop' => array (
@@ -415,11 +472,14 @@ class ApiQuery extends ApiBase {
),
'redirects' => false,
'indexpageids' => false,
+ 'export' => false,
+ 'exportnowrap' => false,
);
}
/**
* Override the parent to generate help messages for all available query modules.
+ * @return string
*/
public function makeHelpMsg() {
@@ -450,10 +510,13 @@ class ApiQuery extends ApiBase {
/**
* For all modules in $moduleList, generate help messages and join them together
+ * @param $moduleList array(modulename => classname)
+ * @param $paramName string Parameter name
+ * @return string
*/
private function makeHelpMsgHelper($moduleList, $paramName) {
- $moduleDscriptions = array ();
+ $moduleDescriptions = array ();
foreach ($moduleList as $moduleName => $moduleClass) {
$module = new $moduleClass ($this, $moduleName, null);
@@ -466,14 +529,15 @@ class ApiQuery extends ApiBase {
$this->mAllowedGenerators[] = $moduleName;
$msg .= "Generator:\n This module may be used as a generator\n";
}
- $moduleDscriptions[] = $msg;
+ $moduleDescriptions[] = $msg;
}
- return implode("\n", $moduleDscriptions);
+ return implode("\n", $moduleDescriptions);
}
/**
* Override to add extra parameters from PageSet
+ * @return string
*/
public function makeHelpMsgParameters() {
$psModule = new ApiPageSet($this);
@@ -489,31 +553,35 @@ class ApiQuery extends ApiBase {
'prop' => 'Which properties to get for the titles/revisions/pageids',
'list' => 'Which lists to get',
'meta' => 'Which meta data to get about the site',
- 'generator' => 'Use the output of a list as the input for other prop/list/meta items',
+ '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',
- 'indexpageids' => 'Include an additional pageids section listing all returned page IDs.'
+ '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',
);
}
public function getDescription() {
return array (
'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
- 'and is loosely based on the Query API interface currently available on all MediaWiki servers.',
+ 'and is loosely based on the old query.php interface.',
'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.'
);
}
protected function getExamples() {
return array (
- 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment'
+ 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
+ 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
);
}
public function getVersion() {
$psModule = new ApiPageSet($this);
$vers = array ();
- $vers[] = __CLASS__ . ': $Id: ApiQuery.php 42548 2008-10-25 14:04:43Z tstarling $';
+ $vers[] = __CLASS__ . ': $Id: ApiQuery.php 48629 2009-03-20 11:40:54Z catrope $';
$vers[] = $psModule->getVersion();
return $vers;
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index e6287eea..e835c042 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -103,21 +103,25 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$item = array();
$result->setContent( $item, $titleObj->getText() );
if( isset( $prop['size'] ) ) {
- $item['size'] = $row->cat_pages;
+ $item['size'] = intval($row->cat_pages);
$item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
- $item['files'] = $row->cat_files;
- $item['subcats'] = $row->cat_subcats;
+ $item['files'] = intval($row->cat_files);
+ $item['subcats'] = intval($row->cat_subcats);
}
if( isset( $prop['hidden'] ) && $row->cat_hidden )
$item['hidden'] = '';
- $categories[] = $item;
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $item);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('from', $this->keyToTitle($row->cat_title));
+ break;
+ }
}
}
$db->freeResult($res);
if (is_null($resultPageSet)) {
- $result->setIndexedTagName($categories, 'c');
- $result->addValue('query', $this->getModuleName(), $categories);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'c');
} else {
$resultPageSet->populateFromTitles($pages);
}
@@ -171,6 +175,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllCategories.php 44590 2008-12-14 20:24:23Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryAllCategories.php 47865 2009-02-27 16:03:01Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index 9ad34aa2..7ae24665 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -101,8 +101,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__);
- $data = array ();
+ $pageids = array ();
$count = 0;
+ $result = $this->getResult();
while ($row = $db->fetchObject($res)) {
if (++ $count > $limit) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
@@ -120,10 +121,17 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$vals['fromid'] = intval($row->pl_from);
if ($fld_title) {
$title = Title :: makeTitle($params['namespace'], $row->pl_title);
- $vals['ns'] = intval($title->getNamespace());
- $vals['title'] = $title->getPrefixedText();
+ ApiQueryBase::addTitleInfo($vals, $title);
+ }
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ if($params['unique'])
+ $this->setContinueEnumParameter('from', $this->keyToTitle($row->pl_title));
+ else
+ $this->setContinueEnumParameter('continue', $this->keyToTitle($row->pl_title) . "|" . $row->pl_from);
+ break;
}
- $data[] = $vals;
} else {
$pageids[] = $row->pl_from;
}
@@ -131,9 +139,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$db->freeResult($res);
if (is_null($resultPageSet)) {
- $result = $this->getResult();
- $result->setIndexedTagName($data, 'l');
- $result->addValue('query', $this->getModuleName(), $data);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'l');
} else {
$resultPageSet->populateFromPageIDs($pageids);
}
@@ -190,6 +196,6 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllLinks.php 45850 2009-01-17 20:03:25Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryAllLinks.php 47865 2009-02-27 16:03:01Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 8395808b..5f9ff064 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -57,11 +57,11 @@ class ApiQueryAllUsers extends ApiQueryBase {
$limit = $params['limit'];
$this->addTables('user', 'u1');
- if( !is_null( $params['from'] ) )
- $this->addWhere( 'u1.user_name >= ' . $db->addQuotes( $this->keyToTitle( $params['from'] ) ) );
+ if (!is_null($params['from']))
+ $this->addWhere('u1.user_name >= ' . $db->addQuotes($this->keyToTitle($params['from'])));
- if( isset( $params['prefix'] ) )
- $this->addWhere( 'u1.user_name LIKE "' . $db->escapeLike( $this->keyToTitle( $params['prefix'] ) ) . '%"' );
+ if (!is_null($params['prefix']))
+ $this->addWhere('u1.user_name LIKE "' . $db->escapeLike($this->keyToTitle( $params['prefix'])) . '%"');
if (!is_null($params['group'])) {
// Filter only users that belong to a given group
@@ -70,6 +70,9 @@ class ApiQueryAllUsers extends ApiQueryBase {
$this->addWhereFld('ug1.ug_group', $params['group']);
}
+ if ($params['witheditsonly'])
+ $this->addWhere('user_editcount > 0');
+
if ($fld_groups) {
// Show the groups the given users belong to
// request more than needed to avoid not getting all rows that belong to one user
@@ -124,7 +127,16 @@ class ApiQueryAllUsers extends ApiQueryBase {
if (!$row || $lastUser !== $row->user_name) {
// Save the last pass's user data
if (is_array($lastUserData))
- $data[] = $lastUserData;
+ {
+ $fit = $result->addValue(array('query', $this->getModuleName()),
+ null, $lastUserData);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('from',
+ $this->keyToTitle($lastUserData['name']));
+ break;
+ }
+ }
// No more rows left
if (!$row)
@@ -166,8 +178,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$db->freeResult($res);
- $result->setIndexedTagName($data, 'u');
- $result->addValue('query', $this->getModuleName(), $data);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'u');
}
public function getAllowedParams() {
@@ -192,7 +203,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
ApiBase :: PARAM_MIN => 1,
ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
- )
+ ),
+ 'witheditsonly' => false,
);
}
@@ -205,6 +217,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
'What pieces of information to include.',
'`groups` property uses more server resources and may return fewer results than the limit.'),
'limit' => 'How many total user names to return.',
+ 'witheditsonly' => 'Only list users who have made edits',
);
}
@@ -219,6 +232,6 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllUsers.php 44472 2008-12-11 21:51:01Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryAllUsers.php 46845 2009-02-05 14:30:59Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php
index ea83c667..76d5d238 100644
--- a/includes/api/ApiQueryAllimages.php
+++ b/includes/api/ApiQueryAllimages.php
@@ -97,7 +97,7 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__);
- $data = array ();
+ $titles = array();
$count = 0;
$result = $this->getResult();
while ($row = $db->fetchObject($res)) {
@@ -110,20 +110,23 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
if (is_null($resultPageSet)) {
$file = $repo->newFileFromRow( $row );
- $data[] = array_merge(array('name' => $row->img_name),
+ $info = array_merge(array('name' => $row->img_name),
ApiQueryImageInfo::getInfo($file, $prop, $result));
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $info);
+ if( !$fit ) {
+ $this->setContinueEnumParameter('from', $this->keyToTitle($row->img_name));
+ break;
+ }
} else {
- $data[] = Title::makeTitle(NS_FILE, $row->img_name);
+ $titles[] = Title::makeTitle(NS_IMAGE, $row->img_name);
}
}
$db->freeResult($res);
if (is_null($resultPageSet)) {
- $result = $this->getResult();
- $result->setIndexedTagName($data, 'img');
- $result->addValue('query', $this->getModuleName(), $data);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'img');
} else {
- $resultPageSet->populateFromTitles( $data );
+ $resultPageSet->populateFromTitles($titles);
}
}
@@ -202,6 +205,6 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllimages.php 44121 2008-12-01 17:14:30Z vyznev $';
+ return __CLASS__ . ': $Id: ApiQueryAllimages.php 46845 2009-02-05 14:30:59Z catrope $';
}
}
diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php
index 06683379..b19dc8fb 100644
--- a/includes/api/ApiQueryAllmessages.php
+++ b/includes/api/ApiQueryAllmessages.php
@@ -74,8 +74,13 @@ class ApiQueryAllmessages extends ApiQueryBase {
//Get all requested messages
$messages = array();
+ $skip = !is_null($params['from']);
foreach( $messages_target as $message ) {
- $messages[$message] = wfMsg( $message );
+ // Skip all messages up to $params['from']
+ if($skip && $message === $params['from'])
+ $skip = false;
+ if(!$skip)
+ $messages[$message] = wfMsg( $message );
}
//Print the result
@@ -89,10 +94,14 @@ class ApiQueryAllmessages extends ApiQueryBase {
} else {
$result->setContent( $message, $value );
}
- $messages_out[] = $message;
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $message);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('from', $name);
+ break;
+ }
}
- $result->setIndexedTagName( $messages_out, 'message' );
- $result->addValue( 'query', $this->getModuleName(), $messages_out );
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'message');
}
public function getAllowedParams() {
@@ -102,6 +111,7 @@ class ApiQueryAllmessages extends ApiQueryBase {
),
'filter' => array(),
'lang' => null,
+ 'from' => null,
);
}
@@ -110,6 +120,7 @@ class ApiQueryAllmessages extends ApiQueryBase {
'messages' => 'Which messages to output. "*" means all messages',
'filter' => 'Return only messages that contain this string',
'lang' => 'Return messages in this language',
+ 'from' => 'Return messages starting at this message',
);
}
@@ -125,6 +136,6 @@ class ApiQueryAllmessages extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllmessages.php 37504 2008-07-10 14:28:09Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryAllmessages.php 47048 2009-02-09 19:24:28Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php
index 531fa02a..3d30aba9 100644
--- a/includes/api/ApiQueryAllpages.php
+++ b/includes/api/ApiQueryAllpages.php
@@ -135,8 +135,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$this->addOption('LIMIT', $limit+1);
$res = $this->select(__METHOD__);
- $data = array ();
$count = 0;
+ $result = $this->getResult();
while ($row = $db->fetchObject($res)) {
if (++ $count > $limit) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
@@ -147,10 +147,16 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
if (is_null($resultPageSet)) {
$title = Title :: makeTitle($row->page_namespace, $row->page_title);
- $data[] = array(
+ $vals = array(
'pageid' => intval($row->page_id),
'ns' => intval($title->getNamespace()),
'title' => $title->getPrefixedText());
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('from', $this->keyToTitle($row->page_title));
+ break;
+ }
} else {
$resultPageSet->processDbRow($row);
}
@@ -158,9 +164,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$db->freeResult($res);
if (is_null($resultPageSet)) {
- $result = $this->getResult();
- $result->setIndexedTagName($data, 'p');
- $result->addValue('query', $this->getModuleName(), $data);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'p');
}
}
@@ -264,6 +268,6 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllpages.php 44863 2008-12-20 23:54:04Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryAllpages.php 46845 2009-02-05 14:30:59Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index f67e0044..95972392 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -38,7 +38,9 @@ if (!defined('MEDIAWIKI')) {
*/
class ApiQueryBacklinks extends ApiQueryGeneratorBase {
- private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID;
+ private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID, $redirect;
+ private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_sort, $bl_fields, $hasNS;
+ private $pageMap, $resultArr;
// output element name, database column field prefix, database table
private $backlinksSettings = array (
@@ -61,6 +63,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
public function __construct($query, $moduleName) {
extract($this->backlinksSettings[$moduleName]);
+ $this->resultArr = array();
parent :: __construct($query, $moduleName, $code);
$this->bl_ns = $prefix . '_namespace';
@@ -137,11 +140,11 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addFields($this->bl_title);
if($this->hasNS)
$this->addFields($this->bl_ns);
+ // We can't use LinkBatch here because $this->hasNS may be false
$titleWhere = array();
foreach($this->redirTitles as $t)
- $titleWhere[] = "({$this->bl_title} = ".$db->addQuotes($t->getDBKey()).
- ($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "") .
- ")";
+ $titleWhere[] = "{$this->bl_title} = ".$db->addQuotes($t->getDBKey()).
+ ($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "");
$this->addWhere($db->makeList($titleWhere, LIST_OR));
$this->addWhereFld('page_namespace', $this->params['namespace']);
if(!is_null($this->redirID))
@@ -168,6 +171,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addWhereFld('page_is_redirect', 0);
$this->addOption('LIMIT', $this->params['limit'] + 1);
$this->addOption('ORDER BY', $this->bl_sort);
+ $this->addOption('USE INDEX', array('page' => 'PRIMARY'));
}
private function run($resultPageSet = null) {
@@ -187,7 +191,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__.'::firstQuery');
$count = 0;
- $this->data = array ();
+ $this->pageMap = array(); // Maps ns and title to pageid
$this->continueStr = null;
$this->redirTitles = array();
while ($row = $db->fetchObject($res)) {
@@ -202,6 +206,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->extractRowInfo($row);
else
{
+ $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
if($row->page_is_redirect)
$this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title);
$resultPageSet->processDbRow($row);
@@ -222,10 +227,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
// We need to keep the parent page of this redir in
if($this->hasNS)
- $contTitle = Title::makeTitle($row->{$this->bl_ns}, $row->{$this->bl_title});
+ $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}];
else
- $contTitle = Title::makeTitle(NS_FILE, $row->{$this->bl_title});
- $this->continueStr = $this->getContinueRedirStr($contTitle->getArticleID(), $row->page_id);
+ $parentID = $this->pageMap[NS_IMAGE][$row->{$this->bl_title}];
+ $this->continueStr = $this->getContinueRedirStr($parentID, $row->page_id);
break;
}
@@ -236,41 +241,80 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
$db->freeResult($res);
}
- if(!is_null($this->continueStr))
- $this->setContinueEnumParameter('continue', $this->continueStr);
-
if (is_null($resultPageSet)) {
- $resultData = array();
- foreach($this->data as $ns => $a)
- foreach($a as $title => $arr)
- $resultData[] = $arr;
- $result = $this->getResult();
- $result->setIndexedTagName($resultData, $this->bl_code);
- $result->addValue('query', $this->getModuleName(), $resultData);
+ // Try to add the result data in one go and pray that it fits
+ $fit = $this->getResult()->addValue('query', $this->getModuleName(), array_values($this->resultArr));
+ if(!$fit)
+ {
+ // It didn't fit. Add elements one by one until the
+ // result is full.
+ foreach($this->resultArr as $pageID => $arr)
+ {
+ // Add the basic entry without redirlinks first
+ $fit = $this->getResult()->addValue(
+ array('query', $this->getModuleName()),
+ null, array_diff_key($arr, array('redirlinks' => '')));
+ if(!$fit)
+ {
+ $this->continueStr = $this->getContinueStr($pageID);
+ break;
+ }
+
+ $hasRedirs = false;
+ foreach((array)@$arr['redirlinks'] as $key => $redir)
+ {
+ $fit = $this->getResult()->addValue(
+ array('query', $this->getModuleName(), $pageID, 'redirlinks'),
+ $key, $redir);
+ if(!$fit)
+ {
+ $this->continueStr = $this->getContinueRedirStr($pageID, $redir['pageid']);
+ break;
+ }
+ $hasRedirs = true;
+ }
+ if($hasRedirs)
+ $this->getResult()->setIndexedTagName_internal(
+ array('query', $this->getModuleName(), $pageID, 'redirlinks'),
+ $this->bl_code);
+ if(!$fit)
+ break;
+ }
+ }
+
+ $this->getResult()->setIndexedTagName_internal(
+ array('query', $this->getModuleName()),
+ $this->bl_code);
}
+ if(!is_null($this->continueStr))
+ $this->setContinueEnumParameter('continue', $this->continueStr);
}
private function extractRowInfo($row) {
- if(!isset($this->data[$row->page_namespace][$row->page_title])) {
- $this->data[$row->page_namespace][$row->page_title]['pageid'] = $row->page_id;
- ApiQueryBase::addTitleInfo($this->data[$row->page_namespace][$row->page_title], Title::makeTitle($row->page_namespace, $row->page_title));
- if($row->page_is_redirect)
- {
- $this->data[$row->page_namespace][$row->page_title]['redirect'] = '';
- $this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title);
- }
+ $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
+ $t = Title::makeTitle($row->page_namespace, $row->page_title);
+ $a = array('pageid' => intval($row->page_id));
+ ApiQueryBase::addTitleInfo($a, $t);
+ if($row->page_is_redirect)
+ {
+ $a['redirect'] = '';
+ $this->redirTitles[] = $t;
}
+ // Put all the results in an array first
+ $this->resultArr[$a['pageid']] = $a;
}
private function extractRedirRowInfo($row)
{
- $a['pageid'] = $row->page_id;
+ $a['pageid'] = intval($row->page_id);
ApiQueryBase::addTitleInfo($a, Title::makeTitle($row->page_namespace, $row->page_title));
if($row->page_is_redirect)
$a['redirect'] = '';
$ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE;
- $this->data[$ns][$row->{$this->bl_title}]['redirlinks'][] = $a;
- $this->getResult()->setIndexedTagName($this->data[$ns][$row->{$this->bl_title}]['redirlinks'], $this->bl_code);
+ $parentID = $this->pageMap[$ns][$row->{$this->bl_title}];
+ // Put all the results in an array first
+ $this->resultArr[$parentID]['redirlinks'][] = $a;
+ $this->getResult()->setIndexedTagName($this->resultArr[$parentID]['redirlinks'], $this->bl_code);
}
protected function processContinue() {
@@ -404,8 +448,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
"api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info"
),
'imageusage' => array (
- "api.php?action=query&list=imageusage&iutitle=Image:Albert%20Einstein%20Head.jpg",
- "api.php?action=query&generator=imageusage&giutitle=Image:Albert%20Einstein%20Head.jpg&prop=info"
+ "api.php?action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg",
+ "api.php?action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info"
)
);
@@ -413,6 +457,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryBacklinks.php 46135 2009-01-24 13:03:40Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryBacklinks.php 50217 2009-05-05 13:12:16Z tstarling $';
}
}
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 896dd00c..9d1cbcea 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -30,7 +30,8 @@ if (!defined('MEDIAWIKI')) {
/**
* This is a base class for all Query modules.
- * It provides some common functionality such as constructing various SQL queries.
+ * It provides some common functionality such as constructing various SQL
+ * queries.
*
* @ingroup API
*/
@@ -58,8 +59,9 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of tables to the internal array
- * @param mixed $tables Table name or array of table names
- * @param mixed $alias Table alias, or null for no alias. Cannot be used with multiple tables
+ * @param $tables mixed Table name or array of table names
+ * @param $alias mixed Table alias, or null for no alias. Cannot be
+ * used with multiple tables
*/
protected function addTables($tables, $alias = null) {
if (is_array($tables)) {
@@ -75,8 +77,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Get the SQL for a table name with alias
- * @param string $table Table name
- * @param string $alias Alias
+ * @param $table string Table name
+ * @param $alias string Alias
* @return string SQL
*/
protected function getAliasedName($table, $alias) {
@@ -86,9 +88,11 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of JOIN conditions to the internal array
*
- * JOIN conditions are formatted as array( tablename => array(jointype, conditions)
- * e.g. array('page' => array('LEFT JOIN', 'page_id=rev_page'))
- * @param array $join_conds JOIN conditions
+ * JOIN conditions are formatted as array( tablename => array(jointype,
+ * conditions) e.g. array('page' => array('LEFT JOIN',
+ * 'page_id=rev_page')) . conditions may be a string or an
+ * addWhere()-style array
+ * @param $join_conds array JOIN conditions
*/
protected function addJoinConds($join_conds) {
if(!is_array($join_conds))
@@ -98,7 +102,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of fields to select to the internal array
- * @param mixed $value Field name or array of field names
+ * @param $value mixed Field name or array of field names
*/
protected function addFields($value) {
if (is_array($value))
@@ -109,8 +113,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addFields(), but add the fields only if a condition is met
- * @param mixed $value See addFields()
- * @param bool $condition If false, do nothing
+ * @param $value mixed See addFields()
+ * @param $condition bool If false, do nothing
* @return bool $condition
*/
protected function addFieldsIf($value, $condition) {
@@ -130,7 +134,7 @@ abstract class ApiQueryBase extends ApiBase {
*
* For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates
* to "foo=bar AND baz='3' AND bla='foo'"
- * @param mixed $value String or array
+ * @param $value mixed String or array
*/
protected function addWhere($value) {
if (is_array($value)) {
@@ -145,8 +149,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addWhere(), but add the WHERE clauses only if a condition is met
- * @param mixed $value See addWhere()
- * @param bool $condition If false, do nothing
+ * @param $value mixed See addWhere()
+ * @param $condition boolIf false, do nothing
* @return bool $condition
*/
protected function addWhereIf($value, $condition) {
@@ -159,8 +163,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Equivalent to addWhere(array($field => $value))
- * @param string $field Field name
- * @param string $value Value; ignored if null or empty array;
+ * @param $field string Field name
+ * @param $value string Value; ignored if null or empty array;
*/
protected function addWhereFld($field, $value) {
// Use count() to its full documented capabilities to simultaneously
@@ -172,12 +176,16 @@ 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 string $field Field name
- * @param string $dir If 'newer', sort in ascending order, otherwise sort in descending order
- * @param string $start Value to start the list at. If $dir == 'newer' this is the lower boundary, otherwise it's the upper boundary
- * @param string $end Value to end the list at. If $dir == 'newer' this is the upper boundary, otherwise it's the lower boundary
+ * @param $field string Field name
+ * @param $dir string If 'newer', sort in ascending order, otherwise
+ * sort in descending order
+ * @param $start string 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
+ * is the upper boundary, otherwise it's the lower boundary
+ * @param $sort bool If false, don't add an ORDER BY clause
*/
- protected function addWhereRange($field, $dir, $start, $end) {
+ protected function addWhereRange($field, $dir, $start, $end, $sort = true) {
$isDirNewer = ($dir === 'newer');
$after = ($isDirNewer ? '>=' : '<=');
$before = ($isDirNewer ? '<=' : '>=');
@@ -189,17 +197,20 @@ abstract class ApiQueryBase extends ApiBase {
if (!is_null($end))
$this->addWhere($field . $before . $db->addQuotes($end));
- $order = $field . ($isDirNewer ? '' : ' DESC');
- if (!isset($this->options['ORDER BY']))
- $this->addOption('ORDER BY', $order);
- else
- $this->addOption('ORDER BY', $this->options['ORDER BY'] . ', ' . $order);
+ if ($sort) {
+ $order = $field . ($isDirNewer ? '' : ' DESC');
+ if (!isset($this->options['ORDER BY']))
+ $this->addOption('ORDER BY', $order);
+ else
+ $this->addOption('ORDER BY', $this->options['ORDER BY'] . ', ' . $order);
+ }
}
/**
- * Add an option such as LIMIT or USE INDEX
- * @param string $name Option name
- * @param string $value Option value
+ * 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
*/
protected function addOption($name, $value = null) {
if (is_null($value))
@@ -210,7 +221,8 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Execute a SELECT query based on the values in the internal arrays
- * @param string $method Function the query should be attributed to. You should usually use __METHOD__ here
+ * @param $method string Function the query should be attributed to.
+ * You should usually use __METHOD__ here
* @return ResultWrapper
*/
protected function select($method) {
@@ -243,10 +255,11 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
- * Add information (title and namespace) about a Title object to a result array
- * @param array $arr Result array à la ApiResult
- * @param Title $title Title object
- * @param string $prefix Module prefix
+ * Add information (title and namespace) about a Title object to a
+ * result array
+ * @param $arr array Result array à la ApiResult
+ * @param $title Title
+ * @param $prefix string Module prefix
*/
public static function addTitleInfo(&$arr, $title, $prefix='') {
$arr[$prefix . 'ns'] = intval($title->getNamespace());
@@ -256,7 +269,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Override this method to request extra fields from the pageSet
* using $pageSet->requestField('fieldName')
- * @param ApiPageSet $pageSet
+ * @param $pageSet ApiPageSet
*/
public function requestExtraData($pageSet) {
}
@@ -271,31 +284,54 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a sub-element under the page element with the given page ID
- * @param int $pageId Page ID
- * @param array $data Data array à la ApiResult
+ * @param $pageId int Page ID
+ * @param $data array Data array à la ApiResult
+ * @return bool Whether the element fit in the result
*/
protected function addPageSubItems($pageId, $data) {
$result = $this->getResult();
$result->setIndexedTagName($data, $this->getModulePrefix());
- $result->addValue(array ('query', 'pages', intval($pageId)),
+ return $result->addValue(array('query', 'pages', intval($pageId)),
$this->getModuleName(),
$data);
}
+
+ /**
+ * Same as addPageSubItems(), but one element of $data at a time
+ * @param $pageId int Page ID
+ * @param $data array Data array à la ApiResult
+ * @param $elemname string XML element name. If null, getModuleName()
+ * is used
+ * @return bool Whether the element fit in the result
+ */
+ protected function addPageSubItem($pageId, $item, $elemname = null) {
+ if(is_null($elemname))
+ $elemname = $this->getModulePrefix();
+ $result = $this->getResult();
+ $fit = $result->addValue(array('query', 'pages', $pageId,
+ $this->getModuleName()), null, $item);
+ if(!$fit)
+ return false;
+ $result->setIndexedTagName_internal(array('query', 'pages', $pageId,
+ $this->getModuleName()), $elemname);
+ return true;
+ }
/**
* Set a query-continue value
- * @param $paramName Parameter name
- * @param $paramValue Parameter value
+ * @param $paramName string Parameter name
+ * @param $paramValue string Parameter value
*/
protected function setContinueEnumParameter($paramName, $paramValue) {
-
$paramName = $this->encodeParamName($paramName);
$msg = array( $paramName => $paramValue );
+ $this->getResult()->disableSizeCheck();
$this->getResult()->addValue('query-continue', $this->getModuleName(), $msg);
+ $this->getResult()->enableSizeCheck();
}
/**
- * Get the Query database connection (readonly)
+ * Get the Query database connection (read-only)
* @return Database
*/
protected function getDB() {
@@ -306,12 +342,10 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Selects the query database connection with the given name.
- * 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 $db or $groups new values.
- * @param string $name Name to assign to the database connection
- * @param int $db One of the DB_* constants
- * @param array $groups Query groups
+ * 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
* @return Database
*/
public function selectNamedDB($name, $db, $groups) {
@@ -328,7 +362,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Convert a title to a DB key
- * @param string $title Page title with spaces
+ * @param $title string Page title with spaces
* @return string Page title with underscores
*/
public function titleToKey($title) {
@@ -343,7 +377,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* The inverse of titleToKey()
- * @param string $key Page title with underscores
+ * @param $key string Page title with underscores
* @return string Page title with spaces
*/
public function keyToTitle($key) {
@@ -359,7 +393,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* An alternative to titleToKey() that doesn't trim trailing spaces
- * @param string $titlePart Title part with spaces
+ * @param $titlePart string Title part with spaces
* @return string Title part with underscores
*/
public function titlePartToKey($titlePart) {
@@ -368,7 +402,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* An alternative to keyToTitle() that doesn't trim trailing spaces
- * @param string $keyPart Key part with spaces
+ * @param $keyPart string Key part with spaces
* @return string Key part with underscores
*/
public function keyPartToTitle($keyPart) {
@@ -380,7 +414,7 @@ abstract class ApiQueryBase extends ApiBase {
* @return string
*/
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiQueryBase.php 44461 2008-12-11 19:11:11Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryBase.php 47450 2009-02-18 15:26:09Z catrope $';
}
}
@@ -406,6 +440,8 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
/**
* Overrides base class to prepend 'g' to every generator parameter
+ * @param $paramNames string Parameter name
+ * @return string Prefixed parameter name
*/
public function encodeParamName($paramName) {
if ($this->mIsGenerator)
@@ -416,7 +452,8 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
/**
* Execute this module as a generator
- * @param $resultPageSet PageSet: All output should be appended to this object
+ * @param $resultPageSet ApiPageSet: All output should be appended to
+ * this object
*/
public abstract function executeGenerator($resultPageSet);
}
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index 6f356cea..c5ffc37b 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -115,7 +115,7 @@ class ApiQueryBlocks extends ApiQueryBase {
"ipb_range_end >= '$upper'"
));
}
- if(!$wgUser->isAllowed('suppress'))
+ if(!$wgUser->isAllowed('hideuser'))
$this->addWhereFld('ipb_deleted', 0);
// Purge expired entries on one in every 10 queries
@@ -169,10 +169,14 @@ class ApiQueryBlocks extends ApiQueryBase {
if($row->ipb_allow_usertalk)
$block['allowusertalk'] = '';
}
- $data[] = $block;
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $block);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp));
+ break;
+ }
}
- $result->setIndexedTagName($data, 'block');
- $result->addValue('query', $this->getModuleName(), $data);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'block');
}
protected function prepareUsername($user)
@@ -259,6 +263,6 @@ class ApiQueryBlocks extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryBlocks.php 43676 2008-11-18 15:11:11Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryBlocks.php 48213 2009-03-09 10:01:00Z aaron $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 9c4e9b41..f91a04e5 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -81,6 +81,19 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
$this->addTables('categorylinks');
$this->addWhereFld('cl_from', array_keys($this->getPageSet()->getGoodTitles()));
+ if(!is_null($params['categories']))
+ {
+ $cats = array();
+ foreach($params['categories'] as $cat)
+ {
+ $title = Title::newFromText($cat);
+ if(!$title || $title->getNamespace() != NS_CATEGORY)
+ $this->setWarning("``$cat'' is not a category");
+ else
+ $cats[] = $title->getDBkey();
+ }
+ $this->addWhereFld('cl_to', $cats);
+ }
if(!is_null($params['continue'])) {
$cont = explode('|', $params['continue']);
if(count($cont) != 2)
@@ -112,6 +125,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
$this->addWhere(array('pp_propname IS NULL'));
}
+ $this->addOption('USE INDEX', array('categorylinks' => 'cl_from'));
# Don't order by cl_from if it's constant in the WHERE clause
if(count($this->getPageSet()->getGoodTitles()) == 1)
$this->addOption('ORDER BY', 'cl_to');
@@ -123,8 +137,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
if (is_null($resultPageSet)) {
- $data = array();
- $lastId = 0; // database has no ID 0
$count = 0;
while ($row = $db->fetchObject($res)) {
if (++$count > $params['limit']) {
@@ -134,16 +146,8 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
'|' . $this->keyToTitle($row->cl_to));
break;
}
- if ($lastId != $row->cl_from) {
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
- $data = array();
- }
- $lastId = $row->cl_from;
- }
$title = Title :: makeTitle(NS_CATEGORY, $row->cl_to);
-
$vals = array();
ApiQueryBase :: addTitleInfo($vals, $title);
if ($fld_sortkey)
@@ -151,13 +155,14 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
if ($fld_timestamp)
$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp);
- $data[] = $vals;
- }
-
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
+ $fit = $this->addPageSubItem($row->cl_from, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue', $row->cl_from .
+ '|' . $this->keyToTitle($row->cl_to));
+ break;
+ }
}
-
} else {
$titles = array();
@@ -202,6 +207,9 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
),
'continue' => null,
+ 'categories' => array(
+ ApiBase :: PARAM_ISMULTI => true,
+ ),
);
}
@@ -211,6 +219,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
'limit' => 'How many categories to return',
'show' => 'Which kind of categories to show',
'continue' => 'When more results are available, use this to continue',
+ 'categories' => 'Only list these categories. Useful for checking whether a certain page is in a certain category',
);
}
@@ -228,6 +237,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategories.php 44585 2008-12-14 17:39:50Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryCategories.php 50097 2009-05-01 06:35:57Z tstarling $';
}
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index f83c4a5b..49e4554e 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) {
}
/**
- * This query adds <categories> subelement to all pages with the list of images embedded into those pages.
+ * This query adds the <categories> subelement to all pages with the list of categories the page is in
*
* @ingroup API
*/
@@ -39,7 +39,8 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
parent :: __construct($query, $moduleName, 'ci');
}
- public function execute() {
+ public function execute() {
+ $params = $this->extractRequestParams();
$alltitles = $this->getPageSet()->getAllTitlesByNamespace();
if ( empty( $alltitles[NS_CATEGORY] ) ) {
return;
@@ -65,27 +66,49 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
'pp_propname' => 'hiddencat')),
));
$this->addFields(array('cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'pp_propname AS cat_hidden'));
- $this->addWhere(array('cat_title' => $cattitles));
+ $this->addWhere(array('cat_title' => $cattitles));
+ if(!is_null($params['continue']))
+ {
+ $title = $this->getDB()->addQuotes($params['continue']);
+ $this->addWhere("cat_title >= $title");
+ }
+ $this->addOption('ORDER BY', 'cat_title');
$db = $this->getDB();
$res = $this->select(__METHOD__);
- $data = array();
$catids = array_flip($cattitles);
while($row = $db->fetchObject($res))
{
$vals = array();
- $vals['size'] = $row->cat_pages;
+ $vals['size'] = intval($row->cat_pages);
$vals['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
- $vals['files'] = $row->cat_files;
- $vals['subcats'] = $row->cat_subcats;
+ $vals['files'] = intval($row->cat_files);
+ $vals['subcats'] = intval($row->cat_subcats);
if($row->cat_hidden)
$vals['hidden'] = '';
- $this->addPageSubItems($catids[$row->cat_title], $vals);
+ $fit = $this->addPageSubItems($catids[$row->cat_title], $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue', $row->cat_title);
+ break;
+ }
}
$db->freeResult($res);
}
+ public function getAllowedParams() {
+ return array (
+ 'continue' => null,
+ );
+ }
+
+ public function getParamDescription() {
+ return array (
+ 'continue' => 'When more results are available, use this to continue',
+ );
+ }
+
public function getDescription() {
return 'Returns information about the given categories';
}
@@ -95,6 +118,6 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 44590 2008-12-14 20:24:23Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 47865 2009-02-27 16:03:01Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index e2f577a2..dc5a8265 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -112,31 +112,38 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
break;
}
- $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys
-
if (is_null($resultPageSet)) {
$vals = array();
if ($fld_ids)
$vals['pageid'] = intval($row->page_id);
if ($fld_title) {
$title = Title :: makeTitle($row->page_namespace, $row->page_title);
- $vals['ns'] = intval($title->getNamespace());
- $vals['title'] = $title->getPrefixedText();
+ ApiQueryBase::addTitleInfo($vals, $title);
}
if ($fld_sortkey)
$vals['sortkey'] = $row->cl_sortkey;
if ($fld_timestamp)
$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp);
- $data[] = $vals;
+ $fit = $this->getResult()->addValue(array('query', $this->getModuleName()),
+ null, $vals);
+ if(!$fit)
+ {
+ if ($params['sort'] == 'timestamp')
+ $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->cl_timestamp));
+ else
+ $this->setContinueEnumParameter('continue', $this->getContinueStr($row, $lastSortKey));
+ break;
+ }
} else {
$resultPageSet->processDbRow($row);
}
+ $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys
}
$db->freeResult($res);
if (is_null($resultPageSet)) {
- $this->getResult()->setIndexedTagName($data, 'cm');
- $this->getResult()->addValue('query', $this->getModuleName(), $data);
+ $this->getResult()->setIndexedTagName_internal(
+ array('query', $this->getModuleName()), 'cm');
}
}
@@ -255,6 +262,6 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 42197 2008-10-18 10:09:19Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 47865 2009-02-27 16:03:01Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index 408421c4..bdbe05e8 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -56,13 +56,27 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$fld_len = isset($prop['len']);
$fld_content = isset($prop['content']);
$fld_token = isset($prop['token']);
-
+
$result = $this->getResult();
$pageSet = $this->getPageSet();
$titles = $pageSet->getTitles();
$data = array();
+
+ // This module operates in three modes:
+ // 'revs': List deleted revs for certain titles
+ // 'user': List deleted revs by a certain user
+ // 'all': List all deleted revs
+ $mode = 'all';
+ if(count($titles) > 0)
+ $mode = 'revs';
+ else if(!is_null($params['user']))
+ $mode = 'user';
+
+ if(!is_null($params['user']) && !is_null($params['excludeuser']))
+ $this->dieUsage('user and excludeuser cannot be used together', 'badparams');
$this->addTables('archive');
+ $this->addWhere('ar_deleted = 0');
$this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp'));
if($fld_revid)
$this->addFields('ar_rev_id');
@@ -102,34 +116,88 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$token = $wgUser->editToken();
// We need a custom WHERE clause that matches all titles.
- if(count($titles) > 0)
+ if($mode == 'revs')
{
$lb = new LinkBatch($titles);
$where = $lb->constructSet('ar', $db);
$this->addWhere($where);
- } else {
- $this->dieUsage('You have to specify a page title or titles');
+ }
+ elseif($mode == 'all')
+ {
+ $this->addWhereFld('ar_namespace', $params['namespace']);
+ if(!is_null($params['from']))
+ {
+ $from = $this->getDB()->strencode($this->titleToKey($params['from']));
+ $this->addWhere("ar_title >= '$from'");
+ }
+ }
+
+ if(!is_null($params['user'])) {
+ $this->addWhereFld('ar_user_text', $params['user']);
+ } elseif(!is_null($params['excludeuser'])) {
+ $this->addWhere('ar_user_text != ' .
+ $this->getDB()->addQuotes($params['excludeuser']));
+ }
+
+ 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");
+ $ns = intval($cont[0]);
+ $title = $this->getDB()->strencode($this->titleToKey($cont[1]));
+ $ts = $this->getDB()->strencode($cont[2]);
+ $op = ($params['dir'] == 'newer' ? '>' : '<');
+ $this->addWhere("ar_namespace $op $ns OR " .
+ "(ar_namespace = $ns AND " .
+ "(ar_title $op '$title' OR " .
+ "(ar_title = '$title' AND " .
+ "ar_timestamp = '$ts')))");
}
$this->addOption('LIMIT', $limit + 1);
- $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']);
+ $this->addOption('USE INDEX', array('archive' => ($mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp')));
+ if($mode == 'all')
+ {
+ if($params['unique'])
+ {
+ $this->addOption('GROUP BY', 'ar_title');
+ $this->addOption('ORDER BY', 'ar_title');
+ }
+ else
+ $this->addOption('ORDER BY', 'ar_title, ar_timestamp');
+ }
+ else
+ {
+ if($mode == 'revs')
+ {
+ // Sort by ns and title in the same order as timestamp for efficiency
+ $this->addWhereRange('ar_namespace', $params['dir'], null, null);
+ $this->addWhereRange('ar_title', $params['dir'], null, null);
+ }
+ $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']);
+ }
$res = $this->select(__METHOD__);
- $pages = array();
+ $pageMap = array(); // Maps ns&title to (fake) pageid
$count = 0;
- // First populate the $pages array
+ $newPageID = 0;
while($row = $db->fetchObject($res))
{
if(++$count > $limit)
{
// We've had enough
- $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp));
+ if($mode == 'all' || $mode == 'revs')
+ $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' .
+ $this->keyToTitle($row->ar_title) . '|' . $row->ar_timestamp);
+ else
+ $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp));
break;
}
$rev = array();
$rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp);
if($fld_revid)
- $rev['revid'] = $row->ar_rev_id;
+ $rev['revid'] = intval($row->ar_rev_id);
if($fld_user)
$rev['user'] = $row->ar_user_text;
if($fld_comment)
@@ -142,31 +210,38 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if($fld_content)
ApiResult::setContent($rev, Revision::getRevisionText($row));
- $t = Title::makeTitle($row->ar_namespace, $row->ar_title);
- if(!isset($pages[$t->getPrefixedText()]))
+ if(!isset($pageMap[$row->ar_namespace][$row->ar_title]))
{
- $pages[$t->getPrefixedText()] = array(
- 'title' => $t->getPrefixedText(),
- 'ns' => intval($row->ar_namespace),
- 'revisions' => array($rev)
- );
+ $pageID = $newPageID++;
+ $pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
+ $t = Title::makeTitle($row->ar_namespace, $row->ar_title);
+ $a['revisions'] = array($rev);
+ $result->setIndexedTagName($a['revisions'], 'rev');
+ ApiQueryBase::addTitleInfo($a, $t);
if($fld_token)
- $pages[$t->getPrefixedText()]['token'] = $token;
+ $a['token'] = $token;
+ $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a);
}
else
- $pages[$t->getPrefixedText()]['revisions'][] = $rev;
+ {
+ $pageID = $pageMap[$row->ar_namespace][$row->ar_title];
+ $fit = $result->addValue(
+ array('query', $this->getModuleName(), $pageID, 'revisions'),
+ null, $rev);
+ }
+ if(!$fit)
+ {
+ if($mode == 'all' || $mode == 'revs')
+ $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' .
+ $this->keyToTitle($row->ar_title) . '|' . $row->ar_timestamp);
+ else
+ $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp));
+ break;
+ }
}
$db->freeResult($res);
-
- // We don't want entire pagenames as keys, so let's make this array indexed
- foreach($pages as $page)
- {
- $result->setIndexedTagName($page['revisions'], 'rev');
- $data[] = $page;
- }
- $result->setIndexedTagName($data, 'page');
- $result->addValue('query', $this->getModuleName(), $data);
- }
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page');
+ }
public function getAllowedParams() {
return array (
@@ -183,6 +258,19 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
),
ApiBase :: PARAM_DFLT => 'older'
),
+ 'from' => null,
+ 'continue' => null,
+ 'unique' => false,
+ 'user' => array(
+ ApiBase :: PARAM_TYPE => 'user'
+ ),
+ 'excludeuser' => array(
+ ApiBase :: PARAM_TYPE => 'user'
+ ),
+ 'namespace' => array(
+ ApiBase :: PARAM_TYPE => 'namespace',
+ ApiBase :: PARAM_DFLT => 0,
+ ),
'limit' => array(
ApiBase :: PARAM_DFLT => 10,
ApiBase :: PARAM_TYPE => 'limit',
@@ -202,34 +290,51 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'token'
),
ApiBase :: PARAM_ISMULTI => true
- )
+ ),
);
}
public function getParamDescription() {
return array (
- 'start' => 'The timestamp to start enumerating from',
- 'end' => 'The timestamp to stop enumerating at',
- 'dir' => 'The direction in which to enumerate',
+ 'start' => 'The timestamp to start enumerating from. (1,2)',
+ 'end' => 'The timestamp to stop enumerating at. (1,2)',
+ 'dir' => 'The direction in which to enumerate. (1,2)',
'limit' => 'The maximum amount of revisions to list',
- 'prop' => 'Which properties to get'
+ 'prop' => 'Which properties to get',
+ 'namespace' => 'Only list pages in this namespace (3)',
+ 'user' => 'Only list revisions by this user',
+ 'excludeuser' => 'Don\'t list revisions by this user',
+ 'from' => 'Start listing at this title (3)',
+ 'continue' => 'When more results are available, use this to continue (3)',
+ 'unique' => 'List only one revision for each page (3)',
);
}
public function getDescription() {
- return 'List deleted revisions.';
+ return array( 'List deleted revisions.',
+ 'This module operates in three modes:',
+ '1) List deleted revisions for the given title(s), sorted by timestamp',
+ '2) List deleted contributions for the given user, sorted by timestamp (no titles specified)',
+ '3) List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, druser not set)',
+ 'Certain parameters only apply to some modes and are ignored in others.',
+ 'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3.',
+ );
}
protected function getExamples() {
return array (
- 'List the first 50 deleted revisions',
+ 'List the last deleted revisions of Main Page and Talk:Main Page, with content (mode 1):',
+ ' api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content',
+ 'List the last 50 deleted contributions by Bob (mode 2):',
+ ' api.php?action=query&list=deletedrevs&druser=Bob&drlimit=50',
+ 'List the first 50 deleted revisions in the main namespace (mode 3):',
' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50',
- 'List the last deleted revisions of Main Page and Talk:Main Page, with content:',
- ' api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content'
+ 'List the first 50 deleted pages in the Talk namespace (mode 3):',
+ ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 40798 2008-09-13 20:41:58Z aaron $';
+ return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 50220 2009-05-05 14:07:59Z tstarling $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index 5f7d7ee0..84a8a96d 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -86,9 +86,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__);
$db = $this->getDB();
$count = 0;
- $data = array();
$titles = array();
- $lastName = '';
while($row = $db->fetchObject($res))
{
if(++$count > $params['limit'])
@@ -104,27 +102,23 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$titles[] = Title::makeTitle(NS_FILE, $row->dup_name);
else
{
- if($row->orig_name != $lastName)
- {
- if($lastName != '')
- {
- $this->addPageSubItems($images[$lastName], $data);
- $data = array();
- }
- $lastName = $row->orig_name;
- }
-
- $data[] = array(
+ $r = array(
'name' => $row->dup_name,
'user' => $row->dup_user_text,
'timestamp' => wfTimestamp(TS_ISO_8601, $row->dup_timestamp)
);
+ $fit = $this->addPageSubItem($images[$row->orig_name], $r);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue',
+ $this->keyToTitle($row->orig_name) . '|' .
+ $this->keyToTitle($row->dup_name));
+ break;
+ }
}
}
if(!is_null($resultPageSet))
$resultPageSet->populateFromTitles($titles);
- else if($lastName != '')
- $this->addPageSubItems($images[$lastName], $data);
$db->freeResult($res);
}
@@ -153,12 +147,12 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
}
protected function getExamples() {
- return array ( 'api.php?action=query&titles=Image:Albert_Einstein_Head.jpg&prop=duplicatefiles',
+ return array ( 'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles',
'api.php?action=query&generator=allimages&prop=duplicatefiles',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 44121 2008-12-01 17:14:30Z vyznev $';
+ return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 48215 2009-03-09 10:44:34Z catrope $';
}
}
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index 85e21f42..0ba2767a 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -110,7 +110,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__);
- $data = array ();
+ $result = $this->getResult();
$count = 0;
while ($row = $db->fetchObject($res)) {
if (++ $count > $limit) {
@@ -125,12 +125,16 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$vals['pageid'] = intval($row->page_id);
if ($fld_title) {
$title = Title :: makeTitle($row->page_namespace, $row->page_title);
- $vals['ns'] = intval($title->getNamespace());
- $vals['title'] = $title->getPrefixedText();
+ ApiQueryBase::addTitleInfo($vals, $title);
}
if ($fld_url)
$vals['url'] = $row->el_to;
- $data[] = $vals;
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('offset', $offset + $count - 1);
+ break;
+ }
} else {
$resultPageSet->processDbRow($row);
}
@@ -138,9 +142,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$db->freeResult($res);
if (is_null($resultPageSet)) {
- $result = $this->getResult();
- $result->setIndexedTagName($data, $this->getModulePrefix());
- $result->addValue('query', $this->getModuleName(), $data);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()),
+ $this->getModulePrefix());
}
}
@@ -206,6 +209,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 43271 2008-11-06 22:38:42Z siebrand $';
+ return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 47865 2009-02-27 16:03:01Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index a24f15d8..7a91f432 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -61,8 +61,6 @@ class ApiQueryExternalLinks extends ApiQueryBase {
$db = $this->getDB();
$res = $this->select(__METHOD__);
- $data = array();
- $lastId = 0; // database has no ID 0
$count = 0;
while ($row = $db->fetchObject($res)) {
if (++$count > $params['limit']) {
@@ -71,23 +69,15 @@ class ApiQueryExternalLinks extends ApiQueryBase {
$this->setContinueEnumParameter('offset', @$params['offset'] + $params['limit']);
break;
}
- if ($lastId != $row->el_from) {
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
- $data = array();
- }
- $lastId = $row->el_from;
- }
-
$entry = array();
ApiResult :: setContent($entry, $row->el_to);
- $data[] = $entry;
- }
-
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
+ $fit = $this->addPageSubItem($row->el_from, $entry);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('offset', @$params['offset'] + $count - 1);
+ break;
+ }
}
-
$db->freeResult($res);
}
@@ -123,6 +113,6 @@ class ApiQueryExternalLinks extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 37270 2008-07-07 17:32:22Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 46845 2009-02-05 14:30:59Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index 612d5cc9..7d880456 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -56,49 +56,116 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
$pageIds = $this->getPageSet()->getAllTitlesByNamespace();
- if (!empty($pageIds[NS_FILE])) {
-
+ if ( !empty( $pageIds[NS_FILE] ) ) {
+ $titles = array_keys($pageIds[NS_FILE]);
+ asort($titles); // Ensure the order is always the same
+
+ $skip = false;
+ 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");
+ $fromTitle = strval($cont[0]);
+ $fromTimestamp = $cont[1];
+ // Filter out any titles before $fromTitle
+ foreach($titles as $key => $title)
+ if($title < $fromTitle)
+ unset($titles[$key]);
+ else
+ break;
+ }
+
$result = $this->getResult();
- $images = RepoGroup::singleton()->findFiles( array_keys( $pageIds[NS_FILE] ) );
+ $images = RepoGroup::singleton()->findFiles( $titles );
foreach ( $images as $img ) {
- $data = array();
-
+ $start = $skip ? $fromTimestamp : $params['start'];
+ $pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ];
+
+ $fit = $result->addValue(
+ array('query', 'pages', intval($pageId)),
+ 'imagerepository', $img->getRepoName()
+ );
+ if(!$fit)
+ {
+ if(count($pageIds[NS_IMAGE]) == 1)
+ # The user is screwed. imageinfo can't be solely
+ # responsible for exceeding the limit in this case,
+ # so set a query-continue that just returns the same
+ # thing again. When the violating queries have been
+ # out-continued, the result will get through
+ $this->setContinueEnumParameter('start',
+ wfTimestamp(TS_ISO_8601, $img->getTimestamp()));
+ else
+ $this->setContinueEnumParameter('continue',
+ $this->getContinueStr($img));
+ break;
+ }
+
// Get information about the current version first
// Check that the current version is within the start-end boundaries
- if((is_null($params['start']) || $img->getTimestamp() <= $params['start']) &&
+ $gotOne = false;
+ if((is_null($start) || $img->getTimestamp() <= $start) &&
(is_null($params['end']) || $img->getTimestamp() >= $params['end'])) {
- $data[] = self::getInfo( $img, $prop, $result, $scale );
+ $gotOne = true;
+ $fit = $this->addPageSubItem($pageId,
+ self::getInfo( $img, $prop, $result, $scale));
+ if(!$fit)
+ {
+ if(count($pageIds[NS_IMAGE]) == 1)
+ # See the 'the user is screwed' comment above
+ $this->setContinueEnumParameter('start',
+ wfTimestamp(TS_ISO_8601, $img->getTimestamp()));
+ else
+ $this->setContinueEnumParameter('continue',
+ $this->getContinueStr($img));
+ break;
+ }
}
// Now get the old revisions
// Get one more to facilitate query-continue functionality
- $count = count($data);
- $oldies = $img->getHistory($params['limit'] - $count + 1, $params['start'], $params['end']);
+ $count = ($gotOne ? 1 : 0);
+ $oldies = $img->getHistory($params['limit'] - $count + 1, $start, $params['end']);
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...
// Only set a query-continue if there was only one title
if(count($pageIds[NS_FILE]) == 1)
- $this->setContinueEnumParameter('start', $oldie->getTimestamp());
+ {
+ $this->setContinueEnumParameter('start',
+ wfTimestamp(TS_ISO_8601, $oldie->getTimestamp()));
+ }
+ break;
+ }
+ $fit = $this->addPageSubItem($pageId,
+ self::getInfo($oldie, $prop, $result));
+ if(!$fit)
+ {
+ if(count($pageIds[NS_IMAGE]) == 1)
+ $this->setContinueEnumParameter('start',
+ wfTimestamp(TS_ISO_8601, $oldie->getTimestamp()));
+ else
+ $this->setContinueEnumParameter('continue',
+ $this->getContinueStr($oldie));
break;
}
- $data[] = self::getInfo( $oldie, $prop, $result );
}
-
- $pageId = $pageIds[NS_FILE][ $img->getOriginalTitle()->getDBkey() ];
- $result->addValue(
- array( 'query', 'pages', intval( $pageId ) ),
- 'imagerepository', $img->getRepoName()
- );
- $this->addPageSubItems($pageId, $data);
+ if(!$fit)
+ break;
+ $skip = false;
}
$missing = array_diff( array_keys( $pageIds[NS_FILE] ), array_keys( $images ) );
- foreach ( $missing as $title )
+ foreach ($missing as $title) {
$result->addValue(
- array( 'query', 'pages', intval( $pageIds[NS_FILE][$title] ) ),
+ array('query', 'pages', intval($pageIds[NS_FILE][$title])),
'imagerepository', ''
);
+ // The above can't fail because it doesn't increase the result size
+ }
}
}
@@ -127,8 +194,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
if( $mto && !$mto->isError() )
{
$vals['thumburl'] = $mto->getUrl();
- $vals['thumbwidth'] = $mto->getWidth();
- $vals['thumbheight'] = $mto->getHeight();
+ $vals['thumbwidth'] = intval( $mto->getWidth() );
+ $vals['thumbheight'] = intval( $mto->getHeight() );
}
}
$vals['url'] = $file->getFullURL();
@@ -140,8 +207,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 );
if( isset( $prop['metadata'] ) ) {
$metadata = $file->getMetadata();
- $vals['metadata'] = $metadata ? unserialize( $metadata ) : null;
- $result->setIndexedTagName_recursive( $vals['metadata'], 'meta' );
+ $vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null;
}
if( isset( $prop['mime'] ) )
$vals['mime'] = $file->getMimeType();
@@ -154,6 +220,30 @@ class ApiQueryImageInfo extends ApiQueryBase {
return $vals;
}
+
+ public static function processMetaData($metadata, $result)
+ {
+ $retval = array();
+ if ( is_array( $metadata ) ) {
+ foreach($metadata as $key => $value)
+ {
+ $r = array('name' => $key);
+ if(is_array($value))
+ $r['value'] = self::processMetaData($value, $result);
+ else
+ $r['value'] = $value;
+ $retval[] = $r;
+ }
+ }
+ $result->setIndexedTagName($retval, 'metadata');
+ return $retval;
+ }
+
+ private function getContinueStr($img)
+ {
+ return $img->getOriginalTitle()->getText() .
+ '|' . $img->getTimestamp();
+ }
public function getAllowedParams() {
return array (
@@ -193,7 +283,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
'urlheight' => array(
ApiBase :: PARAM_TYPE => 'integer',
ApiBase :: PARAM_DFLT => -1
- )
+ ),
+ 'continue' => null,
);
}
@@ -206,6 +297,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
'urlwidth' => array('If iiprop=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.'),
'urlheight' => 'Similar to iiurlwidth. Cannot be used without iiurlwidth',
+ 'continue' => 'When more results are available, use this to continue',
);
}
@@ -217,12 +309,12 @@ class ApiQueryImageInfo extends ApiQueryBase {
protected function getExamples() {
return array (
- 'api.php?action=query&titles=Image:Albert%20Einstein%20Head.jpg&prop=imageinfo',
- 'api.php?action=query&titles=Image:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url',
+ 'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo',
+ 'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryImageInfo.php 44121 2008-12-01 17:14:30Z vyznev $';
+ return __CLASS__ . ': $Id: ApiQueryImageInfo.php 50097 2009-05-01 06:35:57Z tstarling $';
}
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index 02fe24f1..69569e9b 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -82,9 +82,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__);
if (is_null($resultPageSet)) {
-
- $data = array();
- $lastId = 0; // database has no ID 0
$count = 0;
while ($row = $db->fetchObject($res)) {
if (++$count > $params['limit']) {
@@ -94,23 +91,16 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
'|' . $this->keyToTitle($row->il_to));
break;
}
- if ($lastId != $row->il_from) {
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
- $data = array();
- }
- $lastId = $row->il_from;
- }
-
$vals = array();
ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle(NS_FILE, $row->il_to));
- $data[] = $vals;
- }
-
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
+ $fit = $this->addPageSubItem($row->il_from, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue', $row->il_from .
+ '|' . $this->keyToTitle($row->il_to));
+ break;
+ }
}
-
} else {
$titles = array();
@@ -165,6 +155,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryImages.php 44121 2008-12-01 17:14:30Z vyznev $';
+ return __CLASS__ . ': $Id: ApiQueryImages.php 46845 2009-02-05 14:30:59Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 0c5c72fc..b7affabc 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -34,6 +34,10 @@ if (!defined('MEDIAWIKI')) {
* @ingroup API
*/
class ApiQueryInfo extends ApiQueryBase {
+
+ private $fld_protection = false, $fld_talkid = false,
+ $fld_subjectid = false, $fld_url = false,
+ $fld_readable = false;
public function __construct($query, $moduleName) {
parent :: __construct($query, $moduleName, 'in');
@@ -49,11 +53,13 @@ class ApiQueryInfo extends ApiQueryBase {
$pageSet->requestField('page_len');
}
+ /**
+ * Get an array mapping token names to their handler functions.
+ * The prototype for a token function is func($pageid, $title)
+ * it should return a token or false (permission denied)
+ * @return array(tokenname => function)
+ */
protected function getTokenFunctions() {
- // tokenname => function
- // function prototype is func($pageid, $title)
- // should return token or false
-
// Don't call the hooks twice
if(isset($this->tokenFunctions))
return $this->tokenFunctions;
@@ -70,6 +76,7 @@ class ApiQueryInfo extends ApiQueryBase {
'block' => array( 'ApiQueryInfo', 'getBlockToken' ),
'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ),
'email' => array( 'ApiQueryInfo', 'getEmailToken' ),
+ 'import' => array( 'ApiQueryInfo', 'getImportToken' ),
);
wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions));
return $this->tokenFunctions;
@@ -79,6 +86,7 @@ class ApiQueryInfo extends ApiQueryBase {
{
// We could check for $title->userCan('edit') here,
// but that's too expensive for this purpose
+ // and would break caching
global $wgUser;
if(!$wgUser->isAllowed('edit'))
return false;
@@ -167,76 +175,201 @@ class ApiQueryInfo extends ApiQueryBase {
$cachedEmailToken = $wgUser->editToken();
return $cachedEmailToken;
}
+
+ public static function getImportToken($pageid, $title)
+ {
+ global $wgUser;
+ if(!$wgUser->isAllowed('import'))
+ return false;
- public function execute() {
+ static $cachedImportToken = null;
+ if(!is_null($cachedImportToken))
+ return $cachedImportToken;
- global $wgUser;
+ $cachedImportToken = $wgUser->editToken();
+ return $cachedImportToken;
+ }
- $params = $this->extractRequestParams();
- $fld_protection = $fld_talkid = $fld_subjectid = $fld_url = $fld_readable = false;
- if(!is_null($params['prop'])) {
- $prop = array_flip($params['prop']);
- $fld_protection = isset($prop['protection']);
- $fld_talkid = isset($prop['talkid']);
- $fld_subjectid = isset($prop['subjectid']);
- $fld_url = isset($prop['url']);
- $fld_readable = isset($prop['readable']);
+ public function execute() {
+ $this->params = $this->extractRequestParams();
+ if(!is_null($this->params['prop'])) {
+ $prop = array_flip($this->params['prop']);
+ $this->fld_protection = isset($prop['protection']);
+ $this->fld_talkid = isset($prop['talkid']);
+ $this->fld_subjectid = isset($prop['subjectid']);
+ $this->fld_url = isset($prop['url']);
+ $this->fld_readable = isset($prop['readable']);
}
$pageSet = $this->getPageSet();
- $titles = $pageSet->getGoodTitles();
- $missing = $pageSet->getMissingTitles();
+ $this->titles = $pageSet->getGoodTitles();
+ $this->missing = $pageSet->getMissingTitles();
+ $this->everything = $this->titles + $this->missing;
$result = $this->getResult();
- $pageRestrictions = $pageSet->getCustomField('page_restrictions');
- $pageIsRedir = $pageSet->getCustomField('page_is_redirect');
- $pageIsNew = $pageSet->getCustomField('page_is_new');
- $pageCounter = $pageSet->getCustomField('page_counter');
- $pageTouched = $pageSet->getCustomField('page_touched');
- $pageLatest = $pageSet->getCustomField('page_latest');
- $pageLength = $pageSet->getCustomField('page_len');
+ uasort($this->everything, array('Title', 'compare'));
+ if(!is_null($this->params['continue']))
+ {
+ // 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");
+ $conttitle = Title::makeTitleSafe($cont[0], $cont[1]);
+ foreach($this->everything as $pageid => $title)
+ {
+ if(Title::compare($title, $conttitle) >= 0)
+ break;
+ unset($this->titles[$pageid]);
+ unset($this->missing[$pageid]);
+ unset($this->everything[$pageid]);
+ }
+ }
+
+ $this->pageRestrictions = $pageSet->getCustomField('page_restrictions');
+ $this->pageIsRedir = $pageSet->getCustomField('page_is_redirect');
+ $this->pageIsNew = $pageSet->getCustomField('page_is_new');
+ $this->pageCounter = $pageSet->getCustomField('page_counter');
+ $this->pageTouched = $pageSet->getCustomField('page_touched');
+ $this->pageLatest = $pageSet->getCustomField('page_latest');
+ $this->pageLength = $pageSet->getCustomField('page_len');
$db = $this->getDB();
- if ($fld_protection && count($titles)) {
- $this->addTables('page_restrictions');
- $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade'));
- $this->addWhereFld('pr_page', array_keys($titles));
+ // Get protection info if requested
+ if ($this->fld_protection)
+ $this->getProtectionInfo();
+
+ // Run the talkid/subjectid query if requested
+ if($this->fld_talkid || $this->fld_subjectid)
+ $this->getTSIDs();
+
+ foreach($this->everything as $pageid => $title) {
+ $pageInfo = $this->extractPageInfo($pageid, $title);
+ $fit = $result->addValue(array (
+ 'query',
+ 'pages'
+ ), $pageid, $pageInfo);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue',
+ $title->getNamespace() . '|' .
+ $title->getText());
+ break;
+ }
+ }
+ }
+
+ /**
+ * Get a result array with information about a title
+ * @param $pageid int Page ID (negative for missing titles)
+ * @param $title Title object
+ * @return array
+ */
+ private function extractPageInfo($pageid, $title)
+ {
+ $pageInfo = array();
+ if($title->exists())
+ {
+ $pageInfo['touched'] = wfTimestamp(TS_ISO_8601, $this->pageTouched[$pageid]);
+ $pageInfo['lastrevid'] = intval($this->pageLatest[$pageid]);
+ $pageInfo['counter'] = intval($this->pageCounter[$pageid]);
+ $pageInfo['length'] = intval($this->pageLength[$pageid]);
+ if ($this->pageIsRedir[$pageid])
+ $pageInfo['redirect'] = '';
+ if ($this->pageIsNew[$pageid])
+ $pageInfo['new'] = '';
+ }
+
+ if (!is_null($this->params['token'])) {
+ $tokenFunctions = $this->getTokenFunctions();
+ $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601, time());
+ foreach($this->params['token'] as $t)
+ {
+ $val = call_user_func($tokenFunctions[$t], $pageid, $title);
+ if($val === false)
+ $this->setWarning("Action '$t' is not allowed for the current user");
+ else
+ $pageInfo[$t . 'token'] = $val;
+ }
+ }
+
+ if($this->fld_protection) {
+ $pageInfo['protection'] = array();
+ if (isset($this->protections[$title->getNamespace()][$title->getDBkey()]))
+ $pageInfo['protection'] =
+ $this->protections[$title->getNamespace()][$title->getDBkey()];
+ $this->getResult()->setIndexedTagName($pageInfo['protection'], 'pr');
+ }
+ if($this->fld_talkid && isset($this->talkids[$title->getNamespace()][$title->getDBKey()]))
+ $pageInfo['talkid'] = $this->talkids[$title->getNamespace()][$title->getDBKey()];
+ if($this->fld_subjectid && isset($this->subjectids[$title->getNamespace()][$title->getDBKey()]))
+ $pageInfo['subjectid'] = $this->subjectids[$title->getNamespace()][$title->getDBKey()];
+ if($this->fld_url) {
+ $pageInfo['fullurl'] = $title->getFullURL();
+ $pageInfo['editurl'] = $title->getFullURL('action=edit');
+ }
+ if($this->fld_readable)
+ if($title->userCanRead())
+ $pageInfo['readable'] = '';
+ return $pageInfo;
+ }
+
+ /**
+ * Get information about protections and put it in $protections
+ */
+ private function getProtectionInfo()
+ {
+ $this->protections = array();
+ $db = $this->getDB();
+
+ // Get normal protections for existing titles
+ if(count($this->titles))
+ {
+ $this->addTables(array('page_restrictions', 'page'));
+ $this->addWhere('page_id=pr_page');
+ $this->addFields(array('pr_page', 'pr_type', 'pr_level',
+ 'pr_expiry', 'pr_cascade', 'page_namespace',
+ 'page_title'));
+ $this->addWhereFld('pr_page', array_keys($this->titles));
$res = $this->select(__METHOD__);
while($row = $db->fetchObject($res)) {
$a = array(
'type' => $row->pr_type,
'level' => $row->pr_level,
- 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 )
+ 'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601)
);
if($row->pr_cascade)
$a['cascade'] = '';
- $protections[$row->pr_page][] = $a;
-
+ $this->protections[$row->page_namespace][$row->page_title][] = $a;
+
# Also check old restrictions
- if($pageRestrictions[$row->pr_page]) {
- foreach(explode(':', trim($pageRestrictions[$pageid])) as $restrict) {
+ if($this->pageRestrictions[$row->pr_page]) {
+ $restrictions = explode(':', trim($this->pageRestrictions[$row->pr_page]));
+ foreach($restrictions as $restrict) {
$temp = explode('=', trim($restrict));
if(count($temp) == 1) {
// old old format should be treated as edit/move restriction
- $restriction = trim( $temp[0] );
+ $restriction = trim($temp[0]);
+
if($restriction == '')
continue;
- $protections[$row->pr_page][] = array(
+ $this->protections[$row->page_namespace][$row->page_title][] = array(
'type' => 'edit',
'level' => $restriction,
'expiry' => 'infinity',
);
- $protections[$row->pr_page][] = array(
+ $this->protections[$row->page_namespace][$row->page_title][] = array(
'type' => 'move',
'level' => $restriction,
'expiry' => 'infinity',
);
} else {
- $restriction = trim( $temp[1] );
+ $restriction = trim($temp[1]);
if($restriction == '')
continue;
- $protections[$row->pr_page][] = array(
+ $this->protections[$row->page_namespace][$row->page_title][] = array(
'type' => $temp[0],
'level' => $restriction,
'expiry' => 'infinity',
@@ -246,272 +379,123 @@ class ApiQueryInfo extends ApiQueryBase {
}
}
$db->freeResult($res);
-
- $imageIds = array();
- foreach ($titles as $id => $title)
- if ($title->getNamespace() == NS_FILE)
- $imageIds[] = $id;
- // To avoid code duplication
- $cascadeTypes = array(
- array(
- 'prefix' => 'tl',
- 'table' => 'templatelinks',
- 'ns' => 'tl_namespace',
- 'title' => 'tl_title',
- 'ids' => array_diff(array_keys($titles), $imageIds)
- ),
- array(
- 'prefix' => 'il',
- 'table' => 'imagelinks',
- 'ns' => NS_FILE,
- 'title' => 'il_to',
- 'ids' => $imageIds
- )
- );
-
- foreach ($cascadeTypes as $type)
- {
- if (count($type['ids']) != 0) {
- $this->resetQueryParams();
- $this->addTables(array('page_restrictions', $type['table']));
- $this->addTables('page', 'page_source');
- $this->addTables('page', 'page_target');
- $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
- 'page_target.page_id AS page_target_id',
- 'page_source.page_namespace AS page_source_namespace',
- 'page_source.page_title AS page_source_title'));
- $this->addWhere(array("{$type['prefix']}_from = pr_page",
- 'page_target.page_namespace = '.$type['ns'],
- 'page_target.page_title = '.$type['title'],
- 'page_source.page_id = pr_page'
- ));
- $this->addWhereFld('pr_cascade', 1);
- $this->addWhereFld('page_target.page_id', $type['ids']);
-
- $res = $this->select(__METHOD__);
- while($row = $db->fetchObject($res)) {
- $source = Title::makeTitle($row->page_source_namespace, $row->page_source_title);
- $a = array(
- 'type' => $row->pr_type,
- 'level' => $row->pr_level,
- 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
- 'source' => $source->getPrefixedText()
- );
- $protections[$row->page_target_id][] = $a;
- }
- $db->freeResult($res);
- }
- }
}
- // We don't need to check for pt stuff if there are no nonexistent titles
- if($fld_protection && count($missing))
+ // Get protections for missing titles
+ if(count($this->missing))
{
$this->resetQueryParams();
- // Construct a custom WHERE clause that matches all titles in $missing
- $lb = new LinkBatch($missing);
+ $lb = new LinkBatch($this->missing);
$this->addTables('protected_titles');
$this->addFields(array('pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry'));
$this->addWhere($lb->constructSet('pt', $db));
$res = $this->select(__METHOD__);
- $prottitles = array();
while($row = $db->fetchObject($res)) {
- $prottitles[$row->pt_namespace][$row->pt_title][] = array(
+ $this->protections[$row->pt_namespace][$row->pt_title][] = array(
'type' => 'create',
'level' => $row->pt_create_perm,
'expiry' => Block::decodeExpiry($row->pt_expiry, TS_ISO_8601)
);
}
$db->freeResult($res);
-
- $images = array();
- $others = array();
- foreach ($missing as $title)
- if ($title->getNamespace() == NS_FILE)
- $images[] = $title->getDBKey();
- else
- $others[] = $title;
-
- if (count($others) != 0) {
- $lb = new LinkBatch($others);
- $this->resetQueryParams();
- $this->addTables(array('page_restrictions', 'page', 'templatelinks'));
- $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
- 'page_title', 'page_namespace',
- 'tl_title', 'tl_namespace'));
- $this->addWhere($lb->constructSet('tl', $db));
- $this->addWhere('pr_page = page_id');
- $this->addWhere('pr_page = tl_from');
- $this->addWhereFld('pr_cascade', 1);
-
- $res = $this->select(__METHOD__);
- while($row = $db->fetchObject($res)) {
- $source = Title::makeTitle($row->page_namespace, $row->page_title);
- $a = array(
- 'type' => $row->pr_type,
- 'level' => $row->pr_level,
- 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
- 'source' => $source->getPrefixedText()
- );
- $prottitles[$row->tl_namespace][$row->tl_title][] = $a;
- }
- $db->freeResult($res);
- }
-
- if (count($images) != 0) {
- $this->resetQueryParams();
- $this->addTables(array('page_restrictions', 'page', 'imagelinks'));
- $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
- 'page_title', 'page_namespace', 'il_to'));
- $this->addWhere('pr_page = page_id');
- $this->addWhere('pr_page = il_from');
- $this->addWhereFld('pr_cascade', 1);
- $this->addWhereFld('il_to', $images);
-
- $res = $this->select(__METHOD__);
- while($row = $db->fetchObject($res)) {
- $source = Title::makeTitle($row->page_namespace, $row->page_title);
- $a = array(
- 'type' => $row->pr_type,
- 'level' => $row->pr_level,
- 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
- 'source' => $source->getPrefixedText()
- );
- $prottitles[NS_FILE][$row->il_to][] = $a;
- }
- $db->freeResult($res);
- }
- }
-
- // Run the talkid/subjectid query
- if($fld_talkid || $fld_subjectid)
- {
- $talktitles = $subjecttitles =
- $talkids = $subjectids = array();
- $everything = array_merge($titles, $missing);
- foreach($everything as $t)
- {
- if(MWNamespace::isTalk($t->getNamespace()))
- {
- if($fld_subjectid)
- $subjecttitles[] = $t->getSubjectPage();
- }
- else if($fld_talkid)
- $talktitles[] = $t->getTalkPage();
- }
- if(count($talktitles) || count($subjecttitles))
- {
- // Construct a custom WHERE clause that matches
- // all titles in $talktitles and $subjecttitles
- $lb = new LinkBatch(array_merge($talktitles, $subjecttitles));
- $this->resetQueryParams();
- $this->addTables('page');
- $this->addFields(array('page_title', 'page_namespace', 'page_id'));
- $this->addWhere($lb->constructSet('page', $db));
- $res = $this->select(__METHOD__);
- while($row = $db->fetchObject($res))
- {
- if(MWNamespace::isTalk($row->page_namespace))
- $talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] = $row->page_id;
- else
- $subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] = $row->page_id;
- }
- }
}
- foreach ( $titles as $pageid => $title ) {
- $pageInfo = array (
- 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]),
- 'lastrevid' => intval($pageLatest[$pageid]),
- 'counter' => intval($pageCounter[$pageid]),
- 'length' => intval($pageLength[$pageid]),
- );
-
- if ($pageIsRedir[$pageid])
- $pageInfo['redirect'] = '';
-
- if ($pageIsNew[$pageid])
- $pageInfo['new'] = '';
+ // Cascading protections
+ $images = $others = array();
+ foreach ($this->everything as $title)
+ if ($title->getNamespace() == NS_FILE)
+ $images[] = $title->getDBKey();
+ else
+ $others[] = $title;
+
+ if (count($others)) {
+ // Non-images: check templatelinks
+ $lb = new LinkBatch($others);
+ $this->resetQueryParams();
+ $this->addTables(array('page_restrictions', 'page', 'templatelinks'));
+ $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
+ 'page_title', 'page_namespace',
+ 'tl_title', 'tl_namespace'));
+ $this->addWhere($lb->constructSet('tl', $db));
+ $this->addWhere('pr_page = page_id');
+ $this->addWhere('pr_page = tl_from');
+ $this->addWhereFld('pr_cascade', 1);
- if (!is_null($params['token'])) {
- $tokenFunctions = $this->getTokenFunctions();
- $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601, time());
- foreach($params['token'] as $t)
- {
- $val = call_user_func($tokenFunctions[$t], $pageid, $title);
- if($val === false)
- $this->setWarning("Action '$t' is not allowed for the current user");
- else
- $pageInfo[$t . 'token'] = $val;
- }
+ $res = $this->select(__METHOD__);
+ while($row = $db->fetchObject($res)) {
+ $source = Title::makeTitle($row->page_namespace, $row->page_title);
+ $this->protections[$row->tl_namespace][$row->tl_title][] = array(
+ 'type' => $row->pr_type,
+ 'level' => $row->pr_level,
+ 'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601),
+ 'source' => $source->getPrefixedText()
+ );
}
+ $db->freeResult($res);
+ }
- if($fld_protection) {
- $pageInfo['protection'] = array();
- if (isset($protections[$pageid])) {
- $pageInfo['protection'] = $protections[$pageid];
- $result->setIndexedTagName($pageInfo['protection'], 'pr');
- }
- }
- if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()]))
- $pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()];
- if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()]))
- $pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()];
- if($fld_url) {
- $pageInfo['fullurl'] = $title->getFullURL();
- $pageInfo['editurl'] = $title->getFullURL('action=edit');
+ if (count($images)) {
+ // Images: check imagelinks
+ $this->resetQueryParams();
+ $this->addTables(array('page_restrictions', 'page', 'imagelinks'));
+ $this->addFields(array('pr_type', 'pr_level', 'pr_expiry',
+ 'page_title', 'page_namespace', 'il_to'));
+ $this->addWhere('pr_page = page_id');
+ $this->addWhere('pr_page = il_from');
+ $this->addWhereFld('pr_cascade', 1);
+ $this->addWhereFld('il_to', $images);
+
+ $res = $this->select(__METHOD__);
+ while($row = $db->fetchObject($res)) {
+ $source = Title::makeTitle($row->page_namespace, $row->page_title);
+ $this->protections[NS_FILE][$row->il_to][] = array(
+ 'type' => $row->pr_type,
+ 'level' => $row->pr_level,
+ 'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601),
+ 'source' => $source->getPrefixedText()
+ );
}
- if($fld_readable)
- if($title->userCanRead())
- $pageInfo['readable'] = '';
-
- $result->addValue(array (
- 'query',
- 'pages'
- ), $pageid, $pageInfo);
+ $db->freeResult($res);
}
+ }
- // Get properties for missing titles if requested
- if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid ||
- $fld_url || $fld_readable)
+ /**
+ * Get talk page IDs (if requested) and subject page IDs (if requested)
+ * and put them in $talkids and $subjectids
+ */
+ private function getTSIDs()
+ {
+ $getTitles = $this->talkids = $this->subjectids = array();
+ $db = $this->getDB();
+ foreach($this->everything as $t)
{
- $res = &$result->getData();
- foreach($missing as $pageid => $title) {
- if(!is_null($params['token']))
- {
- $tokenFunctions = $this->getTokenFunctions();
- $res['query']['pages'][$pageid]['starttimestamp'] = wfTimestamp(TS_ISO_8601, time());
- foreach($params['token'] as $t)
- {
- $val = call_user_func($tokenFunctions[$t], $pageid, $title);
- if($val === false)
- $this->setWarning("Action '$t' is not allowed for the current user");
- else
- $res['query']['pages'][$pageid][$t . 'token'] = $val;
- }
- }
- if($fld_protection)
- {
- // Apparently the XML formatting code doesn't like array(null)
- // This is painful to fix, so we'll just work around it
- if(isset($prottitles[$title->getNamespace()][$title->getDBkey()]))
- $res['query']['pages'][$pageid]['protection'] = $prottitles[$title->getNamespace()][$title->getDBkey()];
- else
- $res['query']['pages'][$pageid]['protection'] = array();
- $result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr');
- }
- if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()]))
- $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()];
- if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()]))
- $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()];
- if($fld_url) {
- $res['query']['pages'][$pageid]['fullurl'] = $title->getFullURL();
- $res['query']['pages'][$pageid]['editurl'] = $title->getFullURL('action=edit');
- }
- if($fld_readable)
- if($title->userCanRead())
- $res['query']['pages'][$pageid]['readable'] = '';
+ if(MWNamespace::isTalk($t->getNamespace()))
+ {
+ if($this->fld_subjectid)
+ $getTitles[] = $t->getSubjectPage();
}
+ else if($this->fld_talkid)
+ $getTitles[] = $t->getTalkPage();
+ }
+ if(!count($getTitles))
+ return;
+
+ // Construct a custom WHERE clause that matches
+ // all titles in $getTitles
+ $lb = new LinkBatch($getTitles);
+ $this->resetQueryParams();
+ $this->addTables('page');
+ $this->addFields(array('page_title', 'page_namespace', 'page_id'));
+ $this->addWhere($lb->constructSet('page', $db));
+ $res = $this->select(__METHOD__);
+ while($row = $db->fetchObject($res))
+ {
+ if(MWNamespace::isTalk($row->page_namespace))
+ $this->talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] =
+ intval($row->page_id);
+ else
+ $this->subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] =
+ intval($row->page_id);
}
}
@@ -531,7 +515,8 @@ class ApiQueryInfo extends ApiQueryBase {
ApiBase :: PARAM_DFLT => NULL,
ApiBase :: PARAM_ISMULTI => true,
ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions())
- )
+ ),
+ 'continue' => null,
);
}
@@ -539,15 +524,15 @@ class ApiQueryInfo extends ApiQueryBase {
return array (
'prop' => array (
'Which additional properties to get:',
- ' "protection" - List the protection level of each page',
- ' "talkid" - The page ID of the talk page for each non-talk page',
- ' "subjectid" - The page ID of the parent page for each talk page'
+ ' protection - List the protection level of each page',
+ ' talkid - The page ID of the talk page for each non-talk page',
+ ' subjectid - The page ID of the parent page for each talk page'
),
'token' => 'Request a token to perform a data-modifying action on a page',
+ 'continue' => 'When more results are available, use this to continue',
);
}
-
public function getDescription() {
return 'Get basic page information such as namespace, title, last touched date, ...';
}
@@ -560,6 +545,6 @@ class ApiQueryInfo extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryInfo.php 45683 2009-01-12 19:10:42Z raymond $';
+ return __CLASS__ . ': $Id: ApiQueryInfo.php 48488 2009-03-17 15:18:26Z catrope $';
}
}
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index 8eaf8d02..3abe5e3d 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -71,8 +71,6 @@ class ApiQueryLangLinks extends ApiQueryBase {
$this->addOption('LIMIT', $params['limit'] + 1);
$res = $this->select(__METHOD__);
- $data = array();
- $lastId = 0; // database has no ID 0
$count = 0;
$db = $this->getDB();
while ($row = $db->fetchObject($res)) {
@@ -82,23 +80,15 @@ class ApiQueryLangLinks extends ApiQueryBase {
$this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}");
break;
}
- if ($lastId != $row->ll_from) {
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
- $data = array();
- }
- $lastId = $row->ll_from;
- }
-
$entry = array('lang' => $row->ll_lang);
ApiResult :: setContent($entry, $row->ll_title);
- $data[] = $entry;
- }
-
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
+ $fit = $this->addPageSubItem($row->ll_from, $entry);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}");
+ break;
+ }
}
-
$db->freeResult($res);
}
@@ -134,6 +124,6 @@ class ApiQueryLangLinks extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLangLinks.php 43271 2008-11-06 22:38:42Z siebrand $';
+ return __CLASS__ . ': $Id: ApiQueryLangLinks.php 46845 2009-02-05 14:30:59Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 91b5b529..40a7c114 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -119,9 +119,6 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__);
if (is_null($resultPageSet)) {
-
- $data = array();
- $lastId = 0; // database has no ID 0
$count = 0;
while ($row = $db->fetchObject($res)) {
if(++$count > $params['limit']) {
@@ -132,23 +129,17 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->keyToTitle($row->pl_title));
break;
}
- if ($lastId != $row->pl_from) {
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
- $data = array();
- }
- $lastId = $row->pl_from;
- }
-
$vals = array();
ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->pl_namespace, $row->pl_title));
- $data[] = $vals;
- }
-
- if($lastId != 0) {
- $this->addPageSubItems($lastId, $data);
+ $fit = $this->addPageSubItem($row->pl_from, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue',
+ "{$row->pl_from}|{$row->pl_namespace}|" .
+ $this->keyToTitle($row->pl_title));
+ break;
+ }
}
-
} else {
$titles = array();
@@ -213,6 +204,6 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLinks.php 43271 2008-11-06 22:38:42Z siebrand $';
+ return __CLASS__ . ': $Id: ApiQueryLinks.php 46845 2009-02-05 14:30:59Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index 83c73b83..864aaa03 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -59,18 +59,21 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addWhere($hideLogs);
// Order is significant here
- $this->addTables(array('user', 'page', 'logging'));
+ $this->addTables(array('logging', 'user', 'page'));
+ $this->addOption('STRAIGHT_JOIN');
$this->addJoinConds(array(
+ 'user' => array('JOIN',
+ 'user_id=log_user'),
'page' => array('LEFT JOIN',
array( 'log_namespace=page_namespace',
'log_title=page_title'))));
- $this->addWhere('user_id=log_user');
- $this->addOption('USE INDEX', array('logging' => 'times')); // default, may change
+ $index = 'times'; // default, may change
$this->addFields(array (
'log_type',
'log_action',
'log_timestamp',
+ 'log_deleted',
));
$this->addFieldsIf('log_id', $this->fld_ids);
@@ -81,20 +84,17 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addFieldsIf('log_title', $this->fld_title);
$this->addFieldsIf('log_comment', $this->fld_comment);
$this->addFieldsIf('log_params', $this->fld_details);
-
- $this->addWhereFld('log_deleted', 0);
if( !is_null($params['type']) ) {
$this->addWhereFld('log_type', $params['type']);
- $this->addOption('USE INDEX', array('logging' => array('type_time')));
+ $index = 'type_time';
}
$this->addWhereRange('log_timestamp', $params['dir'], $params['start'], $params['end']);
$limit = $params['limit'];
$this->addOption('LIMIT', $limit +1);
-
- $index = false;
+
$user = $params['user'];
if (!is_null($user)) {
$userid = User::idFromName($user);
@@ -113,14 +113,19 @@ class ApiQueryLogEvents extends ApiQueryBase {
$this->addWhereFld('log_title', $titleObj->getDBkey());
// Use the title index in preference to the user index if there is a conflict
- $index = 'page_time';
- }
- if ( $index ) {
- $this->addOption( 'USE INDEX', array( 'logging' => $index ) );
+ $index = is_null($user) ? 'page_time' : array('page_time','user_time');
}
+ $this->addOption( 'USE INDEX', array( 'logging' => $index ) );
+
+ // Paranoia: avoid brute force searches (bug 17342)
+ if (!is_null($title)) {
+ $this->addWhere('log_deleted & ' . LogPage::DELETED_ACTION . ' = 0');
+ }
+ if (!is_null($user)) {
+ $this->addWhere('log_deleted & ' . LogPage::DELETED_USER . ' = 0');
+ }
- $data = array ();
$count = 0;
$res = $this->select(__METHOD__);
while ($row = $db->fetchObject($res)) {
@@ -131,13 +136,18 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
$vals = $this->extractRowInfo($row);
- if($vals)
- $data[] = $vals;
+ if(!$vals)
+ continue;
+ $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->log_timestamp));
+ break;
+ }
}
$db->freeResult($res);
- $this->getResult()->setIndexedTagName($data, 'item');
- $this->getResult()->addValue('query', $this->getModuleName(), $data);
+ $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item');
}
public static function addLogParams($result, &$vals, $params, $type, $ts) {
@@ -150,9 +160,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
$vals2 = array();
ApiQueryBase :: addTitleInfo($vals2, $title, "new_");
$vals[$type] = $vals2;
- $params = null;
}
}
+ if (isset ($params[1]) && $params[1]) {
+ $vals[$type]['suppressedredirect'] = '';
+ }
+ $params = null;
break;
case 'patrol':
$vals2 = array();
@@ -191,8 +204,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
if ($this->fld_title) {
- $title = Title :: makeTitle($row->log_namespace, $row->log_title);
- ApiQueryBase :: addTitleInfo($vals, $title);
+ if (LogEventsList::isDeleted($row, LogPage::DELETED_ACTION)) {
+ $vals['actionhidden'] = '';
+ } else {
+ $title = Title :: makeTitle($row->log_namespace, $row->log_title);
+ ApiQueryBase :: addTitleInfo($vals, $title);
+ }
}
if ($this->fld_type) {
@@ -201,21 +218,33 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
if ($this->fld_details && $row->log_params !== '') {
- self::addLogParams($this->getResult(), $vals,
- $row->log_params, $row->log_type,
- $row->log_timestamp);
+ if (LogEventsList::isDeleted($row, LogPage::DELETED_ACTION)) {
+ $vals['actionhidden'] = '';
+ } else {
+ self::addLogParams($this->getResult(), $vals,
+ $row->log_params, $row->log_type,
+ $row->log_timestamp);
+ }
}
if ($this->fld_user) {
- $vals['user'] = $row->user_name;
- if(!$row->log_user)
- $vals['anon'] = '';
+ if (LogEventsList::isDeleted($row, LogPage::DELETED_USER)) {
+ $vals['userhidden'] = '';
+ } else {
+ $vals['user'] = $row->user_name;
+ if(!$row->log_user)
+ $vals['anon'] = '';
+ }
}
if ($this->fld_timestamp) {
$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->log_timestamp);
}
if ($this->fld_comment && isset($row->log_comment)) {
- $vals['comment'] = $row->log_comment;
+ if (LogEventsList::isDeleted($row, LogPage::DELETED_COMMENT)) {
+ $vals['commenthidden'] = '';
+ } else {
+ $vals['comment'] = $row->log_comment;
+ }
}
return $vals;
@@ -290,6 +319,6 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLogEvents.php 44234 2008-12-04 15:59:26Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryLogEvents.php 47904 2009-03-01 11:02:49Z catrope $';
}
}
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
new file mode 100644
index 00000000..779deee5
--- /dev/null
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -0,0 +1,191 @@
+<?php
+
+/*
+ * Created on Feb 13, 2009
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiQueryBase.php');
+}
+
+/**
+ * Query module to enumerate all create-protected pages.
+ *
+ * @ingroup API
+ */
+class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'pt');
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator($resultPageSet) {
+ $this->run($resultPageSet);
+ }
+
+ private function run($resultPageSet = null) {
+ $db = $this->getDB();
+ $params = $this->extractRequestParams();
+
+ $this->addTables('protected_titles');
+ $this->addFields(array('pt_namespace', 'pt_title', 'pt_timestamp'));
+
+ $prop = array_flip($params['prop']);
+ $this->addFieldsIf('pt_user', isset($prop['user']));
+ $this->addFieldsIf('pt_reason', isset($prop['comment']));
+ $this->addFieldsIf('pt_expiry', isset($prop['expiry']));
+ $this->addFieldsIf('pt_create_perm', isset($prop['level']));
+
+ $this->addWhereRange('pt_timestamp', $params['dir'], $params['start'], $params['end']);
+ $this->addWhereFld('pt_namespace', $params['namespace']);
+ $this->addWhereFld('pt_create_perm', $params['level']);
+
+ if(isset($prop['user']))
+ {
+ $this->addTables('user');
+ $this->addFields('user_name');
+ $this->addJoinConds(array('user' => array('LEFT JOIN',
+ 'user_id=pt_user'
+ )));
+ }
+
+ $this->addOption('LIMIT', $params['limit'] + 1);
+ $res = $this->select(__METHOD__);
+
+ $count = 0;
+ $result = $this->getResult();
+ while ($row = $db->fetchObject($res)) {
+ 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->pt_timestamp));
+ break;
+ }
+
+ $title = Title::makeTitle($row->pt_namespace, $row->pt_title);
+ if (is_null($resultPageSet)) {
+ $vals = array();
+ ApiQueryBase::addTitleInfo($vals, $title);
+ if(isset($prop['timestamp']))
+ $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->pt_timestamp);
+ if(isset($prop['user']) && !is_null($row->user_name))
+ $vals['user'] = $row->user_name;
+ if(isset($prop['comment']))
+ $vals['comment'] = $row->pt_reason;
+ if(isset($prop['expiry']))
+ $vals['expiry'] = Block::decodeExpiry($row->pt_expiry, TS_ISO_8601);
+ if(isset($prop['level']))
+ $vals['level'] = $row->pt_create_perm;
+
+ $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('start',
+ wfTimestamp(TS_ISO_8601, $row->pt_timestamp));
+ break;
+ }
+ } else {
+ $titles[] = $title;
+ }
+ }
+ $db->freeResult($res);
+ if(is_null($resultPageSet))
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), $this->getModulePrefix());
+ else
+ $resultPageSet->populateFromTitles($titles);
+ }
+
+ public function getAllowedParams() {
+ global $wgRestrictionLevels;
+ return array (
+ 'namespace' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => 'namespace',
+ ),
+ 'level' => array(
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array_diff($wgRestrictionLevels, array(''))
+ ),
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+ ),
+ 'dir' => array (
+ ApiBase :: PARAM_DFLT => 'older',
+ ApiBase :: PARAM_TYPE => array (
+ 'older',
+ 'newer'
+ )
+ ),
+ 'start' => array(
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'end' => array(
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'prop' => array(
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_DFLT => 'timestamp|level',
+ ApiBase :: PARAM_TYPE => array(
+ 'timestamp',
+ 'user',
+ 'comment',
+ 'expiry',
+ 'level'
+ )
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array (
+ 'namespace' => 'Only list titles in these namespaces',
+ 'start' => 'Start listing at this protection timestamp',
+ 'end' => 'Stop listing at this protection timestamp',
+ 'dir' => 'The direction in which to list',
+ 'limit' => 'How many total pages to return.',
+ 'prop' => 'Which properties to get',
+ 'level' => 'Only list titles with these protection levels',
+ );
+ }
+
+ public function getDescription() {
+ return 'List all titles protected from creation';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&list=protectedtitles',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 47235 2009-02-13 21:53:08Z catrope $';
+ }
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index e7b8bf46..73c4a81c 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -62,7 +62,7 @@ if (!defined('MEDIAWIKI')) {
$this->addFields($resultPageSet->getPageTableFields());
}
- protected function runQuery(&$data, &$resultPageSet) {
+ protected function runQuery(&$resultPageSet) {
$db = $this->getDB();
$res = $this->select(__METHOD__);
$count = 0;
@@ -73,7 +73,14 @@ if (!defined('MEDIAWIKI')) {
// Prevent duplicates
if(!in_array($row->page_id, $this->pageIDs))
{
- $data[] = $this->extractRowInfo($row);
+ $fit = $this->getResult()->addValue(
+ array('query', $this->getModuleName()),
+ null, $this->extractRowInfo($row));
+ if(!$fit)
+ # We can't really query-continue a random list.
+ # Return an insanely high value so
+ # $count < $limit is false
+ return 1E9;
$this->pageIDs[] = $row->page_id;
}
}
@@ -87,11 +94,10 @@ if (!defined('MEDIAWIKI')) {
public function run($resultPageSet = null) {
$params = $this->extractRequestParams();
$result = $this->getResult();
- $data = array();
$this->pageIDs = array();
$this->prepareQuery(wfRandom(), $params['limit'], $params['namespace'], $resultPageSet, $params['redirect']);
- $count = $this->runQuery($data, $resultPageSet);
+ $count = $this->runQuery($resultPageSet);
if($count < $params['limit'])
{
/* We got too few pages, we probably picked a high value
@@ -99,21 +105,19 @@ if (!defined('MEDIAWIKI')) {
* also the comment in Title::getRandomTitle()
*/
$this->prepareQuery(0, $params['limit'] - $count, $params['namespace'], $resultPageSet, $params['redirect']);
- $this->runQuery($data, $resultPageSet);
+ $this->runQuery($resultPageSet);
}
if(is_null($resultPageSet)) {
- $result->setIndexedTagName($data, 'page');
- $result->addValue('query', $this->getModuleName(), $data);
+ $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page');
}
}
private function extractRowInfo($row) {
$title = Title::makeTitle($row->page_namespace, $row->page_title);
$vals = array();
- $vals['title'] = $title->getPrefixedText();
- $vals['ns'] = $row->page_namespace;
- $vals['id'] = $row->page_id;
+ $vals['id'] = intval($row->page_id);
+ ApiQueryBase::addTitleInfo($vals, $title);
return $vals;
}
@@ -157,4 +161,4 @@ if (!defined('MEDIAWIKI')) {
public function getVersion() {
return __CLASS__ . ': $Id: ApiQueryRandom.php overlordq$';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 04eb910f..191eec28 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -97,25 +97,6 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$this->addWhereRange('rc_timestamp', $params['dir'], $params['start'], $params['end']);
$this->addWhereFld('rc_namespace', $params['namespace']);
$this->addWhereFld('rc_deleted', 0);
- if($params['titles'])
- {
- $lb = new LinkBatch;
- foreach($params['titles'] as $t)
- {
- $obj = Title::newFromText($t);
- $lb->addObj($obj);
- if($obj->getNamespace() < 0)
- {
- // LinkBatch refuses these, but we need them anyway
- if(!array_key_exists($obj->getNamespace(), $lb->data))
- $lb->data[$obj->getNamespace()] = array();
- $lb->data[$obj->getNamespace()][$obj->getDBKey()] = 1;
- }
- }
- $where = $lb->constructSet('rc', $this->getDB());
- if($where != '')
- $this->addWhere($where);
- }
if(!is_null($params['type']))
$this->addWhereFld('rc_type', $this->parseRCType($params['type']));
@@ -210,9 +191,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$this->token = $params['token'];
$this->addOption('LIMIT', $params['limit'] +1);
- $data = array ();
$count = 0;
-
/* Perform the actual query. */
$db = $this->getDB();
$res = $this->select(__METHOD__);
@@ -229,16 +208,20 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$vals = $this->extractRowInfo($row);
/* Add that row's data to our final output. */
- if($vals)
- $data[] = $vals;
+ if(!$vals)
+ continue;
+ $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp));
+ break;
+ }
}
$db->freeResult($res);
/* Format the result */
- $result = $this->getResult();
- $result->setIndexedTagName($data, 'rc');
- $result->addValue('query', $this->getModuleName(), $data);
+ $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'rc');
}
/**
@@ -328,7 +311,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$vals['patrolled'] = '';
if ($this->fld_loginfo && $row->rc_type == RC_LOG) {
- $vals['logid'] = $row->rc_logid;
+ $vals['logid'] = intval($row->rc_logid);
$vals['logtype'] = $row->rc_log_type;
$vals['logaction'] = $row->rc_log_action;
ApiQueryLogEvents::addLogParams($this->getResult(),
@@ -389,9 +372,6 @@ class ApiQueryRecentChanges extends ApiQueryBase {
ApiBase :: PARAM_ISMULTI => true,
ApiBase :: PARAM_TYPE => 'namespace'
),
- 'titles' => array(
- ApiBase :: PARAM_ISMULTI => true
- ),
'prop' => array (
ApiBase :: PARAM_ISMULTI => true,
ApiBase :: PARAM_DFLT => 'title|timestamp|ids',
@@ -451,7 +431,6 @@ class ApiQueryRecentChanges extends ApiQueryBase {
'end' => 'The timestamp to end enumerating.',
'dir' => 'In which direction to enumerate.',
'namespace' => 'Filter log entries to only this namespace(s)',
- 'titles' => 'Filter log entries to only these page titles',
'prop' => 'Include additional pieces of information',
'token' => 'Which tokens to obtain for each change',
'show' => array (
@@ -474,6 +453,6 @@ class ApiQueryRecentChanges extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 44719 2008-12-17 16:34:01Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 50094 2009-05-01 06:24:09Z tstarling $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index 977e792b..ca9152ad 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -100,9 +100,29 @@ class ApiQueryRevisions extends ApiQueryBase {
if ($pageCount > 1 && $enumRevMode)
$this->dieUsage('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.', 'multpages');
+ if (!is_null($params['diffto'])) {
+ if ($params['diffto'] == 'cur')
+ $params['diffto'] = 0;
+ if ((!ctype_digit($params['diffto']) || $params['diffto'] < 0)
+ && $params['diffto'] != 'prev' && $params['diffto'] != 'next')
+ $this->dieUsage('rvdiffto must be set to a non-negative number, "prev", "next" or "cur"', 'diffto');
+ // Check whether the revision exists and is readable,
+ // DifferenceEngine returns a rather ambiguous empty
+ // string if that's not the case
+ if ($params['diffto'] != 0) {
+ $difftoRev = Revision::newFromID($params['diffto']);
+ if (!$difftoRev)
+ $this->dieUsageMsg(array('nosuchrevid', $params['diffto']));
+ if (!$difftoRev->userCan(Revision::DELETED_TEXT)) {
+ $this->setWarning("Couldn't diff to r{$difftoRev->getID()}: content is hidden");
+ $params['diffto'] = null;
+ }
+ }
+ }
+
$this->addTables('revision');
- $this->addFields( Revision::selectFields() );
- $this->addTables( 'page' );
+ $this->addFields(Revision::selectFields());
+ $this->addTables('page');
$this->addWhere('page_id = rev_page');
$prop = array_flip($params['prop']);
@@ -116,6 +136,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->fld_size = isset ($prop['size']);
$this->fld_user = isset ($prop['user']);
$this->token = $params['token'];
+ $this->diffto = $params['diffto'];
if ( !is_null($this->token) || $pageCount > 0) {
$this->addFields( Revision::selectPageFields() );
@@ -134,7 +155,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->addTables('text');
$this->addWhere('rev_text_id=old_id');
$this->addFields('old_id');
- $this->addFields( Revision::selectTextFields() );
+ $this->addFields(Revision::selectTextFields());
$this->fld_content = true;
@@ -176,9 +197,14 @@ class ApiQueryRevisions extends ApiQueryBase {
if (is_null($params['startid']) && is_null($params['endid']))
$this->addWhereRange('rev_timestamp', $params['dir'],
$params['start'], $params['end']);
- else
+ else {
$this->addWhereRange('rev_id', $params['dir'],
$params['startid'], $params['endid']);
+ // One of start and end can be set
+ // If neither is set, this does nothing
+ $this->addWhereRange('rev_timestamp', $params['dir'],
+ $params['start'], $params['end'], false);
+ }
// must manually initialize unset limit
if (is_null($limit))
@@ -186,14 +212,18 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->validateLimit('limit', $limit, 1, $userMax, $botMax);
// There is only one ID, use it
- $this->addWhereFld('rev_page', current(array_keys($pageSet->getGoodTitles())));
+ $this->addWhereFld('rev_page', reset(array_keys($pageSet->getGoodTitles())));
if(!is_null($params['user'])) {
$this->addWhereFld('rev_user_text', $params['user']);
- } elseif (!is_null( $params['excludeuser'])) {
+ } elseif (!is_null($params['excludeuser'])) {
$this->addWhere('rev_user_text != ' .
$this->getDB()->addQuotes($params['excludeuser']));
}
+ if(!is_null($params['user']) || !is_null($params['excludeuser'])) {
+ // Paranoia: avoid brute force searches (bug 17342)
+ $this->addWhere('rev_deleted & ' . Revision::DELETED_USER . ' = 0');
+ }
}
elseif ($revCount > 0) {
$max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
@@ -204,6 +234,10 @@ class ApiQueryRevisions extends ApiQueryBase {
// Get all revision IDs
$this->addWhereFld('rev_id', array_keys($revs));
+ if(!is_null($params['continue']))
+ $this->addWhere("rev_id >= '" . intval($params['continue']) . "'");
+ $this->addOption('ORDER BY', 'rev_id');
+
// assumption testing -- we should never get more then $revCount rows.
$limit = $revCount;
}
@@ -220,6 +254,22 @@ class ApiQueryRevisions extends ApiQueryBase {
// Get all page IDs
$this->addWhereFld('page_id', array_keys($titles));
+ // Every time someone relies on equality propagation, god kills a kitten :)
+ $this->addWhereFld('rev_page', array_keys($titles));
+
+ 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");
+ $pageid = intval($cont[0]);
+ $revid = intval($cont[1]);
+ $this->addWhere("rev_page > '$pageid' OR " .
+ "(rev_page = '$pageid' AND " .
+ "rev_id >= '$revid')");
+ }
+ $this->addOption('ORDER BY', 'rev_page, rev_id');
// assumption testing -- we should never get more then $pageCount rows.
$limit = $pageCount;
@@ -242,37 +292,30 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->setContinueEnumParameter('startid', intval($row->rev_id));
break;
}
-
$revision = new Revision( $row );
- $this->getResult()->addValue(
- array (
- 'query',
- 'pages',
- $revision->getPage(),
- 'revisions'),
- null,
- $this->extractRowInfo( $revision ));
- }
- $db->freeResult($res);
-
- // Ensure that all revisions are shown as '<rev>' elements
- $result = $this->getResult();
- if ($result->getIsRawMode()) {
- $data =& $result->getData();
- foreach ($data['query']['pages'] as & $page) {
- if (is_array($page) && array_key_exists('revisions', $page)) {
- $result->setIndexedTagName($page['revisions'], 'rev');
- }
+ //
+ $fit = $this->addPageSubItem($revision->getPage(), $this->extractRowInfo($revision), 'rev');
+ if(!$fit)
+ {
+ if($enumRevMode)
+ $this->setContinueEnumParameter('startid', intval($row->rev_id));
+ else if($revCount > 0)
+ $this->setContinueEnumParameter('continue', intval($row->rev_id));
+ else
+ $this->setContinueEnumParameter('continue', intval($row->rev_page) .
+ '|' . intval($row->rev_id));
+ break;
}
}
+ $db->freeResult($res);
}
private function extractRowInfo( $revision ) {
-
+ $title = $revision->getTitle();
$vals = array ();
if ($this->fld_ids) {
- $vals['revid'] = $revision->getId();
+ $vals['revid'] = intval($revision->getId());
// $vals['oldid'] = intval($row->rev_text_id); // todo: should this be exposed?
}
@@ -280,9 +323,13 @@ class ApiQueryRevisions extends ApiQueryBase {
$vals['minor'] = '';
if ($this->fld_user) {
- $vals['user'] = $revision->getUserText();
- if (!$revision->getUser())
- $vals['anon'] = '';
+ if ($revision->isDeleted(Revision::DELETED_USER)) {
+ $vals['userhidden'] = '';
+ } else {
+ $vals['user'] = $revision->getUserText();
+ if (!$revision->getUser())
+ $vals['anon'] = '';
+ }
}
if ($this->fld_timestamp) {
@@ -290,17 +337,18 @@ class ApiQueryRevisions extends ApiQueryBase {
}
if ($this->fld_size && !is_null($revision->getSize())) {
- $vals['size'] = $revision->getSize();
+ $vals['size'] = intval($revision->getSize());
}
if ($this->fld_comment) {
- $comment = $revision->getComment();
- if (strval($comment) !== '')
- $vals['comment'] = $comment;
- }
-
- if(!is_null($this->token) || ($this->fld_content && $this->expandTemplates))
- $title = $revision->getTitle();
+ if ($revision->isDeleted(Revision::DELETED_COMMENT)) {
+ $vals['commenthidden'] = '';
+ } else {
+ $comment = $revision->getComment();
+ if (strval($comment) !== '')
+ $vals['comment'] = $comment;
+ }
+ }
if(!is_null($this->token))
{
@@ -314,8 +362,8 @@ class ApiQueryRevisions extends ApiQueryBase {
$vals[$t . 'token'] = $val;
}
}
-
- if ($this->fld_content) {
+
+ if ($this->fld_content && !$revision->isDeleted(Revision::DELETED_TEXT)) {
global $wgParser;
$text = $revision->getText();
# Expand templates after getting section content because
@@ -341,6 +389,24 @@ class ApiQueryRevisions extends ApiQueryBase {
$text = $wgParser->preprocess( $text, $title, new ParserOptions() );
}
ApiResult :: setContent($vals, $text);
+ } else if ($this->fld_content) {
+ $vals['texthidden'] = '';
+ }
+
+ if (!is_null($this->diffto)) {
+ global $wgAPIMaxUncachedDiffs;
+ static $n = 0; // Numer of uncached diffs we've had
+ if($n< $wgAPIMaxUncachedDiffs) {
+ $engine = new DifferenceEngine($title, $revision->getID(), $this->diffto);
+ $difftext = $engine->getDiffBody();
+ $vals['diff']['from'] = $engine->getOldid();
+ $vals['diff']['to'] = $engine->getNewid();
+ ApiResult::setContent($vals['diff'], $difftext);
+ if(!$engine->wasCacheHit())
+ $n++;
+ } else {
+ $vals['diff']['notcached'] = '';
+ }
}
return $vals;
}
@@ -398,6 +464,8 @@ class ApiQueryRevisions extends ApiQueryBase {
ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()),
ApiBase :: PARAM_ISMULTI => true
),
+ 'continue' => null,
+ 'diffto' => null,
);
}
@@ -416,6 +484,9 @@ class ApiQueryRevisions extends ApiQueryBase {
'generatexml' => 'generate XML parse tree for revision content',
'section' => 'only retrieve the content of this section',
'token' => 'Which tokens to obtain for each revision',
+ 'continue' => 'When more results are available, use this to continue',
+ 'diffto' => array('Revision ID to diff each revision to.',
+ 'Use "prev", "next" and "cur" for the previous, next and current revision respectively.'),
);
}
@@ -448,6 +519,6 @@ class ApiQueryRevisions extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRevisions.php 44719 2008-12-17 16:34:01Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryRevisions.php 48642 2009-03-20 20:21:38Z midom $';
}
}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index cb020fff..668f00e5 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -87,7 +87,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$this->dieUsage("{$what} search is disabled",
"search-{$what}-disabled");
- $data = array ();
+ $titles = array ();
$count = 0;
while( $result = $matches->next() ) {
if (++ $count > $limit) {
@@ -102,20 +102,23 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$title = $result->getTitle();
if (is_null($resultPageSet)) {
- $data[] = array(
- 'ns' => intval($title->getNamespace()),
- 'title' => $title->getPrefixedText());
+ $vals = array();
+ ApiQueryBase::addTitleInfo($vals, $title);
+ $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('offset', $params['offset'] + $count - 1);
+ break;
+ }
} else {
- $data[] = $title;
+ $titles[] = $title;
}
}
if (is_null($resultPageSet)) {
- $result = $this->getResult();
- $result->setIndexedTagName($data, 'p');
- $result->addValue('query', $this->getModuleName(), $data);
+ $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'p');
} else {
- $resultPageSet->populateFromTitles($data);
+ $resultPageSet->populateFromTitles($titles);
}
}
@@ -170,6 +173,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQuerySearch.php 44186 2008-12-03 19:33:57Z catrope $';
+ return __CLASS__ . ': $Id: ApiQuerySearch.php 47865 2009-02-27 16:03:01Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index 84757f7f..6b867abb 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -41,44 +41,60 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
+ $done = array();
foreach( $params['prop'] as $p )
{
switch ( $p )
{
case 'general':
- $this->appendGeneralInfo( $p );
+ $fit = $this->appendGeneralInfo( $p );
break;
case 'namespaces':
- $this->appendNamespaces( $p );
+ $fit = $this->appendNamespaces( $p );
break;
case 'namespacealiases':
- $this->appendNamespaceAliases( $p );
+ $fit = $this->appendNamespaceAliases( $p );
break;
case 'specialpagealiases':
- $this->appendSpecialPageAliases( $p );
+ $fit = $this->appendSpecialPageAliases( $p );
break;
case 'magicwords':
- $this->appendMagicWords( $p );
+ $fit = $this->appendMagicWords( $p );
break;
case 'interwikimap':
$filteriw = isset( $params['filteriw'] ) ? $params['filteriw'] : false;
- $this->appendInterwikiMap( $p, $filteriw );
+ $fit = $this->appendInterwikiMap( $p, $filteriw );
break;
case 'dbrepllag':
- $this->appendDbReplLagInfo( $p, $params['showalldb'] );
+ $fit = $this->appendDbReplLagInfo( $p, $params['showalldb'] );
break;
case 'statistics':
- $this->appendStatistics( $p );
+ $fit = $this->appendStatistics( $p );
break;
case 'usergroups':
- $this->appendUserGroups( $p );
+ $fit = $this->appendUserGroups( $p );
break;
case 'extensions':
- $this->appendExtensions( $p );
+ $fit = $this->appendExtensions( $p );
+ break;
+ case 'fileextensions':
+ $fit = $this->appendFileExtensions( $p );
+ break;
+ case 'rightsinfo':
+ $fit = $this->appendRightsInfo( $p );
break;
default :
ApiBase :: dieDebug( __METHOD__, "Unknown prop=$p" );
}
+ if(!$fit)
+ {
+ # Abuse siprop as a query-continue parameter
+ # and set it to all unprocessed props
+ $this->setContinueEnumParameter('prop', implode('|',
+ array_diff($params['prop'], $done)));
+ break;
+ }
+ $done[] = $p;
}
}
@@ -121,9 +137,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$offset = 0;
}
$data['timezone'] = $tz;
- $data['timeoffset'] = $offset;
+ $data['timeoffset'] = intval($offset);
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendNamespaces( $property ) {
@@ -132,7 +148,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
foreach( $wgContLang->getFormattedNamespaces() as $ns => $title )
{
$data[$ns] = array(
- 'id' => $ns
+ 'id' => intval($ns)
);
ApiResult :: setContent( $data[$ns], $title );
$canonical = MWNamespace::getCanonicalName( $ns );
@@ -145,24 +161,29 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$this->getResult()->setIndexedTagName( $data, 'ns' );
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendNamespaceAliases( $property ) {
global $wgNamespaceAliases, $wgContLang;
$wgContLang->load();
- $aliases = array_merge($wgNamespaceAliases, $wgContLang->namespaceAliases);
+ $aliases = array_merge( $wgNamespaceAliases, $wgContLang->namespaceAliases );
+ $namespaces = $wgContLang->getNamespaces();
$data = array();
foreach( $aliases as $title => $ns ) {
+ if( $namespaces[$ns] == $title ) {
+ // Don't list duplicates
+ continue;
+ }
$item = array(
- 'id' => $ns
+ 'id' => intval($ns)
);
ApiResult :: setContent( $item, strtr( $title, '_', ' ' ) );
$data[] = $item;
}
$this->getResult()->setIndexedTagName( $data, 'ns' );
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendSpecialPageAliases( $property ) {
@@ -175,7 +196,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data[] = $arr;
}
$this->getResult()->setIndexedTagName( $data, 'specialpage' );
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendMagicWords( $property ) {
@@ -191,7 +212,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data[] = $arr;
}
$this->getResult()->setIndexedTagName($data, 'magicword');
- $this->getResult()->addValue('query', $property, $data);
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendInterwikiMap( $property, $filter ) {
@@ -229,7 +250,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$db->freeResult( $res );
$this->getResult()->setIndexedTagName( $data, 'iw' );
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendDbReplLagInfo( $property, $includeAll ) {
@@ -251,27 +272,30 @@ class ApiQuerySiteinfo extends ApiQueryBase {
list( $host, $lag ) = wfGetLB()->getMaxLag();
$data[] = array(
'host' => $wgShowHostnames ? $host : '',
- 'lag' => $lag
+ 'lag' => intval( $lag )
);
}
$result = $this->getResult();
$result->setIndexedTagName( $data, 'db' );
- $result->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendStatistics( $property ) {
+ global $wgDisableCounters;
$data = array();
$data['pages'] = intval( SiteStats::pages() );
$data['articles'] = intval( SiteStats::articles() );
- $data['views'] = intval( SiteStats::views() );
+ if ( !$wgDisableCounters ) {
+ $data['views'] = intval( SiteStats::views() );
+ }
$data['edits'] = intval( SiteStats::edits() );
$data['images'] = intval( SiteStats::images() );
$data['users'] = intval( SiteStats::users() );
$data['activeusers'] = intval( SiteStats::activeUsers() );
$data['admins'] = intval( SiteStats::numberingroup('sysop') );
$data['jobs'] = intval( SiteStats::jobs() );
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendUserGroups( $property ) {
@@ -284,7 +308,18 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$this->getResult()->setIndexedTagName( $data, 'group' );
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
+ }
+
+ protected function appendFileExtensions( $property ) {
+ global $wgFileExtensions;
+
+ $data = array();
+ foreach( $wgFileExtensions as $ext ) {
+ $data[] = array( 'ext' => $ext );
+ }
+ $this->getResult()->setIndexedTagName( $data, 'fe' );
+ return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendExtensions( $property ) {
@@ -317,7 +352,25 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$this->getResult()->setIndexedTagName( $data, 'ext' );
- $this->getResult()->addValue( 'query', $property, $data );
+ return $this->getResult()->addValue( 'query', $property, $data );
+ }
+
+
+ protected function appendRightsInfo( $property ) {
+ global $wgRightsPage, $wgRightsUrl, $wgRightsText;
+ $title = Title::newFromText( $wgRightsPage );
+ $url = $title ? $title->getFullURL() : $wgRightsUrl;
+ $text = $wgRightsText;
+ if( !$text && $title ) {
+ $text = $title->getPrefixedText();
+ }
+
+ $data = array(
+ 'url' => $url ? $url : '',
+ 'text' => $text ? $text : ''
+ );
+
+ return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -337,6 +390,8 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'statistics',
'usergroups',
'extensions',
+ 'fileextensions',
+ 'rightsinfo',
)
),
'filteriw' => array(
@@ -353,16 +408,18 @@ class ApiQuerySiteinfo extends ApiQueryBase {
return array(
'prop' => array(
'Which sysinfo properties to get:',
- ' "general" - Overall system information',
- ' "namespaces" - List of registered namespaces and their canonical names',
- ' "namespacealiases" - List of registered namespace aliases',
- ' "specialpagealiases" - List of special page aliases',
- ' "magicwords" - List of magic words and their aliases',
- ' "statistics" - Returns site statistics',
- ' "interwikimap" - Returns interwiki map (optionally filtered)',
- ' "dbrepllag" - Returns database server with the highest replication lag',
- ' "usergroups" - Returns user groups and the associated permissions',
- ' "extensions" - Returns extensions installed on the wiki',
+ ' general - Overall system information',
+ ' namespaces - List of registered namespaces and their canonical names',
+ ' namespacealiases - List of registered namespace aliases',
+ ' specialpagealiases - List of special page aliases',
+ ' magicwords - List of magic words and their aliases',
+ ' statistics - Returns site statistics',
+ ' interwikimap - Returns interwiki map (optionally filtered)',
+ ' dbrepllag - Returns database server with the highest replication lag',
+ ' usergroups - Returns user groups and the associated permissions',
+ ' extensions - Returns extensions installed on the wiki',
+ ' fileextensions - Returns list of file extensions allowed to be uploaded',
+ ' rightsinfo - Returns wiki rights (license) information if available',
),
'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
@@ -382,6 +439,6 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 44862 2008-12-20 23:49:16Z catrope $';
+ return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 48060 2009-03-05 13:52:14Z demon $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index be6c8bc4..24c73996 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -41,7 +41,8 @@ class ApiQueryContributions extends ApiQueryBase {
private $params, $username;
private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
- $fld_comment = false, $fld_flags = false;
+ $fld_comment = false, $fld_flags = false,
+ $fld_patrolled = false;
public function execute() {
@@ -54,6 +55,7 @@ class ApiQueryContributions extends ApiQueryBase {
$this->fld_comment = isset($prop['comment']);
$this->fld_flags = isset($prop['flags']);
$this->fld_timestamp = isset($prop['timestamp']);
+ $this->fld_patrolled = isset($prop['patrolled']);
// TODO: if the query is going only against the revision table, should this be done?
$this->selectNamedDB('contributions', DB_SLAVE, 'contributions');
@@ -81,7 +83,6 @@ class ApiQueryContributions extends ApiQueryBase {
$res = $this->select( __METHOD__ );
//Initialise some variables
- $data = array ();
$count = 0;
$limit = $this->params['limit'];
@@ -97,16 +98,21 @@ class ApiQueryContributions extends ApiQueryBase {
}
$vals = $this->extractRowInfo($row);
- if ($vals)
- $data[] = $vals;
+ $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ if($this->multiUserMode)
+ $this->setContinueEnumParameter('continue', $this->continueStr($row));
+ else
+ $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp));
+ break;
+ }
}
//Free the database record so the connection can get on with other stuff
$db->freeResult($res);
- //And send the whole shebang out as output.
- $this->getResult()->setIndexedTagName($data, 'item');
- $this->getResult()->addValue('query', $this->getModuleName(), $data);
+ $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item');
}
/**
@@ -132,12 +138,12 @@ class ApiQueryContributions extends ApiQueryBase {
* Prepares the query and returns the limit of rows requested
*/
private function prepareQuery() {
-
- //We're after the revision table, and the corresponding page row for
- //anything we retrieve.
- $this->addTables(array('revision', 'page'));
+ // We're after the revision table, and the corresponding page
+ // row for anything we retrieve. We may also need the
+ // recentchanges row.
+ $tables = array('page', 'revision'); // Order may change
$this->addWhere('page_id=rev_page');
-
+
// Handle continue parameter
if($this->multiUserMode && !is_null($this->params['continue']))
{
@@ -162,7 +168,8 @@ class ApiQueryContributions extends ApiQueryBase {
// ... and in the specified timeframe.
// Ensure the same sort order for rev_user_text and rev_timestamp
// so our query is indexed
- $this->addWhereRange('rev_user_text', $this->params['dir'], null, null);
+ if($this->multiUserMode)
+ $this->addWhereRange('rev_user_text', $this->params['dir'], null, null);
$this->addWhereRange('rev_timestamp',
$this->params['dir'], $this->params['start'], $this->params['end'] );
$this->addWhereFld('page_namespace', $this->params['namespace']);
@@ -170,14 +177,17 @@ class ApiQueryContributions extends ApiQueryBase {
$show = $this->params['show'];
if (!is_null($show)) {
$show = array_flip($show);
- if (isset ($show['minor']) && isset ($show['!minor']))
+ if ((isset($show['minor']) && isset($show['!minor']))
+ || (isset($show['patrolled']) && isset($show['!patrolled'])))
$this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show');
- $this->addWhereIf('rev_minor_edit = 0', isset ($show['!minor']));
- $this->addWhereIf('rev_minor_edit != 0', isset ($show['minor']));
+ $this->addWhereIf('rev_minor_edit = 0', isset($show['!minor']));
+ $this->addWhereIf('rev_minor_edit != 0', isset($show['minor']));
+ $this->addWhereIf('rc_patrolled = 0', isset($show['!patrolled']));
+ $this->addWhereIf('rc_patrolled != 0', isset($show['patrolled']));
}
$this->addOption('LIMIT', $this->params['limit'] + 1);
- $this->addOption( 'USE INDEX', array( 'revision' => 'usertext_timestamp' ) );
+ $index['revision'] = 'usertext_timestamp';
// Mandatory fields: timestamp allows request continuation
// ns+title checks if the user has access rights for this page
@@ -187,15 +197,49 @@ class ApiQueryContributions extends ApiQueryBase {
'page_namespace',
'page_title',
'rev_user_text',
- ));
+ ));
+
+ if(isset($show['patrolled']) || isset($show['!patrolled']) ||
+ $this->fld_patrolled)
+ {
+ global $wgUser;
+ if(!$wgUser->useRCPatrol() && !$wgUser->useNPPatrol())
+ $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied');
+ // Use a redundant join condition on both
+ // timestamp and ID so we can use the timestamp
+ // index
+ $index['recentchanges'] = 'rc_user_text';
+ if(isset($show['patrolled']) || isset($show['!patrolled']))
+ {
+ // Put the tables in the right order for
+ // STRAIGHT_JOIN
+ $tables = array('revision', 'recentchanges', 'page');
+ $this->addOption('STRAIGHT_JOIN');
+ $this->addWhere('rc_user_text=rev_user_text');
+ $this->addWhere('rc_timestamp=rev_timestamp');
+ $this->addWhere('rc_this_oldid=rev_id');
+ }
+ else
+ {
+ $tables[] = 'recentchanges';
+ $this->addJoinConds(array('recentchanges' => array(
+ 'LEFT JOIN', array(
+ 'rc_user_text=rev_user_text',
+ 'rc_timestamp=rev_timestamp',
+ 'rc_this_oldid=rev_id'))));
+ }
+ }
+ $this->addTables($tables);
+ $this->addOption('USE INDEX', $index);
$this->addFieldsIf('rev_page', $this->fld_ids);
$this->addFieldsIf('rev_id', $this->fld_ids || $this->fld_flags);
$this->addFieldsIf('page_latest', $this->fld_flags);
// $this->addFieldsIf('rev_text_id', $this->fld_ids); // Should this field be exposed?
$this->addFieldsIf('rev_comment', $this->fld_comment);
$this->addFieldsIf('rev_minor_edit', $this->fld_flags);
- $this->addFieldsIf('page_is_new', $this->fld_flags);
+ $this->addFieldsIf('rev_parent_id', $this->fld_flags);
+ $this->addFieldsIf('rc_patrolled', $this->fld_patrolled);
}
/**
@@ -220,7 +264,7 @@ class ApiQueryContributions extends ApiQueryBase {
$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp);
if ($this->fld_flags) {
- if ($row->page_is_new)
+ if ($row->rev_parent_id == 0)
$vals['new'] = '';
if ($row->rev_minor_edit)
$vals['minor'] = '';
@@ -228,9 +272,12 @@ class ApiQueryContributions extends ApiQueryBase {
$vals['top'] = '';
}
- if ($this->fld_comment && isset( $row->rev_comment ) )
+ if ($this->fld_comment && isset($row->rev_comment))
$vals['comment'] = $row->rev_comment;
+ if ($this->fld_patrolled && $row->rc_patrolled)
+ $vals['patrolled'] = '';
+
return $vals;
}
@@ -279,7 +326,8 @@ class ApiQueryContributions extends ApiQueryBase {
'title',
'timestamp',
'comment',
- 'flags'
+ 'flags',
+ 'patrolled',
)
),
'show' => array (
@@ -287,6 +335,8 @@ class ApiQueryContributions extends ApiQueryBase {
ApiBase :: PARAM_TYPE => array (
'minor',
'!minor',
+ 'patrolled',
+ '!patrolled',
)
),
);
@@ -303,7 +353,8 @@ class ApiQueryContributions extends ApiQueryBase {
'dir' => 'The direction to search (older or newer).',
'namespace' => 'Only list contributions in these namespaces',
'prop' => 'Include additional pieces of information',
- 'show' => 'Show only items that meet this criteria, e.g. non minor edits only: show=!minor',
+ 'show' => array('Show only items that meet this criteria, e.g. non minor edits only: show=!minor',
+ 'NOTE: if show=patrolled or show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown',),
);
}
@@ -319,6 +370,6 @@ class ApiQueryContributions extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUserContributions.php 43271 2008-11-06 22:38:42Z siebrand $';
+ return __CLASS__ . ': $Id: ApiQueryUserContributions.php 47037 2009-02-09 14:07:18Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index 203b7e25..ac99ad6d 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -57,7 +57,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
global $wgUser;
$result = $this->getResult();
$vals = array();
- $vals['id'] = $wgUser->getId();
+ $vals['id'] = intval($wgUser->getId());
$vals['name'] = $wgUser->getName();
if($wgUser->isAnon())
@@ -87,11 +87,17 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['preferencestoken'] = $wgUser->editToken();
}
if (isset($this->prop['editcount'])) {
- $vals['editcount'] = $wgUser->getEditCount();
+ $vals['editcount'] = intval($wgUser->getEditCount());
}
if (isset($this->prop['ratelimits'])) {
$vals['ratelimits'] = $this->getRateLimits();
}
+ if (isset($this->prop['email'])) {
+ $vals['email'] = $wgUser->getEmail();
+ $auth = $wgUser->getEmailAuthenticationTimestamp();
+ if(!is_null($auth))
+ $vals['emailauthenticated'] = wfTimestamp(TS_ISO_8601, $auth);
+ }
return $vals;
}
@@ -122,8 +128,8 @@ class ApiQueryUserInfo extends ApiQueryBase {
foreach($categories as $cat)
if(isset($limits[$cat]) && !is_null($limits[$cat]))
{
- $retval[$action][$cat]['hits'] = $limits[$cat][0];
- $retval[$action][$cat]['seconds'] = $limits[$cat][1];
+ $retval[$action][$cat]['hits'] = intval($limits[$cat][0]);
+ $retval[$action][$cat]['seconds'] = intval($limits[$cat][1]);
}
return $retval;
}
@@ -141,7 +147,8 @@ class ApiQueryUserInfo extends ApiQueryBase {
'options',
'preferencestoken',
'editcount',
- 'ratelimits'
+ 'ratelimits',
+ 'email',
)
)
);
@@ -174,6 +181,6 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUserInfo.php 43764 2008-11-20 15:15:00Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryUserInfo.php 47865 2009-02-27 16:03:01Z catrope $';
}
}
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index e50d8d82..b8aa60e7 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -51,82 +51,91 @@ if (!defined('MEDIAWIKI')) {
$this->prop = array();
}
- if(is_array($params['users'])) {
- $r = $this->getOtherUsersInfo($params['users']);
- $result->setIndexedTagName($r, 'user');
- }
- $result->addValue("query", $this->getModuleName(), $r);
- }
-
- protected function getOtherUsersInfo($users) {
- $goodNames = $retval = array();
+ $users = (array)$params['users'];
+ $goodNames = $done = array();
+ $result = $this->getResult();
// Canonicalize user names
foreach($users as $u) {
$n = User::getCanonicalName($u);
if($n === false || $n === '')
- $retval[] = array('name' => $u, 'invalid' => '');
+ {
+ $vals = array('name' => $u, 'invalid' => '');
+ $fit = $result->addValue(array('query', $this->getModuleName()),
+ null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('users',
+ implode('|', array_diff($users, $done)));
+ $goodNames = array();
+ break;
+ }
+ $done[] = $u;
+ }
else
$goodNames[] = $n;
}
- if(!count($goodNames))
- return $retval;
-
- $db = $this->getDB();
- $this->addTables('user', 'u1');
- $this->addFields('u1.*');
- $this->addWhereFld('u1.user_name', $goodNames);
-
- if(isset($this->prop['groups'])) {
- $this->addTables('user_groups');
- $this->addJoinConds(array('user_groups' => array('LEFT JOIN', 'ug_user=u1.user_id')));
- $this->addFields('ug_group');
- }
- if(isset($this->prop['blockinfo'])) {
- $this->addTables('ipblocks');
- $this->addTables('user', 'u2');
- $u2 = $this->getAliasedName('user', 'u2');
- $this->addJoinConds(array(
- 'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'),
- $u2 => array('LEFT JOIN', 'ipb_by=u2.user_id')));
- $this->addFields(array('ipb_reason', 'u2.user_name blocker_name'));
- }
+ if(count($goodNames))
+ {
+ $db = $this->getDb();
+ $this->addTables('user', 'u1');
+ $this->addFields('u1.*');
+ $this->addWhereFld('u1.user_name', $goodNames);
+
+ if(isset($this->prop['groups'])) {
+ $this->addTables('user_groups');
+ $this->addJoinConds(array('user_groups' => array('LEFT JOIN', 'ug_user=u1.user_id')));
+ $this->addFields('ug_group');
+ }
+ if(isset($this->prop['blockinfo'])) {
+ $this->addTables('ipblocks');
+ $this->addTables('user', 'u2');
+ $u2 = $this->getAliasedName('user', 'u2');
+ $this->addJoinConds(array(
+ 'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'),
+ $u2 => array('LEFT JOIN', 'ipb_by=u2.user_id')));
+ $this->addFields(array('ipb_reason', 'u2.user_name AS blocker_name'));
+ }
- $data = array();
- $res = $this->select(__METHOD__);
- while(($r = $db->fetchObject($res))) {
- $user = User::newFromRow($r);
- $name = $user->getName();
- $data[$name]['name'] = $name;
- if(isset($this->prop['editcount']))
- // No proper member function in User class for this
- $data[$name]['editcount'] = $r->user_editcount;
- if(isset($this->prop['registration']))
- // Nor for this one
- $data[$name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $r->user_registration);
- if(isset($this->prop['groups']))
- // This row contains only one group, others will be added from other rows
- if(!is_null($r->ug_group))
+ $data = array();
+ $res = $this->select(__METHOD__);
+ while(($r = $db->fetchObject($res))) {
+ $user = User::newFromRow($r);
+ $name = $user->getName();
+ $data[$name]['name'] = $name;
+ if(isset($this->prop['editcount']))
+ $data[$name]['editcount'] = intval($user->getEditCount());
+ if(isset($this->prop['registration']))
+ $data[$name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $user->getRegistration());
+ if(isset($this->prop['groups']) && !is_null($r->ug_group))
+ // This row contains only one group, others will be added from other rows
$data[$name]['groups'][] = $r->ug_group;
- if(isset($this->prop['blockinfo']))
- if(!is_null($r->blocker_name)) {
+ if(isset($this->prop['blockinfo']) && !is_null($r->blocker_name)) {
$data[$name]['blockedby'] = $r->blocker_name;
$data[$name]['blockreason'] = $r->ipb_reason;
}
- if(isset($this->prop['emailable']) && $user->canReceiveEmail())
- $data[$name]['emailable'] = '';
+ if(isset($this->prop['emailable']) && $user->canReceiveEmail())
+ $data[$name]['emailable'] = '';
+ }
}
-
// Second pass: add result data to $retval
foreach($goodNames as $u) {
if(!isset($data[$u]))
- $retval[] = array('name' => $u, 'missing' => '');
+ $data[$u] = array('name' => $u, 'missing' => '');
else {
if(isset($this->prop['groups']) && isset($data[$u]['groups']))
$this->getResult()->setIndexedTagName($data[$u]['groups'], 'g');
- $retval[] = $data[$u];
}
+ $fit = $result->addValue(array('query', $this->getModuleName()),
+ null, $data[$u]);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('users',
+ implode('|', array_diff($users, $done)));
+ break;
+ }
+ $done[] = $u;
}
- return $retval;
+ return $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'user');
}
public function getAllowedParams() {
@@ -171,6 +180,6 @@ if (!defined('MEDIAWIKI')) {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUsers.php 44231 2008-12-04 14:42:30Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryUsers.php 50094 2009-05-01 06:24:09Z tstarling $';
}
}
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index ed3482fb..b3949102 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -92,6 +92,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addFieldsIf('rc_new', $this->fld_flags);
$this->addFieldsIf('rc_minor', $this->fld_flags);
+ $this->addFieldsIf('rc_bot', $this->fld_flags);
$this->addFieldsIf('rc_user', $this->fld_user);
$this->addFieldsIf('rc_user_text', $this->fld_user);
$this->addFieldsIf('rc_comment', $this->fld_comment);
@@ -168,7 +169,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addOption('LIMIT', $params['limit'] +1);
- $data = array ();
+ $ids = array ();
$count = 0;
$res = $this->select(__METHOD__);
@@ -182,13 +183,18 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if (is_null($resultPageSet)) {
$vals = $this->extractRowInfo($row);
- if ($vals)
- $data[] = $vals;
+ $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('start',
+ wfTimestamp(TS_ISO_8601, $row->rc_timestamp));
+ break;
+ }
} else {
if ($params['allrev']) {
- $data[] = intval($row->rc_this_oldid);
+ $ids[] = intval($row->rc_this_oldid);
} else {
- $data[] = intval($row->rc_cur_id);
+ $ids[] = intval($row->rc_cur_id);
}
}
}
@@ -196,13 +202,12 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$db->freeResult($res);
if (is_null($resultPageSet)) {
- $this->getResult()->setIndexedTagName($data, 'item');
- $this->getResult()->addValue('query', $this->getModuleName(), $data);
+ $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item');
}
elseif ($params['allrev']) {
- $resultPageSet->populateFromRevisionIDs($data);
+ $resultPageSet->populateFromRevisionIDs($ids);
} else {
- $resultPageSet->populateFromPageIDs($data);
+ $resultPageSet->populateFromPageIDs($ids);
}
}
@@ -229,6 +234,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$vals['new'] = '';
if ($row->rc_minor)
$vals['minor'] = '';
+ if ($row->rc_bot)
+ $vals['bot'] = '';
}
if ($this->fld_patrol && isset($row->rc_patrolled))
@@ -237,8 +244,6 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ($this->fld_timestamp)
$vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp);
- $this->addFieldsIf('rc_new_len', $this->fld_sizes);
-
if ($this->fld_sizes) {
$vals['oldlen'] = intval($row->rc_old_len);
$vals['newlen'] = intval($row->rc_new_len);
@@ -338,6 +343,6 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryWatchlist.php 44719 2008-12-17 16:34:01Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryWatchlist.php 47865 2009-02-27 16:03:01Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index e9951b42..54bb5a35 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -89,7 +89,6 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
$res = $this->select(__METHOD__);
$db = $this->getDB();
- $data = array();
$titles = array();
$count = 0;
while($row = $db->fetchObject($res))
@@ -108,16 +107,19 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
ApiQueryBase::addTitleInfo($vals, $t);
if(isset($prop['changed']) && !is_null($row->wl_notificationtimestamp))
$vals['changed'] = wfTimestamp(TS_ISO_8601, $row->wl_notificationtimestamp);
- $data[] = $vals;
+ $fit = $this->getResult()->addValue($this->getModuleName(), null, $vals);
+ if(!$fit)
+ {
+ $this->setContinueEnumParameter('continue', $row->wl_namespace . '|' .
+ $this->keyToTitle($row->wl_title));
+ break;
+ }
}
else
$titles[] = $t;
}
if(is_null($resultPageSet))
- {
- $this->getResult()->setIndexedTagName($data, 'wr');
- $this->getResult()->addValue(null, $this->getModuleName(), $data);
- }
+ $this->getResult()->setIndexedTagName_internal($this->getModuleName(), 'wr');
else
$resultPageSet->populateFromTitles($titles);
}
@@ -174,6 +176,6 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 41651 2008-10-04 14:30:33Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 46845 2009-02-05 14:30:59Z catrope $';
}
-}
+} \ No newline at end of file
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 900953e0..3dbee08a 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -47,14 +47,16 @@ if (!defined('MEDIAWIKI')) {
*/
class ApiResult extends ApiBase {
- private $mData, $mIsRawMode;
+ private $mData, $mIsRawMode, $mSize, $mCheckingSize;
/**
- * Constructor
- */
+ * Constructor
+ * @param $main ApiMain object
+ */
public function __construct($main) {
parent :: __construct($main, 'result');
$this->mIsRawMode = false;
+ $this->mCheckingSize = true;
$this->reset();
}
@@ -63,6 +65,7 @@ class ApiResult extends ApiBase {
*/
public function reset() {
$this->mData = array ();
+ $this->mSize = 0;
}
/**
@@ -74,22 +77,68 @@ class ApiResult extends ApiBase {
}
/**
- * Returns true if the result is being created for the formatter that requested raw data.
+ * Returns true whether the formatter requested raw data.
+ * @return bool
*/
public function getIsRawMode() {
return $this->mIsRawMode;
}
/**
- * Get result's internal data array
+ * Get the result's internal data array (read-only)
+ * @return array
*/
- public function & getData() {
+ public function getData() {
return $this->mData;
}
+
+ /**
+ * Get the 'real' size of a result item. This means the strlen() of the item,
+ * or the sum of the strlen()s of the elements if the item is an array.
+ * @param $value mixed
+ * @return int
+ */
+ public static function size($value) {
+ $s = 0;
+ if(is_array($value))
+ foreach($value as $v)
+ $s += self::size($v);
+ else if(!is_object($value))
+ // Objects can't always be cast to string
+ $s = strlen($value);
+ return $s;
+ }
+
+ /**
+ * Get the size of the result, i.e. the amount of bytes in it
+ * @return int
+ */
+ public function getSize() {
+ return $this->mSize;
+ }
+
+ /**
+ * Disable size checking in addValue(). Don't use this unless you
+ * REALLY know what you're doing. Values added while size checking
+ * was disabled will not be counted (ever)
+ */
+ public function disableSizeCheck() {
+ $this->mCheckingSize = false;
+ }
+
+ /**
+ * Re-enable size checking in addValue()
+ */
+ public function enableSizeCheck() {
+ $this->mCheckingSize = true;
+ }
/**
* 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 $value mixed
*/
public static function setElement(& $arr, $name, $value) {
if ($arr === null || $name === null || $value === null || !is_array($arr) || is_array($name))
@@ -109,10 +158,12 @@ class ApiResult extends ApiBase {
}
/**
- * Adds the content element to the array.
+ * Adds a content element to an array.
* Use this function instead of hardcoding the '*' element.
- * @param string $subElemName when present, content element is created as a sub item of the arr.
- * Use this parameter to create elements in format <elem>text</elem> without attributes
+ * @param $arr array to add the content element to
+ * @param $subElemName string 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
*/
public static function setContent(& $arr, $value, $subElemName = null) {
if (is_array($value))
@@ -128,7 +179,10 @@ class ApiResult extends ApiBase {
/**
* In case the array contains indexed values (in addition to named),
- * all indexed values will have the given tag name.
+ * give all indexed values the given tag name. This function MUST be
+ * called on every arrray that has numerical indexes.
+ * @param $arr array
+ * @param $tag string Tag name
*/
public function setIndexedTagName(& $arr, $tag) {
// In raw mode, add the '_element', otherwise just ignore
@@ -141,7 +195,9 @@ class ApiResult extends ApiBase {
}
/**
- * Calls setIndexedTagName() on $arr and each sub-array
+ * Calls setIndexedTagName() on each sub-array of $arr
+ * @param $arr array
+ * @param $tag string Tag name
*/
public function setIndexedTagName_recursive(&$arr, $tag)
{
@@ -157,14 +213,41 @@ 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 $tag string
+ */
+ public function setIndexedTagName_internal( $path, $tag ) {
+ $data = & $this->mData;
+ foreach((array)$path as $p) {
+ if ( !isset( $data[$p] ) ) {
+ $data[$p] = array();
+ }
+ $data = & $data[$p];
+ }
+ if(is_null($data))
+ return;
+ $this->setIndexedTagName($data, $tag);
+ }
+
+ /**
* Add value to the output data at the given path.
* Path is an indexed array, each element specifing the branch at which to add the new value
* Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value
* If $name is empty, the $value is added as a next list element data[] = $value
+ * @return bool True if $value fits in the result, false if not
*/
public function addValue($path, $name, $value) {
-
- $data = & $this->getData();
+ global $wgAPIMaxResultSize;
+ $data = & $this->mData;
+ if( $this->mCheckingSize ) {
+ $newsize = $this->mSize + self::size($value);
+ if($newsize > $wgAPIMaxResultSize)
+ return false;
+ $this->mSize = $newsize;
+ }
if (!is_null($path)) {
if (is_array($path)) {
@@ -184,6 +267,26 @@ class ApiResult extends ApiBase {
$data[] = $value; // Add list element
else
ApiResult :: setElement($data, $name, $value); // Add named element
+ return true;
+ }
+
+ /**
+ * 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 $name string
+ */
+ public function unsetValue($path, $name) {
+ $data = & $this->mData;
+ if(!is_null($path))
+ foreach((array)$path as $p) {
+ if(!isset($data[$p]))
+ return;
+ $data = & $data[$p];
+ }
+ $this->mSize -= self::size($data[$name]);
+ unset($data[$name]);
}
/**
@@ -191,8 +294,17 @@ class ApiResult extends ApiBase {
*/
public function cleanUpUTF8()
{
- $data = & $this->getData();
- array_walk_recursive($data, array('UtfNormal', 'cleanUp'));
+ array_walk_recursive($this->mData, array('ApiResult', 'cleanUp_helper'));
+ }
+
+ /**
+ * Callback function for cleanUpUTF8()
+ */
+ private static function cleanUp_helper(&$s)
+ {
+ if(!is_string($s))
+ return;
+ $s = UtfNormal::cleanUp($s);
}
public function execute() {
@@ -200,7 +312,7 @@ class ApiResult extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiResult.php 45752 2009-01-14 21:36:57Z catrope $';
+ return __CLASS__ . ': $Id: ApiResult.php 47447 2009-02-18 12:41:28Z tstarling $';
}
}
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index 653dca9e..0f0eae10 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -37,7 +37,6 @@ class ApiRollback extends ApiBase {
}
public function execute() {
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
$titleObj = NULL;
@@ -68,15 +67,15 @@ class ApiRollback extends ApiBase {
if($retval)
// We don't care about multiple errors, just report one of them
- $this->dieUsageMsg(current($retval));
+ $this->dieUsageMsg(reset($retval));
$info = array(
'title' => $titleObj->getPrefixedText(),
- 'pageid' => $details['current']->getPage(),
+ 'pageid' => intval($details['current']->getPage()),
'summary' => $details['summary'],
- 'revid' => $titleObj->getLatestRevID(),
- 'old_revid' => $details['current']->getID(),
- 'last_revid' => $details['target']->getID()
+ 'revid' => intval($titleObj->getLatestRevID()),
+ 'old_revid' => intval($details['current']->getID()),
+ 'last_revid' => intval($details['target']->getID())
);
$this->getResult()->addValue(null, $this->getModuleName(), $info);
@@ -84,6 +83,10 @@ class ApiRollback extends ApiBase {
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'title' => null,
@@ -119,6 +122,6 @@ class ApiRollback extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiRollback.php 45043 2008-12-26 04:13:47Z mrzman $';
+ return __CLASS__ . ': $Id: ApiRollback.php 48122 2009-03-07 12:58:41Z catrope $';
}
}
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index cd52c518..9216317a 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -44,7 +44,6 @@ class ApiUnblock extends ApiBase {
*/
public function execute() {
global $wgUser;
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
if($params['gettoken'])
@@ -72,7 +71,7 @@ class ApiUnblock extends ApiBase {
if($retval)
$this->dieUsageMsg($retval);
- $res['id'] = $id;
+ $res['id'] = intval($id);
$res['user'] = $user;
$res['reason'] = $reason;
$this->getResult()->addValue(null, $this->getModuleName(), $res);
@@ -80,6 +79,10 @@ class ApiUnblock extends ApiBase {
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'id' => null,
@@ -114,6 +117,6 @@ class ApiUnblock extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUnblock.php 42651 2008-10-27 12:06:49Z catrope $';
+ return __CLASS__ . ': $Id: ApiUnblock.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index 7ae9a3c0..ddc9f7f8 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -38,7 +38,6 @@ class ApiUndelete extends ApiBase {
public function execute() {
global $wgUser;
- $this->getMain()->requestWriteMode();
$params = $this->extractRequestParams();
$titleObj = NULL;
@@ -78,14 +77,18 @@ class ApiUndelete extends ApiBase {
array($titleObj, array(), $wgUser, $params['reason']) );
$info['title'] = $titleObj->getPrefixedText();
- $info['revisions'] = $retval[0];
- $info['fileversions'] = $retval[1];
- $info['reason'] = $retval[2];
+ $info['revisions'] = intval($retval[0]);
+ $info['fileversions'] = intval($retval[1]);
+ $info['reason'] = intval($retval[2]);
$this->getResult()->addValue(null, $this->getModuleName(), $info);
}
public function mustBePosted() { return true; }
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'title' => null,
@@ -121,6 +124,6 @@ class ApiUndelete extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUndelete.php 43270 2008-11-06 22:30:55Z siebrand $';
+ return __CLASS__ . ': $Id: ApiUndelete.php 48091 2009-03-06 13:49:44Z catrope $';
}
}
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index ab122fea..1b98fb86 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -29,8 +29,7 @@ if (!defined('MEDIAWIKI')) {
}
/**
- * API module to allow users to log out of the wiki. API equivalent of
- * Special:Userlogout.
+ * API module to allow users to watch a page
*
* @ingroup API
*/
@@ -42,7 +41,6 @@ class ApiWatch extends ApiBase {
public function execute() {
global $wgUser;
- $this->getMain()->requestWriteMode();
if(!$wgUser->isLoggedIn())
$this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin');
$params = $this->extractRequestParams();
@@ -66,6 +64,10 @@ class ApiWatch extends ApiBase {
$this->getResult()->addValue(null, $this->getModuleName(), $res);
}
+ public function isWriteMode() {
+ return true;
+ }
+
public function getAllowedParams() {
return array (
'title' => null,
@@ -94,6 +96,6 @@ class ApiWatch extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiWatch.php 40460 2008-09-04 22:20:32Z ialex $';
+ return __CLASS__ . ': $Id: ApiWatch.php 48091 2009-03-06 13:49:44Z catrope $';
}
}