MediaWiki r46460 - Code Review

Jump to: navigation, search
Repository:MediaWiki
Revision:r46459‎ | r46460 (on ViewVC)‎ | r46461 >
Date:19:08, 28 January 2009
Author:werdna
Status:deferred (Comments)
Tags:
Comment:
Branch merge of change-tagging branch with trunk
-- Introduce tagging of individual changes (revisions, logs, and on recentchanges). The tags are customisable, and currently settable by the Abuse Filter and by the TorBlock extension. The tags can be styled on the various pages on which they appear.
-- Introduces a schema change, three new tables (valid_tag, change_tag, and tag_summary).
Modified paths:

Diff [purge]

Index: trunk/phase3/maintenance/archives/patch-change_tag.sql
===================================================================
--- trunk/phase3/maintenance/archives/patch-change_tag.sql	(revision 0)
+++ trunk/phase3/maintenance/archives/patch-change_tag.sql	(revision 46460)
@@ -0,0 +1,31 @@
+-- A table to track tags for revisions, logs and recent changes.
+-- Andrew Garrett, 2009-01
+CREATE TABLE /*_*/change_tag (
+	ct_rc_id int NULL,
+	ct_log_id int NULL,
+	ct_rev_id int NULL,
+	ct_tag varchar(255) NOT NULL,
+	ct_params BLOB NULL,
+
+	UNIQUE KEY (ct_rc_id,ct_tag),
+	UNIQUE KEY (ct_log_id,ct_tag),
+	UNIQUE KEY (ct_rev_id,ct_tag),
+	KEY (ct_tag,ct_rc_id,ct_rev_id,ct_log_id) -- Covering index, so we can pull all the info only out of the index.
+) /*$wgDBTableOptions*/;
+
+-- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT that only works on MySQL 4.1+
+CREATE TABLE /*_*/tag_summary (
+	ts_rc_id int NULL,
+	ts_log_id int NULL,
+	ts_rev_id int NULL,
+	ts_tags BLOB NOT NULL,
+
+	UNIQUE KEY (ts_rc_id),
+	UNIQUE KEY (ts_log_id),
+	UNIQUE KEY (ts_rev_id)
+) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*_*/valid_tag (
+	vt_tag varchar(255) NOT NULL,
+	PRIMARY KEY (vt_tag)
+) /*$wgDBTableOptions*/;
\ No newline at end of file
Index: trunk/phase3/maintenance/updaters.inc
===================================================================
--- trunk/phase3/maintenance/updaters.inc	(revision 46459)
+++ trunk/phase3/maintenance/updaters.inc	(revision 46460)
@@ -158,6 +158,9 @@
 		array( 'do_active_users_init' ),
 		array( 'add_field', 'ipblocks',     'ipb_allow_usertalk',  'patch-ipb_allow_usertalk.sql' ),
 		array( 'sqlite_initial_indexes' ),
+		array( 'add_table', 'change_tag',			   'patch-change_tag.sql' ),
+		array( 'add_table', 'tag_summary',			   'patch-change_tag.sql' ),
+		array( 'add_table', 'valid_tag',			   'patch-change_tag.sql' ),
 	),
 );
 
Index: trunk/phase3/maintenance/tables.sql
===================================================================
--- trunk/phase3/maintenance/tables.sql	(revision 46459)
+++ trunk/phase3/maintenance/tables.sql	(revision 46460)
@@ -1234,4 +1234,36 @@
   ul_key varchar(255) NOT NULL PRIMARY KEY
 ) /*$wgDBTableOptions*/;
 
+--- A table to track tags for revisions, logs and recent changes.
+REATE TABLE /*_*/change_tag (
+	ct_rc_id int NULL,
+	ct_log_id int NULL,
+	ct_rev_id int NULL,
+	ct_tag varchar(255) NOT NULL,
+	ct_params BLOB NULL,
+
+	UNIQUE KEY (ct_rc_id,ct_tag),
+	UNIQUE KEY (ct_log_id,ct_tag),
+	UNIQUE KEY (ct_rev_id,ct_tag),
+	KEY (ct_tag,ct_rc_id,ct_rev_id,ct_log_id) -- Covering index, so we can pull all the info only out of the index.
+) /*$wgDBTableOptions*/;
+
+-- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT that only works on MySQL 4.1+
+CREATE TABLE /*_*/tag_summary (
+	ts_rc_id int NULL,
+	ts_log_id int NULL,
+	ts_rev_id int NULL,
+	ts_tags BLOB NOT NULL,
+
+	UNIQUE KEY (ts_rc_id),
+	UNIQUE KEY (ts_log_id),
+	UNIQUE KEY (ts_rev_id),
+) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*_*/valid_tag (
+	vt_tag varchar(255) NOT NULL,
+	PRIMARY KEY (vt_tag)
+) /*$wgDBTableOptions*/;
+
+
 -- vim: sw=2 sts=2 et
Index: trunk/phase3/skins/common/history.js
===================================================================
--- trunk/phase3/skins/common/history.js	(revision 46459)
+++ trunk/phase3/skins/common/history.js	(revision 46460)
@@ -27,7 +27,13 @@
 				}
 				if (oli) { // it's the second checked radio
 					if (inputs[1].checked) {
-						oli.className = "selected";
+						if ( (typeof oli.className) != 'undefined') {
+							oli.classNameOriginal = oli.className.replace( 'selected', '' );
+						} else {
+							oli.classNameOriginal = '';
+						}
+						
+						oli.className = "selected "+oli.classNameOriginal;
 						return false;
 					}
 				} else if (inputs[0].checked) {
@@ -42,7 +48,13 @@
 				if (dli) {
 					inputs[1].style.visibility = 'hidden';
 				}
-				lis[i].className = "selected";
+				if ( (typeof lis[i].className) != 'undefined') {
+					lis[i].classNameOriginal = lis[i].className.replace( 'selected', '' );
+				} else {
+					lis[i].classNameOriginal = '';
+				}
+						
+				lis[i].className = "selected "+lis[i].classNameOriginal;
 				oli = lis[i];
 			}  else { // no radio is checked in this row
 				if (!oli) {
@@ -55,7 +67,7 @@
 				} else {
 					inputs[1].style.visibility = 'visible';
 				}
-				lis[i].className = "";
+				lis[i].className = lis[i].classNameOriginal;
 			}
 		}
 	}
Index: trunk/phase3/docs/hooks.txt
===================================================================
--- trunk/phase3/docs/hooks.txt	(revision 46459)
+++ trunk/phase3/docs/hooks.txt	(revision 46460)
@@ -846,6 +846,9 @@
 'LinksUpdateConstructed': At the end of LinksUpdate() is contruction.
 &$linksUpdate: the LinkUpdate object
 
