From 8f416baead93a48e5799e44b8bd2e2c4859f4e04 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 14 Sep 2007 13:18:58 +0200 Subject: auf Version 1.11 aktualisiert; Login-Bug behoben --- includes/api/ApiBase.php | 165 +++++++++++++----- includes/api/ApiFeedWatchlist.php | 121 +++++++++---- includes/api/ApiFormatBase.php | 41 +++-- includes/api/ApiFormatJson.php | 31 +++- includes/api/ApiFormatJson_json.php | 2 +- includes/api/ApiFormatPhp.php | 6 +- includes/api/ApiFormatWddx.php | 6 +- includes/api/ApiFormatXml.php | 6 +- includes/api/ApiFormatYaml.php | 6 +- includes/api/ApiFormatYaml_spyc.php | 2 +- includes/api/ApiHelp.php | 8 +- includes/api/ApiLogin.php | 139 ++++++++++++++- includes/api/ApiMain.php | 268 +++++++++++++++++++++++------ includes/api/ApiOpenSearch.php | 14 +- includes/api/ApiPageSet.php | 115 ++++++++----- includes/api/ApiQuery.php | 253 +++++++++++++++++++-------- includes/api/ApiQueryAllLinks.php | 179 +++++++++++++++++++ includes/api/ApiQueryAllUsers.php | 204 ++++++++++++++++++++++ includes/api/ApiQueryAllpages.php | 104 +++++++---- includes/api/ApiQueryBacklinks.php | 125 +++++++++----- includes/api/ApiQueryBase.php | 247 ++++++++------------------ includes/api/ApiQueryCategories.php | 157 +++++++++++++++++ includes/api/ApiQueryCategoryMembers.php | 238 +++++++++++++++++++++++++ includes/api/ApiQueryExtLinksUsage.php | 200 +++++++++++++++++++++ includes/api/ApiQueryExternalLinks.php | 93 ++++++++++ includes/api/ApiQueryImageInfo.php | 156 +++++++++++++++++ includes/api/ApiQueryImages.php | 118 +++++++++++++ includes/api/ApiQueryInfo.php | 126 +++++++++++++- includes/api/ApiQueryLangLinks.php | 94 ++++++++++ includes/api/ApiQueryLinks.php | 162 +++++++++++++++++ includes/api/ApiQueryLogEvents.php | 155 +++++++++++++---- includes/api/ApiQueryRecentChanges.php | 117 ++++++++++--- includes/api/ApiQueryRevisions.php | 187 ++++++++++++++------ includes/api/ApiQuerySearch.php | 151 ++++++++++++++++ includes/api/ApiQuerySiteinfo.php | 202 +++++++++++++++++----- includes/api/ApiQueryUserContributions.php | 231 ++++++++++++++++++------- includes/api/ApiQueryUserInfo.php | 133 ++++++++++++++ includes/api/ApiQueryWatchlist.php | 133 ++++++++++---- includes/api/ApiResult.php | 42 +++-- 39 files changed, 3928 insertions(+), 809 deletions(-) create mode 100644 includes/api/ApiQueryAllLinks.php create mode 100644 includes/api/ApiQueryAllUsers.php create mode 100644 includes/api/ApiQueryCategories.php create mode 100644 includes/api/ApiQueryCategoryMembers.php create mode 100644 includes/api/ApiQueryExtLinksUsage.php create mode 100644 includes/api/ApiQueryExternalLinks.php create mode 100644 includes/api/ApiQueryImageInfo.php create mode 100644 includes/api/ApiQueryImages.php create mode 100644 includes/api/ApiQueryLangLinks.php create mode 100644 includes/api/ApiQueryLinks.php create mode 100644 includes/api/ApiQuerySearch.php create mode 100644 includes/api/ApiQueryUserInfo.php (limited to 'includes/api') diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index c4218825..b324c52f 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -24,7 +24,16 @@ */ /** - * @todo Document - e.g. Provide top-level description of this class. + * This abstract class implements many basic API functions, and is the base of all API classes. + * The class functions are divided into several areas of functionality: + * + * Module parameters: Derived classes can define getAllowedParams() to specify which parameters to expect, + * how to parse and validate them. + * + * Profiling: various methods to allow keeping tabs on various tasks and their time costs + * + * Self-documentation: code to allow api to document its own state. + * * @addtogroup API */ abstract class ApiBase { @@ -34,24 +43,24 @@ abstract class ApiBase { const PARAM_DFLT = 0; const PARAM_ISMULTI = 1; const PARAM_TYPE = 2; - const PARAM_MAX1 = 3; + const PARAM_MAX = 3; const PARAM_MAX2 = 4; const PARAM_MIN = 5; - const LIMIT_BIG1 = 500; // Fast query, user's limit - const LIMIT_BIG2 = 5000; // Fast query, bot's limit - const LIMIT_SML1 = 50; // Slow query, user's limit - const LIMIT_SML2 = 500; // Slow query, bot's limit + const LIMIT_BIG1 = 500; // Fast query, std user limit + const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit + const LIMIT_SML1 = 50; // Slow query, std user limit + const LIMIT_SML2 = 500; // Slow query, bot/sysop limit - private $mMainModule, $mModuleName, $mParamPrefix; + private $mMainModule, $mModuleName, $mModulePrefix; /** * Constructor */ - public function __construct($mainModule, $moduleName, $paramPrefix = '') { + public function __construct($mainModule, $moduleName, $modulePrefix = '') { $this->mMainModule = $mainModule; $this->mModuleName = $moduleName; - $this->mParamPrefix = $paramPrefix; + $this->mModulePrefix = $modulePrefix; } /** @@ -66,6 +75,13 @@ abstract class ApiBase { return $this->mModuleName; } + /** + * Get parameter prefix (usually two letters or an empty string). + */ + public function getModulePrefix() { + return $this->mModulePrefix; + } + /** * Get the name of the module as shown in the profiler log */ @@ -108,6 +124,15 @@ abstract class ApiBase { return $this->getResult()->getData(); } + /** + * Set warning section for this module. Users should monitor this section to notice any changes in API. + */ + public function setWarning($warning) { + $msg = array(); + ApiResult :: setContent($msg, $warning); + $this->getResult()->addValue('warnings', $this->getModuleName(), $msg); + } + /** * If the module may only be used with a certain format module, * it should override this method to return an instance of that formatter. @@ -191,11 +216,38 @@ abstract class ApiBase { $prompt = 'One value: '; if (is_array($type)) { - $desc .= $paramPrefix . $prompt . implode(', ', $type); - } - elseif ($type == 'namespace') { - // Special handling because namespaces are type-limited, yet they are not given - $desc .= $paramPrefix . $prompt . implode(', ', ApiBase :: getValidNamespaces()); + $choices = array(); + $nothingPrompt = false; + foreach ($type as $t) + if ($t=='') + $nothingPrompt = 'Can be empty, or '; + else + $choices[] = $t; + $desc .= $paramPrefix . $nothingPrompt . $prompt . implode(', ', $choices); + } else { + switch ($type) { + case 'namespace': + // Special handling because namespaces are type-limited, yet they are not given + $desc .= $paramPrefix . $prompt . implode(', ', ApiBase :: getValidNamespaces()); + break; + case 'limit': + $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]} ({$paramSettings[self :: PARAM_MAX2]} for bots) allowed."; + break; + case 'integer': + $hasMin = isset($paramSettings[self :: PARAM_MIN]); + $hasMax = isset($paramSettings[self :: PARAM_MAX]); + if ($hasMin || $hasMax) { + if (!$hasMax) + $intRangeStr = "The value must be no less than {$paramSettings[self :: PARAM_MIN]}"; + elseif (!$hasMin) + $intRangeStr = "The value must be no more than {$paramSettings[self :: PARAM_MAX]}"; + else + $intRangeStr = "The value must be between {$paramSettings[self :: PARAM_MIN]} and {$paramSettings[self :: PARAM_MAX]}"; + + $desc .= $paramPrefix . $intRangeStr; + } + break; + } } } @@ -244,7 +296,7 @@ abstract class ApiBase { * Override this method to change parameter name during runtime */ public function encodeParamName($paramName) { - return $this->mParamPrefix . $paramName; + return $this->mModulePrefix . $paramName; } /** @@ -293,7 +345,7 @@ abstract class ApiBase { protected function getParameterFromSettings($paramName, $paramSettings) { // Some classes may decide to change parameter names - $paramName = $this->encodeParamName($paramName); + $encParamName = $this->encodeParamName($paramName); if (!is_array($paramSettings)) { $default = $paramSettings; @@ -316,19 +368,19 @@ abstract class ApiBase { if ($type == 'boolean') { if (isset ($default) && $default !== false) { // Having a default value of anything other than 'false' is pointless - ApiBase :: dieDebug(__METHOD__, "Boolean param $paramName's default is set to '$default'"); + ApiBase :: dieDebug(__METHOD__, "Boolean param $encParamName's default is set to '$default'"); } - $value = $this->getMain()->getRequest()->getCheck($paramName); + $value = $this->getMain()->getRequest()->getCheck($encParamName); } else { - $value = $this->getMain()->getRequest()->getVal($paramName, $default); + $value = $this->getMain()->getRequest()->getVal($encParamName, $default); if (isset ($value) && $type == 'namespace') $type = ApiBase :: getValidNamespaces(); } if (isset ($value) && ($multi || is_array($type))) - $value = $this->parseMultiValue($paramName, $value, $multi, is_array($type) ? $type : null); + $value = $this->parseMultiValue($encParamName, $value, $multi, is_array($type) ? $type : null); // More validation only when choices were not given // choices were validated in parseMultiValue() @@ -339,32 +391,48 @@ abstract class ApiBase { break; case 'string' : // nothing to do break; - case 'integer' : // Force everything using intval() + case 'integer' : // Force everything using intval() and optionally validate limits + $value = is_array($value) ? array_map('intval', $value) : intval($value); + $min = isset ($paramSettings[self :: PARAM_MIN]) ? $paramSettings[self :: PARAM_MIN] : null; + $max = isset ($paramSettings[self :: PARAM_MAX]) ? $paramSettings[self :: PARAM_MAX] : null; + + if (!is_null($min) || !is_null($max)) { + $values = is_array($value) ? $value : array($value); + foreach ($values as $v) { + $this->validateLimit($paramName, $v, $min, $max); + } + } break; case 'limit' : - if (!isset ($paramSettings[self :: PARAM_MAX1]) || !isset ($paramSettings[self :: PARAM_MAX2])) - ApiBase :: dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit $paramName"); + if (!isset ($paramSettings[self :: PARAM_MAX]) || !isset ($paramSettings[self :: PARAM_MAX2])) + ApiBase :: dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit $encParamName"); if ($multi) - ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $encParamName"); $min = isset ($paramSettings[self :: PARAM_MIN]) ? $paramSettings[self :: PARAM_MIN] : 0; $value = intval($value); - $this->validateLimit($paramName, $value, $min, $paramSettings[self :: PARAM_MAX1], $paramSettings[self :: PARAM_MAX2]); + $this->validateLimit($paramName, $value, $min, $paramSettings[self :: PARAM_MAX], $paramSettings[self :: PARAM_MAX2]); break; case 'boolean' : if ($multi) - ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $encParamName"); break; case 'timestamp' : if ($multi) - ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $encParamName"); $value = wfTimestamp(TS_UNIX, $value); if ($value === 0) - $this->dieUsage("Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$paramName}"); + $this->dieUsage("Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}"); $value = wfTimestamp(TS_MW, $value); break; + case 'user' : + $title = Title::makeTitleSafe( NS_USER, $value ); + if ( is_null( $title ) ) + $this->dieUsage("Invalid value for user parameter $encParamName", "baduser_{$encParamName}"); + $value = $title->getText(); + break; default : - ApiBase :: dieDebug(__METHOD__, "Param $paramName's type is unknown - $type"); + ApiBase :: dieDebug(__METHOD__, "Param $encParamName's type is unknown - $type"); } } @@ -405,19 +473,26 @@ abstract class ApiBase { /** * Validate the value against the minimum and user/bot maximum limits. Prints usage info on failure. */ - function validateLimit($varname, $value, $min, $max, $botMax) { - if ($value < $min) { - $this->dieUsage("$varname may not be less than $min (set to $value)", $varname); + function validateLimit($paramName, $value, $min, $max, $botMax = null) { + if (!is_null($min) && $value < $min) { + $this->dieUsage($this->encodeParamName($paramName) . " may not be less than $min (set to $value)", $paramName); } - if ($this->getMain()->isBot()) { - if ($value > $botMax) { - $this->dieUsage("$varname may not be over $botMax (set to $value) for bots", $varname); + // Minimum is always validated, whereas maximum is checked only if not running in internal call mode + if ($this->getMain()->isInternalMode()) + return; + + // Optimization: do not check user's bot status unless really needed -- skips db query + // assumes $botMax >= $max + if (!is_null($max) && $value > $max) { + if (!is_null($botMax) && ($this->getMain()->isBot() || $this->getMain()->isSysop())) { + if ($value > $botMax) { + $this->dieUsage($this->encodeParamName($paramName) . " may not be over $botMax (set to $value) for bots or sysops", $paramName); + } + } else { + $this->dieUsage($this->encodeParamName($paramName) . " may not be over $max (set to $value) for users", $paramName); } } - elseif ($value > $max) { - $this->dieUsage("$varname may not be over $max (set to $value) for users", $varname); - } } /** @@ -526,11 +601,19 @@ abstract class ApiBase { ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBOut() first'); return $this->mDBTime; } + + public static function debugPrint($value, $name = 'unknown', $backtrace = false) { + print "\n\n
Debuging value '$name':\n\n";
+		var_export($value);
+		if ($backtrace)
+			print "\n" . wfBacktrace();
+		print "\n
\n"; + } public abstract function getVersion(); public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiBase.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiBase.php 24934 2007-08-20 08:04:12Z nickj $'; } } -?> + diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php index 7918ee0e..b2f6ceff 100644 --- a/includes/api/ApiFeedWatchlist.php +++ b/includes/api/ApiFeedWatchlist.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,10 @@ if (!defined('MEDIAWIKI')) { } /** + * This action allows users to get their watchlist items in RSS/Atom formats. + * When executed, it performs a nested call to the API to get the needed data, + * and formats it in a proper format. + * * @addtogroup API */ class ApiFeedWatchlist extends ApiBase { @@ -37,47 +41,81 @@ class ApiFeedWatchlist extends ApiBase { parent :: __construct($main, $action); } + /** + * This module uses a custom feed wrapper printer. + */ public function getCustomPrinter() { return new ApiFormatFeedWrapper($this->getMain()); } + /** + * Make a nested call to the API to request watchlist items in the last $hours. + * Wrap the result as an RSS/Atom feed. + */ public function execute() { - $feedformat = null; - extract($this->extractRequestParams()); - - // limit to 1 day - $startTime = wfTimestamp(TS_MW, time() - intval(1 * 86400)); - - // Prepare nested request - $params = new FauxRequest(array ( - 'action' => 'query', - 'meta' => 'siteinfo', - 'siprop' => 'general', - 'list' => 'watchlist', - 'wlprop' => 'user|comment|timestamp', - 'wlstart' => $startTime, - 'wllimit' => 50 - )); - - // Execute - $module = new ApiMain($params); - $module->execute(); - - // Get data array - $data = $module->getResultData(); - - $feedItems = array (); - foreach ($data['query']['watchlist'] as $info) { - $feedItems[] = $this->createFeedItem($info); - } - + global $wgFeedClasses, $wgSitename, $wgContLanguageCode; - $feedTitle = $wgSitename . ' - ' . wfMsgForContent('watchlist') . ' [' . $wgContLanguageCode . ']'; - $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl(); - - $feed = new $wgFeedClasses[$feedformat] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl); - ApiFormatFeedWrapper :: setResult($this->getResult(), $feed, $feedItems); + try { + $params = $this->extractRequestParams(); + + // limit to the number of hours going from now back + $endTime = wfTimestamp(TS_MW, time() - intval($params['hours'] * 60 * 60)); + + // Prepare nested request + $fauxReq = new FauxRequest(array ( + 'action' => 'query', + 'meta' => 'siteinfo', + 'siprop' => 'general', + 'list' => 'watchlist', + 'wlprop' => 'title|user|comment|timestamp', + 'wldir' => 'older', // reverse order - from newest to oldest + 'wlend' => $endTime, // stop at this time + 'wllimit' => 50 + )); + + // Execute + $module = new ApiMain($fauxReq); + $module->execute(); + + // Get data array + $data = $module->getResultData(); + + $feedItems = array (); + foreach ($data['query']['watchlist'] as $info) { + $feedItems[] = $this->createFeedItem($info); + } + + $feedTitle = $wgSitename . ' - ' . wfMsgForContent('watchlist') . ' [' . $wgContLanguageCode . ']'; + $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl(); + + $feed = new $wgFeedClasses[$params['feedformat']] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl); + + ApiFormatFeedWrapper :: setResult($this->getResult(), $feed, $feedItems); + + } catch (Exception $e) { + + // Error results should not be cached + $this->getMain()->setCacheMaxAge(0); + + $feedTitle = $wgSitename . ' - Error - ' . wfMsgForContent('watchlist') . ' [' . $wgContLanguageCode . ']'; + $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullUrl(); + + $feedFormat = isset($params['feedformat']) ? $params['feedformat'] : 'rss'; + $feed = new $wgFeedClasses[$feedFormat] ($feedTitle, htmlspecialchars(wfMsgForContent('watchlist')), $feedUrl); + + + if ($e instanceof UsageException) { + $errorCode = $e->getCodeString(); + } else { + // Something is seriously wrong + $errorCode = 'internal_api_error'; + } + + $errorText = $e->getMessage(); + $feedItems[] = new FeedItem("Error ($errorCode)", $errorText, "", "", ""); + ApiFormatFeedWrapper :: setResult($this->getResult(), $feed, $feedItems); + } } private function createFeedItem($info) { @@ -100,13 +138,20 @@ class ApiFeedWatchlist extends ApiBase { 'feedformat' => array ( ApiBase :: PARAM_DFLT => 'rss', ApiBase :: PARAM_TYPE => $feedFormatNames + ), + 'hours' => array ( + ApiBase :: PARAM_DFLT => 24, + ApiBase :: PARAM_TYPE => 'integer', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => 72, ) ); } protected function getParamDescription() { return array ( - 'feedformat' => 'The format of the feed' + 'feedformat' => 'The format of the feed', + 'hours' => 'List pages modified within this many hours from now' ); } @@ -121,7 +166,7 @@ class ApiFeedWatchlist extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFeedWatchlist.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFeedWatchlist.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 782a4161..861310d2 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,8 @@ if (!defined('MEDIAWIKI')) { } /** + * This is the abstract base class for API formatters. + * * @addtogroup API */ abstract class ApiFormatBase extends ApiBase { @@ -36,7 +38,8 @@ abstract class ApiFormatBase extends ApiBase { private $mIsHtml, $mFormat; /** - * Constructor + * Create a new instance of the formatter. + * If the format name ends with 'fm', wrap its output in the proper HTML. */ public function __construct($main, $format) { parent :: __construct($main, $format); @@ -56,6 +59,11 @@ abstract class ApiFormatBase extends ApiBase { */ public abstract function getMimeType(); + /** + * If formatter outputs data results as is, the results must first be sanitized. + * An XML formatter on the other hand uses special tags, such as "_element" for special handling, + * and thus needs to override this function to return true. + */ public function getNeedsRawData() { return false; } @@ -77,6 +85,7 @@ abstract class ApiFormatBase extends ApiBase { function initPrinter($isError) { $isHtml = $this->getIsHtml(); $mime = $isHtml ? 'text/html' : $this->getMimeType(); + $script = wfScript( 'api' ); // Some printers (ex. Feed) do their own header settings, // in which case $mime will be set to null @@ -96,14 +105,14 @@ abstract class ApiFormatBase extends ApiBase {
-You are looking at the HTML representation of the mFormat?> format.
+You are looking at the HTML representation of the mFormat ); ?> format.
HTML is good for debugging, but probably is not suitable for your application.
-Please see "format" parameter documentation at the API help -for more information. +See complete documentation, or +API help for more information.
getIsHtml()) echo $this->formatHTML($text); @@ -152,9 +165,9 @@ for more information. $text = preg_replace('/\<(!--.*?--|.*?)\>/', '<\1>', $text); // identify URLs $protos = "http|https|ftp|gopher"; - $text = ereg_replace("($protos)://[^ '\"()<\n]+", '\\0', $text); + $text = ereg_replace("($protos)://[^ \\'\"()<\n]+", '\\0', $text); // identify requests to api.php - $text = ereg_replace("api\\.php\\?[^ ()<\n\t]+", '\\0', $text); + $text = ereg_replace("api\\.php\\?[^ \\()<\n\t]+", '\\0', $text); // make strings inside * bold $text = ereg_replace("\\*[^<>\n]+\\*", '\\0', $text); // make strings inside $ italic @@ -175,7 +188,7 @@ for more information. } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 25746 2007-09-10 21:36:51Z brion $'; } } @@ -190,7 +203,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } /** - * Call this method to initialize output data + * Call this method to initialize output data. See self::execute() */ public static function setResult($result, $feed, $feedItems) { // Store output in the Result data. @@ -214,6 +227,11 @@ class ApiFormatFeedWrapper extends ApiFormatBase { return true; } + /** + * This class expects the result data to be in a custom format set by self::setResult() + * $result['_feed'] - an instance of one of the $wgFeedClasses classes + * $result['_feeditems'] - an array of FeedItem instances + */ public function execute() { $data = $this->getResultData(); if (isset ($data['_feed']) && isset ($data['_feeditems'])) { @@ -232,7 +250,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 25746 2007-09-10 21:36:51Z brion $'; } } -?> diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index dd1847c4..ed9bd938 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -49,14 +49,35 @@ class ApiFormatJson extends ApiFormatBase { } public function execute() { + $prefix = $suffix = ""; + + $params = $this->extractRequestParams(); + $callback = $params['callback']; + if(!is_null($callback)) { + $prefix = ereg_replace("[^_A-Za-z0-9]", "", $callback ) . "("; + $suffix = ")"; + } + if (!function_exists('json_encode') || $this->getIsHtml()) { $json = new Services_JSON(); - $this->printText($json->encode($this->getResultData(), $this->getIsHtml())); + $this->printText($prefix . $json->encode($this->getResultData(), $this->getIsHtml()) . $suffix); } else { - $this->printText(json_encode($this->getResultData())); + $this->printText($prefix . json_encode($this->getResultData()) . $suffix); } } + protected function getAllowedParams() { + return array ( + 'callback' => null + ); + } + + protected function getParamDescription() { + return array ( + 'callback' => 'If specified, wraps the output into a given function call', + ); + } + protected function getDescription() { if ($this->mIsRaw) return 'Output data with the debuging elements in JSON format' . parent :: getDescription(); @@ -65,7 +86,7 @@ class ApiFormatJson extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatJson.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFormatJson.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + diff --git a/includes/api/ApiFormatJson_json.php b/includes/api/ApiFormatJson_json.php index 2cd87930..a8c649c3 100644 --- a/includes/api/ApiFormatJson_json.php +++ b/includes/api/ApiFormatJson_json.php @@ -843,4 +843,4 @@ if (class_exists('PEAR_Error')) { } -?> + diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php index add63362..766d7041 100644 --- a/includes/api/ApiFormatPhp.php +++ b/includes/api/ApiFormatPhp.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -50,7 +50,7 @@ class ApiFormatPhp extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatPhp.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFormatPhp.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index bc720490..0ddfac73 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -85,7 +85,7 @@ class ApiFormatWddx extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatWddx.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFormatWddx.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index 7d54b441..02647923 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -141,7 +141,7 @@ class ApiFormatXml extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatXml.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFormatXml.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php index 0107eb2b..400c0a4b 100644 --- a/includes/api/ApiFormatYaml.php +++ b/includes/api/ApiFormatYaml.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -50,7 +50,7 @@ class ApiFormatYaml extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatYaml.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiFormatYaml.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php index a67bbb22..b3ccff0f 100644 --- a/includes/api/ApiFormatYaml_spyc.php +++ b/includes/api/ApiFormatYaml_spyc.php @@ -857,4 +857,4 @@ return $ret; } } -?> + diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index 7c5144fd..9f1e88ea 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,8 @@ if (!defined('MEDIAWIKI')) { } /** + * This is a simple class to handle action=help + * * @addtogroup API */ class ApiHelp extends ApiBase { @@ -51,7 +53,7 @@ class ApiHelp extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiHelp.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiHelp.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index 147d37a1..af68b29d 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -5,7 +5,8 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006-2007 Yuri Astrakhan @gmail.com, + * Daniel Cannon (cannon dot danielc at gmail dot 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 @@ -29,18 +30,60 @@ if (!defined('MEDIAWIKI')) { } /** + * Unit to authenticate log-in attempts to the current wiki. + * * @addtogroup API */ class ApiLogin extends ApiBase { - + + /** + * Time (in seconds) a user must wait after submitting + * a bad login (will be multiplied by the THROTTLE_FACTOR for each bad attempt) + */ + const THROTTLE_TIME = 1; + + /** + * 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'); } + /** + * 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 + * 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. + * + * @access public + */ public function execute() { $name = $password = $domain = null; extract($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, @@ -48,8 +91,6 @@ class ApiLogin extends ApiBase { 'wpRemember' => '' )); - $result = array (); - $loginForm = new LoginForm($params); switch ($loginForm->authenticateUserData()) { case LoginForm :: SUCCESS : @@ -86,9 +127,89 @@ class ApiLogin extends ApiBase { ApiBase :: dieDebug(__METHOD__, 'Unhandled case value'); } + if ($result['result'] != 'Success') { + $result['wait'] = $this->cacheBadLogin(); + } + // 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() ); + } + protected function getAllowedParams() { return array ( 'name' => null, @@ -107,7 +228,11 @@ class ApiLogin extends ApiBase { protected function getDescription() { return array ( - 'This module is used to login and get the authentication tokens.' + 'This module is used to login and get the authentication tokens. ', + 'In the event of a successful log-in, a cookie will be attached', + 'to your session. In the event of a failed log-in, you will not ', + 'be able to attempt another log-in through this method for 60 seconds.', + 'This is to prevent password guessing by automated password crackers.' ); } @@ -118,7 +243,7 @@ class ApiLogin extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogin.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiLogin.php 24695 2007-08-09 09:53:05Z yurik $'; } } -?> + diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 9a6b0f83..31870449 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,7 +29,16 @@ if (!defined('MEDIAWIKI')) { } /** - * This is the main API class, used for both external and internal processing. + * This is the main API class, used for both external and internal processing. + * When executed, it will create the requested formatter object, + * instantiate and execute an object associated with the needed action, + * and use formatter to print results. + * In case of an exception, an error message will be printed using the same formatter. + * + * To use API from another application, run it using FauxRequest object, in which + * case any internal exceptions will not be handled but passed up to the caller. + * After successful execution, use getResult() for the resulting data. + * * @addtogroup API */ class ApiMain extends ApiBase { @@ -43,11 +52,11 @@ class ApiMain extends ApiBase { * List of available modules: action name => module class */ private static $Modules = array ( - 'help' => 'ApiHelp', 'login' => 'ApiLogin', + 'query' => 'ApiQuery', 'opensearch' => 'ApiOpenSearch', 'feedwatchlist' => 'ApiFeedWatchlist', - 'query' => 'ApiQuery' + 'help' => 'ApiHelp', ); /** @@ -68,10 +77,11 @@ class ApiMain extends ApiBase { ); private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames; - private $mResult, $mShowVersions, $mEnableWrite, $mRequest, $mInternalMode, $mSquidMaxage; + private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest, $mInternalMode, $mSquidMaxage; /** - * Constructor + * Constructs an instance of ApiMain that utilizes the module and format specified by $request. + * * @param $request object - if this is an instance of FauxRequest, errors are thrown and no printing occurs * @param $enableWrite bool should be set to true if the api may modify data */ @@ -82,7 +92,23 @@ class ApiMain extends ApiBase { // Special handling for the main module: $parent === $this parent :: __construct($this, $this->mInternalMode ? 'main_int' : 'main'); - $this->mModules = self :: $Modules; + if (!$this->mInternalMode) { + + // Impose module restrictions. + // If the current user cannot read, + // Remove all modules other than login + global $wgUser; + if (!$wgUser->isAllowed('read')) { + self::$Modules = array( + 'login' => self::$Modules['login'], + 'help' => self::$Modules['help'] + ); + } + } + + global $wgAPIModules; // extension modules + $this->mModules = $wgAPIModules + self :: $Modules; + $this->mModuleNames = array_keys($this->mModules); // todo: optimize $this->mFormats = self :: $Formats; $this->mFormatNames = array_keys($this->mFormats); // todo: optimize @@ -96,28 +122,53 @@ class ApiMain extends ApiBase { $this->mSquidMaxage = 0; } - public function & getRequest() { + /** + * Return true if the API was started by other PHP code using FauxRequest + */ + public function isInternalMode() { + return $this->mInternalMode; + } + + /** + * Return the request object that contains client's request + */ + public function getRequest() { return $this->mRequest; } + /** + * Get the ApiResult object asscosiated with current request + */ public function getResult() { return $this->mResult; } + /** + * This method will simply cause an error if the write mode was disabled for this api. + */ public function requestWriteMode() { if (!$this->mEnableWrite) $this->dieUsage('Editing of this site is disabled. Make sure the $wgEnableWriteAPI=true; ' . 'statement is included in the site\'s LocalSettings.php file', 'readonly'); } + /** + * Set how long the response should be cached. + */ public function setCacheMaxAge($maxage) { $this->mSquidMaxage = $maxage; } + /** + * Create an instance of an output formatter by its name + */ public function createPrinterByName($format) { return new $this->mFormats[$format] ($this, $format); } + /** + * Execute api request. Any errors will be handled if the API was called by the remote client. + */ public function execute() { $this->profileIn(); if ($this->mInternalMode) @@ -127,10 +178,14 @@ class ApiMain extends ApiBase { $this->profileOut(); } + /** + * Execute an action, and in case of an error, erase whatever partial results + * have been accumulated, and replace it with an error message and a help screen. + */ protected function executeActionWithErrorHandling() { // In case an error occurs during data output, - // this clear the output buffer and print just the error information + // clear the output buffer and print just the error information ob_start(); try { @@ -142,12 +197,51 @@ class ApiMain extends ApiBase { // handler will process and log it. // + $errCode = $this->substituteResultWithError($e); + // Error results should not be cached $this->setCacheMaxAge(0); + $headerStr = 'MediaWiki-API-Error: ' . $errCode; + if ($e->getCode() === 0) + header($headerStr, true); + else + header($headerStr, true, $e->getCode()); + + // Reset and print just the error message + ob_clean(); + + // If the error occured during printing, do a printer->profileOut() + $this->mPrinter->safeProfileOut(); + $this->printResult(true); + } + + // Set the cache expiration at the last moment, as any errors may change the expiration. + // if $this->mSquidMaxage == 0, the expiry time is set to the first second of unix epoch + $expires = $this->mSquidMaxage == 0 ? 1 : time() + $this->mSquidMaxage; + header('Expires: ' . wfTimestamp(TS_RFC2822, $expires)); + header('Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0'); + + if($this->mPrinter->getIsHtml()) + echo wfReportTime(); + + ob_end_flush(); + } + + /** + * Replace the result data with the information about an exception. + * Returns the error code + */ + protected function substituteResultWithError($e) { + // Printer may not be initialized if the extractRequestParams() fails for the main module if (!isset ($this->mPrinter)) { - $this->mPrinter = $this->createPrinterByName(self :: API_DEFAULT_FORMAT); + // The printer has not been created yet. Try to manually get formatter value. + $value = $this->getRequest()->getVal('format', self::API_DEFAULT_FORMAT); + if (!in_array($value, $this->mFormatNames)) + $value = self::API_DEFAULT_FORMAT; + + $this->mPrinter = $this->createPrinterByName($value); if ($this->mPrinter->getNeedsRawData()) $this->getResult()->setRawMode(); } @@ -157,8 +251,12 @@ class ApiMain extends ApiBase { // User entered incorrect parameters - print usage screen // $errMessage = array ( - 'code' => $e->getCodeString(), 'info' => $e->getMessage()); - ApiResult :: setContent($errMessage, $this->makeHelpMsg()); + 'code' => $e->getCodeString(), + 'info' => $e->getMessage()); + + // Only print the help message when this is for the developer, not runtime + if ($this->mPrinter->getIsHtml() || $this->mAction == 'help') + ApiResult :: setContent($errMessage, $this->makeHelpMsg()); } else { // @@ -171,41 +269,24 @@ class ApiMain extends ApiBase { ApiResult :: setContent($errMessage, "\n\n{$e->getTraceAsString()}\n\n"); } - $headerStr = 'MediaWiki-API-Error: ' . $errMessage['code']; - if ($e->getCode() === 0) - header($headerStr, true); - else - header($headerStr, true, $e->getCode()); - - // Reset and print just the error message - ob_clean(); $this->getResult()->reset(); $this->getResult()->addValue(null, 'error', $errMessage); - // If the error occured during printing, do a printer->profileOut() - $this->mPrinter->safeProfileOut(); - $this->printResult(true); - } - - // Set the cache expiration at the last moment, as any errors may change the expiration. - // if $this->mSquidMaxage == 0, the expiry time is set to the first second of unix epoch - $expires = $this->mSquidMaxage == 0 ? 1 : time() + $this->mSquidMaxage; - header('Expires: ' . wfTimestamp(TS_RFC2822, $expires)); - header('Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0'); - - ob_end_flush(); + return $errMessage['code']; } /** * Execute the actual module, without any error handling */ protected function executeAction() { - $action = $format = $version = null; - extract($this->extractRequestParams()); - $this->mShowVersions = $version; + + $params = $this->extractRequestParams(); + + $this->mShowVersions = $params['version']; + $this->mAction = $params['action']; // Instantiate the module requested by the user - $module = new $this->mModules[$action] ($this, $action); + $module = new $this->mModules[$this->mAction] ($this, $this->mAction); if (!$this->mInternalMode) { @@ -213,7 +294,7 @@ class ApiMain extends ApiBase { $this->mPrinter = $module->getCustomPrinter(); if (is_null($this->mPrinter)) { // Create an appropriate printer - $this->mPrinter = $this->createPrinterByName($format); + $this->mPrinter = $this->createPrinterByName($params['format']); } if ($this->mPrinter->getNeedsRawData()) @@ -232,7 +313,7 @@ class ApiMain extends ApiBase { } /** - * Internal printer + * Print results using the current printer */ protected function printResult($isError) { $printer = $this->mPrinter; @@ -243,6 +324,9 @@ class ApiMain extends ApiBase { $printer->profileOut(); } + /** + * See ApiBase for description. + */ protected function getAllowedParams() { return array ( 'format' => array ( @@ -257,6 +341,9 @@ class ApiMain extends ApiBase { ); } + /** + * See ApiBase for description. + */ protected function getParamDescription() { return array ( 'format' => 'The format of the output', @@ -265,24 +352,44 @@ class ApiMain extends ApiBase { ); } + /** + * See ApiBase for description. + */ protected function getDescription() { return array ( '', - 'This API allows programs to access various functions of MediaWiki software.', - 'For more details see API Home Page @ http://meta.wikimedia.org/wiki/API', '', - 'Status: ALPHA -- all features shown on this page should be working,', - ' but the API is still in active development, and may change at any time.', - ' Make sure you monitor changes to this page, wikitech-l mailing list,', - ' or the source code in the includes/api directory for any changes.', - '' + '******************************************************************', + '** **', + '** This is an auto-generated MediaWiki API documentation page **', + '** **', + '** Documentation and Examples: **', + '** http://www.mediawiki.org/wiki/API **', + '** **', + '******************************************************************', + '', + 'Status: All features shown on this page should be working, but the API', + ' is still in active development, and may change at any time.', + ' Make sure to monitor our mailing list for any updates.', + '', + 'Documentation: http://www.mediawiki.org/wiki/API', + 'Mailing list: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api', + 'Bugs & Requests: http://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts', + '', + '', + '', + '', + '', ); } + /** + * Returns an array of strings with credits for the API + */ protected function getCredits() { return array( - 'This API is being implemented by Yuri Astrakhan [[User:Yurik]] / FirstnameLastname@gmail.com', - 'Please leave your comments and suggestions at http://meta.wikimedia.org/wiki/API' + 'This API is being implemented by Yuri Astrakhan [[User:Yurik]] / @gmail.com', + 'Please leave your comments and suggestions at http://www.mediawiki.org/wiki/API' ); } @@ -297,8 +404,8 @@ class ApiMain extends ApiBase { $astriks = str_repeat('*** ', 10); $msg .= "\n\n$astriks Modules $astriks\n\n"; foreach( $this->mModules as $moduleName => $unused ) { - $msg .= "* action=$moduleName *"; $module = new $this->mModules[$moduleName] ($this, $moduleName); + $msg .= self::makeHelpMsgHeader($module, 'action'); $msg2 = $module->makeHelpMsg(); if ($msg2 !== false) $msg .= $msg2; @@ -307,8 +414,8 @@ class ApiMain extends ApiBase { $msg .= "\n$astriks Formats $astriks\n\n"; foreach( $this->mFormats as $formatName => $unused ) { - $msg .= "* format=$formatName *"; $module = $this->createPrinterByName($formatName); + $msg .= self::makeHelpMsgHeader($module, 'format'); $msg2 = $module->makeHelpMsg(); if ($msg2 !== false) $msg .= $msg2; @@ -321,7 +428,21 @@ class ApiMain extends ApiBase { return $msg; } + public static function makeHelpMsgHeader($module, $paramName) { + $modulePrefix = $module->getModulePrefix(); + if (!empty($modulePrefix)) + $modulePrefix = "($modulePrefix) "; + + return "* $paramName={$module->getModuleName()} $modulePrefix*"; + } + private $mIsBot = null; + + private $mIsSysop = null; + + /** + * Returns true if the currently logged in user is a bot, false otherwise + */ public function isBot() { if (!isset ($this->mIsBot)) { global $wgUser; @@ -329,24 +450,69 @@ class ApiMain extends ApiBase { } return $this->mIsBot; } + + /** + * Similar to isBot(), this method returns true if the logged in user is + * a sysop, and false if not. + */ + public function isSysop() { + if (!isset ($this->mIsSysop)) { + global $wgUser; + $this->mIsSysop = in_array( 'sysop', $wgUser->getGroups()); + } + + return $this->mIsSysop; + } public function getShowVersions() { return $this->mShowVersions; } + /** + * Returns the version information of this file, plus it includes + * the versions for all files that are not callable proper API modules + */ public function getVersion() { $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiMain.php 21402 2007-04-20 08:55:14Z nickj $'; + $vers[] = 'MediaWiki ' . SpecialVersion::getVersion(); + $vers[] = __CLASS__ . ': $Id: ApiMain.php 25364 2007-08-31 15:23:48Z tstarling $'; $vers[] = ApiBase :: getBaseVersion(); $vers[] = ApiFormatBase :: getBaseVersion(); $vers[] = ApiQueryBase :: getBaseVersion(); $vers[] = ApiFormatFeedWrapper :: getVersion(); // not accessible with format=xxx return $vers; } + + /** + * Add or overwrite a module in this ApiMain instance. Intended for use by extending + * classes who wish to add their own modules to their lexicon or override the + * behavior of inherent ones. + * + * @access protected + * @param $mdlName String The identifier for this module. + * @param $mdlClass String The class where this module is implemented. + */ + protected function addModule( $mdlName, $mdlClass ) { + $this->mModules[$mdlName] = $mdlClass; + } + + /** + * Add or overwrite an output format for this ApiMain. Intended for use by extending + * classes who wish to add to or modify current formatters. + * + * @access protected + * @param $fmtName The identifier for this format. + * @param $fmtClass The class implementing this format. + */ + protected function addFormat( $fmtName, $fmtClass ) { + $this->mFormats[$fmtName] = $fmtClass; + } } /** * This exception will be thrown when dieUsage is called to stop module execution. + * The exception handling code will print a help screen explaining how this API may be used. + * * @addtogroup API */ class UsageException extends Exception { @@ -364,4 +530,4 @@ class UsageException extends Exception { return "{$this->getCodeString()}: {$this->getMessage()}"; } } -?> + diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php index 77f8b889..8484b163 100644 --- a/includes/api/ApiOpenSearch.php +++ b/includes/api/ApiOpenSearch.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -42,8 +42,8 @@ class ApiOpenSearch extends ApiBase { } public function execute() { - $search = null; - extract($this->ExtractRequestParams()); + $params = $this->extractRequestParams(); + $search = $params['search']; // Open search results may be stored for a very long time $this->getMain()->setCacheMaxAge(1200); @@ -53,7 +53,7 @@ class ApiOpenSearch extends ApiBase { return; // Return empty result // Prepare nested request - $params = new FauxRequest(array ( + $req = new FauxRequest(array ( 'action' => 'query', 'list' => 'allpages', 'apnamespace' => $title->getNamespace(), @@ -62,7 +62,7 @@ class ApiOpenSearch extends ApiBase { )); // Execute - $module = new ApiMain($params); + $module = new ApiMain($req); $module->execute(); // Get resulting data @@ -105,7 +105,7 @@ class ApiOpenSearch extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiOpenSearch.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiOpenSearch.php 24099 2007-07-15 00:52:35Z yurik $'; } } -?> + diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index dea87b88..185c0c59 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,14 +29,25 @@ if (!defined('MEDIAWIKI')) { } /** + * This class contains a list of pages that the client has requested. + * Initially, when the client passes in titles=, pageids=, or revisions= parameter, + * an instance of the ApiPageSet class will normalize titles, + * determine if the pages/revisions exist, and prefetch any additional data page data requested. + * + * When generator is used, the result of the generator will become the input for the + * second instance of this class, and all subsequent actions will go use the second instance + * for all their work. + * * @addtogroup API */ class ApiPageSet extends ApiQueryBase { private $mAllPages; // [ns][dbkey] => page_id or 0 when missing - private $mTitles, $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles; + private $mTitles, $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles; + private $mNormalizedTitles, $mInterwikiTitles; private $mResolveRedirects, $mPendingRedirectIDs; private $mGoodRevIDs, $mMissingRevIDs; + private $mFakePageId; private $mRequestedPageFields; @@ -50,6 +61,7 @@ class ApiPageSet extends ApiQueryBase { $this->mMissingPageIDs = array (); $this->mRedirectTitles = array (); $this->mNormalizedTitles = array (); + $this->mInterwikiTitles = array (); $this->mGoodRevIDs = array(); $this->mMissingRevIDs = array(); @@ -57,6 +69,8 @@ class ApiPageSet extends ApiQueryBase { $this->mResolveRedirects = $resolveRedirects; if($resolveRedirects) $this->mPendingRedirectIDs = array(); + + $this->mFakePageId = -1; } public function isResolvingRedirects() { @@ -88,7 +102,16 @@ class ApiPageSet extends ApiQueryBase { if ($this->mResolveRedirects) $pageFlds['page_is_redirect'] = null; - return array_keys(array_merge($pageFlds, $this->mRequestedPageFields)); + $pageFlds = array_merge($pageFlds, $this->mRequestedPageFields); + return array_keys($pageFlds); + } + + /** + * Returns an array [ns][dbkey] => page_id for all requested titles + * page_id is a unique negative number in case title was not found + */ + public function getAllTitlesByNamespace() { + return $this->mAllPages; } /** @@ -123,6 +146,7 @@ class ApiPageSet extends ApiQueryBase { /** * Title objects that were NOT found in the database. + * The array's index will be negative for each item * @return array of Title objects */ public function getMissingTitles() { @@ -154,6 +178,15 @@ class ApiPageSet extends ApiQueryBase { return $this->mNormalizedTitles; } + /** + * Get a list of interwiki titles - maps the title given + * with to the interwiki prefix. + * @return array raw_prefixed_title (string) => interwiki_prefix (string) + */ + public function getInterwikiTitles() { + return $this->mInterwikiTitles; + } + /** * Get the list of revision IDs (requested with revids= parameter) * @return array revID (int) => pageID (int) @@ -233,7 +266,6 @@ class ApiPageSet extends ApiQueryBase { */ public function populateFromPageIDs($pageIDs) { $this->profileIn(); - $pageIDs = array_map('intval', $pageIDs); // paranoia $this->initFromPageIds($pageIDs); $this->profileOut(); } @@ -265,22 +297,18 @@ class ApiPageSet extends ApiQueryBase { // Store Title object in various data structures $title = Title :: makeTitle($row->page_namespace, $row->page_title); - // skip any pages that user has no rights to read - if ($title->userCanRead()) { - - $pageId = intval($row->page_id); - $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId; - $this->mTitles[] = $title; - - if ($this->mResolveRedirects && $row->page_is_redirect == '1') { - $this->mPendingRedirectIDs[$pageId] = $title; - } else { - $this->mGoodTitles[$pageId] = $title; - } - - foreach ($this->mRequestedPageFields as $fieldName => & $fieldValues) - $fieldValues[$pageId] = $row-> $fieldName; + $pageId = intval($row->page_id); + $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId; + $this->mTitles[] = $title; + + if ($this->mResolveRedirects && $row->page_is_redirect == '1') { + $this->mPendingRedirectIDs[$pageId] = $title; + } else { + $this->mGoodTitles[$pageId] = $title; } + + foreach ($this->mRequestedPageFields as $fieldName => & $fieldValues) + $fieldValues[$pageId] = $row-> $fieldName; } public function finishPageSetGeneration() { @@ -306,7 +334,7 @@ class ApiPageSet extends ApiQueryBase { private function initFromTitles($titles) { // Get validated and normalized title objects - $linkBatch = $this->processTitlesStrArray($titles); + $linkBatch = $this->processTitlesArray($titles); if($linkBatch->isEmpty()) return; @@ -328,7 +356,8 @@ class ApiPageSet extends ApiQueryBase { private function initFromPageIds($pageids) { if(empty($pageids)) return; - + + $pageids = array_map('intval', $pageids); // paranoia $set = array ( 'page_id' => $pageids ); @@ -386,8 +415,9 @@ class ApiPageSet extends ApiQueryBase { foreach ($remaining as $ns => $dbkeys) { foreach ( $dbkeys as $dbkey => $unused ) { $title = Title :: makeTitle($ns, $dbkey); - $this->mMissingTitles[] = $title; - $this->mAllPages[$ns][$dbkey] = 0; + $this->mAllPages[$ns][$dbkey] = $this->mFakePageId; + $this->mMissingTitles[$this->mFakePageId] = $title; + $this->mFakePageId--; $this->mTitles[] = $title; } } @@ -536,39 +566,46 @@ class ApiPageSet extends ApiQueryBase { /** * Given an array of title strings, convert them into Title objects. + * Alternativelly, an array of Title objects may be given. * This method validates access rights for the title, * and appends normalization values to the output. * * @return LinkBatch of title objects. */ - private function processTitlesStrArray($titles) { + private function processTitlesArray($titles) { $linkBatch = new LinkBatch(); - foreach ($titles as $titleString) { - $titleObj = Title :: newFromText($titleString); - - // Validation + foreach ($titles as $title) { + + $titleObj = is_string($title) ? Title :: newFromText($title) : $title; if (!$titleObj) - $this->dieUsage("bad title $titleString", 'invalidtitle'); - if ($titleObj->getNamespace() < 0) - $this->dieUsage("No support for special page $titleString has been implemented", 'unsupportednamespace'); - if (!$titleObj->userCanRead()) - $this->dieUsage("No read permission for $titleString", 'titleaccessdenied'); + $this->dieUsage("bad title", 'invalidtitle'); - $linkBatch->addObj($titleObj); + $iw = $titleObj->getInterwiki(); + if (!empty($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); + } + // Make sure we remember the original title that was given to us // This way the caller can correlate new titles with the originally requested, // i.e. namespace is localized or capitalization is different - if ($titleString !== $titleObj->getPrefixedText()) { - $this->mNormalizedTitles[$titleString] = $titleObj->getPrefixedText(); + if (is_string($title) && $title !== $titleObj->getPrefixedText()) { + $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText(); } } return $linkBatch; } - + protected function getAllowedParams() { return array ( 'titles' => array ( @@ -594,7 +631,7 @@ class ApiPageSet extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPageSet.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiPageSet.php 24935 2007-08-20 08:13:16Z nickj $'; } } -?> + diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 6ee05085..76dbb338 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,49 +29,67 @@ if (!defined('MEDIAWIKI')) { } /** + * This is the main query class. It behaves similar to ApiMain: based on the parameters given, + * it will create a list of titles to work on (an instance of the ApiPageSet object) + * instantiate and execute various property/list/meta modules, + * and assemble all resulting data into a single ApiResult object. + * + * In the generator mode, a generator will be first executed to populate a second ApiPageSet object, + * and that object will be used for all subsequent modules. + * * @addtogroup API */ class ApiQuery extends ApiBase { private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames; private $mPageSet; + private $params, $redirect; private $mQueryPropModules = array ( 'info' => 'ApiQueryInfo', - 'revisions' => 'ApiQueryRevisions' + 'revisions' => 'ApiQueryRevisions', + 'links' => 'ApiQueryLinks', + 'langlinks' => 'ApiQueryLangLinks', + 'images' => 'ApiQueryImages', + 'imageinfo' => 'ApiQueryImageInfo', + 'templates' => 'ApiQueryLinks', + 'categories' => 'ApiQueryCategories', + 'extlinks' => 'ApiQueryExternalLinks', ); - // 'categories' => 'ApiQueryCategories', - // 'imageinfo' => 'ApiQueryImageinfo', - // 'langlinks' => 'ApiQueryLanglinks', - // 'links' => 'ApiQueryLinks', - // 'templates' => 'ApiQueryTemplates', private $mQueryListModules = array ( 'allpages' => 'ApiQueryAllpages', - 'logevents' => 'ApiQueryLogEvents', - 'watchlist' => 'ApiQueryWatchlist', - 'recentchanges' => 'ApiQueryRecentChanges', + 'alllinks' => 'ApiQueryAllLinks', + 'allusers' => 'ApiQueryAllUsers', 'backlinks' => 'ApiQueryBacklinks', + 'categorymembers' => 'ApiQueryCategoryMembers', 'embeddedin' => 'ApiQueryBacklinks', - 'imagelinks' => 'ApiQueryBacklinks', - 'usercontribs' => 'ApiQueryContributions' + 'imageusage' => 'ApiQueryBacklinks', + 'logevents' => 'ApiQueryLogEvents', + 'recentchanges' => 'ApiQueryRecentChanges', + 'search' => 'ApiQuerySearch', + 'usercontribs' => 'ApiQueryContributions', + 'watchlist' => 'ApiQueryWatchlist', + 'exturlusage' => 'ApiQueryExtLinksUsage', ); - // 'categorymembers' => 'ApiQueryCategorymembers', - // 'embeddedin' => 'ApiQueryEmbeddedin', - // 'imagelinks' => 'ApiQueryImagelinks', - // 'recentchanges' => 'ApiQueryRecentchanges', - // 'users' => 'ApiQueryUsers', - // 'watchlist' => 'ApiQueryWatchlist', private $mQueryMetaModules = array ( - 'siteinfo' => 'ApiQuerySiteinfo' + 'siteinfo' => 'ApiQuerySiteinfo', + 'userinfo' => 'ApiQueryUserInfo', ); - // 'userinfo' => 'ApiQueryUserinfo', private $mSlaveDB = null; + private $mNamedDB = array(); public function __construct($main, $action) { 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); + $this->mPropModuleNames = array_keys($this->mQueryPropModules); $this->mListModuleNames = array_keys($this->mQueryListModules); $this->mMetaModuleNames = array_keys($this->mQueryMetaModules); @@ -81,6 +99,20 @@ class ApiQuery extends ApiBase { $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames); } + /** + * Helper function to append any add-in modules to the list + */ + private static function appendUserModules(&$modules, $newModules) { + if (is_array( $newModules )) { + foreach ( $newModules as $moduleName => $moduleClass) { + $modules[$moduleName] = $moduleClass; + } + } + } + + /** + * Gets a default slave database connection object + */ public function getDB() { if (!isset ($this->mSlaveDB)) { $this->profileDBIn(); @@ -90,6 +122,24 @@ class ApiQuery extends ApiBase { return $this->mSlaveDB; } + /** + * Get the query database connection with the given name. + * If no such connection has been requested before, it will be created. + * Subsequent calls with the same $name will return the same connection + * as the first, regardless of $db or $groups new values. + */ + public function getNamedDB($name, $db, $groups) { + if (!array_key_exists($name, $this->mNamedDB)) { + $this->profileDBIn(); + $this->mNamedDB[$name] = wfGetDB($db, $groups); + $this->profileDBOut(); + } + return $this->mNamedDB[$name]; + } + + /** + * Gets the set of pages the user has requested (or generated) + */ public function getPageSet() { return $this->mPageSet; } @@ -105,42 +155,33 @@ class ApiQuery extends ApiBase { * #5 Execute all requested modules */ public function execute() { - $prop = $list = $meta = $generator = $redirects = null; - extract($this->extractRequestParams()); - + + $this->params = $this->extractRequestParams(); + $this->redirects = $this->params['redirects']; + // // Create PageSet // - $this->mPageSet = new ApiPageSet($this, $redirects); - - // Instantiate required modules - $modules = array (); - if (isset ($prop)) - foreach ($prop as $moduleName) - $modules[] = new $this->mQueryPropModules[$moduleName] ($this, $moduleName); - if (isset ($list)) - foreach ($list as $moduleName) - $modules[] = new $this->mQueryListModules[$moduleName] ($this, $moduleName); - if (isset ($meta)) - foreach ($meta as $moduleName) - $modules[] = new $this->mQueryMetaModules[$moduleName] ($this, $moduleName); - - // Modules may optimize data requests through the $this->getPageSet() object - // Execute all requested modules. - foreach ($modules as $module) { - $module->requestExtraData(); - } + $this->mPageSet = new ApiPageSet($this, $this->redirects); // - // If given, execute generator to substitute user supplied data with generated data. + // Instantiate requested modules // - if (isset ($generator)) - $this->executeGeneratorModule($generator, $redirects); + $modules = array (); + $this->InstantiateModules($modules, 'prop', $this->mQueryPropModules); + $this->InstantiateModules($modules, 'list', $this->mQueryListModules); + $this->InstantiateModules($modules, 'meta', $this->mQueryMetaModules); // - // Populate page information for the given pageSet + // If given, execute generator to substitute user supplied data with generated data. // - $this->mPageSet->execute(); + if (isset ($this->params['generator'])) { + $this->executeGeneratorModule($this->params['generator'], $modules); + } else { + // Append custom fields and populate page/revision information + $this->addCustomFldsToPageSet($modules, $this->mPageSet); + $this->mPageSet->execute(); + } // // Record page information (title, namespace, if exists, etc) @@ -156,7 +197,33 @@ class ApiQuery extends ApiBase { $module->profileOut(); } } + + /** + * Query modules may optimize data requests through the $this->getPageSet() object + * by adding extra fields from the page table. + * This function will gather all the extra request fields from the modules. + */ + private function addCustomFldsToPageSet($modules, $pageSet) { + // Query all requested modules. + foreach ($modules as $module) { + $module->requestExtraData($pageSet); + } + } + + /** + * Create instances of all modules requested by the client + */ + private function InstantiateModules(&$modules, $param, $moduleList) { + $list = $this->params[$param]; + if (isset ($list)) + foreach ($list as $moduleName) + $modules[] = new $moduleList[$moduleName] ($this, $moduleName); + } + /** + * Appends an element for each page in the current pageSet with the most general + * information (id, title), plus any title normalizations and missing title/pageids/revids. + */ private function outputGeneralPageInfo() { $pageSet = $this->getPageSet(); @@ -175,7 +242,21 @@ class ApiQuery extends ApiBase { $result->setIndexedTagName($normValues, 'n'); $result->addValue('query', 'normalized', $normValues); } + + // Interwiki titles + $intrwValues = array (); + foreach ($pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr) { + $intrwValues[] = array ( + 'title' => $rawTitleStr, + 'iw' => $interwikiStr + ); + } + if (!empty ($intrwValues)) { + $result->setIndexedTagName($intrwValues, 'i'); + $result->addValue('query', 'interwiki', $intrwValues); + } + // Show redirect information $redirValues = array (); foreach ($pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo) { @@ -211,10 +292,11 @@ class ApiQuery extends ApiBase { $pages = array (); // Report any missing titles - $fakepageid = -1; - foreach ($pageSet->getMissingTitles() as $title) { - $pages[$fakepageid--] = array ( - 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'missing' => ''); + foreach ($pageSet->getMissingTitles() as $fakeId => $title) { + $vals = array(); + ApiQueryBase :: addTitleInfo($vals, $title); + $vals['missing'] = ''; + $pages[$fakeId] = $vals; } // Report any missing page ids @@ -227,32 +309,43 @@ class ApiQuery extends ApiBase { // Output general page information for found titles foreach ($pageSet->getGoodTitles() as $pageid => $title) { - $pages[$pageid] = array ( - 'pageid' => $pageid, - 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText()); + $vals = array(); + $vals['pageid'] = $pageid; + ApiQueryBase :: addTitleInfo($vals, $title); + $pages[$pageid] = $vals; } if (!empty ($pages)) { + + if ($this->params['indexpageids']) { + $pageIDs = array_keys($pages); + // json treats all map keys as strings - converting to match + $pageIDs = array_map('strval', $pageIDs); + $result->setIndexedTagName($pageIDs, 'id'); + $result->addValue('query', 'pageids', $pageIDs); + } + $result->setIndexedTagName($pages, 'page'); $result->addValue('query', 'pages', $pages); } } - protected function executeGeneratorModule($generatorName, $redirects) { + /** + * For generator mode, execute generator, and use its output as new pageSet + */ + protected function executeGeneratorModule($generatorName, $modules) { // Find class that implements requested generator if (isset ($this->mQueryListModules[$generatorName])) { $className = $this->mQueryListModules[$generatorName]; - } - elseif (isset ($this->mQueryPropModules[$generatorName])) { + } elseif (isset ($this->mQueryPropModules[$generatorName])) { $className = $this->mQueryPropModules[$generatorName]; } else { ApiBase :: dieDebug(__METHOD__, "Unknown generator=$generatorName"); } - // Use current pageset as the result, and create a new one just for the generator - $resultPageSet = $this->mPageSet; - $this->mPageSet = new ApiPageSet($this, $redirects); + // Generator results + $resultPageSet = new ApiPageSet($this, $this->redirects); // Create and execute the generator $generator = new $className ($this, $generatorName); @@ -260,9 +353,12 @@ class ApiQuery extends ApiBase { $this->dieUsage("Module $generatorName cannot be used as a generator", "badgenerator"); $generator->setGeneratorMode(); - $generator->requestExtraData(); - // execute current pageSet to get the data for the generator module + // Add any additional fields modules may need + $generator->requestExtraData($this->mPageSet); + $this->addCustomFldsToPageSet($modules, $resultPageSet); + + // Populate page information with the original user input $this->mPageSet->execute(); // populate resultPageSet with the generator output @@ -275,6 +371,10 @@ class ApiQuery extends ApiBase { $this->mPageSet = $resultPageSet; } + /** + * Returns the list of allowed parameters for this module. + * Qurey module also lists all ApiPageSet parameters as its own. + */ protected function getAllowedParams() { return array ( 'prop' => array ( @@ -292,7 +392,8 @@ class ApiQuery extends ApiBase { 'generator' => array ( ApiBase :: PARAM_TYPE => $this->mAllowedGenerators ), - 'redirects' => false + 'redirects' => false, + 'indexpageids' => false, ); } @@ -301,12 +402,12 @@ class ApiQuery extends ApiBase { */ public function makeHelpMsg() { - // Use parent to make default message for the query module - $msg = parent :: makeHelpMsg(); + $msg = ''; // Make sure the internal object is empty // (just in case a sub-module decides to optimize during instantiation) $this->mPageSet = null; + $this->mAllowedGenerators = array(); // Will be repopulated $astriks = str_repeat('--- ', 8); $msg .= "\n$astriks Query: Prop $astriks\n\n"; @@ -316,21 +417,32 @@ class ApiQuery extends ApiBase { $msg .= "\n$astriks Query: Meta $astriks\n\n"; $msg .= $this->makeHelpMsgHelper($this->mQueryMetaModules, 'meta'); + // Perform the base call last because the $this->mAllowedGenerators + // will be updated inside makeHelpMsgHelper() + // Use parent to make default message for the query module + $msg = parent :: makeHelpMsg() . $msg; + return $msg; } + /** + * For all modules in $moduleList, generate help messages and join them together + */ private function makeHelpMsgHelper($moduleList, $paramName) { $moduleDscriptions = array (); foreach ($moduleList as $moduleName => $moduleClass) { - $msg = "* $paramName=$moduleName *"; $module = new $moduleClass ($this, $moduleName, null); + + $msg = ApiMain::makeHelpMsgHeader($module, $paramName); $msg2 = $module->makeHelpMsg(); if ($msg2 !== false) $msg .= $msg2; - if ($module instanceof ApiQueryGeneratorBase) + if ($module instanceof ApiQueryGeneratorBase) { + $this->mAllowedGenerators[] = $moduleName; $msg .= "Generator:\n This module may be used as a generator\n"; + } $moduleDscriptions[] = $msg; } @@ -351,7 +463,8 @@ class ApiQuery extends ApiBase { 'list' => 'Which lists to get', 'meta' => 'Which meta data to get about the site', 'generator' => 'Use the output of a list as the input for other prop/list/meta items', - 'redirects' => 'Automatically resolve redirects' + 'redirects' => 'Automatically resolve redirects', + 'indexpageids' => 'Include an additional pageids section listing all returned page IDs.' ); } @@ -372,9 +485,9 @@ class ApiQuery extends ApiBase { public function getVersion() { $psModule = new ApiPageSet($this); $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiQuery.php 21402 2007-04-20 08:55:14Z nickj $'; + $vers[] = __CLASS__ . ': $Id: ApiQuery.php 24494 2007-07-31 17:53:37Z yurik $'; $vers[] = $psModule->getVersion(); return $vers; } } -?> + diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php new file mode 100644 index 00000000..17f24b65 --- /dev/null +++ b/includes/api/ApiQueryAllLinks.php @@ -0,0 +1,179 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * Query module to enumerate links from all pages together. + * + * @addtogroup API + */ +class ApiQueryAllLinks extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'al'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + + $db = $this->getDB(); + $params = $this->extractRequestParams(); + + $prop = array_flip($params['prop']); + $fld_ids = isset($prop['ids']); + $fld_title = isset($prop['title']); + + if ($params['unique']) { + if (!is_null($resultPageSet)) + $this->dieUsage($this->getModuleName() . ' cannot be used as a generator in unique links mode', 'params'); + if ($fld_ids) + $this->dieUsage($this->getModuleName() . ' cannot return corresponding page ids in unique links mode', 'params'); + $this->addOption('DISTINCT'); + } + + $this->addTables('pagelinks'); + $this->addWhereFld('pl_namespace', $params['namespace']); + + if (!is_null($params['from'])) + $this->addWhere('pl_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($params['from']))); + if (isset ($params['prefix'])) + $this->addWhere("pl_title LIKE '" . $db->escapeLike(ApiQueryBase :: titleToKey($params['prefix'])) . "%'"); + + if (is_null($resultPageSet)) { + $this->addFields(array ( + 'pl_namespace', + 'pl_title' + )); + $this->addFieldsIf('pl_from', $fld_ids); + } else { + $this->addFields('pl_from'); + $pageids = array(); + } + + $this->addOption('USE INDEX', 'pl_namespace'); + $limit = $params['limit']; + $this->addOption('LIMIT', $limit+1); + $this->addOption('ORDER BY', 'pl_namespace, pl_title'); + + $res = $this->select(__METHOD__); + + $data = array (); + $count = 0; + 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... + // TODO: Security issue - if the user has no right to view next title, it will still be shown + $this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->pl_title)); + break; + } + + if (is_null($resultPageSet)) { + $vals = array(); + if ($fld_ids) + $vals['fromid'] = intval($row->pl_from); + if ($fld_title) { + $title = Title :: makeTitle($row->pl_namespace, $row->pl_title); + $vals['ns'] = intval($title->getNamespace()); + $vals['title'] = $title->getPrefixedText(); + } + $data[] = $vals; + } else { + $pageids[] = $row->pl_from; + } + } + $db->freeResult($res); + + if (is_null($resultPageSet)) { + $result = $this->getResult(); + $result->setIndexedTagName($data, 'l'); + $result->addValue('query', $this->getModuleName(), $data); + } else { + $resultPageSet->populateFromPageIDs($pageids); + } + } + + protected function getAllowedParams() { + return array ( + 'from' => null, + 'prefix' => null, + 'unique' => false, + 'prop' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'title', + ApiBase :: PARAM_TYPE => array ( + 'ids', + 'title' + ) + ), + 'namespace' => array ( + ApiBase :: PARAM_DFLT => 0, + 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 + ) + ); + } + + protected function getParamDescription() { + return array ( + 'from' => 'The page title to start enumerating from.', + 'prefix' => 'Search for all page titles that begin with this value.', + 'unique' => 'Only show unique links. Cannot be used with generator or prop=ids', + 'prop' => 'What pieces of information to include', + 'namespace' => 'The namespace to enumerate.', + 'limit' => 'How many total links to return.' + ); + } + + protected function getDescription() { + return 'Enumerate all links that point to a given namespace'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=alllinks&alunique&alfrom=B', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryAllLinks.php 24453 2007-07-30 08:09:15Z yurik $'; + } +} diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php new file mode 100644 index 00000000..92bcc1a1 --- /dev/null +++ b/includes/api/ApiQueryAllUsers.php @@ -0,0 +1,204 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * Query module to enumerate all registered users. + * + * @addtogroup API + */ +class ApiQueryAllUsers extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'au'); + } + + public function execute() { + $db = $this->getDB(); + $params = $this->extractRequestParams(); + + $prop = $params['prop']; + if (!is_null($prop)) { + $prop = array_flip($prop); + $fld_editcount = isset($prop['editcount']); + $fld_groups = isset($prop['groups']); + } else { + $fld_editcount = $fld_groups = false; + } + + $limit = $params['limit']; + $tables = $db->tableName('user'); + + if( !is_null( $params['from'] ) ) + $this->addWhere( 'user_name >= ' . $db->addQuotes( self::keyToTitle( $params['from'] ) ) ); + + if( isset( $params['prefix'] ) ) + $this->addWhere( 'user_name LIKE "' . $db->escapeLike( self::keyToTitle( $params['prefix'] ) ) . '%"' ); + + if (!is_null($params['group'])) { + // Filter only users that belong to a given group + $tblName = $db->tableName('user_groups'); + $tables = "$tables INNER JOIN $tblName ug1 ON ug1.ug_user=user_id"; + $this->addWhereFld('ug1.ug_group', $params['group']); + } + + if ($fld_groups) { + // Show the groups the given users belong to + // request more than needed to avoid not getting all rows that belong to one user + $groupCount = count(User::getAllGroups()); + $sqlLimit = $limit+$groupCount+1; + + $tblName = $db->tableName('user_groups'); + $tables = "$tables LEFT JOIN $tblName ug2 ON ug2.ug_user=user_id"; + $this->addFields('ug2.ug_group ug_group2'); + } else { + $sqlLimit = $limit+1; + } + + $this->addOption('LIMIT', $sqlLimit); + $this->addTables($tables); + + $this->addFields('user_name'); + $this->addFieldsIf('user_editcount', $fld_editcount); + + $this->addOption('ORDER BY', 'user_name'); + + $res = $this->select(__METHOD__); + + $data = array (); + $count = 0; + $lastUserData = false; + $lastUser = false; + $result = $this->getResult(); + + // + // This loop keeps track of the last entry. + // For each new row, if the new row is for different user then the last, the last entry is added to results. + // Otherwise, the group of the new row is appended to the last entry. + // The setContinue... is more complex because of this, and takes into account the higher sql limit + // to make sure all rows that belong to the same user are received. + // + while (true) { + + $row = $db->fetchObject($res); + $count++; + + if (!$row || $lastUser != $row->user_name) { + // Save the last pass's user data + if (is_array($lastUserData)) + $data[] = $lastUserData; + + // No more rows left + if (!$row) + break; + + if ($count > $limit) { + // We've reached the one extra which shows that there are additional pages to be had. Stop here... + $this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->user_name)); + break; + } + + // Record new user's data + $lastUser = $row->user_name; + $lastUserData = array( 'name' => $lastUser ); + if ($fld_editcount) + $lastUserData['editcount'] = intval($row->user_editcount); + + } + + if ($sqlLimit == $count) { + // BUG! database contains group name that User::getAllGroups() does not return + // TODO: should handle this more gracefully + ApiBase :: dieDebug(__METHOD__, + 'MediaWiki configuration error: the database contains more user groups than known to User::getAllGroups() function'); + } + + // Add user's group info + if ($fld_groups && !is_null($row->ug_group2)) { + $lastUserData['groups'][] = $row->ug_group2; + $result->setIndexedTagName($lastUserData['groups'], 'g'); + } + } + + $db->freeResult($res); + + $result->setIndexedTagName($data, 'u'); + $result->addValue('query', $this->getModuleName(), $data); + } + + protected function getAllowedParams() { + return array ( + 'from' => null, + 'prefix' => null, + 'group' => array( + ApiBase :: PARAM_TYPE => User::getAllGroups() + ), + 'prop' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'editcount', + 'groups', + ) + ), + '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 + ) + ); + } + + protected function getParamDescription() { + return array ( + 'from' => 'The user name to start enumerating from.', + 'prefix' => 'Search for all page titles that begin with this value.', + 'group' => 'Limit users to a given group name', + 'prop' => array( + 'What pieces of information to include.', + '`groups` property uses more server resources and may return fewer results than the limit.'), + 'limit' => 'How many total user names to return.', + ); + } + + protected function getDescription() { + return 'Enumerate all registered users'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=allusers&aufrom=Y', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryAllUsers.php 24870 2007-08-17 13:01:35Z robchurch $'; + } +} diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index 494f7707..d9715b1a 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,8 @@ if (!defined('MEDIAWIKI')) { } /** + * Query module to enumerate all available pages. + * * @addtogroup API */ class ApiQueryAllpages extends ApiQueryGeneratorBase { @@ -50,22 +52,51 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { private function run($resultPageSet = null) { - wfProfileIn($this->getModuleProfileName() . '-getDB'); $db = $this->getDB(); - wfProfileOut($this->getModuleProfileName() . '-getDB'); - - wfProfileIn($this->getModuleProfileName() . '-parseParams'); - $limit = $from = $namespace = $filterredir = $prefix = null; - extract($this->extractRequestParams()); + $params = $this->extractRequestParams(); + + // Page filters + if (!$this->addWhereIf('page_is_redirect = 1', $params['filterredir'] === 'redirects')) + $this->addWhereIf('page_is_redirect = 0', $params['filterredir'] === 'nonredirects'); + $this->addWhereFld('page_namespace', $params['namespace']); + if (!is_null($params['from'])) + $this->addWhere('page_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($params['from']))); + if (isset ($params['prefix'])) + $this->addWhere("page_title LIKE '" . $db->escapeLike(ApiQueryBase :: titleToKey($params['prefix'])) . "%'"); + + $forceNameTitleIndex = true; + if (isset ($params['minsize'])) { + $this->addWhere('page_len>=' . intval($params['minsize'])); + $forceNameTitleIndex = false; + } + + if (isset ($params['maxsize'])) { + $this->addWhere('page_len<=' . intval($params['maxsize'])); + $forceNameTitleIndex = false; + } + + // Page protection filtering + if (isset ($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 != '*') + $this->addWhereFld('pr_level', $prlevel); + + $forceNameTitleIndex = false; + + } else if (isset ($params['prlevel'])) { + $this->dieUsage('prlevel may not be used without prtype', 'params'); + } + $this->addTables('page'); - if (!$this->addWhereIf('page_is_redirect = 1', $filterredir === 'redirects')) - $this->addWhereIf('page_is_redirect = 0', $filterredir === 'nonredirects'); - $this->addWhereFld('page_namespace', $namespace); - if (isset ($from)) - $this->addWhere('page_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($from))); - if (isset ($prefix)) - $this->addWhere("page_title LIKE '{$db->strencode(ApiQueryBase :: titleToKey($prefix))}%'"); + if ($forceNameTitleIndex) + $this->addOption('USE INDEX', 'name_title'); + if (is_null($resultPageSet)) { $this->addFields(array ( @@ -77,29 +108,28 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $this->addFields($resultPageSet->getPageTableFields()); } - $this->addOption('USE INDEX', 'name_title'); - $this->addOption('LIMIT', $limit +1); + $limit = $params['limit']; + $this->addOption('LIMIT', $limit+1); $this->addOption('ORDER BY', 'page_namespace, page_title'); - wfProfileOut($this->getModuleProfileName() . '-parseParams'); - $res = $this->select(__METHOD__); - wfProfileIn($this->getModuleProfileName() . '-saveResults'); - $data = array (); $count = 0; 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... + // TODO: Security issue - if the user has no right to view next title, it will still be shown $this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->page_title)); break; } if (is_null($resultPageSet)) { - $vals = $this->addRowInfo('page', $row); - if ($vals) - $data[intval($row->page_id)] = $vals; + $title = Title :: makeTitle($row->page_namespace, $row->page_title); + $data[] = array( + 'pageid' => intval($row->page_id), + 'ns' => intval($title->getNamespace()), + 'title' => $title->getPrefixedText()); } else { $resultPageSet->processDbRow($row); } @@ -111,17 +141,17 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $result->setIndexedTagName($data, 'p'); $result->addValue('query', $this->getModuleName(), $data); } - - wfProfileOut($this->getModuleProfileName() . '-saveResults'); } protected function getAllowedParams() { + global $wgRestrictionTypes, $wgRestrictionLevels; + return array ( 'from' => null, 'prefix' => null, 'namespace' => array ( ApiBase :: PARAM_DFLT => 0, - ApiBase :: PARAM_TYPE => 'namespace' + ApiBase :: PARAM_TYPE => 'namespace', ), 'filterredir' => array ( ApiBase :: PARAM_DFLT => 'all', @@ -131,11 +161,23 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { 'nonredirects' ) ), + 'minsize' => array ( + ApiBase :: PARAM_TYPE => 'integer', + ), + 'maxsize' => array ( + ApiBase :: PARAM_TYPE => 'integer', + ), + 'prtype' => array ( + ApiBase :: PARAM_TYPE => $wgRestrictionTypes, + ), + 'prlevel' => array ( + ApiBase :: PARAM_TYPE => $wgRestrictionLevels, + ), 'limit' => array ( ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ) ); @@ -147,6 +189,10 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { 'prefix' => 'Search for all page titles that begin with this value.', 'namespace' => 'The namespace to enumerate.', 'filterredir' => 'Which pages to list.', + 'minsize' => 'Limit to pages with at least this many bytes', + 'maxsize' => 'Limit to pages with at most this many bytes', + 'prtype' => 'Limit to protected pages only', + 'prlevel' => 'The protection level (must be used with apprtype= parameter)', 'limit' => 'How many total pages to return.' ); } @@ -169,7 +215,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllpages.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryAllpages.php 24694 2007-08-09 08:41:58Z yurik $'; } } -?> + diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index 1a6783a9..a676b4bf 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,11 +29,16 @@ if (!defined('MEDIAWIKI')) { } /** + * This is three-in-one module to query: + * * backlinks - links pointing to the given page, + * * embeddedin - what pages transclude the given page within themselves, + * * imageusage - what pages use the given image + * * @addtogroup API */ class ApiQueryBacklinks extends ApiQueryGeneratorBase { - private $rootTitle, $contRedirs, $contLevel, $contTitle, $contID; + private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID; // output element name, database column field prefix, database table private $backlinksSettings = array ( @@ -47,8 +52,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { 'prefix' => 'tl', 'linktbl' => 'templatelinks' ), - 'imagelinks' => array ( - 'code' => 'il', + 'imageusage' => array ( + 'code' => 'iu', 'prefix' => 'il', 'linktbl' => 'imagelinks' ) @@ -67,7 +72,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { ); $this->bl_code = $code; - $this->hasNS = $moduleName !== 'imagelinks'; + $this->hasNS = $moduleName !== 'imageusage'; if ($this->hasNS) { $this->bl_title = $prefix . '_title'; $this->bl_sort = "{$this->bl_ns}, {$this->bl_title}, {$this->bl_from}"; @@ -93,13 +98,13 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } private function run($resultPageSet = null) { - $continue = $namespace = $redirect = $limit = null; - extract($this->extractRequestParams()); - + $this->params = $this->extractRequestParams(); + + $redirect = $this->params['redirect']; if ($redirect) - ApiBase :: dieDebug(__METHOD__, 'Redirect is not yet been implemented', 'notimplemented'); + $this->dieDebug('Redirect has not been implemented', 'notimplemented'); - $this->processContinue($continue, $redirect); + $this->processContinue(); $this->addFields($this->bl_fields); if (is_null($resultPageSet)) @@ -117,15 +122,19 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { if ($this->hasNS) $this->addWhereFld($this->bl_ns, $this->rootTitle->getNamespace()); $this->addWhereFld($this->bl_title, $this->rootTitle->getDBkey()); - $this->addWhereFld('page_namespace', $namespace); - $this->addOption('LIMIT', $limit +1); - $this->addOption('ORDER BY', $this->bl_sort); + $this->addWhereFld('page_namespace', $this->params['namespace']); - if ($redirect) + if($this->params['filterredir'] == 'redirects') + $this->addWhereFld('page_is_redirect', 1); + if($this->params['filterredir'] == 'nonredirects') $this->addWhereFld('page_is_redirect', 0); + $limit = $this->params['limit']; + $this->addOption('LIMIT', $limit +1); + $this->addOption('ORDER BY', $this->bl_sort); + $db = $this->getDB(); - if (!is_null($continue)) { + if (!is_null($this->params['continue'])) { $plfrm = intval($this->contID); if ($this->contLevel == 0) { // For the first level, there is only one target title, so no need for complex filtering @@ -150,48 +159,61 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { if (++ $count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... if ($redirect) { - $ns = $row-> { - $this->bl_ns }; - $t = $row-> { - $this->bl_title }; + $ns = $row-> { $this->bl_ns }; + $t = $row-> { $this->bl_title }; $continue = $this->getContinueRedirStr(false, 0, $ns, $t, $row->page_id); } else $continue = $this->getContinueStr($row->page_id); + // TODO: Security issue - if the user has no right to view next title, it will still be shown $this->setContinueEnumParameter('continue', $continue); break; } if (is_null($resultPageSet)) { - $vals = $this->addRowInfo('page', $row); + $vals = $this->extractRowInfo($row); if ($vals) - $data[intval($row->page_id)] = $vals; + $data[] = $vals; } else { $resultPageSet->processDbRow($row); } } $db->freeResult($res); - if (is_null($resultPageSet)) { + if (is_null($resultPageSet) && !empty($data)) { $result = $this->getResult(); $result->setIndexedTagName($data, $this->bl_code); $result->addValue('query', $this->getModuleName(), $data); } } - protected function processContinue($continue, $redirect) { + private function extractRowInfo($row) { + + $vals = array(); + $vals['pageid'] = intval($row->page_id); + ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->page_namespace, $row->page_title)); + + return $vals; + } + + protected function processContinue() { $pageSet = $this->getPageSet(); $count = $pageSet->getTitleCount(); - if (!is_null($continue)) { - if ($count !== 0) - $this->dieUsage("When continuing the {$this->getModuleName()} query, no other titles may be provided", 'titles_on_continue'); - $this->parseContinueParam($continue, $redirect); + + if (!is_null($this->params['continue'])) { + $this->parseContinueParam(); // Skip all completed links } else { - if ($count !== 1) - $this->dieUsage("The {$this->getModuleName()} query requires one title to start", 'bad_title_count'); - $this->rootTitle = current($pageSet->getTitles()); // only one title there + $title = $this->params['title']; + if (!is_null($title)) { + $this->rootTitle = Title :: newFromText($title); + } else { // This case is obsolete. Will support this for a while + if ($count !== 1) + $this->dieUsage("The {$this->getModuleName()} query requires one title to start", 'bad_title_count'); + $this->rootTitle = current($pageSet->getTitles()); // only one title there + $this->setWarning('Using titles parameter is obsolete for this list. Use ' . $this->encodeParamName('title') . ' instead.'); + } } // only image titles are allowed for the root @@ -199,9 +221,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->dieUsage("The title for {$this->getModuleName()} query must be an image", 'bad_image_title'); } - protected function parseContinueParam($continue, $redirect) { - $continueList = explode('|', $continue); - if ($redirect) { + protected function parseContinueParam() { + $continueList = explode('|', $this->params['continue']); + if ($this->params['redirect']) { // // expected redirect-mode parameter: // ns|db_key|step|level|ns|db_key|id @@ -215,7 +237,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $rootNs = intval($continueList[0]); if (($rootNs !== 0 || $continueList[0] === '0') && !empty ($continueList[1])) { $this->rootTitle = Title :: makeTitleSafe($rootNs, $continueList[1]); - if ($this->rootTitle && $this->rootTitle->userCanRead()) { + if ($this->rootTitle) { $step = intval($continueList[2]); if ($step === 1 || $step === 2) { @@ -263,7 +285,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $rootNs = intval($continueList[0]); if (($rootNs !== 0 || $continueList[0] === '0') && !empty ($continueList[1])) { $this->rootTitle = Title :: makeTitleSafe($rootNs, $continueList[1]); - if ($this->rootTitle && $this->rootTitle->userCanRead()) { + if ($this->rootTitle) { $contID = intval($continueList[2]); if ($contID !== 0) { @@ -296,17 +318,26 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { protected function getAllowedParams() { return array ( + 'title' => null, 'continue' => null, 'namespace' => array ( ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => 'namespace' ), + 'filterredir' => array( + ApiBase :: PARAM_DFLT => 'all', + ApiBase :: PARAM_TYPE => array( + 'all', + 'redirects', + 'nonredirects' + ) + ), 'redirect' => false, 'limit' => array ( ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ) ); @@ -314,8 +345,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { protected function getParamDescription() { return array ( + 'title' => 'Title to search. If null, titles= parameter will be used instead, but will be obsolete soon.', 'continue' => 'When more results are available, use this to continue.', 'namespace' => 'The namespace to enumerate.', + 'filterredir' => 'How to filter for redirects', 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect (not implemented)', 'limit' => 'How many total pages to return.' ); @@ -327,7 +360,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { return 'Find all pages that link to the given page'; case 'embeddedin' : return 'Find all pages that embed (transclude) the given title'; - case 'imagelinks' : + case 'imageusage' : return 'Find all pages that use the given image title.'; default : ApiBase :: dieDebug(__METHOD__, 'Unknown module name'); @@ -337,16 +370,16 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { protected function getExamples() { static $examples = array ( 'backlinks' => array ( - "api.php?action=query&list=backlinks&titles=Main%20Page", - "api.php?action=query&generator=backlinks&titles=Main%20Page&prop=info" + "api.php?action=query&list=backlinks&bltitle=Main%20Page", + "api.php?action=query&generator=backlinks&gbltitle=Main%20Page&prop=info" ), 'embeddedin' => array ( - "api.php?action=query&list=embeddedin&titles=Template:Stub", - "api.php?action=query&generator=embeddedin&titles=Template:Stub&prop=info" + "api.php?action=query&list=embeddedin&eititle=Template:Stub", + "api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info" ), - 'imagelinks' => array ( - "api.php?action=query&list=imagelinks&titles=Image:Albert%20Einstein%20Head.jpg", - "api.php?action=query&generator=imagelinks&titles=Image:Albert%20Einstein%20Head.jpg&prop=info" + 'imageusage' => array ( + "api.php?action=query&list=imageusage&iutitle=Image:Albert%20Einstein%20Head.jpg", + "api.php?action=query&generator=imageusage&giutitle=Image:Albert%20Einstein%20Head.jpg&prop=info" ) ); @@ -354,7 +387,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBacklinks.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryBacklinks.php 25476 2007-09-04 14:44:46Z catrope $'; } } -?> + diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index da07bb6c..28adb415 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,15 +29,19 @@ if (!defined('MEDIAWIKI')) { } /** + * This is a base class for all Query modules. + * It provides some common functionality such as constructing various SQL queries. + * * @addtogroup API */ abstract class ApiQueryBase extends ApiBase { - private $mQueryModule, $tables, $where, $fields, $options; + private $mQueryModule, $mDb, $tables, $where, $fields, $options; public function __construct($query, $moduleName, $paramPrefix = '') { parent :: __construct($query->getMain(), $moduleName, $paramPrefix); $this->mQueryModule = $query; + $this->mDb = null; $this->resetQueryParams(); } @@ -48,11 +52,16 @@ abstract class ApiQueryBase extends ApiBase { $this->options = array (); } - protected function addTables($value) { - if (is_array($value)) - $this->tables = array_merge($this->tables, $value); - else - $this->tables[] = $value; + protected function addTables($tables, $alias = null) { + if (is_array($tables)) { + if (!is_null($alias)) + ApiBase :: dieDebug(__METHOD__, 'Multiple table aliases not supported'); + $this->tables = array_merge($this->tables, $tables); + } else { + if (!is_null($alias)) + $tables = $this->getDB()->tableName($tables) . ' ' . $alias; + $this->tables[] = $tables; + } } protected function addFields($value) { @@ -124,176 +133,16 @@ abstract class ApiQueryBase extends ApiBase { return $res; } - protected function addRowInfo($prefix, $row) { - - $vals = array (); - - // ID - if ( isset( $row-> { $prefix . '_id' } ) ) - $vals[$prefix . 'id'] = intval( $row-> { $prefix . '_id' } ); - - // Title - $title = ApiQueryBase :: addRowInfo_title($row, $prefix . '_namespace', $prefix . '_title'); - if ($title) { - if (!$title->userCanRead()) - return false; - $vals['ns'] = $title->getNamespace(); - $vals['title'] = $title->getPrefixedText(); - } - - switch ($prefix) { - - case 'page' : - // page_is_redirect - @ $tmp = $row->page_is_redirect; - if ($tmp) - $vals['redirect'] = ''; - - break; - - case 'rc' : - // PageId - @ $tmp = $row->rc_cur_id; - if (!is_null($tmp)) - $vals['pageid'] = intval($tmp); - - @ $tmp = $row->rc_this_oldid; - if (!is_null($tmp)) - $vals['revid'] = intval($tmp); - - if ( isset( $row->rc_last_oldid ) ) - $vals['old_revid'] = intval( $row->rc_last_oldid ); - - $title = ApiQueryBase :: addRowInfo_title($row, 'rc_moved_to_ns', 'rc_moved_to_title'); - if ($title) { - if (!$title->userCanRead()) - return false; - $vals['new_ns'] = $title->getNamespace(); - $vals['new_title'] = $title->getPrefixedText(); - } - - if ( isset( $row->rc_patrolled ) ) - $vals['patrolled'] = ''; - - break; - - case 'log' : - // PageId - @ $tmp = $row->page_id; - if (!is_null($tmp)) - $vals['pageid'] = intval($tmp); - - if ($row->log_params !== '') { - $params = explode("\n", $row->log_params); - if ($row->log_type == 'move' && isset ($params[0])) { - $newTitle = Title :: newFromText($params[0]); - if ($newTitle) { - $vals['new_ns'] = $newTitle->getNamespace(); - $vals['new_title'] = $newTitle->getPrefixedText(); - $params = null; - } - } - - if (!empty ($params)) { - $this->getResult()->setIndexedTagName($params, 'param'); - $vals = array_merge($vals, $params); - } - } - - break; - - case 'rev' : - // PageID - @ $tmp = $row->rev_page; - if (!is_null($tmp)) - $vals['pageid'] = intval($tmp); - } - - // Type - @ $tmp = $row-> { - $prefix . '_type' }; - if (!is_null($tmp)) - $vals['type'] = $tmp; - - // Action - @ $tmp = $row-> { - $prefix . '_action' }; - if (!is_null($tmp)) - $vals['action'] = $tmp; - - // Old ID - @ $tmp = $row-> { - $prefix . '_text_id' }; - if (!is_null($tmp)) - $vals['oldid'] = intval($tmp); - - // User Name / Anon IP - @ $tmp = $row-> { - $prefix . '_user_text' }; - if (is_null($tmp)) - @ $tmp = $row->user_name; - if (!is_null($tmp)) { - $vals['user'] = $tmp; - @ $tmp = !$row-> { - $prefix . '_user' }; - if (!is_null($tmp) && $tmp) - $vals['anon'] = ''; - } - - // Bot Edit - @ $tmp = $row-> { - $prefix . '_bot' }; - if (!is_null($tmp) && $tmp) - $vals['bot'] = ''; - - // New Edit - @ $tmp = $row-> { - $prefix . '_new' }; - if (is_null($tmp)) - @ $tmp = $row-> { - $prefix . '_is_new' }; - if (!is_null($tmp) && $tmp) - $vals['new'] = ''; - - // Minor Edit - @ $tmp = $row-> { - $prefix . '_minor_edit' }; - if (is_null($tmp)) - @ $tmp = $row-> { - $prefix . '_minor' }; - if (!is_null($tmp) && $tmp) - $vals['minor'] = ''; - - // Timestamp - @ $tmp = $row-> { - $prefix . '_timestamp' }; - if (!is_null($tmp)) - $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $tmp); - - // Comment - @ $tmp = $row-> { - $prefix . '_comment' }; - if (!empty ($tmp)) // optimize bandwidth - $vals['comment'] = $tmp; - - return $vals; - } - - private static function addRowInfo_title($row, $nsfld, $titlefld) { - if ( isset( $row-> $nsfld ) ) { - $ns = $row-> $nsfld; - @ $title = $row-> $titlefld; - if (!empty ($title)) - return Title :: makeTitle($ns, $title); - } - return false; + public static function addTitleInfo(&$arr, $title, $prefix='') { + $arr[$prefix . 'ns'] = intval($title->getNamespace()); + $arr[$prefix . 'title'] = $title->getPrefixedText(); } - + /** * Override this method to request extra fields from the pageSet - * using $this->getPageSet()->requestField('fieldName') + * using $pageSet->requestField('fieldName') */ - public function requestExtraData() { + public function requestExtraData($pageSet) { } /** @@ -303,10 +152,25 @@ abstract class ApiQueryBase extends ApiBase { return $this->mQueryModule; } + /** + * Add sub-element under the page element with the given pageId. + */ + protected function addPageSubItems($pageId, $data) { + $result = $this->getResult(); + $result->setIndexedTagName($data, $this->getModulePrefix()); + $result->addValue(array ('query', 'pages', intval($pageId)), + $this->getModuleName(), + $data); + } + protected function setContinueEnumParameter($paramName, $paramValue) { - $msg = array ( - $this->encodeParamName($paramName - ) => $paramValue); + + $paramName = $this->encodeParamName($paramName); + $msg = array( $paramName => $paramValue ); + +// This is an alternative continue format as a part of the URL string +// ApiResult :: setContent($msg, $paramName . '=' . urlencode($paramValue)); + $this->getResult()->addValue('query-continue', $this->getModuleName(), $msg); } @@ -314,7 +178,19 @@ abstract class ApiQueryBase extends ApiBase { * Get the Query database connection (readonly) */ protected function getDB() { - return $this->getQuery()->getDB(); + if (is_null($this->mDb)) + $this->mDb = $this->getQuery()->getDB(); + return $this->mDb; + } + + /** + * Selects the query database connection with the given name. + * If no such connection has been requested before, it will be created. + * Subsequent calls with the same $name will return the same connection + * as the first, regardless of $db or $groups new values. + */ + public function selectNamedDB($name, $db, $groups) { + $this->mDb = $this->getQuery()->getNamedDB($name, $db, $groups); } /** @@ -322,7 +198,7 @@ abstract class ApiQueryBase extends ApiBase { * @return ApiPageSet data */ protected function getPageSet() { - return $this->mQueryModule->getPageSet(); + return $this->getQuery()->getPageSet(); } /** @@ -338,8 +214,19 @@ abstract class ApiQueryBase extends ApiBase { return str_replace('_', ' ', $key); } + public function getTokenFlag($tokenArr, $action) { + if (in_array($action, $tokenArr)) { + global $wgUser; + if ($wgUser->isAllowed($action)) + return true; + else + $this->dieUsage("Action '$action' is not allowed for the current user", 'permissiondenied'); + } + return false; + } + public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiQueryBase.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryBase.php 24533 2007-08-01 22:46:22Z yurik $'; } } @@ -375,4 +262,4 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase { */ public abstract function executeGenerator($resultPageSet); } -?> + diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php new file mode 100644 index 00000000..42bc1c38 --- /dev/null +++ b/includes/api/ApiQueryCategories.php @@ -0,0 +1,157 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiQueryBase.php"); +} + +/** + * A query module to enumerate categories the set of pages belong to. + * + * @addtogroup API + */ +class ApiQueryCategories extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'cl'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + + if ($this->getPageSet()->getGoodTitleCount() == 0) + return; // nothing to do + + $params = $this->extractRequestParams(); + $prop = $params['prop']; + + $this->addFields(array ( + 'cl_from', + 'cl_to' + )); + + $fld_sortkey = false; + if (!is_null($prop)) { + foreach($prop as $p) { + switch ($p) { + case 'sortkey': + $this->addFields('cl_sortkey'); + $fld_sortkey = true; + break; + default : + ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p"); + } + } + } + + $this->addTables('categorylinks'); + $this->addWhereFld('cl_from', array_keys($this->getPageSet()->getGoodTitles())); + $this->addOption('ORDER BY', "cl_from, cl_to"); + + $db = $this->getDB(); + $res = $this->select(__METHOD__); + + if (is_null($resultPageSet)) { + + $data = array(); + $lastId = 0; // database has no ID 0 + while ($row = $db->fetchObject($res)) { + if ($lastId != $row->cl_from) { + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + $data = array(); + } + $lastId = $row->cl_from; + } + + $title = Title :: makeTitle(NS_CATEGORY, $row->cl_to); + + $vals = array(); + ApiQueryBase :: addTitleInfo($vals, $title); + if ($fld_sortkey) + $vals['sortkey'] = $row->cl_sortkey; + + $data[] = $vals; + } + + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + } + + } else { + + $titles = array(); + while ($row = $db->fetchObject($res)) { + $titles[] = Title :: makeTitle(NS_CATEGORY, $row->cl_to); + } + $resultPageSet->populateFromTitles($titles); + } + + $db->freeResult($res); + } + + protected function getAllowedParams() { + return array ( + 'prop' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'sortkey', + ) + ) + ); + } + + protected function getParamDescription() { + return array ( + 'prop' => 'Which additional properties to get for each category.', + ); + } + + protected function getDescription() { + return 'List all categories the page(s) belong to'; + } + + protected function getExamples() { + return array ( + "Get a list of categories [[Albert Einstein]] belongs to:", + " api.php?action=query&prop=categories&titles=Albert%20Einstein", + "Get information about all categories used in the [[Albert Einstein]]:", + " api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info" + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryCategories.php 24092 2007-07-14 19:04:31Z yurik $'; + } +} + diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php new file mode 100644 index 00000000..58a454a5 --- /dev/null +++ b/includes/api/ApiQueryCategoryMembers.php @@ -0,0 +1,238 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiQueryBase.php"); +} + +/** + * A query module to enumerate pages that belong to a category. + * + * @addtogroup API + */ +class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'cm'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + + $params = $this->extractRequestParams(); + + $category = $params['category']; + if (is_null($category)) + $this->dieUsage("Category parameter is required", 'param_category'); + $categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $category ); + if ( is_null( $categoryTitle ) ) + $this->dieUsage("Category name $category is not valid", 'param_category'); + + $prop = array_flip($params['prop']); + $fld_ids = isset($prop['ids']); + $fld_title = isset($prop['title']); + $fld_sortkey = isset($prop['sortkey']); + $fld_timestamp = isset($prop['timestamp']); + + if (is_null($resultPageSet)) { + $this->addFields(array('cl_from', 'cl_sortkey', 'page_namespace', 'page_title')); + $this->addFieldsIf('page_id', $fld_ids); + } else { + $this->addFields($resultPageSet->getPageTableFields()); // will include page_ id, ns, title + $this->addFields(array('cl_from', 'cl_sortkey')); + } + + $this->addFieldsIf('cl_timestamp', $fld_timestamp); + $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'); + $this->addOption('ORDER BY', 'cl_to, cl_timestamp'); + } + else + { + $this->addOption('USE INDEX', 'cl_sortkey'); + $this->addOption('ORDER BY', 'cl_to, cl_sortkey, cl_from'); + } + + $this->addWhere('cl_from=page_id'); + $this->setContinuation($params['continue']); + $this->addWhereFld('cl_to', $categoryTitle->getDBkey()); + $this->addWhereFld('page_namespace', $params['namespace']); + + $limit = $params['limit']; + $this->addOption('LIMIT', $limit +1); + + $db = $this->getDB(); + + $data = array (); + $count = 0; + $lastSortKey = null; + $res = $this->select(__METHOD__); + 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... + // TODO: Security issue - if the user has no right to view next title, it will still be shown + $this->setContinueEnumParameter('continue', $this->getContinueStr($row, $lastSortKey)); + break; + } + + $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys + + if (is_null($resultPageSet)) { + $vals = array(); + if ($fld_ids) + $vals['pageid'] = intval($row->page_id); + if ($fld_title) { + $title = Title :: makeTitle($row->page_namespace, $row->page_title); + $vals['ns'] = intval($title->getNamespace()); + $vals['title'] = $title->getPrefixedText(); + } + if ($fld_sortkey) + $vals['sortkey'] = $row->cl_sortkey; + if ($fld_timestamp) + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp); + $data[] = $vals; + } else { + $resultPageSet->processDbRow($row); + } + } + $db->freeResult($res); + + if (is_null($resultPageSet)) { + $this->getResult()->setIndexedTagName($data, 'cm'); + $this->getResult()->addValue('query', $this->getModuleName(), $data); + } + } + + private function getContinueStr($row, $lastSortKey) { + $ret = $row->cl_sortkey . '|'; + if ($row->cl_sortkey == $lastSortKey) // duplicate sort key, add cl_from + $ret .= $row->cl_from; + return $ret; + } + + /** + * Add DB WHERE clause to continue previous query based on 'continue' parameter + */ + private function setContinuation($continue) { + 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); + } + + if ($hasError) + $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "badcontinue"); + + $encSortKey = $this->getDB()->addQuotes($continueList[0]); + $encFrom = $this->getDB()->addQuotes($from); + + if ($from != 0) { + // Duplicate sort key continue + $this->addWhere( "cl_sortkey>$encSortKey OR (cl_sortkey=$encSortKey AND cl_from>=$encFrom)" ); + } else { + $this->addWhere( "cl_sortkey>=$encSortKey" ); + } + } + + protected function getAllowedParams() { + return array ( + 'category' => null, + 'prop' => array ( + ApiBase :: PARAM_DFLT => 'ids|title', + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'ids', + 'title', + 'sortkey', + 'timestamp', + ) + ), + 'namespace' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => 'namespace', + ), + 'continue' => null, + 'limit' => array ( + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'sort' => array( + ApiBase :: PARAM_DFLT => 'sortkey', + ApiBase :: PARAM_TYPE => array( + 'sortkey', + 'timestamp' + ) + ) + ); + } + + protected function getParamDescription() { + return array ( + 'category' => 'Which category to enumerate (required)', + 'prop' => 'What pieces of information to include', + 'namespace' => 'Only include pages in these namespaces', + 'sort' => 'Property to sort by', + 'continue' => 'For large categories, give the value retured from previous query', + 'limit' => 'The maximum number of pages to return.', + ); + } + + protected function getDescription() { + return 'List all pages in a given category'; + } + + protected function getExamples() { + return array ( + "Get first 10 pages in the categories [[Physics]]:", + " api.php?action=query&list=categorymembers&cmcategory=Physics", + "Get page info about first 10 pages in the categories [[Physics]]:", + " api.php?action=query&generator=categorymembers&gcmcategory=Physics&prop=info", + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 25474 2007-09-04 14:30:31Z catrope $'; + } +} + diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php new file mode 100644 index 00000000..385ae65b --- /dev/null +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -0,0 +1,200 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * @addtogroup API + */ +class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'eu'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + + $params = $this->extractRequestParams(); + + $protocol = $params['protocol']; + $query = $params['query']; + if (is_null($query)) + $this->dieUsage('Missing required query parameter', 'params'); + + // Find the right prefix + global $wgUrlProtocols; + foreach ($wgUrlProtocols as $p) { + if( substr( $p, 0, strlen( $protocol ) ) === $protocol ) { + $protocol = $p; + break; + } + } + + $likeQuery = LinkFilter::makeLike($query , $protocol); + if (!$likeQuery) + $this->dieUsage('Invalid query', 'bad_query'); + $likeQuery = substr($likeQuery, 0, strpos($likeQuery,'%')+1); + + $this->addTables(array('page','externallinks')); // must be in this order for 'USE INDEX' + $this->addOption('USE INDEX', 'el_index'); + + $db = $this->getDB(); + $this->addWhere('page_id=el_from'); + $this->addWhere('el_index LIKE ' . $db->addQuotes( $likeQuery )); + $this->addWhereFld('page_namespace', $params['namespace']); + + $prop = array_flip($params['prop']); + $fld_ids = isset($prop['ids']); + $fld_title = isset($prop['title']); + $fld_url = isset($prop['url']); + + if (is_null($resultPageSet)) { + $this->addFields(array ( + 'page_id', + 'page_namespace', + 'page_title' + )); + $this->addFieldsIf('el_to', $fld_url); + } else { + $this->addFields($resultPageSet->getPageTableFields()); + } + + $limit = $params['limit']; + $offset = $params['offset']; + $this->addOption('LIMIT', $limit +1); + if (isset ($offset)) + $this->addOption('OFFSET', $offset); + + $res = $this->select(__METHOD__); + + $data = array (); + $count = 0; + 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('offset', $offset+$limit+1); + break; + } + + if (is_null($resultPageSet)) { + $vals = array(); + if ($fld_ids) + $vals['pageid'] = intval($row->page_id); + if ($fld_title) { + $title = Title :: makeTitle($row->page_namespace, $row->page_title); + $vals['ns'] = intval($title->getNamespace()); + $vals['title'] = $title->getPrefixedText(); + } + if ($fld_url) + $vals['url'] = $row->el_to; + $data[] = $vals; + } else { + $resultPageSet->processDbRow($row); + } + } + $db->freeResult($res); + + if (is_null($resultPageSet)) { + $result = $this->getResult(); + $result->setIndexedTagName($data, $this->getModulePrefix()); + $result->addValue('query', $this->getModuleName(), $data); + } + } + + protected function getAllowedParams() { + global $wgUrlProtocols; + $protocols = array(); + foreach ($wgUrlProtocols as $p) { + $protocols[] = substr($p, 0, strpos($p,':')); + } + + return array ( + 'prop' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'ids|title|url', + ApiBase :: PARAM_TYPE => array ( + 'ids', + 'title', + 'url' + ) + ), + 'offset' => array ( + ApiBase :: PARAM_TYPE => 'integer' + ), + 'protocol' => array ( + ApiBase :: PARAM_TYPE => $protocols, + ApiBase :: PARAM_DFLT => 'http', + ), + 'query' => 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 + ) + ); + } + + protected function getParamDescription() { + return array ( + 'prop' => 'What pieces of information to include', + 'offset' => 'Used for paging. Use the value returned for "continue"', + 'protocol' => 'Protocol of the url', + 'query' => 'Search string without protocol. See [[Special:LinkSearch]]', + 'namespace' => 'The page namespace(s) to enumerate.', + 'limit' => 'How many entries to return.' + ); + } + + protected function getDescription() { + return 'Enumerate pages that contain a given URL'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=exturlusage&euquery=www.mediawiki.org' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 24694 2007-08-09 08:41:58Z yurik $'; + } +} diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php new file mode 100644 index 00000000..440b31d6 --- /dev/null +++ b/includes/api/ApiQueryExternalLinks.php @@ -0,0 +1,93 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiQueryBase.php"); +} + +/** + * A query module to list all external URLs found on a given set of pages. + * + * @addtogroup API + */ +class ApiQueryExternalLinks extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'el'); + } + + public function execute() { + + $this->addFields(array ( + 'el_from', + 'el_to' + )); + + $this->addTables('externallinks'); + $this->addWhereFld('el_from', array_keys($this->getPageSet()->getGoodTitles())); + + $db = $this->getDB(); + $res = $this->select(__METHOD__); + + $data = array(); + $lastId = 0; // database has no ID 0 + while ($row = $db->fetchObject($res)) { + if ($lastId != $row->el_from) { + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + $data = array(); + } + $lastId = $row->el_from; + } + + $entry = array(); + ApiResult :: setContent($entry, $row->el_to); + $data[] = $entry; + } + + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + } + + $db->freeResult($res); + } + + protected function getDescription() { + return 'Returns all external urls (not interwikies) from the given page(s)'; + } + + protected function getExamples() { + return array ( + "Get a list of external links on the [[Main Page]]:", + " api.php?action=query&prop=extlinks&titles=Main%20Page", + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 23819 2007-07-07 03:05:09Z yurik $'; + } +} + diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php new file mode 100644 index 00000000..3d568ba1 --- /dev/null +++ b/includes/api/ApiQueryImageInfo.php @@ -0,0 +1,156 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * A query action to get image information and upload history. + * + * @addtogroup API + */ +class ApiQueryImageInfo extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'ii'); + } + + public function execute() { + $params = $this->extractRequestParams(); + + $history = $params['history']; + + $prop = array_flip($params['prop']); + $fld_timestamp = isset($prop['timestamp']); + $fld_user = isset($prop['user']); + $fld_comment = isset($prop['comment']); + $fld_url = isset($prop['url']); + $fld_size = isset($prop['size']); + $fld_sha1 = isset($prop['sha1']); + + $pageIds = $this->getPageSet()->getAllTitlesByNamespace(); + if (!empty($pageIds[NS_IMAGE])) { + foreach ($pageIds[NS_IMAGE] as $dbKey => $pageId) { + + $title = Title :: makeTitle(NS_IMAGE, $dbKey); + $img = wfFindFile($title); + + $data = array(); + if ( !$img ) { + $repository = ''; + } else { + + $repository = $img->getRepoName(); + + $isCur = true; + while($line = $img->nextHistoryLine()) { // assignment + $row = get_object_vars( $line ); + $vals = array(); + $prefix = $isCur ? 'img' : 'oi'; + + if ($fld_timestamp) + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row["${prefix}_timestamp"]); + if ($fld_user) { + $vals['user'] = $row["${prefix}_user_text"]; + if(!$row["${prefix}_user"]) + $vals['anon'] = ''; + } + if ($fld_size) { + $vals['size'] = intval($row["{$prefix}_size"]); + $vals['width'] = intval($row["{$prefix}_width"]); + $vals['height'] = intval($row["{$prefix}_height"]); + } + if ($fld_url) + $vals['url'] = $isCur ? $img->getURL() : $img->getArchiveUrl($row["oi_archive_name"]); + if ($fld_comment) + $vals['comment'] = $row["{$prefix}_description"]; + + if ($fld_sha1) + $vals['sha1'] = wfBaseConvert($row["{$prefix}_sha1"], 36, 16, 40); + + $data[] = $vals; + + if (!$history) // Stop after the first line. + break; + + $isCur = false; + } + + $img->resetHistory(); + } + + $this->getResult()->addValue(array ('query', 'pages', intval($pageId)), + 'imagerepository', + $repository); + if (!empty($data)) + $this->addPageSubItems($pageId, $data); + } + } + } + + protected function getAllowedParams() { + return array ( + 'prop' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'timestamp|user', + ApiBase :: PARAM_TYPE => array ( + 'timestamp', + 'user', + 'comment', + 'url', + 'size', + 'sha1' + ) + ), + 'history' => false, + ); + } + + protected function getParamDescription() { + return array ( + 'prop' => 'What image information to get.', + 'history' => 'Include upload history', + ); + } + + protected function getDescription() { + return array ( + 'Returns image information and upload history' + ); + } + + protected function getExamples() { + return array ( + 'api.php?action=query&titles=Image:Albert%20Einstein%20Head.jpg&prop=imageinfo', + 'api.php?action=query&titles=Image:Test.jpg&prop=imageinfo&iihistory&iiprop=timestamp|user|url', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryImageInfo.php 25456 2007-09-03 19:58:05Z catrope $'; + } +} diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php new file mode 100644 index 00000000..d64a653b --- /dev/null +++ b/includes/api/ApiQueryImages.php @@ -0,0 +1,118 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiQueryBase.php"); +} + +/** + * This query adds subelement to all pages with the list of images embedded into those pages. + * + * @addtogroup API + */ +class ApiQueryImages extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'im'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + + if ($this->getPageSet()->getGoodTitleCount() == 0) + return; // nothing to do + + $this->addFields(array ( + 'il_from', + 'il_to' + )); + + $this->addTables('imagelinks'); + $this->addWhereFld('il_from', array_keys($this->getPageSet()->getGoodTitles())); + $this->addOption('ORDER BY', "il_from, il_to"); + + $db = $this->getDB(); + $res = $this->select(__METHOD__); + + if (is_null($resultPageSet)) { + + $data = array(); + $lastId = 0; // database has no ID 0 + while ($row = $db->fetchObject($res)) { + if ($lastId != $row->il_from) { + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + $data = array(); + } + $lastId = $row->il_from; + } + + $vals = array(); + ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle(NS_IMAGE, $row->il_to)); + $data[] = $vals; + } + + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + } + + } else { + + $titles = array(); + while ($row = $db->fetchObject($res)) { + $titles[] = Title :: makeTitle(NS_IMAGE, $row->il_to); + } + $resultPageSet->populateFromTitles($titles); + } + + $db->freeResult($res); + } + + protected function getDescription() { + return 'Returns all images contained on the given page(s)'; + } + + protected function getExamples() { + return array ( + "Get a list of images used in the [[Main Page]]:", + " api.php?action=query&prop=images&titles=Main%20Page", + "Get information about all images used in the [[Main Page]]:", + " api.php?action=query&generator=images&titles=Main%20Page&prop=info" + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryImages.php 24092 2007-07-14 19:04:31Z yurik $'; + } +} + diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 77489a5f..bebf4006 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,59 +29,167 @@ if (!defined('MEDIAWIKI')) { } /** + * A query module to show basic page information. + * * @addtogroup API */ class ApiQueryInfo extends ApiQueryBase { public function __construct($query, $moduleName) { - parent :: __construct($query, $moduleName); + parent :: __construct($query, $moduleName, 'in'); } - public function requestExtraData() { - $pageSet = $this->getPageSet(); + public function requestExtraData($pageSet) { $pageSet->requestField('page_is_redirect'); + $pageSet->requestField('page_is_new'); + $pageSet->requestField('page_counter'); $pageSet->requestField('page_touched'); $pageSet->requestField('page_latest'); + $pageSet->requestField('page_len'); } public function execute() { + global $wgUser; + + $params = $this->extractRequestParams(); + $fld_protection = false; + if(!is_null($params['prop'])) { + $prop = array_flip($params['prop']); + $fld_protection = isset($prop['protection']); + } + if(!is_null($params['token'])) { + $token = $params['token']; + $tok_edit = $this->getTokenFlag($token, 'edit'); + $tok_delete = $this->getTokenFlag($token, 'delete'); + $tok_protect = $this->getTokenFlag($token, 'protect'); + $tok_move = $this->getTokenFlag($token, 'move'); + } + $pageSet = $this->getPageSet(); $titles = $pageSet->getGoodTitles(); $result = $this->getResult(); $pageIsRedir = $pageSet->getCustomField('page_is_redirect'); + $pageIsNew = $pageSet->getCustomField('page_is_new'); + $pageCounter = $pageSet->getCustomField('page_counter'); $pageTouched = $pageSet->getCustomField('page_touched'); $pageLatest = $pageSet->getCustomField('page_latest'); + $pageLength = $pageSet->getCustomField('page_len'); + + if ($fld_protection && count($titles) > 0) { + $this->addTables('page_restrictions'); + $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry')); + $this->addWhereFld('pr_page', array_keys($titles)); - foreach ( $titles as $pageid => $unused ) { + $db = $this->getDB(); + $res = $this->select(__METHOD__); + while($row = $db->fetchObject($res)) { + $protections[$row->pr_page][] = array( + 'type' => $row->pr_type, + 'level' => $row->pr_level, + 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ) + ); + } + $db->freeResult($res); + } + + foreach ( $titles as $pageid => $title ) { $pageInfo = array ( 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]), - 'lastrevid' => intval($pageLatest[$pageid]) + 'lastrevid' => intval($pageLatest[$pageid]), + 'counter' => intval($pageCounter[$pageid]), + 'length' => intval($pageLength[$pageid]), ); if ($pageIsRedir[$pageid]) $pageInfo['redirect'] = ''; + if ($pageIsNew[$pageid]) + $pageInfo['new'] = ''; + + if (!is_null($token)) { + // Currently all tokens are generated the same way, but it might change + if ($tok_edit) + $pageInfo['edittoken'] = $wgUser->editToken(); + if ($tok_delete) + $pageInfo['deletetoken'] = $wgUser->editToken(); + if ($tok_protect) + $pageInfo['protecttoken'] = $wgUser->editToken(); + if ($tok_move) + $pageInfo['movetoken'] = $wgUser->editToken(); + } + + if($fld_protection) { + if (isset($protections[$pageid])) { + $pageInfo['protection'] = $protections[$pageid]; + $result->setIndexedTagName($pageInfo['protection'], 'pr'); + } else { + $pageInfo['protection'] = array(); + } + } + $result->addValue(array ( 'query', 'pages' ), $pageid, $pageInfo); } + + // Get edit tokens for missing titles if requested + // Delete, protect and move tokens are N/A for missing titles anyway + if($tok_edit) + { + $missing = $pageSet->getMissingTitles(); + $res = $result->getData(); + foreach($missing as $pageid => $title) + $res['query']['pages'][$pageid]['edittoken'] = $wgUser->editToken(); + } } + protected function getAllowedParams() { + return array ( + 'prop' => array ( + ApiBase :: PARAM_DFLT => NULL, + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'protection' + )), + 'token' => array ( + ApiBase :: PARAM_DFLT => NULL, + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'edit', + 'delete', + 'protect', + 'move', + )), + ); + } + + protected function getParamDescription() { + return array ( + 'prop' => array ( + 'Which additional properties to get:', + ' "protection" - List the protection level of each page' + ), + 'token' => 'Request a token to perform a data-modifying action on a page', + ); + } + + protected function getDescription() { return 'Get basic page information such as namespace, title, last touched date, ...'; } protected function getExamples() { return array ( - 'api.php?action=query&prop=info&titles=Main%20Page' + 'api.php?action=query&prop=info&titles=Main%20Page', + 'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page' ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryInfo.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryInfo.php 25457 2007-09-03 20:17:53Z catrope $'; } } -?> + diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php new file mode 100644 index 00000000..ae5ff790 --- /dev/null +++ b/includes/api/ApiQueryLangLinks.php @@ -0,0 +1,94 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiQueryBase.php"); +} + +/** + * A query module to list all langlinks (links to correspanding foreign language pages). + * + * @addtogroup API + */ +class ApiQueryLangLinks extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'll'); + } + + public function execute() { + $this->addFields(array ( + 'll_from', + 'll_lang', + 'll_title' + )); + + $this->addTables('langlinks'); + $this->addWhereFld('ll_from', array_keys($this->getPageSet()->getGoodTitles())); + $this->addOption('ORDER BY', "ll_from, ll_lang"); + $res = $this->select(__METHOD__); + + $data = array(); + $lastId = 0; // database has no ID 0 + $db = $this->getDB(); + while ($row = $db->fetchObject($res)) { + + if ($lastId != $row->ll_from) { + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + $data = array(); + } + $lastId = $row->ll_from; + } + + $entry = array('lang'=>$row->ll_lang); + ApiResult :: setContent($entry, $row->ll_title); + $data[] = $entry; + } + + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + } + + $db->freeResult($res); + } + + protected function getDescription() { + return 'Returns all interlanguage links from the given page(s)'; + } + + protected function getExamples() { + return array ( + "Get interlanguage links from the [[Main Page]]:", + " api.php?action=query&prop=langlinks&titles=Main%20Page&redirects", + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryLangLinks.php 23819 2007-07-07 03:05:09Z yurik $'; + } +} + diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php new file mode 100644 index 00000000..7ec20f44 --- /dev/null +++ b/includes/api/ApiQueryLinks.php @@ -0,0 +1,162 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiQueryBase.php"); +} + +/** + * A query module to list all wiki links on a given set of pages. + * + * @addtogroup API + */ +class ApiQueryLinks extends ApiQueryGeneratorBase { + + const LINKS = 'links'; + const TEMPLATES = 'templates'; + + private $table, $prefix, $description; + + public function __construct($query, $moduleName) { + + switch ($moduleName) { + case self::LINKS : + $this->table = 'pagelinks'; + $this->prefix = 'pl'; + $this->description = 'link'; + break; + case self::TEMPLATES : + $this->table = 'templatelinks'; + $this->prefix = 'tl'; + $this->description = 'template'; + break; + default : + ApiBase :: dieDebug(__METHOD__, 'Unknown module name'); + } + + parent :: __construct($query, $moduleName, $this->prefix); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + + if ($this->getPageSet()->getGoodTitleCount() == 0) + return; // nothing to do + + $params = $this->extractRequestParams(); + + $this->addFields(array ( + $this->prefix . '_from pl_from', + $this->prefix . '_namespace pl_namespace', + $this->prefix . '_title pl_title' + )); + + $this->addTables($this->table); + $this->addWhereFld($this->prefix . '_from', array_keys($this->getPageSet()->getGoodTitles())); + $this->addWhereFld($this->prefix . '_namespace', $params['namespace']); + $this->addOption('ORDER BY', str_replace('pl_', $this->prefix . '_', 'pl_from, pl_namespace, pl_title')); + + $db = $this->getDB(); + $res = $this->select(__METHOD__); + + if (is_null($resultPageSet)) { + + $data = array(); + $lastId = 0; // database has no ID 0 + while ($row = $db->fetchObject($res)) { + if ($lastId != $row->pl_from) { + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + $data = array(); + } + $lastId = $row->pl_from; + } + + $vals = array(); + ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->pl_namespace, $row->pl_title)); + $data[] = $vals; + } + + if($lastId != 0) { + $this->addPageSubItems($lastId, $data); + } + + } else { + + $titles = array(); + while ($row = $db->fetchObject($res)) { + $titles[] = Title :: makeTitle($row->pl_namespace, $row->pl_title); + } + $resultPageSet->populateFromTitles($titles); + } + + $db->freeResult($res); + } + + protected function getAllowedParams() + { + return array( + 'namespace' => array( + ApiBase :: PARAM_TYPE => 'namespace', + ApiBase :: PARAM_ISMULTI => true + ) + ); + } + + protected function getParamDescription() + { + return array( + 'namespace' => "Show {$this->description}s in this namespace(s) only" + ); + } + + protected function getDescription() { + return "Returns all {$this->description}s from the given page(s)"; + } + + protected function getExamples() { + return array ( + "Get {$this->description}s from the [[Main Page]]:", + " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page", + "Get information about the {$this->description} pages in the [[Main Page]]:", + " api.php?action=query&generator={$this->getModuleName()}&titles=Main%20Page&prop=info", + "Get {$this->description}s from the Main Page in the User and Template namespaces:", + " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page&{$this->prefix}namespace=2|10" + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryLinks.php 24092 2007-07-14 19:04:31Z yurik $'; + } +} + diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index d9f23758..0f143658 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,8 @@ if (!defined('MEDIAWIKI')) { } /** + * Query action to List the log events, with optional filtering by various parameters. + * * @addtogroup API */ class ApiQueryLogEvents extends ApiQueryBase { @@ -38,11 +40,18 @@ class ApiQueryLogEvents extends ApiQueryBase { } public function execute() { - $limit = $type = $start = $end = $dir = $user = $title = null; - extract($this->extractRequestParams()); - + $params = $this->extractRequestParams(); $db = $this->getDB(); + $prop = $params['prop']; + $this->fld_ids = in_array('ids', $prop); + $this->fld_title = in_array('title', $prop); + $this->fld_type = in_array('type', $prop); + $this->fld_user = in_array('user', $prop); + $this->fld_timestamp = in_array('timestamp', $prop); + $this->fld_comment = in_array('comment', $prop); + $this->fld_details = in_array('details', $prop); + list($tbl_logging, $tbl_page, $tbl_user) = $db->tableNamesN('logging', 'page', 'user'); $this->addOption('STRAIGHT_JOIN'); @@ -54,19 +63,27 @@ class ApiQueryLogEvents extends ApiQueryBase { 'log_type', 'log_action', 'log_timestamp', - 'log_user', - 'user_name', - 'log_namespace', - 'log_title', - 'page_id', - 'log_comment', - 'log_params' )); + + // FIXME: Fake out log_id for now until the column is live on Wikimedia + // $this->addFieldsIf('log_id', $this->fld_ids); + $this->addFieldsIf('page_id', $this->fld_ids); + $this->addFieldsIf('log_user', $this->fld_user); + $this->addFieldsIf('user_name', $this->fld_user); + $this->addFieldsIf('log_namespace', $this->fld_title); + $this->addFieldsIf('log_title', $this->fld_title); + $this->addFieldsIf('log_comment', $this->fld_comment); + $this->addFieldsIf('log_params', $this->fld_details); + + + $this->addWhereFld('log_deleted', 0); + $this->addWhereFld('log_type', $params['type']); + $this->addWhereRange('log_timestamp', $params['dir'], $params['start'], $params['end']); - $this->addWhereFld('log_type', $type); - $this->addWhereRange('log_timestamp', $dir, $start, $end); + $limit = $params['limit']; $this->addOption('LIMIT', $limit +1); + $user = $params['user']; if (!is_null($user)) { $userid = $db->selectField('user', 'user_id', array ( 'user_name' => $user @@ -76,6 +93,7 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addWhereFld('log_user', $userid); } + $title = $params['title']; if (!is_null($title)) { $titleObj = Title :: newFromText($title); if (is_null($titleObj)) @@ -90,11 +108,11 @@ class ApiQueryLogEvents 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', $row->log_timestamp); + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->log_timestamp)); break; } - $vals = $this->addRowInfo('log', $row); + $vals = $this->extractRowInfo($row); if($vals) $data[] = $vals; } @@ -104,23 +122,102 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->getResult()->addValue('query', $this->getModuleName(), $data); } + private function extractRowInfo($row) { + $vals = array(); + + if ($this->fld_ids) { + // FIXME: Fake out log_id for now until the column is live on Wikimedia + // $vals['logid'] = intval($row->log_id); + $vals['logid'] = 0; + $vals['pageid'] = intval($row->page_id); + } + + if ($this->fld_title) { + $title = Title :: makeTitle($row->log_namespace, $row->log_title); + ApiQueryBase :: addTitleInfo($vals, $title); + } + + if ($this->fld_type) { + $vals['type'] = $row->log_type; + $vals['action'] = $row->log_action; + } + + 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); + } + } + + if ($this->fld_user) { + $vals['user'] = $row->user_name; + if(!$row->log_user) + $vals['anon'] = ''; + } + if ($this->fld_timestamp) { + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->log_timestamp); + } + if ($this->fld_comment && !empty ($row->log_comment)) { + $vals['comment'] = $row->log_comment; + } + + return $vals; + } + + protected function getAllowedParams() { + global $wgLogTypes; return array ( - 'type' => array ( + 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'ids|title|type|user|timestamp|comment|details', ApiBase :: PARAM_TYPE => array ( - 'block', - 'protect', - 'rights', - 'delete', - 'upload', - 'move', - 'import', - 'renameuser', - 'newusers', - 'makebot' + 'ids', + 'title', + 'type', + 'user', + 'timestamp', + 'comment', + 'details', ) ), + 'type' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => $wgLogTypes + ), 'start' => array ( ApiBase :: PARAM_TYPE => 'timestamp' ), @@ -140,7 +237,7 @@ class ApiQueryLogEvents extends ApiQueryBase { ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ) ); @@ -169,7 +266,7 @@ class ApiQueryLogEvents extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLogEvents.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryLogEvents.php 24256 2007-07-18 21:47:09Z robchurch $'; } } -?> + diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 25f7ff3e..309beaf9 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,9 @@ if (!defined('MEDIAWIKI')) { } /** + * A query action to enumerate the recent changes that were done to the wiki. + * Various filters are supported. + * * @addtogroup API */ class ApiQueryRecentChanges extends ApiQueryBase { @@ -37,6 +40,10 @@ class ApiQueryRecentChanges extends ApiQueryBase { parent :: __construct($query, $moduleName, 'rc'); } + private $fld_comment = false, $fld_user = false, $fld_flags = false, + $fld_timestamp = false, $fld_title = false, $fld_ids = false, + $fld_sizes = false; + public function execute() { $limit = $prop = $namespace = $show = $dir = $start = $end = null; extract($this->extractRequestParams()); @@ -44,6 +51,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addTables('recentchanges'); $this->addWhereRange('rc_timestamp', $dir, $start, $end); $this->addWhereFld('rc_namespace', $namespace); + $this->addWhereFld('rc_deleted', 0); if (!is_null($show)) { $show = array_flip($show); @@ -62,9 +70,6 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'rc_timestamp', 'rc_namespace', 'rc_title', - 'rc_cur_id', - 'rc_this_oldid', - 'rc_last_oldid', 'rc_type', 'rc_moved_to_ns', 'rc_moved_to_title' @@ -72,16 +77,27 @@ class ApiQueryRecentChanges extends ApiQueryBase { if (!is_null($prop)) { $prop = array_flip($prop); - $this->addFieldsIf('rc_comment', isset ($prop['comment'])); - if (isset ($prop['user'])) { - $this->addFields('rc_user'); - $this->addFields('rc_user_text'); - } - if (isset ($prop['flags'])) { - $this->addFields('rc_minor'); - $this->addFields('rc_bot'); - $this->addFields('rc_new'); - } + + $this->fld_comment = isset ($prop['comment']); + $this->fld_user = isset ($prop['user']); + $this->fld_flags = isset ($prop['flags']); + $this->fld_timestamp = isset ($prop['timestamp']); + $this->fld_title = isset ($prop['title']); + $this->fld_ids = isset ($prop['ids']); + $this->fld_sizes = isset ($prop['sizes']); + + $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); + $this->addFieldsIf('rc_user', $this->fld_user); + $this->addFieldsIf('rc_user_text', $this->fld_user); + $this->addFieldsIf('rc_minor', $this->fld_flags); + $this->addFieldsIf('rc_bot', $this->fld_flags); + $this->addFieldsIf('rc_new', $this->fld_flags); + $this->addFieldsIf('rc_old_len', $this->fld_sizes); + $this->addFieldsIf('rc_new_len', $this->fld_sizes); } $this->addOption('LIMIT', $limit +1); @@ -91,15 +107,16 @@ class ApiQueryRecentChanges extends ApiQueryBase { $count = 0; $db = $this->getDB(); $res = $this->select(__METHOD__); + 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', $row->rc_timestamp); + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); break; } - $vals = $this->addRowInfo('rc', $row); - if ($vals) + $vals = $this->extractRowInfo($row); + if($vals) $data[] = $vals; } $db->freeResult($res); @@ -109,6 +126,59 @@ class ApiQueryRecentChanges extends ApiQueryBase { $result->addValue('query', $this->getModuleName(), $data); } + private function extractRowInfo($row) { + $movedToTitle = false; + if (!empty($row->rc_moved_to_title)) + $movedToTitle = Title :: makeTitle($row->rc_moved_to_ns, $row->rc_moved_to_title); + + $title = Title :: makeTitle($row->rc_namespace, $row->rc_title); + $vals = array (); + + $vals['type'] = intval($row->rc_type); + + if ($this->fld_title) { + ApiQueryBase :: addTitleInfo($vals, $title); + if ($movedToTitle) + ApiQueryBase :: addTitleInfo($vals, $movedToTitle, "new_"); + } + + if ($this->fld_ids) { + $vals['rcid'] = intval($row->rc_id); + $vals['pageid'] = intval($row->rc_cur_id); + $vals['revid'] = intval($row->rc_this_oldid); + $vals['old_revid'] = intval( $row->rc_last_oldid ); + } + + if ($this->fld_user) { + $vals['user'] = $row->rc_user_text; + if(!$row->rc_user) + $vals['anon'] = ''; + } + + if ($this->fld_flags) { + if ($row->rc_bot) + $vals['bot'] = ''; + if ($row->rc_new) + $vals['new'] = ''; + if ($row->rc_minor) + $vals['minor'] = ''; + } + + if ($this->fld_sizes) { + $vals['oldlen'] = intval($row->rc_old_len); + $vals['newlen'] = intval($row->rc_new_len); + } + + if ($this->fld_timestamp) + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); + + if ($this->fld_comment && !empty ($row->rc_comment)) { + $vals['comment'] = $row->rc_comment; + } + + return $vals; + } + protected function getAllowedParams() { return array ( 'start' => array ( @@ -130,10 +200,15 @@ class ApiQueryRecentChanges extends ApiQueryBase { ), 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'title|timestamp|ids', ApiBase :: PARAM_TYPE => array ( 'user', 'comment', - 'flags' + 'flags', + 'timestamp', + 'title', + 'ids', + 'sizes' ) ), 'show' => array ( @@ -151,7 +226,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ) ); @@ -183,7 +258,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 24100 2007-07-15 01:12:54Z yurik $'; } } -?> + diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index fc5f6241..2672478b 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,10 @@ if (!defined('MEDIAWIKI')) { } /** + * A query action to enumerate revisions of a given page, or show top revisions of multiple pages. + * Various pieces of information may be shown - flags, comments, and the actual wiki markup of the rev. + * In the enumeration mode, ranges of revisions may be requested and filtered. + * * @addtogroup API */ class ApiQueryRevisions extends ApiQueryBase { @@ -37,15 +41,18 @@ class ApiQueryRevisions extends ApiQueryBase { parent :: __construct($query, $moduleName, 'rv'); } + private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false, + $fld_comment = false, $fld_user = false, $fld_content = false; + public function execute() { - $limit = $startid = $endid = $start = $end = $dir = $prop = null; + $limit = $startid = $endid = $start = $end = $dir = $prop = $user = $excludeuser = null; extract($this->extractRequestParams()); // If any of those parameters are used, work in 'enumeration' mode. // Enum mode can only be used when exactly one page is provided. // Enumerating revisions on multiple pages make it extremelly // difficult to manage continuations and require additional sql indexes - $enumRevMode = (!is_null($limit) || !is_null($startid) || !is_null($endid) || $dir === 'newer' || !is_null($start) || !is_null($end)); + $enumRevMode = (!is_null($user) || !is_null($excludeuser) || !is_null($limit) || !is_null($startid) || !is_null($endid) || $dir === 'newer' || !is_null($start) || !is_null($end)); $pageSet = $this->getPageSet(); $pageCount = $pageSet->getGoodTitleCount(); @@ -59,39 +66,50 @@ class ApiQueryRevisions extends ApiQueryBase { $this->dieUsage('The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids'); if ($pageCount > 1 && $enumRevMode) - $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, start, and end parameters may only be used on a single page.', 'multpages'); + $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start, and end parameters may only be used on a single page.', 'multpages'); $this->addTables('revision'); - $this->addFields(array ( - 'rev_id', - 'rev_page', - 'rev_text_id', - 'rev_minor_edit' - )); $this->addWhere('rev_deleted=0'); - $showContent = false; + $prop = array_flip($prop); - if (!is_null($prop)) { - $prop = array_flip($prop); - $this->addFieldsIf('rev_timestamp', isset ($prop['timestamp'])); - $this->addFieldsIf('rev_comment', isset ($prop['comment'])); - if (isset ($prop['user'])) { - $this->addFields('rev_user'); - $this->addFields('rev_user_text'); - } - if (isset ($prop['content'])) { - $this->addTables('text'); - $this->addWhere('rev_text_id=old_id'); - $this->addFields('old_id'); - $this->addFields('old_text'); - $this->addFields('old_flags'); - $showContent = true; + // These field are needed regardless of the client requesting them + $this->addFields('rev_id'); + $this->addFields('rev_page'); + + // Optional fields + $this->fld_ids = isset ($prop['ids']); + // $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed? + $this->fld_flags = $this->addFieldsIf('rev_minor_edit', isset ($prop['flags'])); + $this->fld_timestamp = $this->addFieldsIf('rev_timestamp', isset ($prop['timestamp'])); + $this->fld_comment = $this->addFieldsIf('rev_comment', isset ($prop['comment'])); + $this->fld_size = $this->addFieldsIf('rev_len', isset ($prop['size'])); + + if (isset ($prop['user'])) { + $this->addFields('rev_user'); + $this->addFields('rev_user_text'); + $this->fld_user = true; + } + if (isset ($prop['content'])) { + + // For each page we will request, the user must have read rights for that page + foreach ($pageSet->getGoodTitles() as $title) { + if( !$title->userCanRead() ) + $this->dieUsage( + 'The current user is not allowed to read ' . $title->getPrefixedText(), + 'accessdenied'); } + + $this->addTables('text'); + $this->addWhere('rev_text_id=old_id'); + $this->addFields('old_id'); + $this->addFields('old_text'); + $this->addFields('old_flags'); + $this->fld_content = true; } - $userMax = ($showContent ? 50 : 500); - $botMax = ($showContent ? 200 : 10000); + $userMax = ($this->fld_content ? 50 : 500); + $botMax = ($this->fld_content ? 200 : 10000); if ($enumRevMode) { @@ -102,6 +120,9 @@ class ApiQueryRevisions extends ApiQueryBase { if (!is_null($endid) && !is_null($end)) $this->dieUsage('end and endid cannot be used together', 'badparams'); + if(!is_null($user) && !is_null( $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 // the same result. This way users may request revisions starting at a given time, // but to page through results use the rev_id returned after each page. @@ -117,10 +138,25 @@ class ApiQueryRevisions extends ApiQueryBase { // must manually initialize unset limit if (is_null($limit)) $limit = 10; - $this->validateLimit($this->encodeParamName('limit'), $limit, 1, $userMax, $botMax); + $this->validateLimit('limit', $limit, 1, $userMax, $botMax); // 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)); + } + } + elseif ($revCount > 0) { + $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax); + + // Get all revision IDs + $this->addWhereFld('rev_id', array_keys($pageSet->getRevisionIDs())); + + // assumption testing -- we should never get more then $revCount rows. + $limit = $revCount; } elseif ($pageCount > 0) { // When working in multi-page non-enumeration mode, @@ -133,15 +169,8 @@ class ApiQueryRevisions extends ApiQueryBase { // Get all page IDs $this->addWhereFld('page_id', array_keys($pageSet->getGoodTitles())); - $limit = $pageCount; // assumption testing -- we should never get more then $pageCount rows. - } - elseif ($revCount > 0) { - $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax); - - // Get all revision IDs - $this->addWhereFld('rev_id', array_keys($pageSet->getRevisionIDs())); - - $limit = $revCount; // assumption testing -- we should never get more then $revCount rows. + // assumption testing -- we should never get more then $pageCount rows. + $limit = $pageCount; } else ApiBase :: dieDebug(__METHOD__, 'param validation?'); @@ -158,21 +187,18 @@ class ApiQueryRevisions extends ApiQueryBase { // We've reached the one extra which shows that there are additional pages to be had. Stop here... if (!$enumRevMode) ApiBase :: dieDebug(__METHOD__, 'Got more rows then expected'); // bug report - $this->setContinueEnumParameter('startid', $row->rev_id); + $this->setContinueEnumParameter('startid', intval($row->rev_id)); break; } - $vals = $this->addRowInfo('rev', $row); - if ($vals) { - if ($showContent) - ApiResult :: setContent($vals, Revision :: getRevisionText($row)); - - $this->getResult()->addValue(array ( + $this->getResult()->addValue( + array ( 'query', 'pages', - intval($row->rev_page - ), 'revisions'), intval($row->rev_id), $vals); - } + intval($row->rev_page), + 'revisions'), + null, + $this->extractRowInfo($row)); } $db->freeResult($res); @@ -188,21 +214,62 @@ class ApiQueryRevisions extends ApiQueryBase { } } + private function extractRowInfo($row) { + + $vals = array (); + + if ($this->fld_ids) { + $vals['revid'] = intval($row->rev_id); + // $vals['oldid'] = intval($row->rev_text_id); // todo: should this be exposed? + } + + if ($this->fld_flags && $row->rev_minor_edit) + $vals['minor'] = ''; + + if ($this->fld_user) { + $vals['user'] = $row->rev_user_text; + if (!$row->rev_user) + $vals['anon'] = ''; + } + + if ($this->fld_timestamp) { + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp); + } + + if ($this->fld_size && !is_null($row->rev_len)) { + $vals['size'] = intval($row->rev_len); + } + + if ($this->fld_comment && !empty ($row->rev_comment)) { + $vals['comment'] = $row->rev_comment; + } + + if ($this->fld_content) { + ApiResult :: setContent($vals, Revision :: getRevisionText($row)); + } + + return $vals; + } + protected function getAllowedParams() { return array ( 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'ids|timestamp|flags|comment|user', ApiBase :: PARAM_TYPE => array ( + 'ids', + 'flags', 'timestamp', 'user', + 'size', 'comment', - 'content' + 'content', ) ), 'limit' => array ( ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_SML1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_SML1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_SML2 ), 'startid' => array ( @@ -223,6 +290,12 @@ class ApiQueryRevisions extends ApiQueryBase { 'newer', 'older' ) + ), + 'user' => array( + ApiBase :: PARAM_TYPE => 'user' + ), + 'excludeuser' => array( + ApiBase :: PARAM_TYPE => 'user' ) ); } @@ -235,7 +308,9 @@ class ApiQueryRevisions extends ApiQueryBase { 'endid' => 'stop revision enumeration on this revid (enum)', 'start' => 'from which revision timestamp to start enumeration (enum)', 'end' => 'enumerate up to this timestamp (enum)', - 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)' + 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)', + 'user' => 'only include revisions made by user', + 'excludeuser' => 'exclude revisions made by user', ); } @@ -259,12 +334,16 @@ class ApiQueryRevisions extends ApiQueryBase { 'Get first 5 revisions of the "Main Page":', ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer', 'Get first 5 revisions of the "Main Page" made after 2006-05-01:', - ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000' + ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000', + 'Get first 5 revisions of the "Main Page" that were not made made by anonymous user "127.0.0.1"', + ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1', + 'Get first 5 revisions of the "Main Page" that were made by the user "MediaWiki default"', + ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvuser=MediaWiki%20default', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRevisions.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryRevisions.php 25407 2007-09-02 14:00:11Z tstarling $'; } } -?> + diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php new file mode 100644 index 00000000..268616b1 --- /dev/null +++ b/includes/api/ApiQuerySearch.php @@ -0,0 +1,151 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * Query module to perform full text search within wiki titles and content + * + * @addtogroup API + */ +class ApiQuerySearch extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'sr'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + + $params = $this->extractRequestParams(); + + $limit = $params['limit']; + $query = $params['search']; + if (is_null($query) || empty($query)) + $this->dieUsage("empty search string is not allowed", 'param-search'); + + $search = SearchEngine::create(); + $search->setLimitOffset( $limit+1, $params['offset'] ); + $search->setNamespaces( $params['namespace'] ); + $search->showRedirects = $params['redirects']; + + if ($params['what'] == 'text') + $matches = $search->searchText( $query ); + else + $matches = $search->searchTitle( $query ); + + $data = array (); + $count = 0; + while( $result = $matches->next() ) { + if (++ $count > $limit) { + // We've reached the one extra which shows that there are additional items to be had. Stop here... + $this->setContinueEnumParameter('offset', $params['offset'] + $params['limit']); + break; + } + + $title = $result->getTitle(); + if (is_null($resultPageSet)) { + $data[] = array( + 'ns' => intval($title->getNamespace()), + 'title' => $title->getPrefixedText()); + } else { + $data[] = $title; + } + } + + if (is_null($resultPageSet)) { + $result = $this->getResult(); + $result->setIndexedTagName($data, 'p'); + $result->addValue('query', $this->getModuleName(), $data); + } else { + $resultPageSet->populateFromTitles($data); + } + } + + protected function getAllowedParams() { + return array ( + 'search' => null, + 'namespace' => array ( + ApiBase :: PARAM_DFLT => 0, + ApiBase :: PARAM_TYPE => 'namespace', + ApiBase :: PARAM_ISMULTI => true, + ), + 'what' => array ( + ApiBase :: PARAM_DFLT => 'title', + ApiBase :: PARAM_TYPE => array ( + 'title', + 'text', + ) + ), + 'redirects' => false, + 'offset' => 0, + '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 + ) + ); + } + + protected function getParamDescription() { + return array ( + 'search' => 'Search for all page titles (or content) that has this value.', + 'namespace' => 'The namespace(s) to enumerate.', + 'what' => 'Search inside the text or titles.', + 'redirects' => 'Include redirect pages in the search.', + 'offset' => 'Use this value to continue paging (return by query)', + 'limit' => 'How many total pages to return.' + ); + } + + protected function getDescription() { + return 'Perform a full text search'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=search&srsearch=meaning', + 'api.php?action=query&list=search&srwhat=text&srsearch=meaning', + 'api.php?action=query&generator=search&gsrsearch=meaning&prop=info', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQuerySearch.php 24453 2007-07-30 08:09:15Z yurik $'; + } +} + diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index fa185c97..1fa3d8fc 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,8 @@ if (!defined('MEDIAWIKI')) { } /** + * A query action to return meta information about the wiki site. + * * @addtogroup API */ class ApiQuerySiteinfo extends ApiQueryBase { @@ -38,58 +40,164 @@ class ApiQuerySiteinfo extends ApiQueryBase { } public function execute() { - $prop = null; - extract($this->extractRequestParams()); - foreach ($prop as $p) { - switch ($p) { + $params = $this->extractRequestParams(); + foreach ($params['prop'] as $p) { + switch ($p) { + default : + ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p"); case 'general' : - - global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText; - $data = array (); - $mainPage = Title :: newFromText(wfMsgForContent('mainpage')); - $data['mainpage'] = $mainPage->getText(); - $data['base'] = $mainPage->getFullUrl(); - $data['sitename'] = $wgSitename; - $data['generator'] = "MediaWiki $wgVersion"; - $data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future - if (isset($wgRightsCode)) - $data['rightscode'] = $wgRightsCode; - $data['rights'] = $wgRightsText; - $this->getResult()->addValue('query', $p, $data); + $this->appendGeneralInfo($p); break; - case 'namespaces' : - - global $wgContLang; - $data = array (); - foreach ($wgContLang->getFormattedNamespaces() as $ns => $title) { - $data[$ns] = array ( - 'id' => $ns - ); - ApiResult :: setContent($data[$ns], $title); - } - $this->getResult()->setIndexedTagName($data, 'ns'); - $this->getResult()->addValue('query', $p, $data); + $this->appendNamespaces($p); + break; + case 'interwikimap' : + $filteriw = isset($params['filteriw']) ? $params['filteriw'] : false; + $this->appendInterwikiMap($p, $filteriw); + break; + case 'dbrepllag' : + $this->appendDbReplLagInfo($p, $params['showalldb']); + break; + case 'statistics' : + $this->appendStatistics($p); break; - - default : - ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p"); } } } + protected function appendGeneralInfo($property) { + global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText, $wgLanguageCode; + + $data = array (); + $mainPage = Title :: newFromText(wfMsgForContent('mainpage')); + $data['mainpage'] = $mainPage->getText(); + $data['base'] = $mainPage->getFullUrl(); + $data['sitename'] = $wgSitename; + $data['generator'] = "MediaWiki $wgVersion"; + $data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future + if (isset($wgRightsCode)) + $data['rightscode'] = $wgRightsCode; + $data['rights'] = $wgRightsText; + $data['lang'] = $wgLanguageCode; + + $this->getResult()->addValue('query', $property, $data); + } + + protected function appendNamespaces($property) { + global $wgContLang; + + $data = array (); + foreach ($wgContLang->getFormattedNamespaces() as $ns => $title) { + $data[$ns] = array ( + 'id' => $ns + ); + ApiResult :: setContent($data[$ns], $title); + } + + $this->getResult()->setIndexedTagName($data, 'ns'); + $this->getResult()->addValue('query', $property, $data); + } + + protected function appendInterwikiMap($property, $filter) { + + $this->resetQueryParams(); + $this->addTables('interwiki'); + $this->addFields(array('iw_prefix', 'iw_local', 'iw_url')); + + if($filter === 'local') { + $this->addWhere('iw_local = 1'); + } elseif($filter === '!local') { + $this->addWhere('iw_local = 0'); + } elseif($filter !== false) { + ApiBase :: dieDebug(__METHOD__, "Unknown filter=$filter"); + } + + $this->addOption('ORDER BY', 'iw_prefix'); + + $db = $this->getDB(); + $res = $this->select(__METHOD__); + + $data = array(); + while($row = $db->fetchObject($res)) + { + $val['prefix'] = $row->iw_prefix; + if ($row->iw_local == '1') + $val['local'] = ''; +// $val['trans'] = intval($row->iw_trans); // should this be exposed? + $val['url'] = $row->iw_url; + + $data[] = $val; + } + $db->freeResult($res); + + $this->getResult()->setIndexedTagName($data, 'iw'); + $this->getResult()->addValue('query', $property, $data); + } + + protected function appendDbReplLagInfo($property, $includeAll) { + global $wgLoadBalancer, $wgShowHostnames; + + $data = array(); + + if ($includeAll) { + if (!$wgShowHostnames) + $this->dieUsage('Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied'); + + global $wgDBservers; + $lags = $wgLoadBalancer->getLagTimes(); + foreach( $lags as $i => $lag ) { + $data[] = array ( + 'host' => $wgDBservers[$i]['host'], + 'lag' => $lag); + } + } else { + list( $host, $lag ) = $wgLoadBalancer->getMaxLag(); + $data[] = array ( + 'host' => $wgShowHostnames ? $host : '', + 'lag' => $lag); + } + + $result = $this->getResult(); + $result->setIndexedTagName($data, 'db'); + $result->addValue('query', $property, $data); + } + + protected function appendStatistics($property) { + $data = array (); + $data['pages'] = intval(SiteStats::pages()); + $data['articles'] = intval(SiteStats::articles()); + $data['views'] = intval(SiteStats::views()); + $data['edits'] = intval(SiteStats::edits()); + $data['images'] = intval(SiteStats::images()); + $data['users'] = intval(SiteStats::users()); + $data['admins'] = intval(SiteStats::admins()); + $data['jobs'] = intval(SiteStats::jobs()); + $this->getResult()->addValue('query', $property, $data); + } + protected function getAllowedParams() { return array ( + 'prop' => array ( ApiBase :: PARAM_DFLT => 'general', ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array ( 'general', - 'namespaces' - ) - ) + 'namespaces', + 'interwikimap', + 'dbrepllag', + 'statistics', + )), + + 'filteriw' => array ( + ApiBase :: PARAM_TYPE => array ( + 'local', + '!local', + )), + + 'showalldb' => false, ); } @@ -97,9 +205,14 @@ class ApiQuerySiteinfo extends ApiQueryBase { return array ( 'prop' => array ( 'Which sysinfo properties to get:', - ' "general" - Overall system information', - ' "namespaces" - List of registered namespaces (localized)' - ) + ' "general" - Overall system information', + ' "namespaces" - List of registered namespaces (localized)', + ' "statistics" - Returns site statistics', + ' "interwikimap" - Returns interwiki map (optionally filtered)', + ' "dbrepllag" - Returns database server with the highest replication lag', + ), + 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map', + 'showalldb' => 'List all database servers, not just the one lagging the most', ); } @@ -108,11 +221,14 @@ class ApiQuerySiteinfo extends ApiQueryBase { } protected function getExamples() { - return 'api.php?action=query&meta=siteinfo&siprop=general|namespaces'; + return array( + 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|statistics', + 'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local', + 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb', + ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 25238 2007-08-28 15:37:31Z robchurch $'; } -} -?> +} \ No newline at end of file diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index 05bfbb20..05c3d945 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,8 @@ if (!defined('MEDIAWIKI')) { } /** + * This query action adds a list of a specified user's contributions to the output. + * * @addtogroup API */ class ApiQueryContributions extends ApiQueryBase { @@ -37,83 +39,49 @@ class ApiQueryContributions extends ApiQueryBase { parent :: __construct($query, $moduleName, 'uc'); } + private $params, $username; + private $fld_ids = false, $fld_title = false, $fld_timestamp = false, + $fld_comment = false, $fld_flags = false; + public function execute() { - //Blank all our variables - $limit = $user = $start = $end = $dir = null; + // Parse some parameters + $this->params = $this->extractRequestParams(); - //Get our parameters out - extract($this->extractRequestParams()); + $prop = array_flip($this->params['prop']); + $this->fld_ids = isset($prop['ids']); + $this->fld_title = isset($prop['title']); + $this->fld_comment = isset($prop['comment']); + $this->fld_flags = isset($prop['flags']); + $this->fld_timestamp = isset($prop['timestamp']); - //Get a database instance + // TODO: if the query is going only against the revision table, should this be done? + $this->selectNamedDB('contributions', DB_SLAVE, 'contributions'); $db = $this->getDB(); - if (is_null($user)) - $this->dieUsage("User parameter may not be empty", 'param_user'); - $userid = $db->selectField('user', 'user_id', array ( - 'user_name' => $user - )); - if (!$userid) - $this->dieUsage("User name $user not found", 'param_user'); - - //Get the table names - list ($tbl_page, $tbl_revision) = $db->tableNamesN('page', 'revision'); - - //We're after the revision table, and the corresponding page row for - //anything we retrieve. - $this->addTables("$tbl_revision LEFT OUTER JOIN $tbl_page ON " . - "page_id=rev_page"); - - //We want to know the namespace, title, new-ness, and ID of a page, - // and the id, text-id, timestamp, minor-status, summary and page - // of a revision. - $this->addFields(array('page_namespace', 'page_title', 'page_is_new', - 'rev_id', 'rev_text_id', 'rev_timestamp', 'rev_minor_edit', - 'rev_comment', 'rev_page')); - - // We only want pages by the specified user. - $this->addWhereFld('rev_user_text', $user); - // ... and in the specified timeframe. - $this->addWhereRange('rev_timestamp', $dir, $start, $end ); + // Prepare query + $this->prepareUsername(); + $this->prepareQuery(); - $this->addOption('LIMIT', $limit + 1); + //Do the actual query. + $res = $this->select( __METHOD__ ); //Initialise some variables $data = array (); $count = 0; - - //Do the actual query. - $res = $this->select( __METHOD__ ); + $limit = $this->params['limit']; //Fetch each row 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', $row->rev_timestamp); + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp)); break; } - //There's a fancy function in ApiQueryBase that does - // most of the work for us. Use that for the page - // and revision. - $revvals = $this->addRowInfo('rev', $row); - $pagevals = $this->addRowInfo('page', $row); - - //If we got data on the revision only, use only - // that data. - if($revvals && !$pagevals) { - $data[] = $revvals; - } - //If we got data on the page only, use only - // that data. - else if($pagevals && !$revvals) { - $data[] = $pagevals; - } - //... and if we got data on both the revision and - // the page, merge the data and send it out. - else if($pagevals && $revvals) { - $data[] = array_merge($revvals, $pagevals); - } + $vals = $this->extractRowInfo($row); + if ($vals) + $data[] = $vals; } //Free the database record so the connection can get on with other stuff @@ -124,13 +92,117 @@ class ApiQueryContributions extends ApiQueryBase { $this->getResult()->addValue('query', $this->getModuleName(), $data); } + /** + * Validate the 'user' parameter and set the value to compare + * against `revision`.`rev_user_text` + */ + private function prepareUsername() { + $user = $this->params['user']; + if( $user ) { + $name = User::isIP( $user ) + ? $user + : User::getCanonicalName( $user, 'valid' ); + if( $name === false ) { + $this->dieUsage( "User name {$user} is not valid", 'param_user' ); + } else { + $this->username = $name; + } + } else { + $this->dieUsage( 'User parameter may not be empty', 'param_user' ); + } + } + + /** + * Prepares the query and returns the limit of rows requested + */ + private function prepareQuery() { + + //We're after the revision table, and the corresponding page row for + //anything we retrieve. + list ($tbl_page, $tbl_revision) = $this->getDB()->tableNamesN('page', 'revision'); + $this->addTables("$tbl_revision LEFT OUTER JOIN $tbl_page ON page_id=rev_page"); + + $this->addWhereFld('rev_deleted', 0); + + // We only want pages by the specified user. + $this->addWhereFld( 'rev_user_text', $this->username ); + + // ... and in the specified timeframe. + $this->addWhereRange('rev_timestamp', + $this->params['dir'], $this->params['start'], $this->params['end'] ); + + $this->addWhereFld('page_namespace', $this->params['namespace']); + + $show = $this->params['show']; + if (!is_null($show)) { + $show = array_flip($show); + if (isset ($show['minor']) && isset ($show['!minor'])) + $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); + + $this->addWhereIf('rev_minor_edit = 0', isset ($show['!minor'])); + $this->addWhereIf('rev_minor_edit != 0', isset ($show['minor'])); + } + + $this->addOption('LIMIT', $this->params['limit'] + 1); + + // Mandatory fields: timestamp allows request continuation + // ns+title checks if the user has access rights for this page + $this->addFields(array( + 'rev_timestamp', + 'page_namespace', + 'page_title', + )); + + $this->addFieldsIf('rev_page', $this->fld_ids); + $this->addFieldsIf('rev_id', $this->fld_ids); + // $this->addFieldsIf('rev_text_id', $this->fld_ids); // Should this field be exposed? + $this->addFieldsIf('rev_comment', $this->fld_comment); + $this->addFieldsIf('rev_minor_edit', $this->fld_flags); + + // These fields depend only work if the page table is joined + $this->addFieldsIf('page_is_new', $this->fld_flags); + } + + /** + * Extract fields from the database row and append them to a result array + */ + private function extractRowInfo($row) { + + $vals = array(); + + if ($this->fld_ids) { + $vals['pageid'] = intval($row->rev_page); + $vals['revid'] = intval($row->rev_id); + // $vals['textid'] = intval($row->rev_text_id); // todo: Should this field be exposed? + } + + if ($this->fld_title) + ApiQueryBase :: addTitleInfo($vals, + Title :: makeTitle($row->page_namespace, $row->page_title)); + + if ($this->fld_timestamp) + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp); + + if ($this->fld_flags) { + if ($row->page_is_new) + $vals['new'] = ''; + if ($row->rev_minor_edit) + $vals['minor'] = ''; + } + + if ($this->fld_comment && !empty ($row->rev_comment)) + $vals['comment'] = $row->rev_comment; + + return $vals; + } + protected function getAllowedParams() { return array ( 'limit' => array ( ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ), 'start' => array ( @@ -139,14 +211,38 @@ class ApiQueryContributions extends ApiQueryBase { 'end' => array ( ApiBase :: PARAM_TYPE => 'timestamp' ), - 'user' => null, + 'user' => array ( + ApiBase :: PARAM_TYPE => 'user' + ), 'dir' => array ( ApiBase :: PARAM_DFLT => 'older', ApiBase :: PARAM_TYPE => array ( 'newer', 'older' ) - ) + ), + 'namespace' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => 'namespace' + ), + 'prop' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'ids|title|timestamp|flags|comment', + ApiBase :: PARAM_TYPE => array ( + 'ids', + 'title', + 'timestamp', + 'comment', + 'flags' + ) + ), + 'show' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'minor', + '!minor', + ) + ), ); } @@ -156,12 +252,15 @@ class ApiQueryContributions extends ApiQueryBase { 'start' => 'The start timestamp to return from.', 'end' => 'The end timestamp to return to.', 'user' => 'The user to retrieve contributions for.', - 'dir' => 'The direction to search (older or newer).' + 'dir' => 'The direction to search (older or newer).', + 'namespace' => 'Only list contributions in these namespaces', + 'prop' => 'Include additional pieces of information', + 'show' => 'Show only items that meet this criteria, e.g. non minor edits only: show=!minor', ); } protected function getDescription() { - return 'Get edits by a user..'; + return 'Get all edits by a user'; } protected function getExamples() { @@ -171,7 +270,7 @@ class ApiQueryContributions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserContributions.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryUserContributions.php 24754 2007-08-13 18:18:18Z robchurch $'; } } -?> + diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php new file mode 100644 index 00000000..a41b8679 --- /dev/null +++ b/includes/api/ApiQueryUserInfo.php @@ -0,0 +1,133 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * Query module to get information about the currently logged-in user + * + * @addtogroup API + */ +class ApiQueryUserInfo extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'ui'); + } + + public function execute() { + + global $wgUser; + + $params = $this->extractRequestParams(); + $result = $this->getResult(); + + $vals = array(); + $vals['name'] = $wgUser->getName(); + + if( $wgUser->isAnon() ) $vals['anon'] = ''; + + if (!is_null($params['prop'])) { + $prop = array_flip($params['prop']); + if (isset($prop['blockinfo'])) { + if ($wgUser->isBlocked()) { + $vals['blockedby'] = User::whoIs($wgUser->blockedBy()); + $vals['blockreason'] = $wgUser->blockedFor(); + } + } + if (isset($prop['hasmsg']) && $wgUser->getNewtalk()) { + $vals['messages'] = ''; + } + if (isset($prop['groups'])) { + $vals['groups'] = $wgUser->getGroups(); + $result->setIndexedTagName($vals['groups'], 'g'); // even if empty + } + if (isset($prop['rights'])) { + $vals['rights'] = $wgUser->getRights(); + $result->setIndexedTagName($vals['rights'], 'r'); // even if empty + } + } + + if (!empty($params['option'])) { + foreach( $params['option'] as $option ) { + if (empty($option)) + $this->dieUsage('Empty value is not allowed for the option parameter', 'option'); + $vals['options'][$option] = $wgUser->getOption($option); + } + } + + $result->addValue(null, $this->getModuleName(), $vals); + } + + protected function getAllowedParams() { + return array ( + 'prop' => array ( + ApiBase :: PARAM_DFLT => NULL, + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'blockinfo', + 'hasmsg', + 'groups', + 'rights', + )), + 'option' => array ( + ApiBase :: PARAM_DFLT => NULL, + ApiBase :: PARAM_ISMULTI => true, + ), + ); + } + + protected function getParamDescription() { + return array ( + 'prop' => array( + 'What pieces of information to include', + ' blockinfo - tags if the user is blocked, by whom, and for what reason', + ' hasmsg - adds a tag "message" if user has pending messages', + ' groups - lists all the groups the current user belongs to', + ' rights - lists of all rights the current user has', + ), + 'option' => 'A list of user preference options to get', + ); + } + + protected function getDescription() { + return 'Get information about the current user'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&meta=userinfo', + 'api.php?action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg', + 'api.php?action=query&meta=userinfo&uioption=rememberpassword', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryUserInfo.php 24529 2007-08-01 20:11:29Z yurik $'; + } +} + diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index 73c31abb..16586a40 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,9 @@ if (!defined('MEDIAWIKI')) { } /** + * This query action allows clients to retrieve a list of recently modified pages + * that are part of the logged-in user's watchlist. + * * @addtogroup API */ class ApiQueryWatchlist extends ApiQueryGeneratorBase { @@ -45,8 +48,13 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->run($resultPageSet); } + private $fld_ids = false, $fld_title = false, $fld_patrol = false, $fld_flags = false, + $fld_timestamp = false, $fld_user = false, $fld_comment = false, $fld_sizes = false; + private function run($resultPageSet = null) { - global $wgUser; + global $wgUser, $wgDBtype; + + $this->selectNamedDB('watchlist', DB_SLAVE, 'watchlist'); if (!$wgUser->isLoggedIn()) $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin'); @@ -54,17 +62,20 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $allrev = $start = $end = $namespace = $dir = $limit = $prop = null; extract($this->extractRequestParams()); - $patrol = $timestamp = $user = $comment = false; - if (!is_null($prop)) { - if (!is_null($resultPageSet)) - $this->dieUsage('prop parameter may not be used in a generator', 'params'); + if (!is_null($prop) && is_null($resultPageSet)) { + + $prop = array_flip($prop); - $user = (false !== array_search('user', $prop)); - $comment = (false !== array_search('comment', $prop)); - $timestamp = (false !== array_search('timestamp', $prop)); // TODO: $timestamp not currently being used. - $patrol = (false !== array_search('patrol', $prop)); + $this->fld_ids = isset($prop['ids']); + $this->fld_title = isset($prop['title']); + $this->fld_flags = isset($prop['flags']); + $this->fld_user = isset($prop['user']); + $this->fld_comment = isset($prop['comment']); + $this->fld_timestamp = isset($prop['timestamp']); + $this->fld_sizes = isset($prop['sizes']); + $this->fld_patrol = isset($prop['patrol']); - if ($patrol) { + if ($this->fld_patrol) { global $wgUseRCPatrol, $wgUser; if (!$wgUseRCPatrol || !$wgUser->isAllowed('patrol')) $this->dieUsage('patrol property is not available', 'patrol'); @@ -77,15 +88,17 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'rc_this_oldid', 'rc_namespace', 'rc_title', - 'rc_new', - 'rc_minor', 'rc_timestamp' )); - $this->addFieldsIf('rc_user', $user); - $this->addFieldsIf('rc_user_text', $user); - $this->addFieldsIf('rc_comment', $comment); - $this->addFieldsIf('rc_patrolled', $patrol); + $this->addFieldsIf('rc_new', $this->fld_flags); + $this->addFieldsIf('rc_minor', $this->fld_flags); + $this->addFieldsIf('rc_user', $this->fld_user); + $this->addFieldsIf('rc_user_text', $this->fld_user); + $this->addFieldsIf('rc_comment', $this->fld_comment); + $this->addFieldsIf('rc_patrolled', $this->fld_patrol); + $this->addFieldsIf('rc_old_len', $this->fld_sizes); + $this->addFieldsIf('rc_new_len', $this->fld_sizes); } elseif ($allrev) { $this->addFields(array ( @@ -114,12 +127,16 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'wl_namespace = rc_namespace', 'wl_title = rc_title', 'rc_cur_id = page_id', - 'wl_user' => $userId + 'wl_user' => $userId, + 'rc_deleted' => 0, )); + $this->addWhereRange('rc_timestamp', $dir, $start, $end); $this->addWhereFld('wl_namespace', $namespace); $this->addWhereIf('rc_this_oldid=page_latest', !$allrev); - $this->addWhereIf("rc_timestamp > ''", !isset ($start) && !isset ($end)); + + # This is a index optimization for mysql, as done in the Special:Watchlist page + $this->addWhereIf("rc_timestamp > ''", !isset ($start) && !isset ($end) && $wgDBtype == 'mysql'); $this->addOption('LIMIT', $limit +1); @@ -131,23 +148,19 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 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', $row->rc_timestamp); + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); break; } if (is_null($resultPageSet)) { - $vals = $this->addRowInfo('rc', $row); - if($vals) + $vals = $this->extractRowInfo($row); + if ($vals) $data[] = $vals; } else { - $title = Title :: makeTitle($row->rc_namespace, $row->rc_title); - // skip any pages that user has no rights to read - if ($title->userCanRead()) { - if ($allrev) { - $data[] = intval($row->rc_this_oldid); - } else { - $data[] = intval($row->rc_cur_id); - } + if ($allrev) { + $data[] = intval($row->rc_this_oldid); + } else { + $data[] = intval($row->rc_cur_id); } } } @@ -165,6 +178,50 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } } + private function extractRowInfo($row) { + + $vals = array (); + + if ($this->fld_ids) { + $vals['pageid'] = intval($row->rc_cur_id); + $vals['revid'] = intval($row->rc_this_oldid); + } + + if ($this->fld_title) + ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->rc_namespace, $row->rc_title)); + + if ($this->fld_user) { + $vals['user'] = $row->rc_user_text; + if (!$row->rc_user) + $vals['anon'] = ''; + } + + if ($this->fld_flags) { + if ($row->rc_new) + $vals['new'] = ''; + if ($row->rc_minor) + $vals['minor'] = ''; + } + + if ($this->fld_patrol && isset($row->rc_patrolled)) + $vals['patrolled'] = ''; + + if ($this->fld_timestamp) + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); + + $this->addFieldsIf('rc_new_len', $this->fld_sizes); + + if ($this->fld_sizes) { + $vals['oldlen'] = intval($row->rc_old_len); + $vals['newlen'] = intval($row->rc_new_len); + } + + if ($this->fld_comment && !empty ($row->rc_comment)) + $vals['comment'] = $row->rc_comment; + + return $vals; + } + protected function getAllowedParams() { return array ( 'allrev' => false, @@ -189,16 +246,21 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ), 'prop' => array ( APIBase :: PARAM_ISMULTI => true, + APIBase :: PARAM_DFLT => 'ids|title|flags', APIBase :: PARAM_TYPE => array ( + 'ids', + 'title', + 'flags', 'user', 'comment', 'timestamp', - 'patrol' + 'patrol', + 'sizes', ) ) ); @@ -223,14 +285,15 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { protected function getExamples() { return array ( 'api.php?action=query&list=watchlist', - 'api.php?action=query&list=watchlist&wlallrev', + 'api.php?action=query&list=watchlist&wlprop=ids|title|timestamp|user|comment', + 'api.php?action=query&list=watchlist&wlallrev&wlprop=ids|title|timestamp|user|comment', 'api.php?action=query&generator=watchlist&prop=info', 'api.php?action=query&generator=watchlist&gwlallrev&prop=revisions&rvprop=timestamp|user' ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlist.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryWatchlist.php 24092 2007-07-14 19:04:31Z yurik $'; } } -?> + diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index 79fd34a1..a318d808 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -5,7 +5,7 @@ * * API for MediaWiki 1.8+ * - * Copyright (C) 2006 Yuri Astrakhan + * Copyright (C) 2006 Yuri Astrakhan @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 @@ -29,6 +29,20 @@ if (!defined('MEDIAWIKI')) { } /** + * This class represents the result of the API operations. + * It simply wraps a nested array() structure, adding some functions to simplify array's modifications. + * As various modules execute, they add different pieces of information to this result, + * structuring it as it will be given to the client. + * + * Each subarray may either be a dictionary - key-value pairs with unique keys, + * or lists, where the items are added using $data[] = $value notation. + * + * There are two special key values that change how XML output is generated: + * '_element' This key sets the tag name for the rest of the elements in the current array. + * It is only inserted if the formatter returned true for getNeedsRawData() + * '*' This key has special meaning only to the XML formatter, and is outputed as is + * for all others. In XML it becomes the content of the current element. + * * @addtogroup API */ class ApiResult extends ApiBase { @@ -44,6 +58,9 @@ class ApiResult extends ApiBase { $this->reset(); } + /** + * Clear the current result data. + */ public function reset() { $this->mData = array (); } @@ -56,10 +73,16 @@ class ApiResult extends ApiBase { $this->mIsRawMode = true; } + /** + * Returns true if the result is being created for the formatter that requested raw data. + */ public function getIsRawMode() { return $this->mIsRawMode; } + /** + * Get result's internal data array + */ public function & getData() { return $this->mData; } @@ -103,11 +126,6 @@ class ApiResult extends ApiBase { } } - // public static function makeContentElement($tag, $value) { - // $result = array(); - // ApiResult::setContent($result, ) - // } - // /** * In case the array contains indexed values (in addition to named), * all indexed values will have the given tag name. @@ -125,7 +143,8 @@ class ApiResult extends ApiBase { /** * Add value to the output data at the given path. * Path is an indexed array, each element specifing the branch at which to add the new value - * Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value + * Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value + * If $name is empty, the $value is added as a next list element data[] = $value */ public function addValue($path, $name, $value) { @@ -145,7 +164,10 @@ class ApiResult extends ApiBase { } } - ApiResult :: setElement($data, $name, $value); + if (empty($name)) + $data[] = $value; // Add list element + else + ApiResult :: setElement($data, $name, $value); // Add named element } public function execute() { @@ -153,7 +175,7 @@ class ApiResult extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiResult.php 21402 2007-04-20 08:55:14Z nickj $'; + return __CLASS__ . ': $Id: ApiResult.php 23531 2007-06-29 01:19:14Z simetrical $'; } } -?> + -- cgit v1.2.2