Index: includes/api/ApiQuery.php
===================================================================
--- includes/api/ApiQuery.php	(revision 55711)
+++ includes/api/ApiQuery.php	(working copy)
@@ -74,6 +74,7 @@
 		'logevents' => 'ApiQueryLogEvents',
 		'recentchanges' => 'ApiQueryRecentChanges',
 		'search' => 'ApiQuerySearch',
+		'tags' => 'ApiQueryTags',
 		'usercontribs' => 'ApiQueryContributions',
 		'watchlist' => 'ApiQueryWatchlist',
 		'watchlistraw' => 'ApiQueryWatchlistRaw',
Index: includes/api/ApiQueryLogEvents.php
===================================================================
--- includes/api/ApiQueryLogEvents.php	(revision 55711)
+++ includes/api/ApiQueryLogEvents.php	(working copy)
@@ -51,6 +51,7 @@
 		$this->fld_timestamp = in_array('timestamp', $prop);
 		$this->fld_comment = in_array('comment', $prop);
 		$this->fld_details = in_array('details', $prop);
+		$this->fld_tags = in_array('tags', $prop);
 
 		list($tbl_logging, $tbl_page, $tbl_user) = $db->tableNamesN('logging', 'page', 'user');
 
@@ -85,6 +86,18 @@
 		$this->addFieldsIf('log_comment', $this->fld_comment);
 		$this->addFieldsIf('log_params', $this->fld_details);
 		
+		if($this->fld_tags) {
+			$this->addTables('tag_summary');
+			$this->addJoinConds(array('tag_summary' => array('LEFT JOIN', 'log_id=ts_log_id')));
+			$this->addFields('ts_tags');
+		}
+		
+		if( !is_null($params['tag']) ) {
+			$this->addTables('change_tag');
+			$this->addJoinConds(array('change_tag' => array('INNER JOIN', array('log_id=ct_log_id'))));
+			$this->addWhereFld('ct_tag' , $params['tag']);
+		}
+		
 		if( !is_null($params['type']) ) {
 			$this->addWhereFld('log_type', $params['type']);
 			$index = 'type_time';
@@ -247,6 +260,16 @@
 			}
 		}
 
+		if ($this->fld_tags) {
+			if ($row->ts_tags) {
+				$tags = explode(',', $row->ts_tags);
+				$this->getResult()->setIndexedTagName($tags, 'tag');
+				$vals['tags'] = $tags;
+			} else {
+				$vals['tags'] = array();
+			}
+		}
+		
 		return $vals;
 	}
 
@@ -265,6 +288,7 @@
 					'timestamp',
 					'comment',
 					'details',
+					'tags'
 				)
 			),
 			'type' => array (
Index: includes/api/ApiQueryRecentChanges.php
===================================================================
--- includes/api/ApiQueryRecentChanges.php	(revision 55711)
+++ includes/api/ApiQueryRecentChanges.php	(working copy)
@@ -174,6 +174,7 @@
 			$this->fld_redirect = isset($prop['redirect']);
 			$this->fld_patrolled = isset($prop['patrolled']);
 			$this->fld_loginfo = isset($prop['loginfo']);
+			$this->fld_tags = isset($prop['tags']);
 
 			global $wgUser;
 			if($this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol())
@@ -203,6 +204,18 @@
 				$this->addFields('page_is_redirect');
 			}
 		}
+		
+		if($this->fld_tags) {
+			$this->addTables('tag_summary');
+			$this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rc_id=ts_rc_id'))));
+			$this->addFields('ts_tags');
+		}
+			
+		if(!is_null($params['tag'])) {
+			$this->addTables('change_tag');
+			$this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rc_id=ct_rc_id'))));
+			$this->addWhereFld('ct_tag' , $params['tag']);
+		}
 		$this->token = $params['token'];
 		$this->addOption('LIMIT', $params['limit'] +1);
 		$this->addOption('USE INDEX', array('recentchanges' => $index));
@@ -335,6 +348,16 @@
 				$row->rc_log_type, $row->rc_timestamp);
 		}
 		
+		if ($this->fld_tags) {
+			if ($row->ts_tags) {
+				$tags = explode(',', $row->ts_tags);
+				$this->getResult()->setIndexedTagName($tags, 'tag');
+				$vals['tags'] = $tags;
+			} else {
+				$vals['tags'] = array();
+			}
+		}
+			
 		if(!is_null($this->token))
 		{
 			$tokenFunctions = $this->getTokenFunctions();
@@ -408,6 +431,7 @@
 					'redirect',
 					'patrolled',
 					'loginfo',
+					'tags'
 				)
 			),
 			'token' => array(
Index: includes/api/ApiQueryRevisions.php
===================================================================
--- includes/api/ApiQueryRevisions.php	(revision 55711)
+++ includes/api/ApiQueryRevisions.php	(working copy)
@@ -42,7 +42,7 @@
 	}
 
 	private $fld_ids = false, $fld_flags = false, $fld_timestamp = false, $fld_size = false,