+'ListDefinedTags': When trying to find all defined tags.
+&$tags: The list of tags.
+
 'LoadAllMessages': called by MessageCache::loadAllMessages() to load extensions messages
 
 'LoadExtensionSchemaUpdates': called by maintenance/updaters.inc when upgrading database schema
Index: trunk/phase3/includes/ChangeTags.php
===================================================================
--- trunk/phase3/includes/ChangeTags.php	(revision 0)
+++ trunk/phase3/includes/ChangeTags.php	(revision 46460)
@@ -0,0 +1,163 @@
+<?php
+
+if (!defined( 'MEDIAWIKI' ))
+	die;
+
+class ChangeTags {
+	static function formatSummaryRow( $tags, $page ) {
+		if (!$tags)
+			return array('',array());
+
+		$classes = array();
+		
+		$tags = explode( ',', $tags );
+		$displayTags = array();
+		foreach( $tags as $tag ) {
+			$displayTags[] = self::tagDescription( $tag );
+			$classes[] = "mw-tag-$tag";
+		}
+
+		return array( '(' . implode( ', ', $displayTags ) . ')', $classes );
+	}
+
+	static function tagDescription( $tag ) {
+		$msg = wfMsgExt( "tag-$tag", 'parseinline' );
+		if ( wfEmptyMsg( "tag-$tag", $msg ) ) {
+			return htmlspecialchars($tag);
+		}
+		return $msg;
+	}
+
+	## Basic utility method to add tags to a particular change, given its rc_id, rev_id and/or log_id.
+	static function addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params = null ) {
+		if ( !is_array($tags) ) {
+			$tags = array( $tags );
+		}
+
+		$tags = array_filter( $tags ); // Make sure we're submitting all tags...
+
+		if (!$rc_id && !$rev_id && !$log_id) {
+			throw new MWException( "At least one of: RCID, revision ID, and log ID MUST be specified when adding a tag to a change!" );
+		}
+
+		$dbr = wfGetDB( DB_SLAVE );
+
+		// Might as well look for rcids and so on.
+		if (!$rc_id) {
+			$dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
+			if ($log_id) {
+				$rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ );
+			} elseif ($rev_id) {
+				$rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ );
+			}
+		} elseif (!$log_id && !$rev_id) {
+			$dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
+			$log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ );
+			$rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ );
+		}
+
+		$tsConds = array_filter( array( 'ts_rc_id' => $rc_id, 'ts_rev_id' => $rev_id, 'ts_log_id' => $log_id ) );
+
+		## Update the summary row.
+		$prevTags = $dbr->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
+		$prevTags = $prevTags ? $prevTags : '';
+		$prevTags = array_filter( explode( ',', $prevTags ) );
+		$newTags = array_unique( array_merge( $prevTags, $tags ) );
+		sort($prevTags);
+		sort($newTags);
+
+		if ( $prevTags == $newTags ) {
+			// No change.
+			return false;
+		}
+
+		$dbw = wfGetDB( DB_MASTER );
+		$dbw->replace( 'tag_summary', array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ),  array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ), __METHOD__ );
+
+		// Insert the tags rows.
+		$tagsRows = array();
+		foreach( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally.
+			$tagsRows[] = array_filter( array( 'ct_tag' => $tag, 'ct_rc_id' => $rc_id, 'ct_log_id' => $log_id, 'ct_rev_id' => $rev_id, 'ct_params' => $params ) );
+		}
+
+		$dbw->insert( 'change_tag', $tagsRows, __METHOD__, array('IGNORE') );
+
+		return true;
+	}
+
+	/**
+	 * Applies all tags-related changes to a query.
+	 * Handles selecting tags, and filtering.
+	 * Needs $tables to be set up properly, so we can figure out which join conditions to use.
+	*/
+	static function modifyDisplayQuery( &$tables, &$fields,  &$conds, &$join_conds, $filter_tag = false ) {
+		global $wgRequest;
+		
+		if ($filter_tag === false) {
+			$filter_tag = $wgRequest->getVal( 'tagfilter' );
+		}
+
+		// Figure out which conditions can be done.
+		$join_field = '';
+		if ( in_array('recentchanges', $tables) ) {
+			$join_cond = 'rc_id';
+		} elseif( in_array('logging', $tables) ) {
+			$join_cond = 'log_id';
+		} elseif ( in_array('revision', $tables) ) {
+			$join_cond = 'rev_id';
+		} else {
+			throw new MWException( "Unable to determine appropriate JOIN condition for tagging." );
+		}
+
+		// JOIN on tag_summary
+		$tables[] = 'tag_summary';
+		$join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" );
+		$fields[] = 'ts_tags';
+		
+		if ($filter_tag) {
+			// Somebody wants to filter on a tag.
+			// Add an INNER JOIN on change_tag
+
+			$tables[] = 'change_tag';
+			$join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" );
+			$conds['ct_tag'] = $filter_tag;
+		}
+	}
+
+	/**
+	 * If $fullForm is set to false, then it returns an array of (label, form).
+	 * If $fullForm is true, it returns an entire form.
+	 */
+	static function buildTagFilterSelector( $selected='', $fullForm = false /* used to put a full form around the selector */ ) {
+		global $wgTitle;
+		
+		$data = array( wfMsgExt( 'tag-filter', 'parseinline' ), Xml::input( 'tagfilter', 20, $selected ) );
+
+		if (!$fullForm) {
+			return $data;
+		}
+
+		$html = implode( '&nbsp;', $data );
+		$html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMsg( 'tag-filter-submit' ) ) );
+		$html .= "\n" . Xml::hidden( 'title', $wgTitle-> getPrefixedText() );
+		$html = Xml::tags( 'form', array( 'action' => $wgTitle->getLocalURL(), 'method' => 'get' ), $html );
+
+		return $html;
+	}
+
+	/** Basically lists defined tags which count even if they aren't applied to anything */
+	static function listDefinedTags() {
+		$emptyTags = array();
+
+		// Some DB stuff
+		$dbr = wfGetDB( DB_SLAVE );
+		$res = $dbr->select( 'valid_tag', 'vt_tag', array(), __METHOD__ );
+		while( $row = $res->fetchObject() ) {
+			$emptyTags[] = $row->vt_tag;
+		}
+		
+		wfRunHooks( 'ListDefinedTags', array(&$emptyTags) );
+
+		return array_filter( array_unique( $emptyTags ) );
+	}
+}
\ No newline at end of file
Index: trunk/phase3/includes/LogEventsList.php
===================================================================
--- trunk/phase3/includes/LogEventsList.php	(revision 46459)
+++ trunk/phase3/includes/LogEventsList.php	(revision 46460)
@@ -68,7 +68,7 @@
 	 * @param $filter Boolean
 	 */
 	public function showOptions( $type = '', $user = '', $page = '', $pattern = '', $year = '', 
-			$month = '', $filter = null ) 
+			$month = '', $filter = null, $tagFilter='' ) 
 	{
 		global $wgScript, $wgMiserMode;
 		$action = htmlspecialchars( $wgScript );
@@ -83,7 +83,8 @@
 			$this->getTitleInput( $page ) . "\n" .
 			( !$wgMiserMode ? ($this->getTitlePattern( $pattern )."\n") : "" ) .
 			"<p>" . $this->getDateMenu( $year, $month ) . "\n" .
-			( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) .
+			Xml::tags( 'p', null, implode( '&nbsp;', ChangeTags::buildTagFilterSelector( $tagFilter ) ) ) . "\n" .
+			( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) . "\n" .
 			Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "</p>\n" .
 			"</fieldset></form>"
 		);
