\r
$apiStartTime = microtime(true);\r
\r
+/**\r
+ * When no format parameter is given, this format will be used\r
+ */\r
+define('API_DEFAULT_FORMAT', 'xmlfm');\r
+\r
+$apidir = 'includes/api';\r
/**\r
* List of classes and containing files.\r
*/\r
$apiAutoloadClasses = array (\r
- 'ApiBase' => 'includes/api/ApiBase.php',\r
- 'ApiMain' => 'includes/api/ApiMain.php',\r
- 'ApiResult' => 'includes/api/ApiResult.php',\r
-\r
- // Available modules - should match the $apiModules list\r
- 'ApiHelp' => 'includes/api/ApiHelp.php',\r
- 'ApiLogin' => 'includes/api/ApiLogin.php',\r
- 'ApiQuery' => 'includes/api/ApiQuery.php'\r
+\r
+ 'ApiMain' => "$apidir/ApiMain.php",\r
+\r
+ // Utility classes\r
+ 'ApiBase' => "$apidir/ApiBase.php",\r
+ 'ApiQueryBase' => "$apidir/ApiQueryBase.php",\r
+ 'ApiResult' => "$apidir/ApiResult.php",\r
+\r
+ // Formats\r
+ 'ApiFormatBase' => "$apidir/ApiFormatBase.php",\r
+ 'ApiFormatYaml' => "$apidir/ApiFormatYaml.php",\r
+ 'ApiFormatXml' => "$apidir/ApiFormatXml.php",\r
+ 'ApiFormatJson' => "$apidir/ApiFormatJson.php",\r
+\r
+ // Modules (action=...) - should match the $apiModules list\r
+ 'ApiHelp' => "$apidir/ApiHelp.php",\r
+ 'ApiLogin' => "$apidir/ApiLogin.php",\r
+ 'ApiQuery' => "$apidir/ApiQuery.php",\r
+\r
+ // Query items (what/list=...)\r
+ 'ApiQueryContent' => "$apidir/ApiQueryContent.php",\r
+\r
+ 'ApiPageSet' => "$apidir/ApiPageSet.php"\r
);\r
\r
/**\r
* List of available modules: action name => module class\r
* The class must also be listed in the $apiAutoloadClasses array. \r
- */ \r
+ */\r
$apiModules = array (\r
'help' => 'ApiHelp',\r
'login' => 'ApiLogin',\r
'query' => 'ApiQuery'\r
);\r
\r
+/**\r
+ * List of available formats: format name => format class\r
+ * The class must also be listed in the $apiAutoloadClasses array. \r
+ */\r
+$apiFormats = array (\r
+ 'json' => 'ApiFormatJson',\r
+ 'jsonfm' => 'ApiFormatJson',\r
+ 'xml' => 'ApiFormatXml',\r
+ 'xmlfm' => 'ApiFormatXml',\r
+ 'yaml' => 'ApiFormatYaml',\r
+ 'yamlfm' => 'ApiFormatYaml'\r
+);\r
\r
// Initialise common code\r
require_once ('./includes/WebStart.php');\r
wfProfileIn('api.php');\r
\r
-\r
// Verify that the API has not been disabled\r
// The next line should be \r
// if (isset ($wgEnableAPI) && !$wgEnableAPI) {\r
die(-1);\r
}\r
\r
-\r
ApiInitAutoloadClasses($apiAutoloadClasses);\r
-$processor = new ApiMain($apiStartTime, $apiModules);\r
+$processor = new ApiMain($apiStartTime, $apiModules, $apiFormats);\r
$processor->Execute();\r
\r
wfProfileOut('api.php');\r
wfLogProfilingData();\r
exit; // Done!\r
\r
-\r
function ApiInitAutoloadClasses($apiAutoloadClasses) {\r
\r
// Append $apiAutoloadClasses to $wgAutoloadClasses\r
$wgAutoloadClasses = $apiAutoloadClasses;\r
}\r
}\r
-?>\r
+?>
\ No newline at end of file
*/
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'
public function GetMimeType() {
return 'text/xml';
}
+
+ public function GetNeedsRawData() {
+ return true;
+ }
public function Execute() {
$xmlindent = null;
*/\r
private function PrintResult($isError) {\r
$this->mPrinter->InitPrinter($isError);\r
+ if (!$this->mPrinter->GetNeedsRawData())\r
+ $this->GetResult()->SanitizeData();\r
$this->mPrinter->Execute();\r
$this->mPrinter->ClosePrinter();\r
}\r
$msg = parent :: MakeHelpMsg();\r
\r
$astriks = str_repeat('*** ', 10);\r
- $msg .= "\n\n$astriks Modules $astriks\n\n";\r
+ $msg .= "\n\n$astriks Modules $astriks\n\n";\r
foreach ($this->mModules as $moduleName => $moduleClass) {\r
$msg .= "* action=$moduleName *";\r
$module = new $this->mModules[$moduleName] ($this, $moduleName);\r
$msg .= "\n";\r
}\r
\r
- $msg .= "\n$astriks Formats $astriks\n\n";\r
+ $msg .= "\n$astriks Formats $astriks\n\n";\r
foreach ($this->mFormats as $moduleName => $moduleClass) {\r
$msg .= "* format=$moduleName *";\r
$module = new $this->mFormats[$moduleName] ($this, $moduleName);\r
--- /dev/null
+<?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 ("ApiBase.php");
+}
+
+class ApiPageSet {
+
+ private $allPages; // [ns][dbkey] => page_id or 0 when missing
+ private $db, $resolveRedirs;
+ private $goodTitles, $missingTitles, $redirectTitles;
+
+ public function __construct($db, $resolveRedirs) {
+ $this->db = $db;
+ $this->resolveRedirs = $resolveRedirs;
+
+ $this->allPages = array ();
+ $this->goodTitles = array ();
+ $this->missingTitles = array ();
+
+ // only when resolving redirects:
+ if ($resolveRedirs) {
+ $this->redirectTitles = array ();
+ }
+ }
+
+ /**
+ * Title objects that were found in the database.
+ * @return array page_id (int) => Title (obj)
+ */
+ public function GetGoodTitles() {
+ return $this->goodTitles;
+ }
+
+ /**
+ * Title objects that were NOT found in the database.
+ * @return array of Title objects
+ */
+ public function GetMissingTitles() {
+ return $this->missingTitles;
+ }
+
+ /**
+ * Get a list of redirects when doing redirect resolution
+ * @return array prefixed_title (string) => prefixed_title (string)
+ */
+ public function GetRedirectTitles() {
+ return $this->redirectTitles;
+ }
+
+ /**
+ * This method populates internal variables with page information
+ * based on the list of page titles given as a LinkBatch object.
+ *
+ * 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
+ */
+ public function PopulateTitles($linkBatch) {
+ $pageFlds = array (
+ 'page_id',
+ 'page_namespace',
+ 'page_title'
+ );
+ if ($this->resolveRedirs) {
+ $pageFlds[] = 'page_is_redirect';
+ }
+
+ //
+ // Repeat until all redirects have been resolved
+ //
+ while (false !== ($set = $linkBatch->constructSet('page', $this->db))) {
+
+ // Hack: Get the ns:titles stored in array(ns => array(titles)) format
+ $remaining = $linkBatch->data;
+
+ if ($this->resolveRedirs)
+ $redirectIds = array ();
+
+ //
+ // Get data about $linkBatch from `page` table
+ //
+ $res = $this->db->select('page', $pageFlds, $set, __CLASS__ . '::' . __FUNCTION__);
+ while ($row = $this->db->fetchObject($res)) {
+
+ unset ($remaining[$row->page_namespace][$row->page_title]);
+ $title = Title :: makeTitle($row->page_namespace, $row->page_title);
+ $this->allPages[$row->page_namespace][$row->page_title] = $row->page_id;
+
+ if ($this->resolveRedirs && $row->page_is_redirect == '1') {
+ $redirectIds[$row->page_id] = $title;
+ } else {
+ $this->goodTitles[$row->page_id] = $title;
+ }
+ }
+ $this->db->freeResult($res);
+
+ //
+ // The remaining titles in $remaining are non-existant pages
+ //
+ foreach ($remaining as $ns => $dbkeys) {
+ foreach ($dbkeys as $dbkey => $nothing) {
+ $this->missingTitles[] = Title :: makeTitle($ns, $dbkey);
+ $this->allPages[$ns][$dbkey] = 0;
+ }
+ }
+
+ if (!$this->resolveRedirs || empty ($redirectIds))
+ break;
+
+ //
+ // Resolve redirects by querying the pagelinks table, and repeat the process
+ //
+
+ // Create a new linkBatch object for the next pass
+ $linkBatch = new LinkBatch();
+
+ // find redirect targets for all redirect pages
+ $res = $this->db->select('pagelinks', array (
+ 'pl_from',
+ 'pl_namespace',
+ 'pl_title'
+ ), array (
+ 'pl_from' => array_keys($redirectIds
+ )), __CLASS__ . '::' . __FUNCTION__);
+
+ while ($row = $this->db->fetchObject($res)) {
+
+ // 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 ($redirectIds[$row->pl_from])) { // remove line when 7304 is fixed
+
+ $titleStrFrom = $redirectIds[$row->pl_from]->getPrefixedText();
+ $titleStrTo = Title :: makeTitle($row->pl_namespace, $row->pl_title)->getPrefixedText();
+ $this->redirectTitles[$titleStrFrom] = $titleStrTo;
+
+ unset ($redirectIds[$row->pl_from]); // remove line when 7304 is fixed
+
+ // Avoid an infinite loop by checking if we have already processed this target
+ if (!isset ($this->allPages[$row->pl_namespace][$row->pl_title])) {
+ $linkBatch->add($row->pl_namespace, $row->pl_title);
+ }
+ }
+ }
+ $this->db->freeResult($res);
+ }
+ }
+}
+?>
\ No newline at end of file
var $mMetaModuleNames, $mPropModuleNames, $mListModuleNames;\r
\r
private $mQueryMetaModules = array (\r
-// 'siteinfo' => 'ApiQuerySiteinfo',\r
-// 'userinfo' => 'ApiQueryUserinfo'\r
+ 'siteinfo' => 'ApiQuerySiteinfo',\r
+ //'userinfo' => 'ApiQueryUserinfo' \r
+\r
+ \r
);\r
+\r
private $mQueryPropModules = array (\r
-// 'info' => 'ApiQueryInfo',\r
-// 'categories' => 'ApiQueryCategories',\r
-// 'imageinfo' => 'ApiQueryImageinfo',\r
-// 'langlinks' => 'ApiQueryLanglinks',\r
-// 'links' => 'ApiQueryLinks',\r
-// 'templates' => 'ApiQueryTemplates',\r
-// 'revisions' => 'ApiQueryRevisions',\r
+ // 'info' => 'ApiQueryInfo',\r
+ // 'categories' => 'ApiQueryCategories',\r
+ // 'imageinfo' => 'ApiQueryImageinfo',\r
+ // 'langlinks' => 'ApiQueryLanglinks',\r
+ // 'links' => 'ApiQueryLinks',\r
+ // 'templates' => 'ApiQueryTemplates',\r
+ // 'revisions' => 'ApiQueryRevisions',\r
\r
// Should be removed\r
- 'content' => 'ApiQueryContent'\r
+ 'content' => 'ApiQueryContent'\r
);\r
+\r
private $mQueryListModules = array (\r
-// 'allpages' => 'ApiQueryAllpages',\r
-// 'backlinks' => 'ApiQueryBacklinks',\r
-// 'categorymembers' => 'ApiQueryCategorymembers',\r
-// 'embeddedin' => 'ApiQueryEmbeddedin',\r
-// 'imagelinks' => 'ApiQueryImagelinks',\r
-// 'logevents' => 'ApiQueryLogevents',\r
-// 'recentchanges' => 'ApiQueryRecentchanges',\r
-// 'usercontribs' => 'ApiQueryUsercontribs',\r
-// 'users' => 'ApiQueryUsers',\r
-// 'watchlist' => 'ApiQueryWatchlist',\r
+ 'allpages' => 'ApiQueryAllpages',\r
+ // 'backlinks' => 'ApiQueryBacklinks',\r
+ // 'categorymembers' => 'ApiQueryCategorymembers',\r
+ // 'embeddedin' => 'ApiQueryEmbeddedin',\r
+ // 'imagelinks' => 'ApiQueryImagelinks',\r
+ // 'logevents' => 'ApiQueryLogevents',\r
+ // 'recentchanges' => 'ApiQueryRecentchanges',\r
+ // 'usercontribs' => 'ApiQueryUsercontribs',\r
+ // 'users' => 'ApiQueryUsers',\r
+ // 'watchlist' => 'ApiQueryWatchlist'\r
+\r
+ \r
);\r
\r
private $mSlaveDB = null;\r
$this->mMetaModuleNames = array_keys($this->mQueryMetaModules);\r
$this->mPropModuleNames = array_keys($this->mQueryPropModules);\r
$this->mListModuleNames = array_keys($this->mQueryListModules);\r
- \r
- $this->mAllowedGenerators = array_merge( $this->mListModuleNames, $this->mPropModuleNames);\r
+\r
+ $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames);\r
}\r
\r
public function GetDB() {\r
// Only one of the titles/pageids/revids is allowed at the same time\r
//\r
$dataSource = null;\r
- if (isset($titles))\r
+ if (isset ($titles))\r
$dataSource = 'titles';\r
- if (isset($pageids)) {\r
- if (isset($dataSource))\r
+ if (isset ($pageids)) {\r
+ if (isset ($dataSource))\r
$this->DieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource');\r
$dataSource = 'pageids';\r
}\r
- if (isset($revids)) {\r
- if (isset($dataSource))\r
+ if (isset ($revids)) {\r
+ if (isset ($dataSource))\r
$this->DieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource');\r
$dataSource = 'revids';\r
}\r
- \r
- //\r
+\r
+ if (isset($dataSource) && $dataSource !== 'titles')\r
+ $this->DieUsage('Currently only titles= parameter is supported.', 'notimplemented');\r
+\r
// Normalize titles\r
- //\r
- if ($dataSource === 'titles') {\r
- $linkBatch = new LinkBatch;\r
- foreach ( $titles as &$titleString ) {\r
- $titleObj = &Title::newFromText( $titleString );\r
-\r
- // Validation\r
- if (!$titleObj)\r
- $this->dieUsage( "bad title $titleString", 'pi_invalidtitle' );\r
- if ($titleObj->getNamespace() < 0)\r
- $this->dieUsage( "No support for special page $titleString has been implemented", 'pi_unsupportednamespace' );\r
- if (!$titleObj->userCanRead())\r
- $this->dieUsage( "No read permission for $titleString", 'pi_titleaccessdenied' );\r
-\r
- $linkBatch->addObj( $titleObj );\r
-\r
- // Make sure we remember the original title that was given to us\r
- // This way the caller can correlate new titles with the originally requested, i.e. namespace is localized or capitalization\r
- if( $titleString !== $titleObj->getPrefixedText() ) {\r
- $this->GetResult()->AddMessage('query', 'normalized', array($titleString => $titleObj->getPrefixedText()));\r
- }\r
+ $linkBatch = $this->ProcessTitles($titles);\r
+\r
+ // Get titles info from DB\r
+ $data = new ApiPageSet($this->GetDB(), $redirects);\r
+ $data->PopulateTitles($linkBatch);\r
+\r
+ // Show redirects information\r
+ if ($redirects) {\r
+ foreach ($data->GetRedirectTitles() as $titleStrFrom => $titleStrTo) {\r
+ $this->GetResult()->AddMessage('query', 'redirects', array (\r
+ 'from' => $titleStrFrom,\r
+ 'to' => $titleStrTo\r
+ ), 'r');\r
}\r
}\r
}\r
\r
+ /**\r
+ * Given an array of title strings, convert them into Title objects.\r
+ * This method validates access rights for the title, \r
+ * and appends normalization values to the output.\r
+ * @return LinkBatch of title objects.\r
+ */\r
+ protected function ProcessTitles($titles) {\r
+\r
+ $linkBatch = new LinkBatch();\r
+\r
+ foreach ($titles as $titleString) {\r
+ $titleObj = Title :: newFromText($titleString);\r
+\r
+ // Validation\r
+ if (!$titleObj)\r
+ $this->dieUsage("bad title $titleString", 'invalidtitle');\r
+ if ($titleObj->getNamespace() < 0)\r
+ $this->dieUsage("No support for special page $titleString has been implemented", 'unsupportednamespace');\r
+ if (!$titleObj->userCanRead())\r
+ $this->dieUsage("No read permission for $titleString", 'titleaccessdenied');\r
+\r
+ $linkBatch->addObj($titleObj);\r
+\r
+ // Make sure we remember the original title that was given to us\r
+ // This way the caller can correlate new titles with the originally requested, i.e. namespace is localized or capitalization\r
+ if ($titleString !== $titleObj->getPrefixedText()) {\r
+ $this->GetResult()->AddMessage('query', 'normalized', array (\r
+ 'from' => $titleString,\r
+ 'to' => $titleObj->getPrefixedText()), 'n');\r
+ }\r
+ }\r
+\r
+ return $linkBatch;\r
+ }\r
+\r
protected function GetAllowedParams() {\r
return array (\r
'meta' => array (\r
'revids' => array (\r
GN_ENUM_TYPE => 'integer',\r
GN_ENUM_ISMULTI => true\r
- )\r
+ ),\r
+ 'redirects' => false\r
);\r
}\r
\r
}\r
\r
protected function GetDescription() {\r
- return array(\r
- 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',\r
- 'and is loosely based on the Query API interface currently available on all MediaWiki servers.',\r
- 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.');\r
+ return array (\r
+ 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',\r
+ 'and is loosely based on the Query API interface currently available on all MediaWiki servers.',\r
+ 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.'\r
+ );\r
}\r
\r
protected function GetExamples() {\r
\r
private $mQueryModule;\r
\r
- /**\r
- * Constructor\r
- */\r
public function __construct($main, $query) {\r
parent :: __construct($main);\r
$this->mQueryModule = $query;\r
}\r
\r
+ /**\r
+ * Get the name of the query being executed by this instance \r
+ */\r
public function GetQuery() {\r
return $this->mQueryModule;\r
}\r
+ \r
+ /**\r
+ * Derived classes return true when they can be used as title generators for other query modules.\r
+ */\r
+ protected static abstract function GetCanGenerate();\r
+ \r
+ /**\r
+ * Return true if this instance is being used as a generator.\r
+ */\r
+ protected function GetIsGenerator() {\r
+ return false;\r
+ }\r
}\r
?>
\ No newline at end of file
return $this->mData;\r
}\r
\r
- /* function addPage($title)\r
- {\r
- if (!isset($this->mPages))\r
- $this->mPages &= $this->mData['pages'];\r
- }\r
- */\r
- \r
- function AddMessage($mainSection, $subSection, $value, $preserveXmlSpacing = false) {\r
+ function AddMessage($mainSection, $subSection, $value, $multiitem = false, $preserveXmlSpacing = false) {\r
if (!array_key_exists($mainSection, $this->mData)) {\r
$this->mData[$mainSection] = array ();\r
}\r
} else {\r
$element = & $this->mData[$mainSection];\r
}\r
- if (is_array($value)) {\r
- $element = array_merge($element, $value);\r
- if (!array_key_exists('*', $element)) {\r
- $element['*'] = '';\r
- }\r
+ if( $multiitem ) {\r
+ $element['_element'] = $multiitem;\r
+ $element[] = $value;\r
} else {\r
- if (array_key_exists('*', $element)) {\r
- $element['*'] .= $value;\r
+ if (is_array($value)) {\r
+ $element = array_merge($element, $value);\r
+ if (!array_key_exists('*', $element)) {\r
+ $element['*'] = '';\r
+ }\r
} else {\r
- $element['*'] = $value;\r
- }\r
- if ($preserveXmlSpacing) {\r
- $element['xml:space'] = 'preserve';\r
+ if (array_key_exists('*', $element)) {\r
+ $element['*'] .= $value;\r
+ } else {\r
+ $element['*'] = $value;\r
+ }\r
+ if ($preserveXmlSpacing) {\r
+ $element['xml:space'] = 'preserve';\r
+ }\r
}\r
}\r
}\r