From a58285fd06c8113c45377c655dd43cef6337e815 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 11 Jan 2007 19:06:07 +0000 Subject: Aktualisierung auf MediaWiki 1.9.0 --- includes/api/ApiBase.php | 200 +++++++++++----- includes/api/ApiFeedWatchlist.php | 125 ++++++++++ includes/api/ApiFormatBase.php | 109 +++++++-- includes/api/ApiFormatJson.php | 23 +- includes/api/ApiFormatPhp.php | 54 +++++ includes/api/ApiFormatWddx.php | 89 +++++++ includes/api/ApiFormatXml.php | 36 +-- includes/api/ApiFormatYaml.php | 5 +- includes/api/ApiFormatYaml_spyc.php | 9 +- includes/api/ApiLogin.php | 6 +- includes/api/ApiMain.php | 294 ++++++++++++++++------- includes/api/ApiOpenSearch.php | 109 +++++++++ includes/api/ApiPageSet.php | 138 ++++++++--- includes/api/ApiQuery.php | 58 +++-- includes/api/ApiQueryAllpages.php | 114 +++++---- includes/api/ApiQueryBacklinks.php | 358 +++++++++++++++++++++++++++++ includes/api/ApiQueryBase.php | 273 +++++++++++++++++++++- includes/api/ApiQueryInfo.php | 13 +- includes/api/ApiQueryLogEvents.php | 173 ++++++++++++++ includes/api/ApiQueryRecentChanges.php | 187 +++++++++++++++ includes/api/ApiQueryRevisions.php | 194 ++++++---------- includes/api/ApiQuerySiteinfo.php | 11 +- includes/api/ApiQueryUserContributions.php | 175 ++++++++++++++ includes/api/ApiQueryWatchlist.php | 234 +++++++++++++++++++ includes/api/ApiResult.php | 64 +++--- 25 files changed, 2589 insertions(+), 462 deletions(-) create mode 100644 includes/api/ApiFeedWatchlist.php create mode 100644 includes/api/ApiFormatPhp.php create mode 100644 includes/api/ApiFormatWddx.php create mode 100644 includes/api/ApiOpenSearch.php create mode 100644 includes/api/ApiQueryBacklinks.php create mode 100644 includes/api/ApiQueryLogEvents.php create mode 100644 includes/api/ApiQueryRecentChanges.php create mode 100644 includes/api/ApiQueryUserContributions.php create mode 100644 includes/api/ApiQueryWatchlist.php (limited to 'includes/api') diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index f578f41b..1a9c1e3d 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -35,6 +35,11 @@ abstract class ApiBase { 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 + private $mMainModule, $mModuleName, $mParamPrefix; /** @@ -42,7 +47,7 @@ abstract class ApiBase { */ public function __construct($mainModule, $moduleName, $paramPrefix = '') { $this->mMainModule = $mainModule; - $this->mModuleName = $moduleName; + $this->mModuleName = $moduleName; $this->mParamPrefix = $paramPrefix; } @@ -51,12 +56,22 @@ abstract class ApiBase { */ public abstract function execute(); - /** - * Get the name of the query being executed by this instance - */ - public function getModuleName() { - return $this->mModuleName; - } + /** + * Get the name of the module being executed by this instance + */ + public function getModuleName() { + return $this->mModuleName; + } + + /** + * Get the name of the module as shown in the profiler log + */ + public function getModuleProfileName($db = false) { + if ($db) + return 'API:' . $this->mModuleName . '-DB'; + else + return 'API:' . $this->mModuleName; + } /** * Get main module @@ -90,6 +105,15 @@ abstract class ApiBase { return $this->getResult()->getData(); } + /** + * If the module may only be used with a certain format module, + * it should override this method to return an instance of that formatter. + * A value of null means the default format will be used. + */ + public function getCustomPrinter() { + return null; + } + /** * Generates help message for this module, or false if there is no description */ @@ -126,8 +150,17 @@ abstract class ApiBase { if ($this->getMain()->getShowVersions()) { $versions = $this->getVersion(); - if (is_array($versions)) + $pattern = '(\$.*) ([0-9a-z_]+\.php) (.*\$)'; + $replacement = '\\0' . "\n " . 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/api/\\2'; + + if (is_array($versions)) { + foreach ($versions as &$v) + $v = eregi_replace($pattern, $replacement, $v); $versions = implode("\n ", $versions); + } + else + $versions = eregi_replace($pattern, $replacement, $versions); + $msg .= "Version:\n $versions\n"; } } @@ -141,10 +174,32 @@ abstract class ApiBase { $paramsDescription = $this->getParamDescription(); $msg = ''; - foreach (array_keys($params) as $paramName) { + $paramPrefix = "\n" . str_repeat(' ', 19); + foreach ($params as $paramName => $paramSettings) { $desc = isset ($paramsDescription[$paramName]) ? $paramsDescription[$paramName] : ''; if (is_array($desc)) - $desc = implode("\n" . str_repeat(' ', 19), $desc); + $desc = implode($paramPrefix, $desc); + + @ $type = $paramSettings[self :: PARAM_TYPE]; + if (isset ($type)) { + if (isset ($paramSettings[self :: PARAM_ISMULTI])) + $prompt = 'Values (separate with \'|\'): '; + else + $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()); + } + } + + $default = is_array($paramSettings) ? (isset ($paramSettings[self :: PARAM_DFLT]) ? $paramSettings[self :: PARAM_DFLT] : null) : $paramSettings; + if (!is_null($default) && $default !== false) + $desc .= $paramPrefix . "Default: $default"; + $msg .= sprintf(" %-14s - %s\n", $this->encodeParamName($paramName), $desc); } return $msg; @@ -180,7 +235,7 @@ abstract class ApiBase { protected function getParamDescription() { return false; } - + /** * This method mangles parameter name based on the prefix supplied to the constructor. * Override this method to change parameter name during runtime @@ -213,13 +268,26 @@ abstract class ApiBase { return $this->getParameterFromSettings($paramName, $paramSettings); } + public static function getValidNamespaces() { + static $mValidNamespaces = null; + if (is_null($mValidNamespaces)) { + + global $wgContLang; + $mValidNamespaces = array (); + foreach (array_keys($wgContLang->getNamespaces()) as $ns) { + if ($ns >= 0) + $mValidNamespaces[] = $ns; + } + } + return $mValidNamespaces; + } + /** * Using the settings determine the value for the given parameter * @param $paramName String: parameter name * @param $paramSettings Mixed: default value or an array of settings using PARAM_* constants. - */ + */ protected function getParameterFromSettings($paramName, $paramSettings) { - global $wgRequest; // Some classes may decide to change parameter names $paramName = $this->encodeParamName($paramName); @@ -248,48 +316,58 @@ abstract class ApiBase { ApiBase :: dieDebug(__METHOD__, "Boolean param $paramName's default is set to '$default'"); } - $value = $wgRequest->getCheck($paramName); - } else - $value = $wgRequest->getVal($paramName, $default); + $value = $this->getMain()->getRequest()->getCheck($paramName); + } else { + $value = $this->getMain()->getRequest()->getVal($paramName, $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); // More validation only when choices were not given // choices were validated in parseMultiValue() - if (!is_array($type) && isset ($value)) { - - switch ($type) { - case 'NULL' : // nothing to do - break; - case 'string' : // nothing to do - break; - case 'integer' : // Force everything using intval() - $value = is_array($value) ? array_map('intval', $value) : intval($value); - 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 ($multi) - ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); - $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]); - break; - case 'boolean' : - if ($multi) - ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); - break; - case 'timestamp' : - if ($multi) - ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); - if (!preg_match('/^[0-9]{14}$/', $value)) - $this->dieUsage("Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$valueName}"); - break; - default : - ApiBase :: dieDebug(__METHOD__, "Param $paramName's type is unknown - $type"); - + if (isset ($value)) { + if (!is_array($type)) { + switch ($type) { + case 'NULL' : // nothing to do + break; + case 'string' : // nothing to do + break; + case 'integer' : // Force everything using intval() + $value = is_array($value) ? array_map('intval', $value) : intval($value); + 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 ($multi) + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + $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]); + break; + case 'boolean' : + if ($multi) + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + break; + case 'timestamp' : + if ($multi) + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + $value = wfTimestamp(TS_UNIX, $value); + if ($value === 0) + $this->dieUsage("Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$paramName}"); + $value = wfTimestamp(TS_MW, $value); + break; + default : + ApiBase :: dieDebug(__METHOD__, "Param $paramName's type is unknown - $type"); + } } + + // There should never be any duplicate values in a list + if (is_array($value)) + $value = array_unique($value); } return $value; @@ -314,7 +392,7 @@ abstract class ApiBase { if (is_array($allowedValues)) { $unknownValues = array_diff($valuesList, $allowedValues); if ($unknownValues) { - $this->dieUsage('Unrecognised value' . (count($unknownValues) > 1 ? "s '" : " '") . implode("', '", $unknownValues) . "' for parameter '$valueName'", "unknown_$valueName"); + $this->dieUsage('Unrecognised value' . (count($unknownValues) > 1 ? "s" : "") . " for parameter '$valueName'", "unknown_$valueName"); } } @@ -325,8 +403,6 @@ 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) { - global $wgUser; - if ($value < $min) { $this->dieUsage("$varname may not be less than $min (set to $value)", $varname); } @@ -345,7 +421,7 @@ abstract class ApiBase { * Call main module's error handler */ public function dieUsage($description, $errorCode, $httpRespCode = 0) { - $this->getMain()->mainDieUsage($description, $this->encodeParamName($errorCode), $httpRespCode); + throw new UsageException($description, $this->encodeParamName($errorCode), $httpRespCode); } /** @@ -367,6 +443,7 @@ abstract class ApiBase { if ($this->mTimeIn !== 0) ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileOut()'); $this->mTimeIn = microtime(true); + wfProfileIn($this->getModuleProfileName()); } /** @@ -380,6 +457,19 @@ abstract class ApiBase { $this->mModuleTime += microtime(true) - $this->mTimeIn; $this->mTimeIn = 0; + wfProfileOut($this->getModuleProfileName()); + } + + /** + * When modules crash, sometimes it is needed to do a profileOut() regardless + * of the profiling state the module was in. This method does such cleanup. + */ + public function safeProfileOut() { + if ($this->mTimeIn !== 0) { + if ($this->mDBTimeIn !== 0) + $this->profileDBOut(); + $this->profileOut(); + } } /** @@ -405,6 +495,7 @@ abstract class ApiBase { if ($this->mDBTimeIn !== 0) ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileDBOut()'); $this->mDBTimeIn = microtime(true); + wfProfileIn($this->getModuleProfileName(true)); } /** @@ -421,6 +512,7 @@ abstract class ApiBase { $this->mDBTime += $time; $this->getMain()->mDBTime += $time; + wfProfileOut($this->getModuleProfileName(true)); } /** @@ -433,9 +525,9 @@ abstract class ApiBase { } public abstract function getVersion(); - + public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiBase.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiBase.php 17880 2006-11-23 08:25:56Z nickj $'; } } ?> \ No newline at end of file diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php new file mode 100644 index 00000000..7d1c1519 --- /dev/null +++ b/includes/api/ApiFeedWatchlist.php @@ -0,0 +1,125 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiBase.php"); +} + +class ApiFeedWatchlist extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function getCustomPrinter() { + return new ApiFormatFeedWrapper($this->getMain()); + } + + 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); + } + + private function createFeedItem($info) { + $titleStr = $info['title']; + $title = Title :: newFromText($titleStr); + $titleUrl = $title->getFullUrl(); + $comment = isset( $info['comment'] ) ? $info['comment'] : null; + $timestamp = $info['timestamp']; + $user = $info['user']; + + $completeText = "$comment ($user)"; + + return new FeedItem($titleStr, $completeText, $titleUrl, $timestamp, $user); + } + + protected function getAllowedParams() { + global $wgFeedClasses; + $feedFormatNames = array_keys($wgFeedClasses); + return array ( + 'feedformat' => array ( + ApiBase :: PARAM_DFLT => 'rss', + ApiBase :: PARAM_TYPE => $feedFormatNames + ) + ); + } + + protected function getParamDescription() { + return array ( + 'feedformat' => 'The format of the feed' + ); + } + + protected function getDescription() { + return 'This module returns a watchlist feed'; + } + + protected function getExamples() { + return array ( + 'api.php?action=feedwatchlist' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFeedWatchlist.php 17987 2006-11-29 05:45:03Z nickj $'; + } +} +?> diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 6f5b4aca..611960d3 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -75,32 +75,40 @@ abstract class ApiFormatBase extends ApiBase { function initPrinter($isError) { $isHtml = $this->getIsHtml(); $mime = $isHtml ? 'text/html' : $this->getMimeType(); + + // Some printers (ex. Feed) do their own header settings, + // in which case $mime will be set to null + if (is_null($mime)) + return; // skip any initialization + header("Content-Type: $mime; charset=utf-8;"); if ($isHtml) { ?> - - - MediaWiki API - - + + + + MediaWiki API + + -
- - This result is being shown in mFormat?> format, - which might not be suitable for your application.
- See API help for more information.
-
+
+ +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. +
-
+
 getIsHtml()) {
 ?>
-		
- + +
+ + ]+)\>', '<\1>', $text); + $text = ereg_replace('\<([^>]+)\>', '<\1>', $text); // identify URLs - $text = ereg_replace("[a-zA-Z]+://[^ '()<\n]+", '\\0', $text); + $protos = "http|https|ftp|gopher"; + $text = ereg_replace("($protos)://[^ '\"()<\n]+", '\\0', $text); // identify requests to api.php $text = ereg_replace("api\\.php\\?[^ ()<\n\t]+", '\\0', $text); // make strings inside * bold @@ -151,11 +162,71 @@ abstract class ApiFormatBase extends ApiBase { * Returns usage examples for this format. */ protected function getExamples() { - return 'api.php?action=query&meta=siteinfo&si=namespaces&format=' . $this->getModuleName(); + return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName(); + } + + protected function getDescription() { + return $this->getIsHtml() ? ' (pretty-print in HTML)' : ''; } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 17374 2006-11-03 06:53:47Z yurik $'; + } +} + +/** + * This printer is used to wrap an instance of the Feed class + */ +class ApiFormatFeedWrapper extends ApiFormatBase { + + public function __construct($main) { + parent :: __construct($main, 'feed'); + } + + /** + * Call this method to initialize output data + */ + public static function setResult($result, $feed, $feedItems) { + // Store output in the Result data. + // This way we can check during execution if any error has occured + $data = & $result->getData(); + $data['_feed'] = $feed; + $data['_feeditems'] = $feedItems; + } + + /** + * Feed does its own headers + */ + public function getMimeType() { + return null; + } + + /** + * Optimization - no need to sanitize data that will not be needed + */ + public function getNeedsRawData() { + return true; + } + + public function execute() { + $data = $this->getResultData(); + if (isset ($data['_feed']) && isset ($data['_feeditems'])) { + $feed = $data['_feed']; + $items = $data['_feeditems']; + + $feed->outHeader(); + foreach ($items as & $item) + $feed->outItem($item); + $feed->outFooter(); + } else { + // Error has occured, print something usefull + // TODO: make this error more informative using ApiBase :: dieDebug() or similar + wfHttpError(500, 'Internal Server Error', ''); + } + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFormatBase.php 17374 2006-11-03 06:53:47Z yurik $'; } } -?> \ No newline at end of file +?> diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index fdc29cf2..45c735c8 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -31,26 +31,39 @@ if (!defined('MEDIAWIKI')) { class ApiFormatJson extends ApiFormatBase { + private $mIsRaw; + public function __construct($main, $format) { parent :: __construct($main, $format); + $this->mIsRaw = ($format === 'rawfm'); } public function getMimeType() { return 'application/json'; } + public function getNeedsRawData() { + return $this->mIsRaw; + } + public function execute() { - require ('ApiFormatJson_json.php'); - $json = new Services_JSON(); - $this->printText($json->encode($this->getResultData(), true)); + if (!function_exists('json_encode') || $this->getIsHtml()) { + $json = new Services_JSON(); + $this->printText($json->encode($this->getResultData(), $this->getIsHtml())); + } else { + $this->printText(json_encode($this->getResultData())); + } } protected function getDescription() { - return 'Output data in JSON format'; + if ($this->mIsRaw) + return 'Output data with the debuging elements in JSON format' . parent :: getDescription(); + else + return 'Output data in JSON format' . parent :: getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatJson.php 16725 2006-10-01 21:20:55Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatJson.php 17374 2006-11-03 06:53:47Z yurik $'; } } ?> \ No newline at end of file diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php new file mode 100644 index 00000000..938ba032 --- /dev/null +++ b/includes/api/ApiFormatPhp.php @@ -0,0 +1,54 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiFormatBase.php'); +} + +class ApiFormatPhp extends ApiFormatBase { + + public function __construct($main, $format) { + parent :: __construct($main, $format); + } + + public function getMimeType() { + return 'application/vnd.php.serialized'; + } + + public function execute() { + $this->printText(serialize($this->getResultData())); + } + + protected function getDescription() { + return 'Output data in serialized PHP format' . parent :: getDescription(); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFormatPhp.php 17374 2006-11-03 06:53:47Z yurik $'; + } +} +?> \ No newline at end of file diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php new file mode 100644 index 00000000..e97b996c --- /dev/null +++ b/includes/api/ApiFormatWddx.php @@ -0,0 +1,89 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiFormatBase.php'); +} + +class ApiFormatWddx extends ApiFormatBase { + + public function __construct($main, $format) { + parent :: __construct($main, $format); + } + + public function getMimeType() { + return 'text/xml'; + } + + public function execute() { + if (function_exists('wddx_serialize_value')) { + $this->printText(wddx_serialize_value($this->getResultData())); + } else { + $this->printText(''); + $this->printText('
'); + $this->slowWddxPrinter($this->getResultData()); + $this->printText(''); + } + } + + /** + * Recursivelly go through the object and output its data in WDDX format. + */ + function slowWddxPrinter($elemValue) { + switch (gettype($elemValue)) { + case 'array' : + $this->printText(''); + foreach ($elemValue as $subElemName => $subElemValue) { + $this->printText(wfElement('var', array ( + 'name' => $subElemName + ), null)); + $this->slowWddxPrinter($subElemValue); + $this->printText(''); + } + $this->printText(''); + break; + case 'integer' : + case 'double' : + $this->printText(wfElement('number', null, $elemValue)); + break; + case 'string' : + $this->printText(wfElement('string', null, $elemValue)); + break; + default : + ApiBase :: dieDebug(__METHOD__, 'Unknown type ' . gettype($elemValue)); + } + } + + protected function getDescription() { + return 'Output data in WDDX format' . parent :: getDescription(); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFormatWddx.php 17374 2006-11-03 06:53:47Z yurik $'; + } +} +?> \ No newline at end of file diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index 6aa08e00..2326ba42 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -31,6 +31,8 @@ if (!defined('MEDIAWIKI')) { class ApiFormatXml extends ApiFormatBase { + private $mRootElemName = 'api'; + public function __construct($main, $format) { parent :: __construct($main, $format); } @@ -42,18 +44,14 @@ class ApiFormatXml extends ApiFormatBase { public function getNeedsRawData() { return true; } + + public function setRootElement($rootElemName) { + $this->mRootElemName = $rootElemName; + } public function execute() { - $xmlindent = null; - extract($this->extractRequestParams()); - - if ($xmlindent || $this->getIsHtml()) - $xmlindent = -2; - else - $xmlindent = null; - $this->printText(''); - $this->recXmlPrint('api', $this->getResultData(), $xmlindent); + $this->recXmlPrint($this->mRootElemName, $this->getResultData(), $this->getIsHtml() ? -2 : null); } /** @@ -98,8 +96,6 @@ class ApiFormatXml extends ApiFormatBase { $subElements = array (); foreach ($elemValue as $subElemId => & $subElemValue) { if (gettype($subElemId) === 'integer') { - if (!is_array($subElemValue)) - ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has a scalar indexed value."); $indElements[] = $subElemValue; unset ($elemValue[$subElemId]); } elseif (is_array($subElemValue)) { @@ -109,7 +105,7 @@ class ApiFormatXml extends ApiFormatBase { } if (is_null($subElemIndName) && !empty ($indElements)) - ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value"); + ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()."); if (!empty ($subElements) && !empty ($indElements) && !is_null($subElemContent)) ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has content and subelements"); @@ -139,23 +135,11 @@ class ApiFormatXml extends ApiFormatBase { } } protected function getDescription() { - return 'Output data in XML format'; - } - - protected function getAllowedParams() { - return array ( - 'xmlindent' => false - ); - } - - protected function getParamDescription() { - return array ( - 'xmlindent' => 'Enable XML indentation' - ); + return 'Output data in XML format' . parent :: getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatXml.php 16725 2006-10-01 21:20:55Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatXml.php 17374 2006-11-03 06:53:47Z yurik $'; } } ?> \ No newline at end of file diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php index bd74f01a..2371903f 100644 --- a/includes/api/ApiFormatYaml.php +++ b/includes/api/ApiFormatYaml.php @@ -40,16 +40,15 @@ class ApiFormatYaml extends ApiFormatBase { } public function execute() { - require ('ApiFormatYaml_spyc.php'); $this->printText(Spyc :: YAMLDump($this->getResultData())); } protected function getDescription() { - return 'Output data in YAML format'; + return 'Output data in YAML format' . parent :: getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatYaml.php 16725 2006-10-01 21:20:55Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatYaml.php 17374 2006-11-03 06:53:47Z yurik $'; } } ?> \ No newline at end of file diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php index 05a39e23..1ec8af48 100644 --- a/includes/api/ApiFormatYaml_spyc.php +++ b/includes/api/ApiFormatYaml_spyc.php @@ -463,6 +463,7 @@ * @param string $line A line from the YAML file */ function _getIndent($line) { + $match = array(); preg_match('/^\s{1,}/',$line,$match); if (!empty($match[0])) { $indent = substr_count($match[0],' '); @@ -500,6 +501,7 @@ } elseif (preg_match('/^(.+):/',$line,$key)) { // It's a key/value pair most likely // If the key is in double quotes pull it out + $matches = array(); if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { $value = trim(str_replace($matches[1],'',$line)); $key = $matches[2]; @@ -529,6 +531,7 @@ * @return mixed */ function _toType($value) { + $matches = array(); if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) { $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches)); $value = preg_replace('/\\\\"/','"',$value); @@ -596,6 +599,7 @@ // Check for strings $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + $strings = array(); if (preg_match_all($regex,$inline,$strings)) { $saved_strings[] = $strings[0][0]; $inline = preg_replace($regex,'YAMLString',$inline); @@ -603,12 +607,14 @@ unset($regex); // Check for sequences + $seqs = array(); if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) { $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline); $seqs = $seqs[0]; } // Check for mappings + $maps = array(); if (preg_match_all('/{(.+)}/U',$inline,$maps)) { $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline); $maps = $maps[0]; @@ -704,6 +710,7 @@ function _linkRef(&$n,$key,$k = NULL,$v = NULL) { if (empty($k) && empty($v)) { // Look for &refs + $matches = array(); if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) { // Flag the node so we know it's a reference $this->_allNodes[$n->id]->ref = substr($matches[0],1); @@ -837,7 +844,7 @@ $ret = array(); foreach($keys as $key) { - list($unused,$val) = each($vals); + list( /* unused */ ,$val) = each($vals); // This is the good part! If a key already exists, but it's part of a // sequence (an int), just keep addin numbers until we find a fresh one. if (isset($ret[$key]) and is_int($key)) { diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index 2aa571c1..d9697dc3 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -89,8 +89,8 @@ class ApiLogin extends ApiBase { protected function getAllowedParams() { return array ( - 'name' => '', - 'password' => '', + 'name' => null, + 'password' => null, 'domain' => null ); } @@ -116,7 +116,7 @@ class ApiLogin extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogin.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiLogin.php 17065 2006-10-17 02:11:29Z yurik $'; } } ?> diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 046d7d7c..606f022b 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -29,36 +29,79 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiBase.php'); } +/** + * This is the main API class, used for both external and internal processing. + */ class ApiMain extends ApiBase { + /** + * When no format parameter is given, this format will be used + */ + const API_DEFAULT_FORMAT = 'xmlfm'; + + /** + * List of available modules: action name => module class + */ + private static $Modules = array ( + 'help' => 'ApiHelp', + 'login' => 'ApiLogin', + 'opensearch' => 'ApiOpenSearch', + 'feedwatchlist' => 'ApiFeedWatchlist', + 'query' => 'ApiQuery' + ); + + /** + * List of available formats: format name => format class + */ + private static $Formats = array ( + 'json' => 'ApiFormatJson', + 'jsonfm' => 'ApiFormatJson', + 'php' => 'ApiFormatPhp', + 'phpfm' => 'ApiFormatPhp', + 'wddx' => 'ApiFormatWddx', + 'wddxfm' => 'ApiFormatWddx', + 'xml' => 'ApiFormatXml', + 'xmlfm' => 'ApiFormatXml', + 'yaml' => 'ApiFormatYaml', + 'yamlfm' => 'ApiFormatYaml', + 'rawfm' => 'ApiFormatJson' + ); + private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames; - private $mApiStartTime, $mResult, $mShowVersions, $mEnableWrite; + private $mResult, $mShowVersions, $mEnableWrite, $mRequest, $mInternalMode, $mSquidMaxage; /** * Constructor - * $apiStartTime - time of the originating call for profiling purposes - * $modules - an array of actions (keys) and classes that handle them (values) + * @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 */ - public function __construct($apiStartTime, $modules, $formats, $enableWrite) { + public function __construct($request, $enableWrite = false) { + + $this->mInternalMode = ($request instanceof FauxRequest); + // Special handling for the main module: $parent === $this - parent :: __construct($this, 'main'); + parent :: __construct($this, $this->mInternalMode ? 'main_int' : 'main'); + + $this->mModules = self :: $Modules; + $this->mModuleNames = array_keys($this->mModules); // todo: optimize + $this->mFormats = self :: $Formats; + $this->mFormatNames = array_keys($this->mFormats); // todo: optimize - $this->mModules = $modules; - $this->mModuleNames = array_keys($modules); - $this->mFormats = $formats; - $this->mFormatNames = array_keys($formats); - $this->mApiStartTime = $apiStartTime; $this->mResult = new ApiResult($this); $this->mShowVersions = false; $this->mEnableWrite = $enableWrite; + + $this->mRequest = & $request; + + $this->mSquidMaxage = 0; } - public function & getResult() { - return $this->mResult; + public function & getRequest() { + return $this->mRequest; } - public function getShowVersions() { - return $this->mShowVersions; + public function getResult() { + return $this->mResult; } public function requestWriteMode() { @@ -67,94 +110,180 @@ class ApiMain extends ApiBase { 'statement is included in the site\'s LocalSettings.php file', 'readonly'); } - protected function getAllowedParams() { - return array ( - 'format' => array ( - ApiBase :: PARAM_DFLT => API_DEFAULT_FORMAT, - ApiBase :: PARAM_TYPE => $this->mFormatNames - ), - 'action' => array ( - ApiBase :: PARAM_DFLT => 'help', - ApiBase :: PARAM_TYPE => $this->mModuleNames - ), - 'version' => false - ); + public function setCacheMaxAge($maxage) { + $this->mSquidMaxage = $maxage; } - protected function getParamDescription() { - return array ( - 'format' => 'The format of the output', - 'action' => 'What action you would like to perform', - 'version' => 'When showing help, include version for each module' - ); + public function createPrinterByName($format) { + return new $this->mFormats[$format] ($this, $format); } public function execute() { $this->profileIn(); - $action = $format = $version = null; - try { - extract($this->extractRequestParams()); - $this->mShowVersions = $version; + if ($this->mInternalMode) + $this->executeAction(); + else + $this->executeActionWithErrorHandling(); + $this->profileOut(); + } - // Create an appropriate printer - $this->mPrinter = new $this->mFormats[$format] ($this, $format); + protected function executeActionWithErrorHandling() { - // Instantiate and execute module requested by the user - $module = new $this->mModules[$action] ($this, $action); - $module->profileIn(); - $module->execute(); - $module->profileOut(); - $this->printResult(false); + // In case an error occurs during data output, + // this clear the output buffer and print just the error information + ob_start(); - } catch (UsageException $e) { + try { + $this->executeAction(); + } catch (Exception $e) { + // + // Handle any kind of exception by outputing properly formatted error message. + // If this fails, an unhandled exception should be thrown so that global error + // handler will process and log it. + // + + // Error results should not be cached + $this->setCacheMaxAge(0); // Printer may not be initialized if the extractRequestParams() fails for the main module - if (!isset ($this->mPrinter)) - $this->mPrinter = new $this->mFormats[API_DEFAULT_FORMAT] ($this, API_DEFAULT_FORMAT); + if (!isset ($this->mPrinter)) { + $this->mPrinter = $this->createPrinterByName(self :: API_DEFAULT_FORMAT); + if ($this->mPrinter->getNeedsRawData()) + $this->getResult()->setRawMode(); + } + + if ($e instanceof UsageException) { + // + // User entered incorrect parameters - print usage screen + // + $errMessage = array ( + 'code' => $e->getCodeString(), 'info' => $e->getMessage()); + ApiResult :: setContent($errMessage, $this->makeHelpMsg()); + + } else { + // + // Something is seriously wrong + // + $errMessage = array ( + 'code' => 'internal_api_error', + 'info' => "Exception Caught: {$e->getMessage()}" + ); + 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(); + } + + /** + * Execute the actual module, without any error handling + */ + protected function executeAction() { + $action = $format = $version = null; + extract($this->extractRequestParams()); + $this->mShowVersions = $version; + + // Instantiate the module requested by the user + $module = new $this->mModules[$action] ($this, $action); + + if (!$this->mInternalMode) { + + // See if custom printer is used + $this->mPrinter = $module->getCustomPrinter(); + if (is_null($this->mPrinter)) { + // Create an appropriate printer + $this->mPrinter = $this->createPrinterByName($format); + } + + if ($this->mPrinter->getNeedsRawData()) + $this->getResult()->setRawMode(); + } + + // Execute + $module->profileIn(); + $module->execute(); + $module->profileOut(); + + if (!$this->mInternalMode) { + // Print result data + $this->printResult(false); } - $this->profileOut(); } /** * Internal printer */ - private function printResult($isError) { + protected function printResult($isError) { $printer = $this->mPrinter; $printer->profileIn(); $printer->initPrinter($isError); - if (!$printer->getNeedsRawData()) - $this->getResult()->SanitizeData(); $printer->execute(); $printer->closePrinter(); $printer->profileOut(); } + protected function getAllowedParams() { + return array ( + 'format' => array ( + ApiBase :: PARAM_DFLT => ApiMain :: API_DEFAULT_FORMAT, + ApiBase :: PARAM_TYPE => $this->mFormatNames + ), + 'action' => array ( + ApiBase :: PARAM_DFLT => 'help', + ApiBase :: PARAM_TYPE => $this->mModuleNames + ), + 'version' => false + ); + } + + protected function getParamDescription() { + return array ( + 'format' => 'The format of the output', + 'action' => 'What action you would like to perform', + 'version' => 'When showing help, include version for each module' + ); + } + 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.', '' ); } - - public function mainDieUsage($description, $errorCode, $httpRespCode = 0) { - $this->mResult->Reset(); - if ($httpRespCode === 0) - header($errorCode, true); - else - header($errorCode, true, $httpRespCode); - - $data = array ( - 'code' => $errorCode, - 'info' => $description + + 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' ); - ApiResult :: setContent($data, $this->makeHelpMsg()); - $this->mResult->addValue(null, 'error', $data); - - throw new UsageException($description, $errorCode); } /** @@ -167,7 +296,7 @@ class ApiMain extends ApiBase { $astriks = str_repeat('*** ', 10); $msg .= "\n\n$astriks Modules $astriks\n\n"; - foreach ($this->mModules as $moduleName => $moduleClass) { + foreach( $this->mModules as $moduleName => $unused ) { $msg .= "* action=$moduleName *"; $module = new $this->mModules[$moduleName] ($this, $moduleName); $msg2 = $module->makeHelpMsg(); @@ -177,14 +306,17 @@ class ApiMain extends ApiBase { } $msg .= "\n$astriks Formats $astriks\n\n"; - foreach ($this->mFormats as $moduleName => $moduleClass) { - $msg .= "* format=$moduleName *"; - $module = new $this->mFormats[$moduleName] ($this, $moduleName); + foreach( $this->mFormats as $formatName => $unused ) { + $msg .= "* format=$formatName *"; + $module = $this->createPrinterByName($formatName); $msg2 = $module->makeHelpMsg(); if ($msg2 !== false) $msg .= $msg2; $msg .= "\n"; } + + $msg .= "\n*** Credits: ***\n " . implode("\n ", $this->getCredits()) . "\n"; + return $msg; } @@ -198,12 +330,17 @@ class ApiMain extends ApiBase { return $this->mIsBot; } + public function getShowVersions() { + return $this->mShowVersions; + } + public function getVersion() { $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiMain.php 16820 2006-10-06 01:02:14Z yurik $'; + $vers[] = __CLASS__ . ': $Id: ApiMain.php 17987 2006-11-29 05:45:03Z nickj $'; $vers[] = ApiBase :: getBaseVersion(); $vers[] = ApiFormatBase :: getBaseVersion(); $vers[] = ApiQueryBase :: getBaseVersion(); + $vers[] = ApiFormatFeedWrapper :: getVersion(); // not accessible with format=xxx return $vers; } } @@ -213,14 +350,17 @@ class ApiMain extends ApiBase { */ class UsageException extends Exception { - private $codestr; + private $mCodestr; - public function __construct($message, $codestr) { - parent :: __construct($message); - $this->codestr = $codestr; + public function __construct($message, $codestr, $code = 0) { + parent :: __construct($message, $code); + $this->mCodestr = $codestr; + } + public function getCodeString() { + return $this->mCodestr; } public function __toString() { - return "{$this->codestr}: {$this->message}"; + return "{$this->getCodeString()}: {$this->getMessage()}"; } } -?> \ No newline at end of file +?> diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php new file mode 100644 index 00000000..a5a13a7b --- /dev/null +++ b/includes/api/ApiOpenSearch.php @@ -0,0 +1,109 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ("ApiBase.php"); +} + +class ApiOpenSearch extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function getCustomPrinter() { + return $this->getMain()->createPrinterByName('json'); + } + + public function execute() { + $search = null; + extract($this->ExtractRequestParams()); + + // Open search results may be stored for a very long time + $this->getMain()->setCacheMaxAge(1200); + + $title = Title :: newFromText($search); + if(!$title) + return; // Return empty result + + // Prepare nested request + $params = new FauxRequest(array ( + 'action' => 'query', + 'list' => 'allpages', + 'apnamespace' => $title->getNamespace(), + 'aplimit' => 10, + 'apprefix' => $title->getDBkey() + )); + + // Execute + $module = new ApiMain($params); + $module->execute(); + + // Get resulting data + $data = $module->getResultData(); + + // Reformat useful data for future printing by JSON engine + $srchres = array (); + foreach ($data['query']['allpages'] as & $pageinfo) { + // Note: this data will no be printable by the xml engine + // because it does not support lists of unnamed items + $srchres[] = $pageinfo['title']; + } + + // Set top level elements + $result = $this->getResult(); + $result->addValue(null, 0, $search); + $result->addValue(null, 1, $srchres); + } + + protected function getAllowedParams() { + return array ( + 'search' => null + ); + } + + protected function getParamDescription() { + return array ( + 'search' => 'Search string' + ); + } + + protected function getDescription() { + return 'This module implements OpenSearch protocol'; + } + + protected function getExamples() { + return array ( + 'api.php?action=opensearch&search=Te' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiOpenSearch.php 17880 2006-11-23 08:25:56Z nickj $'; + } +} +?> \ No newline at end of file diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index d2384b39..4728a9f8 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -32,8 +32,9 @@ if (!defined('MEDIAWIKI')) { class ApiPageSet extends ApiQueryBase { private $mAllPages; // [ns][dbkey] => page_id or 0 when missing - private $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles; + private $mTitles, $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles; private $mResolveRedirects, $mPendingRedirectIDs; + private $mGoodRevIDs, $mMissingRevIDs; private $mRequestedPageFields; @@ -41,11 +42,14 @@ class ApiPageSet extends ApiQueryBase { parent :: __construct($query, __CLASS__); $this->mAllPages = array (); + $this->mTitles = array(); $this->mGoodTitles = array (); $this->mMissingTitles = array (); $this->mMissingPageIDs = array (); $this->mRedirectTitles = array (); $this->mNormalizedTitles = array (); + $this->mGoodRevIDs = array(); + $this->mMissingRevIDs = array(); $this->mRequestedPageFields = array (); $this->mResolveRedirects = $resolveRedirects; @@ -85,6 +89,21 @@ class ApiPageSet extends ApiQueryBase { return array_keys(array_merge($pageFlds, $this->mRequestedPageFields)); } + /** + * All Title objects provided. + * @return array of Title objects + */ + public function getTitles() { + return $this->mTitles; + } + + /** + * Returns the number of unique pages (not revisions) in the set. + */ + public function getTitleCount() { + return count($this->mTitles); + } + /** * Title objects that were found in the database. * @return array page_id (int) => Title (obj) @@ -94,10 +113,10 @@ class ApiPageSet extends ApiQueryBase { } /** - * Returns the number of unique pages (not revisions) in the set. + * Returns the number of found unique pages (not revisions) in the set. */ public function getGoodTitleCount() { - return count($this->getGoodTitles()); + return count($this->mGoodTitles); } /** @@ -135,16 +154,25 @@ class ApiPageSet extends ApiQueryBase { /** * Get the list of revision IDs (requested with revids= parameter) + * @return array revID (int) => pageID (int) */ public function getRevisionIDs() { - $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented'); + return $this->mGoodRevIDs; + } + + /** + * Revision IDs that were not found in the database + * @return array of revision IDs + */ + public function getMissingRevisionIDs() { + return $this->mMissingRevIDs; } /** * Returns the number of revisions (requested with revids= parameter) */ public function getRevisionCount() { - return 0; // TODO: implement + return count($this->getRevisionIDs()); } /** @@ -178,6 +206,8 @@ class ApiPageSet extends ApiQueryBase { $this->initFromPageIds($pageids); break; case 'revids' : + if($this->mResolveRedirects) + $this->dieUsage('revids may not be used with redirect resolution', 'params'); $this->initFromRevIDs($revids); break; default : @@ -215,24 +245,40 @@ class ApiPageSet extends ApiQueryBase { $this->profileOut(); } + /** + * Initialize PageSet from a list of Revision IDs + */ + public function populateFromRevisionIDs($revIDs) { + $this->profileIn(); + $revIDs = array_map('intval', $revIDs); // paranoia + $this->initFromRevIDs($revIDs); + $this->profileOut(); + } + /** * Extract all requested fields from the row received from the database */ public function processDbRow($row) { - $pageId = intval($row->page_id); - + // Store Title object in various data structures $title = Title :: makeTitle($row->page_namespace, $row->page_title); - $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId; + + // skip any pages that user has no rights to read + if ($title->userCanRead()) { - if ($this->mResolveRedirects && $row->page_is_redirect == '1') { - $this->mPendingRedirectIDs[$pageId] = $title; - } else { - $this->mGoodTitles[$pageId] = $title; + $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; } - - foreach ($this->mRequestedPageFields as $fieldName => & $fieldValues) - $fieldValues[$pageId] = $row-> $fieldName; } public function finishPageSetGeneration() { @@ -256,10 +302,13 @@ class ApiPageSet extends ApiQueryBase { * #6 Repeat from step #1 */ private function initFromTitles($titles) { - $db = $this->getDB(); // Get validated and normalized title objects $linkBatch = $this->processTitlesStrArray($titles); + if($linkBatch->isEmpty()) + return; + + $db = & $this->getDB(); $set = $linkBatch->constructSet('page', $db); // Get pageIDs data from the `page` table @@ -275,12 +324,15 @@ class ApiPageSet extends ApiQueryBase { } private function initFromPageIds($pageids) { - $db = $this->getDB(); - + if(empty($pageids)) + return; + $set = array ( 'page_id' => $pageids ); + $db = & $this->getDB(); + // Get pageIDs data from the `page` table $this->profileDBIn(); $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__); @@ -306,7 +358,7 @@ class ApiPageSet extends ApiQueryBase { */ private function initFromQueryResult($db, $res, &$remaining = null, $processTitles = null) { if (!is_null($remaining) && is_null($processTitles)) - $this->dieDebug('Missing $processTitles parameter when $remaining is provided'); + ApiBase :: dieDebug(__METHOD__, 'Missing $processTitles parameter when $remaining is provided'); while ($row = $db->fetchObject($res)) { @@ -330,9 +382,11 @@ class ApiPageSet extends ApiQueryBase { if($processTitles) { // The remaining titles in $remaining are non-existant pages foreach ($remaining as $ns => $dbkeys) { - foreach ($dbkeys as $dbkey => $nothing) { - $this->mMissingTitles[] = Title :: makeTitle($ns, $dbkey); + foreach ( $dbkeys as $dbkey => $unused ) { + $title = Title :: makeTitle($ns, $dbkey); + $this->mMissingTitles[] = $title; $this->mAllPages[$ns][$dbkey] = 0; + $this->mTitles[] = $title; } } } @@ -348,13 +402,43 @@ class ApiPageSet extends ApiQueryBase { } private function initFromRevIDs($revids) { - $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented'); + + if(empty($revids)) + return; + + $db = & $this->getDB(); + $pageids = array(); + $remaining = array_flip($revids); + + $tables = array('revision'); + $fields = array('rev_id','rev_page'); + $where = array('rev_deleted' => 0, 'rev_id' => $revids); + + // Get pageIDs data from the `page` table + $this->profileDBIn(); + $res = $db->select( $tables, $fields, $where, __METHOD__ ); + while ( $row = $db->fetchObject( $res ) ) { + $revid = intval($row->rev_id); + $pageid = intval($row->rev_page); + $this->mGoodRevIDs[$revid] = $pageid; + $pageids[$pageid] = ''; + unset($remaining[$revid]); + } + $db->freeResult( $res ); + $this->profileDBOut(); + + $this->mMissingRevIDs = array_keys($remaining); + + // Populate all the page information + if($this->mResolveRedirects) + ApiBase :: dieDebug(__METHOD__, 'revids may not be used with redirect resolution'); + $this->initFromPageIds(array_keys($pageids)); } private function resolvePendingRedirects() { if($this->mResolveRedirects) { - $db = $this->getDB(); + $db = & $this->getDB(); $pageFlds = $this->getPageTableFields(); // Repeat until all redirects have been resolved @@ -386,7 +470,7 @@ class ApiPageSet extends ApiQueryBase { private function getRedirectTargets() { $linkBatch = new LinkBatch(); - $db = $this->getDB(); + $db = & $this->getDB(); // find redirect targets for all redirect pages $this->profileDBIn(); @@ -443,7 +527,7 @@ class ApiPageSet extends ApiQueryBase { // All IDs must exist in the page table if (!empty($this->mPendingRedirectIDs[$plfrom])) - $this->dieDebug('Invalid redirect IDs were found'); + ApiBase :: dieDebug(__METHOD__, 'Invalid redirect IDs were found'); return $linkBatch; } @@ -508,7 +592,7 @@ class ApiPageSet extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPageSet.php 16820 2006-10-06 01:02:14Z yurik $'; + return __CLASS__ . ': $Id: ApiPageSet.php 17929 2006-11-25 17:11:58Z tstarling $'; } } -?> \ No newline at end of file +?> diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 985bde63..e7b7f351 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -45,15 +45,19 @@ class ApiQuery extends ApiBase { // 'templates' => 'ApiQueryTemplates', private $mQueryListModules = array ( - 'allpages' => 'ApiQueryAllpages' + 'allpages' => 'ApiQueryAllpages', + 'logevents' => 'ApiQueryLogEvents', + 'watchlist' => 'ApiQueryWatchlist', + 'recentchanges' => 'ApiQueryRecentChanges', + 'backlinks' => 'ApiQueryBacklinks', + 'embeddedin' => 'ApiQueryBacklinks', + 'imagelinks' => 'ApiQueryBacklinks', + 'usercontribs' => 'ApiQueryContributions' ); - // 'backlinks' => 'ApiQueryBacklinks', // 'categorymembers' => 'ApiQueryCategorymembers', // 'embeddedin' => 'ApiQueryEmbeddedin', // 'imagelinks' => 'ApiQueryImagelinks', - // 'logevents' => 'ApiQueryLogevents', // 'recentchanges' => 'ApiQueryRecentchanges', - // 'usercontribs' => 'ApiQueryUsercontribs', // 'users' => 'ApiQueryUsers', // 'watchlist' => 'ApiQueryWatchlist', @@ -75,9 +79,12 @@ class ApiQuery extends ApiBase { $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames); } - public function getDB() { - if (!isset ($this->mSlaveDB)) + public function & getDB() { + if (!isset ($this->mSlaveDB)) { + $this->profileDBIn(); $this->mSlaveDB = & wfGetDB(DB_SLAVE); + $this->profileDBOut(); + } return $this->mSlaveDB; } @@ -151,6 +158,7 @@ class ApiQuery extends ApiBase { private function outputGeneralPageInfo() { $pageSet = $this->getPageSet(); + $result = $this->getResult(); // Title normalizations $normValues = array (); @@ -162,8 +170,8 @@ class ApiQuery extends ApiBase { } if (!empty ($normValues)) { - ApiResult :: setIndexedTagName($normValues, 'n'); - $this->getResult()->addValue('query', 'normalized', $normValues); + $result->setIndexedTagName($normValues, 'n'); + $result->addValue('query', 'normalized', $normValues); } // Show redirect information @@ -176,8 +184,23 @@ class ApiQuery extends ApiBase { } if (!empty ($redirValues)) { - ApiResult :: setIndexedTagName($redirValues, 'r'); - $this->getResult()->addValue('query', 'redirects', $redirValues); + $result->setIndexedTagName($redirValues, 'r'); + $result->addValue('query', 'redirects', $redirValues); + } + + // + // Missing revision elements + // + $missingRevIDs = $pageSet->getMissingRevisionIDs(); + if (!empty ($missingRevIDs)) { + $revids = array (); + foreach ($missingRevIDs as $revid) { + $revids[$revid] = array ( + 'revid' => $revid + ); + } + $result->setIndexedTagName($revids, 'rev'); + $result->addValue('query', 'badrevids', $revids); } // @@ -195,7 +218,7 @@ class ApiQuery extends ApiBase { // Report any missing page ids foreach ($pageSet->getMissingPageIDs() as $pageid) { $pages[$pageid] = array ( - 'id' => $pageid, + 'pageid' => $pageid, 'missing' => '' ); } @@ -203,12 +226,13 @@ class ApiQuery extends ApiBase { // Output general page information for found titles foreach ($pageSet->getGoodTitles() as $pageid => $title) { $pages[$pageid] = array ( - 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'id' => $pageid); + 'pageid' => $pageid, + 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText()); } if (!empty ($pages)) { - ApiResult :: setIndexedTagName($pages, 'page'); - $this->getResult()->addValue('query', 'pages', $pages); + $result->setIndexedTagName($pages, 'page'); + $result->addValue('query', 'pages', $pages); } } @@ -238,13 +262,13 @@ class ApiQuery extends ApiBase { // execute current pageSet to get the data for the generator module $this->mPageSet->execute(); - + // populate resultPageSet with the generator output $generator->profileIn(); $generator->executeGenerator($resultPageSet); $resultPageSet->finishPageSetGeneration(); $generator->profileOut(); - + // Swap the resulting pageset back in $this->mPageSet = $resultPageSet; } @@ -346,7 +370,7 @@ class ApiQuery extends ApiBase { public function getVersion() { $psModule = new ApiPageSet($this); $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiQuery.php 16820 2006-10-06 01:02:14Z yurik $'; + $vers[] = __CLASS__ . ': $Id: ApiQuery.php 17374 2006-11-03 06:53:47Z yurik $'; $vers[] = $psModule->getVersion(); return $vers; } diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index 51330d62..9c076e65 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -42,95 +42,84 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { public function executeGenerator($resultPageSet) { if ($resultPageSet->isResolvingRedirects()) $this->dieUsage('Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params'); - + $this->run($resultPageSet); } private function run($resultPageSet = null) { - $limit = $from = $namespace = $filterredir = null; - extract($this->extractRequestParams()); - $db = $this->getDB(); + wfProfileIn($this->getModuleProfileName() . '-getDB'); + $db = & $this->getDB(); + wfProfileOut($this->getModuleProfileName() . '-getDB'); - $where = array ( - 'page_namespace' => $namespace - ); - - if (isset ($from)) { - $where[] = 'page_title>=' . $db->addQuotes(ApiQueryBase :: titleToKey($from)); - } - - if ($filterredir === 'redirects') { - $where['page_is_redirect'] = 1; - } - elseif ($filterredir === 'nonredirects') { - $where['page_is_redirect'] = 0; - } + wfProfileIn($this->getModuleProfileName() . '-parseParams'); + $limit = $from = $namespace = $filterredir = $prefix = null; + extract($this->extractRequestParams()); + + $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 (is_null($resultPageSet)) { - $fields = array ( + $this->addFields(array ( 'page_id', 'page_namespace', 'page_title' - ); + )); } else { - $fields = $resultPageSet->getPageTableFields(); + $this->addFields($resultPageSet->getPageTableFields()); } - $this->profileDBIn(); - $res = $db->select('page', $fields, $where, __CLASS__ . '::' . __METHOD__, array ( - 'USE INDEX' => 'name_title', - 'LIMIT' => $limit +1, - 'ORDER BY' => 'page_namespace, page_title' - )); - $this->profileDBOut(); + $this->addOption('USE INDEX', 'name_title'); + $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... - $msg = array ( - 'continue' => $this->encodeParamName('from' - ) . '=' . ApiQueryBase :: keyToTitle($row->page_title)); - $this->getResult()->addValue('query-status', 'allpages', $msg); + $this->setContinueEnumParameter('from', ApiQueryBase :: keyToTitle($row->page_title)); break; } - $title = Title :: makeTitle($row->page_namespace, $row->page_title); - // skip any pages that user has no rights to read - if ($title->userCanRead()) { - - if (is_null($resultPageSet)) { - $id = intval($row->page_id); - $data[] = $id; // in generator mode, just assemble a list of page IDs. - } else { - $resultPageSet->processDbRow($row); - } + if (is_null($resultPageSet)) { + $vals = $this->addRowInfo('page', $row); + if ($vals) + $data[intval($row->page_id)] = $vals; + } else { + $resultPageSet->processDbRow($row); } } $db->freeResult($res); if (is_null($resultPageSet)) { - ApiResult :: setIndexedTagName($data, 'p'); - $this->getResult()->addValue('query', 'allpages', $data); + $result = $this->getResult(); + $result->setIndexedTagName($data, 'p'); + $result->addValue('query', $this->getModuleName(), $data); } + + wfProfileOut($this->getModuleProfileName() . '-saveResults'); } protected function getAllowedParams() { - - global $wgContLang; - $validNamespaces = array (); - foreach (array_keys($wgContLang->getNamespaces()) as $ns) { - if ($ns >= 0) - $validNamespaces[] = $ns; // strval($ns); - } - return array ( 'from' => null, + 'prefix' => null, 'namespace' => array ( ApiBase :: PARAM_DFLT => 0, - ApiBase :: PARAM_TYPE => $validNamespaces + ApiBase :: PARAM_TYPE => 'namespace' ), 'filterredir' => array ( ApiBase :: PARAM_DFLT => 'all', @@ -144,8 +133,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, - ApiBase :: PARAM_MAX1 => 500, - ApiBase :: PARAM_MAX2 => 5000 + ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ) ); } @@ -153,9 +142,10 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { protected function getParamDescription() { return array ( 'from' => 'The page title to start enumerating from.', - 'namespace' => 'The namespace to enumerate. Default 0 (Main).', - 'filterredir' => 'Which pages to list: "all" (default), "redirects", or "nonredirects"', - 'limit' => 'How many total pages to return' + 'prefix' => 'Search for all page titles that begin with this value.', + 'namespace' => 'The namespace to enumerate.', + 'filterredir' => 'Which pages to list.', + 'limit' => 'How many total pages to return.' ); } @@ -166,8 +156,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { protected function getExamples() { return array ( 'Simple Use', - ' api.php?action=query&list=allpages', - ' api.php?action=query&list=allpages&apfrom=B&aplimit=5', + ' Show a list of pages starting at the letter "B"', + ' api.php?action=query&list=allpages&apfrom=B', 'Using as Generator', ' Show info about 4 pages starting at the letter "T"', ' api.php?action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info', @@ -177,7 +167,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllpages.php 16820 2006-10-06 01:02:14Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryAllpages.php 17880 2006-11-23 08:25:56Z nickj $'; } } -?> \ No newline at end of file +?> diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php new file mode 100644 index 00000000..413068f8 --- /dev/null +++ b/includes/api/ApiQueryBacklinks.php @@ -0,0 +1,358 @@ + + * + * 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"); +} + +class ApiQueryBacklinks extends ApiQueryGeneratorBase { + + private $rootTitle, $contRedirs, $contLevel, $contTitle, $contID; + + // output element name, database column field prefix, database table + private $backlinksSettings = array ( + 'backlinks' => array ( + 'code' => 'bl', + 'prefix' => 'pl', + 'linktbl' => 'pagelinks' + ), + 'embeddedin' => array ( + 'code' => 'ei', + 'prefix' => 'tl', + 'linktbl' => 'templatelinks' + ), + 'imagelinks' => array ( + 'code' => 'il', + 'prefix' => 'il', + 'linktbl' => 'imagelinks' + ) + ); + + public function __construct($query, $moduleName) { + $code = $prefix = $linktbl = null; + extract($this->backlinksSettings[$moduleName]); + + parent :: __construct($query, $moduleName, $code); + $this->bl_ns = $prefix . '_namespace'; + $this->bl_from = $prefix . '_from'; + $this->bl_tables = array ( + $linktbl, + 'page' + ); + $this->bl_code = $code; + + $this->hasNS = $moduleName !== 'imagelinks'; + if ($this->hasNS) { + $this->bl_title = $prefix . '_title'; + $this->bl_sort = "{$this->bl_ns}, {$this->bl_title}, {$this->bl_from}"; + $this->bl_fields = array ( + $this->bl_ns, + $this->bl_title + ); + } else { + $this->bl_title = $prefix . '_to'; + $this->bl_sort = "{$this->bl_title}, {$this->bl_from}"; + $this->bl_fields = array ( + $this->bl_title + ); + } + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + $continue = $namespace = $redirect = $limit = null; + extract($this->extractRequestParams()); + + if ($redirect) + ApiBase :: dieDebug(__METHOD__, 'Redirect is not yet been implemented', 'notimplemented'); + + $this->processContinue($continue, $redirect); + + $this->addFields($this->bl_fields); + if (is_null($resultPageSet)) + $this->addFields(array ( + 'page_id', + 'page_namespace', + 'page_title' + )); + else + $this->addFields($resultPageSet->getPageTableFields()); // will include page_id + + $this->addTables($this->bl_tables); + $this->addWhere($this->bl_from . '=page_id'); + + 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); + + if ($redirect) + $this->addWhereFld('page_is_redirect', 0); + + $db = & $this->getDB(); + if (!is_null($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 + $this->addWhere($this->bl_from . '>=' . $plfrm); + } else { + $ns = $this->contTitle->getNamespace(); + $t = $db->addQuotes($this->contTitle->getDBkey()); + $whereWithoutNS = "{$this->bl_title}>$t OR ({$this->bl_title}=$t AND {$this->bl_from}>=$plfrm))"; + + if ($this->hasNS) + $this->addWhere("{$this->bl_ns}>$ns OR ({$this->bl_ns}=$ns AND ($whereWithoutNS)"); + else + $this->addWhere($whereWithoutNS); + } + } + + $res = $this->select(__METHOD__); + + $count = 0; + $data = array (); + 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... + if ($redirect) { + $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); + $this->setContinueEnumParameter('continue', $continue); + break; + } + + if (is_null($resultPageSet)) { + $vals = $this->addRowInfo('page', $row); + if ($vals) + $data[intval($row->page_id)] = $vals; + } else { + $resultPageSet->processDbRow($row); + } + } + $db->freeResult($res); + + if (is_null($resultPageSet)) { + $result = $this->getResult(); + $result->setIndexedTagName($data, $this->bl_code); + $result->addValue('query', $this->getModuleName(), $data); + } + } + + protected function processContinue($continue, $redirect) { + $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); + + // 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 + } + + // only image titles are allowed for the root + if (!$this->hasNS && $this->rootTitle->getNamespace() !== NS_IMAGE) + $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) { + // + // expected redirect-mode parameter: + // ns|db_key|step|level|ns|db_key|id + // ns+db_key -- the root title + // step = 1 or 2 - which step to continue from - 1-titles, 2-redirects + // level -- how many levels to follow before starting enumerating. + // if level > 0 -- ns+title to continue from, otherwise skip these + // id = last page_id to continue from + // + if (count($continueList) > 4) { + $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()) { + + $step = intval($continueList[2]); + if ($step === 1 || $step === 2) { + $this->contRedirs = ($step === 2); + + $level = intval($continueList[3]); + if ($level !== 0 || $continueList[3] === '0') { + $this->contLevel = $level; + + if ($level === 0) { + if (count($continueList) === 5) { + $contID = intval($continueList[4]); + if ($contID !== 0 || $continueList[4] === '0') { + $this->contID = $contID; + return; // done + } + } + } else { + if (count($continueList) === 7) { + $contNs = intval($continueList[4]); + if (($contNs !== 0 || $continueList[4] === '0') && !empty ($continueList[5])) { + $this->contTitle = Title :: makeTitleSafe($contNs, $continueList[5]); + + $contID = intval($continueList[6]); + if ($contID !== 0 || $continueList[6] === '0') { + $this->contID = $contID; + return; // done + } + } + } + } + } + } + } + } + } + } else { + // + // expected non-redirect-mode parameter: + // ns|db_key|id + // ns+db_key -- the root title + // id = last page_id to continue from + // + if (count($continueList) === 3) { + $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()) { + + $contID = intval($continueList[2]); + if ($contID !== 0) { + $this->contID = $contID; + return; // done + } + } + } + } + } + + $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue"); + } + + protected function getContinueStr($lastPageID) { + return $this->rootTitle->getNamespace() . + '|' . $this->rootTitle->getDBkey() . + '|' . $lastPageID; + } + + protected function getContinueRedirStr($isRedirPhase, $level, $ns, $title, $lastPageID) { + return $this->rootTitle->getNamespace() . + '|' . $this->rootTitle->getDBkey() . + '|' . ($isRedirPhase ? 1 : 2) . + '|' . $level . + ($level > 0 ? ('|' . $ns . '|' . $title) : '') . + '|' . $lastPageID; + } + + protected function getAllowedParams() { + + return array ( + 'continue' => null, + 'namespace' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => 'namespace' + ), + 'redirect' => false, + 'limit' => array ( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ) + ); + } + + protected function getParamDescription() { + return array ( + 'continue' => 'When more results are available, use this to continue.', + 'namespace' => 'The namespace to enumerate.', + 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect (not implemented)', + 'limit' => 'How many total pages to return.' + ); + } + + protected function getDescription() { + switch ($this->getModuleName()) { + case 'backlinks' : + return 'Find all pages that link to the given page'; + case 'embeddedin' : + return 'Find all pages that embed (transclude) the given title'; + case 'imagelinks' : + return 'Find all pages that use the given image title.'; + default : + ApiBase :: dieDebug(__METHOD__, 'Unknown module name'); + } + } + + 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" + ), + 'embeddedin' => array ( + "api.php?action=query&list=embeddedin&titles=Template:Stub", + "api.php?action=query&generator=embeddedin&titles=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" + ) + ); + + return $examples[$this->getModuleName()]; + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryBacklinks.php 17880 2006-11-23 08:25:56Z nickj $'; + } +} +?> \ No newline at end of file diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index 574f742e..ae4edf98 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -31,11 +31,260 @@ if (!defined('MEDIAWIKI')) { abstract class ApiQueryBase extends ApiBase { - private $mQueryModule; - + private $mQueryModule, $tables, $where, $fields, $options; + public function __construct($query, $moduleName, $paramPrefix = '') { parent :: __construct($query->getMain(), $moduleName, $paramPrefix); $this->mQueryModule = $query; + $this->resetQueryParams(); + } + + protected function resetQueryParams() { + $this->tables = array (); + $this->where = array (); + $this->fields = array (); + $this->options = array (); + } + + protected function addTables($value) { + if (is_array($value)) + $this->tables = array_merge($this->tables, $value); + else + $this->tables[] = $value; + } + + protected function addFields($value) { + if (is_array($value)) + $this->fields = array_merge($this->fields, $value); + else + $this->fields[] = $value; + } + + protected function addFieldsIf($value, $condition) { + if ($condition) { + $this->addFields($value); + return true; + } + return false; + } + + protected function addWhere($value) { + if (is_array($value)) + $this->where = array_merge($this->where, $value); + else + $this->where[] = $value; + } + + protected function addWhereIf($value, $condition) { + if ($condition) { + $this->addWhere($value); + return true; + } + return false; + } + + protected function addWhereFld($field, $value) { + if (!is_null($value)) + $this->where[$field] = $value; + } + + protected function addWhereRange($field, $dir, $start, $end) { + $isDirNewer = ($dir === 'newer'); + $after = ($isDirNewer ? '>=' : '<='); + $before = ($isDirNewer ? '<=' : '>='); + $db = $this->getDB(); + + if (!is_null($start)) + $this->addWhere($field . $after . $db->addQuotes($start)); + + if (!is_null($end)) + $this->addWhere($field . $before . $db->addQuotes($end)); + + $this->addOption('ORDER BY', $field . ($isDirNewer ? '' : ' DESC')); + } + + protected function addOption($name, $value = null) { + if (is_null($value)) + $this->options[] = $name; + else + $this->options[$name] = $value; + } + + protected function select($method) { + + // getDB has its own profileDBIn/Out calls + $db = $this->getDB(); + + $this->profileDBIn(); + $res = $db->select($this->tables, $this->fields, $this->where, $method, $this->options); + $this->profileDBOut(); + + 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; } /** @@ -46,12 +295,19 @@ abstract class ApiQueryBase extends ApiBase { } /** - * Get the main Query module + * Get the main Query module */ public function getQuery() { return $this->mQueryModule; } + protected function setContinueEnumParameter($paramName, $paramValue) { + $msg = array ( + $this->encodeParamName($paramName + ) => $paramValue); + $this->getResult()->addValue('query-continue', $this->getModuleName(), $msg); + } + /** * Get the Query database connection (readonly) */ @@ -67,6 +323,11 @@ abstract class ApiQueryBase extends ApiBase { return $this->mQueryModule->getPageSet(); } + /** + * This is a very simplistic utility function + * to convert a non-namespaced title string to a db key. + * It will replace all ' ' with '_' + */ public static function titleToKey($title) { return str_replace(' ', '_', $title); } @@ -76,7 +337,7 @@ abstract class ApiQueryBase extends ApiBase { } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiQueryBase.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryBase.php 17987 2006-11-29 05:45:03Z nickj $'; } } @@ -86,7 +347,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase { public function __construct($query, $moduleName, $paramPrefix = '') { parent :: __construct($query, $moduleName, $paramPrefix); - $mIsGenerator = false; + $this->mIsGenerator = false; } public function setGeneratorMode() { @@ -109,4 +370,4 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase { */ public abstract function executeGenerator($resultPageSet); } -?> \ No newline at end of file +?> diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index de651b00..d93d37a2 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -46,14 +46,17 @@ class ApiQueryInfo extends ApiQueryBase { $pageSet = $this->getPageSet(); $titles = $pageSet->getGoodTitles(); - $result = & $this->getResult(); + $result = $this->getResult(); $pageIsRedir = $pageSet->getCustomField('page_is_redirect'); $pageTouched = $pageSet->getCustomField('page_touched'); $pageLatest = $pageSet->getCustomField('page_latest'); - foreach ($titles as $pageid => $title) { - $pageInfo = array ('touched' => $pageTouched[$pageid], 'lastrevid' => $pageLatest[$pageid]); + foreach ( $titles as $pageid => $unused ) { + $pageInfo = array ( + 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]), + 'lastrevid' => intval($pageLatest[$pageid]) + ); if ($pageIsRedir[$pageid]) $pageInfo['redirect'] = ''; @@ -76,7 +79,7 @@ class ApiQueryInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryInfo.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryInfo.php 17929 2006-11-25 17:11:58Z tstarling $'; } } -?> \ No newline at end of file +?> diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php new file mode 100644 index 00000000..243f96fa --- /dev/null +++ b/includes/api/ApiQueryLogEvents.php @@ -0,0 +1,173 @@ + + * + * 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'); +} + +class ApiQueryLogEvents extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'le'); + } + + public function execute() { + $limit = $type = $start = $end = $dir = $user = $title = null; + extract($this->extractRequestParams()); + + $db = & $this->getDB(); + + list($tbl_logging, $tbl_page, $tbl_user) = $db->tableNamesN('logging', 'page', 'user'); + + $this->addOption('STRAIGHT_JOIN'); + $this->addTables("$tbl_logging LEFT OUTER JOIN $tbl_page ON " . + "log_namespace=page_namespace AND log_title=page_title " . + "INNER JOIN $tbl_user ON user_id=log_user"); + + $this->addFields(array ( + 'log_type', + 'log_action', + 'log_timestamp', + 'log_user', + 'user_name', + 'log_namespace', + 'log_title', + 'page_id', + 'log_comment', + 'log_params' + )); + + $this->addWhereFld('log_type', $type); + $this->addWhereRange('log_timestamp', $dir, $start, $end); + $this->addOption('LIMIT', $limit +1); + + if (!is_null($user)) { + $userid = $db->selectField('user', 'user_id', array ( + 'user_name' => $user + )); + if (!$userid) + $this->dieUsage("User name $user not found", 'param_user'); + $this->addWhereFld('log_user', $userid); + } + + if (!is_null($title)) { + $titleObj = Title :: newFromText($title); + if (is_null($titleObj)) + $this->dieUsage("Bad title value '$title'", 'param_title'); + $this->addWhereFld('log_namespace', $titleObj->getNamespace()); + $this->addWhereFld('log_title', $titleObj->getDBkey()); + } + + $data = array (); + $count = 0; + $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->log_timestamp); + break; + } + + $vals = $this->addRowInfo('log', $row); + if($vals) + $data[] = $vals; + } + $db->freeResult($res); + + $this->getResult()->setIndexedTagName($data, 'item'); + $this->getResult()->addValue('query', $this->getModuleName(), $data); + } + + protected function getAllowedParams() { + return array ( + 'type' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'block', + 'protect', + 'rights', + 'delete', + 'upload', + 'move', + 'import', + 'renameuser', + 'newusers', + 'makebot' + ) + ), + 'start' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'dir' => array ( + ApiBase :: PARAM_DFLT => 'older', + ApiBase :: PARAM_TYPE => array ( + 'newer', + 'older' + ) + ), + 'user' => null, + 'title' => null, + 'limit' => array ( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ) + ); + } + + protected function getParamDescription() { + return array ( + 'type' => 'Filter log entries to only this type(s)', + 'start' => 'The timestamp to start enumerating from.', + 'end' => 'The timestamp to end enumerating.', + 'dir' => 'In which direction to enumerate.', + 'user' => 'Filter entries to those made by the given user.', + 'title' => 'Filter entries to those related to a page.', + 'limit' => 'How many total event entries to return.' + ); + } + + protected function getDescription() { + return 'Get events from logs.'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=logevents' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryLogEvents.php 17952 2006-11-27 08:36:57Z nickj $'; + } +} +?> diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php new file mode 100644 index 00000000..38f51b05 --- /dev/null +++ b/includes/api/ApiQueryRecentChanges.php @@ -0,0 +1,187 @@ + + * + * 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'); +} + +class ApiQueryRecentChanges extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'rc'); + } + + public function execute() { + $limit = $prop = $namespace = $show = $dir = $start = $end = null; + extract($this->extractRequestParams()); + + $this->addTables('recentchanges'); + $this->addWhereRange('rc_timestamp', $dir, $start, $end); + $this->addWhereFld('rc_namespace', $namespace); + + if (!is_null($show)) { + $show = array_flip($show); + if ((isset ($show['minor']) && isset ($show['!minor'])) || (isset ($show['bot']) && isset ($show['!bot'])) || (isset ($show['anon']) && isset ($show['!anon']))) + $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); + + $this->addWhereIf('rc_minor = 0', isset ($show['!minor'])); + $this->addWhereIf('rc_minor != 0', isset ($show['minor'])); + $this->addWhereIf('rc_bot = 0', isset ($show['!bot'])); + $this->addWhereIf('rc_bot != 0', isset ($show['bot'])); + $this->addWhereIf('rc_user = 0', isset ($show['anon'])); + $this->addWhereIf('rc_user != 0', isset ($show['!anon'])); + } + + $this->addFields(array ( + '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' + )); + + 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->addOption('LIMIT', $limit +1); + $this->addOption('USE INDEX', 'rc_timestamp'); + + $data = array (); + $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); + break; + } + + $vals = $this->addRowInfo('rc', $row); + if ($vals) + $data[] = $vals; + } + $db->freeResult($res); + + $result = $this->getResult(); + $result->setIndexedTagName($data, 'rc'); + $result->addValue('query', $this->getModuleName(), $data); + } + + protected function getAllowedParams() { + return array ( + 'start' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + '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_TYPE => array ( + 'user', + 'comment', + 'flags' + ) + ), + 'show' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'minor', + '!minor', + 'bot', + '!bot', + 'anon', + '!anon' + ) + ), + 'limit' => array ( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ) + ); + } + + protected function getParamDescription() { + return array ( + 'start' => 'The timestamp to start enumerating from.', + 'end' => 'The timestamp to end enumerating.', + 'dir' => 'In which direction to enumerate.', + 'namespace' => 'Filter log entries to only this namespace(s)', + 'prop' => 'Include additional pieces of information', + 'show' => array ( + 'Show only items that meet this criteria.', + 'For example, to see only minor edits done by logged-in users, set show=minor|!anon' + ), + 'limit' => 'How many total pages to return.' + ); + } + + protected function getDescription() { + return 'Enumerate recent changes'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=recentchanges' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 17880 2006-11-23 08:25:56Z nickj $'; + } +} +?> \ No newline at end of file diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index f6097bad..3f678ff7 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -39,16 +39,11 @@ class ApiQueryRevisions extends ApiQueryBase { $limit = $startid = $endid = $start = $end = $dir = $prop = null; extract($this->extractRequestParams()); - $db = $this->getDB(); - - // true when ordered by timestamp from older to newer, false otherwise - $dirNewer = ($dir === 'newer'); - // 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 = ($limit !== 0 || $startid !== 0 || $endid !== 0 || $dirNewer || isset ($start) || isset ($end)); + $enumRevMode = (!is_null($limit) || !is_null($startid) || !is_null($endid) || $dir === 'newer' || !is_null($start) || !is_null($end)); $pageSet = $this->getPageSet(); $pageCount = $pageSet->getGoodTitleCount(); @@ -58,57 +53,38 @@ class ApiQueryRevisions extends ApiQueryBase { if ($revCount === 0 && $pageCount === 0) return; - if ($revCount > 0 && $pageCount > 0) - $this->dieUsage('The revids= parameter may not be used with titles, pageids, or generator options.', 'revids'); - if ($revCount > 0 && $enumRevMode) $this->dieUsage('The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids'); - if ($revCount === 0 && $pageCount > 1 && $enumRevMode) + 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'); - $tables = array ( - 'revision' - ); - $fields = array ( + $this->addTables('revision'); + $this->addFields(array ( 'rev_id', 'rev_page', 'rev_text_id', 'rev_minor_edit' - ); - $conds = array ( - 'rev_deleted' => 0 - ); - $options = array (); - - $showTimestamp = $showUser = $showComment = $showContent = false; - if (isset ($prop)) { - foreach ($prop as $p) { - switch ($p) { - case 'timestamp' : - $fields[] = 'rev_timestamp'; - $showTimestamp = true; - break; - case 'user' : - $fields[] = 'rev_user'; - $fields[] = 'rev_user_text'; - $showUser = true; - break; - case 'comment' : - $fields[] = 'rev_comment'; - $showComment = true; - break; - case 'content' : - $tables[] = 'text'; - $conds[] = 'rev_text_id=old_id'; - $fields[] = 'old_id'; - $fields[] = 'old_text'; - $fields[] = 'old_flags'; - $showContent = true; - break; - default : - ApiBase :: dieDebug(__METHOD__, "unknown prop $p"); - } + )); + $this->addWhere('rev_deleted=0'); + + $showContent = false; + + 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; } } @@ -118,10 +94,10 @@ class ApiQueryRevisions extends ApiQueryBase { if ($enumRevMode) { // This is mostly to prevent parameter errors (and optimize sql?) - if ($startid !== 0 && isset ($start)) + if (!is_null($startid) && !is_null($start)) $this->dieUsage('start and startid cannot be used together', 'badparams'); - if ($endid !== 0 && isset ($end)) + if (!is_null($endid) && !is_null($end)) $this->dieUsage('end and endid cannot be used together', 'badparams'); // This code makes an assumption that sorting by rev_id and rev_timestamp produces @@ -130,40 +106,30 @@ class ApiQueryRevisions extends ApiQueryBase { // Switching to rev_id removes the potential problem of having more than // one row with the same timestamp for the same page. // The order needs to be the same as start parameter to avoid SQL filesort. - $options['ORDER BY'] = ($startid !== 0 ? 'rev_id' : 'rev_timestamp') . ($dirNewer ? '' : ' DESC'); - - $before = ($dirNewer ? '<=' : '>='); - $after = ($dirNewer ? '>=' : '<='); - if ($startid !== 0) - $conds[] = 'rev_id' . $after . intval($startid); - if ($endid !== 0) - $conds[] = 'rev_id' . $before . intval($endid); - if (isset ($start)) - $conds[] = 'rev_timestamp' . $after . $db->addQuotes($start); - if (isset ($end)) - $conds[] = 'rev_timestamp' . $before . $db->addQuotes($end); + if (is_null($startid)) + $this->addWhereRange('rev_timestamp', $dir, $start, $end); + else + $this->addWhereRange('rev_id', $dir, $startid, $endid); // must manually initialize unset limit - if (!isset ($limit)) + if (is_null($limit)) $limit = 10; - $this->validateLimit($this->encodeParamName('limit'), $limit, 1, $userMax, $botMax); // There is only one ID, use it - $conds['rev_page'] = array_pop(array_keys($pageSet->getGoodTitles())); - + $this->addWhereFld('rev_page', current(array_keys($pageSet->getGoodTitles()))); } elseif ($pageCount > 0) { // When working in multi-page non-enumeration mode, // limit to the latest revision only - $tables[] = 'page'; - $conds[] = 'page_id=rev_page'; - $conds[] = 'page_latest=rev_id'; + $this->addTables('page'); + $this->addWhere('page_id=rev_page'); + $this->addWhere('page_latest=rev_id'); $this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax); // Get all page IDs - $conds['page_id'] = array_keys($pageSet->getGoodTitles()); + $this->addWhereFld('page_id', array_keys($pageSet->getGoodTitles())); $limit = $pageCount; // assumption testing -- we should never get more then $pageCount rows. } @@ -171,72 +137,51 @@ class ApiQueryRevisions extends ApiQueryBase { $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax); // Get all revision IDs - $conds['rev_id'] = array_keys($pageSet->getRevisionIDs()); + $this->addWhereFld('rev_id', array_keys($pageSet->getRevisionIDs())); $limit = $revCount; // assumption testing -- we should never get more then $revCount rows. } else ApiBase :: dieDebug(__METHOD__, 'param validation?'); - $options['LIMIT'] = $limit +1; - - $this->profileDBIn(); - $res = $db->select($tables, $fields, $conds, __METHOD__, $options); - $this->profileDBOut(); + $this->addOption('LIMIT', $limit +1); $data = array (); $count = 0; + $res = $this->select(__METHOD__); + + $db = & $this->getDB(); 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... if (!$enumRevMode) ApiBase :: dieDebug(__METHOD__, 'Got more rows then expected'); // bug report - - $startStr = 'startid=' . $row->rev_id; - $msg = array ( - 'continue' => $startStr - ); - $this->getResult()->addValue('query-status', 'revisions', $msg); + $this->setContinueEnumParameter('startid', $row->rev_id); break; } - $vals = array ( - 'revid' => intval($row->rev_id - ), 'oldid' => intval($row->rev_text_id)); - - if ($row->rev_minor_edit) { - $vals['minor'] = ''; - } - - if ($showTimestamp) - $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp); - - if ($showUser) { - $vals['user'] = $row->rev_user_text; - if (!$row->rev_user) - $vals['anon'] = ''; - } - - if ($showComment) - $vals['comment'] = $row->rev_comment; + $vals = $this->addRowInfo('rev', $row); + if ($vals) { + if ($showContent) + ApiResult :: setContent($vals, Revision :: getRevisionText($row)); - if ($showContent) { - ApiResult :: setContent($vals, Revision :: getRevisionText($row)); + $this->getResult()->addValue(array ( + 'query', + 'pages', + intval($row->rev_page + ), 'revisions'), intval($row->rev_id), $vals); } - - $this->getResult()->addValue(array ( - 'query', - 'pages', - intval($row->rev_page - ), 'revisions'), intval($row->rev_id), $vals); } $db->freeResult($res); - // Ensure that all revisions are shown as '' elements - $data = & $this->getResultData(); - foreach ($data['query']['pages'] as & $page) { - if (is_array($page) && array_key_exists('revisions', $page)) { - ApiResult :: setIndexedTagName($page['revisions'], 'rev'); + // Ensure that all revisions are shown as '' elements + $result = $this->getResult(); + if ($result->getIsRawMode()) { + $data = $result->getData(); + foreach ($data['query']['pages'] as & $page) { + if (is_array($page) && array_key_exists('revisions', $page)) { + $result->setIndexedTagName($page['revisions'], 'rev'); + } } } } @@ -253,14 +198,17 @@ class ApiQueryRevisions extends ApiQueryBase { ) ), 'limit' => array ( - ApiBase :: PARAM_DFLT => 0, ApiBase :: PARAM_TYPE => 'limit', - ApiBase :: PARAM_MIN => 0, - ApiBase :: PARAM_MAX1 => 50, - ApiBase :: PARAM_MAX2 => 500 + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_SML1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_SML2 + ), + 'startid' => array ( + ApiBase :: PARAM_TYPE => 'integer' + ), + 'endid' => array ( + ApiBase :: PARAM_TYPE => 'integer' ), - 'startid' => 0, - 'endid' => 0, 'start' => array ( ApiBase :: PARAM_TYPE => 'timestamp' ), @@ -279,7 +227,7 @@ class ApiQueryRevisions extends ApiQueryBase { protected function getParamDescription() { return array ( - 'prop' => 'Which properties to get for each revision: user|timestamp|comment|content', + 'prop' => 'Which properties to get for each revision.', 'limit' => 'limit how many revisions will be returned (enum)', 'startid' => 'from which revision id to start enumeration (enum)', 'endid' => 'stop revision enumeration on this revid (enum)', @@ -314,7 +262,7 @@ class ApiQueryRevisions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRevisions.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiQueryRevisions.php 17374 2006-11-03 06:53:47Z yurik $'; } } -?> \ No newline at end of file +?> diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 27c3f187..9e8c11ff 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -44,7 +44,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { case 'general' : - global $wgSitename, $wgVersion, $wgCapitalLinks; + global $wgSitename, $wgVersion, $wgCapitalLinks, $wgRightsCode, $wgRightsText; $data = array (); $mainPage = Title :: newFromText(wfMsgForContent('mainpage')); $data['mainpage'] = $mainPage->getText(); @@ -52,6 +52,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { $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); break; @@ -65,10 +68,10 @@ class ApiQuerySiteinfo extends ApiQueryBase { ); ApiResult :: setContent($data[$ns], $title); } - ApiResult :: setIndexedTagName($data, 'ns'); + $this->getResult()->setIndexedTagName($data, 'ns'); $this->getResult()->addValue('query', $p, $data); break; - + default : ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p"); } @@ -107,7 +110,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 17265 2006-10-27 03:50:34Z yurik $'; } } ?> \ No newline at end of file diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php new file mode 100644 index 00000000..4f63cadb --- /dev/null +++ b/includes/api/ApiQueryUserContributions.php @@ -0,0 +1,175 @@ + + * + * 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'); +} + +class ApiQueryContributions extends ApiQueryBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'uc'); + } + + public function execute() { + + //Blank all our variables + $limit = $user = $start = $end = $dir = null; + + //Get our parameters out + extract($this->extractRequestParams()); + + //Get a database instance + $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 ); + + $this->addOption('LIMIT', $limit + 1); + + //Initialise some variables + $data = array (); + $count = 0; + + //Do the actual query. + $res = $this->select( __METHOD__ ); + + //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); + 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); + } + } + + //Free the database record so the connection can get on with other stuff + $db->freeResult($res); + + //And send the whole shebang out as output. + $this->getResult()->setIndexedTagName($data, 'item'); + $this->getResult()->addValue('query', $this->getModuleName(), $data); + } + + 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_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'start' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'user' => null, + 'dir' => array ( + ApiBase :: PARAM_DFLT => 'older', + ApiBase :: PARAM_TYPE => array ( + 'newer', + 'older' + ) + ) + ); + } + + protected function getParamDescription() { + return array ( + 'limit' => 'The maximum number of contributions to return.', + '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).' + ); + } + + protected function getDescription() { + return 'Get edits by a user..'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=usercontribs&ucuser=YurikBot' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryUserContributions.php 17952 2006-11-27 08:36:57Z nickj $'; + } +} +?> diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php new file mode 100644 index 00000000..67564d62 --- /dev/null +++ b/includes/api/ApiQueryWatchlist.php @@ -0,0 +1,234 @@ + + * + * 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'); +} + +class ApiQueryWatchlist extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'wl'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + global $wgUser; + + if (!$wgUser->isLoggedIn()) + $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin'); + + $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'); + + $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)); + + if ($patrol) { + global $wgUseRCPatrol, $wgUser; + if (!$wgUseRCPatrol || !$wgUser->isAllowed('patrol')) + $this->dieUsage('patrol property is not available', 'patrol'); + } + } + + if (is_null($resultPageSet)) { + $this->addFields(array ( + 'rc_cur_id', + '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); + } + elseif ($allrev) { + $this->addFields(array ( + 'rc_this_oldid', + 'rc_namespace', + 'rc_title', + 'rc_timestamp' + )); + } else { + $this->addFields(array ( + 'rc_cur_id', + 'rc_namespace', + 'rc_title', + 'rc_timestamp' + )); + } + + $this->addTables(array ( + 'watchlist', + 'page', + 'recentchanges' + )); + + $userId = $wgUser->getID(); + $this->addWhere(array ( + 'wl_namespace = rc_namespace', + 'wl_title = rc_title', + 'rc_cur_id = page_id', + 'wl_user' => $userId + )); + $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->addOption('LIMIT', $limit +1); + + $data = array (); + $count = 0; + $res = $this->select(__METHOD__); + + $db = $this->getDB(); + 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); + break; + } + + if (is_null($resultPageSet)) { + $vals = $this->addRowInfo('rc', $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); + } + } + } + } + + $db->freeResult($res); + + if (is_null($resultPageSet)) { + $this->getResult()->setIndexedTagName($data, 'item'); + $this->getResult()->addValue('query', $this->getModuleName(), $data); + } + elseif ($allrev) { + $resultPageSet->populateFromRevisionIDs($data); + } else { + $resultPageSet->populateFromPageIDs($data); + } + } + + protected function getAllowedParams() { + return array ( + 'allrev' => false, + 'start' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array ( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'namespace' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => 'namespace' + ), + 'dir' => array ( + ApiBase :: PARAM_DFLT => 'older', + ApiBase :: PARAM_TYPE => array ( + 'newer', + 'older' + ) + ), + 'limit' => array ( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX1 => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'prop' => array ( + APIBase :: PARAM_ISMULTI => true, + APIBase :: PARAM_TYPE => array ( + 'user', + 'comment', + 'timestamp', + 'patrol' + ) + ) + ); + } + + protected function getParamDescription() { + return array ( + 'allrev' => 'Include multiple revisions of the same page within given timeframe.', + 'start' => 'The timestamp to start enumerating from.', + 'end' => 'The timestamp to end enumerating.', + 'namespace' => 'Filter changes to only the given namespace(s).', + 'dir' => 'In which direction to enumerate pages.', + 'limit' => 'How many total pages to return per request.', + 'prop' => 'Which additional items to get (non-generator mode only).' + ); + } + + protected function getDescription() { + return ''; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=watchlist', + 'api.php?action=query&list=watchlist&wlallrev', + '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 17987 2006-11-29 05:45:03Z nickj $'; + } +} +?> diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index 67fbf41e..c9bfcfb9 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -31,21 +31,34 @@ if (!defined('MEDIAWIKI')) { class ApiResult extends ApiBase { - private $mData; + private $mData, $mIsRawMode; /** * Constructor */ public function __construct($main) { parent :: __construct($main, 'result'); - $this->Reset(); + $this->mIsRawMode = false; + $this->reset(); } - public function Reset() { + public function reset() { $this->mData = array (); } + + /** + * Call this function when special elements such as '_element' + * are needed by the formatter, for example in XML printing. + */ + public function setRawMode() { + $this->mIsRawMode = true; + } + + public function getIsRawMode() { + return $this->mIsRawMode; + } - function & getData() { + public function & getData() { return $this->mData; } @@ -73,11 +86,19 @@ class ApiResult extends ApiBase { /** * Adds the content element to the array. * Use this function instead of hardcoding the '*' element. + * @param string $subElemName when present, content element is created as a sub item of the arr. + * Use this parameter to create elements in format text without attributes */ - public static function setContent(& $arr, $value) { + public static function setContent(& $arr, $value, $subElemName = null) { if (is_array($value)) ApiBase :: dieDebug(__METHOD__, 'Bad parameter'); - ApiResult :: setElement($arr, '*', $value); + if (is_null($subElemName)) { + ApiResult :: setElement($arr, '*', $value); + } else { + if (!isset ($arr[$subElemName])) + $arr[$subElemName] = array (); + ApiResult :: setElement($arr[$subElemName], '*', $value); + } } // public static function makeContentElement($tag, $value) { @@ -89,10 +110,13 @@ class ApiResult extends ApiBase { * In case the array contains indexed values (in addition to named), * all indexed values will have the given tag name. */ - public static function setIndexedTagName(& $arr, $tag) { - // Do not use setElement() as it is ok to call this more than once + public function setIndexedTagName(& $arr, $tag) { + // In raw mode, add the '_element', otherwise just ignore + if (!$this->getIsRawMode()) + return; if ($arr === null || $tag === null || !is_array($arr) || is_array($tag)) ApiBase :: dieDebug(__METHOD__, 'Bad parameter'); + // Do not use setElement() as it is ok to call this more than once $arr['_element'] = $tag; } @@ -105,7 +129,7 @@ class ApiResult extends ApiBase { $data = & $this->getData(); - if (isset ($path)) { + if (!is_null($path)) { if (is_array($path)) { foreach ($path as $p) { if (!isset ($data[$p])) @@ -122,32 +146,12 @@ class ApiResult extends ApiBase { ApiResult :: setElement($data, $name, $value); } - /** - * Recursivelly removes any elements from the array that begin with an '_'. - * The content element '*' is the only special element that is left. - * Use this method when the entire data object gets sent to the user. - */ - public function SanitizeData() { - ApiResult :: SanitizeDataInt($this->mData); - } - - private static function SanitizeDataInt(& $data) { - foreach ($data as $key => & $value) { - if ($key[0] === '_') { - unset ($data[$key]); - } - elseif (is_array($value)) { - ApiResult :: SanitizeDataInt($value); - } - } - } - public function execute() { ApiBase :: dieDebug(__METHOD__, 'execute() is not supported on Result object'); } public function getVersion() { - return __CLASS__ . ': $Id: ApiResult.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiResult.php 17076 2006-10-18 05:35:24Z yurik $'; } } ?> \ No newline at end of file -- cgit v1.2.2