@@ -230,6 +231,7 @@
 		global $wgLang, $wgUser, $wgContLang;
 
 		$title = Title::makeTitle( $row->log_namespace, $row->log_title );
+		$classes = array( "mw-logline-{$row->log_type}" );
 		$time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->log_timestamp), true );
 		// User links
 		if( self::isDeleted($row,LogPage::DELETED_USER) ) {
@@ -357,12 +359,16 @@
 				$this->skin, $paramArray, true );
 		}
 
+		// Any tags...
+		list($tagDisplay, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' );
+		$classes = array_merge( $classes, $newClasses );
+
 		if( $revert != '' ) {
 			$revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
 		}
 
-		return Xml::tags( 'li', array( "class" => "mw-logline-$row->log_type" ),
-			$del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert ) . "\n";
+		return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ),
+			$del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert . " $tagDisplay" ) . "\n";
 	}
 
 	/**
@@ -508,7 +514,7 @@
 	 * @param $month Integer
 	 */
 	public function __construct( $list, $type = '', $user = '', $title = '', $pattern = '', 
-		$conds = array(), $year = false, $month = false ) 
+		$conds = array(), $year = false, $month = false, $tagFilter = '' ) 
 	{
 		parent::__construct();
 		$this->mConds = $conds;
@@ -519,6 +525,7 @@
 		$this->limitUser( $user );
 		$this->limitTitle( $title, $pattern );
 		$this->getDateCond( $year, $month );
+		$this->mTagFilter = $tagFilter;
 	}
 
 	public function getDefaultQuery() {
@@ -643,13 +650,18 @@
 		} else {
 			$index = array( 'USE INDEX' => array( 'logging' => 'times' ) );
 		}
-		return array(
+		$info = array(
 			'tables' => array( 'logging', 'user' ),
 			'fields' => array( 'log_type', 'log_action', 'log_user', 'log_namespace', 'log_title', 'log_params',
 				'log_comment', 'log_id', 'log_deleted', 'log_timestamp', 'user_name', 'user_editcount' ),
 			'conds' => $this->mConds,
-			'options' => $index
+			'options' => $index,
+			'join_conds' => array( 'user' => array( 'INNER JOIN', 'user_id=log_user' ) ),
 		);
+
+		ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'], $info['join_conds'], $this->mTagFilter );
+
+		return $info;
 	}
 
 	function getIndexField() {
@@ -700,6 +712,10 @@
 	public function getMonth() {
 		return $this->mMonth;
 	}
+
+	public function getTagFilter() {
+		return $this->mTagFilter;
+	}
 }
 
 /**
@@ -721,6 +737,7 @@
 		$pattern = $request->getBool( 'pattern' );
 		$year = $request->getIntOrNull( 'year' );
 		$month = $request->getIntOrNull( 'month' );
+		$tagFilter = $request->getVal( 'tagfilter' );
 		# Don't let the user get stuck with a certain date
 		$skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
 		if( $skip ) {
@@ -729,7 +746,7 @@
 		}
 		# Use new list class to output results
 		$loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
-		$this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month );
+		$this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month, $tagFilter );
 	}
 
 	/**
Index: trunk/phase3/includes/AutoLoader.php
===================================================================
--- trunk/phase3/includes/AutoLoader.php	(revision 46459)
+++ trunk/phase3/includes/AutoLoader.php	(revision 46460)
@@ -28,6 +28,7 @@
 	'CategoryViewer' => 'includes/CategoryPage.php',
 	'ChangesList' => 'includes/ChangesList.php',
 	'ChangesFeed' => 'includes/ChangesFeed.php',
+	'ChangeTags' => 'includes/ChangeTags.php',
 	'ChannelFeed' => 'includes/Feed.php',
 	'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
 	'ConstantDependency' => 'includes/CacheDependency.php',
@@ -495,6 +496,7 @@
 	'SpecialSearch' => 'includes/specials/SpecialSearch.php',
 	'SpecialSearchOld' => 'includes/specials/SpecialSearch.php',
 	'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
+	'SpecialTags' => 'includes/specials/SpecialTags.php',
 	'SpecialVersion' => 'includes/specials/SpecialVersion.php',
 	'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php',
 	'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php',
Index: trunk/phase3/includes/ChangesList.php
===================================================================
--- trunk/phase3/includes/ChangesList.php	(revision 46459)
+++ trunk/phase3/includes/ChangesList.php	(revision 46460)
@@ -338,6 +338,12 @@
 			}
 		}	
 	}
+
+	protected function insertTags( &$s, &$rc, &$classes ) {
+		list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' );
+		$classes = array_merge( $classes, $newClasses );
+		$s .= ' ' . $tagSummary;
+	}
 }
 
 
@@ -358,6 +364,7 @@
 		$this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
 
 		$s = '';
+		$classes = array();
 		// Moved pages
 		if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) {
 			$this->insertMove( $s, $rc );
@@ -394,6 +401,8 @@
 		$this->insertAction( $s, $rc );
 		# Edit or log comment
 		$this->insertComment( $s, $rc );
+		# Tags
+		$this->insertTags( $s, $rc, $classes );
 		# Rollback
 		$this->insertRollback( $s, $rc );
 		# Mark revision as deleted if so
@@ -409,7 +418,7 @@
 		wfRunHooks( 'OldChangesListRecentChangesLine', array(&$this, &$s, $rc) );
 
 		wfProfileOut( __METHOD__ );
-		return "$dateheader<li>$s</li>\n";
+		return "$dateheader<li class=\"".implode( ' ', $classes )."\">$s</li>\n";
 	}
 }
 
Index: trunk/phase3/includes/DefaultSettings.php
===================================================================
--- trunk/phase3/includes/DefaultSettings.php	(revision 46459)
+++ trunk/phase3/includes/DefaultSettings.php	(revision 46460)
@@ -1455,7 +1455,7 @@
  * to ensure that client-side caches don't keep obsolete copies of global
  * styles.
  */