-			$fld_comment = false, $fld_user = false, $fld_content = false;
+			$fld_comment = false, $fld_user = false, $fld_content = false, $fld_tags = false;
 
 	protected function getTokenFunctions() {
 		// tokenname => function
@@ -121,9 +121,8 @@
 		}
 
 		$db = $this->getDB();
-		$this->addTables('revision');
+		$this->addTables(array('page', 'revision'));
 		$this->addFields(Revision::selectFields());
-		$this->addTables('page');
 		$this->addWhere('page_id = rev_page');
 
 		$prop = array_flip($params['prop']);
@@ -143,6 +142,19 @@
 			$this->addFields( Revision::selectPageFields() );
 		}
 
+		if (isset ($prop['tags'])) {
+			$this->fld_tags = true;
+			$this->addTables('tag_summary');
+			$this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rev_id=ts_rev_id'))));
+			$this->addFields('ts_tags');
+		}
+		
+		if( !is_null($params['tag']) ) {
+			$this->addTables('change_tag');
+			$this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rev_id=ct_rev_id'))));
+			$this->addWhereFld('ct_tag' , $params['tag']);
+		}
+		
 		if (isset ($prop['content'])) {
 
 			// For each page we will request, the user must have read rights for that page
@@ -293,9 +305,9 @@
 				$this->setContinueEnumParameter('startid', intval($row->rev_id));
 				break;
 			}
-			$revision = new Revision( $row );
+			
 			//
-			$fit = $this->addPageSubItem($revision->getPage(), $this->extractRowInfo($revision), 'rev');
+			$fit = $this->addPageSubItem($row->rev_page, $this->extractRowInfo($row), 'rev');
 			if(!$fit)
 			{
 				if($enumRevMode)
@@ -311,7 +323,8 @@
 		$db->freeResult($res);
 	}
 
-	private function extractRowInfo( $revision ) {
+	private function extractRowInfo( $row ) {
+		$revision = new Revision( $row );
 		$title = $revision->getTitle();
 		$vals = array ();
 
@@ -353,6 +366,16 @@
 			}
 		}	
 
+		if ($this->fld_tags) {
+			if ($row->ts_tags) {
+				$tags = explode(',', $row->ts_tags);
+				$this->getResult()->setIndexedTagName($tags, 'tag');
+				$vals['tags'] = $tags;
+			} else {
+				$vals['tags'] = array();
+			}
+		}
+		
 		if(!is_null($this->token))
 		{
 			$tokenFunctions = $this->getTokenFunctions();
@@ -427,6 +450,7 @@
 					'size',
 					'comment',
 					'content',
+					'tags'
 				)
 			),
 			'limit' => array (
Index: includes/api/ApiQueryTags.php
===================================================================
--- includes/api/ApiQueryTags.php	(revision 0)
+++ includes/api/ApiQueryTags.php	(revision 0)
@@ -0,0 +1,177 @@
+<?php
+
+/*
+ * Created on Jul 9, 2009
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+	// Eclipse helper - will be ignored in production
+	require_once ('ApiQueryBase.php');
+}
+
+/**
+ * Query module to enumerate change tags.
+ *
+ * @ingroup API
+ */
+class ApiQueryTags extends ApiQueryBase {
+	
+	private $limit, $result;
+	private $fld_displayname = false, $fld_description = false,
+			$fld_hitcount = false;
+	
+	public function __construct($query, $moduleName) {
+		parent :: __construct($query, $moduleName, 'tg');
+	}
+
+	public function execute() {
+		$params = $this->extractRequestParams();
+		
+		$prop = array_flip($params['prop']);
+		
+		$this->fld_displayname = isset($prop['displayname']);
+		$this->fld_description = isset($prop['description']);
+		$this->fld_hitcount = isset($prop['hitcount']);
+		
+		$this->limit = $params['limit'];
+		$this->result = $this->getResult();
+		
+		$pageSet = $this->getPageSet();
+		$titles = $pageSet->getTitles();
+		$data = array();
+		
+		$this->addTables('change_tag');
+		$this->addFields('ct_tag');
+		
+		if($this->fld_hitcount)
+			$this->addFields('count(*) AS hitcount');
+		
+		$this->addOption('LIMIT', $this->limit + 1);
+		$this->addOption('GROUP BY', 'ct_tag');
+		$this->addWhereRange('ct_tag', 'newer', $params['continue'], null);
+		
+		$res = $this->select(__METHOD__);
+		
+		$ok = true;
+		
+		while ( $row = $res->fetchObject() ) {
+			if(!$ok) break;
+			$ok = $this->doTag( $row->ct_tag, $row->hitcount );
+		}
+		
+		//include tags with no hits yet
+		foreach( ChangeTags::listDefinedTags() as $tag ) {
+			if(!$ok) break;
+			$ok = $this->doTag( $tag, 0 );
+		}
+			
+		$this->result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'tag');
+	}
+	
+	private function doTag( $tagName, $hitcount ) {
+		static $count = 0;
+		static $doneTags = array();
+		
+		if ( in_array( $tagName, $doneTags ) ) {
+			return true;
+		}
+		
+		if(++$count > $this->limit)
+		{
+			$this->setContinueEnumParameter('continue', $tagName);
+			return false;
+		}
+		
+		$tag = array();
+		$tag['name'] = $tagName;
+		
+		if($this->fld_displayname)
+			$tag['displayname'] = ChangeTags::tagDescription( $tagName );
+		
+		if($this->fld_description)
+		{
+			$msg = wfMsg( "tag-$tagName-description" );
+			$msg = wfEmptyMsg( "tag-$tagName-description", $msg ) ? '' : $msg;
+			$tag['description'] = $msg;
+		}
+			
+		if($this->fld_hitcount)
+			$tag['hitcount'] = $hitcount;
+		
+		$doneTags[] = $tagName;
+		
+		$fit = $this->result->addValue(array('query', $this->getModuleName()), null, $tag);
+		if(!$fit)
+		{
+			$this->setContinueEnumParameter('continue', $tagName);
+			return false;
+		}
+			
+		return true;
+	}
+	
+	public function getAllowedParams() {
+		return array (
+			'continue' => array(
+			),
+			'limit' => array(
+				ApiBase :: PARAM_DFLT => 10,
+				ApiBase :: PARAM_TYPE => 'limit',
+				ApiBase :: PARAM_MIN => 1,
+				ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
+				ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
+			),
+			'prop' => array(
+				ApiBase :: PARAM_DFLT => 'name',
+				ApiBase :: PARAM_TYPE => array(
+						'name',
+						'displayname',
+						'description',
+						'hitcount'
+					),
+				ApiBase :: PARAM_ISMULTI => true
+			)
+		);
+	}
+
+	public function getParamDescription() {
+		return array (
+			'continue' => 'When more results are available, use this to continue',
+			'limit' => 'The maximum number of tags to list',
+			'prop' => 'Which properties to get',
+		);
+	}
+
+	public function getDescription() {
+		return 'List change tags.';
+	}
+
+	protected function getExamples() {
+		return array (
+			'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
+		);
+	}
+
+	public function getVersion() {
+		return __CLASS__ . ': $Id: ApiQueryTags.php';
+	}
+}
\ No newline at end of file
Index: includes/api/ApiQueryUserContributions.php
===================================================================
--- includes/api/ApiQueryUserContributions.php	(revision 55711)
+++ includes/api/ApiQueryUserContributions.php	(working copy)
@@ -42,7 +42,7 @@
 	private $params, $username;
 	private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
 			$fld_comment = false, $fld_flags = false,
