From 086ae52d12011746a75f5588e877347bc0457352 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 21 Mar 2008 11:49:34 +0100 Subject: Update auf MediaWiki 1.12.0 --- includes/api/ApiBase.php | 186 ++++++++++++++++++++-- includes/api/ApiBlock.php | 164 ++++++++++++++++++++ includes/api/ApiChangeRights.php | 155 +++++++++++++++++++ includes/api/ApiDelete.php | 155 +++++++++++++++++++ includes/api/ApiExpandTemplates.php | 97 ++++++++++++ includes/api/ApiFeedWatchlist.php | 29 ++-- includes/api/ApiFormatBase.php | 52 +++++-- includes/api/ApiFormatDbg.php | 59 +++++++ includes/api/ApiFormatJson.php | 8 +- includes/api/ApiFormatPhp.php | 4 +- includes/api/ApiFormatTxt.php | 59 +++++++ includes/api/ApiFormatWddx.php | 4 +- includes/api/ApiFormatXml.php | 4 +- includes/api/ApiFormatYaml.php | 4 +- includes/api/ApiFormatYaml_spyc.php | 14 +- includes/api/ApiHelp.php | 8 +- includes/api/ApiLogin.php | 22 ++- includes/api/ApiLogout.php | 71 +++++++++ includes/api/ApiMain.php | 112 +++++++++++--- includes/api/ApiMove.php | 152 ++++++++++++++++++ includes/api/ApiOpenSearch.php | 51 ++---- includes/api/ApiParamInfo.php | 167 ++++++++++++++++++++ includes/api/ApiParse.php | 202 ++++++++++++++++++++++++ includes/api/ApiProtect.php | 154 +++++++++++++++++++ includes/api/ApiQuery.php | 26 +++- includes/api/ApiQueryAllCategories.php | 142 +++++++++++++++++ includes/api/ApiQueryAllLinks.php | 8 +- includes/api/ApiQueryAllUsers.php | 17 +- includes/api/ApiQueryAllmessages.php | 129 ++++++++++++++++ includes/api/ApiQueryAllpages.php | 48 +++++- includes/api/ApiQueryBacklinks.php | 10 +- includes/api/ApiQueryBase.php | 7 +- includes/api/ApiQueryBlocks.php | 239 +++++++++++++++++++++++++++++ includes/api/ApiQueryCategories.php | 8 +- includes/api/ApiQueryCategoryMembers.php | 61 +++++--- includes/api/ApiQueryDeletedrevs.php | 235 ++++++++++++++++++++++++++++ includes/api/ApiQueryExtLinksUsage.php | 8 +- includes/api/ApiQueryExternalLinks.php | 4 +- includes/api/ApiQueryImageInfo.php | 170 +++++++++++++------- includes/api/ApiQueryImages.php | 4 +- includes/api/ApiQueryInfo.php | 113 +++++++++++--- includes/api/ApiQueryLangLinks.php | 4 +- includes/api/ApiQueryLinks.php | 8 +- includes/api/ApiQueryLogEvents.php | 9 +- includes/api/ApiQueryRandom.php | 157 +++++++++++++++++++ includes/api/ApiQueryRecentChanges.php | 110 +++++++++++-- includes/api/ApiQueryRevisions.php | 77 +++++++--- includes/api/ApiQuerySearch.php | 8 +- includes/api/ApiQuerySiteinfo.php | 39 ++++- includes/api/ApiQueryUserContributions.php | 36 ++--- includes/api/ApiQueryUserInfo.php | 138 +++++++++++------ includes/api/ApiQueryUsers.php | 162 +++++++++++++++++++ includes/api/ApiQueryWatchlist.php | 50 +++++- includes/api/ApiResult.php | 45 +++++- includes/api/ApiRollback.php | 128 +++++++++++++++ includes/api/ApiUnblock.php | 124 +++++++++++++++ includes/api/ApiUndelete.php | 123 +++++++++++++++ 57 files changed, 4013 insertions(+), 367 deletions(-) create mode 100644 includes/api/ApiBlock.php create mode 100644 includes/api/ApiChangeRights.php create mode 100644 includes/api/ApiDelete.php create mode 100644 includes/api/ApiExpandTemplates.php create mode 100644 includes/api/ApiFormatDbg.php create mode 100644 includes/api/ApiFormatTxt.php create mode 100644 includes/api/ApiLogout.php create mode 100644 includes/api/ApiMove.php create mode 100644 includes/api/ApiParamInfo.php create mode 100644 includes/api/ApiParse.php create mode 100644 includes/api/ApiProtect.php create mode 100644 includes/api/ApiQueryAllCategories.php create mode 100644 includes/api/ApiQueryAllmessages.php create mode 100644 includes/api/ApiQueryBlocks.php create mode 100644 includes/api/ApiQueryDeletedrevs.php create mode 100644 includes/api/ApiQueryRandom.php create mode 100644 includes/api/ApiQueryUsers.php create mode 100644 includes/api/ApiRollback.php create mode 100644 includes/api/ApiUnblock.php create mode 100644 includes/api/ApiUndelete.php (limited to 'includes/api') diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index b324c52f..3a7b5099 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -63,11 +63,35 @@ abstract class ApiBase { $this->mModulePrefix = $modulePrefix; } - /** - * Executes this module + /***************************************************************************** + * ABSTRACT METHODS * + *****************************************************************************/ + + /** + * 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 result data should be stored in the result object referred to by + * "getResult()". Refer to ApiResult.php for details on populating a result + * object. */ 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! + */ + public abstract function getVersion(); + /** * Get the name of the module being executed by this instance */ @@ -100,14 +124,16 @@ abstract class ApiBase { } /** - * If this module's $this is the same as $this->mMainModule, its the root, otherwise no + * Returns true if this module is the main module ($this === $this->mMainModule), + * false otherwise. */ public function isMain() { return $this === $this->mMainModule; } /** - * Get result object + * Get the result object. Please refer to the documentation in ApiResult.php + * for details on populating and accessing data in a result object. */ public function getResult() { // Main module has getResult() method overriden @@ -125,7 +151,8 @@ abstract class ApiBase { } /** - * 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. */ public function setWarning($warning) { $msg = array(); @@ -196,6 +223,10 @@ abstract class ApiBase { return $msg; } + /** + * Generates the parameter descriptions for this module, to be displayed in the + * module's help. + */ public function makeHelpMsgParameters() { $params = $this->getAllowedParams(); if ($params !== false) { @@ -208,7 +239,7 @@ abstract class ApiBase { if (is_array($desc)) $desc = implode($paramPrefix, $desc); - @ $type = $paramSettings[self :: PARAM_TYPE]; + $type = $paramSettings[self :: PARAM_TYPE]; if (isset ($type)) { if (isset ($paramSettings[self :: PARAM_ISMULTI])) $prompt = 'Values (separate with \'|\'): '; @@ -303,13 +334,15 @@ abstract class ApiBase { * 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. * This method can be used to generate local variables using extract(). + * 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. */ - public function extractRequestParams() { + public function extractRequestParams($parseMaxLimit = true) { $params = $this->getAllowedParams(); $results = array (); foreach ($params as $paramName => $paramSettings) - $results[$paramName] = $this->getParameterFromSettings($paramName, $paramSettings); + $results[$paramName] = $this->getParameterFromSettings($paramName, $paramSettings, $parseMaxLimit); return $results; } @@ -323,6 +356,10 @@ abstract class ApiBase { return $this->getParameterFromSettings($paramName, $paramSettings); } + /** + * Returns an array of the namespaces (by integer id) that exist on the + * wiki. Used primarily in help documentation. + */ public static function getValidNamespaces() { static $mValidNamespaces = null; if (is_null($mValidNamespaces)) { @@ -339,10 +376,12 @@ 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 $parseMaxLimit Boolean: parse limit when max is given? */ - protected function getParameterFromSettings($paramName, $paramSettings) { + protected function getParameterFromSettings($paramName, $paramSettings, $parseMaxLimit) { // Some classes may decide to change parameter names $encParamName = $this->encodeParamName($paramName); @@ -410,8 +449,17 @@ abstract class ApiBase { if ($multi) ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $encParamName"); $min = isset ($paramSettings[self :: PARAM_MIN]) ? $paramSettings[self :: PARAM_MIN] : 0; - $value = intval($value); - $this->validateLimit($paramName, $value, $min, $paramSettings[self :: PARAM_MAX], $paramSettings[self :: PARAM_MAX2]); + if( $value == 'max' ) { + if( $parseMaxLimit ) { + $value = $this->getMain()->canApiHighLimits() ? $paramSettings[self :: PARAM_MAX2] : $paramSettings[self :: PARAM_MAX]; + $this->getResult()->addValue( 'limits', $this->getModuleName(), $value ); + $this->validateLimit($paramName, $value, $min, $paramSettings[self :: PARAM_MAX], $paramSettings[self :: PARAM_MAX2]); + } + } + else { + $value = intval($value); + $this->validateLimit($paramName, $value, $min, $paramSettings[self :: PARAM_MAX], $paramSettings[self :: PARAM_MAX2]); + } break; case 'boolean' : if ($multi) @@ -485,7 +533,7 @@ abstract class ApiBase { // Optimization: do not check user's bot status unless really needed -- skips db query // assumes $botMax >= $max if (!is_null($max) && $value > $max) { - if (!is_null($botMax) && ($this->getMain()->isBot() || $this->getMain()->isSysop())) { + if (!is_null($botMax) && $this->getMain()->canApiHighLimits()) { if ($value > $botMax) { $this->dieUsage($this->encodeParamName($paramName) . " may not be over $botMax (set to $value) for bots or sysops", $paramName); } @@ -501,6 +549,90 @@ abstract class ApiBase { public function dieUsage($description, $errorCode, $httpRespCode = 0) { throw new UsageException($description, $this->encodeParamName($errorCode), $httpRespCode); } + + /** + * Array that maps message keys to error messages. $1 and friends are replaced. + */ + public static $messageMap = array( + // This one MUST be present, or dieUsageMsg() will recurse infinitely + 'unknownerror' => array('code' => 'unknownerror', 'info' => "Unknown error: ``\$1''"), + 'unknownerror-nocode' => array('code' => 'unknownerror', 'info' => 'Unknown error'), + + // Messages from Title::getUserPermissionsErrors() + 'ns-specialprotected' => array('code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited"), + 'protectedinterface' => array('code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages"), + 'namespaceprotected' => array('code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the ``\$1'' namespace"), + 'customcssjsprotected' => array('code' => 'customcssjsprotected', 'info' => "You're not allowed to edit custom CSS and JavaScript pages"), + 'cascadeprotected' => array('code' => 'cascadeprotected', 'info' =>"The page you're trying to edit is protected because it's included in a cascade-protected page"), + 'protectedpagetext' => array('code' => 'protectedpage', 'info' => "The ``\$1'' right is required to edit this page"), + 'protect-cantedit' => array('code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it"), + 'badaccess-group0' => array('code' => 'permissiondenied', 'info' => "Permission denied"), // Generic permission denied message + 'badaccess-group1' => array('code' => 'permissiondenied', 'info' => "Permission denied"), // Can't use the parameter 'cause it's wikilinked + 'badaccess-group2' => array('code' => 'permissiondenied', 'info' => "Permission denied"), + 'badaccess-groups' => array('code' => 'permissiondenied', 'info' => "Permission denied"), + 'titleprotected' => array('code' => 'protectedtitle', 'info' => "This title has been protected from creation"), + 'nocreate-loggedin' => array('code' => 'cantcreate', 'info' => "You don't have permission to create new pages"), + 'nocreatetext' => array('code' => 'cantcreate-anon', 'info' => "Anonymous users can't create new pages"), + 'movenologintext' => array('code' => 'cantmove-anon', 'info' => "Anonymous users can't move pages"), + 'movenotallowed' => array('code' => 'cantmove', 'info' => "You don't have permission to move pages"), + 'confirmedittext' => array('code' => 'confirmemail', 'info' => "You must confirm your e-mail address before you can edit"), + 'blockedtext' => array('code' => 'blocked', 'info' => "You have been blocked from editing"), + 'autoblockedtext' => array('code' => 'autoblocked', 'info' => "Your IP address has been blocked automatically, because it was used by a blocked user"), + + // Miscellaneous interface messages + 'actionthrottledtext' => array('code' => 'ratelimited', 'info' => "You've exceeded your rate limit. Please wait some time and try again"), + 'alreadyrolled' => array('code' => 'alreadyrolled', 'info' => "The page you tried to rollback was already rolled back"), + 'cantrollback' => array('code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author"), + 'readonlytext' => array('code' => 'readonly', 'info' => "The wiki is currently in read-only mode"), + 'sessionfailure' => array('code' => 'badtoken', 'info' => "Invalid token"), + 'cannotdelete' => array('code' => 'cantdelete', 'info' => "Couldn't delete ``\$1''. Maybe it was deleted already by someone else"), + 'notanarticle' => array('code' => 'missingtitle', 'info' => "The page you requested doesn't exist"), + 'selfmove' => array('code' => 'selfmove', 'info' => "Can't move a page to itself"), + 'immobile_namespace' => array('code' => 'immobilenamespace', 'info' => "You tried to move pages from or to a namespace that is protected from moving"), + 'articleexists' => array('code' => 'articleexists', 'info' => "The destination article already exists and is not a redirect to the source article"), + 'protectedpage' => array('code' => 'protectedpage', 'info' => "You don't have permission to perform this move"), + 'hookaborted' => array('code' => 'hookaborted', 'info' => "The modification you tried to make was aborted by an extension hook"), + 'cantmove-titleprotected' => array('code' => 'protectedtitle', 'info' => "The destination article has been protected from creation"), + // 'badarticleerror' => shouldn't happen + // 'badtitletext' => shouldn't happen + 'ip_range_invalid' => array('code' => 'invalidrange', 'info' => "Invalid IP range"), + 'range_block_disabled' => array('code' => 'rangedisabled', 'info' => "Blocking IP ranges has been disabled"), + 'nosuchusershort' => array('code' => 'nosuchuser', 'info' => "The user you specified doesn't exist"), + 'badipaddress' => array('code' => 'invalidip', 'info' => "Invalid IP address specified"), + 'ipb_expiry_invalid' => array('code' => 'invalidexpiry', 'info' => "Invalid expiry time"), + 'ipb_already_blocked' => array('code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked"), + 'ipb_blocked_as_range' => array('code' => 'blockedasrange', 'info' => "IP address ``\$1'' was blocked as part of range ``\$2''. You can't unblock the IP invidually, but you can unblock the range as a whole."), + 'ipb_cant_unblock' => array('code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already"), + + // API-specific messages + 'missingparam' => array('code' => 'no$1', 'info' => "The \$1 parameter must be set"), + 'invalidtitle' => array('code' => 'invalidtitle', 'info' => "Bad title ``\$1''"), + 'invaliduser' => array('code' => 'invaliduser', 'info' => "Invalid username ``\$1''"), + 'invalidexpiry' => array('code' => 'invalidexpiry', 'info' => "Invalid expiry time"), + 'pastexpiry' => array('code' => 'pastexpiry', 'info' => "Expiry time is in the past"), + 'create-titleexists' => array('code' => 'create-titleexists', 'info' => "Existing titles can't be protected with 'create'"), + 'missingtitle-createonly' => array('code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'"), + 'cantblock' => array('code' => 'cantblock', 'info' => "You don't have permission to block users"), + 'canthide' => array('code' => 'canthide', 'info' => "You don't have permission to hide user names from the block log"), + 'cantblock-email' => array('code' => 'cantblock-email', 'info' => "You don't have permission to block users from sending e-mail through the wiki"), + 'unblock-notarget' => array('code' => 'notarget', 'info' => "Either the id or the user parameter must be set"), + 'unblock-idanduser' => array('code' => 'idanduser', 'info' => "The id and user parameters can\'t be used together"), + 'cantunblock' => array('code' => 'permissiondenied', 'info' => "You don't have permission to unblock users"), + 'cannotundelete' => array('code' => 'cantundelete', 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"), + 'permdenied-undelete' => array('code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions"), + ); + + /** + * Output the error message related to a certain array + * @param array $error Element of a getUserPermissionsErrors() + */ + public function dieUsageMsg($error) { + $key = array_shift($error); + if(isset(self::$messageMap[$key])) + $this->dieUsage(wfMsgReplaceArgs(self::$messageMap[$key]['info'], $error), wfMsgReplaceArgs(self::$messageMap[$key]['code'], $error)); + // If the key isn't present, throw an "unknown error" + $this->dieUsageMsg(array('unknownerror', $key)); + } /** * Internal code errors should be reported with this method @@ -509,6 +641,28 @@ abstract class ApiBase { wfDebugDieBacktrace("Internal error in $method: $message"); } + /** + * Indicates if API needs to check maxlag + */ + public function shouldCheckMaxlag() { + return true; + } + + /** + * Indicates if this module requires edit mode + */ + public function isEditMode() { + return false; + } + + /** + * Indicates whether this module must be called with a POST request + */ + public function mustBePosted() { + return false; + } + + /** * Profiling: total module execution time */ @@ -610,10 +764,12 @@ abstract class ApiBase { print "\n\n"; } - public abstract function getVersion(); + /** + * Returns a String that identifies the version of this class. + */ public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiBase.php 24934 2007-08-20 08:04:12Z nickj $'; - } + return __CLASS__ . ': $Id: ApiBase.php 31259 2008-02-25 14:14:55Z catrope $'; + } } diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php new file mode 100644 index 00000000..e5c238ae --- /dev/null +++ b/includes/api/ApiBlock.php @@ -0,0 +1,164 @@ +.@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 facilitates the blocking of users. Requires API write mode +* to be enabled. +* + * @addtogroup API + */ +class ApiBlock extends ApiBase { + + /** + * Std ctor. + */ + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + /** + * Blocks the user specified in the parameters for the given expiry, with the + * given reason, and with all other settings provided in the params. If the block + * succeeds, produces a result containing the details of the block and notice + * of success. If it fails, the result will specify the nature of the error. + */ + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + if($params['gettoken']) + { + $res['blocktoken'] = $wgUser->editToken(); + $this->getResult()->addValue(null, $this->getModuleName(), $res); + return; + } + + if(is_null($params['user'])) + $this->dieUsageMsg(array('missingparam', 'user')); + if(is_null($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + if(!$wgUser->isAllowed('block')) + $this->dieUsageMsg(array('cantblock')); + if($params['hidename'] && !$wgUser->isAllowed('hideuser')) + $this->dieUsageMsg(array('canthide')); + if($params['noemail'] && !$wgUser->isAllowed('blockemail')) + $this->dieUsageMsg(array('cantblock-email')); + if(wfReadOnly()) + $this->dieUsageMsg(array('readonlytext')); + + $form = new IPBlockForm(''); + $form->BlockAddress = $params['user']; + $form->BlockReason = (is_null($params['reason']) ? '' : $params['reason']); + $form->BlockReasonList = 'other'; + $form->BlockExpiry = ($params['expiry'] == 'never' ? 'infinite' : $params['expiry']); + $form->BlockOther = ''; + $form->BlockAnonOnly = $params['anononly']; + $form->BlockCreateAccount = $params['nocreate']; + $form->BlockEnableAutoBlock = $params['autoblock']; + $form->BlockEmail = $params['noemail']; + $form->BlockHideName = $params['hidename']; + + $dbw = wfGetDb(DB_MASTER); + $dbw->begin(); + $retval = $form->doBlock($userID, $expiry); + if(!empty($retval)) + // We don't care about multiple errors, just report one of them + $this->dieUsageMsg($retval); + + $dbw->commit(); + $res['user'] = $params['user']; + $res['userID'] = $userID; + $res['expiry'] = ($expiry == Block::infinity() ? 'infinite' : $expiry); + $res['reason'] = $params['reason']; + if($params['anononly']) + $res['anononly'] = ''; + if($params['nocreate']) + $res['nocreate'] = ''; + if($params['autoblock']) + $res['autoblock'] = ''; + if($params['noemail']) + $res['noemail'] = ''; + if($params['hidename']) + $res['hidename'] = ''; + + $this->getResult()->addValue(null, $this->getModuleName(), $res); + } + + public function mustBePosted() { return true; } + + public function getAllowedParams() { + return array ( + 'user' => null, + 'token' => null, + 'gettoken' => false, + 'expiry' => 'never', + 'reason' => null, + 'anononly' => false, + 'nocreate' => false, + 'autoblock' => false, + 'noemail' => false, + 'hidename' => false, + ); + } + + public function getParamDescription() { + return array ( + 'user' => 'Username, IP address or IP range you want to block', + 'token' => 'A block token previously obtained through the gettoken parameter', + 'gettoken' => 'If set, a block token will be returned, and no other action will be taken', + 'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.', + 'reason' => 'Reason for block (optional)', + 'anononly' => 'Block anonymous users only (i.e. disable anonymous edits for this IP)', + 'nocreate' => 'Prevent account creation', + 'autoblock' => 'Automatically block the last used IP address, and any subsequent IP addresses they try to login from', + 'noemail' => 'Prevent user from sending e-mail through the wiki. (Requires the "blockemail" right.)', + 'hidename' => 'Hide the username from the block log. (Requires the "hideuser" right.)' + ); + } + + public function getDescription() { + return array( + 'Block a user.' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike', + 'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate&autoblock&noemail' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiBlock.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiChangeRights.php b/includes/api/ApiChangeRights.php new file mode 100644 index 00000000..647a5194 --- /dev/null +++ b/includes/api/ApiChangeRights.php @@ -0,0 +1,155 @@ +.@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 facilitates the changing of user rights. The API eqivalent of + * Special:Userrights. Requires API write mode to be enabled. + * + * @addtogroup API + */ +class ApiChangeRights extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser, $wgRequest; + $this->getMain()->requestWriteMode(); + + if(wfReadOnly()) + $this->dieUsage('The wiki is in read-only mode', 'readonly'); + $params = $this->extractRequestParams(); + + $ur = new UserrightsPage($wgRequest); + $allowed = $ur->changeableGroups(); + $res = array(); + + $u = $ur->fetchUser_real($params['user']); + if(is_array($u)) + switch($u[0]) + { + case UserrightsPage::FETCHUSER_NO_INTERWIKI: + $this->dieUsage("You don't have permission to change users' rights on other wikis", 'nointerwiki'); + case UserrightsPage::FETCHUSER_NO_DATABASE: + $this->dieUsage("Database ``{$u[1]}'' does not exist or is not local", 'nosuchdatabase'); + case UserrightsPage::FETCHUSER_NO_USER: + $this->dieUsage("You specified an empty username, or none at all", 'emptyuser'); + case UserrightsPage::FETCHUSER_NOSUCH_USERID: + $this->dieUsage("There is no user with ID ``{$u[1]}''", 'nosuchuserid'); + case UserrightsPage::FETCHUSER_NOSUCH_USERNAME: + $this->dieUsage("There is no user with username ``{$u[1]}''", 'nosuchusername'); + default: + $this->dieDebug(__METHOD__, "UserrightsPage::fetchUser_real() returned an unknown error ({$u[0]})"); + } + + $curgroups = $u->getGroups(); + if($params['listgroups']) + { + $res['user'] = $u->getName(); + $res['allowedgroups'] = $allowed; + $res['ingroups'] = $curgroups; + $this->getResult()->setIndexedTagName($res['ingroups'], 'group'); + $this->getResult()->setIndexedTagName($res['allowedgroups']['add'], 'group'); + $this->getResult()->setIndexedTagName($res['allowedgroups']['remove'], 'group'); + } +; + if($params['gettoken']) + { + $res['changerightstoken'] = $wgUser->editToken($u->getName()); + $this->getResult()->addValue(null, $this->getModuleName(), $res); + return; + } + + if(empty($params['addto']) && empty($params['rmfrom'])) + $this->dieUsage('At least one of the addto and rmfrom parameters must be set', 'noaddrm'); + if(is_null($params['token'])) + $this->dieUsage('The token parameter must be set', 'notoken'); + if(!$wgUser->matchEditToken($params['token'], $u->getName())) + $this->dieUsage('Invalid token', 'badtoken'); + + $dbw = wfGetDb(DB_MASTER); + $dbw->begin(); + $ur->saveUserGroups($u, $params['rmfrom'], $params['addto'], $params['reason']); + $dbw->commit(); + $res['user'] = $u->getName(); + $res['addedto'] = (array)$params['addto']; + $res['removedfrom'] = (array)$params['rmfrom']; + $res['reason'] = $params['reason']; + + $this->getResult()->setIndexedTagName($res['addedto'], 'group'); + $this->getResult()->setIndexedTagName($res['removedfrom'], 'group'); + $this->getResult()->addValue(null, $this->getModuleName(), $res); + } + + public function getAllowedParams() { + return array ( + 'user' => null, + 'token' => null, + 'gettoken' => false, + 'listgroups' => false, + 'addto' => array( + ApiBase :: PARAM_ISMULTI => true, + ), + 'rmfrom' => array( + ApiBase :: PARAM_ISMULTI => true, + ), + 'reason' => '' + ); + } + + public function getParamDescription() { + return array ( + 'user' => 'The user you want to add to or remove from groups.', + 'token' => 'A changerights token previously obtained through the gettoken parameter.', + 'gettoken' => 'Output a token. Note that the user parameter still has to be set.', + 'listgroups' => 'List the groups the user is in, and the ones you can add them to and remove them from.', + 'addto' => 'Pipe-separated list of groups to add this user to', + 'rmfrom' => 'Pipe-separated list of groups to remove this user from', + 'reason' => 'Reason for change (optional)' + ); + } + + public function getDescription() { + return array( + 'Add or remove a user from certain groups.' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=changerights&user=Bob&gettoken&listgroups', + 'api.php?action=changerights&user=Bob&token=123ABC&addto=sysop&reason=Promoting%20per%20RFA' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiChangeRights.php 28216 2007-12-06 18:33:18Z vasilievvv $'; + } +} diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php new file mode 100644 index 00000000..cd747e7e --- /dev/null +++ b/includes/api/ApiDelete.php @@ -0,0 +1,155 @@ +.@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 facilitates deleting pages. The API eqivalent of action=delete. + * Requires API write mode to be enabled. + * + * @addtogroup API + */ +class ApiDelete extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + /** + * Extracts the title, token, and reason from the request parameters and invokes + * the local delete() function with these as arguments. It does not make use of + * the delete function specified by Article.php. If the deletion succeeds, the + * details of the article deleted and the reason for deletion are added to the + * result object. + */ + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + $titleObj = NULL; + if(!isset($params['title'])) + $this->dieUsageMsg(array('missingparam', 'title')); + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + + $titleObj = Title::newFromText($params['title']); + if(!$titleObj) + $this->dieUsageMsg(array('invalidtitle', $params['title'])); + if(!$titleObj->exists()) + $this->dieUsageMsg(array('notanarticle')); + + $articleObj = new Article($titleObj); + $reason = (isset($params['reason']) ? $params['reason'] : NULL); + $dbw = wfGetDb(DB_MASTER); + $dbw->begin(); + $retval = self::delete($articleObj, $params['token'], $reason); + + if(!empty($retval)) + // We don't care about multiple errors, just report one of them + $this->dieUsageMsg(current($retval)); + + $dbw->commit(); + $r = array('title' => $titleObj->getPrefixedText(), 'reason' => $reason); + $this->getResult()->addValue(null, $this->getModuleName(), $r); + } + + /** + * We have our own delete() function, since Article.php's implementation is split in two phases + * + * @param Article $article - Article object to work on + * @param string $token - Delete token (same as edit token) + * @param string $reason - Reason for the deletion. Autogenerated if NULL + * @return Title::getUserPermissionsErrors()-like array + */ + public static function delete(&$article, $token, &$reason = NULL) + { + global $wgUser; + + // Check permissions + $errors = $article->mTitle->getUserPermissionsErrors('delete', $wgUser); + if(!empty($errors)) + return $errors; + if(wfReadOnly()) + return array(array('readonlytext')); + if($wgUser->isBlocked()) + return array(array('blocked')); + + // Check token + if(!$wgUser->matchEditToken($token)) + return array(array('sessionfailure')); + + // Auto-generate a summary, if necessary + if(is_null($reason)) + { + $reason = $article->generateReason($hasHistory); + if($reason === false) + return array(array('cannotdelete')); + } + + // Luckily, Article.php provides a reusable delete function that does the hard work for us + if($article->doDeleteArticle($reason)) + return array(); + return array(array('cannotdelete', $article->mTitle->getPrefixedText())); + } + + public function mustBePosted() { return true; } + + public function getAllowedParams() { + return array ( + 'title' => null, + 'token' => null, + 'reason' => null, + ); + } + + public function getParamDescription() { + return array ( + 'title' => 'Title of the page you want to delete.', + 'token' => 'A delete token previously retrieved through prop=info', + 'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used.' + ); + } + + public function getDescription() { + return array( + 'Deletes a page. You need to be logged in as a sysop to use this function, see also action=login.' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=delete&title=Main%20Page&token=123ABC', + 'api.php?action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiDelete.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php new file mode 100644 index 00000000..278896fa --- /dev/null +++ b/includes/api/ApiExpandTemplates.php @@ -0,0 +1,97 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 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 functions as a shortcut to the wikitext preprocessor. Expands + * any templates in a provided string, and returns the result of this expansion + * to the caller. + * + * @addtogroup API + */ +class ApiExpandTemplates extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + // Get parameters + $params = $this->extractRequestParams(); + $text = $params['text']; + $title = $params['title']; + $retval = ''; + + //Create title for parser + $title_obj = Title :: newFromText($params['title']); + if(!$title_obj) + $title_obj = Title :: newFromText("API"); // Default title is "API". For example, ExpandTemplates uses "ExpendTemplates" for it + + // Parse text + global $wgParser; + $retval = $wgParser->preprocess( $text, $title_obj, new ParserOptions() ); + + // Return result + $result = $this->getResult(); + $retval_array = array(); + $result->setContent( $retval_array, $retval ); + $result->addValue( null, $this->getModuleName(), $retval_array ); + } + + public function getAllowedParams() { + return array ( + 'title' => array( + ApiBase :: PARAM_DFLT => 'API', + ), + 'text' => null + ); + } + + public function getParamDescription() { + return array ( + 'text' => 'Wikitext to convert', + 'title' => 'Title of page', + ); + } + + public function getDescription() { + return 'This module expand all templates in wikitext'; + } + + protected function getExamples() { + return array ( + 'api.php?action=expandtemplates&text={{Project:Sandbox}}' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiExpandTemplates.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} + diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php index b2f6ceff..9b17b9d3 100644 --- a/includes/api/ApiFeedWatchlist.php +++ b/includes/api/ApiFeedWatchlist.php @@ -62,17 +62,24 @@ class ApiFeedWatchlist extends ApiBase { // limit to the number of hours going from now back $endTime = wfTimestamp(TS_MW, time() - intval($params['hours'] * 60 * 60)); - // Prepare nested request - $fauxReq = new FauxRequest(array ( + $dbr = wfGetDB( DB_SLAVE ); + // Prepare parameters for nested request + $fauxReqArr = array ( 'action' => 'query', 'meta' => 'siteinfo', 'siprop' => 'general', 'list' => 'watchlist', 'wlprop' => 'title|user|comment|timestamp', 'wldir' => 'older', // reverse order - from newest to oldest - 'wlend' => $endTime, // stop at this time + 'wlend' => $dbr->timestamp($endTime), // stop at this time 'wllimit' => 50 - )); + ); + + // Check for 'allrev' parameter, and if found, show all revisions to each page on wl. + if ( ! is_null ( $params['allrev'] ) ) $fauxReqArr['wlallrev'] = ''; + + // Create the request + $fauxReq = new FauxRequest ( $fauxReqArr ); // Execute $module = new ApiMain($fauxReq); @@ -131,7 +138,7 @@ class ApiFeedWatchlist extends ApiBase { return new FeedItem($titleStr, $completeText, $titleUrl, $timestamp, $user); } - protected function getAllowedParams() { + public function getAllowedParams() { global $wgFeedClasses; $feedFormatNames = array_keys($wgFeedClasses); return array ( @@ -144,18 +151,20 @@ class ApiFeedWatchlist extends ApiBase { ApiBase :: PARAM_TYPE => 'integer', ApiBase :: PARAM_MIN => 1, ApiBase :: PARAM_MAX => 72, - ) + ), + 'allrev' => null ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'feedformat' => 'The format of the feed', - 'hours' => 'List pages modified within this many hours from now' + 'hours' => 'List pages modified within this many hours from now', + 'allrev' => 'Include multiple revisions of the same page within given timeframe.' ); } - protected function getDescription() { + public function getDescription() { return 'This module returns a watchlist feed'; } @@ -166,7 +175,7 @@ class ApiFeedWatchlist extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFeedWatchlist.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiFeedWatchlist.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 861310d2..768a18ac 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -35,7 +35,7 @@ if (!defined('MEDIAWIKI')) { */ abstract class ApiFormatBase extends ApiBase { - private $mIsHtml, $mFormat; + private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp; /** * Create a new instance of the formatter. @@ -68,6 +68,18 @@ abstract class ApiFormatBase extends ApiBase { return false; } + /** + * Specify whether or not ampersands should be escaped to '&' 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. + * + * @param boolean Whether or not ampersands should be escaped. + */ + public function setUnescapeAmps ( $b ) { + $this->mUnescapeAmps = $b; + } + /** * Returns true when an HTML filtering printer should be used. * The default implementation assumes that formats ending with 'fm' @@ -99,7 +111,11 @@ abstract class ApiFormatBase extends ApiBase { - MediaWiki API +mUnescapeAmps) { +?> MediaWiki API + MediaWiki API Result + complete documentation, or echo $text; } + /** + * Says pretty-printer that it should use *bold* and $italics$ formatting + */ + 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 < @@ -160,7 +183,7 @@ See complete documentation, or protected function formatHTML($text) { // Escape everything first for full coverage $text = htmlspecialchars($text); - + // encode all comments or tags as safe blue strings $text = preg_replace('/\<(!--.*?--|.*?)\>/', '<\1>', $text); // identify URLs @@ -168,10 +191,19 @@ See complete documentation, or $text = ereg_replace("($protos)://[^ \\'\"()<\n]+", '\\0', $text); // identify requests to api.php $text = ereg_replace("api\\.php\\?[^ \\()<\n\t]+", '\\0', $text); - // make strings inside * bold - $text = ereg_replace("\\*[^<>\n]+\\*", '\\0', $text); - // make strings inside $ italic - $text = ereg_replace("\\$[^<>\n]+\\$", '\\0', $text); + if( $this->mHelp ) { + // make strings inside * bold + $text = ereg_replace("\\*[^<>\n]+\\*", '\\0', $text); + // make strings inside $ italic + $text = ereg_replace("\\$[^<>\n]+\\$", '\\0', $text); + } + + /* Temporary fix for bad links in help messages. As a special case, + * XML-escaped metachars are de-escaped one level in the help message + * for legibility. Should be removed once we have completed a fully-html + * version of the help message. */ + if ( $this->mUnescapeAmps ) + $text = preg_replace( '/&(amp|quot|lt|gt);/', '&\1;', $text ); return $text; } @@ -183,12 +215,12 @@ See complete documentation, or return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName(); } - protected function getDescription() { + public function getDescription() { return $this->getIsHtml() ? ' (pretty-print in HTML)' : ''; } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 25746 2007-09-10 21:36:51Z brion $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 30222 2008-01-28 19:05:26Z catrope $'; } } @@ -250,6 +282,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 25746 2007-09-10 21:36:51Z brion $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php new file mode 100644 index 00000000..f0fc5e91 --- /dev/null +++ b/includes/api/ApiFormatDbg.php @@ -0,0 +1,59 @@ +.@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'); +} + +/** + * @addtogroup API + */ +class ApiFormatDbg extends ApiFormatBase { + + public function __construct($main, $format) { + parent :: __construct($main, $format); + } + + public function getMimeType() { + # This looks like it should be text/plain, but IE7 is so + # brain-damaged it tries to parse text/plain as HTML if it + # contains HTML tags. Using MIME text/text works around this bug + return 'text/text'; + } + + public function execute() { + $this->printText(var_export($this->getResultData(), true)); + } + + public function getDescription() { + return 'Output data in PHP\'s var_export() format' . parent :: getDescription(); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFormatPhp.php 23531 2007-06-29 01:19:14Z simetrical $'; + } +} + diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index 59f3b492..852a64b6 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -66,19 +66,19 @@ class ApiFormatJson extends ApiFormatBase { } } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'callback' => null ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'callback' => 'If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.', ); } - protected function getDescription() { + public function getDescription() { if ($this->mIsRaw) return 'Output data with the debuging elements in JSON format' . parent :: getDescription(); else @@ -86,7 +86,7 @@ class ApiFormatJson extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatJson.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiFormatJson.php 31484 2008-03-03 05:46:20Z brion $'; } } diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php index 766d7041..f830d8e1 100644 --- a/includes/api/ApiFormatPhp.php +++ b/includes/api/ApiFormatPhp.php @@ -45,12 +45,12 @@ class ApiFormatPhp extends ApiFormatBase { $this->printText(serialize($this->getResultData())); } - protected function getDescription() { + public function getDescription() { return 'Output data in serialized PHP format' . parent :: getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatPhp.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiFormatPhp.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php new file mode 100644 index 00000000..c4c45f68 --- /dev/null +++ b/includes/api/ApiFormatTxt.php @@ -0,0 +1,59 @@ +.@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'); +} + +/** + * @addtogroup API + */ +class ApiFormatTxt extends ApiFormatBase { + + public function __construct($main, $format) { + parent :: __construct($main, $format); + } + + public function getMimeType() { + # This looks like it should be text/plain, but IE7 is so + # brain-damaged it tries to parse text/plain as HTML if it + # contains HTML tags. Using MIME text/text works around this bug + return 'text/text'; + } + + public function execute() { + $this->printText(print_r($this->getResultData(), true)); + } + + public function getDescription() { + return 'Output data in PHP\'s print_r() format' . parent :: getDescription(); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFormatPhp.php 23531 2007-06-29 01:19:14Z simetrical $'; + } +} + diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index 0ddfac73..22a0e482 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -80,12 +80,12 @@ class ApiFormatWddx extends ApiFormatBase { } } - protected function getDescription() { + public function getDescription() { return 'Output data in WDDX format' . parent :: getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatWddx.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiFormatWddx.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index 02647923..d39e8049 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -136,12 +136,12 @@ class ApiFormatXml extends ApiFormatBase { break; } } - protected function getDescription() { + public function getDescription() { return 'Output data in XML format' . parent :: getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatXml.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiFormatXml.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php index 400c0a4b..5e15aee6 100644 --- a/includes/api/ApiFormatYaml.php +++ b/includes/api/ApiFormatYaml.php @@ -45,12 +45,12 @@ class ApiFormatYaml extends ApiFormatBase { $this->printText(Spyc :: YAMLDump($this->getResultData())); } - protected function getDescription() { + public function getDescription() { return 'Output data in YAML format' . parent :: getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatYaml.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiFormatYaml.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php index b3ccff0f..b2973b8c 100644 --- a/includes/api/ApiFormatYaml_spyc.php +++ b/includes/api/ApiFormatYaml_spyc.php @@ -385,6 +385,18 @@ return false; } } + + /** + * Find out whether a string needs to be output as a literal rather than in plain style. + * Added by Roan Kattouw 13-03-2008 + * @param $value The string to check + * @return bool + */ + function _needLiteral($value) { + # Check whether the string contains # or : or begins with any of: + # [ - ? , [ ] { } ! * & | > ' " % @ ` ] + return (bool)(preg_match("/[#:]/", $value) || preg_match("/^[-?,[\]{}!*&|>'\"%@`]/", $value)); + } /** * Returns YAML from a key and a value @@ -396,7 +408,7 @@ */ function _dumpNode($key,$value,$indent) { // do some folding here, for blocks - if (strpos($value,"\n")) { + if (strpos($value,"\n") || $this->_needLiteral($value)) { $value = $this->_doLiteralBlock($value,$indent); } else { $value = $this->_doFolding($value,$indent); diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index 9f1e88ea..47a45ea1 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -46,14 +46,18 @@ class ApiHelp extends ApiBase { $this->dieUsage('', 'help'); } - protected function getDescription() { + public function shouldCheckMaxlag() { + return false; + } + + public function getDescription() { return array ( 'Display this help screen.' ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiHelp.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiHelp.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index af68b29d..3e66ed79 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -40,7 +40,7 @@ class ApiLogin extends ApiBase { * Time (in seconds) a user must wait after submitting * a bad login (will be multiplied by the THROTTLE_FACTOR for each bad attempt) */ - const THROTTLE_TIME = 1; + const THROTTLE_TIME = 5; /** * The factor by which the wait-time in between authentication @@ -91,10 +91,15 @@ class ApiLogin extends ApiBase { 'wpRemember' => '' )); + // Init session if necessary + if( session_id() == '' ) { + wfSetupSession(); + } + $loginForm = new LoginForm($params); switch ($loginForm->authenticateUserData()) { case LoginForm :: SUCCESS : - global $wgUser; + global $wgUser, $wgCookiePrefix; $wgUser->setOption('rememberpassword', 1); $wgUser->setCookies(); @@ -103,6 +108,8 @@ class ApiLogin extends ApiBase { $result['lguserid'] = $_SESSION['wsUserID']; $result['lgusername'] = $_SESSION['wsUserName']; $result['lgtoken'] = $_SESSION['wsToken']; + $result['cookieprefix'] = $wgCookiePrefix; + $result['sessionid'] = session_id(); break; case LoginForm :: NO_NAME : @@ -129,6 +136,7 @@ class ApiLogin extends ApiBase { if ($result['result'] != 'Success') { $result['wait'] = $this->cacheBadLogin(); + $result['details'] = "Please wait " . self::THROTTLE_TIME . " seconds before next log-in attempt"; } // if we were allowed to try to login, memcache is fine @@ -209,8 +217,10 @@ class ApiLogin extends ApiBase { private function getMemCacheKey() { return wfMemcKey( 'apilogin', 'badlogin', 'ip', wfGetIP() ); } + + public function mustBePosted() { return true; } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'name' => null, 'password' => null, @@ -218,7 +228,7 @@ class ApiLogin extends ApiBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'name' => 'User Name', 'password' => 'Password', @@ -226,7 +236,7 @@ class ApiLogin extends ApiBase { ); } - protected function getDescription() { + public function getDescription() { return array ( 'This module is used to login and get the authentication tokens. ', 'In the event of a successful log-in, a cookie will be attached', @@ -243,7 +253,7 @@ class ApiLogin extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogin.php 24695 2007-08-09 09:53:05Z yurik $'; + return __CLASS__ . ': $Id: ApiLogin.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php new file mode 100644 index 00000000..d578acf3 --- /dev/null +++ b/includes/api/ApiLogout.php @@ -0,0 +1,71 @@ +@gmail.com, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 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 to allow users to log out of the wiki. API equivalent of + * Special:Userlogout. + * + * @addtogroup API + */ +class ApiLogout extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser; + $wgUser->logout(); + } + + public function getAllowedParams() { + return array (); + } + + public function getParamDescription() { + return array (); + } + + public function getDescription() { + return array ( + 'This module is used to logout and clear session data' + ); + } + + protected function getExamples() { + return array( + 'api.php?action=logout' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id$'; + } +} diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 00b3f63f..874e531c 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -53,10 +53,26 @@ class ApiMain extends ApiBase { */ private static $Modules = array ( 'login' => 'ApiLogin', + 'logout' => 'ApiLogout', 'query' => 'ApiQuery', + 'expandtemplates' => 'ApiExpandTemplates', + 'parse' => 'ApiParse', 'opensearch' => 'ApiOpenSearch', 'feedwatchlist' => 'ApiFeedWatchlist', 'help' => 'ApiHelp', + 'paraminfo' => 'ApiParamInfo', + ); + + private static $WriteModules = array ( + 'rollback' => 'ApiRollback', + 'delete' => 'ApiDelete', + 'undelete' => 'ApiUndelete', + 'protect' => 'ApiProtect', + 'block' => 'ApiBlock', + 'unblock' => 'ApiUnblock', + 'move' => 'ApiMove', + #'changerights' => 'ApiChangeRights' + # Disabled for now ); /** @@ -73,7 +89,11 @@ class ApiMain extends ApiBase { 'xmlfm' => 'ApiFormatXml', 'yaml' => 'ApiFormatYaml', 'yamlfm' => 'ApiFormatYaml', - 'rawfm' => 'ApiFormatJson' + 'rawfm' => 'ApiFormatJson', + 'txt' => 'ApiFormatTxt', + 'txtfm' => 'ApiFormatTxt', + 'dbg' => 'ApiFormatDbg', + 'dbgfm' => 'ApiFormatDbg' ); private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames; @@ -108,14 +128,17 @@ class ApiMain extends ApiBase { if (!$wgUser->isAllowed('read')) { self::$Modules = array( - 'login' => self::$Modules['login'], - 'help' => self::$Modules['help'] + 'login' => self::$Modules['login'], + 'logout' => self::$Modules['logout'], + 'help' => self::$Modules['help'], ); } } - global $wgAPIModules; // extension modules + global $wgAPIModules, $wgEnableWriteAPI; // extension modules $this->mModules = $wgAPIModules + self :: $Modules; + if($wgEnableWriteAPI) + $this->mModules += self::$WriteModules; $this->mModuleNames = array_keys($this->mModules); // todo: optimize $this->mFormats = self :: $Formats; @@ -157,7 +180,7 @@ class ApiMain extends ApiBase { public function requestWriteMode() { if (!$this->mEnableWrite) $this->dieUsage('Editing of this site is disabled. Make sure the $wgEnableWriteAPI=true; ' . - 'statement is included in the site\'s LocalSettings.php file', 'readonly'); + 'statement is included in the site\'s LocalSettings.php file', 'noapiwrite'); } /** @@ -271,7 +294,7 @@ class ApiMain extends ApiBase { // Something is seriously wrong // $errMessage = array ( - 'code' => 'internal_api_error', + 'code' => 'internal_api_error_'. get_class($e), 'info' => "Exception Caught: {$e->getMessage()}" ); ApiResult :: setContent($errMessage, "\n\n{$e->getTraceAsString()}\n\n"); @@ -287,16 +310,34 @@ class ApiMain extends ApiBase { * Execute the actual module, without any error handling */ protected function executeAction() { - + $params = $this->extractRequestParams(); - + $this->mShowVersions = $params['version']; $this->mAction = $params['action']; // Instantiate the module requested by the user $module = new $this->mModules[$this->mAction] ($this, $this->mAction); - + + if( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) { + // Check for maxlag + global $wgLoadBalancer, $wgShowHostnames; + $maxLag = $params['maxlag']; + list( $host, $lag ) = $wgLoadBalancer->getMaxLag(); + if ( $lag > $maxLag ) { + if( $wgShowHostnames ) { + ApiBase :: dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); + } else { + ApiBase :: dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' ); + } + return; + } + } + if (!$this->mInternalMode) { + // Ignore mustBePosted() for internal calls + if($module->mustBePosted() && !$this->mRequest->wasPosted()) + $this->dieUsage("The {$this->mAction} module requires a POST request", 'mustbeposted'); // See if custom printer is used $this->mPrinter = $module->getCustomPrinter(); @@ -326,7 +367,16 @@ class ApiMain extends ApiBase { protected function printResult($isError) { $printer = $this->mPrinter; $printer->profileIn(); + + /* If the help message is requested in the default (xmlfm) format, + * tell the printer not to escape ampersands so that our links do + * not break. */ + $params = $this->extractRequestParams(); + $printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError ) + && $params['format'] == ApiMain::API_DEFAULT_FORMAT ); + $printer->initPrinter($isError); + $printer->execute(); $printer->closePrinter(); $printer->profileOut(); @@ -335,7 +385,7 @@ class ApiMain extends ApiBase { /** * See ApiBase for description. */ - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'format' => array ( ApiBase :: PARAM_DFLT => ApiMain :: API_DEFAULT_FORMAT, @@ -345,25 +395,29 @@ class ApiMain extends ApiBase { ApiBase :: PARAM_DFLT => 'help', ApiBase :: PARAM_TYPE => $this->mModuleNames ), - 'version' => false + 'version' => false, + 'maxlag' => array ( + ApiBase :: PARAM_TYPE => 'integer' + ), ); } /** * See ApiBase for description. */ - protected function getParamDescription() { + public function getParamDescription() { return array ( 'format' => 'The format of the output', 'action' => 'What action you would like to perform', - 'version' => 'When showing help, include version for each module' + 'version' => 'When showing help, include version for each module', + 'maxlag' => 'Maximum lag' ); } /** * See ApiBase for description. */ - protected function getDescription() { + public function getDescription() { return array ( '', '', @@ -396,8 +450,9 @@ class ApiMain extends ApiBase { */ protected function getCredits() { return array( - 'This API is being implemented by Yuri Astrakhan [[User:Yurik]] / @gmail.com', - 'Please leave your comments and suggestions at http://www.mediawiki.org/wiki/API' + 'This API is being implemented by Roan Kattouw .@home.nl', + 'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org', + 'or file a bug report at http://bugzilla.wikimedia.org/' ); } @@ -405,6 +460,8 @@ class ApiMain extends ApiBase { * Override the parent to generate help messages for all available modules. */ public function makeHelpMsg() { + + $this->mPrinter->setHelp(); // Use parent to make default message for the main module $msg = parent :: makeHelpMsg(); @@ -445,11 +502,12 @@ class ApiMain extends ApiBase { } private $mIsBot = null; - private $mIsSysop = null; + private $mCanApiHighLimits = null; /** * Returns true if the currently logged in user is a bot, false otherwise + * OBSOLETE, use canApiHighLimits() instead */ public function isBot() { if (!isset ($this->mIsBot)) { @@ -462,6 +520,7 @@ class ApiMain extends ApiBase { /** * Similar to isBot(), this method returns true if the logged in user is * a sysop, and false if not. + * OBSOLETE, use canApiHighLimits() instead */ public function isSysop() { if (!isset ($this->mIsSysop)) { @@ -471,6 +530,15 @@ class ApiMain extends ApiBase { return $this->mIsSysop; } + + public function canApiHighLimits() { + if (!isset($this->mCanApiHighLimits)) { + global $wgUser; + $this->mCanApiHighLimits = $wgUser->isAllowed('apihighlimits'); + } + + return $this->mCanApiHighLimits; + } public function getShowVersions() { return $this->mShowVersions; @@ -483,7 +551,7 @@ class ApiMain extends ApiBase { public function getVersion() { $vers = array (); $vers[] = 'MediaWiki ' . SpecialVersion::getVersion(); - $vers[] = __CLASS__ . ': $Id: ApiMain.php 25364 2007-08-31 15:23:48Z tstarling $'; + $vers[] = __CLASS__ . ': $Id: ApiMain.php 31484 2008-03-03 05:46:20Z brion $'; $vers[] = ApiBase :: getBaseVersion(); $vers[] = ApiFormatBase :: getBaseVersion(); $vers[] = ApiQueryBase :: getBaseVersion(); @@ -515,6 +583,13 @@ class ApiMain extends ApiBase { protected function addFormat( $fmtName, $fmtClass ) { $this->mFormats[$fmtName] = $fmtClass; } + + /** + * Get the array mapping module names to class names + */ + function getModules() { + return $this->mModules; + } } /** @@ -539,3 +614,4 @@ class UsageException extends Exception { } } + diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php new file mode 100644 index 00000000..a8c39c9a --- /dev/null +++ b/includes/api/ApiMove.php @@ -0,0 +1,152 @@ +.@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"); +} + + +/** + * @addtogroup API + */ +class ApiMove extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + if(is_null($params['reason'])) + $params['reason'] = ''; + + $titleObj = NULL; + if(!isset($params['from'])) + $this->dieUsageMsg(array('missingparam', 'from')); + if(!isset($params['to'])) + $this->dieUsageMsg(array('missingparam', 'to')); + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + + $fromTitle = Title::newFromText($params['from']); + if(!$fromTitle) + $this->dieUsageMsg(array('invalidtitle', $params['from'])); + if(!$fromTitle->exists()) + $this->dieUsageMsg(array('notanarticle')); + $fromTalk = $fromTitle->getTalkPage(); + + $toTitle = Title::newFromText($params['to']); + if(!$toTitle) + $this->dieUsageMsg(array('invalidtitle', $params['to'])); + $toTalk = $toTitle->getTalkPage(); + + // Run getUserPermissionsErrors() here so we get message arguments too, + // rather than just a message key. The latter is troublesome for messages + // that use arguments. + // FIXME: moveTo() should really return an array, requires some + // refactoring of other code, though (mainly SpecialMovepage.php) + $errors = array_merge($fromTitle->getUserPermissionsErrors('move', $wgUser), + $fromTitle->getUserPermissionsErrors('edit', $wgUser), + $toTitle->getUserPermissionsErrors('move', $wgUser), + $toTitle->getUserPermissionsErrors('edit', $wgUser)); + if(!empty($errors)) + // We don't care about multiple errors, just report one of them + $this->dieUsageMsg(current($errors)); + + $dbw = wfGetDB(DB_MASTER); + $dbw->begin(); + $retval = $fromTitle->moveTo($toTitle, true, $params['reason'], !$params['noredirect']); + if($retval !== true) + $this->dieUsageMsg(array($retval)); + + $r = array('from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason']); + if(!$params['noredirect']) + $r['redirectcreated'] = ''; + + 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) + { + $r['talkfrom'] = $fromTalk->getPrefixedText(); + $r['talkto'] = $toTalk->getPrefixedText(); + } + // We're not gonna dieUsage() on failure, since we already changed something + else + { + $r['talkmove-error-code'] = ApiBase::$messageMap[$retval]['code']; + $r['talkmove-error-info'] = ApiBase::$messageMap[$retval]['info']; + } + } + $dbw->commit(); // Make sure all changes are really written to the DB + $this->getResult()->addValue(null, $this->getModuleName(), $r); + } + + public function mustBePosted() { return true; } + + public function getAllowedParams() { + return array ( + 'from' => null, + 'to' => null, + 'token' => null, + 'reason' => null, + 'movetalk' => false, + 'noredirect' => false + ); + } + + public function getParamDescription() { + return array ( + 'from' => 'Title of the page you want to move.', + 'to' => 'Title you want to rename the page to.', + 'token' => 'A move token previously retrieved through prop=info', + 'reason' => 'Reason for the move (optional).', + 'movetalk' => 'Move the talk page, if it exists.', + 'noredirect' => 'Don\'t create a redirect' + ); + } + + public function getDescription() { + return array( + 'Moves a page.' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=move&from=Exampel&to=Example&token=123ABC&reason=Misspelled%20title&movetalk&noredirect' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiMove.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php index 8484b163..f4b600fe 100644 --- a/includes/api/ApiOpenSearch.php +++ b/includes/api/ApiOpenSearch.php @@ -44,37 +44,12 @@ class ApiOpenSearch extends ApiBase { public function execute() { $params = $this->extractRequestParams(); $search = $params['search']; + $limit = $params['limit']; // Open search results may be stored for a very long time $this->getMain()->setCacheMaxAge(1200); - - $title = Title :: newFromText($search); - if(!$title) - return; // Return empty result - - // Prepare nested request - $req = new FauxRequest(array ( - 'action' => 'query', - 'list' => 'allpages', - 'apnamespace' => $title->getNamespace(), - 'aplimit' => 10, - 'apprefix' => $title->getDBkey() - )); - - // Execute - $module = new ApiMain($req); - $module->execute(); - - // Get resulting data - $data = $module->getResultData(); - - // Reformat useful data for future printing by JSON engine - $srchres = array (); - foreach ($data['query']['allpages'] as & $pageinfo) { - // Note: this data will no be printable by the xml engine - // because it does not support lists of unnamed items - $srchres[] = $pageinfo['title']; - } + + $srchres = PrefixSearch::titleSearch( $search, $limit ); // Set top level elements $result = $this->getResult(); @@ -82,19 +57,27 @@ class ApiOpenSearch extends ApiBase { $result->addValue(null, 1, $srchres); } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( - 'search' => null + 'search' => null, + 'limit' => array ( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => 100, + ApiBase :: PARAM_MAX2 => 100 + ) ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( - 'search' => 'Search string' + 'search' => 'Search string', + 'limit' => 'Maximum amount of results to return' ); } - protected function getDescription() { + public function getDescription() { return 'This module implements OpenSearch protocol'; } @@ -105,7 +88,7 @@ class ApiOpenSearch extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiOpenSearch.php 24099 2007-07-15 00:52:35Z yurik $'; + return __CLASS__ . ': $Id: ApiOpenSearch.php 30275 2008-01-30 01:07:49Z brion $'; } } diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php new file mode 100644 index 00000000..7de22252 --- /dev/null +++ b/includes/api/ApiParamInfo.php @@ -0,0 +1,167 @@ +.@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"); +} + +/** + * @addtogroup API + */ +class ApiParamInfo extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + // Get parameters + $params = $this->extractRequestParams(); + $result = $this->getResult(); + $r = array(); + if(is_array($params['modules'])) + { + $modArr = $this->getMain()->getModules(); + foreach($params['modules'] as $m) + { + if(!isset($modArr[$m])) + { + $r['modules'][] = array('name' => $m, 'missing' => ''); + continue; + } + $obj = new $modArr[$m]($this->getMain(), $m); + $a = $this->getClassInfo($obj); + $a['name'] = $m; + $r['modules'][] = $a; + } + $result->setIndexedTagName($r['modules'], 'module'); + } + if(is_array($params['querymodules'])) + { + $queryObj = new ApiQuery($this->getMain(), 'query'); + $qmodArr = $queryObj->getModules(); + foreach($params['querymodules'] as $qm) + { + if(!isset($qmodArr[$qm])) + { + $r['querymodules'][] = array('name' => $qm, 'missing' => ''); + continue; + } + $obj = new $qmodArr[$qm]($this, $qm); + $a = $this->getClassInfo($obj); + $a['name'] = $qm; + $r['querymodules'][] = $a; + } + $result->setIndexedTagName($r['querymodules'], 'module'); + } + $result->addValue(null, $this->getModuleName(), $r); + } + + function getClassInfo($obj) + { + $result = $this->getResult(); + $retval['classname'] = get_class($obj); + $retval['description'] = (is_array($obj->getDescription()) ? implode("\n", $obj->getDescription()) : $obj->getDescription()); + $retval['prefix'] = $obj->getModulePrefix(); + $allowedParams = $obj->getAllowedParams(); + if(!is_array($allowedParams)) + return $retval; + $retval['parameters'] = array(); + $paramDesc = $obj->getParamDescription(); + foreach($obj->getAllowedParams() as $n => $p) + { + $a = array('name' => $n); + if(!is_array($p)) + { + if(is_bool($p)) + { + $a['type'] = 'bool'; + $a['default'] = ($p ? 'true' : 'false'); + } + if(is_string($p)) + $a['default'] = $p; + $retval['parameters'][] = $a; + continue; + } + + if(isset($p[ApiBase::PARAM_DFLT])) + $a['default'] = $p[ApiBase::PARAM_DFLT]; + if(isset($p[ApiBase::PARAM_ISMULTI])) + if($p[ApiBase::PARAM_ISMULTI]) + $a['multi'] = ''; + if(isset($p[ApiBase::PARAM_TYPE])) + { + $a['type'] = $p[ApiBase::PARAM_TYPE]; + if(is_array($a['type'])) + $result->setIndexedTagName($a['type'], 't'); + } + if(isset($p[ApiBase::PARAM_MAX])) + $a['max'] = $p[ApiBase::PARAM_MAX]; + if(isset($p[ApiBase::PARAM_MAX2])) + $a['highmax'] = $p[ApiBase::PARAM_MAX2]; + if(isset($p[ApiBase::PARAM_MIN])) + $a['min'] = $p[ApiBase::PARAM_MIN]; + if(isset($paramDesc[$n])) + $a['description'] = (is_array($paramDesc[$n]) ? implode("\n", $paramDesc[$n]) : $paramDesc[$n]); + $retval['parameters'][] = $a; + } + $result->setIndexedTagName($retval['parameters'], 'param'); + return $retval; + } + + public function getAllowedParams() { + return array ( + 'modules' => array( + ApiBase :: PARAM_ISMULTI => true + ), + 'querymodules' => array( + ApiBase :: PARAM_ISMULTI => true + ) + ); + } + + public function getParamDescription() { + return array ( + 'modules' => 'List of module names (value of the action= parameter)', + 'querymodules' => 'List of query module names (value of prop=, meta= or list= parameter)', + ); + } + + public function getDescription() { + return 'Obtain information about certain API parameters'; + } + + protected function getExamples() { + return array ( + 'api.php?action=paraminfo&modules=parse&querymodules=allpages|siteinfo' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiParse.php 29810 2008-01-15 21:33:08Z catrope $'; + } +} + diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php new file mode 100644 index 00000000..21a21e8d --- /dev/null +++ b/includes/api/ApiParse.php @@ -0,0 +1,202 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 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"); +} + +/** + * @addtogroup API + */ +class ApiParse extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + // Get parameters + $params = $this->extractRequestParams(); + $text = $params['text']; + $title = $params['title']; + $page = $params['page']; + if(!is_null($page) && (!is_null($text) || $title != "API")) + $this->dieUsage("The page parameter cannot be used together with the text and title parameters", 'params'); + $prop = array_flip($params['prop']); + + global $wgParser, $wgUser; + if(!is_null($page)) { + $titleObj = Title::newFromText($page); + if(!$titleObj) + $this->dieUsageMsg(array('missingtitle', $page)); + + // Try the parser cache first + $articleObj = new Article($titleObj); + $pcache =& ParserCache::singleton(); + $p_result = $pcache->get($articleObj, $wgUser); + if(!$p_result) { + $p_result = $wgParser->parse($articleObj->getContent(), $titleObj, new ParserOptions()); + global $wgUseParserCache; + if($wgUseParserCache) + $pcache->save($p_result, $articleObj, $wgUser); + } + } else { + $titleObj = Title::newFromText($title); + if(!$titleObj) + $titleObj = Title::newFromText("API"); + $p_result = $wgParser->parse($text, $titleObj, new ParserOptions()); + } + + // Return result + $result = $this->getResult(); + $result_array = array(); + if(isset($prop['text'])) { + $result_array['text'] = array(); + $result->setContent($result_array['text'], $p_result->getText()); + } + if(isset($prop['langlinks'])) + $result_array['langlinks'] = $this->formatLangLinks($p_result->getLanguageLinks()); + if(isset($prop['categories'])) + $result_array['categories'] = $this->formatCategoryLinks($p_result->getCategories()); + if(isset($prop['links'])) + $result_array['links'] = $this->formatLinks($p_result->getLinks()); + if(isset($prop['templates'])) + $result_array['templates'] = $this->formatLinks($p_result->getTemplates()); + if(isset($prop['images'])) + $result_array['images'] = array_keys($p_result->getImages()); + if(isset($prop['externallinks'])) + $result_array['externallinks'] = array_keys($p_result->getExternalLinks()); + if(isset($prop['sections'])) + $result_array['sections'] = $p_result->getSections(); + + $result_mapping = array( + 'langlinks' => 'll', + 'categories' => 'cl', + 'links' => 'pl', + 'templates' => 'tl', + 'images' => 'img', + 'externallinks' => 'el', + 'sections' => 's', + ); + $this->setIndexedTagNames( $result_array, $result_mapping ); + $result->addValue( null, $this->getModuleName(), $result_array ); + } + + private function formatLangLinks( $links ) { + $result = array(); + foreach( $links as $link ) { + $entry = array(); + $bits = split( ':', $link, 2 ); + $entry['lang'] = $bits[0]; + $this->getResult()->setContent( $entry, $bits[1] ); + $result[] = $entry; + } + return $result; + } + + private function formatCategoryLinks( $links ) { + $result = array(); + foreach( $links as $link => $sortkey ) { + $entry = array(); + $entry['sortkey'] = $sortkey; + $this->getResult()->setContent( $entry, $link ); + $result[] = $entry; + } + return $result; + } + + private function formatLinks( $links ) { + $result = array(); + foreach( $links as $ns => $nslinks ) { + foreach( $nslinks as $title => $id ) { + $entry = array(); + $entry['ns'] = $ns; + $this->getResult()->setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() ); + if( $id != 0 ) + $entry['exists'] = ''; + $result[] = $entry; + } + } + return $result; + } + + private function setIndexedTagNames( &$array, $mapping ) { + foreach( $mapping as $key => $name ) { + if( isset( $array[$key] ) ) + $this->getResult()->setIndexedTagName( $array[$key], $name ); + } + } + + public function getAllowedParams() { + return array ( + 'title' => array( + ApiBase :: PARAM_DFLT => 'API', + ), + 'text' => null, + 'page' => null, + 'prop' => array( + ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections', + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array( + 'text', + 'langlinks', + 'categories', + 'links', + 'templates', + 'images', + 'externallinks', + 'sections' + ) + ) + ); + } + + public function getParamDescription() { + return array ( + 'text' => 'Wikitext to parse', + 'title' => 'Title of page the text belongs to', + 'page' => 'Parse the content of this page. Cannot be used together with text and title', + 'prop' => array('Which pieces of information to get.', + 'NOTE: Section tree is only generated if there are more than 4 sections, or if the __TOC__ keyword is present' + ), + ); + } + + public function getDescription() { + return 'This module parses wikitext and returns parser output'; + } + + protected function getExamples() { + return array ( + 'api.php?action=parse&text={{Project:Sandbox}}' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiParse.php 30262 2008-01-29 14:47:27Z catrope $'; + } +} + diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php new file mode 100644 index 00000000..40a4b73d --- /dev/null +++ b/includes/api/ApiProtect.php @@ -0,0 +1,154 @@ +.@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"); +} + +/** + * @addtogroup API + */ +class ApiProtect extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + $titleObj = NULL; + if(!isset($params['title'])) + $this->dieUsageMsg(array('missingparam', 'title')); + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + if(!isset($params['protections']) || empty($params['protections'])) + $this->dieUsageMsg(array('missingparam', 'protections')); + + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + + $titleObj = Title::newFromText($params['title']); + if(!$titleObj) + $this->dieUsageMsg(array('invalidtitle', $params['title'])); + + $errors = $titleObj->getUserPermissionsErrors('protect', $wgUser); + if(!empty($errors)) + // We don't care about multiple errors, just report one of them + $this->dieUsageMsg(current($errors)); + + if(in_array($params['expiry'], array('infinite', 'indefinite', 'never'))) + $expiry = Block::infinity(); + else + { + $expiry = strtotime($params['expiry']); + if($expiry < 0 || $expiry == false) + $this->dieUsageMsg(array('invalidexpiry')); + + $expiry = wfTimestamp(TS_MW, $expiry); + if($expiry < wfTimestampNow()) + $this->dieUsageMsg(array('pastexpiry')); + } + + $protections = array(); + foreach($params['protections'] as $prot) + { + $p = explode('=', $prot); + $protections[$p[0]] = ($p[1] == 'all' ? '' : $p[1]); + if($titleObj->exists() && $p[0] == 'create') + $this->dieUsageMsg(array('create-titleexists')); + if(!$titleObj->exists() && $p[0] != 'create') + $this->dieUsageMsg(array('missingtitles-createonly')); + } + + $dbw = wfGetDb(DB_MASTER); + $dbw->begin(); + if($titleObj->exists()) { + $articleObj = new Article($titleObj); + $ok = $articleObj->updateRestrictions($protections, $params['reason'], $params['cascade'], $expiry); + } else + $ok = $titleObj->updateTitleProtection($protections['create'], $params['reason'], $expiry); + if(!$ok) + // This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime? + // Just throw an unknown error in this case, as it's very likely to be a race condition + $this->dieUsageMsg(array()); + $dbw->commit(); + $res = array('title' => $titleObj->getPrefixedText(), 'reason' => $params['reason']); + if($expiry == Block::infinity()) + $res['expiry'] = 'infinity'; + else + $res['expiry'] = wfTimestamp(TS_ISO_8601, $expiry); + + if($params['cascade']) + $res['cascade'] = ''; + $res['protections'] = $protections; + $this->getResult()->addValue(null, $this->getModuleName(), $res); + } + + public function mustBePosted() { return true; } + + public function getAllowedParams() { + return array ( + 'title' => null, + 'token' => null, + 'protections' => array( + ApiBase :: PARAM_ISMULTI => true + ), + 'expiry' => 'infinite', + 'reason' => '', + 'cascade' => false + ); + } + + public function getParamDescription() { + return array ( + 'title' => 'Title of the page you want to restore.', + 'token' => 'A protect token previously retrieved through prop=info', + 'protections' => 'Pipe-separated list of protection levels, formatted action=group (e.g. edit=sysop)', + 'expiry' => 'Expiry timestamp. If set to \'infinite\', \'indefinite\' or \'never\', the protection will never expire.', + 'reason' => 'Reason for (un)protecting (optional)', + 'cascade' => 'Enable cascading protection (i.e. protect pages included in this page)' + ); + } + + public function getDescription() { + return array( + 'Change the protection level of a page.' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=sysop|move=sysop&cascade&expiry=20070901163000', + 'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=all|move=all&reason=Lifting%20restrictions' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiProtect.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 76dbb338..29abd859 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -60,9 +60,12 @@ class ApiQuery extends ApiBase { private $mQueryListModules = array ( 'allpages' => 'ApiQueryAllpages', 'alllinks' => 'ApiQueryAllLinks', + 'allcategories' => 'ApiQueryAllCategories', 'allusers' => 'ApiQueryAllUsers', 'backlinks' => 'ApiQueryBacklinks', + 'blocks' => 'ApiQueryBlocks', 'categorymembers' => 'ApiQueryCategoryMembers', + 'deletedrevs' => 'ApiQueryDeletedrevs', 'embeddedin' => 'ApiQueryBacklinks', 'imageusage' => 'ApiQueryBacklinks', 'logevents' => 'ApiQueryLogEvents', @@ -71,11 +74,14 @@ class ApiQuery extends ApiBase { 'usercontribs' => 'ApiQueryContributions', 'watchlist' => 'ApiQueryWatchlist', 'exturlusage' => 'ApiQueryExtLinksUsage', + 'users' => 'ApiQueryUsers', + 'random' => 'ApiQueryRandom', ); private $mQueryMetaModules = array ( 'siteinfo' => 'ApiQuerySiteinfo', 'userinfo' => 'ApiQueryUserInfo', + 'allmessages' => 'ApiQueryAllmessages', ); private $mSlaveDB = null; @@ -143,6 +149,13 @@ class ApiQuery extends ApiBase { public function getPageSet() { return $this->mPageSet; } + + /** + * Get the array mapping module names to class names + */ + function getModules() { + return array_merge($this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules); + } /** * Query execution happens in the following steps: @@ -375,7 +388,7 @@ class ApiQuery extends ApiBase { * Returns the list of allowed parameters for this module. * Qurey module also lists all ApiPageSet parameters as its own. */ - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, @@ -456,8 +469,13 @@ class ApiQuery extends ApiBase { $psModule = new ApiPageSet($this); return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters(); } + + // @todo should work correctly + public function shouldCheckMaxlag() { + return true; + } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => 'Which properties to get for the titles/revisions/pageids', 'list' => 'Which lists to get', @@ -468,7 +486,7 @@ class ApiQuery extends ApiBase { ); } - protected function getDescription() { + 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.', @@ -485,7 +503,7 @@ class ApiQuery extends ApiBase { public function getVersion() { $psModule = new ApiPageSet($this); $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiQuery.php 24494 2007-07-31 17:53:37Z yurik $'; + $vers[] = __CLASS__ . ': $Id: ApiQuery.php 30222 2008-01-28 19:05:26Z catrope $'; $vers[] = $psModule->getVersion(); return $vers; } diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php new file mode 100644 index 00000000..84494876 --- /dev/null +++ b/includes/api/ApiQueryAllCategories.php @@ -0,0 +1,142 @@ +.@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 categories, even the ones that don't have + * category pages. + * + * @addtogroup API + */ +class ApiQueryAllCategories extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'ac'); + } + + 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('categorylinks'); + $this->addFields('cl_to'); + + if (!is_null($params['from'])) + $this->addWhere('cl_to>=' . $db->addQuotes(ApiQueryBase :: titleToKey($params['from']))); + if (isset ($params['prefix'])) + $this->addWhere("cl_to LIKE '" . $db->escapeLike(ApiQueryBase :: titleToKey($params['prefix'])) . "%'"); + + $this->addOption('LIMIT', $params['limit']+1); + $this->addOption('ORDER BY', 'cl_to' . ($params['dir'] == 'descending' ? ' DESC' : '')); + $this->addOption('DISTINCT'); + + $res = $this->select(__METHOD__); + + $pages = array(); + $count = 0; + while ($row = $db->fetchObject($res)) { + if (++ $count > $params['limit']) { + // We've reached the one extra which shows that there are additional cats to be had. Stop here... + // TODO: Security issue - if the user has no right to view next title, it will still be shown + $this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->cl_to)); + break; + } + + // Normalize titles + $titleObj = Title::makeTitle(NS_CATEGORY, $row->cl_to); + if(!is_null($resultPageSet)) + $pages[] = $titleObj->getPrefixedText(); + else + // Don't show "Category:" everywhere in non-generator mode + $pages[] = $titleObj->getText(); + } + $db->freeResult($res); + + if (is_null($resultPageSet)) { + $result = $this->getResult(); + $result->setIndexedTagName($pages, 'c'); + $result->addValue('query', $this->getModuleName(), $pages); + } else { + $resultPageSet->populateFromTitles($pages); + } + } + + public function getAllowedParams() { + return array ( + 'from' => null, + 'prefix' => null, + 'dir' => array( + ApiBase :: PARAM_DFLT => 'ascending', + ApiBase :: PARAM_TYPE => array( + 'ascending', + 'descending' + ), + ), + '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 + ) + ); + } + + public function getParamDescription() { + return array ( + 'from' => 'The category to start enumerating from.', + 'prefix' => 'Search for all category titles that begin with this value.', + 'dir' => 'Direction to sort in.', + 'limit' => 'How many categories to return.' + ); + } + + public function getDescription() { + return 'Enumerate all categories'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&generator=allcategories&gacprefix=List&prop=info', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryAllLinks.php 28216 2007-12-06 18:33:18Z vasilievvv $'; + } +} diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php index 17f24b65..d5b80644 100644 --- a/includes/api/ApiQueryAllLinks.php +++ b/includes/api/ApiQueryAllLinks.php @@ -125,7 +125,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { } } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'from' => null, 'prefix' => null, @@ -152,7 +152,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'from' => 'The page title to start enumerating from.', 'prefix' => 'Search for all page titles that begin with this value.', @@ -163,7 +163,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { ); } - protected function getDescription() { + public function getDescription() { return 'Enumerate all links that point to a given namespace'; } @@ -174,6 +174,6 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllLinks.php 24453 2007-07-30 08:09:15Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryAllLinks.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php index 92bcc1a1..e055b3c5 100644 --- a/includes/api/ApiQueryAllUsers.php +++ b/includes/api/ApiQueryAllUsers.php @@ -48,8 +48,9 @@ class ApiQueryAllUsers extends ApiQueryBase { $prop = array_flip($prop); $fld_editcount = isset($prop['editcount']); $fld_groups = isset($prop['groups']); + $fld_registration = isset($prop['registration']); } else { - $fld_editcount = $fld_groups = false; + $fld_editcount = $fld_groups = $fld_registration = false; } $limit = $params['limit']; @@ -80,6 +81,9 @@ class ApiQueryAllUsers extends ApiQueryBase { } else { $sqlLimit = $limit+1; } + + if ($fld_registration) + $this->addFields('user_registration'); $this->addOption('LIMIT', $sqlLimit); $this->addTables($tables); @@ -129,6 +133,8 @@ class ApiQueryAllUsers extends ApiQueryBase { $lastUserData = array( 'name' => $lastUser ); if ($fld_editcount) $lastUserData['editcount'] = intval($row->user_editcount); + if ($fld_registration) + $lastUserData['registration'] = wfTimestamp(TS_ISO_8601, $row->user_registration); } @@ -152,7 +158,7 @@ class ApiQueryAllUsers extends ApiQueryBase { $result->addValue('query', $this->getModuleName(), $data); } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'from' => null, 'prefix' => null, @@ -164,6 +170,7 @@ class ApiQueryAllUsers extends ApiQueryBase { ApiBase :: PARAM_TYPE => array ( 'editcount', 'groups', + 'registration', ) ), 'limit' => array ( @@ -176,7 +183,7 @@ class ApiQueryAllUsers extends ApiQueryBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'from' => 'The user name to start enumerating from.', 'prefix' => 'Search for all page titles that begin with this value.', @@ -188,7 +195,7 @@ class ApiQueryAllUsers extends ApiQueryBase { ); } - protected function getDescription() { + public function getDescription() { return 'Enumerate all registered users'; } @@ -199,6 +206,6 @@ class ApiQueryAllUsers extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllUsers.php 24870 2007-08-17 13:01:35Z robchurch $'; + return __CLASS__ . ': $Id: ApiQueryAllUsers.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php new file mode 100644 index 00000000..b7c86a91 --- /dev/null +++ b/includes/api/ApiQueryAllmessages.php @@ -0,0 +1,129 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 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'); +} + +/** + * A query action to return messages from site message cache + * + * @addtogroup API + */ +class ApiQueryAllmessages extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'am'); + } + + public function execute() { + global $wgMessageCache; + $params = $this->extractRequestParams(); + + if(!is_null($params['lang'])) + { + global $wgLang; + $wgLang = Language::factory($params['lang']); + } + + + //Determine which messages should we print + $messages_target = array(); + if( $params['messages'] == '*' ) { + $wgMessageCache->loadAllMessages(); + $message_names = array_keys( array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) ) ); + sort( $message_names ); + $messages_target = $message_names; + } else { + $messages_target = explode( '|', $params['messages'] ); + } + + //Filter messages + if( isset( $params['filter'] ) ) { + $messages_filtered = array(); + foreach( $messages_target as $message ) { + if( strpos( $message, $params['filter'] ) !== false ) { //!== is used because filter can be at the beginnig of the string + $messages_filtered[] = $message; + } + } + $messages_target = $messages_filtered; + } + + $wgMessageCache->disableTransform(); + + //Get all requested messages + $messages = array(); + foreach( $messages_target as $message ) { + $message = trim( $message ); //Message list can be formatted like "msg1 | msg2 | msg3", so let's trim() it + $messages[$message] = wfMsg( $message ); + } + + //Print the result + $result = $this->getResult(); + $messages_out = array(); + foreach( $messages as $name => $value ) { + $message = array(); + $message['name'] = $name; + $result->setContent( $message, $value ); + $messages_out[] = $message; + } + $result->setIndexedTagName( $messages_out, 'message' ); + $result->addValue( 'query', $this->getModuleName(), $messages_out ); + } + + public function getAllowedParams() { + return array ( + 'messages' => array ( + ApiBase :: PARAM_DFLT => '*', + ), + 'filter' => array(), + 'lang' => null, + ); + } + + public function getParamDescription() { + return array ( + 'messages' => 'Which messages to output. "*" means all messages', + 'filter' => 'Return only messages that contains specified string', + 'lang' => 'Language code', + ); + } + + public function getDescription() { + return 'Return messages from this site.'; + } + + protected function getExamples() { + return array( + 'api.php?action=query&meta=allmessages&amfilter=ipb-', + 'api.php?action=query&meta=allmessages&ammessages=august|mainpage&amlang=de', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryAllmessages.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index d9715b1a..280d1de2 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -86,6 +86,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $prlevel = $params['prlevel']; if (!is_null($prlevel) && $prlevel != '' && $prlevel != '*') $this->addWhereFld('pr_level', $prlevel); + + $this->addOption('DISTINCT'); $forceNameTitleIndex = false; @@ -93,10 +95,22 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $this->dieUsage('prlevel may not be used without prtype', 'params'); } - $this->addTables('page'); + if($params['filterlanglinks'] == 'withoutlanglinks') { + $pageName = $this->getDB()->tableName('page'); + $llName = $this->getDB()->tableName('langlinks'); + $tables = "$pageName LEFT JOIN $llName ON page_id=ll_from"; + $this->addWhere('ll_from IS NULL'); + $this->addTables($tables); + $forceNameTitleIndex = false; + } else if($params['filterlanglinks'] == 'withlanglinks') { + $this->addTables(array('page', 'langlinks')); + $this->addWhere('page_id=ll_from'); + $forceNameTitleIndex = false; + } else { + $this->addTables('page'); + } if ($forceNameTitleIndex) $this->addOption('USE INDEX', 'name_title'); - if (is_null($resultPageSet)) { $this->addFields(array ( @@ -110,7 +124,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $limit = $params['limit']; $this->addOption('LIMIT', $limit+1); - $this->addOption('ORDER BY', 'page_namespace, page_title'); + $this->addOption('ORDER BY', 'page_namespace, page_title' . + ($params['dir'] == 'descending' ? ' DESC' : '')); $res = $this->select(__METHOD__); @@ -143,7 +158,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } } - protected function getAllowedParams() { + public function getAllowedParams() { global $wgRestrictionTypes, $wgRestrictionLevels; return array ( @@ -169,9 +184,11 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { ), 'prtype' => array ( ApiBase :: PARAM_TYPE => $wgRestrictionTypes, + ApiBase :: PARAM_ISMULTI => true ), 'prlevel' => array ( ApiBase :: PARAM_TYPE => $wgRestrictionLevels, + ApiBase :: PARAM_ISMULTI => true ), 'limit' => array ( ApiBase :: PARAM_DFLT => 10, @@ -179,25 +196,42 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { ApiBase :: PARAM_MIN => 1, ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'dir' => array ( + ApiBase :: PARAM_DFLT => 'ascending', + ApiBase :: PARAM_TYPE => array ( + 'ascending', + 'descending' + ) + ), + 'filterlanglinks' => array( + ApiBase :: PARAM_TYPE => array( + 'withlanglinks', + 'withoutlanglinks', + 'all' + ), + ApiBase :: PARAM_DFLT => 'all' ) ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'from' => 'The page title to start enumerating from.', 'prefix' => 'Search for all page titles that begin with this value.', 'namespace' => 'The namespace to enumerate.', 'filterredir' => 'Which pages to list.', + 'dir' => 'The direction in which to list', 'minsize' => 'Limit to pages with at least this many bytes', 'maxsize' => 'Limit to pages with at most this many bytes', 'prtype' => 'Limit to protected pages only', 'prlevel' => 'The protection level (must be used with apprtype= parameter)', + 'filterlanglinks' => 'Filter based on whether a page has langlinks', 'limit' => 'How many total pages to return.' ); } - protected function getDescription() { + public function getDescription() { return 'Enumerate all pages sequentially in a given namespace'; } @@ -215,7 +249,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllpages.php 24694 2007-08-09 08:41:58Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryAllpages.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index a676b4bf..1ca5c33a 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -179,7 +179,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } $db->freeResult($res); - if (is_null($resultPageSet) && !empty($data)) { + if (is_null($resultPageSet)) { $result = $this->getResult(); $result->setIndexedTagName($data, $this->bl_code); $result->addValue('query', $this->getModuleName(), $data); @@ -315,7 +315,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { '|' . $lastPageID; } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'title' => null, @@ -343,7 +343,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'title' => 'Title to search. If null, titles= parameter will be used instead, but will be obsolete soon.', 'continue' => 'When more results are available, use this to continue.', @@ -354,7 +354,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { ); } - protected function getDescription() { + public function getDescription() { switch ($this->getModuleName()) { case 'backlinks' : return 'Find all pages that link to the given page'; @@ -387,7 +387,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBacklinks.php 25476 2007-09-04 14:44:46Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryBacklinks.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index c810cfa7..031e3c02 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -110,8 +110,9 @@ abstract class ApiQueryBase extends ApiBase { if (!is_null($end)) $this->addWhere($field . $before . $db->addQuotes($end)); - - $this->addOption('ORDER BY', $field . ($isDirNewer ? '' : ' DESC')); + + if (!isset($this->options['ORDER BY'])) + $this->addOption('ORDER BY', $field . ($isDirNewer ? '' : ' DESC')); } protected function addOption($name, $value = null) { @@ -230,7 +231,7 @@ abstract class ApiQueryBase extends ApiBase { } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiQueryBase.php 24533 2007-08-01 22:46:22Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryBase.php 31484 2008-03-03 05:46:20Z brion $'; } } diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php new file mode 100644 index 00000000..165792b5 --- /dev/null +++ b/includes/api/ApiQueryBlocks.php @@ -0,0 +1,239 @@ +.@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 available pages. + * + * @addtogroup API + */ +class ApiQueryBlocks extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'bk'); + } + + public function execute() { + $this->run(); + } + + private function run() { + global $wgUser; + + $params = $this->extractRequestParams(); + $prop = array_flip($params['prop']); + $fld_id = isset($prop['id']); + $fld_user = isset($prop['user']); + $fld_by = isset($prop['by']); + $fld_timestamp = isset($prop['timestamp']); + $fld_expiry = isset($prop['expiry']); + $fld_reason = isset($prop['reason']); + $fld_range = isset($prop['range']); + $fld_flags = isset($prop['flags']); + + $result = $this->getResult(); + $pageSet = $this->getPageSet(); + $titles = $pageSet->getTitles(); + $data = array(); + + $this->addTables('ipblocks'); + if($fld_id) + $this->addFields('ipb_id'); + if($fld_user) + $this->addFields(array('ipb_address', 'ipb_user')); + if($fld_by) + { + $this->addTables('user'); + $this->addFields(array('ipb_by', 'user_name')); + $this->addWhere('user_id = ipb_by'); + } + if($fld_timestamp) + $this->addFields('ipb_timestamp'); + if($fld_expiry) + $this->addFields('ipb_expiry'); + if($fld_reason) + $this->addFields('ipb_reason'); + if($fld_range) + $this->addFields(array('ipb_range_start', 'ipb_range_end')); + if($fld_flags) + $this->addFields(array('ipb_auto', 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', 'ipb_block_email', 'ipb_deleted')); + + $this->addOption('LIMIT', $params['limit'] + 1); + $this->addWhereRange('ipb_timestamp', $params['dir'], $params['start'], $params['end']); + if(isset($params['ids'])) + $this->addWhere(array('ipb_id' => $params['ids'])); + if(isset($params['users'])) + $this->addWhere(array('ipb_address' => $params['users'])); + if(!$wgUser->isAllowed('oversight')) + $this->addWhere(array('ipb_deleted' => 0)); + + // Purge expired entries on one in every 10 queries + if(!mt_rand(0, 10)) + Block::purgeExpired(); + + $res = $this->select(__METHOD__); + $db = wfGetDB(); + + $count = 0; + while($row = $db->fetchObject($res)) + { + if($count++ == $params['limit']) + { + // We've had enough + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp)); + break; + } + $block = array(); + if($fld_id) + $block['id'] = $row->ipb_id; + if($fld_user && !$row->ipb_auto) + { + $block['user'] = $row->ipb_address; + } + if($fld_by) + { + $block['by'] = $row->user_name; + } + if($fld_timestamp) + $block['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ipb_timestamp); + if($fld_expiry) + $block['expiry'] = Block::decodeExpiry($row->ipb_expiry, TS_ISO_8601); + if($fld_reason) + $block['reason'] = $row->ipb_reason; + if($fld_range) + { + $block['rangestart'] = $this->convertHexIP($row->ipb_range_start); + $block['rangeend'] = $this->convertHexIP($row->ipb_range_end); + } + if($fld_flags) + { + // For clarity, these flags use the same names as their action=block counterparts + if($row->ipb_auto) + $block['automatic'] = ''; + if($row->ipb_anon_only) + $block['anononly'] = ''; + if($row->ipb_create_account) + $block['nocreate'] = ''; + if($row->ipb_enable_autoblock) + $block['autoblock'] = ''; + if($row->ipb_block_email) + $block['noemail'] = ''; + if($row->ipb_deleted) + $block['hidden'] = ''; + } + $data[] = $block; + } + $result->setIndexedTagName($data, 'block'); + $result->addValue('query', $this->getModuleName(), $data); + } + + protected function convertHexIP($ip) + { + // Converts a hexadecimal IP to nnn.nnn.nnn.nnn format + $dec = wfBaseConvert($ip, 16, 10); + $parts[0] = (int)($dec / (256*256*256)); + $dec %= 256*256*256; + $parts[1] = (int)($dec / (256*256)); + $dec %= 256*256; + $parts[2] = (int)($dec / 256); + $parts[3] = $dec % 256; + return implode('.', $parts); + } + + public function getAllowedParams() { + return array ( + 'start' => array( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array( + ApiBase :: PARAM_TYPE => 'timestamp', + ), + 'dir' => array( + ApiBase :: PARAM_TYPE => array( + 'newer', + 'older' + ), + ApiBase :: PARAM_DFLT => 'older' + ), + 'ids' => array( + ApiBase :: PARAM_TYPE => 'integer', + ApiBase :: PARAM_ISMULTI => true + ), + 'users' => array( + ApiBase :: PARAM_ISMULTI => true + ), + 'limit' => array( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'prop' => array( + ApiBase :: PARAM_DFLT => 'id|user|by|timestamp|expiry|reason|flags', + ApiBase :: PARAM_TYPE => array( + 'id', + 'user', + 'by', + 'timestamp', + 'expiry', + 'reason', + 'range', + 'flags' + ), + 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', + 'ids' => 'Pipe-separated list of block IDs to list (optional)', + 'users' => 'Pipe-separated list of users to search for (optional)', + 'limit' => 'The maximum amount of blocks to list', + 'prop' => 'Which properties to get', + ); + } + + public function getDescription() { + return 'List all blocked users and IP addresses.'; + } + + protected function getExamples() { + return array ( + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryBlocks.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php index 42bc1c38..63d42bfa 100644 --- a/includes/api/ApiQueryCategories.php +++ b/includes/api/ApiQueryCategories.php @@ -120,7 +120,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { $db->freeResult($res); } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, @@ -131,13 +131,13 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => 'Which additional properties to get for each category.', ); } - protected function getDescription() { + public function getDescription() { return 'List all categories the page(s) belong to'; } @@ -151,7 +151,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategories.php 24092 2007-07-14 19:04:31Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryCategories.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php index 58a454a5..e831f291 100644 --- a/includes/api/ApiQueryCategoryMembers.php +++ b/includes/api/ApiQueryCategoryMembers.php @@ -51,12 +51,18 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { $params = $this->extractRequestParams(); - $category = $params['category']; - if (is_null($category)) - $this->dieUsage("Category parameter is required", 'param_category'); - $categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $category ); - if ( is_null( $categoryTitle ) ) - $this->dieUsage("Category name $category is not valid", 'param_category'); + if (is_null($params['category'])) { + if (is_null($params['title'])) + $this->dieUsage("Either the cmcategory or the cmtitle parameter is required", 'notitle'); + else + $categoryTitle = Title::newFromText($params['title']); + } else if(is_null($params['title'])) + $categoryTitle = Title::makeTitleSafe(NS_CATEGORY, $params['category']); + else + $this->dieUsage("The cmcategory and cmtitle parameters can't be used together", 'titleandcategory'); + + if ( is_null( $categoryTitle ) || $categoryTitle->getNamespace() != NS_CATEGORY ) + $this->dieUsage("The category name you entered is not valid", 'invalidcategory'); $prop = array_flip($params['prop']); $fld_ids = isset($prop['ids']); @@ -78,18 +84,19 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { if($params['sort'] == 'timestamp') { $this->addOption('USE INDEX', 'cl_timestamp'); - $this->addOption('ORDER BY', 'cl_to, cl_timestamp'); + $this->addOption('ORDER BY', 'cl_to, cl_timestamp' . ($params['dir'] == 'desc' ? ' DESC' : '')); } else { $this->addOption('USE INDEX', 'cl_sortkey'); - $this->addOption('ORDER BY', 'cl_to, cl_sortkey, cl_from'); + $this->addOption('ORDER BY', 'cl_to, cl_sortkey' . ($params['dir'] == 'desc' ? ' DESC' : '') . ', cl_from'); } $this->addWhere('cl_from=page_id'); $this->setContinuation($params['continue']); $this->addWhereFld('cl_to', $categoryTitle->getDBkey()); $this->addWhereFld('page_namespace', $params['namespace']); + $this->addWhereRange('cl_timestamp', ($params['dir'] == 'asc' ? 'newer' : 'older'), $params['start'], $params['end']); $limit = $params['limit']; $this->addOption('LIMIT', $limit +1); @@ -172,9 +179,10 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { } } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( - 'category' => null, + 'title' => null, + 'category' => null, // DEPRECATED, will be removed in early March 'prop' => array ( ApiBase :: PARAM_DFLT => 'ids|title', ApiBase :: PARAM_ISMULTI => true, @@ -203,36 +211,53 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { 'sortkey', 'timestamp' ) + ), + 'dir' => array( + ApiBase :: PARAM_DFLT => 'asc', + ApiBase :: PARAM_TYPE => array( + 'asc', + 'desc' + ) + ), + 'start' => array( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array( + ApiBase :: PARAM_TYPE => 'timestamp' ) ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( - 'category' => 'Which category to enumerate (required)', + 'title' => 'Which category to enumerate (required). Must include Category: prefix', 'prop' => 'What pieces of information to include', 'namespace' => 'Only include pages in these namespaces', 'sort' => 'Property to sort by', + 'dir' => 'In which direction to sort', + 'start' => 'Timestamp to start listing from', + 'end' => 'Timestamp to end listing at', 'continue' => 'For large categories, give the value retured from previous query', 'limit' => 'The maximum number of pages to return.', + 'category' => 'DEPRECATED. Like title, but without the Category: prefix.', ); } - protected function getDescription() { + public function getDescription() { return 'List all pages in a given category'; } protected function getExamples() { return array ( - "Get first 10 pages in the categories [[Physics]]:", - " api.php?action=query&list=categorymembers&cmcategory=Physics", - "Get page info about first 10 pages in the categories [[Physics]]:", - " api.php?action=query&generator=categorymembers&gcmcategory=Physics&prop=info", + "Get first 10 pages in [[Category:Physics]]:", + " api.php?action=query&list=categorymembers&cmtitle=Category:Physics", + "Get page info about first 10 pages in [[Category:Physics]]:", + " api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info", ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 25474 2007-09-04 14:30:31Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 30670 2008-02-07 15:17:42Z catrope $'; } } diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php new file mode 100644 index 00000000..1b7fbdb0 --- /dev/null +++ b/includes/api/ApiQueryDeletedrevs.php @@ -0,0 +1,235 @@ +.@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 available pages. + * + * @addtogroup API + */ +class ApiQueryDeletedrevs extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'dr'); + } + + public function execute() { + + global $wgUser; + // Before doing anything at all, let's check permissions + if(!$wgUser->isAllowed('deletedhistory')) + $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied'); + + $db = $this->getDB(); + $params = $this->extractRequestParams(false); + $prop = array_flip($params['prop']); + $fld_revid = isset($prop['revid']); + $fld_user = isset($prop['user']); + $fld_comment = isset($prop['comment']); + $fld_minor = isset($prop['minor']); + $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->addTables('archive'); + $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp')); + if($fld_revid) + $this->addFields('ar_rev_id'); + if($fld_user) + $this->addFields('ar_user_text'); + if($fld_comment) + $this->addFields('ar_comment'); + if($fld_minor) + $this->addFields('ar_minor_edit'); + if($fld_len) + $this->addFields('ar_len'); + if($fld_content) + { + $this->addTables('text'); + $this->addFields(array('ar_text', 'ar_text_id', 'old_text', 'old_flags')); + $this->addWhere('ar_text_id = old_id'); + + // This also means stricter restrictions + if(!$wgUser->isAllowed('undelete')) + $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); + } + // Check limits + $userMax = $fld_content ? ApiBase :: LIMIT_SML1 : ApiBase :: LIMIT_BIG1; + $botMax = $fld_content ? ApiBase :: LIMIT_SML2 : ApiBase :: LIMIT_BIG2; + if( $limit == 'max' ) { + $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; + $this->getResult()->addValue( 'limits', 'limit', $limit ); + } + $this->validateLimit('limit', $params['limit'], 1, $userMax, $botMax); + if($fld_token) + // Undelete tokens are identical for all pages, so we cache one here + $token = $wgUser->editToken(); + + // We need a custom WHERE clause that matches all titles. + if(count($titles) > 0) + { + $lb = new LinkBatch($titles); + $where = $lb->constructSet('ar', $db); + $this->addWhere($where); + } + + $this->addOption('LIMIT', $params['limit'] + 1); + $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']); + if(isset($params['namespace'])) + $this->addWhereFld('ar_namespace', $params['namespace']); + $res = $this->select(__METHOD__); + $pages = array(); + $count = 0; + // First populate the $pages array + while($row = $db->fetchObject($res)) + { + if($count++ == $params['limit']) + { + // We've had enough + $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; + if($fld_user) + $rev['user'] = $row->ar_user_text; + if($fld_comment) + $rev['comment'] = $row->ar_comment; + if($fld_minor) + if($row->ar_minor_edit == 1) + $rev['minor'] = ''; + if($fld_len) + $rev['len'] = $row->ar_len; + if($fld_content) + ApiResult::setContent($rev, Revision::getRevisionText($row)); + + $t = Title::makeTitle($row->ar_namespace, $row->ar_title); + if(!isset($pages[$t->getPrefixedText()])) + { + $pages[$t->getPrefixedText()] = array( + 'title' => $t->getPrefixedText(), + 'ns' => intval($row->ar_namespace), + 'revisions' => array($rev) + ); + if($fld_token) + $pages[$t->getPrefixedText()]['token'] = $token; + } + else + $pages[$t->getPrefixedText()]['revisions'][] = $rev; + } + $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); + } + + public function getAllowedParams() { + return array ( + 'start' => array( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array( + ApiBase :: PARAM_TYPE => 'timestamp', + ), + 'dir' => array( + ApiBase :: PARAM_TYPE => array( + 'newer', + 'older' + ), + ApiBase :: PARAM_DFLT => 'older' + ), + 'namespace' => array( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => 'namespace' + ), + 'limit' => array( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'prop' => array( + ApiBase :: PARAM_DFLT => 'user|comment', + ApiBase :: PARAM_TYPE => array( + 'revid', + 'user', + 'comment', + 'minor', + 'len', + 'content', + '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', + 'namespace' => 'The namespaces to search in', + 'limit' => 'The maximum amount of revisions to list', + 'prop' => 'Which properties to get' + ); + } + + public function getDescription() { + return 'List deleted revisions.'; + } + + protected function getExamples() { + return array ( + 'List the first 50 deleted revisions in the Category and Category talk namespaces', + ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=14|15', + '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' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php index 385ae65b..896a0171 100644 --- a/includes/api/ApiQueryExtLinksUsage.php +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -134,7 +134,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { } } - protected function getAllowedParams() { + public function getAllowedParams() { global $wgUrlProtocols; $protocols = array(); foreach ($wgUrlProtocols as $p) { @@ -173,7 +173,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => 'What pieces of information to include', 'offset' => 'Used for paging. Use the value returned for "continue"', @@ -184,7 +184,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { ); } - protected function getDescription() { + public function getDescription() { return 'Enumerate pages that contain a given URL'; } @@ -195,6 +195,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 24694 2007-08-09 08:41:58Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php index 440b31d6..07183910 100644 --- a/includes/api/ApiQueryExternalLinks.php +++ b/includes/api/ApiQueryExternalLinks.php @@ -75,7 +75,7 @@ class ApiQueryExternalLinks extends ApiQueryBase { $db->freeResult($res); } - protected function getDescription() { + public function getDescription() { return 'Returns all external urls (not interwikies) from the given page(s)'; } @@ -87,7 +87,7 @@ class ApiQueryExternalLinks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 23819 2007-07-07 03:05:09Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php index 3d568ba1..3714ccf6 100644 --- a/includes/api/ApiQueryImageInfo.php +++ b/includes/api/ApiQueryImageInfo.php @@ -42,15 +42,20 @@ class ApiQueryImageInfo extends ApiQueryBase { public function execute() { $params = $this->extractRequestParams(); - $history = $params['history']; - $prop = array_flip($params['prop']); - $fld_timestamp = isset($prop['timestamp']); - $fld_user = isset($prop['user']); - $fld_comment = isset($prop['comment']); - $fld_url = isset($prop['url']); - $fld_size = isset($prop['size']); - $fld_sha1 = isset($prop['sha1']); + $this->fld_timestamp = isset($prop['timestamp']); + $this->fld_user = isset($prop['user']); + $this->fld_comment = isset($prop['comment']); + $this->fld_url = isset($prop['url']); + $this->fld_size = isset($prop['size']); + $this->fld_sha1 = isset($prop['sha1']); + $this->fld_metadata = isset($prop['metadata']); + + if($params['urlheight'] != -1 && $params['urlwidth'] == -1) + $this->dieUsage("iiurlheight cannot be used without iiurlwidth", 'iiurlwidth'); + $this->scale = ($params['urlwidth'] != -1); + $this->urlwidth = $params['urlwidth']; + $this->urlheight = $params['urlheight']; $pageIds = $this->getPageSet()->getAllTitlesByNamespace(); if (!empty($pageIds[NS_IMAGE])) { @@ -65,54 +70,84 @@ class ApiQueryImageInfo extends ApiQueryBase { } else { $repository = $img->getRepoName(); - - $isCur = true; - while($line = $img->nextHistoryLine()) { // assignment - $row = get_object_vars( $line ); - $vals = array(); - $prefix = $isCur ? 'img' : 'oi'; - - if ($fld_timestamp) - $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row["${prefix}_timestamp"]); - if ($fld_user) { - $vals['user'] = $row["${prefix}_user_text"]; - if(!$row["${prefix}_user"]) - $vals['anon'] = ''; - } - if ($fld_size) { - $vals['size'] = intval($row["{$prefix}_size"]); - $vals['width'] = intval($row["{$prefix}_width"]); - $vals['height'] = intval($row["{$prefix}_height"]); - } - if ($fld_url) - $vals['url'] = $isCur ? $img->getURL() : $img->getArchiveUrl($row["oi_archive_name"]); - if ($fld_comment) - $vals['comment'] = $row["{$prefix}_description"]; - - if ($fld_sha1) - $vals['sha1'] = wfBaseConvert($row["{$prefix}_sha1"], 36, 16, 40); - - $data[] = $vals; - - if (!$history) // Stop after the first line. - break; - - $isCur = false; + + // 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']) && + (is_null($params['end']) || $img->getTimestamp() >= $params['end'])) { + $data[] = $this->getInfo($img); } - $img->resetHistory(); + // 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']); + 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_IMAGE]) == 1) + $this->setContinueEnumParameter('start', $oldie->getTimestamp()); + break; + } + $data[] = $this->getInfo($oldie); + } } - $this->getResult()->addValue(array ('query', 'pages', intval($pageId)), - 'imagerepository', - $repository); - if (!empty($data)) - $this->addPageSubItems($pageId, $data); + $this->getResult()->addValue(array( + 'query', 'pages', intval($pageId)), + 'imagerepository', $repository + ); + if (!empty($data)) + $this->addPageSubItems($pageId, $data); } } } - protected function getAllowedParams() { + /** + * Get result information for an image revision + * @param File f The image + * @return array Result array + */ + protected function getInfo($f) { + $vals = array(); + if($this->fld_timestamp) + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $f->getTimestamp()); + if($this->fld_user) { + $vals['user'] = $f->getUser(); + if(!$f->getUser('id')) + $vals['anon'] = ''; + } + if($this->fld_size) { + $vals['size'] = intval($f->getSize()); + $vals['width'] = intval($f->getWidth()); + $vals['height'] = intval($f->getHeight()); + } + if($this->fld_url) { + if($this->scale && !$f->isOld()) { + $thumb = $f->getThumbnail($this->urlwidth, $this->urlheight); + if($thumb) + { + $vals['thumburl'] = $thumb->getURL(); + $vals['thumbwidth'] = $thumb->getWidth(); + $vals['thumbheight'] = $thumb->getHeight(); + } + } + $vals['url'] = $f->getURL(); + } + if($this->fld_comment) + $vals['comment'] = $f->getDescription(); + if($this->fld_sha1) + $vals['sha1'] = wfBaseConvert($f->getSha1(), 36, 16, 40); + if($this->fld_metadata) { + $metadata = unserialize($f->getMetadata()); + $vals['metadata'] = $metadata ? $metadata : null; + $this->getResult()->setIndexedTagName_recursive($vals['metadata'], 'meta'); + } + return $vals; + } + + public function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, @@ -123,21 +158,46 @@ class ApiQueryImageInfo extends ApiQueryBase { 'comment', 'url', 'size', - 'sha1' + 'sha1', + 'metadata' ) ), - 'history' => false, + 'limit' => array( + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_DFLT => 1, + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'start' => array( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'urlwidth' => array( + ApiBase :: PARAM_TYPE => 'integer', + ApiBase :: PARAM_DFLT => -1 + ), + 'urlheight' => array( + ApiBase :: PARAM_TYPE => 'integer', + ApiBase :: PARAM_DFLT => -1 + ) ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => 'What image information to get.', - 'history' => 'Include upload history', + 'limit' => 'How many image revisions to return', + 'start' => 'Timestamp to start listing from', + 'end' => 'Timestamp to stop listing at', + 'urlwidth' => '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', ); } - protected function getDescription() { + public function getDescription() { return array ( 'Returns image information and upload history' ); @@ -146,11 +206,11 @@ 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&iihistory&iiprop=timestamp|user|url', + 'api.php?action=query&titles=Image:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImageInfo.php 25456 2007-09-03 19:58:05Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryImageInfo.php 30665 2008-02-07 12:21:48Z catrope $'; } } diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php index d64a653b..f7405374 100644 --- a/includes/api/ApiQueryImages.php +++ b/includes/api/ApiQueryImages.php @@ -98,7 +98,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase { $db->freeResult($res); } - protected function getDescription() { + public function getDescription() { return 'Returns all images contained on the given page(s)'; } @@ -112,7 +112,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImages.php 24092 2007-07-14 19:04:31Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryImages.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index bebf4006..2dee22b0 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -40,6 +40,7 @@ class ApiQueryInfo extends ApiQueryBase { } public function requestExtraData($pageSet) { + $pageSet->requestField('page_restrictions'); $pageSet->requestField('page_is_redirect'); $pageSet->requestField('page_is_new'); $pageSet->requestField('page_counter'); @@ -65,11 +66,16 @@ class ApiQueryInfo extends ApiQueryBase { $tok_protect = $this->getTokenFlag($token, 'protect'); $tok_move = $this->getTokenFlag($token, 'move'); } + else + // Fix E_NOTICEs about unset variables + $token = $tok_edit = $tok_delete = $tok_protect = $tok_move = null; $pageSet = $this->getPageSet(); $titles = $pageSet->getGoodTitles(); + $missing = $pageSet->getMissingTitles(); $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'); @@ -77,23 +83,46 @@ class ApiQueryInfo extends ApiQueryBase { $pageLatest = $pageSet->getCustomField('page_latest'); $pageLength = $pageSet->getCustomField('page_len'); - if ($fld_protection && count($titles) > 0) { + $db = $this->getDB(); + if ($fld_protection && !empty($titles)) { $this->addTables('page_restrictions'); - $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry')); + $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade')); $this->addWhereFld('pr_page', array_keys($titles)); - $db = $this->getDB(); $res = $this->select(__METHOD__); while($row = $db->fetchObject($res)) { - $protections[$row->pr_page][] = array( - 'type' => $row->pr_type, - 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ) - ); + $a = array( + 'type' => $row->pr_type, + 'level' => $row->pr_level, + 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ) + ); + if($row->pr_cascade) + $a['cascade'] = ''; + $protections[$row->pr_page][] = $a; } $db->freeResult($res); } - + // We don't need to check for pt stuff if there are no nonexistent titles + if($fld_protection && !empty($missing)) + { + $this->resetQueryParams(); + // Construct a custom WHERE clause that matches all titles in $missing + $lb = new LinkBatch($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( + 'type' => 'create', + 'level' => $row->pt_create_perm, + 'expiry' => Block::decodeExpiry($row->pt_expiry, TS_ISO_8601) + ); + } + $db->freeResult($res); + } + foreach ( $titles as $pageid => $title ) { $pageInfo = array ( 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]), @@ -125,7 +154,36 @@ class ApiQueryInfo extends ApiQueryBase { $pageInfo['protection'] = $protections[$pageid]; $result->setIndexedTagName($pageInfo['protection'], 'pr'); } else { - $pageInfo['protection'] = array(); + # Also check old restrictions + if( $pageRestrictions[$pageid] ) { + foreach( explode( ':', trim( $pageRestrictions[$pageid] ) ) as $restrict ) { + $temp = explode( '=', trim( $restrict ) ); + if(count($temp) == 1) { + // old old format should be treated as edit/move restriction + $restriction = trim( $temp[0] ); + $pageInfo['protection'][] = array( + 'type' => 'edit', + 'level' => $restriction, + 'expiry' => 'infinity', + ); + $pageInfo['protection'][] = array( + 'type' => 'move', + 'level' => $restriction, + 'expiry' => 'infinity', + ); + } else { + $restriction = trim( $temp[1] ); + $pageInfo['protection'][] = array( + 'type' => $temp[0], + 'level' => $restriction, + 'expiry' => 'infinity', + ); + } + } + $result->setIndexedTagName($pageInfo['protection'], 'pr'); + } else { + $pageInfo['protection'] = array(); + } } } @@ -135,18 +193,31 @@ class ApiQueryInfo extends ApiQueryBase { ), $pageid, $pageInfo); } - // Get edit tokens for missing titles if requested - // Delete, protect and move tokens are N/A for missing titles anyway - if($tok_edit) + // Get edit/protect tokens and protection data for missing titles if requested + // Delete and move tokens are N/A for missing titles anyway + if($tok_edit || $tok_protect || $fld_protection) { - $missing = $pageSet->getMissingTitles(); - $res = $result->getData(); - foreach($missing as $pageid => $title) - $res['query']['pages'][$pageid]['edittoken'] = $wgUser->editToken(); + $res = &$result->getData(); + foreach($missing as $pageid => $title) { + if($tok_edit) + $res['query']['pages'][$pageid]['edittoken'] = $wgUser->editToken(); + if($tok_protect) + $res['query']['pages'][$pageid]['protecttoken'] = $wgUser->editToken(); + 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'); + } + } } } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_DFLT => NULL, @@ -166,7 +237,7 @@ class ApiQueryInfo extends ApiQueryBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => array ( 'Which additional properties to get:', @@ -177,7 +248,7 @@ class ApiQueryInfo extends ApiQueryBase { } - protected function getDescription() { + public function getDescription() { return 'Get basic page information such as namespace, title, last touched date, ...'; } @@ -189,7 +260,7 @@ class ApiQueryInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryInfo.php 25457 2007-09-03 20:17:53Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryInfo.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php index ae5ff790..04a930db 100644 --- a/includes/api/ApiQueryLangLinks.php +++ b/includes/api/ApiQueryLangLinks.php @@ -76,7 +76,7 @@ class ApiQueryLangLinks extends ApiQueryBase { $db->freeResult($res); } - protected function getDescription() { + public function getDescription() { return 'Returns all interlanguage links from the given page(s)'; } @@ -88,7 +88,7 @@ class ApiQueryLangLinks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLangLinks.php 23819 2007-07-07 03:05:09Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryLangLinks.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php index 7ec20f44..d77e627a 100644 --- a/includes/api/ApiQueryLinks.php +++ b/includes/api/ApiQueryLinks.php @@ -123,7 +123,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { $db->freeResult($res); } - protected function getAllowedParams() + public function getAllowedParams() { return array( 'namespace' => array( @@ -133,14 +133,14 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { ); } - protected function getParamDescription() + public function getParamDescription() { return array( 'namespace' => "Show {$this->description}s in this namespace(s) only" ); } - protected function getDescription() { + public function getDescription() { return "Returns all {$this->description}s from the given page(s)"; } @@ -156,7 +156,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLinks.php 24092 2007-07-14 19:04:31Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryLinks.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index 0f143658..e25e5275 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -198,7 +198,7 @@ class ApiQueryLogEvents extends ApiQueryBase { } - protected function getAllowedParams() { + public function getAllowedParams() { global $wgLogTypes; return array ( 'prop' => array ( @@ -243,8 +243,9 @@ class ApiQueryLogEvents extends ApiQueryBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( + 'prop' => 'Which properties to get', 'type' => 'Filter log entries to only this type(s)', 'start' => 'The timestamp to start enumerating from.', 'end' => 'The timestamp to end enumerating.', @@ -255,7 +256,7 @@ class ApiQueryLogEvents extends ApiQueryBase { ); } - protected function getDescription() { + public function getDescription() { return 'Get events from logs.'; } @@ -266,7 +267,7 @@ class ApiQueryLogEvents extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLogEvents.php 24256 2007-07-18 21:47:09Z robchurch $'; + return __CLASS__ . ': $Id: ApiQueryLogEvents.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php new file mode 100644 index 00000000..b8282098 --- /dev/null +++ b/includes/api/ApiQueryRandom.php @@ -0,0 +1,157 @@ +run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + protected function prepareQuery($randstr, $limit, $namespace, &$resultPageSet) { + $this->resetQueryParams(); + $this->addTables('page'); + $this->addOption('LIMIT', $limit); + $this->addWhereFld('page_namespace', $namespace); + $this->addWhereRange('page_random', 'newer', $randstr, null); + $this->addWhere(array('page_is_redirect' => 0)); + $this->addOption('USE INDEX', 'page_random'); + if(is_null($resultPageSet)) + $this->addFields(array('page_id', 'page_title', 'page_namespace')); + else + $this->addFields($resultPageSet->getPageTableFields()); + } + + protected function runQuery(&$data, &$resultPageSet) { + $db = $this->getDB(); + $res = $this->select(__METHOD__); + $count = 0; + while($row = $db->fetchObject($res)) { + $count++; + if(is_null($resultPageSet)) + { + // Prevent duplicates + if(!in_array($row->page_id, $this->pageIDs)) + { + $data[] = $this->extractRowInfo($row); + $this->pageIDs[] = $row->page_id; + } + } + else + $resultPageSet->processDbRow($row); + } + $db->freeResult($res); + return $count; + } + + public function run($resultPageSet = null) { + $params = $this->extractRequestParams(); + $result = $this->getResult(); + $data = array(); + $this->pageIDs = array(); + $this->prepareQuery(wfRandom(), $params['limit'], $params['namespace'], $resultPageSet); + $count = $this->runQuery($data, $resultPageSet); + if($count < $params['limit']) + { + /* We got too few pages, we probably picked a high value + * for page_random. We'll just take the lowest ones, see + * also the comment in Title::getRandomTitle() + */ + $this->prepareQuery(0, $params['limit'] - $count, $params['namespace'], $resultPageSet); + $this->runQuery($data, $resultPageSet); + } + + if(is_null($resultPageSet)) { + $result->setIndexedTagName($data, 'page'); + $result->addValue('query', $this->getModuleName(), $data); + } + } + + 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; + return $vals; + } + + public function getAllowedParams() { + return array ( + 'namespace' => array( + ApiBase :: PARAM_TYPE => 'namespace', + ApiBase :: PARAM_ISMULTI => true + ), + 'limit' => array ( + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_DFLT => 1, + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => 10, + ApiBase :: PARAM_MAX2 => 20 + ), + ); + } + + public function getParamDescription() { + return array ( + 'namespace' => 'Return pages in these namespaces only', + 'limit' => 'Limit how many random pages will be returned' + ); + } + + public function getDescription() { + return array( 'Get a set of random pages', + 'NOTE: Pages are listed in a fixed sequence, only the starting point is random. This means that if, for example, "Main Page" is the first ', + ' random page on your list, "List of fictional monkeys" will *always* be second, "List of people on stamps of Vanuatu" third, etc.', + 'NOTE: If the number of pages in the namespace is lower than rnlimit, you will get fewer pages. You will not get the same page twice.' + ); + } + + protected function getExamples() { + return 'api.php?action=query&list=random&rnnamespace=0&rnlimit=2'; + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryRandom.php overlordq$'; + } +} diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 309beaf9..44093854 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -44,20 +44,40 @@ class ApiQueryRecentChanges extends ApiQueryBase { $fld_timestamp = false, $fld_title = false, $fld_ids = false, $fld_sizes = false; + /** + * Generates and outputs the result of this query based upon the provided parameters. + */ public function execute() { - $limit = $prop = $namespace = $show = $dir = $start = $end = null; + /* Initialize vars */ + $limit = $prop = $namespace = $show = $type = $dir = $start = $end = null; + + /* Get the parameters of the request. */ extract($this->extractRequestParams()); + /* Build our basic query. Namely, something along the lines of: + * SELECT * from recentchanges WHERE rc_timestamp > $start + * AND rc_timestamp < $end AND rc_namespace = $namespace + * AND rc_deleted = '0' + */ $this->addTables('recentchanges'); $this->addWhereRange('rc_timestamp', $dir, $start, $end); $this->addWhereFld('rc_namespace', $namespace); $this->addWhereFld('rc_deleted', 0); + if(!is_null($type)) + $this->addWhereFld('rc_type', $this->parseRCType($type)); if (!is_null($show)) { $show = array_flip($show); - if ((isset ($show['minor']) && isset ($show['!minor'])) || (isset ($show['bot']) && isset ($show['!bot'])) || (isset ($show['anon']) && isset ($show['!anon']))) + + /* Check for conflicting parameters. */ + if ((isset ($show['minor']) && isset ($show['!minor'])) + || (isset ($show['bot']) && isset ($show['!bot'])) + || (isset ($show['anon']) && isset ($show['!anon']))) { + $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); + } + /* Add additional conditions to query depending upon parameters. */ $this->addWhereIf('rc_minor = 0', isset ($show['!minor'])); $this->addWhereIf('rc_minor != 0', isset ($show['minor'])); $this->addWhereIf('rc_bot = 0', isset ($show['!bot'])); @@ -66,6 +86,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addWhereIf('rc_user != 0', isset ($show['!anon'])); } + /* Add the fields we're concerned with to out query. */ $this->addFields(array ( 'rc_timestamp', 'rc_namespace', @@ -75,9 +96,11 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'rc_moved_to_title' )); + /* Determine what properties we need to display. */ if (!is_null($prop)) { $prop = array_flip($prop); + /* Set up internal members based upon params. */ $this->fld_comment = isset ($prop['comment']); $this->fld_user = isset ($prop['user']); $this->fld_flags = isset ($prop['flags']); @@ -85,7 +108,8 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->fld_title = isset ($prop['title']); $this->fld_ids = isset ($prop['ids']); $this->fld_sizes = isset ($prop['sizes']); - + + /* Add fields to our query if they are specified as a needed parameter. */ $this->addFieldsIf('rc_id', $this->fld_ids); $this->addFieldsIf('rc_cur_id', $this->fld_ids); $this->addFieldsIf('rc_this_oldid', $this->fld_ids); @@ -100,14 +124,21 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addFieldsIf('rc_new_len', $this->fld_sizes); } + /* Specify the limit for our query. It's $limit+1 because we (possibly) need to + * generate a "continue" parameter, to allow paging. */ $this->addOption('LIMIT', $limit +1); + + /* Specify the index to use in the query as rc_timestamp, instead of rc_revid (default). */ $this->addOption('USE INDEX', 'rc_timestamp'); $data = array (); $count = 0; + + /* Perform the actual query. */ $db = $this->getDB(); $res = $this->select(__METHOD__); - + + /* Iterate through the rows, adding data extracted from them to our query result. */ 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... @@ -115,33 +146,61 @@ class ApiQueryRecentChanges extends ApiQueryBase { break; } + /* Extract the data from a single row. */ $vals = $this->extractRowInfo($row); + + /* Add that row's data to our final output. */ if($vals) $data[] = $vals; } + $db->freeResult($res); + /* Format the result */ $result = $this->getResult(); $result->setIndexedTagName($data, 'rc'); $result->addValue('query', $this->getModuleName(), $data); } + /** + * Extracts from a single sql row the data needed to describe one recent change. + * + * @param $row The row from which to extract the data. + * @return An array mapping strings (descriptors) to their respective string values. + * @access private + */ private function extractRowInfo($row) { + /* If page was moved somewhere, get the title of the move target. */ $movedToTitle = false; if (!empty($row->rc_moved_to_title)) $movedToTitle = Title :: makeTitle($row->rc_moved_to_ns, $row->rc_moved_to_title); + /* Determine the title of the page that has been changed. */ $title = Title :: makeTitle($row->rc_namespace, $row->rc_title); + + /* Our output data. */ $vals = array (); - $vals['type'] = intval($row->rc_type); + $type = intval ( $row->rc_type ); + /* Determine what kind of change this was. */ + switch ( $type ) { + case RC_EDIT: $vals['type'] = 'edit'; break; + case RC_NEW: $vals['type'] = 'new'; break; + case RC_MOVE: $vals['type'] = 'move'; break; + case RC_LOG: $vals['type'] = 'log'; break; + case RC_MOVE_OVER_REDIRECT: $vals['type'] = 'move over redirect'; break; + default: $vals['type'] = $type; + } + + /* Create a new entry in the result for the title. */ if ($this->fld_title) { ApiQueryBase :: addTitleInfo($vals, $title); if ($movedToTitle) ApiQueryBase :: addTitleInfo($vals, $movedToTitle, "new_"); } + /* Add ids, such as rcid, pageid, revid, and oldid to the change's info. */ if ($this->fld_ids) { $vals['rcid'] = intval($row->rc_id); $vals['pageid'] = intval($row->rc_cur_id); @@ -149,12 +208,14 @@ class ApiQueryRecentChanges extends ApiQueryBase { $vals['old_revid'] = intval( $row->rc_last_oldid ); } + /* Add user data and 'anon' flag, if use is anonymous. */ if ($this->fld_user) { $vals['user'] = $row->rc_user_text; if(!$row->rc_user) $vals['anon'] = ''; } + /* Add flags, such as new, minor, bot. */ if ($this->fld_flags) { if ($row->rc_bot) $vals['bot'] = ''; @@ -164,22 +225,42 @@ class ApiQueryRecentChanges extends ApiQueryBase { $vals['minor'] = ''; } + /* Add sizes of each revision. (Only available on 1.10+) */ if ($this->fld_sizes) { $vals['oldlen'] = intval($row->rc_old_len); $vals['newlen'] = intval($row->rc_new_len); } - + + /* Add the timestamp. */ if ($this->fld_timestamp) $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); + /* Add edit summary / log summary. */ if ($this->fld_comment && !empty ($row->rc_comment)) { $vals['comment'] = $row->rc_comment; } return $vals; } + + private function parseRCType($type) + { + if(is_array($type)) + { + $retval = array(); + foreach($type as $t) + $retval[] = $this->parseRCType($t); + return $retval; + } + switch($type) + { + case 'edit': return RC_EDIT; + case 'new': return RC_NEW; + case 'log': return RC_LOG; + } + } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'start' => array ( ApiBase :: PARAM_TYPE => 'timestamp' @@ -228,11 +309,19 @@ class ApiQueryRecentChanges extends ApiQueryBase { ApiBase :: PARAM_MIN => 1, ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'type' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'edit', + 'new', + 'log' + ) ) ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'start' => 'The timestamp to start enumerating from.', 'end' => 'The timestamp to end enumerating.', @@ -243,11 +332,12 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'Show only items that meet this criteria.', 'For example, to see only minor edits done by logged-in users, set show=minor|!anon' ), + 'type' => 'Which types of changes to show.', 'limit' => 'How many total pages to return.' ); } - protected function getDescription() { + public function getDescription() { return 'Enumerate recent changes'; } @@ -258,7 +348,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 24100 2007-07-15 01:12:54Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 2672478b..e22d3b30 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -45,14 +45,15 @@ class ApiQueryRevisions extends ApiQueryBase { $fld_comment = false, $fld_user = false, $fld_content = false; public function execute() { - $limit = $startid = $endid = $start = $end = $dir = $prop = $user = $excludeuser = null; - extract($this->extractRequestParams()); + $limit = $startid = $endid = $start = $end = $dir = $prop = $user = $excludeuser = $token = null; + extract($this->extractRequestParams(false)); // If any of those parameters are used, work in 'enumeration' mode. // Enum mode can only be used when exactly one page is provided. - // Enumerating revisions on multiple pages make it extremelly - // difficult to manage continuations and require additional sql indexes + // Enumerating revisions on multiple pages make it extremely + // difficult to manage continuations and require additional SQL indexes $enumRevMode = (!is_null($user) || !is_null($excludeuser) || !is_null($limit) || !is_null($startid) || !is_null($endid) || $dir === 'newer' || !is_null($start) || !is_null($end)); + $pageSet = $this->getPageSet(); $pageCount = $pageSet->getGoodTitleCount(); @@ -66,7 +67,7 @@ class ApiQueryRevisions extends ApiQueryBase { $this->dieUsage('The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids'); 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'); + $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'); $this->addTables('revision'); $this->addWhere('rev_deleted=0'); @@ -84,12 +85,20 @@ class ApiQueryRevisions extends ApiQueryBase { $this->fld_timestamp = $this->addFieldsIf('rev_timestamp', isset ($prop['timestamp'])); $this->fld_comment = $this->addFieldsIf('rev_comment', isset ($prop['comment'])); $this->fld_size = $this->addFieldsIf('rev_len', isset ($prop['size'])); + $this->tok_rollback = false; // Prevent PHP undefined property notice + if(!is_null($token)) + { + $this->tok_rollback = $this->getTokenFlag($token, 'rollback'); + } if (isset ($prop['user'])) { $this->addFields('rev_user'); $this->addFields('rev_user_text'); $this->fld_user = true; } + else if($this->tok_rollback) + $this->addFields('rev_user_text'); + if (isset ($prop['content'])) { // For each page we will request, the user must have read rights for that page @@ -105,15 +114,22 @@ class ApiQueryRevisions extends ApiQueryBase { $this->addFields('old_id'); $this->addFields('old_text'); $this->addFields('old_flags'); + $this->fld_content = true; + + $this->expandTemplates = $expandtemplates; } - $userMax = ($this->fld_content ? 50 : 500); - $botMax = ($this->fld_content ? 200 : 10000); + $userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 ); + $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 ); + if( $limit == 'max' ) { + $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; + $this->getResult()->addValue( 'limits', $this->getModuleName(), $limit ); + } if ($enumRevMode) { - // This is mostly to prevent parameter errors (and optimize sql?) + // This is mostly to prevent parameter errors (and optimize SQL?) if (!is_null($startid) && !is_null($start)) $this->dieUsage('start and startid cannot be used together', 'badparams'); @@ -130,7 +146,7 @@ class ApiQueryRevisions extends ApiQueryBase { // one row with the same timestamp for the same page. // The order needs to be the same as start parameter to avoid SQL filesort. - if (is_null($startid)) + if (is_null($startid) && is_null($endid)) $this->addWhereRange('rev_timestamp', $dir, $start, $end); else $this->addWhereRange('rev_id', $dir, $startid, $endid); @@ -201,7 +217,7 @@ class ApiQueryRevisions extends ApiQueryBase { $this->extractRowInfo($row)); } $db->freeResult($res); - + // Ensure that all revisions are shown as '' elements $result = $this->getResult(); if ($result->getIsRawMode()) { @@ -244,14 +260,27 @@ class ApiQueryRevisions extends ApiQueryBase { $vals['comment'] = $row->rev_comment; } - if ($this->fld_content) { - ApiResult :: setContent($vals, Revision :: getRevisionText($row)); + if($this->tok_rollback || ($this->fld_content && $this->expandTemplates)) + $title = Title::newFromID($row->rev_page); + + if($this->tok_rollback) { + global $wgUser; + $vals['rollbacktoken'] = $wgUser->editToken(array($title->getPrefixedText(), $row->rev_user_text)); } + + if ($this->fld_content) { + $text = Revision :: getRevisionText($row); + if ($this->expandTemplates) { + global $wgParser; + $text = $wgParser->preprocess( $text, $title, new ParserOptions() ); + } + ApiResult :: setContent($vals, $text); + } return $vals; } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, @@ -269,8 +298,8 @@ class ApiQueryRevisions extends ApiQueryBase { 'limit' => array ( ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX => ApiBase :: LIMIT_SML1, - ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_SML2 + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ), 'startid' => array ( ApiBase :: PARAM_TYPE => 'integer' @@ -296,11 +325,19 @@ class ApiQueryRevisions extends ApiQueryBase { ), 'excludeuser' => array( ApiBase :: PARAM_TYPE => 'user' - ) + ), + + 'expandtemplates' => false, + 'token' => array( + ApiBase :: PARAM_TYPE => array( + 'rollback' + ), + ApiBase :: PARAM_ISMULTI => true + ), ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => 'Which properties to get for each revision.', 'limit' => 'limit how many revisions will be returned (enum)', @@ -311,10 +348,12 @@ class ApiQueryRevisions extends ApiQueryBase { 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)', 'user' => 'only include revisions made by user', 'excludeuser' => 'exclude revisions made by user', + 'expandtemplates' => 'expand templates in revision content', + 'token' => 'Which tokens to obtain for each revision', ); } - protected function getDescription() { + public function getDescription() { return array ( 'Get revision information.', 'This module may be used in several ways:', @@ -343,7 +382,7 @@ class ApiQueryRevisions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRevisions.php 25407 2007-09-02 14:00:11Z tstarling $'; + return __CLASS__ . ': $Id: ApiQueryRevisions.php 31259 2008-02-25 14:14:55Z catrope $'; } } diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index 268616b1..b15f36ce 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -94,7 +94,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'search' => null, 'namespace' => array ( @@ -121,7 +121,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'search' => 'Search for all page titles (or content) that has this value.', 'namespace' => 'The namespace(s) to enumerate.', @@ -132,7 +132,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { ); } - protected function getDescription() { + public function getDescription() { return 'Perform a full text search'; } @@ -145,7 +145,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySearch.php 24453 2007-07-30 08:09:15Z yurik $'; + return __CLASS__ . ': $Id: ApiQuerySearch.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 1fa3d8fc..81af7997 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -53,6 +53,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { case 'namespaces' : $this->appendNamespaces($p); break; + case 'namespacealiases' : + $this->appendNamespaceAliases($p); + break; case 'interwikimap' : $filteriw = isset($params['filteriw']) ? $params['filteriw'] : false; $this->appendInterwikiMap($p, $filteriw); @@ -68,7 +71,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { } protected function appendGeneralInfo($property) { - global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText, $wgLanguageCode; + global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText, $wgLanguageCode, $IP; $data = array (); $mainPage = Title :: newFromText(wfMsgForContent('mainpage')); @@ -76,6 +79,10 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['base'] = $mainPage->getFullUrl(); $data['sitename'] = $wgSitename; $data['generator'] = "MediaWiki $wgVersion"; + + $svn = SpecialVersion::getSvnRevision ( $IP ); + if ( $svn ) $data['rev'] = $svn; + $data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future if (isset($wgRightsCode)) $data['rightscode'] = $wgRightsCode; @@ -100,6 +107,22 @@ class ApiQuerySiteinfo extends ApiQueryBase { $this->getResult()->addValue('query', $property, $data); } + protected function appendNamespaceAliases($property) { + global $wgNamespaceAliases; + + $data = array (); + foreach ($wgNamespaceAliases as $title => $ns) { + $item = array ( + 'id' => $ns + ); + ApiResult :: setContent($item, strtr($title, '_', ' ')); + $data[] = $item; + } + + $this->getResult()->setIndexedTagName($data, 'ns'); + $this->getResult()->addValue('query', $property, $data); + } + protected function appendInterwikiMap($property, $filter) { $this->resetQueryParams(); @@ -177,7 +200,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $this->getResult()->addValue('query', $property, $data); } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'prop' => array ( @@ -186,6 +209,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { ApiBase :: PARAM_TYPE => array ( 'general', 'namespaces', + 'namespacealiases', 'interwikimap', 'dbrepllag', 'statistics', @@ -201,12 +225,13 @@ class ApiQuerySiteinfo extends ApiQueryBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => array ( 'Which sysinfo properties to get:', ' "general" - Overall system information', ' "namespaces" - List of registered namespaces (localized)', + ' "namespacealiases" - List of registered namespace aliases', ' "statistics" - Returns site statistics', ' "interwikimap" - Returns interwiki map (optionally filtered)', ' "dbrepllag" - Returns database server with the highest replication lag', @@ -216,19 +241,19 @@ class ApiQuerySiteinfo extends ApiQueryBase { ); } - protected function getDescription() { + public function getDescription() { return 'Return general information about the site.'; } protected function getExamples() { return array( - 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|statistics', + 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics', 'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local', 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 25238 2007-08-28 15:37:31Z robchurch $'; + return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 30484 2008-02-03 19:29:59Z btongminh $'; } -} \ No newline at end of file +} diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index 05c3d945..57d51cdb 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -60,7 +60,11 @@ class ApiQueryContributions extends ApiQueryBase { $db = $this->getDB(); // Prepare query - $this->prepareUsername(); + $this->usernames = array(); + if(!is_array($this->params['user'])) + $this->params['user'] = array($this->params['user']); + foreach($this->params['user'] as $u) + $this->prepareUsername($u); $this->prepareQuery(); //Do the actual query. @@ -96,8 +100,7 @@ class ApiQueryContributions extends ApiQueryBase { * Validate the 'user' parameter and set the value to compare * against `revision`.`rev_user_text` */ - private function prepareUsername() { - $user = $this->params['user']; + private function prepareUsername($user) { if( $user ) { $name = User::isIP( $user ) ? $user @@ -105,7 +108,7 @@ class ApiQueryContributions extends ApiQueryBase { if( $name === false ) { $this->dieUsage( "User name {$user} is not valid", 'param_user' ); } else { - $this->username = $name; + $this->usernames[] = $name; } } else { $this->dieUsage( 'User parameter may not be empty', 'param_user' ); @@ -123,14 +126,11 @@ class ApiQueryContributions extends ApiQueryBase { $this->addTables("$tbl_revision LEFT OUTER JOIN $tbl_page ON page_id=rev_page"); $this->addWhereFld('rev_deleted', 0); - - // We only want pages by the specified user. - $this->addWhereFld( 'rev_user_text', $this->username ); - + // We only want pages by the specified users. + $this->addWhereFld( 'rev_user_text', $this->usernames ); // ... and in the specified timeframe. $this->addWhereRange('rev_timestamp', $this->params['dir'], $this->params['start'], $this->params['end'] ); - $this->addWhereFld('page_namespace', $this->params['namespace']); $show = $this->params['show']; @@ -142,15 +142,16 @@ class ApiQueryContributions extends ApiQueryBase { $this->addWhereIf('rev_minor_edit = 0', isset ($show['!minor'])); $this->addWhereIf('rev_minor_edit != 0', isset ($show['minor'])); } - $this->addOption('LIMIT', $this->params['limit'] + 1); // Mandatory fields: timestamp allows request continuation - // ns+title checks if the user has access rights for this page + // ns+title checks if the user has access rights for this page + // user_text is necessary if multiple users were specified $this->addFields(array( 'rev_timestamp', 'page_namespace', 'page_title', + 'rev_user_text', )); $this->addFieldsIf('rev_page', $this->fld_ids); @@ -158,8 +159,6 @@ class ApiQueryContributions extends ApiQueryBase { // $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); - - // These fields depend only work if the page table is joined $this->addFieldsIf('page_is_new', $this->fld_flags); } @@ -170,6 +169,7 @@ class ApiQueryContributions extends ApiQueryBase { $vals = array(); + $vals['user'] = $row->rev_user_text; if ($this->fld_ids) { $vals['pageid'] = intval($row->rev_page); $vals['revid'] = intval($row->rev_id); @@ -196,7 +196,7 @@ class ApiQueryContributions extends ApiQueryBase { return $vals; } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'limit' => array ( ApiBase :: PARAM_DFLT => 10, @@ -212,7 +212,7 @@ class ApiQueryContributions extends ApiQueryBase { ApiBase :: PARAM_TYPE => 'timestamp' ), 'user' => array ( - ApiBase :: PARAM_TYPE => 'user' + ApiBase :: PARAM_ISMULTI => true ), 'dir' => array ( ApiBase :: PARAM_DFLT => 'older', @@ -246,7 +246,7 @@ class ApiQueryContributions extends ApiQueryBase { ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'limit' => 'The maximum number of contributions to return.', 'start' => 'The start timestamp to return from.', @@ -259,7 +259,7 @@ class ApiQueryContributions extends ApiQueryBase { ); } - protected function getDescription() { + public function getDescription() { return 'Get all edits by a user'; } @@ -270,7 +270,7 @@ class ApiQueryContributions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserContributions.php 24754 2007-08-13 18:18:18Z robchurch $'; + return __CLASS__ . ': $Id: ApiQueryUserContributions.php 30578 2008-02-05 15:40:58Z catrope $'; } } diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index a41b8679..010d9f4f 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -40,50 +40,90 @@ class ApiQueryUserInfo extends ApiQueryBase { } public function execute() { - - global $wgUser; - $params = $this->extractRequestParams(); $result = $this->getResult(); + $r = array(); + if (!is_null($params['prop'])) { + $this->prop = array_flip($params['prop']); + } else { + $this->prop = array(); + } + $r = $this->getCurrentUserInfo(); + $result->addValue("query", $this->getModuleName(), $r); + } + + protected function getCurrentUserInfo() { + global $wgUser; + $result = $this->getResult(); $vals = array(); + $vals['id'] = $wgUser->getId(); $vals['name'] = $wgUser->getName(); - if( $wgUser->isAnon() ) $vals['anon'] = ''; - - if (!is_null($params['prop'])) { - $prop = array_flip($params['prop']); - if (isset($prop['blockinfo'])) { - if ($wgUser->isBlocked()) { - $vals['blockedby'] = User::whoIs($wgUser->blockedBy()); - $vals['blockreason'] = $wgUser->blockedFor(); - } - } - if (isset($prop['hasmsg']) && $wgUser->getNewtalk()) { - $vals['messages'] = ''; - } - if (isset($prop['groups'])) { - $vals['groups'] = $wgUser->getGroups(); - $result->setIndexedTagName($vals['groups'], 'g'); // even if empty - } - if (isset($prop['rights'])) { - $vals['rights'] = $wgUser->getRights(); - $result->setIndexedTagName($vals['rights'], 'r'); // even if empty + if($wgUser->isAnon()) + $vals['anon'] = ''; + if (isset($this->prop['blockinfo'])) { + if ($wgUser->isBlocked()) { + $vals['blockedby'] = User::whoIs($wgUser->blockedBy()); + $vals['blockreason'] = $wgUser->blockedFor(); } + } + if (isset($this->prop['hasmsg']) && $wgUser->getNewtalk()) { + $vals['messages'] = ''; } - - if (!empty($params['option'])) { - foreach( $params['option'] as $option ) { - if (empty($option)) - $this->dieUsage('Empty value is not allowed for the option parameter', 'option'); - $vals['options'][$option] = $wgUser->getOption($option); - } + if (isset($this->prop['groups'])) { + $vals['groups'] = $wgUser->getGroups(); + $result->setIndexedTagName($vals['groups'], 'g'); // even if empty } - - $result->addValue(null, $this->getModuleName(), $vals); + if (isset($this->prop['rights'])) { + $vals['rights'] = $wgUser->getRights(); + $result->setIndexedTagName($vals['rights'], 'r'); // even if empty + } + if (isset($this->prop['options'])) { + $vals['options'] = (is_null($wgUser->mOptions) ? User::getDefaultOptions() : $wgUser->mOptions); + } + if (isset($this->prop['editcount'])) { + $vals['editcount'] = $wgUser->getEditCount(); + } + if (isset($this->prop['ratelimits'])) { + $vals['ratelimits'] = $this->getRateLimits(); + } + return $vals; } + + protected function getRateLimits() + { + global $wgUser, $wgRateLimits; + if(!$wgUser->isPingLimitable()) + return array(); // No limits + + // Find out which categories we belong to + $categories = array(); + if($wgUser->isAnon()) + $categories[] = 'anon'; + else + $categories[] = 'user'; + if($wgUser->isNewBie()) + { + $categories[] = 'ip'; + $categories[] = 'subnet'; + if(!$wgUser->isAnon()) + $categories[] = 'newbie'; + } + + // Now get the actual limits + $retval = array(); + foreach($wgRateLimits as $action => $limits) + 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]; + } + return $retval; + } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_DFLT => NULL, @@ -93,28 +133,30 @@ class ApiQueryUserInfo extends ApiQueryBase { 'hasmsg', 'groups', 'rights', - )), - 'option' => array ( - ApiBase :: PARAM_DFLT => NULL, - ApiBase :: PARAM_ISMULTI => true, - ), + 'options', + 'editcount', + 'ratelimits' + ) + ) ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'prop' => array( 'What pieces of information to include', - ' blockinfo - tags if the user is blocked, by whom, and for what reason', - ' hasmsg - adds a tag "message" if user has pending messages', - ' groups - lists all the groups the current user belongs to', - ' rights - lists of all rights the current user has', - ), - 'option' => 'A list of user preference options to get', + ' blockinfo - tags if the current user is blocked, by whom, and for what reason', + ' hasmsg - adds a tag "message" if the current user has pending messages', + ' groups - lists all the groups the current user belongs to', + ' rights - lists of all rights the current user has', + ' options - lists all preferences the current user has set', + ' editcount - adds the current user\'s edit count', + ' ratelimits - lists all rate limits applying to the current user' + ) ); } - protected function getDescription() { + public function getDescription() { return 'Get information about the current user'; } @@ -122,12 +164,10 @@ class ApiQueryUserInfo extends ApiQueryBase { return array ( 'api.php?action=query&meta=userinfo', 'api.php?action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg', - 'api.php?action=query&meta=userinfo&uioption=rememberpassword', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserInfo.php 24529 2007-08-01 20:11:29Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryUserInfo.php 30395 2008-02-01 14:46:46Z catrope $'; } } - diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php new file mode 100644 index 00000000..144bfba2 --- /dev/null +++ b/includes/api/ApiQueryUsers.php @@ -0,0 +1,162 @@ +.@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 get information about a list of users + * + * @addtogroup API + */ + + class ApiQueryUsers extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'us'); + } + + public function execute() { + $params = $this->extractRequestParams(); + $result = $this->getResult(); + $r = array(); + + if (!is_null($params['prop'])) { + $this->prop = array_flip($params['prop']); + } else { + $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(); + // Canonicalize user names + foreach($users as $u) { + $n = User::getCanonicalName($u); + if($n === false) + $retval[] = array('name' => $u, 'invalid' => ''); + else + $goodNames[] = $n; + } + + $db = $this->getDb(); + $userTable = $db->tableName('user'); + $tables = "$userTable AS u1"; + $this->addFields('u1.user_name'); + $this->addWhereFld('u1.user_name', $goodNames); + $this->addFieldsIf('u1.user_editcount', isset($this->prop['editcount'])); + + if(isset($this->prop['groups'])) { + $ug = $db->tableName('user_groups'); + $tables = "$tables LEFT JOIN $ug ON ug_user=u1.user_id"; + $this->addFields('ug_group'); + } + if(isset($this->prop['blockinfo'])) { + $ipb = $db->tableName('ipblocks'); + $tables = "$tables LEFT JOIN $ipb ON ipb_user=u1.user_id"; + $tables = "$tables LEFT JOIN $userTable AS u2 ON ipb_by=u2.user_id"; + $this->addFields(array('ipb_reason', 'u2.user_name AS blocker_name')); + } + $this->addTables($tables); + + $data = array(); + $res = $this->select(__METHOD__); + while(($r = $db->fetchObject($res))) { + $data[$r->user_name]['name'] = $r->user_name; + if(isset($this->prop['editcount'])) + $data[$r->user_name]['editcount'] = $r->user_editcount; + 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[$r->user_name]['groups'][] = $r->ug_group; + if(isset($this->prop['blockinfo'])) + if(!is_null($r->blocker_name)) { + $data[$r->user_name]['blockedby'] = $r->blocker_name; + $data[$r->user_name]['blockreason'] = $r->ipb_reason; + } + } + + // Second pass: add result data to $retval + foreach($goodNames as $u) { + if(!isset($data[$u])) + $retval[] = array('name' => $u, 'missing' => ''); + else { + if(isset($this->prop['groups']) && isset($data[$u]['groups'])) + $this->getResult()->setIndexedTagName($data[$u]['groups'], 'g'); + $retval[] = $data[$u]; + } + } + return $retval; + } + + public function getAllowedParams() { + return array ( + 'prop' => array ( + ApiBase :: PARAM_DFLT => NULL, + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'blockinfo', + 'groups', + 'editcount' + ) + ), + 'users' => array( + ApiBase :: PARAM_ISMULTI => true + ) + ); + } + + public function getParamDescription() { + return array ( + 'prop' => array( + 'What pieces of information to include', + ' blockinfo - tags if the user is blocked, by whom, and for what reason', + ' groups - lists all the groups the user belongs to', + ' editcount - adds the user\'s edit count' + ), + 'users' => 'A list of users to obtain the same information for' + ); + } + + public function getDescription() { + return 'Get information about a list of users'; + } + + protected function getExamples() { + return 'api.php?action=query&list=users&ususers=brion|TimStarling&usprop=groups|editcount'; + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryUserInfo.php 30128 2008-01-24 17:59:07Z catrope $'; + } +} diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index 16586a40..91a0c951 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -59,7 +59,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { if (!$wgUser->isLoggedIn()) $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin'); - $allrev = $start = $end = $namespace = $dir = $limit = $prop = null; + $allrev = $start = $end = $namespace = $dir = $limit = $prop = $show = null; extract($this->extractRequestParams()); if (!is_null($prop) && is_null($resultPageSet)) { @@ -135,7 +135,28 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addWhereFld('wl_namespace', $namespace); $this->addWhereIf('rc_this_oldid=page_latest', !$allrev); - # This is a index optimization for mysql, as done in the Special:Watchlist page + if (!is_null($show)) { + $show = array_flip($show); + + /* Check for conflicting parameters. */ + if ((isset ($show['minor']) && isset ($show['!minor'])) + || (isset ($show['bot']) && isset ($show['!bot'])) + || (isset ($show['anon']) && isset ($show['!anon']))) { + + $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); + } + + /* Add additional conditions to query depending upon parameters. */ + $this->addWhereIf('rc_minor = 0', isset ($show['!minor'])); + $this->addWhereIf('rc_minor != 0', isset ($show['minor'])); + $this->addWhereIf('rc_bot = 0', isset ($show['!bot'])); + $this->addWhereIf('rc_bot != 0', isset ($show['bot'])); + $this->addWhereIf('rc_user = 0', isset ($show['anon'])); + $this->addWhereIf('rc_user != 0', isset ($show['!anon'])); + } + + + # This is an index optimization for mysql, as done in the Special:Watchlist page $this->addWhereIf("rc_timestamp > ''", !isset ($start) && !isset ($end) && $wgDBtype == 'mysql'); $this->addOption('LIMIT', $limit +1); @@ -222,7 +243,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { return $vals; } - protected function getAllowedParams() { + public function getAllowedParams() { return array ( 'allrev' => false, 'start' => array ( @@ -262,11 +283,22 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'patrol', 'sizes', ) + ), + 'show' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'minor', + '!minor', + 'bot', + '!bot', + 'anon', + '!anon' + ) ) ); } - protected function getParamDescription() { + public function getParamDescription() { return array ( 'allrev' => 'Include multiple revisions of the same page within given timeframe.', 'start' => 'The timestamp to start enumerating from.', @@ -274,11 +306,15 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'namespace' => 'Filter changes to only the given namespace(s).', 'dir' => 'In which direction to enumerate pages.', 'limit' => 'How many total pages to return per request.', - 'prop' => 'Which additional items to get (non-generator mode only).' + 'prop' => 'Which additional items to get (non-generator mode only).', + 'show' => array ( + 'Show only items that meet this criteria.', + 'For example, to see only minor edits done by logged-in users, set show=minor|!anon' + ) ); } - protected function getDescription() { + public function getDescription() { return ''; } @@ -293,7 +329,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlist.php 24092 2007-07-14 19:04:31Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryWatchlist.php 30222 2008-01-28 19:05:26Z catrope $'; } } diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index a318d808..ffab51ef 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -139,6 +139,22 @@ class ApiResult extends ApiBase { // Do not use setElement() as it is ok to call this more than once $arr['_element'] = $tag; } + + /** + * Calls setIndexedTagName() on $arr and each sub-array + */ + public function setIndexedTagName_recursive(&$arr, $tag) + { + if(!is_array($arr)) + return; + foreach($arr as $a) + { + if(!is_array($a)) + continue; + $this->setIndexedTagName($a, $tag); + $this->setIndexedTagName_recursive($a, $tag); + } + } /** * Add value to the output data at the given path. @@ -175,7 +191,34 @@ class ApiResult extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiResult.php 23531 2007-06-29 01:19:14Z simetrical $'; + return __CLASS__ . ': $Id: ApiResult.php 26855 2007-10-20 18:27:39Z catrope $'; } } +/* For compatibility with PHP versions < 5.1.0, define our own array_intersect_key function. */ +if (!function_exists('array_intersect_key')) { + function array_intersect_key($isec, $keys) { + $argc = func_num_args(); + + if ($argc > 2) { + for ($i = 1; !empty($isec) && $i < $argc; $i++) { + $arr = func_get_arg($i); + + foreach (array_keys($isec) as $key) { + if (!isset($arr[$key])) + unset($isec[$key]); + } + } + + return $isec; + } else { + $res = array(); + foreach (array_keys($isec) as $key) { + if (isset($keys[$key])) + $res[$key] = $isec[$key]; + } + + return $res; + } + } +} diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php new file mode 100644 index 00000000..d714f99c --- /dev/null +++ b/includes/api/ApiRollback.php @@ -0,0 +1,128 @@ +.@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"); +} + +/** + * @addtogroup API + */ +class ApiRollback extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + $titleObj = NULL; + if(!isset($params['title'])) + $this->dieUsageMsg(array('missingparam', 'title')); + if(!isset($params['user'])) + $this->dieUsageMsg(array('missingparam', 'user')); + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + + $titleObj = Title::newFromText($params['title']); + if(!$titleObj) + $this->dieUsageMsg(array('invalidtitle', $params['title'])); + if(!$titleObj->exists()) + $this->dieUsageMsg(array('notanarticle')); + + $username = User::getCanonicalName($params['user']); + if(!$username) + $this->dieUsageMsg(array('invaliduser', $params['user'])); + + $articleObj = new Article($titleObj); + $summary = (isset($params['summary']) ? $params['summary'] : ""); + $details = null; + $dbw = wfGetDb(DB_MASTER); + $dbw->begin(); + $retval = $articleObj->doRollback($username, $summary, $params['token'], $params['markbot'], $details); + + if(!empty($retval)) + // We don't care about multiple errors, just report one of them + $this->dieUsageMsg(current($retval)); + + $dbw->commit(); + $current = $target = $summary = NULL; + extract($details); + + $info = array( + 'title' => $titleObj->getPrefixedText(), + 'pageid' => $current->getPage(), + 'summary' => $summary, + 'revid' => $titleObj->getLatestRevID(), + 'old_revid' => $current->getID(), + 'last_revid' => $target->getID() + ); + + $this->getResult()->addValue(null, $this->getModuleName(), $info); + } + + public function mustBePosted() { return true; } + + public function getAllowedParams() { + return array ( + 'title' => null, + 'user' => null, + 'token' => null, + 'summary' => null, + 'markbot' => false + ); + } + + public function getParamDescription() { + return array ( + 'title' => 'Title of the page you want to rollback.', + 'user' => 'Name of the user whose edits are to be rolled back. If set incorrectly, you\'ll get a badtoken error.', + 'token' => 'A rollback token previously retrieved through prop=info', + 'summary' => 'Custom edit summary. If not set, default summary will be used.', + 'markbot' => 'Mark the reverted edits and the revert as bot edits' + ); + } + + public function getDescription() { + return array( + 'Undoes the last edit to the page. If the last user who edited the page made multiple edits in a row,', + 'they will all be rolled back. You need to be logged in as a sysop to use this function, see also action=login.' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=rollback&title=Main%20Page&user=Catrope&token=123ABC', + 'api.php?action=rollback&title=Main%20Page&user=217.121.114.116&token=123ABC&summary=Reverting%20vandalism&markbot=1' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiRollback.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php new file mode 100644 index 00000000..afbd3f0e --- /dev/null +++ b/includes/api/ApiUnblock.php @@ -0,0 +1,124 @@ +.@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 facilitates the unblocking of users. Requires API write mode + * to be enabled. + * + * @addtogroup API + */ +class ApiUnblock extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + /** + * Unblocks the specified user or provides the reason the unblock failed. + */ + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + if($params['gettoken']) + { + $res['unblocktoken'] = $wgUser->editToken(); + $this->getResult()->addValue(null, $this->getModuleName(), $res); + return; + } + + if(is_null($params['id']) && is_null($params['user'])) + $this->dieUsageMsg(array('unblock-notarget')); + if(!is_null($params['id']) && !is_null($params['user'])) + $this->dieUsageMsg(array('unblock-idanduser')); + if(is_null($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + if(!$wgUser->isAllowed('block')) + $this->dieUsageMsg(array('cantunblock')); + if(wfReadOnly()) + $this->dieUsageMsg(array('readonlytext')); + + $id = $params['id']; + $user = $params['user']; + $reason = (is_null($params['reason']) ? '' : $params['reason']); + $dbw = wfGetDb(DB_MASTER); + $dbw->begin(); + $retval = IPUnblockForm::doUnblock($id, $user, $reason, $range); + if(!empty($retval)) + $this->dieUsageMsg($retval); + + $dbw->commit(); + $res['id'] = $id; + $res['user'] = $user; + $res['reason'] = $reason; + $this->getResult()->addValue(null, $this->getModuleName(), $res); + } + + public function mustBePosted() { return true; } + + public function getAllowedParams() { + return array ( + 'id' => null, + 'user' => null, + 'token' => null, + 'gettoken' => false, + 'reason' => null, + ); + } + + public function getParamDescription() { + return array ( + 'id' => 'ID of the block you want to unblock (obtained through list=blocks). Cannot be used together with user', + 'user' => 'Username, IP address or IP range you want to unblock. Cannot be used together with id', + 'token' => 'An unblock token previously obtained through the gettoken parameter', + 'gettoken' => 'If set, an unblock token will be returned, and no other action will be taken', + 'reason' => 'Reason for unblock (optional)', + ); + } + + public function getDescription() { + return array( + 'Unblock a user.' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=unblock&id=105', + 'api.php?action=unblock&user=Bob&reason=Sorry%20Bob' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiUnblock.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php new file mode 100644 index 00000000..b27841a8 --- /dev/null +++ b/includes/api/ApiUndelete.php @@ -0,0 +1,123 @@ +.@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"); +} + +/** + * @addtogroup API + */ +class ApiUndelete extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + $titleObj = NULL; + if(!isset($params['title'])) + $this->dieUsageMsg(array('missingparam', 'title')); + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + + if(!$wgUser->isAllowed('undelete')) + $this->dieUsageMsg(array('permdenied-undelete')); + if($wgUser->isBlocked()) + $this->dieUsageMsg(array('blockedtext')); + if(wfReadOnly()) + $this->dieUsageMsg(array('readonlytext')); + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + + $titleObj = Title::newFromText($params['title']); + if(!$titleObj) + $this->dieUsageMsg(array('invalidtitle', $params['title'])); + + // Convert timestamps + if(!is_array($params['timestamps'])) + $params['timestamps'] = array($params['timestamps']); + foreach($params['timestamps'] as $i => $ts) + $params['timestamps'][$i] = wfTimestamp(TS_MW, $ts); + + $pa = new PageArchive($titleObj); + $dbw = wfGetDb(DB_MASTER); + $dbw->begin(); + $retval = $pa->undelete((isset($params['timestamps']) ? $params['timestamps'] : array()), $params['reason']); + if(!is_array($retval)) + $this->dieUsageMsg(array('cannotundelete')); + + $dbw->commit(); + $info['title'] = $titleObj->getPrefixedText(); + $info['revisions'] = $retval[0]; + $info['fileversions'] = $retval[1]; + $info['reason'] = $retval[2]; + $this->getResult()->addValue(null, $this->getModuleName(), $info); + } + + public function mustBePosted() { return true; } + + public function getAllowedParams() { + return array ( + 'title' => null, + 'token' => null, + 'reason' => "", + 'timestamps' => array( + ApiBase :: PARAM_ISMULTI => true + ) + ); + } + + public function getParamDescription() { + return array ( + 'title' => 'Title of the page you want to restore.', + 'token' => 'An undelete token previously retrieved through list=deletedrevs', + 'reason' => 'Reason for restoring (optional)', + 'timestamps' => 'Timestamps of the revisions to restore. If not set, all revisions will be restored.' + ); + } + + public function getDescription() { + return array( + 'Restore certain revisions of a deleted page. A list of deleted revisions (including timestamps) can be', + 'retrieved through list=deletedrevs' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=undelete&title=Main%20Page&token=123ABC&reason=Restoring%20main%20page', + 'api.php?action=undelete&title=Main%20Page&token=123ABC×tamps=20070703220045|20070702194856' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiUndelete.php 30222 2008-01-28 19:05:26Z catrope $'; + } +} -- cgit v1.2.2