-$wgStyleVersion = '201';
+$wgStyleVersion = '202';
 
 
 # Server-side caching:
Index: trunk/phase3/includes/specials/SpecialRecentchangeslinked.php
===================================================================
--- trunk/phase3/includes/specials/SpecialRecentchangeslinked.php	(revision 46459)
+++ trunk/phase3/includes/specials/SpecialRecentchangeslinked.php	(revision 46460)
@@ -15,6 +15,7 @@
 		$opts = parent::getDefaultOptions();
 		$opts->add( 'target', '' );
 		$opts->add( 'showlinkedto', false );
+		$opts->add( 'tagfilter', '' );
 		return $opts;
 	}
 
@@ -83,6 +84,8 @@
 			$join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" );
 		}
 
+		ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds, $opts['tagfilter'] );
+
 		// XXX: parent class does this, should we too?
 		// wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) );
 
@@ -169,6 +172,7 @@
 			Xml::input( 'target', 40, str_replace('_',' ',$opts['target']) ) .
 			Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' .
 			Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) );
+		$extraOpts['tagfilter'] = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
 		return $extraOpts;
 	}
 
Index: trunk/phase3/includes/specials/SpecialNewpages.php
===================================================================
--- trunk/phase3/includes/specials/SpecialNewpages.php	(revision 46459)
+++ trunk/phase3/includes/specials/SpecialNewpages.php	(revision 46460)
@@ -32,6 +32,7 @@
 		$opts->add( 'namespace', '0' );
 		$opts->add( 'username', '' );
 		$opts->add( 'feed', '' );
+		$opts->add( 'tagfilter', '' );
 
 		// Set values
 		$opts->fetchValuesFromRequest( $wgRequest );
@@ -176,6 +177,8 @@
 		}
 		$hidden = implode( "\n", $hidden );
 
+		list( $tagFilterLabel, $tagFilterSelector ) = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
+
 		$form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) .
 			Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
 			Xml::fieldset( wfMsg( 'newpages' ) ) .
@@ -188,6 +191,14 @@
 					Xml::namespaceSelector( $namespace, 'all' ) .
 				"</td>
 			</tr>" .
