diff options
Diffstat (limited to 'includes/api')
60 files changed, 2421 insertions, 1682 deletions
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 732adae1..22144333 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -38,14 +38,15 @@ */ abstract class ApiBase { - // These constants allow modules to specify exactly how to treat incomming parameters. + // These constants allow modules to specify exactly how to treat incoming parameters. - const PARAM_DFLT = 0; - const PARAM_ISMULTI = 1; - const PARAM_TYPE = 2; - const PARAM_MAX = 3; - const PARAM_MAX2 = 4; - const PARAM_MIN = 5; + const PARAM_DFLT = 0; // Default value of the parameter + const PARAM_ISMULTI = 1; // Boolean, do we accept more than one item for this parameter (e.g.: titles)? + const PARAM_TYPE = 2; // Can be either a string type (e.g.: 'integer') or an array of allowed values + const PARAM_MAX = 3; // Max value allowed for a parameter. Only applies if TYPE='integer' + const PARAM_MAX2 = 4; // Max value allowed for a parameter for bots and sysops. Only applies if TYPE='integer' + const PARAM_MIN = 5; // Lowest value allowed for a parameter. Only applies if TYPE='integer' + const PARAM_ALLOW_DUPLICATES = 6; // Boolean, do we allow the same value to be set more than once when ISMULTI=true const LIMIT_BIG1 = 500; // Fast query, std user limit const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit @@ -159,6 +160,10 @@ abstract class ApiBase { $data =& $this->getResult()->getData(); if(isset($data['warnings'][$this->getModuleName()])) { + # Don't add duplicate warnings + $warn_regex = preg_quote($warning, '/'); + if(preg_match("/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'])) + return; $warning = "{$data['warnings'][$this->getModuleName()]['*']}\n$warning"; unset($data['warnings'][$this->getModuleName()]); } @@ -238,10 +243,10 @@ abstract class ApiBase { * module's help. */ public function makeHelpMsgParameters() { - $params = $this->getAllowedParams(); + $params = $this->getFinalParams(); if ($params !== false) { - $paramsDescription = $this->getParamDescription(); + $paramsDescription = $this->getFinalParamDescription(); $msg = ''; $paramPrefix = "\n" . str_repeat(' ', 19); foreach ($params as $paramName => $paramSettings) { @@ -260,7 +265,7 @@ abstract class ApiBase { $choices = array(); $nothingPrompt = false; foreach ($type as $t) - if ($t=='') + if ($t === '') $nothingPrompt = 'Can be empty, or '; else $choices[] = $t; @@ -319,18 +324,39 @@ abstract class ApiBase { } /** - * Returns an array of allowed parameters (keys) => default value for that parameter + * Returns an array of allowed parameters (keys) => default value for that parameter. + * Don't call this function directly: use getFinalParams() to allow hooks + * to modify parameters as needed. */ protected function getAllowedParams() { return false; } /** - * Returns the description string for the given parameter. + * Returns an array of parameter descriptions. + * Don't call this functon directly: use getFinalParamDescription() to allow + * hooks to modify descriptions as needed. */ protected function getParamDescription() { return false; } + + /** + * Get final list of parameters, after hooks have had + * a chance to tweak it as needed. + */ + public function getFinalParams() { + $params = $this->getAllowedParams(); + wfRunHooks('APIGetAllowedParams', array(&$this, &$params)); + return $params; + } + + + public function getFinalParamDescription() { + $desc = $this->getParamDescription(); + wfRunHooks('APIGetParamDescription', array(&$this, &$desc)); + return $desc; + } /** * This method mangles parameter name based on the prefix supplied to the constructor. @@ -343,12 +369,11 @@ 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($parseMaxLimit = true) { - $params = $this->getAllowedParams(); + $params = $this->getFinalParams(); $results = array (); foreach ($params as $paramName => $paramSettings) @@ -361,10 +386,27 @@ abstract class ApiBase { * Get a value for the given parameter */ protected function getParameter($paramName, $parseMaxLimit = true) { - $params = $this->getAllowedParams(); + $params = $this->getFinalParams(); $paramSettings = $params[$paramName]; return $this->getParameterFromSettings($paramName, $paramSettings, $parseMaxLimit); } + + /** + * Die if none or more than one of a certain set of parameters is set + */ + public function requireOnlyOneParameter($params) { + $required = func_get_args(); + array_shift($required); + + $intersection = array_intersect(array_keys(array_filter($params, + create_function('$x', 'return !is_null($x);') + )), $required); + if (count($intersection) > 1) { + $this->dieUsage('The parameters '.implode(', ', $intersection).' can not be used together', 'invalidparammix'); + } elseif (count($intersection) == 0) { + $this->dieUsage('One of the parameters '.implode(', ', $required).' is required', 'missingparam'); + } + } /** * Returns an array of the namespaces (by integer id) that exist on the @@ -400,10 +442,12 @@ abstract class ApiBase { $default = $paramSettings; $multi = false; $type = gettype($paramSettings); + $dupes = false; } else { $default = isset ($paramSettings[self :: PARAM_DFLT]) ? $paramSettings[self :: PARAM_DFLT] : null; $multi = isset ($paramSettings[self :: PARAM_ISMULTI]) ? $paramSettings[self :: PARAM_ISMULTI] : false; $type = isset ($paramSettings[self :: PARAM_TYPE]) ? $paramSettings[self :: PARAM_TYPE] : null; + $dupes = isset ($paramSettings[self:: PARAM_ALLOW_DUPLICATES]) ? $paramSettings[self :: PARAM_ALLOW_DUPLICATES] : false; // When type is not given, and no choices, the type is the same as $default if (!isset ($type)) { @@ -494,8 +538,8 @@ abstract class ApiBase { } } - // There should never be any duplicate values in a list - if (is_array($value)) + // Throw out duplicates if requested + if (is_array($value) && !$dupes) $value = array_unique($value); } @@ -515,10 +559,10 @@ abstract class ApiBase { protected function parseMultiValue($valueName, $value, $allowMultiple, $allowedValues) { if( trim($value) === "" ) return array(); - $sizeLimit = $this->mMainModule->canApiHighLimits() ? 501 : 51; - $valuesList = explode('|', $value,$sizeLimit); - if( count($valuesList) == $sizeLimit ) { - $junk = array_pop($valuesList); // kill last jumbled param + $sizeLimit = $this->mMainModule->canApiHighLimits() ? self::LIMIT_SML2 : self::LIMIT_SML1; + $valuesList = explode('|', $value, $sizeLimit + 1); + if( self::truncateArray($valuesList, $sizeLimit) ) { + $this->setWarning("Too many values supplied for parameter '$valueName': the limit is $sizeLimit"); } if (!$allowMultiple && count($valuesList) != 1) { $possibleValues = is_array($allowedValues) ? "of '" . implode("', '", $allowedValues) . "'" : ''; @@ -527,7 +571,7 @@ abstract class ApiBase { if (is_array($allowedValues)) { # Check for unknown values $unknown = array_diff($valuesList, $allowedValues); - if(!empty($unknown)) + if(count($unknown)) { if($allowMultiple) { @@ -569,6 +613,23 @@ abstract class ApiBase { } } } + + /** + * Truncate an array to a certain length. + * @param $arr array Array to truncate + * @param $limit int Maximum length + * @return bool True if the array was truncated, false otherwise + */ + public static function truncateArray(&$arr, $limit) + { + $modified = false; + while(count($arr) > $limit) + { + $junk = array_pop($arr); + $modified = true; + } + return $modified; + } /** * Call main module's error handler @@ -594,8 +655,6 @@ abstract class ApiBase { '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"), @@ -632,13 +691,21 @@ abstract class ApiBase { '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"), + 'mailnologin' => array('code' => 'cantsend', 'info' => "You're not logged in or you don't have a confirmed e-mail address, so you can't send e-mail"), + 'usermaildisabled' => array('code' => 'usermaildisabled', 'info' => "User email has been disabled"), + 'blockedemailuser' => array('code' => 'blockedfrommail', 'info' => "You have been blocked from sending e-mail"), + 'notarget' => array('code' => 'notarget', 'info' => "You have not specified a valid target for this action"), + 'noemail' => array('code' => 'noemail', 'info' => "The user has not specified a valid e-mail address, or has chosen not to receive e-mail from other users"), + 'rcpatroldisabled' => array('code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki"), + 'markedaspatrollederror-noautopatrol' => array('code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes"), // API-specific messages 'missingparam' => array('code' => 'no$1', 'info' => "The \$1 parameter must be set"), 'invalidtitle' => array('code' => 'invalidtitle', 'info' => "Bad title ``\$1''"), + 'nosuchpageid' => array('code' => 'nosuchpageid', 'info' => "There is no page with ID \$1"), '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"), + 'invalidexpiry' => array('code' => 'invalidexpiry', 'info' => "Invalid expiry time ``\$1''"), + 'pastexpiry' => array('code' => 'pastexpiry', 'info' => "Expiry time ``\$1'' is in the past"), 'create-titleexists' => array('code' => 'create-titleexists', 'info' => "Existing titles can't be protected with 'create'"), 'missingtitle-createonly' => array('code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'"), 'cantblock' => array('code' => 'cantblock', 'info' => "You don't have permission to block users"), @@ -651,6 +718,12 @@ abstract class ApiBase { 'permdenied-undelete' => array('code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions"), 'createonly-exists' => array('code' => 'articleexists', 'info' => "The article you tried to create has been created already"), 'nocreate-missing' => array('code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist"), + 'nosuchrcid' => array('code' => 'nosuchrcid', 'info' => "There is no change with rcid ``\$1''"), + 'cantpurge' => array('code' => 'cantpurge', 'info' => "Only users with the 'purge' right can purge pages via the API"), + 'protect-invalidaction' => array('code' => 'protect-invalidaction', 'info' => "Invalid protection type ``\$1''"), + 'protect-invalidlevel' => array('code' => 'protect-invalidlevel', 'info' => "Invalid protection level ``\$1''"), + 'toofewexpiries' => array('code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed"), + // ApiEditPage messages 'noimageredirect-anon' => array('code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects"), @@ -665,18 +738,33 @@ abstract class ApiBase { 'editconflict' => array('code' => 'editconflict', 'info' => "Edit conflict detected"), 'hashcheckfailed' => array('code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect"), 'missingtext' => array('code' => 'notext', 'info' => "One of the text, appendtext and prependtext parameters must be set"), + 'emptynewsection' => array('code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.'), ); /** * Output the error message related to a certain array - * @param array $error Element of a getUserPermissionsErrors() + * @param array $error Element of a getUserPermissionsErrors()-style array */ public function dieUsageMsg($error) { + $parsed = $this->parseMsg($error); + $this->dieUsage($parsed['code'], $parsed['info']); + } + + /** + * Return the error message related to a certain array + * @param array $error Element of a getUserPermissionsErrors()-style array + * @return array('code' => code, 'info' => info) + */ + public function parseMsg($error) { $key = array_shift($error); if(isset(self::$messageMap[$key])) - $this->dieUsage(wfMsgReplaceArgs(self::$messageMap[$key]['info'], $error), wfMsgReplaceArgs(self::$messageMap[$key]['code'], $error)); + return array( 'code' => + wfMsgReplaceArgs(self::$messageMap[$key]['code'], $error), + 'info' => + wfMsgReplaceArgs(self::$messageMap[$key]['info'], $error) + ); // If the key isn't present, throw an "unknown error" - $this->dieUsageMsg(array('unknownerror', $key)); + return $this->parseMsg(array('unknownerror', $key)); } /** @@ -814,6 +902,6 @@ abstract class ApiBase { * Returns a String that identifies the version of this class. */ public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiBase.php 36309 2008-06-15 20:37:28Z catrope $'; + return __CLASS__ . ': $Id: ApiBase.php 47041 2009-02-09 14:39:41Z catrope $'; } } diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index 34813bf7..dfb11061 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -49,7 +49,7 @@ class ApiBlock extends ApiBase { * of success. If it fails, the result will specify the nature of the error. */ public function execute() { - global $wgUser; + global $wgUser, $wgBlockAllowsUTEdit; $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); @@ -72,8 +72,6 @@ class ApiBlock extends ApiBase { $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']; @@ -83,13 +81,15 @@ class ApiBlock extends ApiBase { $form->BlockOther = ''; $form->BlockAnonOnly = $params['anononly']; $form->BlockCreateAccount = $params['nocreate']; - $form->BlockEnableAutoBlock = $params['autoblock']; + $form->BlockEnableAutoblock = $params['autoblock']; $form->BlockEmail = $params['noemail']; $form->BlockHideName = $params['hidename']; + $form->BlockAllowUsertalk = $params['allowusertalk'] && $wgBlockAllowsUTEdit; + $form->BlockReblock = $params['reblock']; $userID = $expiry = null; $retval = $form->doBlock($userID, $expiry); - if(!empty($retval)) + if(count($retval)) // We don't care about multiple errors, just report one of them $this->dieUsageMsg($retval); @@ -107,6 +107,8 @@ class ApiBlock extends ApiBase { $res['noemail'] = ''; if($params['hidename']) $res['hidename'] = ''; + if($params['allowusertalk']) + $res['allowusertalk'] = ''; $this->getResult()->addValue(null, $this->getModuleName(), $res); } @@ -125,13 +127,15 @@ class ApiBlock extends ApiBase { 'autoblock' => false, 'noemail' => false, 'hidename' => false, + 'allowusertalk' => false, + 'reblock' => 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', + 'token' => 'A block token previously obtained through the gettoken parameter or prop=info', '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)', @@ -139,7 +143,9 @@ class ApiBlock extends ApiBase { '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.)' + 'hidename' => 'Hide the username from the block log. (Requires the "hideuser" right.)', + 'allowusertalk' => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)', + 'reblock' => 'If the user is already blocked, overwrite the existing block', ); } @@ -157,6 +163,6 @@ class ApiBlock extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiBlock.php 35388 2008-05-27 10:18:28Z catrope $'; + return __CLASS__ . ': $Id: ApiBlock.php 43677 2008-11-18 15:21:04Z catrope $'; } } diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php index 06592d46..c0212924 100644 --- a/includes/api/ApiDelete.php +++ b/includes/api/ApiDelete.php @@ -52,29 +52,36 @@ class ApiDelete extends ApiBase { $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); - $titleObj = NULL; - if(!isset($params['title'])) - $this->dieUsageMsg(array('missingparam', 'title')); + $this->requireOnlyOneParameter($params, 'title', 'pageid'); if(!isset($params['token'])) $this->dieUsageMsg(array('missingparam', 'token')); - $titleObj = Title::newFromText($params['title']); - if(!$titleObj) - $this->dieUsageMsg(array('invalidtitle', $params['title'])); + if(isset($params['title'])) + { + $titleObj = Title::newFromText($params['title']); + if(!$titleObj) + $this->dieUsageMsg(array('invalidtitle', $params['title'])); + } + else if(isset($params['pageid'])) + { + $titleObj = Title::newFromID($params['pageid']); + if(!$titleObj) + $this->dieUsageMsg(array('nosuchpageid', $params['pageid'])); + } if(!$titleObj->exists()) $this->dieUsageMsg(array('notanarticle')); $reason = (isset($params['reason']) ? $params['reason'] : NULL); - if ($titleObj->getNamespace() == NS_IMAGE) { - $retval = self::deletefile($params['token'], $titleObj, $params['oldimage'], $reason, false); - if(!empty($retval)) + if ($titleObj->getNamespace() == NS_FILE) { + $retval = self::deleteFile($params['token'], $titleObj, $params['oldimage'], $reason, false); + if(count($retval)) // We don't care about multiple errors, just report one of them $this->dieUsageMsg(current($retval)); } else { $articleObj = new Article($titleObj); $retval = self::delete($articleObj, $params['token'], $reason); - if(!empty($retval)) + if(count($retval)) // We don't care about multiple errors, just report one of them $this->dieUsageMsg(current($retval)); @@ -90,8 +97,6 @@ class ApiDelete extends ApiBase { private static function getPermissionsError(&$title, $token) { global $wgUser; - // Check wiki readonly - if (wfReadOnly()) return array(array('readonlytext')); // Check permissions $errors = $title->getUserPermissionsErrors('delete', $wgUser); @@ -114,8 +119,8 @@ class ApiDelete extends ApiBase { public static function delete(&$article, $token, &$reason = NULL) { global $wgUser; - - $errors = self::getPermissionsError($article->getTitle(), $token); + $title = $article->getTitle(); + $errors = self::getPermissionsError($title, $token); if (count($errors)) return $errors; // Auto-generate a summary, if necessary @@ -156,7 +161,8 @@ class ApiDelete extends ApiBase { if( !FileDeleteForm::haveDeletableFile($file, $oldfile, $oldimage) ) return array(array('nofile')); - + if (is_null($reason)) # Log and RC don't like null reasons + $reason = ''; $status = FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress ); if( !$status->isGood() ) @@ -170,6 +176,9 @@ class ApiDelete extends ApiBase { public function getAllowedParams() { return array ( 'title' => null, + 'pageid' => array( + ApiBase::PARAM_TYPE => 'integer' + ), 'token' => null, 'reason' => null, 'watch' => false, @@ -180,7 +189,8 @@ class ApiDelete extends ApiBase { public function getParamDescription() { return array ( - 'title' => 'Title of the page you want to delete.', + 'title' => 'Title of the page you want to delete. Cannot be used together with pageid', + 'pageid' => 'Page ID of the page you want to delete. Cannot be used together with title', 'token' => 'A delete token previously retrieved through prop=info', 'reason' => 'Reason for the deletion. If not set, an automatically generated reason will be used.', 'watch' => 'Add the page to your watchlist', @@ -191,7 +201,7 @@ class ApiDelete extends ApiBase { 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.' + 'Delete a page.' ); } @@ -203,6 +213,6 @@ class ApiDelete extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiDelete.php 35350 2008-05-26 12:15:21Z simetrical $'; + return __CLASS__ . ': $Id: ApiDelete.php 44541 2008-12-13 21:07:18Z mrzman $'; } } diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php new file mode 100644 index 00000000..40e38a0f --- /dev/null +++ b/includes/api/ApiDisabled.php @@ -0,0 +1,72 @@ +<?php + +/* + * Created on Sep 25, 2008 + * API for MediaWiki 1.8+ + * + * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiBase.php"); +} + + +/** + * API module that dies with an error immediately. + * + * Use this to disable core modules with + * $wgAPIModules['modulename'] = 'ApiDisabled'; + * + * To disable submodules of action=query, use ApiQueryDisabled instead + * + * @ingroup API + */ +class ApiDisabled extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + $this->dieUsage("The ``{$this->getModuleName()}'' module has been disabled.", 'moduledisabled'); + } + + public function getAllowedParams() { + return array (); + } + + public function getParamDescription() { + return array (); + } + + public function getDescription() { + return array( + 'This module has been disabled.' + ); + } + + protected function getExamples() { + return array (); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiDisabled.php 41268 2008-09-25 20:50:50Z catrope $'; + } +} diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php index d10432f3..bc5dfa87 100644 --- a/includes/api/ApiEditPage.php +++ b/includes/api/ApiEditPage.php @@ -29,8 +29,10 @@ if (!defined('MEDIAWIKI')) { } /** - * A query module to list all external URLs found on a given set of pages. + * A module that allows for editing and creating pages. * + * Currently, this wraps around the EditPage class in an ugly way, + * EditPage.php should be rewritten to provide a cleaner interface * @ingroup API */ class ApiEditPage extends ApiBase { @@ -66,7 +68,7 @@ class ApiEditPage extends ApiBase { $errors = $titleObj->getUserPermissionsErrors('edit', $wgUser); if(!$titleObj->exists()) $errors = array_merge($errors, $titleObj->getUserPermissionsErrors('create', $wgUser)); - if(!empty($errors)) + if(count($errors)) $this->dieUsageMsg($errors[0]); $articleObj = new Article($titleObj); @@ -98,8 +100,11 @@ class ApiEditPage extends ApiBase { $reqArr['wpEdittime'] = wfTimestamp(TS_MW, $params['basetimestamp']); else $reqArr['wpEdittime'] = $articleObj->getTimestamp(); - # Fake wpStartime - $reqArr['wpStarttime'] = $reqArr['wpEdittime']; + if(!is_null($params['starttimestamp']) && $params['starttimestamp'] != '') + $reqArr['wpStarttime'] = wfTimestamp(TS_MW, $params['starttimestamp']); + else + # Fake wpStartime + $reqArr['wpStarttime'] = $reqArr['wpEdittime']; if($params['minor'] || (!$params['notminor'] && $wgUser->getOption('minordefault'))) $reqArr['wpMinoredit'] = ''; if($params['recreate']) @@ -111,6 +116,8 @@ class ApiEditPage extends ApiBase { $this->dieUsage("The section parameter must be set to an integer or 'new'", "invalidsection"); $reqArr['wpSection'] = $params['section']; } + else + $reqArr['wpSection'] = ''; if($params['watch']) $watch = true; @@ -134,13 +141,13 @@ class ApiEditPage extends ApiBase { # Handle CAPTCHA parameters global $wgRequest; if(isset($params['captchaid'])) - $wgRequest->data['wpCaptchaId'] = $params['captchaid']; + $wgRequest->setVal( 'wpCaptchaId', $params['captchaid'] ); if(isset($params['captchaword'])) - $wgRequest->data['wpCaptchaWord'] = $params['captchaword']; + $wgRequest->setVal( 'wpCaptchaWord', $params['captchaword'] ); $r = array(); if(!wfRunHooks('APIEditBeforeSave', array(&$ep, $ep->textbox1, &$r))) { - if(!empty($r)) + if(count($r)) { $r['result'] = "Failure"; $this->getResult()->addValue(null, $this->getModuleName(), $r); @@ -200,18 +207,24 @@ class ApiEditPage extends ApiBase { case EditPage::AS_CONFLICT_DETECTED: $this->dieUsageMsg(array('editconflict')); #case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary - #case EditPage::AS_TEXTBOX_EMPTY: Can't happen since we don't do sections + case EditPage::AS_TEXTBOX_EMPTY: + $this->dieUsageMsg(array('emptynewsection')); case EditPage::AS_END: # This usually means some kind of race condition # or DB weirdness occurred. Throw an unknown error here. - $this->dieUsageMsg(array('unknownerror', 'AS_END')); + $this->dieUsageMsg(array('unknownerror')); case EditPage::AS_SUCCESS_NEW_ARTICLE: $r['new'] = ''; case EditPage::AS_SUCCESS_UPDATE: $r['result'] = "Success"; $r['pageid'] = $titleObj->getArticleID(); $r['title'] = $titleObj->getPrefixedText(); - $newRevId = $titleObj->getLatestRevId(); + # HACK: We create a new Article object here because getRevIdFetched() + # refuses to be run twice, and because Title::getLatestRevId() + # won't fetch from the master unless we select for update, which we + # don't want to do. + $newArticle = new Article($titleObj); + $newRevId = $newArticle->getRevIdFetched(); if($newRevId == $oldRevId) $r['nochange'] = ''; else @@ -245,6 +258,7 @@ class ApiEditPage extends ApiBase { 'notminor' => false, 'bot' => false, 'basetimestamp' => null, + 'starttimestamp' => null, 'recreate' => false, 'createonly' => false, 'nocreate' => false, @@ -271,6 +285,9 @@ class ApiEditPage extends ApiBase { 'basetimestamp' => array('Timestamp of the base revision (gotten through prop=revisions&rvprop=timestamp).', 'Used to detect edit conflicts; leave unset to ignore conflicts.' ), + 'starttimestamp' => array('Timestamp when you obtained the edit token.', + 'Used to detect edit conflicts; leave unset to ignore conflicts.' + ), 'recreate' => 'Override any errors about the article having been deleted in the meantime', 'createonly' => 'Don\'t edit the page if it exists already', 'nocreate' => 'Throw an error if the page doesn\'t exist', @@ -294,6 +311,6 @@ class ApiEditPage extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiEditPage.php 36309 2008-06-15 20:37:28Z catrope $'; + return __CLASS__ . ': $Id: ApiEditPage.php 44394 2008-12-10 14:12:54Z catrope $'; } } diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php index 7e083536..fbdf495f 100644 --- a/includes/api/ApiEmailUser.php +++ b/includes/api/ApiEmailUser.php @@ -39,6 +39,11 @@ class ApiEmailUser extends ApiBase { public function execute() { global $wgUser; + + // Check whether email is enabled + if ( !EmailUserForm::userEmailEnabled() ) + $this->dieUsageMsg( array( 'usermaildisabled' ) ); + $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); @@ -53,12 +58,12 @@ class ApiEmailUser extends ApiBase { // Validate target $targetUser = EmailUserForm::validateEmailTarget( $params['target'] ); if ( !( $targetUser instanceof User ) ) - $this->dieUsageMsg( array( $targetUser[0] ) ); + $this->dieUsageMsg( array( $targetUser ) ); // Check permissions $error = EmailUserForm::getPermissionsError( $wgUser, $params['token'] ); if ( $error ) - $this->dieUsageMsg( array( $error[0] ) ); + $this->dieUsageMsg( array( $error ) ); $form = new EmailUserForm( $targetUser, $params['text'], $params['subject'], $params['ccme'] ); @@ -89,7 +94,6 @@ class ApiEmailUser extends ApiBase { 'target' => 'User to send email to', 'subject' => 'Subject header', 'text' => 'Mail body', - // FIXME: How to properly get a token? 'token' => 'A token previously acquired via prop=info', 'ccme' => 'Send a copy of this mail to me', ); @@ -97,7 +101,7 @@ class ApiEmailUser extends ApiBase { public function getDescription() { return array( - 'Emails a user.' + 'Email a user.' ); } @@ -108,7 +112,7 @@ class ApiEmailUser extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: $'; + return __CLASS__ . ': $Id: ApiEmailUser.php 41269 2008-09-25 21:39:36Z catrope $'; } }
\ No newline at end of file diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php index 397aece3..f4e6212a 100644 --- a/includes/api/ApiExpandTemplates.php +++ b/includes/api/ApiExpandTemplates.php @@ -43,23 +43,22 @@ class ApiExpandTemplates extends ApiBase { public function execute() { // Get parameters - extract( $this->extractRequestParams() ); - $retval = ''; + $params = $this->extractRequestParams(); //Create title for parser - $title_obj = Title :: newFromText( $title ); + $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 + $title_obj = Title :: newFromText( "API" ); // default $result = $this->getResult(); // Parse text global $wgParser; $options = new ParserOptions(); - if ( $generatexml ) + if ( $params['generatexml'] ) { $wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS ); - $dom = $wgParser->preprocessToDom( $text ); + $dom = $wgParser->preprocessToDom( $params['text'] ); if ( is_callable( array( $dom, 'saveXML' ) ) ) { $xml = $dom->saveXML(); } else { @@ -67,9 +66,9 @@ class ApiExpandTemplates extends ApiBase { } $xml_result = array(); $result->setContent( $xml_result, $xml ); - $result->addValue( null, 'parsetree', $xml_result); + $result->addValue( null, 'parsetree', $xml_result); } - $retval = $wgParser->preprocess( $text, $title_obj, $options ); + $retval = $wgParser->preprocess( $params['text'], $title_obj, $options ); // Return result $retval_array = array(); @@ -106,6 +105,6 @@ class ApiExpandTemplates extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiExpandTemplates.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiExpandTemplates.php 44719 2008-12-17 16:34:01Z catrope $'; } } diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 8f08f4db..9efbbbe0 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -199,15 +199,17 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or * This method also replaces any '<' with < */ protected function formatHTML($text) { + global $wgUrlProtocols; + // Escape everything first for full coverage $text = htmlspecialchars($text); // encode all comments or tags as safe blue strings $text = preg_replace('/\<(!--.*?--|.*?)\>/', '<span style="color:blue;"><\1></span>', $text); // identify URLs - $protos = "http|https|ftp|gopher"; + $protos = implode("|", $wgUrlProtocols); # This regex hacks around bug 13218 (" included in the URL) - $text = preg_replace("#(($protos)://.*?)(")?([ \\'\"()<\n])#", '<a href="\\1">\\1</a>\\3\\4', $text); + $text = preg_replace("#(($protos).*?)(")?([ \\'\"()<\n])#", '<a href="\\1">\\1</a>\\3\\4', $text); // identify requests to api.php $text = preg_replace("#api\\.php\\?[^ \\()<\n\t]+#", '<a href="\\0">\\0</a>', $text); if( $this->mHelp ) { @@ -239,7 +241,7 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 44569 2008-12-14 08:31:04Z tstarling $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 43470 2008-11-14 00:30:34Z tstarling $'; } } @@ -300,6 +302,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 44569 2008-12-14 08:31:04Z tstarling $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 43470 2008-11-14 00:30:34Z tstarling $'; } } diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index 42156849..1d89eb18 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -58,7 +58,10 @@ class ApiFormatJson extends ApiFormatBase { $suffix = ")"; } - if (!function_exists('json_encode') || $this->getIsHtml()) { + // Some versions of PHP have a broken json_encode, see PHP bug + // 46944. Test encoding an affected character (U+20000) to + // avoid this. + if (!function_exists('json_encode') || $this->getIsHtml() || strtolower(json_encode("\xf0\xa0\x80\x80")) != '\ud840\udc00') { $json = new Services_JSON(); $this->printText($prefix . $json->encode($this->getResultData(), $this->getIsHtml()) . $suffix); } else { @@ -86,6 +89,6 @@ class ApiFormatJson extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatJson.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiFormatJson.php 45682 2009-01-12 19:06:33Z raymond $'; } } diff --git a/includes/api/ApiFormatJson_json.php b/includes/api/ApiFormatJson_json.php index 87d7086e..4b29ff56 100644 --- a/includes/api/ApiFormatJson_json.php +++ b/includes/api/ApiFormatJson_json.php @@ -50,7 +50,7 @@ * @author Matt Knapp <mdknapp[at]gmail[dot]com> * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> * @copyright 2005 Michal Migurski -* @version CVS: $Id: ApiFormatJson_json.php 35098 2008-05-20 17:13:28Z ialex $ +* @version CVS: $Id: ApiFormatJson_json.php 45682 2009-01-12 19:06:33Z raymond $ * @license http://www.opensource.org/licenses/bsd-license.php * @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198 */ @@ -168,6 +168,17 @@ class Services_JSON return chr(0xC0 | (($bytes >> 6) & 0x1F)) . chr(0x80 | ($bytes & 0x3F)); + case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16{2})) == 0xDC: + // return a 4-byte UTF-8 character + $char = ((($bytes & 0x03FF) << 10) + | ((ord($utf16{2}) & 0x03) << 8) + | ord($utf16{3})); + $char += 0x10000; + return chr(0xF0 | (($char >> 18) & 0x07)) + . chr(0x80 | (($char >> 12) & 0x3F)) + . chr(0x80 | (($char >> 6) & 0x3F)) + . chr(0x80 | ($char & 0x3F)); + case (0xFFFF & $bytes) == $bytes: // return a 3-byte UTF-8 character // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 @@ -218,6 +229,20 @@ class Services_JSON | (0x0F & (ord($utf8{1}) >> 2))) . chr((0xC0 & (ord($utf8{1}) << 6)) | (0x7F & ord($utf8{2}))); + + case 4: + // return a UTF-16 surrogate pair from a 4-byte UTF-8 char + if(ord($utf8{0}) > 0xF4) return ''; # invalid + $char = ((0x1C0000 & (ord($utf8{0}) << 18)) + | (0x03F000 & (ord($utf8{1}) << 12)) + | (0x000FC0 & (ord($utf8{2}) << 6)) + | (0x00003F & ord($utf8{3}))); + if($char > 0x10FFFF) return ''; # invalid + $char -= 0x10000; + return chr(0xD8 | (($char >> 18) & 0x03)) + . chr(($char >> 10) & 0xFF) + . chr(0xDC | (($char >> 8) & 0x03)) + . chr($char & 0xFF); } // ignoring UTF-32 for now, sorry @@ -346,40 +371,19 @@ class Services_JSON case (($ord_var_c & 0xF8) == 0xF0): // characters U-00010000 - U-001FFFFF, mask 11110XXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + // These will always return a surrogate pair $char = pack('C*', $ord_var_c, ord($var{$c + 1}), ord($var{$c + 2}), ord($var{$c + 3})); $c += 3; $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFC) == 0xF8): - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4})); - $c += 4; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFE) == 0xFC): - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4}), - ord($var{$c + 5})); - $c += 5; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); + if($utf16 == '') { + $ascii .= '\ufffd'; + } else { + $utf16 = str_split($utf16, 2); + $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1])); + } break; } } @@ -591,6 +595,16 @@ class Services_JSON } break; + case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)): + // escaped unicode surrogate pair + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))) + . chr(hexdec(substr($chrs, ($c + 8), 2))) + . chr(hexdec(substr($chrs, ($c + 10), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 11; + break; + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): // single, escaped unicode character $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) @@ -812,6 +826,9 @@ class Services_JSON } } + +// Hide the PEAR_Error variant from Doxygen +/// @cond if (class_exists('PEAR_Error')) { /** @@ -827,6 +844,7 @@ if (class_exists('PEAR_Error')) { } } else { +/// @endcond /** * @todo Ultimately, this class shall be descended from PEAR_Error diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index 0909539e..e741c16d 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -42,38 +42,62 @@ class ApiFormatWddx extends ApiFormatBase { } public function execute() { - if (function_exists('wddx_serialize_value')) { + if (function_exists('wddx_serialize_value') && !$this->getIsHtml()) { $this->printText(wddx_serialize_value($this->getResultData())); } else { - $this->printText('<?xml version="1.0" encoding="utf-8"?>'); - $this->printText('<wddxPacket version="1.0"><header/><data>'); - $this->slowWddxPrinter($this->getResultData()); - $this->printText('</data></wddxPacket>'); + // Don't do newlines and indentation if we weren't asked + // for pretty output + $nl = ($this->getIsHtml() ? "" : "\n"); + $indstr = " "; + $this->printText("<?xml version=\"1.0\"?>$nl"); + $this->printText("<wddxPacket version=\"1.0\">$nl"); + $this->printText("$indstr<header/>$nl"); + $this->printText("$indstr<data>$nl"); + $this->slowWddxPrinter($this->getResultData(), 4); + $this->printText("$indstr</data>$nl"); + $this->printText("</wddxPacket>$nl"); } } /** * Recursivelly go through the object and output its data in WDDX format. */ - function slowWddxPrinter($elemValue) { + function slowWddxPrinter($elemValue, $indent = 0) { + $indstr = ($this->getIsHtml() ? "" : str_repeat(' ', $indent)); + $indstr2 = ($this->getIsHtml() ? "" : str_repeat(' ', $indent + 2)); + $nl = ($this->getIsHtml() ? "" : "\n"); switch (gettype($elemValue)) { case 'array' : - $this->printText('<struct>'); - foreach ($elemValue as $subElemName => $subElemValue) { - $this->printText(wfElement('var', array ( - 'name' => $subElemName - ), null)); - $this->slowWddxPrinter($subElemValue); - $this->printText('</var>'); + // Check whether we've got an associative array (<struct>) + // or a regular array (<array>) + $cnt = count($elemValue); + if($cnt == 0 || array_keys($elemValue) === range(0, $cnt - 1)) { + // Regular array + $this->printText($indstr . Xml::element('array', array( + 'length' => $cnt + ), null) . $nl); + foreach($elemValue as $subElemValue) + $this->slowWddxPrinter($subElemValue, $indent + 2); + $this->printText("$indstr</array>$nl"); + } else { + // Associative array (<struct>) + $this->printText("$indstr<struct>$nl"); + foreach($elemValue as $subElemName => $subElemValue) { + $this->printText($indstr2 . Xml::element('var', array( + 'name' => $subElemName + ), null) . $nl); + $this->slowWddxPrinter($subElemValue, $indent + 4); + $this->printText("$indstr2</var>$nl"); + } + $this->printText("$indstr</struct>$nl"); } - $this->printText('</struct>'); break; case 'integer' : case 'double' : - $this->printText(wfElement('number', null, $elemValue)); + $this->printText($indstr . Xml::element('number', null, $elemValue) . $nl); break; case 'string' : - $this->printText(wfElement('string', null, $elemValue)); + $this->printText($indstr . Xml::element('string', null, $elemValue) . $nl); break; default : ApiBase :: dieDebug(__METHOD__, 'Unknown type ' . gettype($elemValue)); @@ -85,6 +109,6 @@ class ApiFormatWddx extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatWddx.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiFormatWddx.php 44588 2008-12-14 19:14:21Z demon $'; } } diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index d35eb3e9..7ff57324 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -56,12 +56,12 @@ class ApiFormatXml extends ApiFormatBase { $params = $this->extractRequestParams(); $this->mDoubleQuote = $params['xmldoublequote']; - $this->printText('<?xml version="1.0" encoding="utf-8"?>'); + $this->printText('<?xml version="1.0"?>'); $this->recXmlPrint($this->mRootElemName, $this->getResultData(), $this->getIsHtml() ? -2 : null); } /** - * This method takes an array and converts it into an xml. + * This method takes an array and converts it to XML. * There are several noteworthy cases: * * If array contains a key '_element', then the code assumes that ALL other keys are not important and replaces them with the value['_element']. @@ -80,6 +80,7 @@ class ApiFormatXml extends ApiFormatBase { } else { $indstr = ''; } + $elemName = str_replace(' ', '_', $elemName); switch (gettype($elemValue)) { case 'array' : @@ -104,6 +105,14 @@ class ApiFormatXml extends ApiFormatBase { foreach ($elemValue as $subElemId => & $subElemValue) { if (is_string($subElemValue) && $this->mDoubleQuote) $subElemValue = $this->doubleQuote($subElemValue); + + // Replace spaces with underscores + $newSubElemId = str_replace(' ', '_', $subElemId); + if($newSubElemId != $subElemId) { + $elemValue[$newSubElemId] = $subElemValue; + unset($elemValue[$subElemId]); + $subElemId = $newSubElemId; + } if (gettype($subElemId) === 'integer') { $indElements[] = $subElemValue; @@ -114,18 +123,18 @@ class ApiFormatXml extends ApiFormatBase { } } - if (is_null($subElemIndName) && !empty ($indElements)) + if (is_null($subElemIndName) && count($indElements)) ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()."); - if (!empty ($subElements) && !empty ($indElements) && !is_null($subElemContent)) + if (count($subElements) && count($indElements) && !is_null($subElemContent)) ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has content and subelements"); if (!is_null($subElemContent)) { - $this->printText($indstr . wfElement($elemName, $elemValue, $subElemContent)); - } elseif (empty ($indElements) && empty ($subElements)) { - $this->printText($indstr . wfElement($elemName, $elemValue)); + $this->printText($indstr . Xml::element($elemName, $elemValue, $subElemContent)); + } elseif (!count($indElements) && !count($subElements)) { + $this->printText($indstr . Xml::element($elemName, $elemValue)); } else { - $this->printText($indstr . wfElement($elemName, $elemValue, null)); + $this->printText($indstr . Xml::element($elemName, $elemValue, null)); foreach ($subElements as $subElemId => & $subElemValue) $this->recXmlPrint($subElemId, $subElemValue, $indent); @@ -133,14 +142,14 @@ class ApiFormatXml extends ApiFormatBase { foreach ($indElements as $subElemId => & $subElemValue) $this->recXmlPrint($subElemIndName, $subElemValue, $indent); - $this->printText($indstr . wfCloseElement($elemName)); + $this->printText($indstr . Xml::closeElement($elemName)); } break; case 'object' : // ignore break; default : - $this->printText($indstr . wfElement($elemName, null, $elemValue)); + $this->printText($indstr . Xml::element($elemName, null, $elemValue)); break; } } @@ -166,6 +175,6 @@ class ApiFormatXml extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatXml.php 37075 2008-07-04 22:44:57Z brion $'; + return __CLASS__ . ': $Id: ApiFormatXml.php 44588 2008-12-14 19:14:21Z demon $'; } } diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php index c0d4093e..f16b2c8a 100644 --- a/includes/api/ApiFormatYaml_spyc.php +++ b/includes/api/ApiFormatYaml_spyc.php @@ -1,883 +1,234 @@ <?php - /** - * Spyc -- A Simple PHP YAML Class - * @version 0.2.3 -- 2006-02-04 - * @author Chris Wanstrath <chris@ozmm.org> - * @see http://spyc.sourceforge.net/ - * @copyright Copyright 2005-2006 Chris Wanstrath - * @license http://www.opensource.org/licenses/mit-license.php MIT License - */ - - /** - * A node, used by Spyc for parsing YAML. - * @ingroup API - */ - class YAMLNode { - /**#@+ - * @access public - * @var string - */ - var $parent; - var $id; - /**#@-*/ - /** - * @access public - * @var mixed - */ - var $data; - /** - * @access public - * @var int - */ - var $indent; - /** - * @access public - * @var bool - */ - var $children = false; - - /** - * The constructor assigns the node a unique ID. - * @access public - * @return void - */ - function YAMLNode() { - $this->id = uniqid(''); - } - } - - /** - * The Simple PHP YAML Class. - * - * This class can be used to read a YAML file and convert its contents - * into a PHP array. It currently supports a very limited subsection of - * the YAML spec. - * - * Usage: - * <code> - * $parser = new Spyc; - * $array = $parser->load($file); - * </code> - * @ingroup API - */ - class Spyc { - - /** - * Load YAML into a PHP array statically - * - * The load method, when supplied with a YAML stream (string or file), - * will do its best to convert YAML in a file into a PHP array. Pretty - * simple. - * Usage: - * <code> - * $array = Spyc::YAMLLoad('lucky.yml'); - * print_r($array); - * </code> - * @access public - * @return array - * @param string $input Path of YAML file or string containing YAML - */ - function YAMLLoad($input) { - $spyc = new Spyc; - return $spyc->load($input); - } - - /** - * Dump YAML from PHP array statically - * - * The dump method, when supplied with an array, will do its best - * to convert the array into friendly YAML. Pretty simple. Feel free to - * save the returned string as nothing.yml and pass it around. - * - * Oh, and you can decide how big the indent is and what the wordwrap - * for folding is. Pretty cool -- just pass in 'false' for either if - * you want to use the default. - * - * Indent's default is 2 spaces, wordwrap's default is 40 characters. And - * you can turn off wordwrap by passing in 0. - * - * @access public - * @static - * @return string - * @param array $array PHP array - * @param int $indent Pass in false to use the default, which is 2 - * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) - */ - public static function YAMLDump($array,$indent = false,$wordwrap = false) { - $spyc = new Spyc; - return $spyc->dump($array,$indent,$wordwrap); - } - - /** - * Load YAML into a PHP array from an instantiated object - * - * The load method, when supplied with a YAML stream (string or file path), - * will do its best to convert the YAML into a PHP array. Pretty simple. - * Usage: - * <code> - * $parser = new Spyc; - * $array = $parser->load('lucky.yml'); - * print_r($array); - * </code> - * @access public - * @return array - * @param string $input Path of YAML file or string containing YAML - */ - function load($input) { - // See what type of input we're talking about - // If it's not a file, assume it's a string - if (!empty($input) && (strpos($input, "\n") === false) - && file_exists($input)) { - $yaml = file($input); - } else { - $yaml = explode("\n",$input); - } - // Initiate some objects and values - $base = new YAMLNode; - $base->indent = 0; - $this->_lastIndent = 0; - $this->_lastNode = $base->id; - $this->_inBlock = false; - $this->_isInline = false; - - foreach ($yaml as $linenum => $line) { - $ifchk = trim($line); - - // If the line starts with a tab (instead of a space), throw a fit. - if (preg_match('/^(\t)+(\w+)/', $line)) { - $err = 'ERROR: Line '. ($linenum + 1) .' in your input YAML begins'. - ' with a tab. YAML only recognizes spaces. Please reformat.'; - die($err); - } - - if ($this->_inBlock === false && empty($ifchk)) { - continue; - } elseif ($this->_inBlock == true && empty($ifchk)) { - $last =& $this->_allNodes[$this->_lastNode]; - $last->data[key($last->data)] .= "\n"; - } elseif ($ifchk{0} != '#' && substr($ifchk,0,3) != '---') { - // Create a new node and get its indent - $node = new YAMLNode; - $node->indent = $this->_getIndent($line); - - // Check where the node lies in the hierarchy - if ($this->_lastIndent == $node->indent) { - // If we're in a block, add the text to the parent's data - if ($this->_inBlock === true) { - $parent =& $this->_allNodes[$this->_lastNode]; - $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd; - } else { - // The current node's parent is the same as the previous node's - if (isset($this->_allNodes[$this->_lastNode])) { - $node->parent = $this->_allNodes[$this->_lastNode]->parent; - } - } - } elseif ($this->_lastIndent < $node->indent) { - if ($this->_inBlock === true) { - $parent =& $this->_allNodes[$this->_lastNode]; - $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd; - } elseif ($this->_inBlock === false) { - // The current node's parent is the previous node - $node->parent = $this->_lastNode; - - // If the value of the last node's data was > or | we need to - // start blocking i.e. taking in all lines as a text value until - // we drop our indent. - $parent =& $this->_allNodes[$node->parent]; - $this->_allNodes[$node->parent]->children = true; - if (is_array($parent->data)) { - $chk = $parent->data[key($parent->data)]; - if ($chk === '>') { - $this->_inBlock = true; - $this->_blockEnd = ' '; - $parent->data[key($parent->data)] = - str_replace('>','',$parent->data[key($parent->data)]); - $parent->data[key($parent->data)] .= trim($line).' '; - $this->_allNodes[$node->parent]->children = false; - $this->_lastIndent = $node->indent; - } elseif ($chk === '|') { - $this->_inBlock = true; - $this->_blockEnd = "\n"; - $parent->data[key($parent->data)] = - str_replace('|','',$parent->data[key($parent->data)]); - $parent->data[key($parent->data)] .= trim($line)."\n"; - $this->_allNodes[$node->parent]->children = false; - $this->_lastIndent = $node->indent; - } - } - } - } elseif ($this->_lastIndent > $node->indent) { - // Any block we had going is dead now - if ($this->_inBlock === true) { - $this->_inBlock = false; - if ($this->_blockEnd = "\n") { - $last =& $this->_allNodes[$this->_lastNode]; - $last->data[key($last->data)] = - trim($last->data[key($last->data)]); - } - } - - // We don't know the parent of the node so we have to find it - // foreach ($this->_allNodes as $n) { - foreach ($this->_indentSort[$node->indent] as $n) { - if ($n->indent == $node->indent) { - $node->parent = $n->parent; - } - } - } - - if ($this->_inBlock === false) { - // Set these properties with information from our current node - $this->_lastIndent = $node->indent; - // Set the last node - $this->_lastNode = $node->id; - // Parse the YAML line and return its data - $node->data = $this->_parseLine($line); - // Add the node to the master list - $this->_allNodes[$node->id] = $node; - // Add a reference to the node in an indent array - $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id]; - // Add a reference to the node in a References array if this node - // has a YAML reference in it. - if ( - ( (is_array($node->data)) && - isset($node->data[key($node->data)]) && - (!is_array($node->data[key($node->data)])) ) - && - ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)])) - || - (preg_match('/^\*([^ ]+)/',$node->data[key($node->data)])) ) - ) { - $this->_haveRefs[] =& $this->_allNodes[$node->id]; - } elseif ( - ( (is_array($node->data)) && - isset($node->data[key($node->data)]) && - (is_array($node->data[key($node->data)])) ) - ) { - // Incomplete reference making code. Ugly, needs cleaned up. - foreach ($node->data[key($node->data)] as $d) { - if ( !is_array($d) && - ( (preg_match('/^&([^ ]+)/',$d)) - || - (preg_match('/^\*([^ ]+)/',$d)) ) - ) { - $this->_haveRefs[] =& $this->_allNodes[$node->id]; - } - } - } - } - } - } - unset($node); - - // Here we travel through node-space and pick out references (& and *) - $this->_linkReferences(); - - // Build the PHP array out of node-space - $trunk = $this->_buildArray(); - return $trunk; - } - - /** - * Dump PHP array to YAML - * - * The dump method, when supplied with an array, will do its best - * to convert the array into friendly YAML. Pretty simple. Feel free to - * save the returned string as tasteful.yml and pass it around. - * - * Oh, and you can decide how big the indent is and what the wordwrap - * for folding is. Pretty cool -- just pass in 'false' for either if - * you want to use the default. - * - * Indent's default is 2 spaces, wordwrap's default is 40 characters. And - * you can turn off wordwrap by passing in 0. - * - * @access public - * @return string - * @param array $array PHP array - * @param int $indent Pass in false to use the default, which is 2 - * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) - */ - function dump($array,$indent = false,$wordwrap = false) { - // Dumps to some very clean YAML. We'll have to add some more features - // and options soon. And better support for folding. - - // New features and options. - if ($indent === false or !is_numeric($indent)) { - $this->_dumpIndent = 2; - } else { - $this->_dumpIndent = $indent; - } - - if ($wordwrap === false or !is_numeric($wordwrap)) { - $this->_dumpWordWrap = 40; - } else { - $this->_dumpWordWrap = $wordwrap; - } - - // New YAML document - $string = "---\n"; - - // Start at the base of the array and move through it. - foreach ($array as $key => $value) { - $string .= $this->_yamlize($key,$value,0); - } - return $string; - } - - /**** Private Properties ****/ - - /**#@+ - * @access private - * @var mixed - */ - var $_haveRefs; - var $_allNodes; - var $_lastIndent; - var $_lastNode; - var $_inBlock; - var $_isInline; - var $_dumpIndent; - var $_dumpWordWrap; - /**#@-*/ - - /**** Private Methods ****/ - - /** - * Attempts to convert a key / value array item to YAML - * @access private - * @return string - * @param $key The name of the key - * @param $value The value of the item - * @param $indent The indent of the current node - */ - function _yamlize($key,$value,$indent) { - if (is_array($value)) { - // It has children. What to do? - // Make it the right kind of item - $string = $this->_dumpNode($key,NULL,$indent); - // Add the indent - $indent += $this->_dumpIndent; - // Yamlize the array - $string .= $this->_yamlizeArray($value,$indent); - } elseif (!is_array($value)) { - // It doesn't have children. Yip. - $string = $this->_dumpNode($key,$value,$indent); - } - return $string; - } - - /** - * Attempts to convert an array to YAML - * @access private - * @return string - * @param $array The array you want to convert - * @param $indent The indent of the current level - */ - function _yamlizeArray($array,$indent) { - if (is_array($array)) { - $string = ''; - foreach ($array as $key => $value) { - $string .= $this->_yamlize($key,$value,$indent); - } - return $string; - } else { - 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: - # [ - ? , [ ] { } ! * & | > ' " % @ ` ] - # or is a number or contains newlines - return (bool)(gettype($value) == "string" && - (is_numeric($value) || - strpos($value, "\n") || - preg_match("/[#:]/", $value) || - preg_match("/^[-?,[\]{}!*&|>'\"%@`]/", $value))); - - } - - /** - * Returns YAML from a key and a value - * @access private - * @return string - * @param $key The name of the key - * @param $value The value of the item - * @param $indent The indent of the current node - */ - function _dumpNode($key,$value,$indent) { - // do some folding here, for blocks - if ($this->_needLiteral($value)) { - $value = $this->_doLiteralBlock($value,$indent); - } else { - $value = $this->_doFolding($value,$indent); - } - - $spaces = str_repeat(' ',$indent); - - if (is_int($key)) { - // It's a sequence - if ($value) - $string = $spaces.'- '.$value."\n"; - else - $string = $spaces . "-\n"; - } else { - // It's mapped - if ($value) - $string = $spaces.$key.': '.$value."\n"; - else - $string = $spaces . $key . ":\n"; - } - return $string; - } - - /** - * Creates a literal block for dumping - * @access private - * @return string - * @param $value - * @param $indent int The value of the indent - */ - function _doLiteralBlock($value,$indent) { - $exploded = explode("\n",$value); - $newValue = '|'; - $indent += $this->_dumpIndent; - $spaces = str_repeat(' ',$indent); - foreach ($exploded as $line) { - $newValue .= "\n" . $spaces . trim($line); - } - return $newValue; - } - - /** - * Folds a string of text, if necessary - * @access private - * @return string - * @param $value The string you wish to fold - */ - function _doFolding($value,$indent) { - // Don't do anything if wordwrap is set to 0 - if ($this->_dumpWordWrap === 0) { - return $value; - } - - if (strlen($value) > $this->_dumpWordWrap) { - $indent += $this->_dumpIndent; - $indent = str_repeat(' ',$indent); - $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); - $value = ">\n".$indent.$wrapped; - } - return $value; - } - - /* Methods used in loading */ - - /** - * Finds and returns the indentation of a YAML line - * @access private - * @return int - * @param string $line A line from the YAML file - */ - function _getIndent($line) { - $match = array(); - preg_match('/^\s{1,}/',$line,$match); - if (!empty($match[0])) { - $indent = substr_count($match[0],' '); - } else { - $indent = 0; - } - return $indent; - } - - /** - * Parses YAML code and returns an array for a node - * @access private - * @return array - * @param string $line A line from the YAML file - */ - function _parseLine($line) { - $line = trim($line); - - $array = array(); - - if (preg_match('/^-(.*):$/',$line)) { - // It's a mapped sequence - $key = trim(substr(substr($line,1),0,-1)); - $array[$key] = ''; - } elseif ($line[0] == '-' && substr($line,0,3) != '---') { - // It's a list item but not a new stream - if (strlen($line) > 1) { - $value = trim(substr($line,1)); - // Set the type of the value. Int, string, etc - $value = $this->_toType($value); - $array[] = $value; - } else { - $array[] = array(); - } - } elseif (preg_match('/^(.+):/',$line,$key)) { - // It's a key/value pair most likely - // If the key is in double quotes pull it out - $matches = array(); - if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { - $value = trim(str_replace($matches[1],'',$line)); - $key = $matches[2]; - } else { - // Do some guesswork as to the key and the value - $explode = explode(':',$line); - $key = trim($explode[0]); - array_shift($explode); - $value = trim(implode(':',$explode)); - } - - // Set the type of the value. Int, string, etc - $value = $this->_toType($value); - if (empty($key)) { - $array[] = $value; - } else { - $array[$key] = $value; - } - } - return $array; - } - - /** - * Finds the type of the passed value, returns the value as the new type. - * @access private - * @param string $value - * @return mixed - */ - function _toType($value) { - $matches = array(); - if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) { - $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches)); - $value = preg_replace('/\\\\"/','"',$value); - } elseif (preg_match('/^\\[(.+)\\]$/',$value,$matches)) { - // Inline Sequence - - // Take out strings sequences and mappings - $explode = $this->_inlineEscape($matches[1]); - - // Propogate value array - $value = array(); - foreach ($explode as $v) { - $value[] = $this->_toType($v); - } - } elseif (strpos($value,': ')!==false && !preg_match('/^{(.+)/',$value)) { - // It's a map - $array = explode(': ',$value); - $key = trim($array[0]); - array_shift($array); - $value = trim(implode(': ',$array)); - $value = $this->_toType($value); - $value = array($key => $value); - } elseif (preg_match("/{(.+)}$/",$value,$matches)) { - // Inline Mapping - - // Take out strings sequences and mappings - $explode = $this->_inlineEscape($matches[1]); - - // Propogate value array - $array = array(); - foreach ($explode as $v) { - $array = $array + $this->_toType($v); - } - $value = $array; - } elseif (strtolower($value) == 'null' or $value == '' or $value == '~') { - $value = NULL; - } elseif (ctype_digit($value)) { - $value = (int)$value; - } elseif (in_array(strtolower($value), - array('true', 'on', '+', 'yes', 'y'))) { - $value = TRUE; - } elseif (in_array(strtolower($value), - array('false', 'off', '-', 'no', 'n'))) { - $value = FALSE; - } elseif (is_numeric($value)) { - $value = (float)$value; - } else { - // Just a normal string, right? - $value = trim(preg_replace('/#(.+)$/','',$value)); - } - - return $value; - } - - /** - * Used in inlines to check for more inlines or quoted strings - * @access private - * @return array - */ - function _inlineEscape($inline) { - // There's gotta be a cleaner way to do this... - // While pure sequences seem to be nesting just fine, - // pure mappings and mappings with sequences inside can't go very - // deep. This needs to be fixed. - - // Check for strings - $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; - $strings = array(); - if (preg_match_all($regex,$inline,$strings)) { - $saved_strings[] = $strings[0][0]; - $inline = preg_replace($regex,'YAMLString',$inline); - } - unset($regex); - - // Check for sequences - $seqs = array(); - if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) { - $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline); - $seqs = $seqs[0]; - } - - // Check for mappings - $maps = array(); - if (preg_match_all('/{(.+)}/U',$inline,$maps)) { - $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline); - $maps = $maps[0]; - } - - $explode = explode(', ',$inline); - - // Re-add the strings - if (!empty($saved_strings)) { - $i = 0; - foreach ($explode as $key => $value) { - if (strpos($value,'YAMLString')) { - $explode[$key] = str_replace('YAMLString',$saved_strings[$i],$value); - ++$i; - } - } - } - - // Re-add the sequences - if (!empty($seqs)) { - $i = 0; - foreach ($explode as $key => $value) { - if (strpos($value,'YAMLSeq') !== false) { - $explode[$key] = str_replace('YAMLSeq',$seqs[$i],$value); - ++$i; - } - } - } - - // Re-add the mappings - if (!empty($maps)) { - $i = 0; - foreach ($explode as $key => $value) { - if (strpos($value,'YAMLMap') !== false) { - $explode[$key] = str_replace('YAMLMap',$maps[$i],$value); - ++$i; - } - } - } - - return $explode; - } - - /** - * Builds the PHP array from all the YAML nodes we've gathered - * @access private - * @return array - */ - function _buildArray() { - $trunk = array(); - - if (!isset($this->_indentSort[0])) { - return $trunk; - } - - foreach ($this->_indentSort[0] as $n) { - if (empty($n->parent)) { - $this->_nodeArrayizeData($n); - // Check for references and copy the needed data to complete them. - $this->_makeReferences($n); - // Merge our data with the big array we're building - $trunk = $this->_array_kmerge($trunk,$n->data); - } - } - - return $trunk; - } - - /** - * Traverses node-space and sets references (& and *) accordingly - * @access private - * @return bool - */ - function _linkReferences() { - if (is_array($this->_haveRefs)) { - foreach ($this->_haveRefs as $node) { - if (!empty($node->data)) { - $key = key($node->data); - // If it's an array, don't check. - if (is_array($node->data[$key])) { - foreach ($node->data[$key] as $k => $v) { - $this->_linkRef($node,$key,$k,$v); - } - } else { - $this->_linkRef($node,$key); - } - } - } - } - return true; - } - - function _linkRef(&$n,$key,$k = NULL,$v = NULL) { - if (empty($k) && empty($v)) { - // Look for &refs - $matches = array(); - if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) { - // Flag the node so we know it's a reference - $this->_allNodes[$n->id]->ref = substr($matches[0],1); - $this->_allNodes[$n->id]->data[$key] = - substr($n->data[$key],strlen($matches[0])+1); - // Look for *refs - } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) { - $ref = substr($matches[0],1); - // Flag the node as having a reference - $this->_allNodes[$n->id]->refKey = $ref; - } - } elseif (!empty($k) && !empty($v)) { - if (preg_match('/^&([^ ]+)/',$v,$matches)) { - // Flag the node so we know it's a reference - $this->_allNodes[$n->id]->ref = substr($matches[0],1); - $this->_allNodes[$n->id]->data[$key][$k] = - substr($v,strlen($matches[0])+1); - // Look for *refs - } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) { - $ref = substr($matches[0],1); - // Flag the node as having a reference - $this->_allNodes[$n->id]->refKey = $ref; - } - } - } - - /** - * Finds the children of a node and aids in the building of the PHP array - * @access private - * @param int $nid The id of the node whose children we're gathering - * @return array - */ - function _gatherChildren($nid) { - $return = array(); - $node =& $this->_allNodes[$nid]; - foreach ($this->_allNodes as $z) { - if ($z->parent == $node->id) { - // We found a child - $this->_nodeArrayizeData($z); - // Check for references - $this->_makeReferences($z); - // Merge with the big array we're returning - // The big array being all the data of the children of our parent node - $return = $this->_array_kmerge($return,$z->data); - } - } - return $return; - } - - /** - * Turns a node's data and its children's data into a PHP array - * - * @access private - * @param array $node The node which you want to arrayize - * @return boolean - */ - function _nodeArrayizeData(&$node) { - if (is_array($node->data) && $node->children == true) { - // This node has children, so we need to find them - $childs = $this->_gatherChildren($node->id); - // We've gathered all our children's data and are ready to use it - $key = key($node->data); - $key = empty($key) ? 0 : $key; - // If it's an array, add to it of course - if (is_array($node->data[$key])) { - $node->data[$key] = $this->_array_kmerge($node->data[$key],$childs); - } else { - $node->data[$key] = $childs; - } - } elseif (!is_array($node->data) && $node->children == true) { - // Same as above, find the children of this node - $childs = $this->_gatherChildren($node->id); - $node->data = array(); - $node->data[] = $childs; - } - - // We edited $node by reference, so just return true - return true; - } - - /** - * Traverses node-space and copies references to / from this object. - * @access private - * @param object $z A node whose references we wish to make real - * @return bool - */ - function _makeReferences(&$z) { - // It is a reference - if (isset($z->ref)) { - $key = key($z->data); - // Copy the data to this object for easy retrieval later - $this->ref[$z->ref] =& $z->data[$key]; - // It has a reference - } elseif (isset($z->refKey)) { - if (isset($this->ref[$z->refKey])) { - $key = key($z->data); - // Copy the data from this object to make the node a real reference - $z->data[$key] =& $this->ref[$z->refKey]; - } - } - return true; - } - - - /** - * Merges arrays and maintains numeric keys. - * - * An ever-so-slightly modified version of the array_kmerge() function posted - * to php.net by mail at nospam dot iaindooley dot com on 2004-04-08. - * - * http://www.php.net/manual/en/function.array-merge.php#41394 - * - * @access private - * @param array $arr1 - * @param array $arr2 - * @return array - */ - function _array_kmerge($arr1,$arr2) { - if(!is_array($arr1)) - $arr1 = array(); - - if(!is_array($arr2)) - $arr2 = array(); - - $keys1 = array_keys($arr1); - $keys2 = array_keys($arr2); - $keys = array_merge($keys1,$keys2); - $vals1 = array_values($arr1); - $vals2 = array_values($arr2); - $vals = array_merge($vals1,$vals2); - $ret = array(); - - foreach($keys as $key) { - list( /* unused */ ,$val) = each($vals); - // This is the good part! If a key already exists, but it's part of a - // sequence (an int), just keep addin numbers until we find a fresh one. - if (isset($ret[$key]) and is_int($key)) { - while (array_key_exists($key, $ret)) { - $key++; - } - } - $ret[$key] = $val; - } - - return $ret; - } - } +/** + * Spyc -- A Simple PHP YAML Class + * @version 0.2.3 -- 2006-02-04 + * @author Chris Wanstrath <chris@ozmm.org> + * @see http://spyc.sourceforge.net/ + * @copyright Copyright 2005-2006 Chris Wanstrath + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * @ingroup API + */ +class Spyc { + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @return string + * @param $array Array: PHP array + * @param $indent Integer: Pass in false to use the default, which is 2 + * @param $wordwrap Integer: Pass in 0 for no wordwrap, false for default (40) + */ + public static function YAMLDump($array,$indent = false,$wordwrap = false) { + $spyc = new Spyc; + return $spyc->dump($array,$indent,$wordwrap); + } + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @public + * @return string + * @param $array Array: PHP array + * @param $indent Integer: Pass in false to use the default, which is 2 + * @param $wordwrap Integer: Pass in 0 for no wordwrap, false for default (40) + */ + function dump($array,$indent = false,$wordwrap = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = "---\n"; + + // Start at the base of the array and move through it. + foreach ($array as $key => $value) { + $string .= $this->_yamlize($key,$value,0); + } + return $string; + } + + /**** Private Properties ****/ + + private $_haveRefs; + private $_allNodes; + private $_lastIndent; + private $_lastNode; + private $_inBlock; + private $_isInline; + private $_dumpIndent; + private $_dumpWordWrap; + + /**** Private Methods ****/ + + /** + * Attempts to convert a key / value array item to YAML + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _yamlize($key,$value,$indent) { + if (is_array($value)) { + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key,NULL,$indent); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key,$value,$indent); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + private function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + foreach ($array as $key => $value) { + $string .= $this->_yamlize($key,$value,$indent); + } + return $string; + } else { + 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: + # [ - ? , [ ] { } ! * & | > ' " % @ ` ] + # or is a number or contains newlines + return (bool)(gettype($value) == "string" && + (is_numeric($value) || + strpos($value, "\n") || + preg_match("/[#:]/", $value) || + preg_match("/^[-?,[\]{}!*&|>'\"%@`]/", $value))); + + } + + /** + * Returns YAML from a key and a value + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _dumpNode($key,$value,$indent) { + // do some folding here, for blocks + if ($this->_needLiteral($value)) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + $spaces = str_repeat(' ',$indent); + + if (is_int($key)) { + // It's a sequence + if ($value !== '' && !is_null($value)) + $string = $spaces.'- '.$value."\n"; + else + $string = $spaces . "-\n"; + } else { + // It's mapped + if ($value !== '' && !is_null($value)) + $string = $spaces . $key . ': ' . $value . "\n"; + else + $string = $spaces . $key . ":\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @return string + * @param $value + * @param $indent int The value of the indent + */ + private function _doLiteralBlock($value,$indent) { + $exploded = explode("\n",$value); + $newValue = '|'; + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $newValue .= "\n" . $spaces . trim($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @return string + * @param $value The string you wish to fold + */ + private function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + if ($this->_dumpWordWrap === 0) { + return $value; + } + + if (strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } + return $value; + } +} diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index a45390c4..43b30f7c 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -36,23 +36,6 @@ if (!defined('MEDIAWIKI')) { */ 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 = 5; - - /** - * The factor by which the wait-time in between authentication - * attempts is increased every failed attempt. - */ - const THROTTLE_FACTOR = 2; - - /** - * The maximum number of failed logins after which the wait increase stops. - */ - const THOTTLE_MAX_COUNT = 10; - public function __construct($main, $action) { parent :: __construct($main, $action, 'lg'); } @@ -61,7 +44,7 @@ class ApiLogin extends ApiBase { * Executes the log-in attempt using the parameters passed. If * the log-in succeeeds, it attaches a cookie to the session * and outputs the user id, username, and session token. If a - * log-in fails, as the result of a bad password, a nonexistant + * log-in fails, as the result of a bad password, a nonexistent * user, or any other reason, the host is cached with an expiry * and no log-in attempts will be accepted until that expiry * is reached. The expiry is $this->mLoginThrottle. @@ -69,25 +52,14 @@ class ApiLogin extends ApiBase { * @access public */ public function execute() { - $name = $password = $domain = null; - extract($this->extractRequestParams()); + $params = $this->extractRequestParams(); $result = array (); - // Make sure noone is trying to guess the password brut-force - $nextLoginIn = $this->getNextLoginTimeout(); - if ($nextLoginIn > 0) { - $result['result'] = 'NeedToWait'; - $result['details'] = "Please wait $nextLoginIn seconds before next log-in attempt"; - $result['wait'] = $nextLoginIn; - $this->getResult()->addValue(null, 'login', $result); - return; - } - - $params = new FauxRequest(array ( - 'wpName' => $name, - 'wpPassword' => $password, - 'wpDomain' => $domain, + $req = new FauxRequest(array ( + 'wpName' => $params['name'], + 'wpPassword' => $params['password'], + 'wpDomain' => $params['domain'], 'wpRemember' => '' )); @@ -96,8 +68,8 @@ class ApiLogin extends ApiBase { wfSetupSession(); } - $loginForm = new LoginForm($params); - switch ($loginForm->authenticateUserData()) { + $loginForm = new LoginForm($req); + switch ($authRes = $loginForm->authenticateUserData()) { case LoginForm :: SUCCESS : global $wgUser, $wgCookiePrefix; @@ -139,95 +111,18 @@ class ApiLogin extends ApiBase { $result['result'] = 'CreateBlocked'; $result['details'] = 'Your IP address is blocked from account creation'; break; + case LoginForm :: THROTTLED : + global $wgPasswordAttemptThrottle; + $result['result'] = 'Throttled'; + $result['wait'] = $wgPasswordAttemptThrottle['seconds']; + break; default : - ApiBase :: dieDebug(__METHOD__, 'Unhandled case value'); - } - - if ($result['result'] != 'Success' && !isset( $result['details'] ) ) { - $delay = $this->cacheBadLogin(); - $result['wait'] = $delay; - $result['details'] = "Please wait " . $delay . " seconds before next log-in attempt"; + ApiBase :: dieDebug(__METHOD__, "Unhandled case value: {$authRes}"); } - // if we were allowed to try to login, memcache is fine $this->getResult()->addValue(null, 'login', $result); } - - /** - * Caches a bad-login attempt associated with the host and with an - * expiry of $this->mLoginThrottle. These are cached by a key - * separate from that used by the captcha system--as such, logging - * in through the standard interface will get you a legal session - * and cookies to prove it, but will not remove this entry. - * - * Returns the number of seconds until next login attempt will be allowed. - * - * @access private - */ - private function cacheBadLogin() { - global $wgMemc; - - $key = $this->getMemCacheKey(); - $val = $wgMemc->get( $key ); - - $val['lastReqTime'] = time(); - if (!isset($val['count'])) { - $val['count'] = 1; - } else { - $val['count'] = 1 + $val['count']; - } - - $delay = ApiLogin::calculateDelay($val['count']); - - $wgMemc->delete($key); - // Cache expiration should be the maximum timeout - to prevent a "try and wait" attack - $wgMemc->add( $key, $val, ApiLogin::calculateDelay(ApiLogin::THOTTLE_MAX_COUNT) ); - - return $delay; - } - - /** - * How much time the client must wait before it will be - * allowed to try to log-in next. - * The return value is 0 if no wait is required. - */ - private function getNextLoginTimeout() { - global $wgMemc; - - $val = $wgMemc->get($this->getMemCacheKey()); - - $elapse = (time() - $val['lastReqTime']); // in seconds - $canRetryIn = ApiLogin::calculateDelay($val['count']) - $elapse; - - return $canRetryIn < 0 ? 0 : $canRetryIn; - } - - /** - * Based on the number of previously attempted logins, returns - * the delay (in seconds) when the next login attempt will be allowed. - */ - private static function calculateDelay($count) { - // Defensive programming - $count = intval($count); - $count = $count < 1 ? 1 : $count; - $count = $count > self::THOTTLE_MAX_COUNT ? self::THOTTLE_MAX_COUNT : $count; - - return self::THROTTLE_TIME + self::THROTTLE_TIME * ($count - 1) * self::THROTTLE_FACTOR; - } - - /** - * Internal cache key for badlogin checks. Robbed from the - * ConfirmEdit extension and modified to use a key unique to the - * API login.3 - * - * @return string - * @access private - */ - private function getMemCacheKey() { - return wfMemcKey( 'apilogin', 'badlogin', 'ip', wfGetIP() ); - } - public function mustBePosted() { return true; } public function getAllowedParams() { @@ -263,6 +158,6 @@ class ApiLogin extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogin.php 35565 2008-05-29 19:23:37Z btongminh $'; + return __CLASS__ . ': $Id: ApiLogin.php 45275 2009-01-01 02:02:03Z simetrical $'; } } diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php index 694c9e3c..8b178f6a 100644 --- a/includes/api/ApiLogout.php +++ b/includes/api/ApiLogout.php @@ -42,11 +42,12 @@ class ApiLogout extends ApiBase { public function execute() { global $wgUser; + $oldName = $wgUser->getName(); $wgUser->logout(); // Give extensions to do something after user logout $injected_html = ''; - wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html) ); + wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName) ); } public function getAllowedParams() { @@ -70,6 +71,6 @@ class ApiLogout extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogout.php 35294 2008-05-24 20:44:49Z btongminh $'; + return __CLASS__ . ': $Id: ApiLogout.php 43522 2008-11-15 01:23:39Z brion $'; } } diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 2d0e278c..60d932be 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -65,6 +65,7 @@ class ApiMain extends ApiBase { 'feedwatchlist' => 'ApiFeedWatchlist', 'help' => 'ApiHelp', 'paraminfo' => 'ApiParamInfo', + 'purge' => 'ApiPurge', ); private static $WriteModules = array ( @@ -77,6 +78,8 @@ class ApiMain extends ApiBase { 'move' => 'ApiMove', 'edit' => 'ApiEditPage', 'emailuser' => 'ApiEmailUser', + 'watch' => 'ApiWatch', + 'patrol' => 'ApiPatrol', ); /** @@ -99,6 +102,23 @@ class ApiMain extends ApiBase { 'dbg' => 'ApiFormatDbg', 'dbgfm' => 'ApiFormatDbg' ); + + /** + * List of user roles that are specifically relevant to the API. + * array( 'right' => array ( 'msg' => 'Some message with a $1', + * 'params' => array ( $someVarToSubst ) ), + * ); + */ + private static $mRights = array('writeapi' => array( + 'msg' => 'Use of the write API', + 'params' => array() + ), + 'apihighlimits' => array( + 'msg' => 'Use higher limits in API queries (Slow queries: $1 results; Fast queries: $2 results). The limits for slow queries also apply to multivalue parameters.', + 'params' => array (ApiMain::LIMIT_SML2, ApiMain::LIMIT_BIG2) + ) + ); + private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames; private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest, $mInternalMode, $mSquidMaxage; @@ -144,9 +164,9 @@ class ApiMain extends ApiBase { if($wgEnableWriteAPI) $this->mModules += self::$WriteModules; - $this->mModuleNames = array_keys($this->mModules); // todo: optimize + $this->mModuleNames = array_keys($this->mModules); $this->mFormats = self :: $Formats; - $this->mFormatNames = array_keys($this->mFormats); // todo: optimize + $this->mFormatNames = array_keys($this->mFormats); $this->mResult = new ApiResult($this); $this->mShowVersions = false; @@ -193,6 +213,8 @@ class ApiMain extends ApiBase { if (!$wgUser->isAllowed('writeapi')) $this->dieUsage('You\'re not allowed to edit this ' . 'wiki through the API', 'writeapidenied'); + if (wfReadOnly()) + $this->dieUsageMsg(array('readonlytext')); } /** @@ -206,6 +228,8 @@ class ApiMain extends ApiBase { * Create an instance of an output formatter by its name */ public function createPrinterByName($format) { + if( !isset( $this->mFormats[$format] ) ) + $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' ); return new $this->mFormats[$format] ($this, $format); } @@ -235,6 +259,11 @@ class ApiMain extends ApiBase { try { $this->executeAction(); } catch (Exception $e) { + // Log it + if ( $e instanceof MWException ) { + wfDebugLog( 'exception', $e->getLogMessage() ); + } + // // Handle any kind of exception by outputing properly formatted error message. // If this fails, an unhandled exception should be thrown so that global error @@ -248,7 +277,7 @@ class ApiMain extends ApiBase { $headerStr = 'MediaWiki-API-Error: ' . $errCode; if ($e->getCode() === 0) - header($headerStr, true); + header($headerStr); else header($headerStr, true, $e->getCode()); @@ -260,12 +289,11 @@ class ApiMain extends ApiBase { $this->printResult(true); } - global $wgRequest; if($this->mSquidMaxage == -1) { # Nobody called setCacheMaxAge(), use the (s)maxage parameters - $smaxage = $wgRequest->getVal('smaxage', 0); - $maxage = $wgRequest->getVal('maxage', 0); + $smaxage = $this->getParameter('smaxage'); + $maxage = $this->getParameter('maxage'); } else $smaxage = $maxage = $this->mSquidMaxage; @@ -332,6 +360,9 @@ class ApiMain extends ApiBase { } $this->getResult()->reset(); + // Re-add the id + if($this->mRequest->getCheck('requestid')) + $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid')); $this->getResult()->addValue(null, 'error', $errMessage); return $errMessage['code']; @@ -341,12 +372,19 @@ class ApiMain extends ApiBase { * Execute the actual module, without any error handling */ protected function executeAction() { + // First add the id to the top element + if($this->mRequest->getCheck('requestid')) + $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid')); $params = $this->extractRequestParams(); $this->mShowVersions = $params['version']; $this->mAction = $params['action']; + if( !is_string( $this->mAction ) ) { + $this->dieUsage( "The API requires a valid action parameter", 'unknown_action' ); + } + // Instantiate the module requested by the user $module = new $this->mModules[$this->mAction] ($this, $this->mAction); @@ -356,6 +394,9 @@ class ApiMain extends ApiBase { $maxLag = $params['maxlag']; list( $host, $lag ) = wfGetLB()->getMaxLag(); if ( $lag > $maxLag ) { + header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); + header( 'X-Database-Lag: ' . intval( $lag ) ); + // XXX: should we return a 503 HTTP error code like wfMaxlagError() does? if( $wgShowHostnames ) { ApiBase :: dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); } else { @@ -384,6 +425,7 @@ class ApiMain extends ApiBase { // Execute $module->profileIn(); $module->execute(); + wfRunHooks('APIAfterExecute', array(&$module)); $module->profileOut(); if (!$this->mInternalMode) { @@ -396,6 +438,7 @@ class ApiMain extends ApiBase { * Print results using the current printer */ protected function printResult($isError) { + $this->getResult()->cleanupUTF8(); $printer = $this->mPrinter; $printer->profileIn(); @@ -437,6 +480,7 @@ class ApiMain extends ApiBase { ApiBase :: PARAM_TYPE => 'integer', ApiBase :: PARAM_DFLT => 0 ), + 'requestid' => null, ); } @@ -451,6 +495,7 @@ class ApiMain extends ApiBase { 'maxlag' => 'Maximum lag', 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached', 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached', + 'requestid' => 'Request ID to distinguish requests. This will just be output back to you', ); } @@ -493,6 +538,7 @@ class ApiMain extends ApiBase { 'API developers:', ' Roan Kattouw <Firstname>.<Lastname>@home.nl (lead developer Sep 2007-present)', ' Victor Vasiliev - vasilvv at gee mail dot com', + ' Bryan Tong Minh - bryan . tongminh @ gmail . com', ' Yuri Astrakhan <Firstname><Lastname>@gmail.com (creator, lead developer Sep 2006-Sep 2007)', '', 'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org', @@ -521,6 +567,14 @@ class ApiMain extends ApiBase { $msg .= "\n"; } + $msg .= "\n$astriks Permissions $astriks\n\n"; + foreach ( self :: $mRights as $right => $rightMsg ) { + $groups = User::getGroupsWithPermission( $right ); + $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) . + "\nGranted to:\n " . str_replace( "*", "all", implode( ", ", $groups ) ) . "\n"; + + } + $msg .= "\n$astriks Formats $astriks\n\n"; foreach( $this->mFormats as $formatName => $unused ) { $module = $this->createPrinterByName($formatName); @@ -539,7 +593,7 @@ class ApiMain extends ApiBase { public static function makeHelpMsgHeader($module, $paramName) { $modulePrefix = $module->getModulePrefix(); - if (!empty($modulePrefix)) + if (strval($modulePrefix) !== '') $modulePrefix = "($modulePrefix) "; return "* $paramName={$module->getModuleName()} $modulePrefix*"; @@ -602,8 +656,8 @@ class ApiMain extends ApiBase { */ public function getVersion() { $vers = array (); - $vers[] = 'MediaWiki ' . SpecialVersion::getVersion(); - $vers[] = __CLASS__ . ': $Id: ApiMain.php 44569 2008-12-14 08:31:04Z tstarling $'; + $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/"; + $vers[] = __CLASS__ . ': $Id: ApiMain.php 45752 2009-01-14 21:36:57Z catrope $'; $vers[] = ApiBase :: getBaseVersion(); $vers[] = ApiFormatBase :: getBaseVersion(); $vers[] = ApiQueryBase :: getBaseVersion(); diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php index 8687bdcd..13b058c9 100644 --- a/includes/api/ApiMove.php +++ b/includes/api/ApiMove.php @@ -44,9 +44,7 @@ class ApiMove extends ApiBase { if(is_null($params['reason'])) $params['reason'] = ''; - $titleObj = NULL; - if(!isset($params['from'])) - $this->dieUsageMsg(array('missingparam', 'from')); + $this->requireOnlyOneParameter($params, 'from', 'fromid'); if(!isset($params['to'])) $this->dieUsageMsg(array('missingparam', 'to')); if(!isset($params['token'])) @@ -54,9 +52,18 @@ class ApiMove extends ApiBase { if(!$wgUser->matchEditToken($params['token'])) $this->dieUsageMsg(array('sessionfailure')); - $fromTitle = Title::newFromText($params['from']); - if(!$fromTitle) - $this->dieUsageMsg(array('invalidtitle', $params['from'])); + if(isset($params['from'])) + { + $fromTitle = Title::newFromText($params['from']); + if(!$fromTitle) + $this->dieUsageMsg(array('invalidtitle', $params['from'])); + } + else if(isset($params['fromid'])) + { + $fromTitle = Title::newFromID($params['fromid']); + if(!$fromTitle) + $this->dieUsageMsg(array('nosuchpageid', $params['fromid'])); + } if(!$fromTitle->exists()) $this->dieUsageMsg(array('notanarticle')); $fromTalk = $fromTitle->getTalkPage(); @@ -66,27 +73,10 @@ class ApiMove extends ApiBase { $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)); - $hookErr = null; - $retval = $fromTitle->moveTo($toTitle, true, $params['reason'], !$params['noredirect']); if($retval !== true) - { - # FIXME: Title::moveTo() sometimes returns a string $this->dieUsageMsg(reset($retval)); - } $r = array('from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason']); if(!$params['noredirect'] || !$wgUser->isAllowed('suppressredirect')) @@ -105,8 +95,9 @@ class ApiMove extends ApiBase { // 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']; + $parsed = $this->parseMsg(reset($retval)); + $r['talkmove-error-code'] = $parsed['code']; + $r['talkmove-error-info'] = $parsed['info']; } } @@ -129,6 +120,9 @@ class ApiMove extends ApiBase { public function getAllowedParams() { return array ( 'from' => null, + 'fromid' => array( + ApiBase::PARAM_TYPE => 'integer' + ), 'to' => null, 'token' => null, 'reason' => null, @@ -141,7 +135,8 @@ class ApiMove extends ApiBase { public function getParamDescription() { return array ( - 'from' => 'Title of the page you want to move.', + 'from' => 'Title of the page you want to move. Cannot be used together with fromid.', + 'fromid' => 'Page ID of the page you want to move. Cannot be used together with from.', 'to' => 'Title you want to rename the page to.', 'token' => 'A move token previously retrieved through prop=info', 'reason' => 'Reason for the move (optional).', @@ -154,7 +149,7 @@ class ApiMove extends ApiBase { public function getDescription() { return array( - 'Moves a page.' + 'Move a page.' ); } @@ -165,6 +160,6 @@ class ApiMove extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiMove.php 35619 2008-05-30 19:59:47Z btongminh $'; + return __CLASS__ . ': $Id: ApiMove.php 47041 2009-02-09 14:39:41Z catrope $'; } } diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index e09cb285..54482e4b 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -53,7 +53,7 @@ class ApiPageSet extends ApiQueryBase { private $mRequestedPageFields; public function __construct($query, $resolveRedirects = false) { - parent :: __construct($query, __CLASS__); + parent :: __construct($query, 'query'); $this->mAllPages = array (); $this->mTitles = array(); @@ -92,10 +92,11 @@ class ApiPageSet extends ApiQueryBase { */ public function getPageTableFields() { // Ensure we get minimum required fields + // DON'T change this order $pageFlds = array ( - 'page_id' => null, 'page_namespace' => null, - 'page_title' => null + 'page_title' => null, + 'page_id' => null, ); // only store non-default fields @@ -227,19 +228,18 @@ class ApiPageSet extends ApiQueryBase { */ public function execute() { $this->profileIn(); - $titles = $pageids = $revids = null; - extract($this->extractRequestParams()); + $params = $this->extractRequestParams(); // Only one of the titles/pageids/revids is allowed at the same time $dataSource = null; - if (isset ($titles)) + if (isset ($params['titles'])) $dataSource = 'titles'; - if (isset ($pageids)) { + if (isset ($params['pageids'])) { if (isset ($dataSource)) $this->dieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource'); $dataSource = 'pageids'; } - if (isset ($revids)) { + if (isset ($params['revids'])) { if (isset ($dataSource)) $this->dieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource'); $dataSource = 'revids'; @@ -247,15 +247,17 @@ class ApiPageSet extends ApiQueryBase { switch ($dataSource) { case 'titles' : - $this->initFromTitles($titles); + $this->initFromTitles($params['titles']); break; case 'pageids' : - $this->initFromPageIds($pageids); + $this->initFromPageIds($params['pageids']); break; case 'revids' : if($this->mResolveRedirects) - $this->dieUsage('revids may not be used with redirect resolution', 'params'); - $this->initFromRevIDs($revids); + $this->setWarning('Redirect resolution cannot be used together with the revids= parameter. '. + 'Any redirects the revids= point to have not been resolved.'); + $this->mResolveRedirects = false; + $this->initFromRevIDs($params['revids']); break; default : // Do nothing - some queries do not need any of the data sources. @@ -366,7 +368,7 @@ class ApiPageSet extends ApiQueryBase { } private function initFromPageIds($pageids) { - if(empty($pageids)) + if(!count($pageids)) return; $pageids = array_map('intval', $pageids); // paranoia @@ -424,7 +426,7 @@ class ApiPageSet extends ApiQueryBase { if(isset($remaining)) { // Any items left in the $remaining list are added as missing if($processTitles) { - // The remaining titles in $remaining are non-existant pages + // The remaining titles in $remaining are non-existent pages foreach ($remaining as $ns => $dbkeys) { foreach ( $dbkeys as $dbkey => $unused ) { $title = Title :: makeTitle($ns, $dbkey); @@ -438,7 +440,7 @@ class ApiPageSet extends ApiQueryBase { else { // The remaining pageids do not exist - if(empty($this->mMissingPageIDs)) + if(!$this->mMissingPageIDs) $this->mMissingPageIDs = array_keys($remaining); else $this->mMissingPageIDs = array_merge($this->mMissingPageIDs, array_keys($remaining)); @@ -448,16 +450,16 @@ class ApiPageSet extends ApiQueryBase { private function initFromRevIDs($revids) { - if(empty($revids)) + if(!count($revids)) return; $db = $this->getDB(); $pageids = array(); $remaining = array_flip($revids); - $tables = array('revision'); + $tables = array('revision','page'); $fields = array('rev_id','rev_page'); - $where = array('rev_deleted' => 0, 'rev_id' => $revids); + $where = array('rev_deleted' => 0, 'rev_id' => $revids,'rev_page = page_id'); // Get pageIDs data from the `page` table $this->profileDBIn(); @@ -475,8 +477,6 @@ class ApiPageSet extends ApiQueryBase { $this->mMissingRevIDs = array_keys($remaining); // Populate all the page information - if($this->mResolveRedirects) - ApiBase :: dieDebug(__METHOD__, 'revids may not be used with redirect resolution'); $this->initFromPageIds(array_keys($pageids)); } @@ -488,7 +488,7 @@ class ApiPageSet extends ApiQueryBase { // Repeat until all redirects have been resolved // The infinite loop is prevented by keeping all known pages in $this->mAllPages - while (!empty ($this->mPendingRedirectIDs)) { + while ($this->mPendingRedirectIDs) { // Resolve redirects by querying the pagelinks table, and repeat the process // Create a new linkBatch object for the next pass @@ -537,7 +537,7 @@ class ApiPageSet extends ApiQueryBase { $this->mRedirectTitles[$from] = $to; } $db->freeResult($res); - if(!empty($this->mPendingRedirectIDs)) + if($this->mPendingRedirectIDs) { # We found pages that aren't in the redirect table # Add them @@ -580,16 +580,16 @@ class ApiPageSet extends ApiQueryBase { continue; // There's nothing else we can do } $iw = $titleObj->getInterwiki(); - if (!empty($iw)) { + if (strval($iw) !== '') { // This title is an interwiki link. $this->mInterwikiTitles[$titleObj->getPrefixedText()] = $iw; } else { // Validation if ($titleObj->getNamespace() < 0) - $this->dieUsage("No support for special pages has been implemented", 'unsupportednamespace'); - - $linkBatch->addObj($titleObj); + $this->setWarning("No support for special pages has been implemented"); + else + $linkBatch->addObj($titleObj); } // Make sure we remember the original title that was given to us @@ -628,6 +628,6 @@ class ApiPageSet extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPageSet.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiPageSet.php 45275 2009-01-01 02:02:03Z simetrical $'; } } diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index 77ce514f..2cf044cf 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -86,12 +86,12 @@ class ApiParamInfo extends ApiBase { $retval['classname'] = get_class($obj); $retval['description'] = (is_array($obj->getDescription()) ? implode("\n", $obj->getDescription()) : $obj->getDescription()); $retval['prefix'] = $obj->getModulePrefix(); - $allowedParams = $obj->getAllowedParams(); + $allowedParams = $obj->getFinalParams(); if(!is_array($allowedParams)) return $retval; $retval['parameters'] = array(); - $paramDesc = $obj->getParamDescription(); - foreach($obj->getAllowedParams() as $n => $p) + $paramDesc = $obj->getFinalParamDescription(); + foreach($allowedParams as $n => $p) { $a = array('name' => $n); if(!is_array($p)) @@ -111,7 +111,15 @@ class ApiParamInfo extends ApiBase { $a['default'] = $p[ApiBase::PARAM_DFLT]; if(isset($p[ApiBase::PARAM_ISMULTI])) if($p[ApiBase::PARAM_ISMULTI]) + { $a['multi'] = ''; + $a['limit'] = $this->getMain()->canApiHighLimits() ? + ApiBase::LIMIT_SML2 : + ApiBase::LIMIT_SML1; + } + if(isset($p[ApiBase::PARAM_ALLOW_DUPLICATES])) + if($p[ApiBase::PARAM_ALLOW_DUPLICATES]) + $a['allowsduplicates'] = ''; if(isset($p[ApiBase::PARAM_TYPE])) { $a['type'] = $p[ApiBase::PARAM_TYPE]; @@ -161,6 +169,6 @@ class ApiParamInfo extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiParamInfo.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiParamInfo.php 41653 2008-10-04 15:03:03Z catrope $'; } } diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index 4dcc94b6..e221fb1d 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -49,10 +49,13 @@ class ApiParse extends ApiBase { $prop = array_flip($params['prop']); $revid = false; - global $wgParser, $wgUser; + // The parser needs $wgTitle to be set, apparently the + // $title parameter in Parser::parse isn't enough *sigh* + global $wgParser, $wgUser, $wgTitle; $popts = new ParserOptions(); $popts->setTidy(true); $popts->enableLimitReport(); + $redirValues = null; if(!is_null($oldid) || !is_null($page)) { if(!is_null($oldid)) @@ -63,23 +66,42 @@ class ApiParse extends ApiBase { $this->dieUsage("There is no revision ID $oldid", 'missingrev'); if(!$rev->userCan(Revision::DELETED_TEXT)) $this->dieUsage("You don't have permission to view deleted revisions", 'permissiondenied'); - $text = $rev->getRawText(); + $text = $rev->getText( Revision::FOR_THIS_USER ); $titleObj = $rev->getTitle(); + $wgTitle = $titleObj; $p_result = $wgParser->parse($text, $titleObj, $popts); } else { - $titleObj = Title::newFromText($page); + if($params['redirects']) + { + $req = new FauxRequest(array( + 'action' => 'query', + 'redirects' => '', + 'titles' => $page + )); + $main = new ApiMain($req); + $main->execute(); + $data = $main->getResultData(); + $redirValues = @$data['query']['redirects']; + $to = $page; + foreach((array)$redirValues as $r) + $to = $r['to']; + } + else + $to = $page; + $titleObj = Title::newFromText($to); if(!$titleObj) $this->dieUsage("The page you specified doesn't exist", 'missingtitle'); - // Try the parser cache first $articleObj = new Article($titleObj); if(isset($prop['revid'])) $oldid = $articleObj->getRevIdFetched(); + // Try the parser cache first $pcache = ParserCache::singleton(); $p_result = $pcache->get($articleObj, $wgUser); - if(!$p_result) { + if(!$p_result) + { $p_result = $wgParser->parse($articleObj->getContent(), $titleObj, $popts); global $wgUseParserCache; if($wgUseParserCache) @@ -92,12 +114,25 @@ class ApiParse extends ApiBase { $titleObj = Title::newFromText($title); if(!$titleObj) $titleObj = Title::newFromText("API"); + $wgTitle = $titleObj; + if($params['pst'] || $params['onlypst']) + $text = $wgParser->preSaveTransform($text, $titleObj, $wgUser, $popts); + if($params['onlypst']) + { + // Build a result and bail out + $result_array['text'] = array(); + $this->getResult()->setContent($result_array['text'], $text); + $this->getResult()->addValue(null, $this->getModuleName(), $result_array); + return; + } $p_result = $wgParser->parse($text, $titleObj, $popts); } // Return result $result = $this->getResult(); $result_array = array(); + if($params['redirects'] && !is_null($redirValues)) + $result_array['redirects'] = $redirValues; if(isset($prop['text'])) { $result_array['text'] = array(); $result->setContent($result_array['text'], $p_result->getText()); @@ -120,6 +155,7 @@ class ApiParse extends ApiBase { $result_array['revid'] = $oldid; $result_mapping = array( + 'redirects' => 'r', 'langlinks' => 'll', 'categories' => 'cl', 'links' => 'pl', @@ -184,6 +220,7 @@ class ApiParse extends ApiBase { ), 'text' => null, 'page' => null, + 'redirects' => false, 'oldid' => null, 'prop' => array( ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid', @@ -199,19 +236,28 @@ class ApiParse extends ApiBase { 'sections', 'revid' ) - ) + ), + 'pst' => false, + 'onlypst' => false, ); } public function getParamDescription() { return array ( 'text' => 'Wikitext to parse', + 'redirects' => 'If the page parameter is set to a redirect, resolve it', 'title' => 'Title of page the text belongs to', 'page' => 'Parse the content of this page. Cannot be used together with text and title', 'oldid' => 'Parse the content of this revision. Overrides page', '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' ), + 'pst' => array( 'Do a pre-save transform on the input before parsing it.', + 'Ignored if page or oldid is used.' + ), + 'onlypst' => array('Do a PST on the input, but don\'t parse it.', + 'Returns PSTed wikitext. Ignored if page or oldid is used.' + ), ); } @@ -226,6 +272,6 @@ class ApiParse extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiParse.php 36983 2008-07-03 15:01:50Z catrope $'; + return __CLASS__ . ': $Id: ApiParse.php 44858 2008-12-20 20:00:07Z catrope $'; } } diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php new file mode 100644 index 00000000..08de87b0 --- /dev/null +++ b/includes/api/ApiPatrol.php @@ -0,0 +1,99 @@ +<?php + +/* + * Created on Sep 2, 2008 + * + * API for MediaWiki 1.14+ + * + * Copyright (C) 2008 Soxred93 soxred93@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')) { + require_once ('ApiBase.php'); +} + +/** + * Allows user to patrol pages + * @ingroup API + */ +class ApiPatrol extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + /** + * Patrols the article or provides the reason the patrol failed. + */ + public function execute() { + global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol; + $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + if(!isset($params['rcid'])) + $this->dieUsageMsg(array('missingparam', 'rcid')); + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + + $rc = RecentChange::newFromID($params['rcid']); + if(!$rc instanceof RecentChange) + $this->dieUsageMsg(array('nosuchrcid', $params['rcid'])); + $retval = RecentChange::markPatrolled($params['rcid']); + + if($retval) + $this->dieUsageMsg(current($retval)); + + $result = array('rcid' => $rc->getAttribute('rc_id')); + ApiQueryBase::addTitleInfo($result, $rc->getTitle()); + $this->getResult()->addValue(null, $this->getModuleName(), $result); + } + + public function getAllowedParams() { + return array ( + 'token' => null, + 'rcid' => array( + ApiBase :: PARAM_TYPE => 'integer' + ), + ); + } + + public function getParamDescription() { + return array ( + 'token' => 'Patrol token obtained from list=recentchanges', + 'rcid' => 'Recentchanges ID to patrol', + ); + } + + public function getDescription() { + return array ( + 'Patrol a page or revision. ' + ); + } + + protected function getExamples() { + return array( + 'api.php?action=patrol&token=123abc&rcid=230672766' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiPatrol.php 42548 2008-10-25 14:04:43Z tstarling $'; + } +} diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php index 30bcfdbc..522d02b2 100644 --- a/includes/api/ApiProtect.php +++ b/includes/api/ApiProtect.php @@ -37,7 +37,7 @@ class ApiProtect extends ApiBase { } public function execute() { - global $wgUser; + global $wgUser, $wgRestrictionTypes, $wgRestrictionLevels; $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); @@ -46,7 +46,7 @@ class ApiProtect extends ApiBase { $this->dieUsageMsg(array('missingparam', 'title')); if(!isset($params['token'])) $this->dieUsageMsg(array('missingparam', 'token')); - if(!isset($params['protections']) || empty($params['protections'])) + if(empty($params['protections'])) $this->dieUsageMsg(array('missingparam', 'protections')); if(!$wgUser->matchEditToken($params['token'])) @@ -57,25 +57,23 @@ class ApiProtect extends ApiBase { $this->dieUsageMsg(array('invalidtitle', $params['title'])); $errors = $titleObj->getUserPermissionsErrors('protect', $wgUser); - if(!empty($errors)) + if($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 = (array)$params['expiry']; + if(count($expiry) != count($params['protections'])) { - $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')); + if(count($expiry) == 1) + $expiry = array_fill(0, count($params['protections']), $expiry[0]); + else + $this->dieUsageMsg(array('toofewexpiries', count($expiry), count($params['protections']))); } - + $protections = array(); - foreach($params['protections'] as $prot) + $expiryarray = array(); + $resultProtections = array(); + foreach($params['protections'] as $i => $prot) { $p = explode('=', $prot); $protections[$p[0]] = ($p[1] == 'all' ? '' : $p[1]); @@ -83,26 +81,45 @@ class ApiProtect extends ApiBase { $this->dieUsageMsg(array('create-titleexists')); if(!$titleObj->exists() && $p[0] != 'create') $this->dieUsageMsg(array('missingtitles-createonly')); + if(!in_array($p[0], $wgRestrictionTypes) && $p[0] != 'create') + $this->dieUsageMsg(array('protect-invalidaction', $p[0])); + if(!in_array($p[1], $wgRestrictionLevels) && $p[1] != 'all') + $this->dieUsageMsg(array('protect-invalidlevel', $p[1])); + + if(in_array($expiry[$i], array('infinite', 'indefinite', 'never'))) + $expiryarray[$p[0]] = Block::infinity(); + else + { + $exp = strtotime($expiry[$i]); + if($exp < 0 || $exp == false) + $this->dieUsageMsg(array('invalidexpiry', $expiry[$i])); + + $exp = wfTimestamp(TS_MW, $exp); + if($exp < wfTimestampNow()) + $this->dieUsageMsg(array('pastexpiry', $expiry[$i])); + $expiryarray[$p[0]] = $exp; + } + $resultProtections[] = array($p[0] => $protections[$p[0]], + 'expiry' => ($expiryarray[$p[0]] == Block::infinity() ? + 'infinite' : + wfTimestamp(TS_ISO_8601, $expiryarray[$p[0]]))); } + $cascade = $params['cascade']; if($titleObj->exists()) { $articleObj = new Article($titleObj); - $ok = $articleObj->updateRestrictions($protections, $params['reason'], $params['cascade'], $expiry); + $ok = $articleObj->updateRestrictions($protections, $params['reason'], $cascade, $expiryarray); } else - $ok = $titleObj->updateTitleProtection($protections['create'], $params['reason'], $expiry); + $ok = $titleObj->updateTitleProtection($protections['create'], $params['reason'], $expiryarray['create']); if(!$ok) // This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime? // Just throw an unknown error in this case, as it's very likely to be a race condition $this->dieUsageMsg(array()); $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']) + if($cascade) $res['cascade'] = ''; - $res['protections'] = $protections; + $res['protections'] = $resultProtections; + $this->getResult()->setIndexedTagName($res['protections'], 'protection'); $this->getResult()->addValue(null, $this->getModuleName(), $res); } @@ -115,7 +132,11 @@ class ApiProtect extends ApiBase { 'protections' => array( ApiBase :: PARAM_ISMULTI => true ), - 'expiry' => 'infinite', + 'expiry' => array( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_ALLOW_DUPLICATES => true, + ApiBase :: PARAM_DFLT => 'infinite', + ), 'reason' => '', 'cascade' => false ); @@ -123,12 +144,14 @@ class ApiProtect extends ApiBase { public function getParamDescription() { return array ( - 'title' => 'Title of the page you want to restore.', + 'title' => 'Title of the page you want to (un)protect.', '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.', + 'expiry' => array('Expiry timestamps. If only one timestamp is set, it\'ll be used for all protections.', + 'Use \'infinite\', \'indefinite\' or \'never\', for a neverexpiring protection.'), 'reason' => 'Reason for (un)protecting (optional)', - 'cascade' => 'Enable cascading protection (i.e. protect pages included in this page)' + 'cascade' => array('Enable cascading protection (i.e. protect pages included in this page)', + 'Ignored if not all protection levels are \'sysop\' or \'protect\''), ); } @@ -140,12 +163,12 @@ class ApiProtect extends ApiBase { 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=sysop|move=sysop&cascade&expiry=20070901163000|never', '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 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiProtect.php 44426 2008-12-10 22:39:41Z catrope $'; } } diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php new file mode 100644 index 00000000..d7202a46 --- /dev/null +++ b/includes/api/ApiPurge.php @@ -0,0 +1,106 @@ +<?php + +/* + * Created on Sep 2, 2008 + * + * API for MediaWiki 1.14+ + * + * Copyright (C) 2008 Chad Horohoe + * + * 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')) { + require_once ('ApiBase.php'); +} + +/** + * API interface for page purging + * @ingroup API + */ +class ApiPurge extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + /** + * Purges the cache of a page + */ + public function execute() { + global $wgUser; + $params = $this->extractRequestParams(); + if(!$wgUser->isAllowed('purge')) + $this->dieUsageMsg(array('cantpurge')); + if(!isset($params['titles'])) + $this->dieUsageMsg(array('missingparam', 'titles')); + $result = array(); + foreach($params['titles'] as $t) { + $r = array(); + $title = Title::newFromText($t); + if(!$title instanceof Title) + { + $r['title'] = $t; + $r['invalid'] = ''; + $result[] = $r; + continue; + } + ApiQueryBase::addTitleInfo($r, $title); + if(!$title->exists()) + { + $r['missing'] = ''; + $result[] = $r; + continue; + } + $article = new Article($title); + $article->doPurge(); // Directly purge and skip the UI part of purge(). + $r['purged'] = ''; + $result[] = $r; + } + $this->getResult()->setIndexedTagName($result, 'page'); + $this->getResult()->addValue(null, $this->getModuleName(), $result); + } + + public function getAllowedParams() { + return array ( + 'titles' => array( + ApiBase :: PARAM_ISMULTI => true + ) + ); + } + + public function getParamDescription() { + return array ( + 'titles' => 'A list of titles', + ); + } + + public function getDescription() { + return array ( + 'Purge the cache for the given titles.' + ); + } + + protected function getExamples() { + return array( + 'api.php?action=purge&titles=Main_Page|API' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiPurge.php 41020 2008-09-19 00:21:03Z demon $'; + } +} diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index f4a2402f..45a5667a 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -56,6 +56,7 @@ class ApiQuery extends ApiBase { 'categories' => 'ApiQueryCategories', 'extlinks' => 'ApiQueryExternalLinks', 'categoryinfo' => 'ApiQueryCategoryInfo', + 'duplicatefiles' => 'ApiQueryDuplicateFiles', ); private $mQueryListModules = array ( @@ -75,6 +76,7 @@ class ApiQuery extends ApiBase { 'search' => 'ApiQuerySearch', 'usercontribs' => 'ApiQueryContributions', 'watchlist' => 'ApiQueryWatchlist', + 'watchlistraw' => 'ApiQueryWatchlistRaw', 'exturlusage' => 'ApiQueryExtLinksUsage', 'users' => 'ApiQueryUsers', 'random' => 'ApiQueryRandom', @@ -93,10 +95,10 @@ class ApiQuery extends ApiBase { parent :: __construct($main, $action); // Allow custom modules to be added in LocalSettings.php - global $wgApiQueryPropModules, $wgApiQueryListModules, $wgApiQueryMetaModules; - self :: appendUserModules($this->mQueryPropModules, $wgApiQueryPropModules); - self :: appendUserModules($this->mQueryListModules, $wgApiQueryListModules); - self :: appendUserModules($this->mQueryMetaModules, $wgApiQueryMetaModules); + global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules; + self :: appendUserModules($this->mQueryPropModules, $wgAPIPropModules); + self :: appendUserModules($this->mQueryListModules, $wgAPIListModules); + self :: appendUserModules($this->mQueryMetaModules, $wgAPIMetaModules); $this->mPropModuleNames = array_keys($this->mQueryPropModules); $this->mListModuleNames = array_keys($this->mQueryListModules); @@ -209,6 +211,7 @@ class ApiQuery extends ApiBase { foreach ($modules as $module) { $module->profileIn(); $module->execute(); + wfRunHooks('APIQueryAfterExecute', array(&$module)); $module->profileOut(); } } @@ -229,8 +232,8 @@ class ApiQuery extends ApiBase { * Create instances of all modules requested by the client */ private function InstantiateModules(&$modules, $param, $moduleList) { - $list = $this->params[$param]; - if (isset ($list)) + $list = @$this->params[$param]; + if (!is_null ($list)) foreach ($list as $moduleName) $modules[] = new $moduleList[$moduleName] ($this, $moduleName); } @@ -253,7 +256,7 @@ class ApiQuery extends ApiBase { ); } - if (!empty ($normValues)) { + if (count($normValues)) { $result->setIndexedTagName($normValues, 'n'); $result->addValue('query', 'normalized', $normValues); } @@ -267,7 +270,7 @@ class ApiQuery extends ApiBase { ); } - if (!empty ($intrwValues)) { + if (count($intrwValues)) { $result->setIndexedTagName($intrwValues, 'i'); $result->addValue('query', 'interwiki', $intrwValues); } @@ -281,7 +284,7 @@ class ApiQuery extends ApiBase { ); } - if (!empty ($redirValues)) { + if (count($redirValues)) { $result->setIndexedTagName($redirValues, 'r'); $result->addValue('query', 'redirects', $redirValues); } @@ -290,7 +293,7 @@ class ApiQuery extends ApiBase { // Missing revision elements // $missingRevIDs = $pageSet->getMissingRevisionIDs(); - if (!empty ($missingRevIDs)) { + if (count($missingRevIDs)) { $revids = array (); foreach ($missingRevIDs as $revid) { $revids[$revid] = array ( @@ -332,7 +335,7 @@ class ApiQuery extends ApiBase { $pages[$pageid] = $vals; } - if (!empty ($pages)) { + if (count($pages)) { if ($this->params['indexpageids']) { $pageIDs = array_keys($pages); @@ -381,6 +384,7 @@ class ApiQuery extends ApiBase { // populate resultPageSet with the generator output $generator->profileIn(); $generator->executeGenerator($resultPageSet); + wfRunHooks('APIQueryGeneratorAfterExecute', array(&$generator, &$resultPageSet)); $resultPageSet->finishPageSetGeneration(); $generator->profileOut(); @@ -476,7 +480,6 @@ class ApiQuery extends ApiBase { return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters(); } - // @todo should work correctly public function shouldCheckMaxlag() { return true; } @@ -509,7 +512,7 @@ class ApiQuery extends ApiBase { public function getVersion() { $psModule = new ApiPageSet($this); $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiQuery.php 35098 2008-05-20 17:13:28Z ialex $'; + $vers[] = __CLASS__ . ': $Id: ApiQuery.php 42548 2008-10-25 14:04:43Z tstarling $'; $vers[] = $psModule->getVersion(); return $vers; } diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php index 3ff42c88..e6287eea 100644 --- a/includes/api/ApiQueryAllCategories.php +++ b/includes/api/ApiQueryAllCategories.php @@ -56,17 +56,30 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { $this->addTables('category'); $this->addFields('cat_title'); - if (!is_null($params['from'])) - $this->addWhere('cat_title>=' . $db->addQuotes($this->titleToKey($params['from']))); + $dir = ($params['dir'] == 'descending' ? 'older' : 'newer'); + $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from'])); + $this->addWhereRange('cat_title', $dir, $from, null); if (isset ($params['prefix'])) - $this->addWhere("cat_title LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'"); + $this->addWhere("cat_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'"); $this->addOption('LIMIT', $params['limit']+1); $this->addOption('ORDER BY', 'cat_title' . ($params['dir'] == 'descending' ? ' DESC' : '')); $prop = array_flip($params['prop']); $this->addFieldsIf( array( 'cat_pages', 'cat_subcats', 'cat_files' ), isset($prop['size']) ); - $this->addFieldsIf( 'cat_hidden', isset($prop['hidden']) ); + if(isset($prop['hidden'])) + { + $this->addTables(array('page', 'page_props')); + $this->addJoinConds(array( + 'page' => array('LEFT JOIN', array( + 'page_namespace' => NS_CATEGORY, + 'page_title=cat_title')), + 'page_props' => array('LEFT JOIN', array( + 'pp_page=page_id', + 'pp_propname' => 'hiddencat')), + )); + $this->addFields('pp_propname AS cat_hidden'); + } $res = $this->select(__METHOD__); @@ -158,6 +171,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllCategories.php 36790 2008-06-29 22:26:23Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllCategories.php 44590 2008-12-14 20:24:23Z catrope $'; } } diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php index aefbb725..9ad34aa2 100644 --- a/includes/api/ApiQueryAllLinks.php +++ b/includes/api/ApiQueryAllLinks.php @@ -74,30 +74,30 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $arr = explode('|', $params['continue']); if(count($arr) != 2) $this->dieUsage("Invalid continue parameter", 'badcontinue'); - $params['from'] = $arr[0]; // Handled later + $from = $this->getDB()->strencode($this->titleToKey($arr[0])); $id = intval($arr[1]); - $this->addWhere("pl_from >= $id"); + $this->addWhere("pl_title > '$from' OR " . + "(pl_title = '$from' AND " . + "pl_from > $id)"); } if (!is_null($params['from'])) - $this->addWhere('pl_title>=' . $db->addQuotes($this->titleToKey($params['from']))); + $this->addWhere('pl_title>=' . $db->addQuotes($this->titlePartToKey($params['from']))); if (isset ($params['prefix'])) - $this->addWhere("pl_title LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'"); + $this->addWhere("pl_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'"); $this->addFields(array ( - 'pl_namespace', 'pl_title', - 'pl_from' )); + $this->addFieldsIf('pl_from', !$params['unique']); $this->addOption('USE INDEX', 'pl_namespace'); $limit = $params['limit']; $this->addOption('LIMIT', $limit+1); - # Only order by pl_namespace if it isn't constant in the WHERE clause - if(count($params['namespace']) != 1) - $this->addOption('ORDER BY', 'pl_namespace, pl_title'); - else + if($params['unique']) $this->addOption('ORDER BY', 'pl_title'); + else + $this->addOption('ORDER BY', 'pl_title, pl_from'); $res = $this->select(__METHOD__); @@ -107,7 +107,10 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { if (++ $count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... // TODO: Security issue - if the user has no right to view next title, it will still be shown - $this->setContinueEnumParameter('continue', $this->keyToTitle($row->pl_title) . "|" . $row->pl_from); + if($params['unique']) + $this->setContinueEnumParameter('from', $this->keyToTitle($row->pl_title)); + else + $this->setContinueEnumParameter('continue', $this->keyToTitle($row->pl_title) . "|" . $row->pl_from); break; } @@ -116,7 +119,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { if ($fld_ids) $vals['fromid'] = intval($row->pl_from); if ($fld_title) { - $title = Title :: makeTitle($row->pl_namespace, $row->pl_title); + $title = Title :: makeTitle($params['namespace'], $row->pl_title); $vals['ns'] = intval($title->getNamespace()); $vals['title'] = $title->getPrefixedText(); } @@ -187,6 +190,6 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllLinks.php 37258 2008-07-07 14:48:40Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllLinks.php 45850 2009-01-17 20:03:25Z catrope $'; } } diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php index dd0e98a8..8395808b 100644 --- a/includes/api/ApiQueryAllUsers.php +++ b/includes/api/ApiQueryAllUsers.php @@ -121,7 +121,7 @@ class ApiQueryAllUsers extends ApiQueryBase { $row = $db->fetchObject($res); $count++; - if (!$row || $lastUser != $row->user_name) { + if (!$row || $lastUser !== $row->user_name) { // Save the last pass's user data if (is_array($lastUserData)) $data[] = $lastUserData; @@ -219,6 +219,6 @@ class ApiQueryAllUsers extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllUsers.php 36790 2008-06-29 22:26:23Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllUsers.php 44472 2008-12-11 21:51:01Z catrope $'; } } diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php index 26cbc368..ea83c667 100644 --- a/includes/api/ApiQueryAllimages.php +++ b/includes/api/ApiQueryAllimages.php @@ -61,10 +61,11 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { $params = $this->extractRequestParams(); // Image filters - if (!is_null($params['from'])) - $this->addWhere('img_name>=' . $db->addQuotes($this->titleToKey($params['from']))); + $dir = ($params['dir'] == 'descending' ? 'older' : 'newer'); + $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from'])); + $this->addWhereRange('img_name', $dir, $from, null); if (isset ($params['prefix'])) - $this->addWhere("img_name LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'"); + $this->addWhere("img_name LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'"); if (isset ($params['minsize'])) { $this->addWhere('img_size>=' . intval($params['minsize'])); @@ -109,10 +110,10 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { $file = $repo->newFileFromRow( $row ); - - $data[] = ApiQueryImageInfo::getInfo( $file, $prop, $result ); + $data[] = array_merge(array('name' => $row->img_name), + ApiQueryImageInfo::getInfo($file, $prop, $result)); } else { - $data[] = Title::makeTitle( NS_IMAGE, $row->img_name ); + $data[] = Title::makeTitle(NS_FILE, $row->img_name); } } $db->freeResult($res); @@ -162,7 +163,8 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { 'dimensions', // Obsolete 'mime', 'sha1', - 'metadata' + 'metadata', + 'bitdepth', ), ApiBase :: PARAM_DFLT => 'timestamp|url', ApiBase :: PARAM_ISMULTI => true @@ -200,6 +202,6 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllimages.php 37909 2008-07-22 13:26:15Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllimages.php 44121 2008-12-01 17:14:30Z vyznev $'; } } diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index 39490fe7..531fa02a 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -62,11 +62,21 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $this->addWhereIf('page_is_redirect = 0', $params['filterredir'] === 'nonredirects'); $this->addWhereFld('page_namespace', $params['namespace']); $dir = ($params['dir'] == 'descending' ? 'older' : 'newer'); - $from = (is_null($params['from']) ? null : $this->titleToKey($params['from'])); + $from = (is_null($params['from']) ? null : $this->titlePartToKey($params['from'])); $this->addWhereRange('page_title', $dir, $from, null); if (isset ($params['prefix'])) - $this->addWhere("page_title LIKE '" . $db->escapeLike($this->titleToKey($params['prefix'])) . "%'"); + $this->addWhere("page_title LIKE '" . $db->escapeLike($this->titlePartToKey($params['prefix'])) . "%'"); + if (is_null($resultPageSet)) { + $selectFields = array ( + 'page_namespace', + 'page_title', + 'page_id' + ); + } else { + $selectFields = $resultPageSet->getPageTableFields(); + } + $this->addFields($selectFields); $forceNameTitleIndex = true; if (isset ($params['minsize'])) { $this->addWhere('page_len>=' . intval($params['minsize'])); @@ -79,15 +89,20 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } // Page protection filtering - if (isset ($params['prtype'])) { + if (!empty ($params['prtype'])) { $this->addTables('page_restrictions'); $this->addWhere('page_id=pr_page'); $this->addWhere('pr_expiry>' . $db->addQuotes($db->timestamp())); $this->addWhereFld('pr_type', $params['prtype']); - $prlevel = $params['prlevel']; - if (!is_null($prlevel) && $prlevel != '' && $prlevel != '*') + // Remove the empty string and '*' from the prlevel array + $prlevel = array_diff($params['prlevel'], array('', '*')); + if (!empty($prlevel)) $this->addWhereFld('pr_level', $prlevel); + if ($params['prfiltercascade'] == 'cascading') + $this->addWhereFld('pr_cascade', 1); + if ($params['prfiltercascade'] == 'noncascading') + $this->addWhereFld('pr_cascade', 0); $this->addOption('DISTINCT'); @@ -105,20 +120,16 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } else if($params['filterlanglinks'] == 'withlanglinks') { $this->addTables('langlinks'); $this->addWhere('page_id=ll_from'); + $this->addOption('STRAIGHT_JOIN'); + // We have to GROUP BY all selected fields to stop + // PostgreSQL from whining + $this->addOption('GROUP BY', implode(', ', $selectFields)); $forceNameTitleIndex = false; } if ($forceNameTitleIndex) $this->addOption('USE INDEX', 'name_title'); - if (is_null($resultPageSet)) { - $this->addFields(array ( - 'page_id', - 'page_namespace', - 'page_title' - )); - } else { - $this->addFields($resultPageSet->getPageTableFields()); - } + $limit = $params['limit']; $this->addOption('LIMIT', $limit+1); @@ -185,6 +196,14 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { ApiBase :: PARAM_TYPE => $wgRestrictionLevels, ApiBase :: PARAM_ISMULTI => true ), + 'prfiltercascade' => array ( + ApiBase :: PARAM_DFLT => 'all', + ApiBase :: PARAM_TYPE => array ( + 'cascading', + 'noncascading', + 'all' + ), + ), 'limit' => array ( ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', @@ -221,6 +240,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { 'maxsize' => 'Limit to pages with at most this many bytes', 'prtype' => 'Limit to protected pages only', 'prlevel' => 'The protection level (must be used with apprtype= parameter)', + 'prfiltercascade' => 'Filter protections based on cascadingness (ignored when apprtype isn\'t set)', 'filterlanglinks' => 'Filter based on whether a page has langlinks', 'limit' => 'How many total pages to return.' ); @@ -244,6 +264,6 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllpages.php 37775 2008-07-17 09:26:01Z brion $'; + return __CLASS__ . ': $Id: ApiQueryAllpages.php 44863 2008-12-20 23:54:04Z catrope $'; } } diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index fea058f3..f67e0044 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -60,7 +60,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { ); public function __construct($query, $moduleName) { - $code = $prefix = $linktbl = null; extract($this->backlinksSettings[$moduleName]); parent :: __construct($query, $moduleName, $code); @@ -100,7 +99,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { * AND pl_title='Foo' AND pl_namespace=0 * LIMIT 11 ORDER BY pl_from */ - $db = $this->getDb(); + $db = $this->getDB(); $this->addTables(array('page', $this->bl_table)); $this->addWhere("{$this->bl_from}=page_id"); if(is_null($resultPageSet)) @@ -108,12 +107,12 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { else $this->addFields($resultPageSet->getPageTableFields()); $this->addFields('page_is_redirect'); - $this->addWhereFld($this->bl_title, $this->rootTitle->getDbKey()); + $this->addWhereFld($this->bl_title, $this->rootTitle->getDBKey()); if($this->hasNS) $this->addWhereFld($this->bl_ns, $this->rootTitle->getNamespace()); $this->addWhereFld('page_namespace', $this->params['namespace']); if(!is_null($this->contID)) - $this->addWhere("page_id>={$this->contID}"); + $this->addWhere("{$this->bl_from}>={$this->contID}"); if($this->params['filterredir'] == 'redirects') $this->addWhereFld('page_is_redirect', 1); if($this->params['filterredir'] == 'nonredirects') @@ -124,11 +123,11 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { private function prepareSecondQuery($resultPageSet = null) { /* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace - * FROM pagelinks, page WHERE pl_from=page_id - * AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1) - * LIMIT 11 ORDER BY pl_namespace, pl_title, pl_from + FROM pagelinks, page WHERE pl_from=page_id + AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1) + ORDER BY pl_namespace, pl_title, pl_from LIMIT 11 */ - $db = $this->getDb(); + $db = $this->getDB(); $this->addTables(array('page', $this->bl_table)); $this->addWhere("{$this->bl_from}=page_id"); if(is_null($resultPageSet)) @@ -138,16 +137,31 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->addFields($this->bl_title); if($this->hasNS) $this->addFields($this->bl_ns); - $titleWhere = ''; + $titleWhere = array(); foreach($this->redirTitles as $t) - $titleWhere .= ($titleWhere != '' ? " OR " : '') . - "({$this->bl_title} = ".$db->addQuotes($t->getDBKey()). + $titleWhere[] = "({$this->bl_title} = ".$db->addQuotes($t->getDBKey()). ($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "") . ")"; - $this->addWhere($titleWhere); + $this->addWhere($db->makeList($titleWhere, LIST_OR)); $this->addWhereFld('page_namespace', $this->params['namespace']); if(!is_null($this->redirID)) - $this->addWhere("page_id>={$this->redirID}"); + { + $first = $this->redirTitles[0]; + $title = $db->strencode($first->getDBKey()); + $ns = $first->getNamespace(); + $from = $this->redirID; + if($this->hasNS) + $this->addWhere("{$this->bl_ns} > $ns OR ". + "({$this->bl_ns} = $ns AND ". + "({$this->bl_title} > '$title' OR ". + "({$this->bl_title} = '$title' AND ". + "{$this->bl_from} >= $from)))"); + else + $this->addWhere("{$this->bl_title} > '$title' OR ". + "({$this->bl_title} = '$title' AND ". + "{$this->bl_from} >= $from)"); + + } if($this->params['filterredir'] == 'redirects') $this->addWhereFld('page_is_redirect', 1); if($this->params['filterredir'] == 'nonredirects') @@ -170,7 +184,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->prepareFirstQuery($resultPageSet); $db = $this->getDB(); - $res = $this->select(__METHOD__); + $res = $this->select(__METHOD__.'::firstQuery'); $count = 0; $this->data = array (); @@ -195,11 +209,11 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } $db->freeResult($res); - if($this->redirect && !empty($this->redirTitles)) + if($this->redirect && count($this->redirTitles)) { $this->resetQueryParams(); $this->prepareSecondQuery($resultPageSet); - $res = $this->select(__METHOD__); + $res = $this->select(__METHOD__.'::secondQuery'); $count = 0; while($row = $db->fetchObject($res)) { @@ -210,7 +224,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { if($this->hasNS) $contTitle = Title::makeTitle($row->{$this->bl_ns}, $row->{$this->bl_title}); else - $contTitle = Title::makeTitle(NS_IMAGE, $row->{$this->bl_title}); + $contTitle = Title::makeTitle(NS_FILE, $row->{$this->bl_title}); $this->continueStr = $this->getContinueRedirStr($contTitle->getArticleID(), $row->page_id); break; } @@ -229,7 +243,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $resultData = array(); foreach($this->data as $ns => $a) foreach($a as $title => $arr) - $resultData[$arr['pageid']] = $arr; + $resultData[] = $arr; $result = $this->getResult(); $result->setIndexedTagName($resultData, $this->bl_code); $result->addValue('query', $this->getModuleName(), $resultData); @@ -254,7 +268,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { ApiQueryBase::addTitleInfo($a, Title::makeTitle($row->page_namespace, $row->page_title)); if($row->page_is_redirect) $a['redirect'] = ''; - $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_IMAGE; + $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE; $this->data[$ns][$row->{$this->bl_title}]['redirlinks'][] = $a; $this->getResult()->setIndexedTagName($this->data[$ns][$row->{$this->bl_title}]['redirlinks'], $this->bl_code); } @@ -276,7 +290,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } // only image titles are allowed for the root in imageinfo mode - if (!$this->hasNS && $this->rootTitle->getNamespace() !== NS_IMAGE) + if (!$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE) $this->dieUsage("The title for {$this->getModuleName()} query must be an image", 'bad_image_title'); } @@ -399,6 +413,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBacklinks.php 37504 2008-07-10 14:28:09Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryBacklinks.php 46135 2009-01-24 13:03:40Z catrope $'; } } diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index f392186b..896dd00c 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -126,13 +126,19 @@ abstract class ApiQueryBase extends ApiBase { * Clauses can be formatted as 'foo=bar' or array('foo' => 'bar'), * the latter only works if the value is a constant (i.e. not another field) * + * If $value is an empty array, this function does nothing. + * * For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates * to "foo=bar AND baz='3' AND bla='foo'" * @param mixed $value String or array */ protected function addWhere($value) { - if (is_array($value)) - $this->where = array_merge($this->where, $value); + if (is_array($value)) { + // Sanity check: don't insert empty arrays, + // Database::makeList() chokes on them + if ( count( $value ) ) + $this->where = array_merge($this->where, $value); + } else $this->where[] = $value; } @@ -154,10 +160,12 @@ abstract class ApiQueryBase extends ApiBase { /** * Equivalent to addWhere(array($field => $value)) * @param string $field Field name - * @param string $value Value; ignored if nul; + * @param string $value Value; ignored if null or empty array; */ protected function addWhereFld($field, $value) { - if (!is_null($value)) + // Use count() to its full documented capabilities to simultaneously + // test for null, empty array or empty countable object + if ( count( $value ) ) $this->where[$field] = $value; } @@ -236,7 +244,7 @@ abstract class ApiQueryBase extends ApiBase { /** * Add information (title and namespace) about a Title object to a result array - * @param array $arr Result array à la ApiResult + * @param array $arr Result array à la ApiResult * @param Title $title Title object * @param string $prefix Module prefix */ @@ -264,7 +272,7 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a sub-element under the page element with the given page ID * @param int $pageId Page ID - * @param array $data Data array à la ApiResult + * @param array $data Data array à la ApiResult */ protected function addPageSubItems($pageId, $data) { $result = $this->getResult(); @@ -324,10 +332,13 @@ abstract class ApiQueryBase extends ApiBase { * @return string Page title with underscores */ public function titleToKey($title) { + # Don't throw an error if we got an empty string + if(trim($title) == '') + return ''; $t = Title::newFromText($title); if(!$t) $this->dieUsageMsg(array('invalidtitle', $title)); - return $t->getDbKey(); + return $t->getPrefixedDbKey(); } /** @@ -336,19 +347,40 @@ abstract class ApiQueryBase extends ApiBase { * @return string Page title with spaces */ public function keyToTitle($key) { + # Don't throw an error if we got an empty string + if(trim($key) == '') + return ''; $t = Title::newFromDbKey($key); # This really shouldn't happen but we gotta check anyway if(!$t) $this->dieUsageMsg(array('invalidtitle', $key)); return $t->getPrefixedText(); } + + /** + * An alternative to titleToKey() that doesn't trim trailing spaces + * @param string $titlePart Title part with spaces + * @return string Title part with underscores + */ + public function titlePartToKey($titlePart) { + return substr($this->titleToKey($titlePart . 'x'), 0, -1); + } + + /** + * An alternative to keyToTitle() that doesn't trim trailing spaces + * @param string $keyPart Key part with spaces + * @return string Key part with underscores + */ + public function keyPartToTitle($keyPart) { + return substr($this->keyToTitle($keyPart . 'x'), 0, -1); + } /** * Get version string for use in the API help output * @return string */ public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiQueryBase.php 37083 2008-07-05 11:18:50Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryBase.php 44461 2008-12-11 19:11:11Z ialex $'; } } diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php index ebe87908..6f356cea 100644 --- a/includes/api/ApiQueryBlocks.php +++ b/includes/api/ApiQueryBlocks.php @@ -42,10 +42,6 @@ class ApiQueryBlocks extends ApiQueryBase { } public function execute() { - $this->run(); - } - - private function run() { global $wgUser; $params = $this->extractRequestParams(); @@ -87,17 +83,17 @@ class ApiQueryBlocks extends ApiQueryBase { 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->addFields(array('ipb_auto', 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk')); $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'])); + $this->addWhereFld('ipb_id', $params['ids']); if(isset($params['users'])) { foreach((array)$params['users'] as $u) $this->prepareUsername($u); - $this->addWhere(array('ipb_address' => $this->usernames)); + $this->addWhereFld('ipb_address', $this->usernames); } if(isset($params['ip'])) { @@ -120,19 +116,18 @@ class ApiQueryBlocks extends ApiQueryBase { )); } if(!$wgUser->isAllowed('suppress')) - $this->addWhere(array('ipb_deleted' => 0)); + $this->addWhereFld('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)) + while($row = $res->fetchObject()) { - if($count++ == $params['limit']) + if(++$count > $params['limit']) { // We've had enough $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp)); @@ -142,13 +137,9 @@ class ApiQueryBlocks extends ApiQueryBase { 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) @@ -157,8 +148,8 @@ class ApiQueryBlocks extends ApiQueryBase { $block['reason'] = $row->ipb_reason; if($fld_range) { - $block['rangestart'] = $this->convertHexIP($row->ipb_range_start); - $block['rangeend'] = $this->convertHexIP($row->ipb_range_end); + $block['rangestart'] = IP::hexToQuad($row->ipb_range_start); + $block['rangeend'] = IP::hexToQuad($row->ipb_range_end); } if($fld_flags) { @@ -175,6 +166,8 @@ class ApiQueryBlocks extends ApiQueryBase { $block['noemail'] = ''; if($row->ipb_deleted) $block['hidden'] = ''; + if($row->ipb_allow_usertalk) + $block['allowusertalk'] = ''; } $data[] = $block; } @@ -194,19 +187,6 @@ class ApiQueryBlocks extends ApiQueryBase { $this->usernames[] = $name; } - 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( @@ -279,6 +259,6 @@ class ApiQueryBlocks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBlocks.php 37892 2008-07-21 21:37:11Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryBlocks.php 43676 2008-11-18 15:11:11Z catrope $'; } } diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php index 51492d63..9c4e9b41 100644 --- a/includes/api/ApiQueryCategories.php +++ b/includes/api/ApiQueryCategories.php @@ -54,6 +54,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { $params = $this->extractRequestParams(); $prop = $params['prop']; + $show = array_flip((array)$params['show']); $this->addFields(array ( 'cl_from', @@ -86,11 +87,31 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { $this->dieUsage("Invalid continue param. You should pass the " . "original value returned by the previous query", "_badcontinue"); $clfrom = intval($cont[0]); - $clto = $this->getDb()->strencode($this->titleToKey($cont[1])); + $clto = $this->getDB()->strencode($this->titleToKey($cont[1])); $this->addWhere("cl_from > $clfrom OR ". "(cl_from = $clfrom AND ". "cl_to >= '$clto')"); } + if(isset($show['hidden']) && isset($show['!hidden'])) + $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); + if(isset($show['hidden']) || isset($show['!hidden'])) + { + $this->addOption('STRAIGHT_JOIN'); + $this->addTables(array('page', 'page_props')); + $this->addJoinConds(array( + 'page' => array('LEFT JOIN', array( + 'page_namespace' => NS_CATEGORY, + 'page_title = cl_to')), + 'page_props' => array('LEFT JOIN', array( + 'pp_page=page_id', + 'pp_propname' => 'hiddencat')) + )); + if(isset($show['hidden'])) + $this->addWhere(array('pp_propname IS NOT NULL')); + else + $this->addWhere(array('pp_propname IS NULL')); + } + # Don't order by cl_from if it's constant in the WHERE clause if(count($this->getPageSet()->getGoodTitles()) == 1) $this->addOption('ORDER BY', 'cl_to'); @@ -128,7 +149,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { if ($fld_sortkey) $vals['sortkey'] = $row->cl_sortkey; if ($fld_timestamp) - $vals['timestamp'] = $row->cl_timestamp; + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp); $data[] = $vals; } @@ -166,6 +187,13 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { 'timestamp', ) ), + 'show' => array( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array( + 'hidden', + '!hidden', + ) + ), 'limit' => array( ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', @@ -181,6 +209,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { return array ( 'prop' => 'Which additional properties to get for each category.', 'limit' => 'How many categories to return', + 'show' => 'Which kind of categories to show', 'continue' => 'When more results are available, use this to continue', ); } @@ -199,6 +228,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategories.php 37909 2008-07-22 13:26:15Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryCategories.php 44585 2008-12-14 17:39:50Z catrope $'; } } diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php index f809bb15..f83c4a5b 100644 --- a/includes/api/ApiQueryCategoryInfo.php +++ b/includes/api/ApiQueryCategoryInfo.php @@ -41,9 +41,10 @@ class ApiQueryCategoryInfo extends ApiQueryBase { public function execute() { $alltitles = $this->getPageSet()->getAllTitlesByNamespace(); - $categories = $alltitles[NS_CATEGORY]; - if(empty($categories)) + if ( empty( $alltitles[NS_CATEGORY] ) ) { return; + } + $categories = $alltitles[NS_CATEGORY]; $titles = $this->getPageSet()->getGoodTitles() + $this->getPageSet()->getMissingTitles(); @@ -51,11 +52,19 @@ class ApiQueryCategoryInfo extends ApiQueryBase { foreach($categories as $c) { $t = $titles[$c]; - $cattitles[$c] = $t->getDbKey(); + $cattitles[$c] = $t->getDBKey(); } - $this->addTables('category'); - $this->addFields(array('cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'cat_hidden')); + $this->addTables(array('category', 'page', 'page_props')); + $this->addJoinConds(array( + 'page' => array('LEFT JOIN', array( + 'page_namespace' => NS_CATEGORY, + 'page_title=cat_title')), + 'page_props' => array('LEFT JOIN', array( + 'pp_page=page_id', + 'pp_propname' => 'hiddencat')), + )); + $this->addFields(array('cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'pp_propname AS cat_hidden')); $this->addWhere(array('cat_title' => $cattitles)); $db = $this->getDB(); @@ -86,6 +95,6 @@ class ApiQueryCategoryInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 37504 2008-07-10 14:28:09Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 44590 2008-12-14 20:24:23Z catrope $'; } } diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php index 3909b213..e2f577a2 100644 --- a/includes/api/ApiQueryCategoryMembers.php +++ b/includes/api/ApiQueryCategoryMembers.php @@ -76,17 +76,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { $this->addTables(array('page','categorylinks')); // must be in this order for 'USE INDEX' // Not needed after bug 10280 is applied to servers if($params['sort'] == 'timestamp') - { $this->addOption('USE INDEX', 'cl_timestamp'); - // cl_timestamp will be added by addWhereRange() later - $this->addOption('ORDER BY', 'cl_to'); - } else - { - $dir = ($params['dir'] == 'desc' ? ' DESC' : ''); $this->addOption('USE INDEX', 'cl_sortkey'); - $this->addOption('ORDER BY', 'cl_to, cl_sortkey' . $dir . ', cl_from' . $dir); - } $this->addWhere('cl_from=page_id'); $this->setContinuation($params['continue'], $params['dir']); @@ -94,6 +86,11 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { $this->addWhereFld('page_namespace', $params['namespace']); if($params['sort'] == 'timestamp') $this->addWhereRange('cl_timestamp', ($params['dir'] == 'asc' ? 'newer' : 'older'), $params['start'], $params['end']); + else + { + $this->addWhereRange('cl_sortkey', ($params['dir'] == 'asc' ? 'newer' : 'older'), $params['startsortkey'], $params['endsortkey']); + $this->addWhereRange('cl_from', ($params['dir'] == 'asc' ? 'newer' : 'older'), null, null); + } $limit = $params['limit']; $this->addOption('LIMIT', $limit +1); @@ -157,18 +154,15 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { if (is_null($continue)) return; // This is not a continuation request - $continueList = explode('|', $continue); - $hasError = count($continueList) != 2; - $from = 0; - if (!$hasError && strlen($continueList[1]) > 0) { - $from = intval($continueList[1]); - $hasError = ($from == 0); - } + $pos = strrpos($continue, '|'); + $sortkey = substr($continue, 0, $pos); + $fromstr = substr($continue, $pos + 1); + $from = intval($fromstr); - if ($hasError) + if ($from == 0 && strlen($fromstr) > 0) $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "badcontinue"); - $encSortKey = $this->getDB()->addQuotes($continueList[0]); + $encSortKey = $this->getDB()->addQuotes($sortkey); $encFrom = $this->getDB()->addQuotes($from); $op = ($dir == 'desc' ? '<' : '>'); @@ -225,7 +219,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { ), 'end' => array( ApiBase :: PARAM_TYPE => 'timestamp' - ) + ), + 'startsortkey' => null, + 'endsortkey' => null, ); } @@ -238,6 +234,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { 'dir' => 'In which direction to sort', 'start' => 'Timestamp to start listing from. Can only be used with cmsort=timestamp', 'end' => 'Timestamp to end listing at. Can only be used with cmsort=timestamp', + 'startsortkey' => 'Sortkey to start listing from. Can only be used with cmsort=sortkey', + 'endsortkey' => 'Sortkey to end listing at. Can only be used with cmsort=sortkey', 'continue' => 'For large categories, give the value retured from previous query', 'limit' => 'The maximum number of pages to return.', ); @@ -257,6 +255,6 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 42197 2008-10-18 10:09:19Z ialex $'; } } diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php index 8368896d..408421c4 100644 --- a/includes/api/ApiQueryDeletedrevs.php +++ b/includes/api/ApiQueryDeletedrevs.php @@ -107,6 +107,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $lb = new LinkBatch($titles); $where = $lb->constructSet('ar', $db); $this->addWhere($where); + } else { + $this->dieUsage('You have to specify a page title or titles'); } $this->addOption('LIMIT', $limit + 1); @@ -228,6 +230,6 @@ class ApiQueryDeletedrevs extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 37502 2008-07-10 14:13:11Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 40798 2008-09-13 20:41:58Z aaron $'; } } diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php new file mode 100644 index 00000000..50825464 --- /dev/null +++ b/includes/api/ApiQueryDisabled.php @@ -0,0 +1,72 @@ +<?php + +/* + * Created on Sep 25, 2008 + * API for MediaWiki 1.8+ + * + * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiBase.php"); +} + + +/** + * API module that does nothing + * + * Use this to disable core modules with e.g. + * $wgAPIPropModules['modulename'] = 'ApiQueryDisabled'; + * + * To disable top-level modules, use ApiDisabled instead + * + * @ingroup API + */ +class ApiQueryDisabled extends ApiQueryBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + $this->setWarning("The ``{$this->getModuleName()}'' module has been disabled."); + } + + public function getAllowedParams() { + return array (); + } + + public function getParamDescription() { + return array (); + } + + public function getDescription() { + return array( + 'This module has been disabled.' + ); + } + + protected function getExamples() { + return array (); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryDisabled.php 41268 2008-09-25 20:50:50Z catrope $'; + } +} diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php new file mode 100644 index 00000000..5f7d7ee0 --- /dev/null +++ b/includes/api/ApiQueryDuplicateFiles.php @@ -0,0 +1,164 @@ +<?php + +/* + * Created on Sep 27, 2008 + * + * API for MediaWiki 1.8+ + * + * Copyright (C) 2008 Roan Kattow <Firstname>,<Lastname>@home.nl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiQueryBase.php"); +} + +/** + * A query module to list duplicates of the given file(s) + * + * @ingroup API + */ +class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'df'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + $params = $this->extractRequestParams(); + $namespaces = $this->getPageSet()->getAllTitlesByNamespace(); + if ( empty( $namespaces[NS_FILE] ) ) { + return; + } + $images = $namespaces[NS_FILE]; + + $this->addTables('image', 'i1'); + $this->addTables('image', 'i2'); + $this->addFields(array( + 'i1.img_name AS orig_name', + 'i2.img_name AS dup_name', + 'i2.img_user_text AS dup_user_text', + 'i2.img_timestamp AS dup_timestamp' + )); + $this->addWhere(array( + 'i1.img_name' => array_keys($images), + 'i1.img_sha1 = i2.img_sha1', + 'i1.img_name != i2.img_name', + )); + if(isset($params['continue'])) + { + $cont = explode('|', $params['continue']); + if(count($cont) != 2) + $this->dieUsage("Invalid continue param. You should pass the " . + "original value returned by the previous query", "_badcontinue"); + $orig = $this->getDB()->strencode($this->titleTokey($cont[0])); + $dup = $this->getDB()->strencode($this->titleToKey($cont[1])); + $this->addWhere("i1.img_name > '$orig' OR ". + "(i1.img_name = '$orig' AND ". + "i2.img_name >= '$dup')"); + } + $this->addOption('ORDER BY', 'i1.img_name'); + $this->addOption('LIMIT', $params['limit'] + 1); + + $res = $this->select(__METHOD__); + $db = $this->getDB(); + $count = 0; + $data = array(); + $titles = array(); + $lastName = ''; + while($row = $db->fetchObject($res)) + { + if(++$count > $params['limit']) + { + // We've reached the one extra which shows that + // there are additional pages to be had. Stop here... + $this->setContinueEnumParameter('continue', + $this->keyToTitle($row->orig_name) . '|' . + $this->keyToTitle($row->dup_name)); + break; + } + if(!is_null($resultPageSet)) + $titles[] = Title::makeTitle(NS_FILE, $row->dup_name); + else + { + if($row->orig_name != $lastName) + { + if($lastName != '') + { + $this->addPageSubItems($images[$lastName], $data); + $data = array(); + } + $lastName = $row->orig_name; + } + + $data[] = array( + 'name' => $row->dup_name, + 'user' => $row->dup_user_text, + 'timestamp' => wfTimestamp(TS_ISO_8601, $row->dup_timestamp) + ); + } + } + if(!is_null($resultPageSet)) + $resultPageSet->populateFromTitles($titles); + else if($lastName != '') + $this->addPageSubItems($images[$lastName], $data); + $db->freeResult($res); + } + + public function getAllowedParams() { + return array ( + 'limit' => array( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'continue' => null, + ); + } + + public function getParamDescription() { + return array ( + 'limit' => 'How many files to return', + 'continue' => 'When more results are available, use this to continue', + ); + } + + public function getDescription() { + return 'List all files that are duplicates of the given file(s).'; + } + + protected function getExamples() { + return array ( 'api.php?action=query&titles=Image:Albert_Einstein_Head.jpg&prop=duplicatefiles', + 'api.php?action=query&generator=allimages&prop=duplicatefiles', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 44121 2008-12-01 17:14:30Z vyznev $'; + } +} diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php index 8ffb7246..85e21f42 100644 --- a/includes/api/ApiQueryExtLinksUsage.php +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -54,7 +54,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { // Find the right prefix global $wgUrlProtocols; - if(!is_null($protocol) && !empty($protocol) && !in_array($protocol, $wgUrlProtocols)) + if($protocol && !in_array($protocol, $wgUrlProtocols)) { foreach ($wgUrlProtocols as $p) { if( substr( $p, 0, strlen( $protocol ) ) === $protocol ) { @@ -66,7 +66,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { else $protocol = null; - $db = $this->getDb(); + $db = $this->getDB(); $this->addTables(array('page','externallinks')); // must be in this order for 'USE INDEX' $this->addOption('USE INDEX', 'el_index'); $this->addWhere('page_id=el_from'); @@ -206,6 +206,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 37909 2008-07-22 13:26:15Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 43271 2008-11-06 22:38:42Z siebrand $'; } } diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php index 33ff1d3f..612d5cc9 100644 --- a/includes/api/ApiQueryImageInfo.php +++ b/includes/api/ApiQueryImageInfo.php @@ -56,10 +56,10 @@ class ApiQueryImageInfo extends ApiQueryBase { } $pageIds = $this->getPageSet()->getAllTitlesByNamespace(); - if (!empty($pageIds[NS_IMAGE])) { + if (!empty($pageIds[NS_FILE])) { $result = $this->getResult(); - $images = RepoGroup::singleton()->findFiles( array_keys( $pageIds[NS_IMAGE] ) ); + $images = RepoGroup::singleton()->findFiles( array_keys( $pageIds[NS_FILE] ) ); foreach ( $images as $img ) { $data = array(); @@ -78,14 +78,14 @@ class ApiQueryImageInfo extends ApiQueryBase { 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) + if(count($pageIds[NS_FILE]) == 1) $this->setContinueEnumParameter('start', $oldie->getTimestamp()); break; } $data[] = self::getInfo( $oldie, $prop, $result ); } - $pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ]; + $pageId = $pageIds[NS_FILE][ $img->getOriginalTitle()->getDBkey() ]; $result->addValue( array( 'query', 'pages', intval( $pageId ) ), 'imagerepository', $img->getRepoName() @@ -93,10 +93,10 @@ class ApiQueryImageInfo extends ApiQueryBase { $this->addPageSubItems($pageId, $data); } - $missing = array_diff( array_keys( $pageIds[NS_IMAGE] ), array_keys( $images ) ); + $missing = array_diff( array_keys( $pageIds[NS_FILE] ), array_keys( $images ) ); foreach ( $missing as $title ) $result->addValue( - array( 'query', 'pages', intval( $pageIds[NS_IMAGE][$title] ) ), + array( 'query', 'pages', intval( $pageIds[NS_FILE][$title] ) ), 'imagerepository', '' ); } @@ -123,12 +123,12 @@ class ApiQueryImageInfo extends ApiQueryBase { } if( isset( $prop['url'] ) ) { if( !is_null( $scale ) && !$file->isOld() ) { - $thumb = $file->getThumbnail( $scale['width'], $scale['height'] ); - if( $thumb ) + $mto = $file->transform( array( 'width' => $scale['width'], 'height' => $scale['height'] ) ); + if( $mto && !$mto->isError() ) { - $vals['thumburl'] = wfExpandUrl( $thumb->getURL() ); - $vals['thumbwidth'] = $thumb->getWidth(); - $vals['thumbheight'] = $thumb->getHeight(); + $vals['thumburl'] = $mto->getUrl(); + $vals['thumbwidth'] = $mto->getWidth(); + $vals['thumbheight'] = $mto->getHeight(); } } $vals['url'] = $file->getFullURL(); @@ -148,6 +148,9 @@ class ApiQueryImageInfo extends ApiQueryBase { if( isset( $prop['archivename'] ) && $file->isOld() ) $vals['archivename'] = $file->getArchiveName(); + + if( isset( $prop['bitdepth'] ) ) + $vals['bitdepth'] = $file->getBitDepth(); return $vals; } @@ -166,7 +169,8 @@ class ApiQueryImageInfo extends ApiQueryBase { 'sha1', 'mime', 'metadata', - 'archivename' + 'archivename', + 'bitdepth', ) ), 'limit' => array( @@ -219,6 +223,6 @@ class ApiQueryImageInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImageInfo.php 37504 2008-07-10 14:28:09Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryImageInfo.php 44121 2008-12-01 17:14:30Z vyznev $'; } } diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php index 32c4e1b0..02fe24f1 100644 --- a/includes/api/ApiQueryImages.php +++ b/includes/api/ApiQueryImages.php @@ -66,7 +66,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase { $this->dieUsage("Invalid continue param. You should pass the " . "original value returned by the previous query", "_badcontinue"); $ilfrom = intval($cont[0]); - $ilto = $this->getDb()->strencode($this->titleToKey($cont[1])); + $ilto = $this->getDB()->strencode($this->titleToKey($cont[1])); $this->addWhere("il_from > $ilfrom OR ". "(il_from = $ilfrom AND ". "il_to >= '$ilto')"); @@ -103,7 +103,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase { } $vals = array(); - ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle(NS_IMAGE, $row->il_to)); + ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle(NS_FILE, $row->il_to)); $data[] = $vals; } @@ -123,7 +123,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase { '|' . $this->keyToTitle($row->il_to)); break; } - $titles[] = Title :: makeTitle(NS_IMAGE, $row->il_to); + $titles[] = Title :: makeTitle(NS_FILE, $row->il_to); } $resultPageSet->populateFromTitles($titles); } @@ -165,6 +165,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImages.php 37535 2008-07-10 21:20:43Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryImages.php 44121 2008-12-01 17:14:30Z vyznev $'; } } diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 9c6487b3..0c5c72fc 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -68,7 +68,8 @@ class ApiQueryInfo extends ApiQueryBase { 'protect' => array( 'ApiQueryInfo', 'getProtectToken' ), 'move' => array( 'ApiQueryInfo', 'getMoveToken' ), 'block' => array( 'ApiQueryInfo', 'getBlockToken' ), - 'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ) + 'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ), + 'email' => array( 'ApiQueryInfo', 'getEmailToken' ), ); wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions)); return $this->tokenFunctions; @@ -153,17 +154,33 @@ class ApiQueryInfo extends ApiQueryBase { return self::getBlockToken($pageid, $title); } + public static function getEmailToken($pageid, $title) + { + global $wgUser; + if(!$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser()) + return false; + + static $cachedEmailToken = null; + if(!is_null($cachedEmailToken)) + return $cachedEmailToken; + + $cachedEmailToken = $wgUser->editToken(); + return $cachedEmailToken; + } + public function execute() { global $wgUser; $params = $this->extractRequestParams(); - $fld_protection = $fld_talkid = $fld_subjectid = false; + $fld_protection = $fld_talkid = $fld_subjectid = $fld_url = $fld_readable = false; if(!is_null($params['prop'])) { $prop = array_flip($params['prop']); $fld_protection = isset($prop['protection']); $fld_talkid = isset($prop['talkid']); $fld_subjectid = isset($prop['subjectid']); + $fld_url = isset($prop['url']); + $fld_readable = isset($prop['readable']); } $pageSet = $this->getPageSet(); @@ -180,7 +197,7 @@ class ApiQueryInfo extends ApiQueryBase { $pageLength = $pageSet->getCustomField('page_len'); $db = $this->getDB(); - if ($fld_protection && !empty($titles)) { + if ($fld_protection && count($titles)) { $this->addTables('page_restrictions'); $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade')); $this->addWhereFld('pr_page', array_keys($titles)); @@ -195,12 +212,44 @@ class ApiQueryInfo extends ApiQueryBase { if($row->pr_cascade) $a['cascade'] = ''; $protections[$row->pr_page][] = $a; + + # Also check old restrictions + if($pageRestrictions[$row->pr_page]) { + 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] ); + if($restriction == '') + continue; + $protections[$row->pr_page][] = array( + 'type' => 'edit', + 'level' => $restriction, + 'expiry' => 'infinity', + ); + $protections[$row->pr_page][] = array( + 'type' => 'move', + 'level' => $restriction, + 'expiry' => 'infinity', + ); + } else { + $restriction = trim( $temp[1] ); + if($restriction == '') + continue; + $protections[$row->pr_page][] = array( + 'type' => $temp[0], + 'level' => $restriction, + 'expiry' => 'infinity', + ); + } + } + } } $db->freeResult($res); $imageIds = array(); foreach ($titles as $id => $title) - if ($title->getNamespace() == NS_IMAGE) + if ($title->getNamespace() == NS_FILE) $imageIds[] = $id; // To avoid code duplication $cascadeTypes = array( @@ -214,7 +263,7 @@ class ApiQueryInfo extends ApiQueryBase { array( 'prefix' => 'il', 'table' => 'imagelinks', - 'ns' => NS_IMAGE, + 'ns' => NS_FILE, 'title' => 'il_to', 'ids' => $imageIds ) @@ -256,7 +305,7 @@ class ApiQueryInfo extends ApiQueryBase { } // We don't need to check for pt stuff if there are no nonexistent titles - if($fld_protection && !empty($missing)) + if($fld_protection && count($missing)) { $this->resetQueryParams(); // Construct a custom WHERE clause that matches all titles in $missing @@ -278,8 +327,8 @@ class ApiQueryInfo extends ApiQueryBase { $images = array(); $others = array(); foreach ($missing as $title) - if ($title->getNamespace() == NS_IMAGE) - $images[] = $title->getDbKey(); + if ($title->getNamespace() == NS_FILE) + $images[] = $title->getDBKey(); else $others[] = $title; @@ -328,7 +377,7 @@ class ApiQueryInfo extends ApiQueryBase { 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), 'source' => $source->getPrefixedText() ); - $prottitles[NS_IMAGE][$row->il_to][] = $a; + $prottitles[NS_FILE][$row->il_to][] = $a; } $db->freeResult($res); } @@ -350,7 +399,7 @@ class ApiQueryInfo extends ApiQueryBase { else if($fld_talkid) $talktitles[] = $t->getTalkPage(); } - if(!empty($talktitles) || !empty($subjecttitles)) + if(count($talktitles) || count($subjecttitles)) { // Construct a custom WHERE clause that matches // all titles in $talktitles and $subjecttitles @@ -386,6 +435,7 @@ class ApiQueryInfo extends ApiQueryBase { if (!is_null($params['token'])) { $tokenFunctions = $this->getTokenFunctions(); + $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601, time()); foreach($params['token'] as $t) { $val = call_user_func($tokenFunctions[$t], $pageid, $title); @@ -397,46 +447,23 @@ class ApiQueryInfo extends ApiQueryBase { } if($fld_protection) { + $pageInfo['protection'] = array(); if (isset($protections[$pageid])) { $pageInfo['protection'] = $protections[$pageid]; $result->setIndexedTagName($pageInfo['protection'], 'pr'); - } else { - # 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(); - } } } - if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDbKey()])) - $pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDbKey()]; - if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDbKey()])) - $pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDbKey()]; + if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()])) + $pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()]; + if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()])) + $pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()]; + if($fld_url) { + $pageInfo['fullurl'] = $title->getFullURL(); + $pageInfo['editurl'] = $title->getFullURL('action=edit'); + } + if($fld_readable) + if($title->userCanRead()) + $pageInfo['readable'] = ''; $result->addValue(array ( 'query', @@ -444,19 +471,22 @@ class ApiQueryInfo extends ApiQueryBase { ), $pageid, $pageInfo); } - // Get edit/protect tokens and protection data for missing titles if requested - // Delete and move tokens are N/A for missing titles anyway - if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid) + // Get properties for missing titles if requested + if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid || + $fld_url || $fld_readable) { $res = &$result->getData(); foreach($missing as $pageid => $title) { if(!is_null($params['token'])) { $tokenFunctions = $this->getTokenFunctions(); + $res['query']['pages'][$pageid]['starttimestamp'] = wfTimestamp(TS_ISO_8601, time()); foreach($params['token'] as $t) { $val = call_user_func($tokenFunctions[$t], $pageid, $title); - if($val !== false) + if($val === false) + $this->setWarning("Action '$t' is not allowed for the current user"); + else $res['query']['pages'][$pageid][$t . 'token'] = $val; } } @@ -470,10 +500,17 @@ class ApiQueryInfo extends ApiQueryBase { $res['query']['pages'][$pageid]['protection'] = array(); $result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr'); } - if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDbKey()])) - $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDbKey()]; - if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDbKey()])) - $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDbKey()]; + if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()])) + $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()]; + if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()])) + $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()]; + if($fld_url) { + $res['query']['pages'][$pageid]['fullurl'] = $title->getFullURL(); + $res['query']['pages'][$pageid]['editurl'] = $title->getFullURL('action=edit'); + } + if($fld_readable) + if($title->userCanRead()) + $res['query']['pages'][$pageid]['readable'] = ''; } } } @@ -486,7 +523,9 @@ class ApiQueryInfo extends ApiQueryBase { ApiBase :: PARAM_TYPE => array ( 'protection', 'talkid', - 'subjectid' + 'subjectid', + 'url', + 'readable', )), 'token' => array ( ApiBase :: PARAM_DFLT => NULL, @@ -521,6 +560,6 @@ class ApiQueryInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryInfo.php 37191 2008-07-06 18:43:06Z brion $'; + return __CLASS__ . ': $Id: ApiQueryInfo.php 45683 2009-01-12 19:10:42Z raymond $'; } } diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php index e7d84fc3..8eaf8d02 100644 --- a/includes/api/ApiQueryLangLinks.php +++ b/includes/api/ApiQueryLangLinks.php @@ -58,7 +58,7 @@ class ApiQueryLangLinks extends ApiQueryBase { $this->dieUsage("Invalid continue param. You should pass the " . "original value returned by the previous query", "_badcontinue"); $llfrom = intval($cont[0]); - $lllang = $this->getDb()->strencode($cont[1]); + $lllang = $this->getDB()->strencode($cont[1]); $this->addWhere("ll_from > $llfrom OR ". "(ll_from = $llfrom AND ". "ll_lang >= '$lllang')"); @@ -134,6 +134,6 @@ class ApiQueryLangLinks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLangLinks.php 37534 2008-07-10 21:08:37Z brion $'; + return __CLASS__ . ': $Id: ApiQueryLangLinks.php 43271 2008-11-06 22:38:42Z siebrand $'; } } diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php index 546a599d..91b5b529 100644 --- a/includes/api/ApiQueryLinks.php +++ b/includes/api/ApiQueryLinks.php @@ -76,9 +76,9 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { $params = $this->extractRequestParams(); $this->addFields(array ( - $this->prefix . '_from pl_from', - $this->prefix . '_namespace pl_namespace', - $this->prefix . '_title pl_title' + $this->prefix . '_from AS pl_from', + $this->prefix . '_namespace AS pl_namespace', + $this->prefix . '_title AS pl_title' )); $this->addTables($this->table); @@ -92,7 +92,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { "original value returned by the previous query", "_badcontinue"); $plfrom = intval($cont[0]); $plns = intval($cont[1]); - $pltitle = $this->getDb()->strencode($this->titleToKey($cont[2])); + $pltitle = $this->getDB()->strencode($this->titleToKey($cont[2])); $this->addWhere("{$this->prefix}_from > $plfrom OR ". "({$this->prefix}_from = $plfrom AND ". "({$this->prefix}_namespace > $plns OR ". @@ -213,6 +213,6 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLinks.php 37909 2008-07-22 13:26:15Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryLinks.php 43271 2008-11-06 22:38:42Z siebrand $'; } } diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index 47a526bb..83c73b83 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -93,16 +93,15 @@ class ApiQueryLogEvents extends ApiQueryBase { $limit = $params['limit']; $this->addOption('LIMIT', $limit +1); - + + $index = false; $user = $params['user']; if (!is_null($user)) { - $userid = $db->selectField('user', 'user_id', array ( - 'user_name' => $user - )); + $userid = User::idFromName($user); if (!$userid) $this->dieUsage("User name $user not found", 'param_user'); $this->addWhereFld('log_user', $userid); - $this->addOption('USE INDEX', array('logging' => array('user_time','page_time'))); + $index = 'user_time'; } $title = $params['title']; @@ -112,8 +111,14 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->dieUsage("Bad title value '$title'", 'param_title'); $this->addWhereFld('log_namespace', $titleObj->getNamespace()); $this->addWhereFld('log_title', $titleObj->getDBkey()); - $this->addOption('USE INDEX', array('logging' => array('user_time','page_time'))); + + // Use the title index in preference to the user index if there is a conflict + $index = 'page_time'; } + if ( $index ) { + $this->addOption( 'USE INDEX', array( 'logging' => $index ) ); + } + $data = array (); $count = 0; @@ -134,6 +139,48 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->getResult()->setIndexedTagName($data, 'item'); $this->getResult()->addValue('query', $this->getModuleName(), $data); } + + public static function addLogParams($result, &$vals, $params, $type, $ts) { + $params = explode("\n", $params); + switch ($type) { + case 'move': + if (isset ($params[0])) { + $title = Title :: newFromText($params[0]); + if ($title) { + $vals2 = array(); + ApiQueryBase :: addTitleInfo($vals2, $title, "new_"); + $vals[$type] = $vals2; + $params = null; + } + } + break; + case 'patrol': + $vals2 = array(); + list( $vals2['cur'], $vals2['prev'], $vals2['auto'] ) = $params; + $vals[$type] = $vals2; + $params = null; + break; + case 'rights': + $vals2 = array(); + list( $vals2['old'], $vals2['new'] ) = $params; + $vals[$type] = $vals2; + $params = null; + break; + case 'block': + $vals2 = array(); + list( $vals2['duration'], $vals2['flags'] ) = $params; + $vals2['expiry'] = wfTimestamp(TS_ISO_8601, + strtotime($params[0], wfTimestamp(TS_UNIX, $ts))); + $vals[$type] = $vals2; + $params = null; + break; + } + if (!is_null($params)) { + $result->setIndexedTagName($params, 'param'); + $vals = array_merge($vals, $params); + } + return $vals; + } private function extractRowInfo($row) { $vals = array(); @@ -154,43 +201,9 @@ class ApiQueryLogEvents extends ApiQueryBase { } if ($this->fld_details && $row->log_params !== '') { - $params = explode("\n", $row->log_params); - switch ($row->log_type) { - case 'move': - if (isset ($params[0])) { - $title = Title :: newFromText($params[0]); - if ($title) { - $vals2 = array(); - ApiQueryBase :: addTitleInfo($vals2, $title, "new_"); - $vals[$row->log_type] = $vals2; - $params = null; - } - } - break; - case 'patrol': - $vals2 = array(); - list( $vals2['cur'], $vals2['prev'], $vals2['auto'] ) = $params; - $vals[$row->log_type] = $vals2; - $params = null; - break; - case 'rights': - $vals2 = array(); - list( $vals2['old'], $vals2['new'] ) = $params; - $vals[$row->log_type] = $vals2; - $params = null; - break; - case 'block': - $vals2 = array(); - list( $vals2['duration'], $vals2['flags'] ) = $params; - $vals[$row->log_type] = $vals2; - $params = null; - break; - } - - if (isset($params)) { - $this->getResult()->setIndexedTagName($params, 'param'); - $vals = array_merge($vals, $params); - } + self::addLogParams($this->getResult(), $vals, + $row->log_params, $row->log_type, + $row->log_timestamp); } if ($this->fld_user) { @@ -201,7 +214,7 @@ class ApiQueryLogEvents extends ApiQueryBase { if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->log_timestamp); } - if ($this->fld_comment && !empty ($row->log_comment)) { + if ($this->fld_comment && isset($row->log_comment)) { $vals['comment'] = $row->log_comment; } @@ -277,6 +290,6 @@ class ApiQueryLogEvents extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLogEvents.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryLogEvents.php 44234 2008-12-04 15:59:26Z catrope $'; } } diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php index 046157a6..e7b8bf46 100644 --- a/includes/api/ApiQueryRandom.php +++ b/includes/api/ApiQueryRandom.php @@ -48,13 +48,13 @@ if (!defined('MEDIAWIKI')) { $this->run($resultPageSet); } - protected function prepareQuery($randstr, $limit, $namespace, &$resultPageSet) { + protected function prepareQuery($randstr, $limit, $namespace, &$resultPageSet, $redirect) { $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->addWhereFld('page_is_redirect', $redirect); $this->addOption('USE INDEX', 'page_random'); if(is_null($resultPageSet)) $this->addFields(array('page_id', 'page_title', 'page_namespace')); @@ -89,7 +89,8 @@ if (!defined('MEDIAWIKI')) { $result = $this->getResult(); $data = array(); $this->pageIDs = array(); - $this->prepareQuery(wfRandom(), $params['limit'], $params['namespace'], $resultPageSet); + + $this->prepareQuery(wfRandom(), $params['limit'], $params['namespace'], $resultPageSet, $params['redirect']); $count = $this->runQuery($data, $resultPageSet); if($count < $params['limit']) { @@ -97,7 +98,7 @@ if (!defined('MEDIAWIKI')) { * 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->prepareQuery(0, $params['limit'] - $count, $params['namespace'], $resultPageSet, $params['redirect']); $this->runQuery($data, $resultPageSet); } @@ -129,13 +130,15 @@ if (!defined('MEDIAWIKI')) { ApiBase :: PARAM_MAX => 10, ApiBase :: PARAM_MAX2 => 20 ), + 'redirect' => false, ); } public function getParamDescription() { return array ( 'namespace' => 'Return pages in these namespaces only', - 'limit' => 'Limit how many random pages will be returned' + 'limit' => 'Limit how many random pages will be returned', + 'redirect' => 'Load a random redirect instead of a random page' ); } diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 2b8c6a92..04eb910f 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -43,16 +43,48 @@ class ApiQueryRecentChanges extends ApiQueryBase { private $fld_comment = false, $fld_user = false, $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false, $fld_sizes = false; + + protected function getTokenFunctions() { + // tokenname => function + // function prototype is func($pageid, $title, $rev) + // should return token or false + + // Don't call the hooks twice + if(isset($this->tokenFunctions)) + return $this->tokenFunctions; + + // If we're in JSON callback mode, no tokens can be obtained + if(!is_null($this->getMain()->getRequest()->getVal('callback'))) + return array(); + + $this->tokenFunctions = array( + 'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' ) + ); + wfRunHooks('APIQueryRecentChangesTokens', array(&$this->tokenFunctions)); + return $this->tokenFunctions; + } + + public static function getPatrolToken($pageid, $title, $rc) + { + global $wgUser; + if(!$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) + return false; + + // The patrol token is always the same, let's exploit that + static $cachedPatrolToken = null; + if(!is_null($cachedPatrolToken)) + return $cachedPatrolToken; + + $cachedPatrolToken = $wgUser->editToken(); + return $cachedPatrolToken; + } /** * Generates and outputs the result of this query based upon the provided parameters. */ public function execute() { - /* Initialize vars */ - $limit = $prop = $namespace = $titles = $show = $type = $dir = $start = $end = null; - /* Get the parameters of the request. */ - extract($this->extractRequestParams()); + $params = $this->extractRequestParams(); /* Build our basic query. Namely, something along the lines of: * SELECT * FROM recentchanges WHERE rc_timestamp > $start @@ -62,13 +94,13 @@ class ApiQueryRecentChanges extends ApiQueryBase { $db = $this->getDB(); $this->addTables('recentchanges'); $this->addOption('USE INDEX', array('recentchanges' => 'rc_timestamp')); - $this->addWhereRange('rc_timestamp', $dir, $start, $end); - $this->addWhereFld('rc_namespace', $namespace); + $this->addWhereRange('rc_timestamp', $params['dir'], $params['start'], $params['end']); + $this->addWhereFld('rc_namespace', $params['namespace']); $this->addWhereFld('rc_deleted', 0); - if(!empty($titles)) + if($params['titles']) { $lb = new LinkBatch; - foreach($titles as $t) + foreach($params['titles'] as $t) { $obj = Title::newFromText($t); $lb->addObj($obj); @@ -77,19 +109,19 @@ class ApiQueryRecentChanges extends ApiQueryBase { // LinkBatch refuses these, but we need them anyway if(!array_key_exists($obj->getNamespace(), $lb->data)) $lb->data[$obj->getNamespace()] = array(); - $lb->data[$obj->getNamespace()][$obj->getDbKey()] = 1; + $lb->data[$obj->getNamespace()][$obj->getDBKey()] = 1; } } - $where = $lb->constructSet('rc', $this->getDb()); + $where = $lb->constructSet('rc', $this->getDB()); if($where != '') $this->addWhere($where); } - if(!is_null($type)) - $this->addWhereFld('rc_type', $this->parseRCType($type)); + if(!is_null($params['type'])) + $this->addWhereFld('rc_type', $this->parseRCType($params['type'])); - if (!is_null($show)) { - $show = array_flip($show); + if (!is_null($params['show'])) { + $show = array_flip($params['show']); /* Check for conflicting parameters. */ if ((isset ($show['minor']) && isset ($show['!minor'])) @@ -103,7 +135,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { // Check permissions global $wgUser; - if((isset($show['patrolled']) || isset($show['!patrolled'])) && !$wgUser->isAllowed('patrol')) + if((isset($show['patrolled']) || isset($show['!patrolled'])) && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied'); /* Add additional conditions to query depending upon parameters. */ @@ -125,14 +157,15 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'rc_timestamp', 'rc_namespace', 'rc_title', + 'rc_cur_id', 'rc_type', 'rc_moved_to_ns', 'rc_moved_to_title' )); /* Determine what properties we need to display. */ - if (!is_null($prop)) { - $prop = array_flip($prop); + if (!is_null($params['prop'])) { + $prop = array_flip($params['prop']); /* Set up internal members based upon params. */ $this->fld_comment = isset ($prop['comment']); @@ -144,14 +177,14 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->fld_sizes = isset ($prop['sizes']); $this->fld_redirect = isset($prop['redirect']); $this->fld_patrolled = isset($prop['patrolled']); + $this->fld_loginfo = isset($prop['loginfo']); global $wgUser; - if($this->fld_patrolled && !$wgUser->isAllowed('patrol')) + if($this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied'); /* 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); $this->addFieldsIf('rc_last_oldid', $this->fld_ids); $this->addFieldsIf('rc_comment', $this->fld_comment); @@ -163,6 +196,10 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addFieldsIf('rc_old_len', $this->fld_sizes); $this->addFieldsIf('rc_new_len', $this->fld_sizes); $this->addFieldsIf('rc_patrolled', $this->fld_patrolled); + $this->addFieldsIf('rc_logid', $this->fld_loginfo); + $this->addFieldsIf('rc_log_type', $this->fld_loginfo); + $this->addFieldsIf('rc_log_action', $this->fld_loginfo); + $this->addFieldsIf('rc_params', $this->fld_loginfo); if($this->fld_redirect || isset($show['redirect']) || isset($show['!redirect'])) { $this->addTables('page'); @@ -170,9 +207,8 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addFields('page_is_redirect'); } } - /* 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); + $this->token = $params['token']; + $this->addOption('LIMIT', $params['limit'] +1); $data = array (); $count = 0; @@ -183,7 +219,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { /* Iterate through the rows, adding data extracted from them to our query result. */ while ($row = $db->fetchObject($res)) { - if (++ $count > $limit) { + if (++ $count > $params['limit']) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); break; @@ -215,7 +251,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { 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)) + if (isset($row->rc_moved_to_title) && $row->rc_moved_to_title !== '') $movedToTitle = Title :: makeTitle($row->rc_moved_to_ns, $row->rc_moved_to_title); /* Determine the title of the page that has been changed. */ @@ -228,11 +264,11 @@ class ApiQueryRecentChanges extends ApiQueryBase { /* 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; + 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; } @@ -279,7 +315,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); /* Add edit summary / log summary. */ - if ($this->fld_comment && !empty ($row->rc_comment)) { + if ($this->fld_comment && isset($row->rc_comment)) { $vals['comment'] = $row->rc_comment; } @@ -290,6 +326,29 @@ class ApiQueryRecentChanges extends ApiQueryBase { /* Add the patrolled flag */ if ($this->fld_patrolled && $row->rc_patrolled == 1) $vals['patrolled'] = ''; + + if ($this->fld_loginfo && $row->rc_type == RC_LOG) { + $vals['logid'] = $row->rc_logid; + $vals['logtype'] = $row->rc_log_type; + $vals['logaction'] = $row->rc_log_action; + ApiQueryLogEvents::addLogParams($this->getResult(), + $vals, $row->rc_params, + $row->rc_log_type, $row->rc_timestamp); + } + + if(!is_null($this->token)) + { + $tokenFunctions = $this->getTokenFunctions(); + foreach($this->token as $t) + { + $val = call_user_func($tokenFunctions[$t], $row->rc_cur_id, + $title, RecentChange::newFromRow($row)); + if($val === false) + $this->setWarning("Action '$t' is not allowed for the current user"); + else + $vals[$t . 'token'] = $val; + } + } return $vals; } @@ -345,9 +404,14 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'ids', 'sizes', 'redirect', - 'patrolled' + 'patrolled', + 'loginfo', ) ), + 'token' => array( + ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()), + ApiBase :: PARAM_ISMULTI => true + ), 'show' => array ( ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array ( @@ -389,6 +453,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'namespace' => 'Filter log entries to only this namespace(s)', 'titles' => 'Filter log entries to only these page titles', 'prop' => 'Include additional pieces of information', + 'token' => 'Which tokens to obtain for each change', 'show' => array ( 'Show only items that meet this criteria.', 'For example, to see only minor edits done by logged-in users, set show=minor|!anon' @@ -409,6 +474,6 @@ class ApiQueryRecentChanges extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 37909 2008-07-22 13:26:15Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 44719 2008-12-17 16:34:01Z catrope $'; } } diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 1fd2d7c6..977e792b 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -58,7 +58,7 @@ class ApiQueryRevisions extends ApiQueryBase { return array(); $this->tokenFunctions = array( - 'rollback' => array( 'ApiQueryRevisions','getRollbackToken' ) + 'rollback' => array( 'ApiQueryRevisions', 'getRollbackToken' ) ); wfRunHooks('APIQueryRevisionsTokens', array(&$this->tokenFunctions)); return $this->tokenFunctions; @@ -74,14 +74,16 @@ class ApiQueryRevisions extends ApiQueryBase { } public function execute() { - $limit = $startid = $endid = $start = $end = $dir = $prop = $user = $excludeuser = $expandtemplates = $section = $token = null; - extract($this->extractRequestParams(false)); + $params = $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 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)); + $enumRevMode = (!is_null($params['user']) || !is_null($params['excludeuser']) || + !is_null($params['limit']) || !is_null($params['startid']) || + !is_null($params['endid']) || $params['dir'] === 'newer' || + !is_null($params['start']) || !is_null($params['end'])); $pageSet = $this->getPageSet(); @@ -100,8 +102,10 @@ class ApiQueryRevisions extends ApiQueryBase { $this->addTables('revision'); $this->addFields( Revision::selectFields() ); + $this->addTables( 'page' ); + $this->addWhere('page_id = rev_page'); - $prop = array_flip($prop); + $prop = array_flip($params['prop']); // Optional fields $this->fld_ids = isset ($prop['ids']); @@ -111,11 +115,9 @@ class ApiQueryRevisions extends ApiQueryBase { $this->fld_comment = isset ($prop['comment']); $this->fld_size = isset ($prop['size']); $this->fld_user = isset ($prop['user']); - $this->token = $token; + $this->token = $params['token']; - if ( !is_null($this->token) || ( $this->fld_content && $this->expandTemplates ) || $pageCount > 0) { - $this->addTables( 'page' ); - $this->addWhere('page_id=rev_page'); + if ( !is_null($this->token) || $pageCount > 0) { $this->addFields( Revision::selectPageFields() ); } @@ -136,15 +138,17 @@ class ApiQueryRevisions extends ApiQueryBase { $this->fld_content = true; - $this->expandTemplates = $expandtemplates; - if(isset($section)) - $this->section = $section; + $this->expandTemplates = $params['expandtemplates']; + $this->generateXML = $params['generatexml']; + if(isset($params['section'])) + $this->section = $params['section']; else $this->section = false; } $userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 ); $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 ); + $limit = $params['limit']; if( $limit == 'max' ) { $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $this->getResult()->addValue( 'limits', $this->getModuleName(), $limit ); @@ -153,13 +157,13 @@ class ApiQueryRevisions extends ApiQueryBase { if ($enumRevMode) { // This is mostly to prevent parameter errors (and optimize SQL?) - if (!is_null($startid) && !is_null($start)) + if (!is_null($params['startid']) && !is_null($params['start'])) $this->dieUsage('start and startid cannot be used together', 'badparams'); - if (!is_null($endid) && !is_null($end)) + if (!is_null($params['endid']) && !is_null($params['end'])) $this->dieUsage('end and endid cannot be used together', 'badparams'); - if(!is_null($user) && !is_null( $excludeuser)) + if(!is_null($params['user']) && !is_null($params['excludeuser'])) $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); // This code makes an assumption that sorting by rev_id and rev_timestamp produces @@ -169,10 +173,12 @@ 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) && is_null($endid)) - $this->addWhereRange('rev_timestamp', $dir, $start, $end); + if (is_null($params['startid']) && is_null($params['endid'])) + $this->addWhereRange('rev_timestamp', $params['dir'], + $params['start'], $params['end']); else - $this->addWhereRange('rev_id', $dir, $startid, $endid); + $this->addWhereRange('rev_id', $params['dir'], + $params['startid'], $params['endid']); // must manually initialize unset limit if (is_null($limit)) @@ -182,30 +188,38 @@ class ApiQueryRevisions extends ApiQueryBase { // There is only one ID, use it $this->addWhereFld('rev_page', current(array_keys($pageSet->getGoodTitles()))); - if(!is_null($user)) { - $this->addWhereFld('rev_user_text', $user); - } elseif (!is_null( $excludeuser)) { - $this->addWhere('rev_user_text != ' . $this->getDB()->addQuotes($excludeuser)); + if(!is_null($params['user'])) { + $this->addWhereFld('rev_user_text', $params['user']); + } elseif (!is_null( $params['excludeuser'])) { + $this->addWhere('rev_user_text != ' . + $this->getDB()->addQuotes($params['excludeuser'])); } } elseif ($revCount > 0) { - $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax); + $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; + $revs = $pageSet->getRevisionIDs(); + if(self::truncateArray($revs, $max)) + $this->setWarning("Too many values supplied for parameter 'revids': the limit is $max"); // Get all revision IDs - $this->addWhereFld('rev_id', array_keys($pageSet->getRevisionIDs())); + $this->addWhereFld('rev_id', array_keys($revs)); // assumption testing -- we should never get more then $revCount rows. $limit = $revCount; } elseif ($pageCount > 0) { + $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; + $titles = $pageSet->getGoodTitles(); + if(self::truncateArray($titles, $max)) + $this->setWarning("Too many values supplied for parameter 'titles': the limit is $max"); + // When working in multi-page non-enumeration mode, // limit to the latest revision only $this->addWhere('page_id=rev_page'); $this->addWhere('page_latest=rev_id'); - $this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax); - + // Get all page IDs - $this->addWhereFld('page_id', array_keys($pageSet->getGoodTitles())); + $this->addWhereFld('page_id', array_keys($titles)); // assumption testing -- we should never get more then $pageCount rows. $limit = $pageCount; @@ -281,7 +295,7 @@ class ApiQueryRevisions extends ApiQueryBase { if ($this->fld_comment) { $comment = $revision->getComment(); - if (!empty($comment)) + if (strval($comment) !== '') $vals['comment'] = $comment; } @@ -312,6 +326,17 @@ class ApiQueryRevisions extends ApiQueryBase { if($text === false) $this->dieUsage("There is no section {$this->section} in r".$revision->getId(), 'nosuchsection'); } + if ($this->generateXML) { + $wgParser->startExternalParse( $title, new ParserOptions(), OT_PREPROCESS ); + $dom = $wgParser->preprocessToDom( $text ); + if ( is_callable( array( $dom, 'saveXML' ) ) ) { + $xml = $dom->saveXML(); + } else { + $xml = $dom->__toString(); + } + $vals['parsetree'] = $xml; + + } if ($this->expandTemplates) { $text = $wgParser->preprocess( $text, $title, new ParserOptions() ); } @@ -366,11 +391,9 @@ class ApiQueryRevisions extends ApiQueryBase { 'excludeuser' => array( ApiBase :: PARAM_TYPE => 'user' ), - 'expandtemplates' => false, - 'section' => array( - ApiBase :: PARAM_TYPE => 'integer' - ), + 'generatexml' => false, + 'section' => null, 'token' => array( ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()), ApiBase :: PARAM_ISMULTI => true @@ -390,6 +413,7 @@ class ApiQueryRevisions extends ApiQueryBase { 'user' => 'only include revisions made by user', 'excludeuser' => 'exclude revisions made by user', 'expandtemplates' => 'expand templates in revision content', + 'generatexml' => 'generate XML parse tree for revision content', 'section' => 'only retrieve the content of this section', 'token' => 'Which tokens to obtain for each revision', ); @@ -424,6 +448,6 @@ class ApiQueryRevisions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRevisions.php 37300 2008-07-08 08:42:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiQueryRevisions.php 44719 2008-12-17 16:34:01Z catrope $'; } } diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index 84a2ec63..cb020fff 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -53,7 +53,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $limit = $params['limit']; $query = $params['search']; - if (is_null($query) || empty($query)) + $what = $params['what']; + if (strval($query) === '') $this->dieUsage("empty search string is not allowed", 'param-search'); $search = SearchEngine::create(); @@ -61,13 +62,30 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $search->setNamespaces( $params['namespace'] ); $search->showRedirects = $params['redirects']; - if ($params['what'] == 'text') + if ($what == 'text') { $matches = $search->searchText( $query ); - else + } elseif( $what == 'title' ) { $matches = $search->searchTitle( $query ); + } else { + // We default to title searches; this is a terrible legacy + // of the way we initially set up the MySQL fulltext-based + // search engine with separate title and text fields. + // In the future, the default should be for a combined index. + $what = 'title'; + $matches = $search->searchTitle( $query ); + + // Not all search engines support a separate title search, + // for instance the Lucene-based engine we use on Wikipedia. + // In this case, fall back to full-text search (which will + // include titles in it!) + if( is_null( $matches ) ) { + $what = 'text'; + $matches = $search->searchText( $query ); + } + } if (is_null($matches)) - $this->dieUsage("{$params['what']} search is disabled", - "search-{$params['what']}-disabled"); + $this->dieUsage("{$what} search is disabled", + "search-{$what}-disabled"); $data = array (); $count = 0; @@ -78,8 +96,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { break; } - // Silently skip broken titles - if ($result->isBrokenTitle()) continue; + // Silently skip broken and missing titles + if ($result->isBrokenTitle() || $result->isMissingRevision()) + continue; $title = $result->getTitle(); if (is_null($resultPageSet)) { @@ -109,7 +128,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { ApiBase :: PARAM_ISMULTI => true, ), 'what' => array ( - ApiBase :: PARAM_DFLT => 'title', + ApiBase :: PARAM_DFLT => null, ApiBase :: PARAM_TYPE => array ( 'title', 'text', @@ -151,6 +170,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySearch.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiQuerySearch.php 44186 2008-12-03 19:33:57Z catrope $'; } } diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 1fd3b888..84757f7f 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -57,6 +57,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { case 'specialpagealiases': $this->appendSpecialPageAliases( $p ); break; + case 'magicwords': + $this->appendMagicWords( $p ); + break; case 'interwikimap': $filteriw = isset( $params['filteriw'] ) ? $params['filteriw'] : false; $this->appendInterwikiMap( $p, $filteriw ); @@ -70,6 +73,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { case 'usergroups': $this->appendUserGroups( $p ); break; + case 'extensions': + $this->appendExtensions( $p ); + break; default : ApiBase :: dieDebug( __METHOD__, "Unknown prop=$p" ); } @@ -129,8 +135,13 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'id' => $ns ); ApiResult :: setContent( $data[$ns], $title ); - if( MWNamespace::hasSubpages($ns) ) + $canonical = MWNamespace::getCanonicalName( $ns ); + + if( MWNamespace::hasSubpages( $ns ) ) $data[$ns]['subpages'] = ''; + + if( $canonical ) + $data[$ns]['canonical'] = strtr($canonical, '_', ' '); } $this->getResult()->setIndexedTagName( $data, 'ns' ); @@ -138,9 +149,11 @@ class ApiQuerySiteinfo extends ApiQueryBase { } protected function appendNamespaceAliases( $property ) { - global $wgNamespaceAliases; + global $wgNamespaceAliases, $wgContLang; + $wgContLang->load(); + $aliases = array_merge($wgNamespaceAliases, $wgContLang->namespaceAliases); $data = array(); - foreach( $wgNamespaceAliases as $title => $ns ) { + foreach( $aliases as $title => $ns ) { $item = array( 'id' => $ns ); @@ -164,6 +177,22 @@ class ApiQuerySiteinfo extends ApiQueryBase { $this->getResult()->setIndexedTagName( $data, 'specialpage' ); $this->getResult()->addValue( 'query', $property, $data ); } + + protected function appendMagicWords( $property ) { + global $wgContLang; + $data = array(); + foreach($wgContLang->getMagicWords() as $magicword => $aliases) + { + $caseSensitive = array_shift($aliases); + $arr = array('name' => $magicword, 'aliases' => $aliases); + if($caseSensitive) + $arr['case-sensitive'] = ''; + $this->getResult()->setIndexedTagName($arr['aliases'], 'alias'); + $data[] = $arr; + } + $this->getResult()->setIndexedTagName($data, 'magicword'); + $this->getResult()->addValue('query', $property, $data); + } protected function appendInterwikiMap( $property, $filter ) { $this->resetQueryParams(); @@ -174,7 +203,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $this->addWhere( 'iw_local = 1' ); elseif( $filter === '!local' ) $this->addWhere( 'iw_local = 0' ); - elseif( $filter !== false ) + elseif( $filter ) ApiBase :: dieDebug( __METHOD__, "Unknown filter=$filter" ); $this->addOption( 'ORDER BY', 'iw_prefix' ); @@ -239,7 +268,8 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['edits'] = intval( SiteStats::edits() ); $data['images'] = intval( SiteStats::images() ); $data['users'] = intval( SiteStats::users() ); - $data['admins'] = intval( SiteStats::admins() ); + $data['activeusers'] = intval( SiteStats::activeUsers() ); + $data['admins'] = intval( SiteStats::numberingroup('sysop') ); $data['jobs'] = intval( SiteStats::jobs() ); $this->getResult()->addValue( 'query', $property, $data ); } @@ -257,6 +287,40 @@ class ApiQuerySiteinfo extends ApiQueryBase { $this->getResult()->addValue( 'query', $property, $data ); } + protected function appendExtensions( $property ) { + global $wgExtensionCredits; + $data = array(); + foreach ( $wgExtensionCredits as $type => $extensions ) { + foreach ( $extensions as $ext ) { + $ret = array(); + $ret['type'] = $type; + if ( isset( $ext['name'] ) ) + $ret['name'] = $ext['name']; + if ( isset( $ext['description'] ) ) + $ret['description'] = $ext['description']; + if ( isset( $ext['descriptionmsg'] ) ) + $ret['descriptionmsg'] = $ext['descriptionmsg']; + if ( isset( $ext['author'] ) ) { + $ret['author'] = is_array( $ext['author'] ) ? + implode( ', ', $ext['author' ] ) : $ext['author']; + } + if ( isset( $ext['version'] ) ) { + $ret['version'] = $ext['version']; + } elseif ( isset( $ext['svn-revision'] ) && + preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/', + $ext['svn-revision'], $m ) ) + { + $ret['version'] = 'r' . $m[1]; + } + $data[] = $ret; + } + } + + $this->getResult()->setIndexedTagName( $data, 'ext' ); + $this->getResult()->addValue( 'query', $property, $data ); + } + + public function getAllowedParams() { return array( 'prop' => array( @@ -267,10 +331,12 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'namespaces', 'namespacealiases', 'specialpagealiases', + 'magicwords', 'interwikimap', 'dbrepllag', 'statistics', 'usergroups', + 'extensions', ) ), 'filteriw' => array( @@ -288,13 +354,15 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'prop' => array( 'Which sysinfo properties to get:', ' "general" - Overall system information', - ' "namespaces" - List of registered namespaces (localized)', + ' "namespaces" - List of registered namespaces and their canonical names', ' "namespacealiases" - List of registered namespace aliases', ' "specialpagealiases" - List of special page aliases', + ' "magicwords" - List of magic words and their aliases', ' "statistics" - Returns site statistics', ' "interwikimap" - Returns interwiki map (optionally filtered)', ' "dbrepllag" - Returns database server with the highest replication lag', ' "usergroups" - Returns user groups and the associated permissions', + ' "extensions" - Returns extensions installed on the wiki', ), 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map', 'showalldb' => 'List all database servers, not just the one lagging the most', @@ -314,6 +382,6 @@ class ApiQuerySiteinfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 37034 2008-07-04 09:21:11Z vasilievvv $'; + return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 44862 2008-12-20 23:49:16Z catrope $'; } } diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index c477acdb..be6c8bc4 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -62,6 +62,7 @@ class ApiQueryContributions extends ApiQueryBase { if(isset($this->params['userprefix'])) { $this->prefixMode = true; + $this->multiUserMode = true; $this->userprefix = $this->params['userprefix']; } else @@ -72,6 +73,7 @@ class ApiQueryContributions extends ApiQueryBase { foreach($this->params['user'] as $u) $this->prepareUsername($u); $this->prefixMode = false; + $this->multiUserMode = (count($this->params['user']) > 1); } $this->prepareQuery(); @@ -87,7 +89,10 @@ class ApiQueryContributions extends ApiQueryBase { 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... - $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp)); + if($this->multiUserMode) + $this->setContinueEnumParameter('continue', $this->continueStr($row)); + else + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp)); break; } @@ -132,13 +137,28 @@ class ApiQueryContributions extends ApiQueryBase { //anything we retrieve. $this->addTables(array('revision', 'page')); $this->addWhere('page_id=rev_page'); + + // Handle continue parameter + if($this->multiUserMode && !is_null($this->params['continue'])) + { + $continue = explode('|', $this->params['continue']); + if(count($continue) != 2) + $this->dieUsage("Invalid continue param. You should pass the original " . + "value returned by the previous query", "_badcontinue"); + $encUser = $this->getDB()->strencode($continue[0]); + $encTS = wfTimestamp(TS_MW, $continue[1]); + $op = ($this->params['dir'] == 'older' ? '<' : '>'); + $this->addWhere("rev_user_text $op '$encUser' OR " . + "(rev_user_text = '$encUser' AND " . + "rev_timestamp $op= '$encTS')"); + } $this->addWhereFld('rev_deleted', 0); // We only want pages by the specified users. if($this->prefixMode) - $this->addWhere("rev_user_text LIKE '" . $this->getDb()->escapeLike($this->userprefix) . "%'"); + $this->addWhere("rev_user_text LIKE '" . $this->getDB()->escapeLike($this->userprefix) . "%'"); else - $this->addWhereFld( 'rev_user_text', $this->usernames ); + $this->addWhereFld('rev_user_text', $this->usernames); // ... and in the specified timeframe. // Ensure the same sort order for rev_user_text and rev_timestamp // so our query is indexed @@ -157,6 +177,7 @@ class ApiQueryContributions extends ApiQueryBase { $this->addWhereIf('rev_minor_edit != 0', isset ($show['minor'])); } $this->addOption('LIMIT', $this->params['limit'] + 1); + $this->addOption( 'USE INDEX', array( 'revision' => 'usertext_timestamp' ) ); // Mandatory fields: timestamp allows request continuation // ns+title checks if the user has access rights for this page @@ -207,11 +228,17 @@ class ApiQueryContributions extends ApiQueryBase { $vals['top'] = ''; } - if ($this->fld_comment && !empty ($row->rev_comment)) + if ($this->fld_comment && isset( $row->rev_comment ) ) $vals['comment'] = $row->rev_comment; return $vals; } + + private function continueStr($row) + { + return $row->rev_user_text . '|' . + wfTimestamp(TS_ISO_8601, $row->rev_timestamp); + } public function getAllowedParams() { return array ( @@ -228,6 +255,7 @@ class ApiQueryContributions extends ApiQueryBase { 'end' => array ( ApiBase :: PARAM_TYPE => 'timestamp' ), + 'continue' => null, 'user' => array ( ApiBase :: PARAM_ISMULTI => true ), @@ -269,6 +297,7 @@ class ApiQueryContributions extends ApiQueryBase { 'limit' => 'The maximum number of contributions to return.', 'start' => 'The start timestamp to return from.', 'end' => 'The end timestamp to return to.', + 'continue' => 'When more results are available, use this to continue.', 'user' => 'The user to retrieve contributions for.', 'userprefix' => 'Retrieve contibutions for all users whose names begin with this value. Overrides ucuser.', 'dir' => 'The direction to search (older or newer).', @@ -290,6 +319,6 @@ class ApiQueryContributions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserContributions.php 37383 2008-07-09 11:44:49Z btongminh $'; + return __CLASS__ . ': $Id: ApiQueryUserContributions.php 43271 2008-11-06 22:38:42Z siebrand $'; } } diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index 2d55a352..203b7e25 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -76,12 +76,16 @@ class ApiQueryUserInfo extends ApiQueryBase { $result->setIndexedTagName($vals['groups'], 'g'); // even if empty } if (isset($this->prop['rights'])) { - $vals['rights'] = $wgUser->getRights(); + // User::getRights() may return duplicate values, strip them + $vals['rights'] = array_values(array_unique($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['preferencestoken']) && is_null($this->getMain()->getRequest()->getVal('callback'))) { + $vals['preferencestoken'] = $wgUser->editToken(); + } if (isset($this->prop['editcount'])) { $vals['editcount'] = $wgUser->getEditCount(); } @@ -110,6 +114,7 @@ class ApiQueryUserInfo extends ApiQueryBase { if(!$wgUser->isAnon()) $categories[] = 'newbie'; } + $categories = array_merge($categories, $wgUser->getGroups()); // Now get the actual limits $retval = array(); @@ -134,6 +139,7 @@ class ApiQueryUserInfo extends ApiQueryBase { 'groups', 'rights', 'options', + 'preferencestoken', 'editcount', 'ratelimits' ) @@ -168,6 +174,6 @@ class ApiQueryUserInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserInfo.php 35186 2008-05-22 16:39:43Z brion $'; + return __CLASS__ . ': $Id: ApiQueryUserInfo.php 43764 2008-11-20 15:15:00Z catrope $'; } } diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index a8147567..e50d8d82 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -68,15 +68,13 @@ if (!defined('MEDIAWIKI')) { else $goodNames[] = $n; } - if(empty($goodNames)) + if(!count($goodNames)) return $retval; - $db = $this->getDb(); + $db = $this->getDB(); $this->addTables('user', 'u1'); - $this->addFields('u1.user_name'); + $this->addFields('u1.*'); $this->addWhereFld('u1.user_name', $goodNames); - $this->addFieldsIf('u1.user_editcount', isset($this->prop['editcount'])); - $this->addFieldsIf('u1.user_registration', isset($this->prop['registration'])); if(isset($this->prop['groups'])) { $this->addTables('user_groups'); @@ -96,20 +94,26 @@ if (!defined('MEDIAWIKI')) { $data = array(); $res = $this->select(__METHOD__); while(($r = $db->fetchObject($res))) { - $data[$r->user_name]['name'] = $r->user_name; + $user = User::newFromRow($r); + $name = $user->getName(); + $data[$name]['name'] = $name; if(isset($this->prop['editcount'])) - $data[$r->user_name]['editcount'] = $r->user_editcount; + // No proper member function in User class for this + $data[$name]['editcount'] = $r->user_editcount; if(isset($this->prop['registration'])) - $data[$r->user_name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $r->user_registration); + // Nor for this one + $data[$name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $r->user_registration); if(isset($this->prop['groups'])) // This row contains only one group, others will be added from other rows if(!is_null($r->ug_group)) - $data[$r->user_name]['groups'][] = $r->ug_group; + $data[$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; + $data[$name]['blockedby'] = $r->blocker_name; + $data[$name]['blockreason'] = $r->ipb_reason; } + if(isset($this->prop['emailable']) && $user->canReceiveEmail()) + $data[$name]['emailable'] = ''; } // Second pass: add result data to $retval @@ -134,7 +138,8 @@ if (!defined('MEDIAWIKI')) { 'blockinfo', 'groups', 'editcount', - 'registration' + 'registration', + 'emailable', ) ), 'users' => array( @@ -147,9 +152,11 @@ if (!defined('MEDIAWIKI')) { 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' + ' 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', + ' registration - adds the user\'s registration timestamp', + ' emailable - tags if the user can and wants to receive e-mail through [[Special:Emailuser]]', ), 'users' => 'A list of users to obtain the same information for' ); @@ -164,6 +171,6 @@ if (!defined('MEDIAWIKI')) { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUsers.php 38183 2008-07-29 12:58:04Z rotem $'; + return __CLASS__ . ': $Id: ApiQueryUsers.php 44231 2008-12-04 14:42:30Z catrope $'; } } diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index d17e83f6..ed3482fb 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -59,12 +59,11 @@ 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 = $show = null; - extract($this->extractRequestParams()); + $params = $this->extractRequestParams(); - if (!is_null($prop) && is_null($resultPageSet)) { + if (!is_null($params['prop']) && is_null($resultPageSet)) { - $prop = array_flip($prop); + $prop = array_flip($params['prop']); $this->fld_ids = isset($prop['ids']); $this->fld_title = isset($prop['title']); @@ -76,8 +75,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->fld_patrol = isset($prop['patrol']); if ($this->fld_patrol) { - global $wgUseRCPatrol, $wgUser; - if (!$wgUseRCPatrol || !$wgUser->isAllowed('patrol')) + global $wgUser; + if (!$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) $this->dieUsage('patrol property is not available', 'patrol'); } } @@ -100,7 +99,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addFieldsIf('rc_old_len', $this->fld_sizes); $this->addFieldsIf('rc_new_len', $this->fld_sizes); } - elseif ($allrev) { + elseif ($params['allrev']) { $this->addFields(array ( 'rc_this_oldid', 'rc_namespace', @@ -131,20 +130,26 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'rc_deleted' => 0, )); - $this->addWhereRange('rc_timestamp', $dir, $start, $end); - $this->addWhereFld('wl_namespace', $namespace); - $this->addWhereIf('rc_this_oldid=page_latest', !$allrev); + $this->addWhereRange('rc_timestamp', $params['dir'], $params['start'], $params['end']); + $this->addWhereFld('wl_namespace', $params['namespace']); + $this->addWhereIf('rc_this_oldid=page_latest', !$params['allrev']); - if (!is_null($show)) { - $show = array_flip($show); + if (!is_null($params['show'])) { + $show = array_flip($params['show']); /* Check for conflicting parameters. */ if ((isset ($show['minor']) && isset ($show['!minor'])) || (isset ($show['bot']) && isset ($show['!bot'])) - || (isset ($show['anon']) && isset ($show['!anon']))) { + || (isset ($show['anon']) && isset ($show['!anon'])) + || (isset ($show['patrolled']) && isset ($show['!patrolled']))) { $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); } + + // Check permissions + global $wgUser; + if((isset($show['patrolled']) || isset($show['!patrolled'])) && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) + $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied'); /* Add additional conditions to query depending upon parameters. */ $this->addWhereIf('rc_minor = 0', isset ($show['!minor'])); @@ -153,13 +158,15 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $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->addWhereIf('rc_patrolled = 0', isset($show['!patrolled'])); + $this->addWhereIf('rc_patrolled != 0', isset($show['patrolled'])); } # 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->addWhereIf("rc_timestamp > ''", !isset ($params['start']) && !isset ($params['end']) && $wgDBtype == 'mysql'); - $this->addOption('LIMIT', $limit +1); + $this->addOption('LIMIT', $params['limit'] +1); $data = array (); $count = 0; @@ -167,7 +174,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $db = $this->getDB(); while ($row = $db->fetchObject($res)) { - if (++ $count > $limit) { + if (++ $count > $params['limit']) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); break; @@ -178,7 +185,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { if ($vals) $data[] = $vals; } else { - if ($allrev) { + if ($params['allrev']) { $data[] = intval($row->rc_this_oldid); } else { $data[] = intval($row->rc_cur_id); @@ -192,7 +199,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->getResult()->setIndexedTagName($data, 'item'); $this->getResult()->addValue('query', $this->getModuleName(), $data); } - elseif ($allrev) { + elseif ($params['allrev']) { $resultPageSet->populateFromRevisionIDs($data); } else { $resultPageSet->populateFromPageIDs($data); @@ -237,7 +244,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $vals['newlen'] = intval($row->rc_new_len); } - if ($this->fld_comment && !empty ($row->rc_comment)) + if ($this->fld_comment && isset( $row->rc_comment )) $vals['comment'] = $row->rc_comment; return $vals; @@ -292,7 +299,9 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'bot', '!bot', 'anon', - '!anon' + '!anon', + 'patrolled', + '!patrolled', ) ) ); @@ -329,6 +338,6 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlist.php 37909 2008-07-22 13:26:15Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryWatchlist.php 44719 2008-12-17 16:34:01Z catrope $'; } } diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php new file mode 100644 index 00000000..e9951b42 --- /dev/null +++ b/includes/api/ApiQueryWatchlistRaw.php @@ -0,0 +1,179 @@ +<?php + +/* + * Created on Oct 4, 2008 + * + * API for MediaWiki 1.8+ + * + * Copyright (C) 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * This query action allows clients to retrieve a list of pages + * on the logged-in user's watchlist. + * + * @ingroup API + */ +class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'wr'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + global $wgUser; + + $this->selectNamedDB('watchlist', DB_SLAVE, 'watchlist'); + + if (!$wgUser->isLoggedIn()) + $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin'); + $params = $this->extractRequestParams(); + $prop = array_flip((array)$params['prop']); + $show = array_flip((array)$params['show']); + if(isset($show['changed']) && isset($show['!changed'])) + $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); + + $this->addTables('watchlist'); + $this->addFields(array('wl_namespace', 'wl_title')); + $this->addFieldsIf('wl_notificationtimestamp', isset($prop['changed'])); + $this->addWhereFld('wl_user', $wgUser->getId()); + $this->addWhereFld('wl_namespace', $params['namespace']); + $this->addWhereIf('wl_notificationtimestamp IS NOT NULL', isset($show['changed'])); + $this->addWhereIf('wl_notificationtimestamp IS NULL', isset($show['!changed'])); + if(isset($params['continue'])) + { + $cont = explode('|', $params['continue']); + if(count($cont) != 2) + $this->dieUsage("Invalid continue param. You should pass the " . + "original value returned by the previous query", "_badcontinue"); + $ns = intval($cont[0]); + $title = $this->getDB()->strencode($this->titleToKey($cont[1])); + $this->addWhere("wl_namespace > '$ns' OR ". + "(wl_namespace = '$ns' AND ". + "wl_title >= '$title')"); + } + // Don't ORDER BY wl_namespace if it's constant in the WHERE clause + if(count($params['namespace']) == 1) + $this->addOption('ORDER BY', 'wl_title'); + else + $this->addOption('ORDER BY', 'wl_namespace, wl_title'); + $this->addOption('LIMIT', $params['limit'] + 1); + $res = $this->select(__METHOD__); + + $db = $this->getDB(); + $data = array(); + $titles = array(); + $count = 0; + while($row = $db->fetchObject($res)) + { + if(++$count > $params['limit']) + { + // We've reached the one extra which shows that there are additional pages to be had. Stop here... + $this->setContinueEnumParameter('continue', $row->wl_namespace . '|' . + $this->keyToTitle($row->wl_title)); + break; + } + $t = Title::makeTitle($row->wl_namespace, $row->wl_title); + if(is_null($resultPageSet)) + { + $vals = array(); + ApiQueryBase::addTitleInfo($vals, $t); + if(isset($prop['changed']) && !is_null($row->wl_notificationtimestamp)) + $vals['changed'] = wfTimestamp(TS_ISO_8601, $row->wl_notificationtimestamp); + $data[] = $vals; + } + else + $titles[] = $t; + } + if(is_null($resultPageSet)) + { + $this->getResult()->setIndexedTagName($data, 'wr'); + $this->getResult()->addValue(null, $this->getModuleName(), $data); + } + else + $resultPageSet->populateFromTitles($titles); + } + + public function getAllowedParams() { + return array ( + 'continue' => null, + '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_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'changed', + ) + ), + 'show' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'changed', + '!changed', + ) + ) + ); + } + + public function getParamDescription() { + return array ( + 'continue' => 'When more results are available, use this to continue', + 'namespace' => 'Only list pages in the given namespace(s).', + 'limit' => 'How many total results to return per request.', + 'prop' => 'Which additional properties to get (non-generator mode only).', + 'show' => 'Only list items that meet these criteria.', + ); + } + + public function getDescription() { + return "Get all pages on the logged in user's watchlist"; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=watchlistraw', + 'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 41651 2008-10-04 14:30:33Z catrope $'; + } +} diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index 9e798d35..900953e0 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -100,7 +100,7 @@ class ApiResult extends ApiBase { } elseif (is_array($arr[$name]) && is_array($value)) { $merged = array_intersect_key($arr[$name], $value); - if (empty ($merged)) + if (!count($merged)) $arr[$name] += $value; else ApiBase :: dieDebug(__METHOD__, "Attempting to merge element $name"); @@ -180,18 +180,27 @@ class ApiResult extends ApiBase { } } - if (empty($name)) + if (!$name) $data[] = $value; // Add list element else ApiResult :: setElement($data, $name, $value); // Add named element } + /** + * Ensure all values in this result are valid UTF-8. + */ + public function cleanUpUTF8() + { + $data = & $this->getData(); + array_walk_recursive($data, array('UtfNormal', 'cleanUp')); + } + public function execute() { ApiBase :: dieDebug(__METHOD__, 'execute() is not supported on Result object'); } public function getVersion() { - return __CLASS__ . ': $Id: ApiResult.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiResult.php 45752 2009-01-14 21:36:57Z catrope $'; } } @@ -201,7 +210,7 @@ if (!function_exists('array_intersect_key')) { $argc = func_num_args(); if ($argc > 2) { - for ($i = 1; !empty($isec) && $i < $argc; $i++) { + for ($i = 1; $isec && $i < $argc; $i++) { $arr = func_get_arg($i); foreach (array_keys($isec) as $key) { diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php index 3739f694..653dca9e 100644 --- a/includes/api/ApiRollback.php +++ b/includes/api/ApiRollback.php @@ -37,7 +37,6 @@ class ApiRollback extends ApiBase { } public function execute() { - global $wgUser; $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); @@ -55,7 +54,10 @@ class ApiRollback extends ApiBase { if(!$titleObj->exists()) $this->dieUsageMsg(array('notanarticle')); - $username = User::getCanonicalName($params['user']); + #We need to be able to revert IPs, but getCanonicalName rejects them + $username = User::isIP($params['user']) + ? $params['user'] + : User::getCanonicalName($params['user']); if(!$username) $this->dieUsageMsg(array('invaliduser', $params['user'])); @@ -64,20 +66,17 @@ class ApiRollback extends ApiBase { $details = null; $retval = $articleObj->doRollback($username, $summary, $params['token'], $params['markbot'], $details); - if(!empty($retval)) + if($retval) // We don't care about multiple errors, just report one of them $this->dieUsageMsg(current($retval)); - $current = $target = $summary = NULL; - extract($details); - $info = array( 'title' => $titleObj->getPrefixedText(), - 'pageid' => $current->getPage(), - 'summary' => $summary, + 'pageid' => $details['current']->getPage(), + 'summary' => $details['summary'], 'revid' => $titleObj->getLatestRevID(), - 'old_revid' => $current->getID(), - 'last_revid' => $target->getID() + 'old_revid' => $details['current']->getID(), + 'last_revid' => $details['target']->getID() ); $this->getResult()->addValue(null, $this->getModuleName(), $info); @@ -99,7 +98,7 @@ class ApiRollback extends ApiBase { 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', + 'token' => 'A rollback token previously retrieved through prop=revisions', 'summary' => 'Custom edit summary. If not set, default summary will be used.', 'markbot' => 'Mark the reverted edits and the revert as bot edits' ); @@ -107,8 +106,8 @@ class ApiRollback extends ApiBase { 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.' + 'Undo 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.' ); } @@ -120,6 +119,6 @@ class ApiRollback extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiRollback.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiRollback.php 45043 2008-12-26 04:13:47Z mrzman $'; } } diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php index d6a02a2a..cd52c518 100644 --- a/includes/api/ApiUnblock.php +++ b/includes/api/ApiUnblock.php @@ -64,14 +64,12 @@ class ApiUnblock extends ApiBase { $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']); $retval = IPUnblockForm::doUnblock($id, $user, $reason, $range); - if(!empty($retval)) + if($retval) $this->dieUsageMsg($retval); $res['id'] = $id; @@ -96,7 +94,7 @@ class ApiUnblock extends ApiBase { 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', + 'token' => 'An unblock token previously obtained through the gettoken parameter or prop=info', 'gettoken' => 'If set, an unblock token will be returned, and no other action will be taken', 'reason' => 'Reason for unblock (optional)', ); @@ -116,6 +114,6 @@ class ApiUnblock extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiUnblock.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiUnblock.php 42651 2008-10-27 12:06:49Z catrope $'; } } diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php index e054a70e..7ae9a3c0 100644 --- a/includes/api/ApiUndelete.php +++ b/includes/api/ApiUndelete.php @@ -51,8 +51,6 @@ class ApiUndelete extends ApiBase { $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')); @@ -69,7 +67,7 @@ class ApiUndelete extends ApiBase { $params['timestamps'][$i] = wfTimestamp(TS_MW, $ts); $pa = new PageArchive($titleObj); - $dbw = wfGetDb(DB_MASTER); + $dbw = wfGetDB(DB_MASTER); $dbw->begin(); $retval = $pa->undelete((isset($params['timestamps']) ? $params['timestamps'] : array()), $params['reason']); if(!is_array($retval)) @@ -123,6 +121,6 @@ class ApiUndelete extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiUndelete.php 35348 2008-05-26 10:51:31Z catrope $'; + return __CLASS__ . ': $Id: ApiUndelete.php 43270 2008-11-06 22:30:55Z siebrand $'; } } diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php new file mode 100644 index 00000000..ab122fea --- /dev/null +++ b/includes/api/ApiWatch.php @@ -0,0 +1,99 @@ +<?php + +/* + * Created on Jan 4, 2008 + * + * API for MediaWiki 1.8+ + * + * Copyright (C) 2008 Yuri Astrakhan <Firstname><Lastname>@gmail.com, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 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. + * + * @ingroup API + */ +class ApiWatch extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser; + $this->getMain()->requestWriteMode(); + if(!$wgUser->isLoggedIn()) + $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin'); + $params = $this->extractRequestParams(); + $title = Title::newFromText($params['title']); + if(!$title) + $this->dieUsageMsg(array('invalidtitle', $params['title'])); + $article = new Article($title); + $res = array('title' => $title->getPrefixedText()); + if($params['unwatch']) + { + $res['unwatched'] = ''; + $success = $article->doUnwatch(); + } + else + { + $res['watched'] = ''; + $success = $article->doWatch(); + } + if(!$success) + $this->dieUsageMsg(array('hookaborted')); + $this->getResult()->addValue(null, $this->getModuleName(), $res); + } + + public function getAllowedParams() { + return array ( + 'title' => null, + 'unwatch' => false, + ); + } + + public function getParamDescription() { + return array ( + 'title' => 'The page to (un)watch', + 'unwatch' => 'If set the page will be unwatched rather than watched', + ); + } + + public function getDescription() { + return array ( + 'Add or remove a page from/to the current user\'s watchlist' + ); + } + + protected function getExamples() { + return array( + 'api.php?action=watch&title=Main_Page', + 'api.php?action=watch&title=Main_Page&unwatch', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiWatch.php 40460 2008-09-04 22:20:32Z ialex $'; + } +} |