summaryrefslogtreecommitdiff
path: root/includes/api
diff options
context:
space:
mode:
Diffstat (limited to 'includes/api')
-rw-r--r--includes/api/ApiBase.php441
-rw-r--r--includes/api/ApiFormatBase.php161
-rw-r--r--includes/api/ApiFormatJson.php56
-rw-r--r--includes/api/ApiFormatJson_json.php841
-rw-r--r--includes/api/ApiFormatXml.php161
-rw-r--r--includes/api/ApiFormatYaml.php55
-rw-r--r--includes/api/ApiFormatYaml_spyc.php854
-rw-r--r--includes/api/ApiHelp.php55
-rw-r--r--includes/api/ApiLogin.php122
-rw-r--r--includes/api/ApiMain.php226
-rw-r--r--includes/api/ApiPageSet.php514
-rw-r--r--includes/api/ApiQuery.php354
-rw-r--r--includes/api/ApiQueryAllpages.php183
-rw-r--r--includes/api/ApiQueryBase.php112
-rw-r--r--includes/api/ApiQueryInfo.php82
-rw-r--r--includes/api/ApiQueryRevisions.php320
-rw-r--r--includes/api/ApiQuerySiteinfo.php113
-rw-r--r--includes/api/ApiResult.php153
18 files changed, 4803 insertions, 0 deletions
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
new file mode 100644
index 00000000..f578f41b
--- /dev/null
+++ b/includes/api/ApiBase.php
@@ -0,0 +1,441 @@
+<?php
+
+
+/*
+ * Created on Sep 5, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+abstract class ApiBase {
+
+ // These constants allow modules to specify exactly how to treat incomming parameters.
+
+ const PARAM_DFLT = 0;
+ const PARAM_ISMULTI = 1;
+ const PARAM_TYPE = 2;
+ const PARAM_MAX1 = 3;
+ const PARAM_MAX2 = 4;
+ const PARAM_MIN = 5;
+
+ private $mMainModule, $mModuleName, $mParamPrefix;
+
+ /**
+ * Constructor
+ */
+ public function __construct($mainModule, $moduleName, $paramPrefix = '') {
+ $this->mMainModule = $mainModule;
+ $this->mModuleName = $moduleName;
+ $this->mParamPrefix = $paramPrefix;
+ }
+
+ /**
+ * Executes this module
+ */
+ public abstract function execute();
+
+ /**
+ * Get the name of the query being executed by this instance
+ */
+ public function getModuleName() {
+ return $this->mModuleName;
+ }
+
+ /**
+ * Get main module
+ */
+ public function getMain() {
+ return $this->mMainModule;
+ }
+
+ /**
+ * If this module's $this is the same as $this->mMainModule, its the root, otherwise no
+ */
+ public function isMain() {
+ return $this === $this->mMainModule;
+ }
+
+ /**
+ * Get result object
+ */
+ public function getResult() {
+ // Main module has getResult() method overriden
+ // Safety - avoid infinite loop:
+ if ($this->isMain())
+ ApiBase :: dieDebug(__METHOD__, 'base method was called on main module. ');
+ return $this->getMain()->getResult();
+ }
+
+ /**
+ * Get the result data array
+ */
+ public function & getResultData() {
+ return $this->getResult()->getData();
+ }
+
+ /**
+ * Generates help message for this module, or false if there is no description
+ */
+ public function makeHelpMsg() {
+
+ static $lnPrfx = "\n ";
+
+ $msg = $this->getDescription();
+
+ if ($msg !== false) {
+
+ if (!is_array($msg))
+ $msg = array (
+ $msg
+ );
+ $msg = $lnPrfx . implode($lnPrfx, $msg) . "\n";
+
+ // Parameters
+ $paramsMsg = $this->makeHelpMsgParameters();
+ if ($paramsMsg !== false) {
+ $msg .= "Parameters:\n$paramsMsg";
+ }
+
+ // Examples
+ $examples = $this->getExamples();
+ if ($examples !== false) {
+ if (!is_array($examples))
+ $examples = array (
+ $examples
+ );
+ $msg .= 'Example' . (count($examples) > 1 ? 's' : '') . ":\n ";
+ $msg .= implode($lnPrfx, $examples) . "\n";
+ }
+
+ if ($this->getMain()->getShowVersions()) {
+ $versions = $this->getVersion();
+ if (is_array($versions))
+ $versions = implode("\n ", $versions);
+ $msg .= "Version:\n $versions\n";
+ }
+ }
+
+ return $msg;
+ }
+
+ public function makeHelpMsgParameters() {
+ $params = $this->getAllowedParams();
+ if ($params !== false) {
+
+ $paramsDescription = $this->getParamDescription();
+ $msg = '';
+ foreach (array_keys($params) as $paramName) {
+ $desc = isset ($paramsDescription[$paramName]) ? $paramsDescription[$paramName] : '';
+ if (is_array($desc))
+ $desc = implode("\n" . str_repeat(' ', 19), $desc);
+ $msg .= sprintf(" %-14s - %s\n", $this->encodeParamName($paramName), $desc);
+ }
+ return $msg;
+
+ } else
+ return false;
+ }
+
+ /**
+ * Returns the description string for this module
+ */
+ protected function getDescription() {
+ return false;
+ }
+
+ /**
+ * Returns usage examples for this module. Return null if no examples are available.
+ */
+ protected function getExamples() {
+ return false;
+ }
+
+ /**
+ * Returns an array of allowed parameters (keys) => default value for that parameter
+ */
+ protected function getAllowedParams() {
+ return false;
+ }
+
+ /**
+ * Returns the description string for the given parameter.
+ */
+ 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
+ */
+ public function encodeParamName($paramName) {
+ return $this->mParamPrefix . $paramName;
+ }
+
+ /**
+ * Using getAllowedParams(), makes an array of the values provided by the user,
+ * with key being the name of the variable, and value - validated value from user or default.
+ * This method can be used to generate local variables using extract().
+ */
+ public function extractRequestParams() {
+ $params = $this->getAllowedParams();
+ $results = array ();
+
+ foreach ($params as $paramName => $paramSettings)
+ $results[$paramName] = $this->getParameterFromSettings($paramName, $paramSettings);
+
+ return $results;
+ }
+
+ /**
+ * Get a value for the given parameter
+ */
+ protected function getParameter($paramName) {
+ $params = $this->getAllowedParams();
+ $paramSettings = $params[$paramName];
+ return $this->getParameterFromSettings($paramName, $paramSettings);
+ }
+
+ /**
+ * 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);
+
+ if (!is_array($paramSettings)) {
+ $default = $paramSettings;
+ $multi = false;
+ $type = gettype($paramSettings);
+ } else {
+ $default = isset ($paramSettings[self :: PARAM_DFLT]) ? $paramSettings[self :: PARAM_DFLT] : null;
+ $multi = isset ($paramSettings[self :: PARAM_ISMULTI]) ? $paramSettings[self :: PARAM_ISMULTI] : false;
+ $type = isset ($paramSettings[self :: PARAM_TYPE]) ? $paramSettings[self :: PARAM_TYPE] : null;
+
+ // When type is not given, and no choices, the type is the same as $default
+ if (!isset ($type)) {
+ if (isset ($default))
+ $type = gettype($default);
+ else
+ $type = 'NULL'; // allow everything
+ }
+ }
+
+ if ($type == 'boolean') {
+ if (isset ($default) && $default !== false) {
+ // Having a default value of anything other than 'false' is pointless
+ ApiBase :: dieDebug(__METHOD__, "Boolean param $paramName's default is set to '$default'");
+ }
+
+ $value = $wgRequest->getCheck($paramName);
+ } else
+ $value = $wgRequest->getVal($paramName, $default);
+
+ 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");
+
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Return an array of values that were given in a 'a|b|c' notation,
+ * after it optionally validates them against the list allowed values.
+ *
+ * @param valueName - The name of the parameter (for error reporting)
+ * @param value - The value being parsed
+ * @param allowMultiple - Can $value contain more than one value separated by '|'?
+ * @param allowedValues - An array of values to check against. If null, all values are accepted.
+ * @return (allowMultiple ? an_array_of_values : a_single_value)
+ */
+ protected function parseMultiValue($valueName, $value, $allowMultiple, $allowedValues) {
+ $valuesList = explode('|', $value);
+ if (!$allowMultiple && count($valuesList) != 1) {
+ $possibleValues = is_array($allowedValues) ? "of '" . implode("', '", $allowedValues) . "'" : '';
+ $this->dieUsage("Only one $possibleValues is allowed for parameter '$valueName'", "multival_$valueName");
+ }
+ 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");
+ }
+ }
+
+ return $allowMultiple ? $valuesList : $valuesList[0];
+ }
+
+ /**
+ * 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);
+ }
+
+ if ($this->getMain()->isBot()) {
+ if ($value > $botMax) {
+ $this->dieUsage("$varname may not be over $botMax (set to $value) for bots", $varname);
+ }
+ }
+ elseif ($value > $max) {
+ $this->dieUsage("$varname may not be over $max (set to $value) for users", $varname);
+ }
+ }
+
+ /**
+ * Call main module's error handler
+ */
+ public function dieUsage($description, $errorCode, $httpRespCode = 0) {
+ $this->getMain()->mainDieUsage($description, $this->encodeParamName($errorCode), $httpRespCode);
+ }
+
+ /**
+ * Internal code errors should be reported with this method
+ */
+ protected static function dieDebug($method, $message) {
+ wfDebugDieBacktrace("Internal error in $method: $message");
+ }
+
+ /**
+ * Profiling: total module execution time
+ */
+ private $mTimeIn = 0, $mModuleTime = 0;
+
+ /**
+ * Start module profiling
+ */
+ public function profileIn() {
+ if ($this->mTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileOut()');
+ $this->mTimeIn = microtime(true);
+ }
+
+ /**
+ * End module profiling
+ */
+ public function profileOut() {
+ if ($this->mTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileIn() first');
+ if ($this->mDBTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'must be called after database profiling is done with profileDBOut()');
+
+ $this->mModuleTime += microtime(true) - $this->mTimeIn;
+ $this->mTimeIn = 0;
+ }
+
+ /**
+ * Total time the module was executed
+ */
+ public function getProfileTime() {
+ if ($this->mTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileOut() first');
+ return $this->mModuleTime;
+ }
+
+ /**
+ * Profiling: database execution time
+ */
+ private $mDBTimeIn = 0, $mDBTime = 0;
+
+ /**
+ * Start module profiling
+ */
+ public function profileDBIn() {
+ if ($this->mTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'must be called while profiling the entire module with profileIn()');
+ if ($this->mDBTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileDBOut()');
+ $this->mDBTimeIn = microtime(true);
+ }
+
+ /**
+ * End database profiling
+ */
+ public function profileDBOut() {
+ if ($this->mTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'must be called while profiling the entire module with profileIn()');
+ if ($this->mDBTimeIn === 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBIn() first');
+
+ $time = microtime(true) - $this->mDBTimeIn;
+ $this->mDBTimeIn = 0;
+
+ $this->mDBTime += $time;
+ $this->getMain()->mDBTime += $time;
+ }
+
+ /**
+ * Total time the module used the database
+ */
+ public function getProfileDBTime() {
+ if ($this->mDBTimeIn !== 0)
+ ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBOut() first');
+ return $this->mDBTime;
+ }
+
+ public abstract function getVersion();
+
+ public static function getBaseVersion() {
+ return __CLASS__ . ': $Id: ApiBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
new file mode 100644
index 00000000..6f5b4aca
--- /dev/null
+++ b/includes/api/ApiFormatBase.php
@@ -0,0 +1,161 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+abstract class ApiFormatBase extends ApiBase {
+
+ private $mIsHtml, $mFormat;
+
+ /**
+ * Constructor
+ */
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+
+ $this->mIsHtml = (substr($format, -2, 2) === 'fm'); // ends with 'fm'
+ if ($this->mIsHtml)
+ $this->mFormat = substr($format, 0, -2); // remove ending 'fm'
+ else
+ $this->mFormat = $format;
+ $this->mFormat = strtoupper($this->mFormat);
+ }
+
+ /**
+ * Overriding class returns the mime type that should be sent to the client.
+ * This method is not called if getIsHtml() returns true.
+ * @return string
+ */
+ public abstract function getMimeType();
+
+ public function getNeedsRawData() {
+ return false;
+ }
+
+ /**
+ * Returns true when an HTML filtering printer should be used.
+ * The default implementation assumes that formats ending with 'fm'
+ * should be formatted in HTML.
+ */
+ public function getIsHtml() {
+ return $this->mIsHtml;
+ }
+
+ /**
+ * Initialize the printer function and prepares the output headers, etc.
+ * This method must be the first outputing method during execution.
+ * A help screen's header is printed for the HTML-based output
+ */
+ function initPrinter($isError) {
+ $isHtml = $this->getIsHtml();
+ $mime = $isHtml ? 'text/html' : $this->getMimeType();
+ header("Content-Type: $mime; charset=utf-8;");
+
+ if ($isHtml) {
+?>
+ <html>
+ <head>
+ <title>MediaWiki API</title>
+ </head>
+ <body>
+<?php
+
+
+ if (!$isError) {
+?>
+ <br/>
+ <small>
+ This result is being shown in <?=$this->mFormat?> format,
+ which might not be suitable for your application.<br/>
+ See <a href='api.php'>API help</a> for more information.<br/>
+ </small>
+<?php
+
+
+ }
+?>
+ <pre>
+<?php
+
+
+ }
+ }
+
+ /**
+ * Finish printing. Closes HTML tags.
+ */
+ public function closePrinter() {
+ if ($this->getIsHtml()) {
+?>
+ </pre>
+ </body>
+<?php
+
+
+ }
+ }
+
+ public function printText($text) {
+ if ($this->getIsHtml())
+ echo $this->formatHTML($text);
+ else
+ echo $text;
+ }
+
+ /**
+ * Prety-print various elements in HTML format, such as xml tags and URLs.
+ * This method also replaces any '<' with &lt;
+ */
+ protected function formatHTML($text) {
+ // encode all tags as safe blue strings
+ $text = ereg_replace('\<([^>]+)\>', '<font color=blue>&lt;\1&gt;</font>', $text);
+ // identify URLs
+ $text = ereg_replace("[a-zA-Z]+://[^ '()<\n]+", '<a href="\\0">\\0</a>', $text);
+ // identify requests to api.php
+ $text = ereg_replace("api\\.php\\?[^ ()<\n\t]+", '<a href="\\0">\\0</a>', $text);
+ // make strings inside * bold
+ $text = ereg_replace("\\*[^<>\n]+\\*", '<b>\\0</b>', $text);
+ // make strings inside $ italic
+ $text = ereg_replace("\\$[^<>\n]+\\$", '<b><i>\\0</i></b>', $text);
+
+ return $text;
+ }
+
+ /**
+ * Returns usage examples for this format.
+ */
+ protected function getExamples() {
+ return 'api.php?action=query&meta=siteinfo&si=namespaces&format=' . $this->getModuleName();
+ }
+
+ public static function getBaseVersion() {
+ return __CLASS__ . ': $Id: ApiFormatBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
new file mode 100644
index 00000000..fdc29cf2
--- /dev/null
+++ b/includes/api/ApiFormatJson.php
@@ -0,0 +1,56 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiFormatBase.php');
+}
+
+class ApiFormatJson extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'application/json';
+ }
+
+ public function execute() {
+ require ('ApiFormatJson_json.php');
+ $json = new Services_JSON();
+ $this->printText($json->encode($this->getResultData(), true));
+ }
+
+ protected function getDescription() {
+ return 'Output data in JSON format';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatJson.php 16725 2006-10-01 21:20:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatJson_json.php b/includes/api/ApiFormatJson_json.php
new file mode 100644
index 00000000..375de7eb
--- /dev/null
+++ b/includes/api/ApiFormatJson_json.php
@@ -0,0 +1,841 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+* Converts to and from JSON format.
+*
+* JSON (JavaScript Object Notation) is a lightweight data-interchange
+* format. It is easy for humans to read and write. It is easy for machines
+* to parse and generate. It is based on a subset of the JavaScript
+* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+* This feature can also be found in Python. JSON is a text format that is
+* completely language independent but uses conventions that are familiar
+* to programmers of the C-family of languages, including C, C++, C#, Java,
+* JavaScript, Perl, TCL, and many others. These properties make JSON an
+* ideal data-interchange language.
+*
+* This package provides a simple encoder and decoder for JSON notation. It
+* is intended for use with client-side Javascript applications that make
+* use of HTTPRequest to perform server communication functions - data can
+* be encoded into JSON notation for use in a client-side javascript, or
+* decoded from incoming Javascript requests. JSON format is native to
+* Javascript, and can be directly eval()'ed with no further parsing
+* overhead
+*
+* All strings should be in ASCII or UTF-8 format!
+*
+* LICENSE: Redistribution and use in source and binary forms, with or
+* without modification, are permitted provided that the following
+* conditions are met: Redistributions of source code must retain the
+* above copyright notice, this list of conditions and the following
+* disclaimer. Redistributions in binary form must reproduce the above
+* copyright notice, this list of conditions and the following disclaimer
+* in the documentation and/or other materials provided with the
+* distribution.
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+* DAMAGE.
+*
+* @category
+* @package Services_JSON
+* @author Michal Migurski <mike-json@teczno.com>
+* @author Matt Knapp <mdknapp[at]gmail[dot]com>
+* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+* @copyright 2005 Michal Migurski
+* @version CVS: $Id: JSON.php,v 1.30 2006/03/08 16:10:20 migurski Exp $
+* @license http://www.opensource.org/licenses/bsd-license.php
+* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+*/
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_SLICE', 1);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_STR', 2);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_ARR', 3);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_OBJ', 4);
+
+/**
+* Marker constant for Services_JSON::decode(), used to flag stack state
+*/
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+* Behavior switch for Services_JSON::decode()
+*/
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+* Behavior switch for Services_JSON::decode()
+*/
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+* Converts to and from JSON format.
+*
+* Brief example of use:
+*
+* <code>
+* // create a new instance of Services_JSON
+* $json = new Services_JSON();
+*
+* // convert a complexe value to JSON notation, and send it to the browser
+* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+* $output = $json->encode($value);
+*
+* print($output);
+* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+*
+* // accept incoming POST data, assumed to be in JSON notation
+* $input = file_get_contents('php://input', 1000000);
+* $value = $json->decode($input);
+* </code>
+*/
+class Services_JSON
+{
+ /**
+ * constructs a new JSON instance
+ *
+ * @param int $use object behavior flags; combine with boolean-OR
+ *
+ * possible values:
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
+ * "{...}" syntax creates associative arrays
+ * instead of objects in decode().
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
+ * Values which can't be encoded (e.g. resources)
+ * appear as NULL instead of throwing errors.
+ * By default, a deeply-nested resource will
+ * bubble up with an error, so all return values
+ * from encode() should be checked with isError()
+ */
+ function Services_JSON($use = 0)
+ {
+ $this->use = $use;
+ }
+
+ /**
+ * convert a string from one UTF-16 char to one UTF-8 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf16 UTF-16 character
+ * @return string UTF-8 character
+ * @access private
+ */
+ function utf162utf8($utf16)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+ }
+
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+ switch(true) {
+ case ((0x7F & $bytes) == $bytes):
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x7F & $bytes);
+
+ case (0x07FF & $bytes) == $bytes:
+ // return a 2-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
+ . chr(0x80 | ($bytes & 0x3F));
+
+ case (0xFFFF & $bytes) == $bytes:
+ // return a 3-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
+ . chr(0x80 | ($bytes & 0x3F));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * convert a string from one UTF-8 char to one UTF-16 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ * @access private
+ */
+ function utf82utf16($utf8)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch(strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8{0}) >> 2))
+ . chr((0xC0 & (ord($utf8{0}) << 6))
+ | (0x3F & ord($utf8{1})));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8{0}) << 4))
+ | (0x0F & (ord($utf8{1}) >> 2)))
+ . chr((0xC0 & (ord($utf8{1}) << 6))
+ | (0x7F & ord($utf8{2})));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ * @param bool $pretty pretty-print output with indents and newlines
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access public
+ */
+ function encode($var, $pretty=false)
+ {
+ $this->indent = 0;
+ $this->pretty = $pretty;
+ $this->nameValSeparator = $pretty ? ': ' : ':';
+ return $this->encode2($var);
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access private
+ */
+ function encode2($var)
+ {
+ if ($this->pretty) {
+ $close = "\n" . str_repeat("\t", $this->indent);
+ $open = $close . "\t";
+ $mid = ',' . $open;
+ }
+ else {
+ $open = $close = '';
+ $mid = ',';
+ }
+
+ switch (gettype($var)) {
+ case 'boolean':
+ return $var ? 'true' : 'false';
+
+ case 'NULL':
+ return 'null';
+
+ case 'integer':
+ return (int) $var;
+
+ case 'double':
+ case 'float':
+ return (float) $var;
+
+ case 'string':
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+ $ascii = '';
+ $strlen_var = strlen($var);
+
+ /*
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for ($c = 0; $c < $strlen_var; ++$c) {
+
+ $ord_var_c = ord($var{$c});
+
+ switch (true) {
+ case $ord_var_c == 0x08:
+ $ascii .= '\b';
+ break;
+ case $ord_var_c == 0x09:
+ $ascii .= '\t';
+ break;
+ case $ord_var_c == 0x0A:
+ $ascii .= '\n';
+ break;
+ case $ord_var_c == 0x0C:
+ $ascii .= '\f';
+ break;
+ case $ord_var_c == 0x0D:
+ $ascii .= '\r';
+ break;
+
+ case $ord_var_c == 0x22:
+ case $ord_var_c == 0x2F:
+ case $ord_var_c == 0x5C:
+ // double quote, slash, slosh
+ $ascii .= '\\'.$var{$c};
+ break;
+
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $var{$c};
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $c += 1;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}));
+ $c += 2;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}));
+ $c += 3;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFC) == 0xF8):
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}));
+ $c += 4;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFE) == 0xFC):
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}),
+ ord($var{$c + 5}));
+ $c += 5;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+ }
+ }
+
+ return '"'.$ascii.'"';
+
+ case 'array':
+ /*
+ * As per JSON spec if any array key is not an integer
+ * we must treat the the whole array as an object. We
+ * also try to catch a sparsely populated associative
+ * array with numeric keys here because some JS engines
+ * will create an array with empty indexes up to
+ * max_index which can cause memory issues and because
+ * the keys, which may be relevant, will be remapped
+ * otherwise.
+ *
+ * As per the ECMA and JSON specification an object may
+ * have any string as a property. Unfortunately due to
+ * a hole in the ECMA specification if the key is a
+ * ECMA reserved word or starts with a digit the
+ * parameter is only accessible using ECMAScript's
+ * bracket notation.
+ */
+
+ // treat as a JSON object
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+ $this->indent++;
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($var),
+ array_values($var));
+ $this->indent--;
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . $open . join($mid, $properties) . $close . '}';
+ }
+
+ // treat it like a regular array
+ $this->indent++;
+ $elements = array_map(array($this, 'encode2'), $var);
+ $this->indent--;
+
+ foreach($elements as $element) {
+ if(Services_JSON::isError($element)) {
+ return $element;
+ }
+ }
+
+ return '[' . $open . join($mid, $elements) . $close . ']';
+
+ case 'object':
+ $vars = get_object_vars($var);
+
+ $this->indent++;
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($vars),
+ array_values($vars));
+ $this->indent--;
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . $open . join($mid, $properties) . $close . '}';
+
+ default:
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+ ? 'null'
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+ }
+ }
+
+ /**
+ * array-walking function for use in generating JSON-formatted name-value pairs
+ *
+ * @param string $name name of key to use
+ * @param mixed $value reference to an array element to be encoded
+ *
+ * @return string JSON-formatted name-value pair, like '"name":value'
+ * @access private
+ */
+ function name_value($name, $value)
+ {
+ $encoded_value = $this->encode2($value);
+
+ if(Services_JSON::isError($encoded_value)) {
+ return $encoded_value;
+ }
+
+ return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value;
+ }
+
+ /**
+ * reduce a string by removing leading and trailing comments and whitespace
+ *
+ * @param $str string string value to strip of comments and whitespace
+ *
+ * @return string string value stripped of comments and whitespace
+ * @access private
+ */
+ function reduce_string($str)
+ {
+ $str = preg_replace(array(
+
+ // eliminate single line comments in '// ...' form
+ '#^\s*//(.+)$#m',
+
+ // eliminate multi-line comments in '/* ... */' form, at start of string
+ '#^\s*/\*(.+)\*/#Us',
+
+ // eliminate multi-line comments in '/* ... */' form, at end of string
+ '#/\*(.+)\*/\s*$#Us'
+
+ ), '', $str);
+
+ // eliminate extraneous space
+ return trim($str);
+ }
+
+ /**
+ * decodes a JSON string into appropriate variable
+ *
+ * @param string $str JSON-formatted string
+ *
+ * @return mixed number, boolean, string, array, or object
+ * corresponding to given JSON input string.
+ * See argument 1 to Services_JSON() above for object-output behavior.
+ * Note that decode() always returns strings
+ * in ASCII or UTF-8 format!
+ * @access public
+ */
+ function decode($str)
+ {
+ $str = $this->reduce_string($str);
+
+ switch (strtolower($str)) {
+ case 'true':
+ return true;
+
+ case 'false':
+ return false;
+
+ case 'null':
+ return null;
+
+ default:
+ $m = array();
+
+ if (is_numeric($str)) {
+ // Lookie-loo, it's a number
+
+ // This would work on its own, but I'm trying to be
+ // good about returning integers where appropriate:
+ // return (float)$str;
+
+ // Return float or int, as appropriate
+ return ((float)$str == (integer)$str)
+ ? (integer)$str
+ : (float)$str;
+
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+ // STRINGS RETURNED IN UTF-8 FORMAT
+ $delim = substr($str, 0, 1);
+ $chrs = substr($str, 1, -1);
+ $utf8 = '';
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+ $ord_chrs_c = ord($chrs{$c});
+
+ switch (true) {
+ case $substr_chrs_c_2 == '\b':
+ $utf8 .= chr(0x08);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\t':
+ $utf8 .= chr(0x09);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\n':
+ $utf8 .= chr(0x0A);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\f':
+ $utf8 .= chr(0x0C);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\r':
+ $utf8 .= chr(0x0D);
+ ++$c;
+ break;
+
+ case $substr_chrs_c_2 == '\\"':
+ case $substr_chrs_c_2 == '\\\'':
+ case $substr_chrs_c_2 == '\\\\':
+ case $substr_chrs_c_2 == '\\/':
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+ $utf8 .= $chrs{++$c};
+ }
+ break;
+
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+ // single, escaped unicode character
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
+ $utf8 .= $this->utf162utf8($utf16);
+ $c += 5;
+ break;
+
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+ $utf8 .= $chrs{$c};
+ break;
+
+ case ($ord_chrs_c & 0xE0) == 0xC0:
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 2);
+ ++$c;
+ break;
+
+ case ($ord_chrs_c & 0xF0) == 0xE0:
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 3);
+ $c += 2;
+ break;
+
+ case ($ord_chrs_c & 0xF8) == 0xF0:
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 4);
+ $c += 3;
+ break;
+
+ case ($ord_chrs_c & 0xFC) == 0xF8:
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 5);
+ $c += 4;
+ break;
+
+ case ($ord_chrs_c & 0xFE) == 0xFC:
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 6);
+ $c += 5;
+ break;
+
+ }
+
+ }
+
+ return $utf8;
+
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+ // array, or object notation
+
+ if ($str{0} == '[') {
+ $stk = array(SERVICES_JSON_IN_ARR);
+ $arr = array();
+ } else {
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = array();
+ } else {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = new stdClass();
+ }
+ }
+
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
+ 'where' => 0,
+ 'delim' => false));
+
+ $chrs = substr($str, 1, -1);
+ $chrs = $this->reduce_string($chrs);
+
+ if ($chrs == '') {
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } else {
+ return $obj;
+
+ }
+ }
+
+ //print("\nparsing {$chrs}\n");
+
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+ $top = end($stk);
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+ // found a comma that is not inside a string, array, etc.,
+ // OR we've reached the end of the character list
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ // we are in an array, so just push an element onto the stack
+ array_push($arr, $this->decode($slice));
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ // we are in an object, so figure
+ // out the property name and set an
+ // element in an associative array,
+ // for now
+ $parts = array();
+
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // "name":value pair
+ $key = $this->decode($parts[1]);
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // name:value pair, where name is unquoted
+ $key = $parts[1];
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ }
+
+ }
+
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+ // found a quote, and we are not inside a string
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+ //print("Found start of string at {$c}\n");
+
+ } elseif (($chrs{$c} == $top['delim']) &&
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
+ (($chrs{$c - 1} != '\\') ||
+ ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
+ // found a quote, we're in a string, and it's not escaped
+ array_pop($stk);
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '[') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-bracket, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+ //print("Found start of array at {$c}\n");
+
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+ // found a right-bracket, and we're in an array
+ array_pop($stk);
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '{') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-brace, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+ //print("Found start of object at {$c}\n");
+
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+ // found a right-brace, and we're in an object
+ array_pop($stk);
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($substr_chrs_c_2 == '/*') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a comment start, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+ $c++;
+ //print("Found start of comment at {$c}\n");
+
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+ // found a comment end, and we're in one now
+ array_pop($stk);
+ $c++;
+
+ for ($i = $top['where']; $i <= $c; ++$i)
+ $chrs = substr_replace($chrs, ' ', $i, 1);
+
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ }
+
+ }
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ return $obj;
+
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @todo Ultimately, this should just call PEAR::isError()
+ */
+ function isError($data, $code = null)
+ {
+ if (class_exists('pear')) {
+ return PEAR::isError($data, $code);
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+ is_subclass_of($data, 'services_json_error'))) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+if (class_exists('PEAR_Error')) {
+
+ class Services_JSON_Error extends PEAR_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+ }
+ }
+
+} else {
+
+ /**
+ * @todo Ultimately, this class shall be descended from PEAR_Error
+ */
+ class Services_JSON_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+
+ }
+ }
+
+}
+
+?>
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
new file mode 100644
index 00000000..6aa08e00
--- /dev/null
+++ b/includes/api/ApiFormatXml.php
@@ -0,0 +1,161 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiFormatBase.php');
+}
+
+class ApiFormatXml extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'text/xml';
+ }
+
+ public function getNeedsRawData() {
+ return true;
+ }
+
+ public function execute() {
+ $xmlindent = null;
+ extract($this->extractRequestParams());
+
+ if ($xmlindent || $this->getIsHtml())
+ $xmlindent = -2;
+ else
+ $xmlindent = null;
+
+ $this->printText('<?xml version="1.0" encoding="utf-8"?>');
+ $this->recXmlPrint('api', $this->getResultData(), $xmlindent);
+ }
+
+ /**
+ * This method takes an array and converts it into an xml.
+ * There are several noteworthy cases:
+ *
+ * If array contains a key '_element', then the code assumes that ALL other keys are not important and replaces them with the value['_element'].
+ * Example: name='root', value = array( '_element'=>'page', 'x', 'y', 'z') creates <root> <page>x</page> <page>y</page> <page>z</page> </root>
+ *
+ * If any of the array's element key is '*', then the code treats all other key->value pairs as attributes, and the value['*'] as the element's content.
+ * Example: name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10) creates <root lang='en' id='10'>text</root>
+ *
+ * If neither key is found, all keys become element names, and values become element content.
+ * The method is recursive, so the same rules apply to any sub-arrays.
+ */
+ function recXmlPrint($elemName, $elemValue, $indent) {
+ if (!is_null($indent)) {
+ $indent += 2;
+ $indstr = "\n" . str_repeat(" ", $indent);
+ } else {
+ $indstr = '';
+ }
+
+ switch (gettype($elemValue)) {
+ case 'array' :
+
+ if (isset ($elemValue['*'])) {
+ $subElemContent = $elemValue['*'];
+ unset ($elemValue['*']);
+ } else {
+ $subElemContent = null;
+ }
+
+ if (isset ($elemValue['_element'])) {
+ $subElemIndName = $elemValue['_element'];
+ unset ($elemValue['_element']);
+ } else {
+ $subElemIndName = null;
+ }
+
+ $indElements = array ();
+ $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)) {
+ $subElements[$subElemId] = $subElemValue;
+ unset ($elemValue[$subElemId]);
+ }
+ }
+
+ if (is_null($subElemIndName) && !empty ($indElements))
+ ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value");
+
+ if (!empty ($subElements) && !empty ($indElements) && !is_null($subElemContent))
+ ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has content and subelements");
+
+ if (!is_null($subElemContent)) {
+ $this->printText($indstr . wfElement($elemName, $elemValue, $subElemContent));
+ } elseif (empty ($indElements) && empty ($subElements)) {
+ $this->printText($indstr . wfElement($elemName, $elemValue));
+ } else {
+ $this->printText($indstr . wfElement($elemName, $elemValue, null));
+
+ foreach ($subElements as $subElemId => & $subElemValue)
+ $this->recXmlPrint($subElemId, $subElemValue, $indent);
+
+ foreach ($indElements as $subElemId => & $subElemValue)
+ $this->recXmlPrint($subElemIndName, $subElemValue, $indent);
+
+ $this->printText($indstr . wfCloseElement($elemName));
+ }
+ break;
+ case 'object' :
+ // ignore
+ break;
+ default :
+ $this->printText($indstr . wfElement($elemName, null, $elemValue));
+ break;
+ }
+ }
+ 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'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatXml.php 16725 2006-10-01 21:20:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
new file mode 100644
index 00000000..bd74f01a
--- /dev/null
+++ b/includes/api/ApiFormatYaml.php
@@ -0,0 +1,55 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiFormatBase.php');
+}
+
+class ApiFormatYaml extends ApiFormatBase {
+
+ public function __construct($main, $format) {
+ parent :: __construct($main, $format);
+ }
+
+ public function getMimeType() {
+ return 'application/yaml';
+ }
+
+ public function execute() {
+ require ('ApiFormatYaml_spyc.php');
+ $this->printText(Spyc :: YAMLDump($this->getResultData()));
+ }
+
+ protected function getDescription() {
+ return 'Output data in YAML format';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFormatYaml.php 16725 2006-10-01 21:20:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php
new file mode 100644
index 00000000..05a39e23
--- /dev/null
+++ b/includes/api/ApiFormatYaml_spyc.php
@@ -0,0 +1,854 @@
+<?php
+ /**
+ * Spyc -- A Simple PHP YAML Class
+ * @version 0.2.3 -- 2006-02-04
+ * @author Chris Wanstrath <chris@ozmm.org>
+ * @link http://spyc.sourceforge.net/
+ * @copyright Copyright 2005-2006 Chris Wanstrath
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @package Spyc
+ */
+
+ /**
+ * A node, used by Spyc for parsing YAML.
+ * @package Spyc
+ */
+ class YAMLNode {
+ /**#@+
+ * @access public
+ * @var string
+ */
+ var $parent;
+ var $id;
+ /**#@+*/
+ /**
+ * @access public
+ * @var mixed
+ */
+ var $data;
+ /**
+ * @access public
+ * @var int
+ */
+ var $indent;
+ /**
+ * @access public
+ * @var bool
+ */
+ var $children = false;
+
+ /**
+ * The constructor assigns the node a unique ID.
+ * @access public
+ * @return void
+ */
+ function YAMLNode() {
+ $this->id = uniqid('');
+ }
+ }
+
+ /**
+ * The Simple PHP YAML Class.
+ *
+ * This class can be used to read a YAML file and convert its contents
+ * into a PHP array. It currently supports a very limited subsection of
+ * the YAML spec.
+ *
+ * Usage:
+ * <code>
+ * $parser = new Spyc;
+ * $array = $parser->load($file);
+ * </code>
+ * @package Spyc
+ */
+ class Spyc {
+
+ /**
+ * Load YAML into a PHP array statically
+ *
+ * The load method, when supplied with a YAML stream (string or file),
+ * will do its best to convert YAML in a file into a PHP array. Pretty
+ * simple.
+ * Usage:
+ * <code>
+ * $array = Spyc::YAMLLoad('lucky.yml');
+ * print_r($array);
+ * </code>
+ * @access public
+ * @return array
+ * @param string $input Path of YAML file or string containing YAML
+ */
+ function YAMLLoad($input) {
+ $spyc = new Spyc;
+ return $spyc->load($input);
+ }
+
+ /**
+ * Dump YAML from PHP array statically
+ *
+ * The dump method, when supplied with an array, will do its best
+ * to convert the array into friendly YAML. Pretty simple. Feel free to
+ * save the returned string as nothing.yml and pass it around.
+ *
+ * Oh, and you can decide how big the indent is and what the wordwrap
+ * for folding is. Pretty cool -- just pass in 'false' for either if
+ * you want to use the default.
+ *
+ * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
+ * you can turn off wordwrap by passing in 0.
+ *
+ * @access public
+ * @static
+ * @return string
+ * @param array $array PHP array
+ * @param int $indent Pass in false to use the default, which is 2
+ * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
+ */
+ public static function YAMLDump($array,$indent = false,$wordwrap = false) {
+ $spyc = new Spyc;
+ return $spyc->dump($array,$indent,$wordwrap);
+ }
+
+ /**
+ * Load YAML into a PHP array from an instantiated object
+ *
+ * The load method, when supplied with a YAML stream (string or file path),
+ * will do its best to convert the YAML into a PHP array. Pretty simple.
+ * Usage:
+ * <code>
+ * $parser = new Spyc;
+ * $array = $parser->load('lucky.yml');
+ * print_r($array);
+ * </code>
+ * @access public
+ * @return array
+ * @param string $input Path of YAML file or string containing YAML
+ */
+ function load($input) {
+ // See what type of input we're talking about
+ // If it's not a file, assume it's a string
+ if (!empty($input) && (strpos($input, "\n") === false)
+ && file_exists($input)) {
+ $yaml = file($input);
+ } else {
+ $yaml = explode("\n",$input);
+ }
+ // Initiate some objects and values
+ $base = new YAMLNode;
+ $base->indent = 0;
+ $this->_lastIndent = 0;
+ $this->_lastNode = $base->id;
+ $this->_inBlock = false;
+ $this->_isInline = false;
+
+ foreach ($yaml as $linenum => $line) {
+ $ifchk = trim($line);
+
+ // If the line starts with a tab (instead of a space), throw a fit.
+ if (preg_match('/^(\t)+(\w+)/', $line)) {
+ $err = 'ERROR: Line '. ($linenum + 1) .' in your input YAML begins'.
+ ' with a tab. YAML only recognizes spaces. Please reformat.';
+ die($err);
+ }
+
+ if ($this->_inBlock === false && empty($ifchk)) {
+ continue;
+ } elseif ($this->_inBlock == true && empty($ifchk)) {
+ $last =& $this->_allNodes[$this->_lastNode];
+ $last->data[key($last->data)] .= "\n";
+ } elseif ($ifchk{0} != '#' && substr($ifchk,0,3) != '---') {
+ // Create a new node and get its indent
+ $node = new YAMLNode;
+ $node->indent = $this->_getIndent($line);
+
+ // Check where the node lies in the hierarchy
+ if ($this->_lastIndent == $node->indent) {
+ // If we're in a block, add the text to the parent's data
+ if ($this->_inBlock === true) {
+ $parent =& $this->_allNodes[$this->_lastNode];
+ $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
+ } else {
+ // The current node's parent is the same as the previous node's
+ if (isset($this->_allNodes[$this->_lastNode])) {
+ $node->parent = $this->_allNodes[$this->_lastNode]->parent;
+ }
+ }
+ } elseif ($this->_lastIndent < $node->indent) {
+ if ($this->_inBlock === true) {
+ $parent =& $this->_allNodes[$this->_lastNode];
+ $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
+ } elseif ($this->_inBlock === false) {
+ // The current node's parent is the previous node
+ $node->parent = $this->_lastNode;
+
+ // If the value of the last node's data was > or | we need to
+ // start blocking i.e. taking in all lines as a text value until
+ // we drop our indent.
+ $parent =& $this->_allNodes[$node->parent];
+ $this->_allNodes[$node->parent]->children = true;
+ if (is_array($parent->data)) {
+ $chk = $parent->data[key($parent->data)];
+ if ($chk === '>') {
+ $this->_inBlock = true;
+ $this->_blockEnd = ' ';
+ $parent->data[key($parent->data)] =
+ str_replace('>','',$parent->data[key($parent->data)]);
+ $parent->data[key($parent->data)] .= trim($line).' ';
+ $this->_allNodes[$node->parent]->children = false;
+ $this->_lastIndent = $node->indent;
+ } elseif ($chk === '|') {
+ $this->_inBlock = true;
+ $this->_blockEnd = "\n";
+ $parent->data[key($parent->data)] =
+ str_replace('|','',$parent->data[key($parent->data)]);
+ $parent->data[key($parent->data)] .= trim($line)."\n";
+ $this->_allNodes[$node->parent]->children = false;
+ $this->_lastIndent = $node->indent;
+ }
+ }
+ }
+ } elseif ($this->_lastIndent > $node->indent) {
+ // Any block we had going is dead now
+ if ($this->_inBlock === true) {
+ $this->_inBlock = false;
+ if ($this->_blockEnd = "\n") {
+ $last =& $this->_allNodes[$this->_lastNode];
+ $last->data[key($last->data)] =
+ trim($last->data[key($last->data)]);
+ }
+ }
+
+ // We don't know the parent of the node so we have to find it
+ // foreach ($this->_allNodes as $n) {
+ foreach ($this->_indentSort[$node->indent] as $n) {
+ if ($n->indent == $node->indent) {
+ $node->parent = $n->parent;
+ }
+ }
+ }
+
+ if ($this->_inBlock === false) {
+ // Set these properties with information from our current node
+ $this->_lastIndent = $node->indent;
+ // Set the last node
+ $this->_lastNode = $node->id;
+ // Parse the YAML line and return its data
+ $node->data = $this->_parseLine($line);
+ // Add the node to the master list
+ $this->_allNodes[$node->id] = $node;
+ // Add a reference to the node in an indent array
+ $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id];
+ // Add a reference to the node in a References array if this node
+ // has a YAML reference in it.
+ if (
+ ( (is_array($node->data)) &&
+ isset($node->data[key($node->data)]) &&
+ (!is_array($node->data[key($node->data)])) )
+ &&
+ ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)]))
+ ||
+ (preg_match('/^\*([^ ]+)/',$node->data[key($node->data)])) )
+ ) {
+ $this->_haveRefs[] =& $this->_allNodes[$node->id];
+ } elseif (
+ ( (is_array($node->data)) &&
+ isset($node->data[key($node->data)]) &&
+ (is_array($node->data[key($node->data)])) )
+ ) {
+ // Incomplete reference making code. Ugly, needs cleaned up.
+ foreach ($node->data[key($node->data)] as $d) {
+ if ( !is_array($d) &&
+ ( (preg_match('/^&([^ ]+)/',$d))
+ ||
+ (preg_match('/^\*([^ ]+)/',$d)) )
+ ) {
+ $this->_haveRefs[] =& $this->_allNodes[$node->id];
+ }
+ }
+ }
+ }
+ }
+ }
+ unset($node);
+
+ // Here we travel through node-space and pick out references (& and *)
+ $this->_linkReferences();
+
+ // Build the PHP array out of node-space
+ $trunk = $this->_buildArray();
+ return $trunk;
+ }
+
+ /**
+ * Dump PHP array to YAML
+ *
+ * The dump method, when supplied with an array, will do its best
+ * to convert the array into friendly YAML. Pretty simple. Feel free to
+ * save the returned string as tasteful.yml and pass it around.
+ *
+ * Oh, and you can decide how big the indent is and what the wordwrap
+ * for folding is. Pretty cool -- just pass in 'false' for either if
+ * you want to use the default.
+ *
+ * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
+ * you can turn off wordwrap by passing in 0.
+ *
+ * @access public
+ * @return string
+ * @param array $array PHP array
+ * @param int $indent Pass in false to use the default, which is 2
+ * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
+ */
+ function dump($array,$indent = false,$wordwrap = false) {
+ // Dumps to some very clean YAML. We'll have to add some more features
+ // and options soon. And better support for folding.
+
+ // New features and options.
+ if ($indent === false or !is_numeric($indent)) {
+ $this->_dumpIndent = 2;
+ } else {
+ $this->_dumpIndent = $indent;
+ }
+
+ if ($wordwrap === false or !is_numeric($wordwrap)) {
+ $this->_dumpWordWrap = 40;
+ } else {
+ $this->_dumpWordWrap = $wordwrap;
+ }
+
+ // New YAML document
+ $string = "---\n";
+
+ // Start at the base of the array and move through it.
+ foreach ($array as $key => $value) {
+ $string .= $this->_yamlize($key,$value,0);
+ }
+ return $string;
+ }
+
+ /**** Private Properties ****/
+
+ /**#@+
+ * @access private
+ * @var mixed
+ */
+ var $_haveRefs;
+ var $_allNodes;
+ var $_lastIndent;
+ var $_lastNode;
+ var $_inBlock;
+ var $_isInline;
+ var $_dumpIndent;
+ var $_dumpWordWrap;
+ /**#@+*/
+
+ /**** Private Methods ****/
+
+ /**
+ * Attempts to convert a key / value array item to YAML
+ * @access private
+ * @return string
+ * @param $key The name of the key
+ * @param $value The value of the item
+ * @param $indent The indent of the current node
+ */
+ function _yamlize($key,$value,$indent) {
+ if (is_array($value)) {
+ // It has children. What to do?
+ // Make it the right kind of item
+ $string = $this->_dumpNode($key,NULL,$indent);
+ // Add the indent
+ $indent += $this->_dumpIndent;
+ // Yamlize the array
+ $string .= $this->_yamlizeArray($value,$indent);
+ } elseif (!is_array($value)) {
+ // It doesn't have children. Yip.
+ $string = $this->_dumpNode($key,$value,$indent);
+ }
+ return $string;
+ }
+
+ /**
+ * Attempts to convert an array to YAML
+ * @access private
+ * @return string
+ * @param $array The array you want to convert
+ * @param $indent The indent of the current level
+ */
+ function _yamlizeArray($array,$indent) {
+ if (is_array($array)) {
+ $string = '';
+ foreach ($array as $key => $value) {
+ $string .= $this->_yamlize($key,$value,$indent);
+ }
+ return $string;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns YAML from a key and a value
+ * @access private
+ * @return string
+ * @param $key The name of the key
+ * @param $value The value of the item
+ * @param $indent The indent of the current node
+ */
+ function _dumpNode($key,$value,$indent) {
+ // do some folding here, for blocks
+ if (strpos($value,"\n")) {
+ $value = $this->_doLiteralBlock($value,$indent);
+ } else {
+ $value = $this->_doFolding($value,$indent);
+ }
+
+ $spaces = str_repeat(' ',$indent);
+
+ if (is_int($key)) {
+ // It's a sequence
+ $string = $spaces.'- '.$value."\n";
+ } else {
+ // It's mapped
+ $string = $spaces.$key.': '.$value."\n";
+ }
+ return $string;
+ }
+
+ /**
+ * Creates a literal block for dumping
+ * @access private
+ * @return string
+ * @param $value
+ * @param $indent int The value of the indent
+ */
+ function _doLiteralBlock($value,$indent) {
+ $exploded = explode("\n",$value);
+ $newValue = '|';
+ $indent += $this->_dumpIndent;
+ $spaces = str_repeat(' ',$indent);
+ foreach ($exploded as $line) {
+ $newValue .= "\n" . $spaces . trim($line);
+ }
+ return $newValue;
+ }
+
+ /**
+ * Folds a string of text, if necessary
+ * @access private
+ * @return string
+ * @param $value The string you wish to fold
+ */
+ function _doFolding($value,$indent) {
+ // Don't do anything if wordwrap is set to 0
+ if ($this->_dumpWordWrap === 0) {
+ return $value;
+ }
+
+ if (strlen($value) > $this->_dumpWordWrap) {
+ $indent += $this->_dumpIndent;
+ $indent = str_repeat(' ',$indent);
+ $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
+ $value = ">\n".$indent.$wrapped;
+ }
+ return $value;
+ }
+
+ /* Methods used in loading */
+
+ /**
+ * Finds and returns the indentation of a YAML line
+ * @access private
+ * @return int
+ * @param string $line A line from the YAML file
+ */
+ function _getIndent($line) {
+ preg_match('/^\s{1,}/',$line,$match);
+ if (!empty($match[0])) {
+ $indent = substr_count($match[0],' ');
+ } else {
+ $indent = 0;
+ }
+ return $indent;
+ }
+
+ /**
+ * Parses YAML code and returns an array for a node
+ * @access private
+ * @return array
+ * @param string $line A line from the YAML file
+ */
+ function _parseLine($line) {
+ $line = trim($line);
+
+ $array = array();
+
+ if (preg_match('/^-(.*):$/',$line)) {
+ // It's a mapped sequence
+ $key = trim(substr(substr($line,1),0,-1));
+ $array[$key] = '';
+ } elseif ($line[0] == '-' && substr($line,0,3) != '---') {
+ // It's a list item but not a new stream
+ if (strlen($line) > 1) {
+ $value = trim(substr($line,1));
+ // Set the type of the value. Int, string, etc
+ $value = $this->_toType($value);
+ $array[] = $value;
+ } else {
+ $array[] = array();
+ }
+ } elseif (preg_match('/^(.+):/',$line,$key)) {
+ // It's a key/value pair most likely
+ // If the key is in double quotes pull it out
+ if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
+ $value = trim(str_replace($matches[1],'',$line));
+ $key = $matches[2];
+ } else {
+ // Do some guesswork as to the key and the value
+ $explode = explode(':',$line);
+ $key = trim($explode[0]);
+ array_shift($explode);
+ $value = trim(implode(':',$explode));
+ }
+
+ // Set the type of the value. Int, string, etc
+ $value = $this->_toType($value);
+ if (empty($key)) {
+ $array[] = $value;
+ } else {
+ $array[$key] = $value;
+ }
+ }
+ return $array;
+ }
+
+ /**
+ * Finds the type of the passed value, returns the value as the new type.
+ * @access private
+ * @param string $value
+ * @return mixed
+ */
+ function _toType($value) {
+ if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {
+ $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches));
+ $value = preg_replace('/\\\\"/','"',$value);
+ } elseif (preg_match('/^\\[(.+)\\]$/',$value,$matches)) {
+ // Inline Sequence
+
+ // Take out strings sequences and mappings
+ $explode = $this->_inlineEscape($matches[1]);
+
+ // Propogate value array
+ $value = array();
+ foreach ($explode as $v) {
+ $value[] = $this->_toType($v);
+ }
+ } elseif (strpos($value,': ')!==false && !preg_match('/^{(.+)/',$value)) {
+ // It's a map
+ $array = explode(': ',$value);
+ $key = trim($array[0]);
+ array_shift($array);
+ $value = trim(implode(': ',$array));
+ $value = $this->_toType($value);
+ $value = array($key => $value);
+ } elseif (preg_match("/{(.+)}$/",$value,$matches)) {
+ // Inline Mapping
+
+ // Take out strings sequences and mappings
+ $explode = $this->_inlineEscape($matches[1]);
+
+ // Propogate value array
+ $array = array();
+ foreach ($explode as $v) {
+ $array = $array + $this->_toType($v);
+ }
+ $value = $array;
+ } elseif (strtolower($value) == 'null' or $value == '' or $value == '~') {
+ $value = NULL;
+ } elseif (ctype_digit($value)) {
+ $value = (int)$value;
+ } elseif (in_array(strtolower($value),
+ array('true', 'on', '+', 'yes', 'y'))) {
+ $value = TRUE;
+ } elseif (in_array(strtolower($value),
+ array('false', 'off', '-', 'no', 'n'))) {
+ $value = FALSE;
+ } elseif (is_numeric($value)) {
+ $value = (float)$value;
+ } else {
+ // Just a normal string, right?
+ $value = trim(preg_replace('/#(.+)$/','',$value));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Used in inlines to check for more inlines or quoted strings
+ * @access private
+ * @return array
+ */
+ function _inlineEscape($inline) {
+ // There's gotta be a cleaner way to do this...
+ // While pure sequences seem to be nesting just fine,
+ // pure mappings and mappings with sequences inside can't go very
+ // deep. This needs to be fixed.
+
+ // Check for strings
+ $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
+ if (preg_match_all($regex,$inline,$strings)) {
+ $saved_strings[] = $strings[0][0];
+ $inline = preg_replace($regex,'YAMLString',$inline);
+ }
+ unset($regex);
+
+ // Check for sequences
+ if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) {
+ $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline);
+ $seqs = $seqs[0];
+ }
+
+ // Check for mappings
+ if (preg_match_all('/{(.+)}/U',$inline,$maps)) {
+ $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline);
+ $maps = $maps[0];
+ }
+
+ $explode = explode(', ',$inline);
+
+ // Re-add the strings
+ if (!empty($saved_strings)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLString')) {
+ $explode[$key] = str_replace('YAMLString',$saved_strings[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ // Re-add the sequences
+ if (!empty($seqs)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLSeq') !== false) {
+ $explode[$key] = str_replace('YAMLSeq',$seqs[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ // Re-add the mappings
+ if (!empty($maps)) {
+ $i = 0;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLMap') !== false) {
+ $explode[$key] = str_replace('YAMLMap',$maps[$i],$value);
+ ++$i;
+ }
+ }
+ }
+
+ return $explode;
+ }
+
+ /**
+ * Builds the PHP array from all the YAML nodes we've gathered
+ * @access private
+ * @return array
+ */
+ function _buildArray() {
+ $trunk = array();
+
+ if (!isset($this->_indentSort[0])) {
+ return $trunk;
+ }
+
+ foreach ($this->_indentSort[0] as $n) {
+ if (empty($n->parent)) {
+ $this->_nodeArrayizeData($n);
+ // Check for references and copy the needed data to complete them.
+ $this->_makeReferences($n);
+ // Merge our data with the big array we're building
+ $trunk = $this->_array_kmerge($trunk,$n->data);
+ }
+ }
+
+ return $trunk;
+ }
+
+ /**
+ * Traverses node-space and sets references (& and *) accordingly
+ * @access private
+ * @return bool
+ */
+ function _linkReferences() {
+ if (is_array($this->_haveRefs)) {
+ foreach ($this->_haveRefs as $node) {
+ if (!empty($node->data)) {
+ $key = key($node->data);
+ // If it's an array, don't check.
+ if (is_array($node->data[$key])) {
+ foreach ($node->data[$key] as $k => $v) {
+ $this->_linkRef($node,$key,$k,$v);
+ }
+ } else {
+ $this->_linkRef($node,$key);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ function _linkRef(&$n,$key,$k = NULL,$v = NULL) {
+ if (empty($k) && empty($v)) {
+ // Look for &refs
+ if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) {
+ // Flag the node so we know it's a reference
+ $this->_allNodes[$n->id]->ref = substr($matches[0],1);
+ $this->_allNodes[$n->id]->data[$key] =
+ substr($n->data[$key],strlen($matches[0])+1);
+ // Look for *refs
+ } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) {
+ $ref = substr($matches[0],1);
+ // Flag the node as having a reference
+ $this->_allNodes[$n->id]->refKey = $ref;
+ }
+ } elseif (!empty($k) && !empty($v)) {
+ if (preg_match('/^&([^ ]+)/',$v,$matches)) {
+ // Flag the node so we know it's a reference
+ $this->_allNodes[$n->id]->ref = substr($matches[0],1);
+ $this->_allNodes[$n->id]->data[$key][$k] =
+ substr($v,strlen($matches[0])+1);
+ // Look for *refs
+ } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) {
+ $ref = substr($matches[0],1);
+ // Flag the node as having a reference
+ $this->_allNodes[$n->id]->refKey = $ref;
+ }
+ }
+ }
+
+ /**
+ * Finds the children of a node and aids in the building of the PHP array
+ * @access private
+ * @param int $nid The id of the node whose children we're gathering
+ * @return array
+ */
+ function _gatherChildren($nid) {
+ $return = array();
+ $node =& $this->_allNodes[$nid];
+ foreach ($this->_allNodes as $z) {
+ if ($z->parent == $node->id) {
+ // We found a child
+ $this->_nodeArrayizeData($z);
+ // Check for references
+ $this->_makeReferences($z);
+ // Merge with the big array we're returning
+ // The big array being all the data of the children of our parent node
+ $return = $this->_array_kmerge($return,$z->data);
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Turns a node's data and its children's data into a PHP array
+ *
+ * @access private
+ * @param array $node The node which you want to arrayize
+ * @return boolean
+ */
+ function _nodeArrayizeData(&$node) {
+ if (is_array($node->data) && $node->children == true) {
+ // This node has children, so we need to find them
+ $childs = $this->_gatherChildren($node->id);
+ // We've gathered all our children's data and are ready to use it
+ $key = key($node->data);
+ $key = empty($key) ? 0 : $key;
+ // If it's an array, add to it of course
+ if (is_array($node->data[$key])) {
+ $node->data[$key] = $this->_array_kmerge($node->data[$key],$childs);
+ } else {
+ $node->data[$key] = $childs;
+ }
+ } elseif (!is_array($node->data) && $node->children == true) {
+ // Same as above, find the children of this node
+ $childs = $this->_gatherChildren($node->id);
+ $node->data = array();
+ $node->data[] = $childs;
+ }
+
+ // We edited $node by reference, so just return true
+ return true;
+ }
+
+ /**
+ * Traverses node-space and copies references to / from this object.
+ * @access private
+ * @param object $z A node whose references we wish to make real
+ * @return bool
+ */
+ function _makeReferences(&$z) {
+ // It is a reference
+ if (isset($z->ref)) {
+ $key = key($z->data);
+ // Copy the data to this object for easy retrieval later
+ $this->ref[$z->ref] =& $z->data[$key];
+ // It has a reference
+ } elseif (isset($z->refKey)) {
+ if (isset($this->ref[$z->refKey])) {
+ $key = key($z->data);
+ // Copy the data from this object to make the node a real reference
+ $z->data[$key] =& $this->ref[$z->refKey];
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Merges arrays and maintains numeric keys.
+ *
+ * An ever-so-slightly modified version of the array_kmerge() function posted
+ * to php.net by mail at nospam dot iaindooley dot com on 2004-04-08.
+ *
+ * http://us3.php.net/manual/en/function.array-merge.php#41394
+ *
+ * @access private
+ * @param array $arr1
+ * @param array $arr2
+ * @return array
+ */
+ function _array_kmerge($arr1,$arr2) {
+ if(!is_array($arr1))
+ $arr1 = array();
+
+ if(!is_array($arr2))
+ $arr2 = array();
+
+ $keys1 = array_keys($arr1);
+ $keys2 = array_keys($arr2);
+ $keys = array_merge($keys1,$keys2);
+ $vals1 = array_values($arr1);
+ $vals2 = array_values($arr2);
+ $vals = array_merge($vals1,$vals2);
+ $ret = array();
+
+ foreach($keys as $key) {
+ list($unused,$val) = each($vals);
+ // This is the good part! If a key already exists, but it's part of a
+ // sequence (an int), just keep addin numbers until we find a fresh one.
+ if (isset($ret[$key]) and is_int($key)) {
+ while (array_key_exists($key, $ret)) {
+ $key++;
+ }
+ }
+ $ret[$key] = $val;
+ }
+
+ return $ret;
+ }
+ }
+?> \ No newline at end of file
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
new file mode 100644
index 00000000..33fb67fd
--- /dev/null
+++ b/includes/api/ApiHelp.php
@@ -0,0 +1,55 @@
+<?php
+
+
+/*
+ * Created on Sep 6, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+class ApiHelp extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ }
+
+ /**
+ * Stub module for displaying help when no parameters are given
+ */
+ public function execute() {
+ $this->dieUsage('', 'help');
+ }
+
+ protected function getDescription() {
+ return array (
+ 'Display this help screen.'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiHelp.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
new file mode 100644
index 00000000..2aa571c1
--- /dev/null
+++ b/includes/api/ApiLogin.php
@@ -0,0 +1,122 @@
+<?php
+
+
+/*
+ * Created on Sep 19, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+class ApiLogin extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action, 'lg');
+ }
+
+ public function execute() {
+ $name = $password = $domain = null;
+ extract($this->extractRequestParams());
+
+ $params = new FauxRequest(array (
+ 'wpName' => $name,
+ 'wpPassword' => $password,
+ 'wpDomain' => $domain,
+ 'wpRemember' => ''
+ ));
+
+ $result = array ();
+
+ $loginForm = new LoginForm($params);
+ switch ($loginForm->authenticateUserData()) {
+ case LoginForm :: SUCCESS :
+ global $wgUser;
+
+ $wgUser->setOption('rememberpassword', 1);
+ $wgUser->setCookies();
+
+ $result['result'] = 'Success';
+ $result['lguserid'] = $_SESSION['wsUserID'];
+ $result['lgusername'] = $_SESSION['wsUserName'];
+ $result['lgtoken'] = $_SESSION['wsToken'];
+ break;
+
+ case LoginForm :: NO_NAME :
+ $result['result'] = 'NoName';
+ break;
+ case LoginForm :: ILLEGAL :
+ $result['result'] = 'Illegal';
+ break;
+ case LoginForm :: WRONG_PLUGIN_PASS :
+ $result['result'] = 'WrongPluginPass';
+ break;
+ case LoginForm :: NOT_EXISTS :
+ $result['result'] = 'NotExists';
+ break;
+ case LoginForm :: WRONG_PASS :
+ $result['result'] = 'WrongPass';
+ break;
+ case LoginForm :: EMPTY_PASS :
+ $result['result'] = 'EmptyPass';
+ break;
+ default :
+ ApiBase :: dieDebug(__METHOD__, 'Unhandled case value');
+ }
+
+ $this->getResult()->addValue(null, 'login', $result);
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'name' => '',
+ 'password' => '',
+ 'domain' => null
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'name' => 'User Name',
+ 'password' => 'Password',
+ 'domain' => 'Domain (optional)'
+ );
+ }
+
+ protected function getDescription() {
+ return array (
+ 'This module is used to login and get the authentication tokens.'
+ );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=login&lgname=user&lgpassword=password'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiLogin.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?>
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
new file mode 100644
index 00000000..046d7d7c
--- /dev/null
+++ b/includes/api/ApiMain.php
@@ -0,0 +1,226 @@
+<?php
+
+
+/*
+ * Created on Sep 4, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+class ApiMain extends ApiBase {
+
+ private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames;
+ private $mApiStartTime, $mResult, $mShowVersions, $mEnableWrite;
+
+ /**
+ * Constructor
+ * $apiStartTime - time of the originating call for profiling purposes
+ * $modules - an array of actions (keys) and classes that handle them (values)
+ */
+ public function __construct($apiStartTime, $modules, $formats, $enableWrite) {
+ // Special handling for the main module: $parent === $this
+ parent :: __construct($this, 'main');
+
+ $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;
+ }
+
+ public function & getResult() {
+ return $this->mResult;
+ }
+
+ public function getShowVersions() {
+ return $this->mShowVersions;
+ }
+
+ public function requestWriteMode() {
+ if (!$this->mEnableWrite)
+ $this->dieUsage('Editing of this site is disabled. Make sure the $wgEnableWriteAPI=true; ' .
+ 'statement is included in the site\'s LocalSettings.php file', 'readonly');
+ }
+
+ 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
+ );
+ }
+
+ 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 execute() {
+ $this->profileIn();
+ $action = $format = $version = null;
+ try {
+ extract($this->extractRequestParams());
+ $this->mShowVersions = $version;
+
+ // Create an appropriate printer
+ $this->mPrinter = new $this->mFormats[$format] ($this, $format);
+
+ // Instantiate and execute module requested by the user
+ $module = new $this->mModules[$action] ($this, $action);
+ $module->profileIn();
+ $module->execute();
+ $module->profileOut();
+ $this->printResult(false);
+
+ } catch (UsageException $e) {
+
+ // 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);
+ $this->printResult(true);
+
+ }
+ $this->profileOut();
+ }
+
+ /**
+ * Internal printer
+ */
+ private 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 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',
+ ''
+ );
+ }
+
+ 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
+ );
+ ApiResult :: setContent($data, $this->makeHelpMsg());
+ $this->mResult->addValue(null, 'error', $data);
+
+ throw new UsageException($description, $errorCode);
+ }
+
+ /**
+ * Override the parent to generate help messages for all available modules.
+ */
+ public function makeHelpMsg() {
+
+ // Use parent to make default message for the main module
+ $msg = parent :: makeHelpMsg();
+
+ $astriks = str_repeat('*** ', 10);
+ $msg .= "\n\n$astriks Modules $astriks\n\n";
+ foreach ($this->mModules as $moduleName => $moduleClass) {
+ $msg .= "* action=$moduleName *";
+ $module = new $this->mModules[$moduleName] ($this, $moduleName);
+ $msg2 = $module->makeHelpMsg();
+ if ($msg2 !== false)
+ $msg .= $msg2;
+ $msg .= "\n";
+ }
+
+ $msg .= "\n$astriks Formats $astriks\n\n";
+ foreach ($this->mFormats as $moduleName => $moduleClass) {
+ $msg .= "* format=$moduleName *";
+ $module = new $this->mFormats[$moduleName] ($this, $moduleName);
+ $msg2 = $module->makeHelpMsg();
+ if ($msg2 !== false)
+ $msg .= $msg2;
+ $msg .= "\n";
+ }
+
+ return $msg;
+ }
+
+ private $mIsBot = null;
+ public function isBot() {
+ if (!isset ($this->mIsBot)) {
+ global $wgUser;
+ $this->mIsBot = $wgUser->isAllowed('bot');
+ }
+ return $this->mIsBot;
+ }
+
+ public function getVersion() {
+ $vers = array ();
+ $vers[] = __CLASS__ . ': $Id: ApiMain.php 16820 2006-10-06 01:02:14Z yurik $';
+ $vers[] = ApiBase :: getBaseVersion();
+ $vers[] = ApiFormatBase :: getBaseVersion();
+ $vers[] = ApiQueryBase :: getBaseVersion();
+ return $vers;
+ }
+}
+
+/**
+* @desc This exception will be thrown when dieUsage is called to stop module execution.
+*/
+class UsageException extends Exception {
+
+ private $codestr;
+
+ public function __construct($message, $codestr) {
+ parent :: __construct($message);
+ $this->codestr = $codestr;
+ }
+ public function __toString() {
+ return "{$this->codestr}: {$this->message}";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
new file mode 100644
index 00000000..d2384b39
--- /dev/null
+++ b/includes/api/ApiPageSet.php
@@ -0,0 +1,514 @@
+<?php
+
+
+/*
+ * Created on Sep 24, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiQueryBase.php');
+}
+
+class ApiPageSet extends ApiQueryBase {
+
+ private $mAllPages; // [ns][dbkey] => page_id or 0 when missing
+ private $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles;
+ private $mResolveRedirects, $mPendingRedirectIDs;
+
+ private $mRequestedPageFields;
+
+ public function __construct($query, $resolveRedirects = false) {
+ parent :: __construct($query, __CLASS__);
+
+ $this->mAllPages = array ();
+ $this->mGoodTitles = array ();
+ $this->mMissingTitles = array ();
+ $this->mMissingPageIDs = array ();
+ $this->mRedirectTitles = array ();
+ $this->mNormalizedTitles = array ();
+
+ $this->mRequestedPageFields = array ();
+ $this->mResolveRedirects = $resolveRedirects;
+ if($resolveRedirects)
+ $this->mPendingRedirectIDs = array();
+ }
+
+ public function isResolvingRedirects() {
+ return $this->mResolveRedirects;
+ }
+
+ public function requestField($fieldName) {
+ $this->mRequestedPageFields[$fieldName] = null;
+ }
+
+ public function getCustomField($fieldName) {
+ return $this->mRequestedPageFields[$fieldName];
+ }
+
+ /**
+ * Get fields that modules have requested from the page table
+ */
+ public function getPageTableFields() {
+ // Ensure we get minimum required fields
+ $pageFlds = array (
+ 'page_id' => null,
+ 'page_namespace' => null,
+ 'page_title' => null
+ );
+
+ // only store non-default fields
+ $this->mRequestedPageFields = array_diff_key($this->mRequestedPageFields, $pageFlds);
+
+ if ($this->mResolveRedirects)
+ $pageFlds['page_is_redirect'] = null;
+
+ return array_keys(array_merge($pageFlds, $this->mRequestedPageFields));
+ }
+
+ /**
+ * Title objects that were found in the database.
+ * @return array page_id (int) => Title (obj)
+ */
+ public function getGoodTitles() {
+ return $this->mGoodTitles;
+ }
+
+ /**
+ * Returns the number of unique pages (not revisions) in the set.
+ */
+ public function getGoodTitleCount() {
+ return count($this->getGoodTitles());
+ }
+
+ /**
+ * Title objects that were NOT found in the database.
+ * @return array of Title objects
+ */
+ public function getMissingTitles() {
+ return $this->mMissingTitles;
+ }
+
+ /**
+ * Page IDs that were not found in the database
+ * @return array of page IDs
+ */
+ public function getMissingPageIDs() {
+ return $this->mMissingPageIDs;
+ }
+
+ /**
+ * Get a list of redirects when doing redirect resolution
+ * @return array prefixed_title (string) => prefixed_title (string)
+ */
+ public function getRedirectTitles() {
+ return $this->mRedirectTitles;
+ }
+
+ /**
+ * Get a list of title normalizations - maps the title given
+ * with its normalized version.
+ * @return array raw_prefixed_title (string) => prefixed_title (string)
+ */
+ public function getNormalizedTitles() {
+ return $this->mNormalizedTitles;
+ }
+
+ /**
+ * Get the list of revision IDs (requested with revids= parameter)
+ */
+ public function getRevisionIDs() {
+ $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented');
+ }
+
+ /**
+ * Returns the number of revisions (requested with revids= parameter)
+ */
+ public function getRevisionCount() {
+ return 0; // TODO: implement
+ }
+
+ /**
+ * Populate from the request parameters
+ */
+ public function execute() {
+ $this->profileIn();
+ $titles = $pageids = $revids = null;
+ extract($this->extractRequestParams());
+
+ // Only one of the titles/pageids/revids is allowed at the same time
+ $dataSource = null;
+ if (isset ($titles))
+ $dataSource = 'titles';
+ if (isset ($pageids)) {
+ if (isset ($dataSource))
+ $this->dieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource');
+ $dataSource = 'pageids';
+ }
+ if (isset ($revids)) {
+ if (isset ($dataSource))
+ $this->dieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource');
+ $dataSource = 'revids';
+ }
+
+ switch ($dataSource) {
+ case 'titles' :
+ $this->initFromTitles($titles);
+ break;
+ case 'pageids' :
+ $this->initFromPageIds($pageids);
+ break;
+ case 'revids' :
+ $this->initFromRevIDs($revids);
+ break;
+ default :
+ // Do nothing - some queries do not need any of the data sources.
+ break;
+ }
+ $this->profileOut();
+ }
+
+ /**
+ * Initialize PageSet from a list of Titles
+ */
+ public function populateFromTitles($titles) {
+ $this->profileIn();
+ $this->initFromTitles($titles);
+ $this->profileOut();
+ }
+
+ /**
+ * Initialize PageSet from a list of Page IDs
+ */
+ public function populateFromPageIDs($pageIDs) {
+ $this->profileIn();
+ $pageIDs = array_map('intval', $pageIDs); // paranoia
+ $this->initFromPageIds($pageIDs);
+ $this->profileOut();
+ }
+
+ /**
+ * Initialize PageSet from a rowset returned from the database
+ */
+ public function populateFromQueryResult($db, $queryResult) {
+ $this->profileIn();
+ $this->initFromQueryResult($db, $queryResult);
+ $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;
+
+ if ($this->mResolveRedirects && $row->page_is_redirect == '1') {
+ $this->mPendingRedirectIDs[$pageId] = $title;
+ } else {
+ $this->mGoodTitles[$pageId] = $title;
+ }
+
+ foreach ($this->mRequestedPageFields as $fieldName => & $fieldValues)
+ $fieldValues[$pageId] = $row-> $fieldName;
+ }
+
+ public function finishPageSetGeneration() {
+ $this->profileIn();
+ $this->resolvePendingRedirects();
+ $this->profileOut();
+ }
+
+ /**
+ * This method populates internal variables with page information
+ * based on the given array of title strings.
+ *
+ * Steps:
+ * #1 For each title, get data from `page` table
+ * #2 If page was not found in the DB, store it as missing
+ *
+ * Additionally, when resolving redirects:
+ * #3 If no more redirects left, stop.
+ * #4 For each redirect, get its links from `pagelinks` table.
+ * #5 Substitute the original LinkBatch object with the new list
+ * #6 Repeat from step #1
+ */
+ private function initFromTitles($titles) {
+ $db = $this->getDB();
+
+ // Get validated and normalized title objects
+ $linkBatch = $this->processTitlesStrArray($titles);
+ $set = $linkBatch->constructSet('page', $db);
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
+ $this->profileDBOut();
+
+ // Hack: get the ns:titles stored in array(ns => array(titles)) format
+ $this->initFromQueryResult($db, $res, $linkBatch->data, true); // process Titles
+
+ // Resolve any found redirects
+ $this->resolvePendingRedirects();
+ }
+
+ private function initFromPageIds($pageids) {
+ $db = $this->getDB();
+
+ $set = array (
+ 'page_id' => $pageids
+ );
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__);
+ $this->profileDBOut();
+
+ $this->initFromQueryResult($db, $res, array_flip($pageids), false); // process PageIDs
+
+ // Resolve any found redirects
+ $this->resolvePendingRedirects();
+ }
+
+ /**
+ * Iterate through the result of the query on 'page' table,
+ * and for each row create and store title object and save any extra fields requested.
+ * @param $db Database
+ * @param $res DB Query result
+ * @param $remaining Array of either pageID or ns/title elements (optional).
+ * If given, any missing items will go to $mMissingPageIDs and $mMissingTitles
+ * @param $processTitles bool Must be provided together with $remaining.
+ * If true, treat $remaining as an array of [ns][title]
+ * If false, treat it as an array of [pageIDs]
+ * @return Array of redirect IDs (only when resolving redirects)
+ */
+ private function initFromQueryResult($db, $res, &$remaining = null, $processTitles = null) {
+ if (!is_null($remaining) && is_null($processTitles))
+ $this->dieDebug('Missing $processTitles parameter when $remaining is provided');
+
+ while ($row = $db->fetchObject($res)) {
+
+ $pageId = intval($row->page_id);
+
+ // Remove found page from the list of remaining items
+ if (isset($remaining)) {
+ if ($processTitles)
+ unset ($remaining[$row->page_namespace][$row->page_title]);
+ else
+ unset ($remaining[$pageId]);
+ }
+
+ // Store any extra fields requested by modules
+ $this->processDbRow($row);
+ }
+ $db->freeResult($res);
+
+ if(isset($remaining)) {
+ // Any items left in the $remaining list are added as missing
+ if($processTitles) {
+ // The remaining titles in $remaining are non-existant pages
+ foreach ($remaining as $ns => $dbkeys) {
+ foreach ($dbkeys as $dbkey => $nothing) {
+ $this->mMissingTitles[] = Title :: makeTitle($ns, $dbkey);
+ $this->mAllPages[$ns][$dbkey] = 0;
+ }
+ }
+ }
+ else
+ {
+ // The remaining pageids do not exist
+ if(empty($this->mMissingPageIDs))
+ $this->mMissingPageIDs = array_keys($remaining);
+ else
+ $this->mMissingPageIDs = array_merge($this->mMissingPageIDs, array_keys($remaining));
+ }
+ }
+ }
+
+ private function initFromRevIDs($revids) {
+ $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented');
+ }
+
+ private function resolvePendingRedirects() {
+
+ if($this->mResolveRedirects) {
+ $db = $this->getDB();
+ $pageFlds = $this->getPageTableFields();
+
+ // Repeat until all redirects have been resolved
+ // The infinite loop is prevented by keeping all known pages in $this->mAllPages
+ while (!empty ($this->mPendingRedirectIDs)) {
+
+ // Resolve redirects by querying the pagelinks table, and repeat the process
+ // Create a new linkBatch object for the next pass
+ $linkBatch = $this->getRedirectTargets();
+
+ if ($linkBatch->isEmpty())
+ break;
+
+ $set = $linkBatch->constructSet('page', $db);
+ if(false === $set)
+ break;
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select('page', $pageFlds, $set, __METHOD__);
+ $this->profileDBOut();
+
+ // Hack: get the ns:titles stored in array(ns => array(titles)) format
+ $this->initFromQueryResult($db, $res, $linkBatch->data, true);
+ }
+ }
+ }
+
+ private function getRedirectTargets() {
+
+ $linkBatch = new LinkBatch();
+ $db = $this->getDB();
+
+ // find redirect targets for all redirect pages
+ $this->profileDBIn();
+ $res = $db->select('pagelinks', array (
+ 'pl_from',
+ 'pl_namespace',
+ 'pl_title'
+ ), array (
+ 'pl_from' => array_keys($this->mPendingRedirectIDs
+ )), __METHOD__);
+ $this->profileDBOut();
+
+ while ($row = $db->fetchObject($res)) {
+
+ $plfrom = intval($row->pl_from);
+
+ // Bug 7304 workaround
+ // ( http://bugzilla.wikipedia.org/show_bug.cgi?id=7304 )
+ // A redirect page may have more than one link.
+ // This code will only use the first link returned.
+ if (isset ($this->mPendingRedirectIDs[$plfrom])) { // remove line when bug 7304 is fixed
+
+ $titleStrFrom = $this->mPendingRedirectIDs[$plfrom]->getPrefixedText();
+ $titleStrTo = Title :: makeTitle($row->pl_namespace, $row->pl_title)->getPrefixedText();
+ unset ($this->mPendingRedirectIDs[$plfrom]); // remove line when bug 7304 is fixed
+
+ // Avoid an infinite loop by checking if we have already processed this target
+ if (!isset ($this->mAllPages[$row->pl_namespace][$row->pl_title])) {
+ $linkBatch->add($row->pl_namespace, $row->pl_title);
+ }
+ } else {
+ // This redirect page has more than one link.
+ // This is very slow, but safer until bug 7304 is resolved
+ $title = Title :: newFromID($plfrom);
+ $titleStrFrom = $title->getPrefixedText();
+
+ $article = new Article($title);
+ $text = $article->getContent();
+ $titleTo = Title :: newFromRedirect($text);
+ $titleStrTo = $titleTo->getPrefixedText();
+
+ if (is_null($titleStrTo))
+ ApiBase :: dieDebug(__METHOD__, 'Bug7304 workaround: redir target from {$title->getPrefixedText()} not found');
+
+ // Avoid an infinite loop by checking if we have already processed this target
+ if (!isset ($this->mAllPages[$titleTo->getNamespace()][$titleTo->getDBkey()])) {
+ $linkBatch->addObj($titleTo);
+ }
+ }
+
+ $this->mRedirectTitles[$titleStrFrom] = $titleStrTo;
+ }
+ $db->freeResult($res);
+
+ // All IDs must exist in the page table
+ if (!empty($this->mPendingRedirectIDs[$plfrom]))
+ $this->dieDebug('Invalid redirect IDs were found');
+
+ return $linkBatch;
+ }
+
+ /**
+ * Given an array of title strings, convert them into Title objects.
+ * This method validates access rights for the title,
+ * and appends normalization values to the output.
+ *
+ * @return LinkBatch of title objects.
+ */
+ private function processTitlesStrArray($titles) {
+
+ $linkBatch = new LinkBatch();
+
+ foreach ($titles as $titleString) {
+ $titleObj = Title :: newFromText($titleString);
+
+ // Validation
+ if (!$titleObj)
+ $this->dieUsage("bad title $titleString", 'invalidtitle');
+ if ($titleObj->getNamespace() < 0)
+ $this->dieUsage("No support for special page $titleString has been implemented", 'unsupportednamespace');
+ if (!$titleObj->userCanRead())
+ $this->dieUsage("No read permission for $titleString", 'titleaccessdenied');
+
+ $linkBatch->addObj($titleObj);
+
+ // Make sure we remember the original title that was given to us
+ // This way the caller can correlate new titles with the originally requested,
+ // i.e. namespace is localized or capitalization is different
+ if ($titleString !== $titleObj->getPrefixedText()) {
+ $this->mNormalizedTitles[$titleString] = $titleObj->getPrefixedText();
+ }
+ }
+
+ return $linkBatch;
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'titles' => array (
+ ApiBase :: PARAM_ISMULTI => true
+ ),
+ 'pageids' => array (
+ ApiBase :: PARAM_TYPE => 'integer',
+ ApiBase :: PARAM_ISMULTI => true
+ ),
+ 'revids' => array (
+ ApiBase :: PARAM_TYPE => 'integer',
+ ApiBase :: PARAM_ISMULTI => true
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'titles' => 'A list of titles to work on',
+ 'pageids' => 'A list of page IDs to work on',
+ 'revids' => 'A list of revision IDs to work on'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiPageSet.php 16820 2006-10-06 01:02:14Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
new file mode 100644
index 00000000..985bde63
--- /dev/null
+++ b/includes/api/ApiQuery.php
@@ -0,0 +1,354 @@
+<?php
+
+
+/*
+ * Created on Sep 7, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+class ApiQuery extends ApiBase {
+
+ private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
+ private $mPageSet;
+
+ private $mQueryPropModules = array (
+ 'info' => 'ApiQueryInfo',
+ 'revisions' => 'ApiQueryRevisions'
+ );
+ // 'categories' => 'ApiQueryCategories',
+ // 'imageinfo' => 'ApiQueryImageinfo',
+ // 'langlinks' => 'ApiQueryLanglinks',
+ // 'links' => 'ApiQueryLinks',
+ // 'templates' => 'ApiQueryTemplates',
+
+ private $mQueryListModules = array (
+ 'allpages' => 'ApiQueryAllpages'
+ );
+ // 'backlinks' => 'ApiQueryBacklinks',
+ // 'categorymembers' => 'ApiQueryCategorymembers',
+ // 'embeddedin' => 'ApiQueryEmbeddedin',
+ // 'imagelinks' => 'ApiQueryImagelinks',
+ // 'logevents' => 'ApiQueryLogevents',
+ // 'recentchanges' => 'ApiQueryRecentchanges',
+ // 'usercontribs' => 'ApiQueryUsercontribs',
+ // 'users' => 'ApiQueryUsers',
+ // 'watchlist' => 'ApiQueryWatchlist',
+
+ private $mQueryMetaModules = array (
+ 'siteinfo' => 'ApiQuerySiteinfo'
+ );
+ // 'userinfo' => 'ApiQueryUserinfo',
+
+ private $mSlaveDB = null;
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ $this->mPropModuleNames = array_keys($this->mQueryPropModules);
+ $this->mListModuleNames = array_keys($this->mQueryListModules);
+ $this->mMetaModuleNames = array_keys($this->mQueryMetaModules);
+
+ // Allow the entire list of modules at first,
+ // but during module instantiation check if it can be used as a generator.
+ $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames);
+ }
+
+ public function getDB() {
+ if (!isset ($this->mSlaveDB))
+ $this->mSlaveDB = & wfGetDB(DB_SLAVE);
+ return $this->mSlaveDB;
+ }
+
+ public function getPageSet() {
+ return $this->mPageSet;
+ }
+
+ /**
+ * Query execution happens in the following steps:
+ * #1 Create a PageSet object with any pages requested by the user
+ * #2 If using generator, execute it to get a new PageSet object
+ * #3 Instantiate all requested modules.
+ * This way the PageSet object will know what shared data is required,
+ * and minimize DB calls.
+ * #4 Output all normalization and redirect resolution information
+ * #5 Execute all requested modules
+ */
+ public function execute() {
+ $prop = $list = $meta = $generator = $redirects = null;
+ extract($this->extractRequestParams());
+
+ //
+ // Create PageSet
+ //
+ $this->mPageSet = new ApiPageSet($this, $redirects);
+
+ // Instantiate required modules
+ $modules = array ();
+ if (isset ($prop))
+ foreach ($prop as $moduleName)
+ $modules[] = new $this->mQueryPropModules[$moduleName] ($this, $moduleName);
+ if (isset ($list))
+ foreach ($list as $moduleName)
+ $modules[] = new $this->mQueryListModules[$moduleName] ($this, $moduleName);
+ if (isset ($meta))
+ foreach ($meta as $moduleName)
+ $modules[] = new $this->mQueryMetaModules[$moduleName] ($this, $moduleName);
+
+ // Modules may optimize data requests through the $this->getPageSet() object
+ // Execute all requested modules.
+ foreach ($modules as $module) {
+ $module->requestExtraData();
+ }
+
+ //
+ // If given, execute generator to substitute user supplied data with generated data.
+ //
+ if (isset ($generator))
+ $this->executeGeneratorModule($generator, $redirects);
+
+ //
+ // Populate page information for the given pageSet
+ //
+ $this->mPageSet->execute();
+
+ //
+ // Record page information (title, namespace, if exists, etc)
+ //
+ $this->outputGeneralPageInfo();
+
+ //
+ // Execute all requested modules.
+ //
+ foreach ($modules as $module) {
+ $module->profileIn();
+ $module->execute();
+ $module->profileOut();
+ }
+ }
+
+ private function outputGeneralPageInfo() {
+
+ $pageSet = $this->getPageSet();
+
+ // Title normalizations
+ $normValues = array ();
+ foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) {
+ $normValues[] = array (
+ 'from' => $rawTitleStr,
+ 'to' => $titleStr
+ );
+ }
+
+ if (!empty ($normValues)) {
+ ApiResult :: setIndexedTagName($normValues, 'n');
+ $this->getResult()->addValue('query', 'normalized', $normValues);
+ }
+
+ // Show redirect information
+ $redirValues = array ();
+ foreach ($pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo) {
+ $redirValues[] = array (
+ 'from' => $titleStrFrom,
+ 'to' => $titleStrTo
+ );
+ }
+
+ if (!empty ($redirValues)) {
+ ApiResult :: setIndexedTagName($redirValues, 'r');
+ $this->getResult()->addValue('query', 'redirects', $redirValues);
+ }
+
+ //
+ // Page elements
+ //
+ $pages = array ();
+
+ // Report any missing titles
+ $fakepageid = -1;
+ foreach ($pageSet->getMissingTitles() as $title) {
+ $pages[$fakepageid--] = array (
+ 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'missing' => '');
+ }
+
+ // Report any missing page ids
+ foreach ($pageSet->getMissingPageIDs() as $pageid) {
+ $pages[$pageid] = array (
+ 'id' => $pageid,
+ 'missing' => ''
+ );
+ }
+
+ // Output general page information for found titles
+ foreach ($pageSet->getGoodTitles() as $pageid => $title) {
+ $pages[$pageid] = array (
+ 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'id' => $pageid);
+ }
+
+ if (!empty ($pages)) {
+ ApiResult :: setIndexedTagName($pages, 'page');
+ $this->getResult()->addValue('query', 'pages', $pages);
+ }
+ }
+
+ protected function executeGeneratorModule($generatorName, $redirects) {
+
+ // Find class that implements requested generator
+ if (isset ($this->mQueryListModules[$generatorName])) {
+ $className = $this->mQueryListModules[$generatorName];
+ }
+ elseif (isset ($this->mQueryPropModules[$generatorName])) {
+ $className = $this->mQueryPropModules[$generatorName];
+ } else {
+ ApiBase :: dieDebug(__METHOD__, "Unknown generator=$generatorName");
+ }
+
+ // Use current pageset as the result, and create a new one just for the generator
+ $resultPageSet = $this->mPageSet;
+ $this->mPageSet = new ApiPageSet($this, $redirects);
+
+ // Create and execute the generator
+ $generator = new $className ($this, $generatorName);
+ if (!$generator instanceof ApiQueryGeneratorBase)
+ $this->dieUsage("Module $generatorName cannot be used as a generator", "badgenerator");
+
+ $generator->setGeneratorMode();
+ $generator->requestExtraData();
+
+ // execute current pageSet to get the data for the generator module
+ $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;
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'prop' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => $this->mPropModuleNames
+ ),
+ 'list' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => $this->mListModuleNames
+ ),
+ 'meta' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => $this->mMetaModuleNames
+ ),
+ 'generator' => array (
+ ApiBase :: PARAM_TYPE => $this->mAllowedGenerators
+ ),
+ 'redirects' => false
+ );
+ }
+
+ /**
+ * Override the parent to generate help messages for all available query modules.
+ */
+ public function makeHelpMsg() {
+
+ // Use parent to make default message for the query module
+ $msg = parent :: makeHelpMsg();
+
+ // Make sure the internal object is empty
+ // (just in case a sub-module decides to optimize during instantiation)
+ $this->mPageSet = null;
+
+ $astriks = str_repeat('--- ', 8);
+ $msg .= "\n$astriks Query: Prop $astriks\n\n";
+ $msg .= $this->makeHelpMsgHelper($this->mQueryPropModules, 'prop');
+ $msg .= "\n$astriks Query: List $astriks\n\n";
+ $msg .= $this->makeHelpMsgHelper($this->mQueryListModules, 'list');
+ $msg .= "\n$astriks Query: Meta $astriks\n\n";
+ $msg .= $this->makeHelpMsgHelper($this->mQueryMetaModules, 'meta');
+
+ return $msg;
+ }
+
+ private function makeHelpMsgHelper($moduleList, $paramName) {
+
+ $moduleDscriptions = array ();
+
+ foreach ($moduleList as $moduleName => $moduleClass) {
+ $msg = "* $paramName=$moduleName *";
+ $module = new $moduleClass ($this, $moduleName, null);
+ $msg2 = $module->makeHelpMsg();
+ if ($msg2 !== false)
+ $msg .= $msg2;
+ if ($module instanceof ApiQueryGeneratorBase)
+ $msg .= "Generator:\n This module may be used as a generator\n";
+ $moduleDscriptions[] = $msg;
+ }
+
+ return implode("\n", $moduleDscriptions);
+ }
+
+ /**
+ * Override to add extra parameters from PageSet
+ */
+ public function makeHelpMsgParameters() {
+ $psModule = new ApiPageSet($this);
+ return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters();
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'prop' => 'Which properties to get for the titles/revisions/pageids',
+ 'list' => 'Which lists to get',
+ 'meta' => 'Which meta data to get about the site',
+ 'generator' => 'Use the output of a list as the input for other prop/list/meta items',
+ 'redirects' => 'Automatically resolve redirects'
+ );
+ }
+
+ protected function getDescription() {
+ return array (
+ 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
+ 'and is loosely based on the Query API interface currently available on all MediaWiki servers.',
+ 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.'
+ );
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment'
+ );
+ }
+
+ public function getVersion() {
+ $psModule = new ApiPageSet($this);
+ $vers = array ();
+ $vers[] = __CLASS__ . ': $Id: ApiQuery.php 16820 2006-10-06 01:02:14Z yurik $';
+ $vers[] = $psModule->getVersion();
+ return $vers;
+ }
+}
+?>
diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php
new file mode 100644
index 00000000..51330d62
--- /dev/null
+++ b/includes/api/ApiQueryAllpages.php
@@ -0,0 +1,183 @@
+<?php
+
+
+/*
+ * Created on Sep 25, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiQueryBase.php');
+}
+
+class ApiQueryAllpages extends ApiQueryGeneratorBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'ap');
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ 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();
+
+ $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;
+ }
+
+ if (is_null($resultPageSet)) {
+ $fields = array (
+ 'page_id',
+ 'page_namespace',
+ 'page_title'
+ );
+ } else {
+ $fields = $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();
+
+ $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);
+ 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);
+ }
+ }
+ }
+ $db->freeResult($res);
+
+ if (is_null($resultPageSet)) {
+ ApiResult :: setIndexedTagName($data, 'p');
+ $this->getResult()->addValue('query', 'allpages', $data);
+ }
+ }
+
+ 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,
+ 'namespace' => array (
+ ApiBase :: PARAM_DFLT => 0,
+ ApiBase :: PARAM_TYPE => $validNamespaces
+ ),
+ 'filterredir' => array (
+ ApiBase :: PARAM_DFLT => 'all',
+ ApiBase :: PARAM_TYPE => array (
+ 'all',
+ 'redirects',
+ 'nonredirects'
+ )
+ ),
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 10,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 1,
+ ApiBase :: PARAM_MAX1 => 500,
+ ApiBase :: PARAM_MAX2 => 5000
+ )
+ );
+ }
+
+ 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'
+ );
+ }
+
+ protected function getDescription() {
+ return 'Enumerate all pages sequentially in a given namespace';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'Simple Use',
+ ' api.php?action=query&list=allpages',
+ ' api.php?action=query&list=allpages&apfrom=B&aplimit=5',
+ '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',
+ ' Show content of first 2 non-redirect pages begining at "Re"',
+ ' api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryAllpages.php 16820 2006-10-06 01:02:14Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
new file mode 100644
index 00000000..574f742e
--- /dev/null
+++ b/includes/api/ApiQueryBase.php
@@ -0,0 +1,112 @@
+<?php
+
+
+/*
+ * Created on Sep 7, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+abstract class ApiQueryBase extends ApiBase {
+
+ private $mQueryModule;
+
+ public function __construct($query, $moduleName, $paramPrefix = '') {
+ parent :: __construct($query->getMain(), $moduleName, $paramPrefix);
+ $this->mQueryModule = $query;
+ }
+
+ /**
+ * Override this method to request extra fields from the pageSet
+ * using $this->getPageSet()->requestField('fieldName')
+ */
+ public function requestExtraData() {
+ }
+
+ /**
+ * Get the main Query module
+ */
+ public function getQuery() {
+ return $this->mQueryModule;
+ }
+
+ /**
+ * Get the Query database connection (readonly)
+ */
+ protected function getDB() {
+ return $this->getQuery()->getDB();
+ }
+
+ /**
+ * Get the PageSet object to work on
+ * @return ApiPageSet data
+ */
+ protected function getPageSet() {
+ return $this->mQueryModule->getPageSet();
+ }
+
+ public static function titleToKey($title) {
+ return str_replace(' ', '_', $title);
+ }
+
+ public static function keyToTitle($key) {
+ return str_replace('_', ' ', $key);
+ }
+
+ public static function getBaseVersion() {
+ return __CLASS__ . ': $Id: ApiQueryBase.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+
+abstract class ApiQueryGeneratorBase extends ApiQueryBase {
+
+ private $mIsGenerator;
+
+ public function __construct($query, $moduleName, $paramPrefix = '') {
+ parent :: __construct($query, $moduleName, $paramPrefix);
+ $mIsGenerator = false;
+ }
+
+ public function setGeneratorMode() {
+ $this->mIsGenerator = true;
+ }
+
+ /**
+ * Overrides base class to prepend 'g' to every generator parameter
+ */
+ public function encodeParamName($paramName) {
+ if ($this->mIsGenerator)
+ return 'g' . parent :: encodeParamName($paramName);
+ else
+ return parent :: encodeParamName($paramName);
+ }
+
+ /**
+ * Execute this module as a generator
+ * @param $resultPageSet PageSet: All output should be appended to this object
+ */
+ public abstract function executeGenerator($resultPageSet);
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
new file mode 100644
index 00000000..de651b00
--- /dev/null
+++ b/includes/api/ApiQueryInfo.php
@@ -0,0 +1,82 @@
+<?php
+
+
+/*
+ * Created on Sep 25, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiQueryBase.php');
+}
+
+class ApiQueryInfo extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName);
+ }
+
+ public function requestExtraData() {
+ $pageSet = $this->getPageSet();
+ $pageSet->requestField('page_is_redirect');
+ $pageSet->requestField('page_touched');
+ $pageSet->requestField('page_latest');
+ }
+
+ public function execute() {
+
+ $pageSet = $this->getPageSet();
+ $titles = $pageSet->getGoodTitles();
+ $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]);
+
+ if ($pageIsRedir[$pageid])
+ $pageInfo['redirect'] = '';
+
+ $result->addValue(array (
+ 'query',
+ 'pages'
+ ), $pageid, $pageInfo);
+ }
+ }
+
+ protected function getDescription() {
+ return 'Get basic page information such as namespace, title, last touched date, ...';
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=query&prop=info&titles=Main%20Page'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryInfo.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
new file mode 100644
index 00000000..f6097bad
--- /dev/null
+++ b/includes/api/ApiQueryRevisions.php
@@ -0,0 +1,320 @@
+<?php
+
+
+/*
+ * Created on Sep 7, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiQueryBase.php');
+}
+
+class ApiQueryRevisions extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'rv');
+ }
+
+ public function execute() {
+ $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));
+
+ $pageSet = $this->getPageSet();
+ $pageCount = $pageSet->getGoodTitleCount();
+ $revCount = $pageSet->getRevisionCount();
+
+ // Optimization -- nothing to do
+ 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)
+ $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 (
+ '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");
+ }
+ }
+ }
+
+ $userMax = ($showContent ? 50 : 500);
+ $botMax = ($showContent ? 200 : 10000);
+
+ if ($enumRevMode) {
+
+ // This is mostly to prevent parameter errors (and optimize sql?)
+ if ($startid !== 0 && isset ($start))
+ $this->dieUsage('start and startid cannot be used together', 'badparams');
+
+ if ($endid !== 0 && isset ($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
+ // the same result. This way users may request revisions starting at a given time,
+ // but to page through results use the rev_id returned after each page.
+ // 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);
+
+ // must manually initialize unset limit
+ if (!isset ($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()));
+
+ }
+ 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->validateLimit('page_count', $pageCount, 1, $userMax, $botMax);
+
+ // Get all page IDs
+ $conds['page_id'] = array_keys($pageSet->getGoodTitles());
+
+ $limit = $pageCount; // assumption testing -- we should never get more then $pageCount rows.
+ }
+ elseif ($revCount > 0) {
+ $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax);
+
+ // Get all revision IDs
+ $conds['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();
+
+ $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...
+ 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);
+ 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;
+
+ if ($showContent) {
+ ApiResult :: setContent($vals, Revision :: getRevisionText($row));
+ }
+
+ $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 '<r>' elements
+ $data = & $this->getResultData();
+ foreach ($data['query']['pages'] as & $page) {
+ if (is_array($page) && array_key_exists('revisions', $page)) {
+ ApiResult :: setIndexedTagName($page['revisions'], 'rev');
+ }
+ }
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'prop' => array (
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array (
+ 'timestamp',
+ 'user',
+ 'comment',
+ 'content'
+ )
+ ),
+ 'limit' => array (
+ ApiBase :: PARAM_DFLT => 0,
+ ApiBase :: PARAM_TYPE => 'limit',
+ ApiBase :: PARAM_MIN => 0,
+ ApiBase :: PARAM_MAX1 => 50,
+ ApiBase :: PARAM_MAX2 => 500
+ ),
+ 'startid' => 0,
+ 'endid' => 0,
+ 'start' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'end' => array (
+ ApiBase :: PARAM_TYPE => 'timestamp'
+ ),
+ 'dir' => array (
+ ApiBase :: PARAM_DFLT => 'older',
+ ApiBase :: PARAM_TYPE => array (
+ 'newer',
+ 'older'
+ )
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'prop' => 'Which properties to get for each revision: user|timestamp|comment|content',
+ '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)',
+ 'start' => 'from which revision timestamp to start enumeration (enum)',
+ 'end' => 'enumerate up to this timestamp (enum)',
+ 'dir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)'
+ );
+ }
+
+ protected function getDescription() {
+ return array (
+ 'Get revision information.',
+ 'This module may be used in several ways:',
+ ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter.',
+ ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params.',
+ ' 3) Get data about a set of revisions by setting their IDs with revids parameter.',
+ 'All parameters marked as (enum) may only be used with a single page (#2).'
+ );
+ }
+
+ protected function getExamples() {
+ return array (
+ 'Get data with content for the last revision of titles "API" and "Main Page":',
+ ' api.php?action=query&prop=revisions&titles=API|Main%20Page&rvprop=timestamp|user|comment|content',
+ 'Get last 5 revisions of the "Main Page":',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment',
+ 'Get first 5 revisions of the "Main Page":',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer',
+ 'Get first 5 revisions of the "Main Page" made after 2006-05-01:',
+ ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryRevisions.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
new file mode 100644
index 00000000..27c3f187
--- /dev/null
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -0,0 +1,113 @@
+<?php
+
+
+/*
+ * Created on Sep 25, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiQueryBase.php');
+}
+
+class ApiQuerySiteinfo extends ApiQueryBase {
+
+ public function __construct($query, $moduleName) {
+ parent :: __construct($query, $moduleName, 'si');
+ }
+
+ public function execute() {
+ $prop = null;
+ extract($this->extractRequestParams());
+
+ foreach ($prop as $p) {
+ switch ($p) {
+
+ case 'general' :
+
+ global $wgSitename, $wgVersion, $wgCapitalLinks;
+ $data = array ();
+ $mainPage = Title :: newFromText(wfMsgForContent('mainpage'));
+ $data['mainpage'] = $mainPage->getText();
+ $data['base'] = $mainPage->getFullUrl();
+ $data['sitename'] = $wgSitename;
+ $data['generator'] = "MediaWiki $wgVersion";
+ $data['case'] = $wgCapitalLinks ? 'first-letter' : 'case-sensitive'; // 'case-insensitive' option is reserved for future
+ $this->getResult()->addValue('query', $p, $data);
+ break;
+
+ case 'namespaces' :
+
+ global $wgContLang;
+ $data = array ();
+ foreach ($wgContLang->getFormattedNamespaces() as $ns => $title) {
+ $data[$ns] = array (
+ 'id' => $ns
+ );
+ ApiResult :: setContent($data[$ns], $title);
+ }
+ ApiResult :: setIndexedTagName($data, 'ns');
+ $this->getResult()->addValue('query', $p, $data);
+ break;
+
+ default :
+ ApiBase :: dieDebug(__METHOD__, "Unknown prop=$p");
+ }
+ }
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'prop' => array (
+ ApiBase :: PARAM_DFLT => 'general',
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array (
+ 'general',
+ 'namespaces'
+ )
+ )
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'prop' => array (
+ 'Which sysinfo properties to get:',
+ ' "general" - Overall system information',
+ ' "namespaces" - List of registered namespaces (localized)'
+ )
+ );
+ }
+
+ protected function getDescription() {
+ return 'Return general information about the site.';
+ }
+
+ protected function getExamples() {
+ return 'api.php?action=query&meta=siteinfo&siprop=general|namespaces';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 16757 2006-10-03 05:41:55Z yurik $';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
new file mode 100644
index 00000000..67fbf41e
--- /dev/null
+++ b/includes/api/ApiResult.php
@@ -0,0 +1,153 @@
+<?php
+
+
+/*
+ * Created on Sep 4, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ('ApiBase.php');
+}
+
+class ApiResult extends ApiBase {
+
+ private $mData;
+
+ /**
+ * Constructor
+ */
+ public function __construct($main) {
+ parent :: __construct($main, 'result');
+ $this->Reset();
+ }
+
+ public function Reset() {
+ $this->mData = array ();
+ }
+
+ function & getData() {
+ return $this->mData;
+ }
+
+ /**
+ * Add an output value to the array by name.
+ * Verifies that value with the same name has not been added before.
+ */
+ public static function setElement(& $arr, $name, $value) {
+ if ($arr === null || $name === null || $value === null || !is_array($arr) || is_array($name))
+ ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
+
+ if (!isset ($arr[$name])) {
+ $arr[$name] = $value;
+ }
+ elseif (is_array($arr[$name]) && is_array($value)) {
+ $merged = array_intersect_key($arr[$name], $value);
+ if (empty ($merged))
+ $arr[$name] += $value;
+ else
+ ApiBase :: dieDebug(__METHOD__, "Attempting to merge element $name");
+ } else
+ ApiBase :: dieDebug(__METHOD__, "Attempting to add element $name=$value, existing value is {$arr[$name]}");
+ }
+
+ /**
+ * Adds the content element to the array.
+ * Use this function instead of hardcoding the '*' element.
+ */
+ public static function setContent(& $arr, $value) {
+ if (is_array($value))
+ ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
+ ApiResult :: setElement($arr, '*', $value);
+ }
+
+ // public static function makeContentElement($tag, $value) {
+ // $result = array();
+ // ApiResult::setContent($result, )
+ // }
+ //
+ /**
+ * In case the array contains indexed values (in addition to named),
+ * all indexed values will have the given tag name.
+ */
+ public static function setIndexedTagName(& $arr, $tag) {
+ // Do not use setElement() as it is ok to call this more than once
+ if ($arr === null || $tag === null || !is_array($arr) || is_array($tag))
+ ApiBase :: dieDebug(__METHOD__, 'Bad parameter');
+ $arr['_element'] = $tag;
+ }
+
+ /**
+ * Add value to the output data at the given path.
+ * Path is an indexed array, each element specifing the branch at which to add the new value
+ * Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value
+ */
+ public function addValue($path, $name, $value) {
+
+ $data = & $this->getData();
+
+ if (isset ($path)) {
+ if (is_array($path)) {
+ foreach ($path as $p) {
+ if (!isset ($data[$p]))
+ $data[$p] = array ();
+ $data = & $data[$p];
+ }
+ } else {
+ if (!isset ($data[$path]))
+ $data[$path] = array ();
+ $data = & $data[$path];
+ }
+ }
+
+ 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 $';
+ }
+}
+?> \ No newline at end of file