+			"<tr>
+				<td class='mw-label'>" .
+					$tagFilterLabel .
+				"</td>
+				<td class='mw-input'>" .
+					$tagFilterSelector .
+				"</td>
+			</tr>" .
 			($wgEnableNewpagesUserFilter ?
 			"<tr>
 				<td class='mw-label'>" .
@@ -235,6 +246,9 @@
 	 */
 	public function formatRow( $result ) {
 		global $wgLang, $wgContLang, $wgUser;
+
+		$classes = array();
+		
 		$dm = $wgContLang->getDirMark();
 
 		$title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title );
@@ -247,9 +261,17 @@
 		$ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' .
 			$this->skin->userToolLinks( $result->rc_user, $result->rc_user_text );
 		$comment = $this->skin->commentBlock( $result->rc_comment );
-		$css = $this->patrollable( $result ) ? " class='not-patrolled'" : '';
+		
+		if ( $this->patrollable( $result ) )
+			$classes[] = 'not-patrolled';
 
-		return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment}</li>\n";
+		# Tags, if any.
+		list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' );
+		$classes = array_merge( $classes, $newClasses );
+
+		$css = count($classes) ? ' class="'.implode( " ", $classes).'"' : '';
+
+		return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n";
 	}
 
 	/**
@@ -378,7 +400,6 @@
 		} else {
 			$rcIndexes = array( 'rc_timestamp' );
 		}
-		$conds[] = 'page_id = rc_cur_id';
 
 		# $wgEnableNewpagesUserFilter - temp WMF hack
 		if( $wgEnableNewpagesUserFilter && $user ) {
@@ -400,13 +421,24 @@
 			$conds['page_is_redirect'] = 0;
 		}
 
-		return array(
+		$info = array(
 			'tables' => array( 'recentchanges', 'page' ),
 			'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment,
-				rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id',
+				rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id, ts_tags',
 			'conds' => $conds,
-			'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) )
+			'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ),
+			'join_conds' => array(
+				'page' => array('INNER JOIN', 'page_id=rc_cur_id'),
+			),
 		);
+
+		## Empty array for fields, it'll be set by us anyway.
+		$fields = array();
+
+		## Modify query for tags
+		ChangeTags::modifyDisplayQuery( $info['tables'], $fields, $info['conds'], $info['join_conds'], $this->opts['tagfilter'] );
+
+		return $info;
 	}
 
 	function getIndexField() {
Index: trunk/phase3/includes/specials/SpecialLog.php
===================================================================
--- trunk/phase3/includes/specials/SpecialLog.php	(revision 46459)
+++ trunk/phase3/includes/specials/SpecialLog.php	(revision 46460)
@@ -45,6 +45,7 @@
 	$pattern = $wgRequest->getBool( 'pattern' );
 	$y = $wgRequest->getIntOrNull( 'year' );
 	$m = $wgRequest->getIntOrNull( 'month' );
+	$tagFilter = $wgRequest->getVal( 'tagfilter' );
 	# Don't let the user get stuck with a certain date
 	$skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev';
 	if( $skip ) {
@@ -53,12 +54,12 @@
 	}
 	# Create a LogPager item to get the results and a LogEventsList item to format them...
 	$loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
-	$pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m );
+	$pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m, $tagFilter );
 	# Set title and add header
 	$loglist->showHeader( $pager->getType() );
 	# Show form options
 	$loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(),
-		$pager->getYear(), $pager->getMonth(), $pager->getFilterParams() );
+		$pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $tagFilter );
 	# Insert list
 	$logBody = $pager->getBody();
 	if( $logBody ) {
Index: trunk/phase3/includes/specials/SpecialRecentchanges.php
===================================================================
--- trunk/phase3/includes/specials/SpecialRecentchanges.php	(revision 46459)
+++ trunk/phase3/includes/specials/SpecialRecentchanges.php	(revision 46460)
@@ -35,6 +35,7 @@
 
 		$opts->add( 'categories', '' );
 		$opts->add( 'categories_any', false );
+		$opts->add( 'tagfilter', '' );
 		return $opts;
 	}
 
@@ -275,13 +276,19 @@
 		$namespace = $opts['namespace'];
 		$invert = $opts['invert'];
 
+		$join_conds = array();
+
 		// JOIN on watchlist for users
 		if( $uid ) {
 			$tables[] = 'watchlist';
-			$join_conds = array( 'watchlist' => array('LEFT JOIN',
-				"wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace") );
+			$join_conds['watchlist'] = array('LEFT JOIN',
+				"wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace");
 		}
 
+		// Tag stuff.
+		$fields = array(); // Fields are * in this case, so let the function modify an empty array to keep it happy.
+		ChangeTags::modifyDisplayQuery( &$tables, $fields, &$conds, &$join_conds, $opts['tagfilter'] );
+
 		wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) );
 
 		// Is there either one namespace selected or excluded?
@@ -454,6 +461,8 @@
 			$extraOpts['category'] = $this->categoryFilterForm( $opts );
 		}
 
+		$extraOpts['tagfilter'] = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
+
 		wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
 		return $extraOpts;
 	}
Index: trunk/phase3/includes/specials/SpecialContributions.php
===================================================================
--- trunk/phase3/includes/specials/SpecialContributions.php	(revision 46459)
+++ trunk/phase3/includes/specials/SpecialContributions.php	(revision 46460)
@@ -63,6 +63,8 @@
 		} else {
 			$this->opts['namespace'] = '';
 		}
+
+		$this->opts['tagfilter'] = $wgRequest->getVal( 'tagfilter' );
 	
 		// Allows reverts to have the bot flag in recent changes. It is just here to
 		// be passed in the form at the top of the page 
@@ -256,6 +258,7 @@
 			Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
 			Xml::namespaceSelector( $this->opts['namespace'], '' ) .
 			'</span>' .
+			Xml::tags( 'p', null, implode( '&nbsp;', ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ) ) ) .
 			Xml::openElement( 'p' ) .
 			'<span style="white-space: nowrap">' .
 			Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
@@ -307,7 +310,7 @@
 		$target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText();
 			
 		$pager = new ContribsPager( $target, $this->opts['namespace'], 
-			$this->opts['year'], $this->opts['month'] );
+			$this->opts['year'], $this->opts['month'], $this->opts['tagfilter'] );
 
 		$pager->mLimit = min( $this->opts['limit'], $wgFeedLimit );
 
@@ -371,13 +374,14 @@
 	var $messages, $target;
 	var $namespace = '', $mDb;
 
-	function __construct( $target, $namespace = false, $year = false, $month = false ) {
+	function __construct( $target, $namespace = false, $year = false, $month = false, $tagFilter = false ) {
 		parent::__construct();
 		foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist newpageletter minoreditletter' ) as $msg ) {
 			$this->messages[$msg] = wfMsgExt( $msg, array( 'escape') );
 		}
 		$this->target = $target;
 		$this->namespace = $namespace;
+		$this->tagFilter = $tagFilter;
 
 		$this->getDateCond( $year, $month );
 
@@ -392,7 +396,10 @@
 
 	function getQueryInfo() {
 		list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond();
-		$conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() );
+		
+		$conds = array_merge( $userCond, $this->getNamespaceCond() );
+		$join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' );
+		
 		$queryInfo = array(
 			'tables' => $tables,
 			'fields' => array(
@@ -404,6 +411,9 @@
 			'options' => array( 'USE INDEX' => array('revision' => $index) ),
 			'join_conds' => $join_cond
 		);
+		
+		ChangeTags::modifyDisplayQuery( $queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $this->tagFilter );
+		
 		wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) );
 		return $queryInfo;
 	}
@@ -463,6 +473,7 @@
 
 		$sk = $this->getSkin();
 		$rev = new Revision( $row );
+		$classes = array();
 
 		$page = Title::newFromRow( $row );
 		$page->resetArticleId( $row->rev_page ); // use process cache
@@ -521,10 +532,17 @@
 		if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
 			$ret .= ' ' . wfMsgHtml( 'deletedrev' );
 		}
+
+		# Tags, if any.
+		list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' );
+		$classes = array_merge( $classes, $newClasses );
+		$ret .= " $tagSummary";
+
 		// Let extensions add data
 		wfRunHooks( 'ContributionsLineEnding', array( &$this, &$ret, $row ) );
-		
-		$ret = "<li>$ret</li>\n";
+
+		$classes = implode( ' ', $classes );
+		$ret = "<li class=\"$classes\">$ret</li>\n";
 		wfProfileOut( __METHOD__ );
 		return $ret;
 	}
Index: trunk/phase3/includes/specials/SpecialTags.php
===================================================================
--- trunk/phase3/includes/specials/SpecialTags.php	(revision 0)
+++ trunk/phase3/includes/specials/SpecialTags.php	(revision 46460)
@@ -0,0 +1,75 @@
+<?php
+
+if (!defined('MEDIAWIKI'))
+	die;
+
+class SpecialTags extends SpecialPage {
+
+	function __construct() {
+		parent::__construct( 'Tags' );
+	}
+
+	function execute() {
+		global $wgOut, $wgUser, $wgMessageCache;
+
+		$wgMessageCache->loadAllMessages();
+		
+		$sk = $wgUser->getSkin();
+		$wgOut->setPageTitle( wfMsg( 'tags-title' ) );
+		$wgOut->addWikiMsg( 'tags-intro' );
+
+		// Write the headers
+		$html = '';
+		$html = Xml::tags( 'tr', null, Xml::tags( 'th', null, wfMsgExt( 'tags-tag', 'parseinline' ) ) .
+				Xml::tags( 'th', null, wfMsgExt( 'tags-display-header', 'parseinline' ) ) .
+				Xml::tags( 'th', null, wfMsgExt( 'tags-description-header', 'parseinline' ) ) .
+				Xml::tags( 'th', null, wfMsgExt( 'tags-hitcount-header', 'parseinline' ) )
+			);
+		$dbr = wfGetDB( DB_SLAVE );
+		$res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) );
+
+		while ( $row = $res->fetchObject() ) {
+			$html .= $this->doTagRow( $row->ct_tag, $row->hitcount );
+		}
+
+		foreach( ChangeTags::listDefinedTags() as $tag ) {
+			$html .= $this->doTagRow( $tag, 0 );
+		}
+
+		$html = "<table style='width: 80%'><tbody>$html</tbody></table>";
+
+		$wgOut->addHTML( $html );
+	}
+
+	function doTagRow( $tag, $hitcount ) {
+		static $sk=null, $doneTags=array();
+		if (!$sk) {
+			global $wgUser;
+			$sk = $wgUser->getSkin();
+		}
+
+		if ( in_array( $tag, $doneTags ) ) {
+			return '';
+		}
+		
+		$newRow = '';
+		$newRow .= Xml::tags( 'td', null, Xml::element( 'tt', null, $tag ) );
+
+		$disp = ChangeTags::tagDescription( $tag );
+		$disp .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsg( 'tag-edit' ) ) . ')';
+		$newRow .= Xml::tags( 'td', null, $disp );
+
+		$desc = wfMsgExt( "tag-$tag-description", 'parseinline' );
+		$desc = wfEmptyMsg( "tag-$tag-description", $desc ) ? '' : $desc;
+		$desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsg( 'tag-edit' ) ) . ')';
+		$newRow .= Xml::tags( 'td', null, $desc );
+
+		$hitcount = wfMsg( 'tags-hitcount', $hitcount );
+		$hitcount = $sk->link( SpecialPage::getTitleFor( 'RecentChanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) );
+		$newRow .= Xml::tags( 'td', null, $hitcount );
+
+		$doneTags[] = $tag;
+
+		return Xml::tags( 'tr', null, $newRow ) . "\n";
+	}
+}
\ No newline at end of file
Index: trunk/phase3/includes/specials/SpecialWatchlist.php
===================================================================
--- trunk/phase3/includes/specials/SpecialWatchlist.php	(revision 46459)
+++ trunk/phase3/includes/specials/SpecialWatchlist.php	(revision 46460)
@@ -218,7 +218,8 @@
 		$tables[] = 'page';
 		$join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id');
 	}
-	
+
+	ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, '' );
 	wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) );
 	
 	$res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
Index: trunk/phase3/includes/PageHistory.php
===================================================================
--- trunk/phase3/includes/PageHistory.php	(revision 46459)
+++ trunk/phase3/includes/PageHistory.php	(revision 46460)
@@ -112,6 +112,7 @@
 		 */
 		$year = $wgRequest->getInt( 'year' );
 		$month = $wgRequest->getInt( 'month' );