-			$fld_patrolled = false;
+			$fld_patrolled = false, $fld_tags = false;
 
 	public function execute() {
 
@@ -57,6 +57,7 @@
 		$this->fld_flags = isset($prop['flags']);
 		$this->fld_timestamp = isset($prop['timestamp']);
 		$this->fld_patrolled = isset($prop['patrolled']);
+		$this->fld_tags = isset($prop['tags']);
 
 		// TODO: if the query is going only against the revision table, should this be done?
 		$this->selectNamedDB('contributions', DB_SLAVE, 'contributions');
@@ -141,7 +142,7 @@
 	private function prepareQuery() {
 		// We're after the revision table, and the corresponding page
 		// row for anything we retrieve. We may also need the
-		// recentchanges row.
+		// recentchanges row and/or tag summary row.
 		global $wgUser;
 		$tables = array('page', 'revision'); // Order may change
 		$this->addWhere('page_id=rev_page');
@@ -245,6 +246,19 @@
 		$this->addFieldsIf('rev_minor_edit', $this->fld_flags);
 		$this->addFieldsIf('rev_parent_id', $this->fld_flags);
 		$this->addFieldsIf('rc_patrolled', $this->fld_patrolled);
+		
+		if($this->fld_tags)
+		{
+			$this->addTables('tag_summary');
+			$this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rev_id=ts_rev_id'))));
+			$this->addFields('ts_tags');
+		}
+		
+		if( !is_null($this->params['tag']) ) {
+			$this->addTables('change_tag');
+			$this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rev_id=ct_rev_id'))));
+			$this->addWhereFld('ct_tag', $this->params['tag']);
+		}
 	}
 
 	/**
@@ -292,6 +306,16 @@
 		if ($this->fld_size && !is_null($row->rev_len))
 			$vals['size'] = intval($row->rev_len);
 
+		if ($this->fld_tags) {
+			if ($row->ts_tags) {
+				$tags = explode(',', $row->ts_tags);
+				$this->getResult()->setIndexedTagName($tags, 'tag');
+				$vals['tags'] = $tags;
+			} else {
+				$vals['tags'] = array();
+			}
+		}
+		
 		return $vals;
 	}
 	
@@ -343,6 +367,7 @@
 					'size',
 					'flags',
 					'patrolled',
+					'tags'
 				)
 			),
 			'show' => array (
Index: includes/AutoLoader.php
===================================================================
--- includes/AutoLoader.php	(revision 55711)
+++ includes/AutoLoader.php	(working copy)
@@ -307,6 +307,7 @@
 	'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
 	'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php',
 	'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
+	'ApiQueryTags' => 'includes/api/ApiQueryTags.php',
 	'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php',
 	'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php',
 	'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php',