+		$tagFilter = $wgRequest->getVal( 'tagfilter' );
 
 		$action = htmlspecialchars( $wgScript );
 		$wgOut->addHTML(
@@ -120,6 +121,7 @@
 			Xml::hidden( 'title', $this->mTitle->getPrefixedDBKey() ) . "\n" .
 			Xml::hidden( 'action', 'history' ) . "\n" .
 			$this->getDateMenu( $year, $month ) . '&nbsp;' .
+			implode( '&nbsp;', ChangeTags::buildTagFilterSelector( $tagFilter ) ) . '&nbsp;' .
 			Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
 			'</fieldset></form>'
 		);
@@ -129,7 +131,7 @@
 		/**
 		 * Do the list
 		 */
-		$pager = new PageHistoryPager( $this, $year, $month );
+		$pager = new PageHistoryPager( $this, $year, $month, $tagFilter );
 		$this->linesonpage = $pager->getNumRows();
 		$wgOut->addHTML(
 			$pager->getNavigationBar() .
@@ -287,6 +289,7 @@
 		$lastlink = $this->lastLink( $rev, $next, $counter );
 		$arbitrary = $this->diffButtons( $rev, $firstInList, $counter );
 		$link = $this->revLink( $rev );
+		$classes = array();
 
 		$s = "($curlink) ($lastlink) $arbitrary";
 
@@ -355,9 +358,16 @@
 			$s .= ' (' . implode( ' | ', $tools ) . ')';
 		}
 
+		# Tags
+		list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' );
+		$classes = array_merge( $classes, $newClasses );
+		$s .= " $tagSummary";
+
 		wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s ) );
 
-		return "<li>$s</li>\n";
+		$classes = implode( ' ', $classes );
+
+		return "<li class=\"$classes\">$s</li>\n";
 	}
 
 	/**
@@ -589,20 +599,23 @@
 class PageHistoryPager extends ReverseChronologicalPager {
 	public $mLastRow = false, $mPageHistory, $mTitle;
 
-	function __construct( $pageHistory, $year='', $month='' ) {
+	function __construct( $pageHistory, $year='', $month='', $tagFilter = '' ) {
 		parent::__construct();
 		$this->mPageHistory = $pageHistory;
 		$this->mTitle =& $this->mPageHistory->mTitle;
+		$this->tagFilter = $tagFilter;
 		$this->getDateCond( $year, $month );
 	}
 
 	function getQueryInfo() {
 		$queryInfo = array(
 			'tables'  => array('revision'),
-			'fields'  => Revision::selectFields(),
+			'fields'  => array_merge( Revision::selectFields(), array('ts_tags') ),
 			'conds'   => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ),
-			'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') )
+			'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') ),
+			'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
 		);
+		ChangeTags::modifyDisplayQuery( $queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $this->tagFilter );
 		wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
 		return $queryInfo;
 	}
Index: trunk/phase3/includes/SpecialPage.php
===================================================================
--- trunk/phase3/includes/SpecialPage.php	(revision 46459)
+++ trunk/phase3/includes/SpecialPage.php	(revision 46460)
@@ -159,6 +159,7 @@
 		'Randomredirect'            => 'SpecialRandomredirect',
 		'Withoutinterwiki'          => array( 'SpecialPage', 'Withoutinterwiki' ),
 		'Filepath'                  => array( 'SpecialPage', 'Filepath' ),
+		'Tags'			    => 'SpecialTags',
 
 		'Mypage'                    => array( 'SpecialMypage' ),
 		'Mytalk'                    => array( 'SpecialMytalk' ),
Index: trunk/phase3/languages/messages/MessagesEn.php
===================================================================
--- trunk/phase3/languages/messages/MessagesEn.php	(revision 46459)
+++ trunk/phase3/languages/messages/MessagesEn.php	(revision 46460)
@@ -3801,4 +3801,15 @@
 
 #Put all regex fragments above this line. Leave this line exactly as it is</pre>',
 
+## Taggng-related stuff
+'tag-filter' => '[[Special:Tags|Tag]] filter:',
+'tag-filter-submit' => 'Filter',
+'tags-title' => 'Tags',
+'tags-intro' => 'This page lists the tags that the software may mark an edit with, and their meaning.',
+'tags-tag' => 'Internal tag name',
+'tags-display-header' => 'Appearance on change lists',
+'tags-description-header' => 'Full description of meaning',
+'tags-hitcount-header' => 'Tagged edits',
+'tags-edit' => 'edit',
+'tags-hitcount' => '$1 changes',
 );
Index: trunk/extensions/TorBlock/TorBlock.i18n.php
===================================================================
--- trunk/extensions/TorBlock/TorBlock.i18n.php	(revision 46459)
+++ trunk/extensions/TorBlock/TorBlock.i18n.php	(revision 46460)
@@ -15,6 +15,8 @@
 	'torblock-blocked' => 'Your IP address, <tt>$1</tt>, has been automatically identified as a tor exit node.
 Editing through tor is blocked to prevent abuse.',
 	'right-torunblocked' => 'Bypass automatic blocks of tor exit nodes',
+	'tag-tor-description' => 'If this tag is set, an edit was made from a Tor exit node.',
+	'tag-tor' => 'Made through tor',
 );
 
 /** Message documentation (Message documentation)
Index: trunk/extensions/TorBlock/TorBlock.php
===================================================================
--- trunk/extensions/TorBlock/TorBlock.php	(revision 46459)
+++ trunk/extensions/TorBlock/TorBlock.php	(revision 46460)
@@ -33,6 +33,8 @@
 $wgHooks['GetAutoPromoteGroups'][] = 'TorBlock::onGetAutoPromoteGroups';
 $wgHooks['GetBlockedStatus'][] = 'TorBlock::onGetBlockedStatus';
 $wgHooks['AutopromoteCondition'][] = 'TorBlock::onAutopromoteCondition';
+$wgHooks['RecentChange_save'][] = 'TorBlock::onRecentChangeSave';
+$wgHooks['ListDefinedTags'][] = 'TorBlock::onListDefinedTags';
 
 // Define new autopromote condition
 define('APCOND_TOR', 'tor'); // Numbers won't work, we'll get collisions
Index: trunk/extensions/TorBlock/TorBlock.class.php
===================================================================
--- trunk/extensions/TorBlock/TorBlock.class.php	(revision 46459)
+++ trunk/extensions/TorBlock/TorBlock.class.php	(revision 46460)
@@ -168,4 +168,16 @@
 		
 		return true;
 	}
+
+	public static function onRecentChangeSave( $recentChange ) {
+		if ( class_exists('ChangeTags') && self::isExitNode() ) {
+			ChangeTags::addTags( 'tor', $recentChange->mAttribs['rc_id'], $recentChange->mAttribs['rc_this_oldid'], $recentChange->mAttribs['rc_logid'] );
+		}
+		return true;
+	}
+
+	public static function onListDefinedTags( &$emptyTags ) {
+		$emptyTags[] = 'tor';
+		return true;
+	}
 }
Index: trunk/extensions/AbuseFilter/AbuseFilter.php
===================================================================
--- trunk/extensions/AbuseFilter/AbuseFilter.php	(revision 46459)
+++ trunk/extensions/AbuseFilter/AbuseFilter.php	(revision 46460)
@@ -52,8 +52,8 @@
 $wgHooks['ArticleDelete'][] = 'AbuseFilterHooks::onArticleDelete';
 $wgHooks['LoadExtensionSchemaUpdates'][] = 'AbuseFilterHooks::onSchemaUpdate';
 $wgHooks['AbortDeleteQueueNominate'][] = 'AbuseFilterHooks::onAbortDeleteQueueNominate';
-// $wgHooks['RecentChange_save'][] = 'AbuseFilterHooks::onRecentChangeSave';
-// $wgHooks['ListDefinedTags'][] = 'AbuseFilterHooks::onListDefinedTags';
+$wgHooks['RecentChange_save'][] = 'AbuseFilterHooks::onRecentChangeSave';
+$wgHooks['ListDefinedTags'][] = 'AbuseFilterHooks::onListDefinedTags';
 
 $wgAvailableRights[] = 'abusefilter-modify';
 $wgAvailableRights[] = 'abusefilter-log-detail';
@@ -63,7 +63,7 @@
 $wgAvailableRights[] = 'abusefilter-modify-restricted';
 $wgAvailableRights[] = 'abusefilter-revert';
 
-$wgAbuseFilterAvailableActions = array( 'flag', 'throttle', 'warn', 'disallow', 'blockautopromote', 'block', 'degroup', /* Disabled because it's ridiculously excessive 'rangeblock'*/ /*, 'tag' Disabled for now to avoid trunk changes. */ );
+$wgAbuseFilterAvailableActions = array( 'flag', 'throttle', 'warn', 'disallow', 'blockautopromote', 'block', 'degroup', 'tag'  );
 
 $wgAbuseFilterConditionLimit = 1000;
 
Index: trunk/extensions/AbuseFilter/Views/AbuseFilterViewEdit.php
===================================================================
--- trunk/extensions/AbuseFilter/Views/AbuseFilterViewEdit.php	(revision 46459)
+++ trunk/extensions/AbuseFilter/Views/AbuseFilterViewEdit.php	(revision 46460)
@@ -328,21 +328,20 @@
 				$warnFields['abusefilter-edit-warn-message'] = Xml::input( 'wpFilterWarnMessage', 45, $warnMsg );
 				$output .= Xml::tags( 'p', null, Xml::buildForm( $warnFields ) );
 				return $output;
-				// Commented out to avoid trunk changes for now.
-// 			case 'tag':
-// 				if ($set) {
-// 					$tags = $parameters;
-// 				} else {
-// 					$tags = array();
-// 				}
-// 				$output = '';
-// 
-// 				$checkbox = Xml::checkLabel( wfMsg('abusefilter-edit-action-tag'), 'wpFilterActionTag', 'wpFilterActionTag', $set );
-// 				$output .= Xml::tags( 'p', null, $checkbox );
-// 
-// 				$tagFields['abusefilter-edit-tag-tag'] = Xml::textarea( 'wpFilterTags', implode( "\n", $tags ) );
-// 				$output .= Xml::tags( 'p', null, Xml::buildForm( $tagFields ) );
-// 				return $output;
+			case 'tag':
+				if ($set) {
+					$tags = $parameters;
+				} else {
+					$tags = array();
+				}
+				$output = '';
+
+				$checkbox = Xml::checkLabel( wfMsg('abusefilter-edit-action-tag'), 'wpFilterActionTag', 'wpFilterActionTag', $set );
+				$output .= Xml::tags( 'p', null, $checkbox );
+
+				$tagFields['abusefilter-edit-tag-tag'] = Xml::textarea( 'wpFilterTags', implode( "\n", $tags ) );
+				$output .= Xml::tags( 'p', null, Xml::buildForm( $tagFields ) );
+				return $output;
 			default:
 				$message = 'abusefilter-edit-action-'.$action;
 				$form_field = 'wpFilterAction' . ucfirst($action);
Index: trunk/extensions/AbuseFilter/AbuseFilter.class.php
===================================================================
--- trunk/extensions/AbuseFilter/AbuseFilter.class.php	(revision 46459)
+++ trunk/extensions/AbuseFilter/AbuseFilter.class.php	(revision 46460)
@@ -439,16 +439,18 @@
 				// Do nothing. Here for completeness.
 				break;
 
-// 			case 'tag':
-// 				// Mark with a tag on recentchanges.
-// 				global $wgUser;
-// 				
-// 				$actionID = implode( '-', array(
-// 						$title->getPrefixedText(), $wgUser->getName(), $vars['ACTION']
-// 					) );
-// 
-// 				AbuseFilter::$tagsToSet[$actionID] = $parameters;
-// 				break;
+			case 'tag':
+				// Mark with a tag on recentchanges.
+				global $wgUser;
+
+				$actionID = implode( '-', array(
+						$title->getPrefixedText(), $wgUser->getName(), $vars['ACTION']
+					) );
+
+				AbuseFilter::$tagsToSet[$actionID] = $parameters;
+				break;
+			default:
+				throw new MWException( "Unrecognised action $action" );
 		}
 		
 		return $display;
Index: trunk/extensions/AbuseFilter/AbuseFilter.hooks.php
===================================================================
--- trunk/extensions/AbuseFilter/AbuseFilter.hooks.php	(revision 46459)
+++ trunk/extensions/AbuseFilter/AbuseFilter.hooks.php	(revision 46460)
@@ -172,31 +172,30 @@
 		return $filter_result == '' || $filter_result === true;
 	}
 
-// Commented out to avoid trunk changes for now.
-// 	public static function onRecentChangeSave( $recentChange ) {
-// 		$title = Title::makeTitle( $recentChange->mAttribs['rc_namespace'], $recentChange->mAttribs['rc_title'] );
-// 		$action = $recentChange->mAttribs['rc_log_type'] ? $recentChange->mAttribs['rc_log_type'] : 'edit';
-// 		$actionID = implode( '-', array(
-// 				$title->getPrefixedText(), $recentChange->mAttribs['rc_user_text'], $action
-// 			) );
-// 
-// 		if ( !empty( AbuseFilter::$tagsToSet[$actionID] ) && count( $tags = AbuseFilter::$tagsToSet[$actionID]) ) {
-// 			ChangeTags::addTags( $tags, $recentChange->mAttribs['rc_id'], $recentChange->mAttribs['rc_this_oldid'], $recentChange->mAttribs['rc_logid'] );
-// 		}
-// 
-// 		return true;
-// 	}
+	public static function onRecentChangeSave( $recentChange ) {
+		$title = Title::makeTitle( $recentChange->mAttribs['rc_namespace'], $recentChange->mAttribs['rc_title'] );
+		$action = $recentChange->mAttribs['rc_log_type'] ? $recentChange->mAttribs['rc_log_type'] : 'edit';
+		$actionID = implode( '-', array(
+				$title->getPrefixedText(), $recentChange->mAttribs['rc_user_text'], $action
+			) );
 
-// 	public static function onListDefinedTags( &$emptyTags ) {
-// 		## This is a pretty awful hack.
-// 		$dbr = wfGetDB( DB_SLAVE );
-// 
-// 		$res = $dbr->select( 'abuse_filter_action', 'afa_parameters', array( 'afa_consequence' => 'tag' ), __METHOD__ );
-// 
-// 		while( $row = $res->fetchObject() ) {
-// 			$emptyTags = array_filter( array_merge( explode( "\n", $row->afa_parameters ), $emptyTags ) );
-// 		}
-// 
-// 		return true;
-// 	}
+		if ( !empty( AbuseFilter::$tagsToSet[$actionID] ) && count( $tags = AbuseFilter::$tagsToSet[$actionID]) ) {
+			ChangeTags::addTags( $tags, $recentChange->mAttribs['rc_id'], $recentChange->mAttribs['rc_this_oldid'], $recentChange->mAttribs['rc_logid'] );
+		}
+
+		return true;
+	}
+
+	public static function onListDefinedTags( &$emptyTags ) {
+		## This is a pretty awful hack.
+		$dbr = wfGetDB( DB_SLAVE );
+
+		$res = $dbr->select( 'abuse_filter_action', 'afa_parameters', array( 'afa_consequence' => 'tag' ), __METHOD__ );
+
+		while( $row = $res->fetchObject() ) {
+			$emptyTags = array_filter( array_merge( explode( "\n", $row->afa_parameters ), $emptyTags ) );
+		}
+
+		return true;
+	}
 }

Follow-up revisions

Rev.Commit summaryAuthorDate
r46462Follow up r46460: Add new messages to messages.inc...raymond19:19, 28 January 2009
r46875Update Postgres schema per r46460ialex20:55, 5 February 2009
r46941Fix for r46460: use "CREATE INDEX ... ON ..." rather than inside "CREATE TABL...ialex21:17, 6 February 2009

Comments

#Comment by Siebrand (Talk | contribs)   23:11, 28 January 2009
  1. Should the tag filter field maybe be hidden if no labels are defined? See betawiki:Special:Contributions/Lockal and betawiki:Special:Tags. As I have no idea what tags are, and there are no tags defined, this is highly confusing.
  2. there's an issue with the link description for Special:Tags (displays as "&lt;tags&gt;"). See betawiki:Special:SpecialPages at the bottom of the page.
  3. The Special:Tags should get a wgSpecialPageGroups
#Comment by Werdna (Talk | contribs)   23:03, 5 February 2009

All fixed on svn. Thanks for the feedback.

#Comment by Werdna (Talk | contribs)   22:55, 11 February 2009

This commit is 'resolved' as-is, but it's such a huge change that I'm leaving it as 'new' until somebody has a good look at it and says that those were the only problems.

#Comment by Werdna (Talk | contribs)   01:20, 12 February 2009

Fix in r46674.

#Comment by Werdna (Talk | contribs)   01:21, 12 February 2009

Fix in r46675.

Status & tagging log

  • 00:20, 14 September 2011 Meno25 (Talk | contribs) changed the status of r46460 [removed: old added: deferred]
Personal tools
Namespaces
Variants
Views
Actions
Site
Support
Download
Development
Communication
Toolbox