MediaWiki r12546 - Code Review

Jump to: navigation, search
Repository:MediaWiki
Revision:r12545‎ | r12546 (on ViewVC)‎ | r12547 >
Date:14:20, 9 January 2006
Author:magnus_manske
Status:old
Tags:
Comment:
BIG ONE - Possible DB server killer! (deactivated by default:-)
Recent changes can now be filtered by categories (AND and OR)
To enable, set $wgAllowCategorizedRecentChanges = true ; in LocalSettings
Modified paths:

Diff [purge]

Index: trunk/phase3/includes/Categoryfinder.php
===================================================================
--- trunk/phase3/includes/Categoryfinder.php	(revision 0)
+++ trunk/phase3/includes/Categoryfinder.php	(revision 12546)
@@ -0,0 +1,191 @@
+<?php
+/*
+The "Categoryfinder" class takes a list of articles, creates an internal representation of all their parent
+categories (as well as parents of parents etc.). From this representation, it determines which of these articles
+are in one or all of a given subset of categories.
+
+Example use :
+
+	# Determines wether the article with the page_id 12345 is in both
+	# "Category 1" and "Category 2" or their subcategories, respectively
+	
+	$cf = new Categoryfinder ;
+	$cf->seed (
+		array ( 12345 ) ,
+		array ( "Category 1","Category 2" ) ,
+		"AND"
+	) ;
+	$a = $cf->run() ;
+	print implode ( "," , $a ) ;
+
+*/
+
+
+if( !defined( 'MEDIAWIKI' ) ) die();
+
+class Categoryfinder {
+
+	var $articles = array () ; # The original article IDs passed to the seed function
+	var $deadend = array () ; # Array of DBKEY category names for categories that don't have a page
+	var $parents = array () ; # Array of [ID => array()]
+	var $next = array () ; # Array of article/category IDs
+	var $targets = array () ; # Array of DBKEY category names
+	var $name2id = array () ;
+	var $mode ; # "AND" or "OR"
+	var $dbr ; # Read-DB slave
+
+	/**
+	 * Constructor (currently empty).
+	*/
+	function Categoryfinder () {
+	}
+
+	/**
+	 * Initializes the instance. Do this prior to calling run().
+	 @param $article_ids Array of article IDs
+	*/
+	function seed ( $article_ids , $categories , $mode = "AND" ) {
+		$this->articles = $article_ids ;
+		$this->next = $article_ids ;
+		$this->mode = $mode ;
+
+		# Set the list of target categories; convert them to DBKEY form first
+		$this->targets = array () ;
+		foreach ( $categories AS $c ) {
+			$ct = Title::newFromText ( $c , NS_CATEGORY ) ;
+			$c = $ct->getDBkey () ;
+			$this->targets[$c] = $c ;
+		}
+	}
+
+	/**
+	 * Iterates through the parent tree starting with the seed values,
+	 * then checks the articles if they match the conditions
+	 @return array of page_ids (those given to seed() that match the conditions)
+	*/
+	function run () {
+		$this->dbr =& wfGetDB( DB_SLAVE );
+		while ( count ( $this->next ) > 0 ) {
+			$this->scan_next_layer () ;
+		}
+
+		# Now check if this applies to the individual articles
+		$ret = array () ;
+		foreach ( $this->articles AS $article ) {
+			$conds = $this->targets ;
+			if ( $this->check ( $article , $conds ) ) {
+				# Matches the conditions
+				$ret[] = $article ;
+			}
+		}
+		return $ret ;
+	}
+
+	/**
+	 * This functions recurses through the parent representation, trying to match the conditions
+	 @param $id The article/category to check
+	 @param $conds The array of categories to match
+	 @return bool Does this match the conditions?
+	*/
+	function check ( $id , &$conds ) {
+		# Shortcut (runtime paranoia): No contitions=all matched
+		if ( count ( $conds ) == 0 ) return true ;
+		
+		if ( !isset ( $this->parents[$id] ) ) return false ;
+
+		# iterate through the parents
+		foreach ( $this->parents[$id] AS $p ) {
+			$pname = $p->cl_to ;
+			
+			# Is this a condition?
+			if ( isset ( $conds[$pname] ) ) {
+				# This key is in the category list!
+				if ( $this->mode == "OR" ) {
+					# One found, that's enough!
+					$conds = array () ;
+					return true ;
+				} else {
+					# Assuming "AND" as default
+					unset ( $conds[$pname] ) ;
+					if ( count ( $conds ) == 0 ) {
+						# All conditions met, done
+						return true ;
+					}
+				}
+			}
+			
+			# Not done yet, try sub-parents
+			if ( !isset ( $this->name2id[$pname] ) ) {
+				# No sub-parent
+				continue ;
+			}
+			$done = $this->check ( $this->name2id[$pname] , $conds ) ;
+			if ( $done OR count ( $conds ) == 0 ) {
+				# Subparents have done it!
+				return true ;
+			}
+		}
+		return false ;
+	}
+
+	/**
+	 * Scans a "parent layer" of the articles/categories in $this->next
+	*/
+	function scan_next_layer () {
+		$fname = "Categoryfinder::scan_next_layer" ;
+	
+		# Find all parents of the article currently in $this->next
+		$layer = array () ;
+		$res = $this->dbr->select(
+				/* FROM   */ 'categorylinks',
+				/* SELECT */ '*',
+				/* WHERE  */ array( 'cl_from' => $this->next ),
+				$fname."-1"
+		);
+		while ( $o = $this->dbr->fetchObject( $res ) ) {
+			$k = $o->cl_to ;
+
+			# Update parent tree
+			if ( !isset ( $this->parents[$o->cl_from] ) ) {
+				$this->parents[$o->cl_from] = array () ;
+			}
+			$this->parents[$o->cl_from][$k] = $o ;
+
+			# Ignore those we already have
+			if ( in_array ( $k , $this->deadend ) ) continue ;
+			if ( isset ( $this->name2id[$k] ) ) continue ;
+
+			# Hey, new category!
+			$layer[$k] = $k ;
+		}
+		$this->dbr->freeResult( $res ) ;
+
+		$this->next = array() ;
+		
+		# Find the IDs of all category pages in $layer, if they exist
+		if ( count ( $layer ) > 0 ) {
+			$res = $this->dbr->select(
+					/* FROM   */ 'page',
+					/* SELECT */ 'page_id,page_title',
+					/* WHERE  */ array( 'page_namespace' => NS_CATEGORY , 'page_title' => $layer ),
+					$fname."-2"
+			);
+			while ( $o = $this->dbr->fetchObject( $res ) ) {
+				$id = $o->page_id ;
+				$name = $o->page_title ;
+				$this->name2id[$name] = $id ;
+				$this->next[] = $id ;
+				unset ( $layer[$name] ) ;
+			}
+			$this->dbr->freeResult( $res ) ;
+			}
+
+		# Mark dead ends
+		foreach ( $layer AS $v ) {
+			$this->deadend[$v] = $v ;
+		}
+	}
+
+} # END OF CLASS "Categoryfinder"
+
+?>
\ No newline at end of file

Property changes on: trunk/phase3/includes/Categoryfinder.php
___________________________________________________________________
Name: svn:eol-style
   + native
Name: svn:keywords
   + Author Date Id Revision

Index: trunk/phase3/includes/SpecialRecentchanges.php
===================================================================
--- trunk/phase3/includes/SpecialRecentchanges.php	(revision 12545)
+++ trunk/phase3/includes/SpecialRecentchanges.php	(revision 12546)
@@ -18,6 +18,7 @@
 function wfSpecialRecentchanges( $par, $specialPage ) {
 	global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
 	global $wgRCShowWatchingUsers, $wgShowUpdatedMarker;
+	global $wgAllowCategorizedRecentChanges ;
 	$fname = 'wfSpecialRecentchanges';
 
 	# Get query parameters
@@ -33,6 +34,7 @@
 	/* text */ 'from' => '',
 	/* text */ 'namespace' => null,
 	/* bool */ 'invert' => false,
+	/* bool */ 'categories_any' => true,
 	);
 
 	extract($defaults);
@@ -168,6 +170,7 @@
 
 		// Run existence checks
 		$batch->execute();
+		$any = $wgRequest->getBool ( 'categories_any' , false ) ;
 
 		// Output header
 		if ( !$specialPage->including() ) {
@@ -185,6 +188,7 @@
 			wfAppendToArrayIfNotDefault( 'from', $from, $defaults, $nondefaults);
 			wfAppendToArrayIfNotDefault( 'namespace', $namespace, $defaults, $nondefaults);
 			wfAppendToArrayIfNotDefault( 'invert', $invert, $defaults, $nondefaults);
+			wfAppendToArrayIfNotDefault( 'categories_any', $any, $defaults, $nondefaults);
 
 			// Add end of the texts
 			$wgOut->addHTML( '<div class="rcoptions">' . rcOptionsPanel( $defaults, $nondefaults ) . "\n" );
@@ -196,6 +200,13 @@
 		$wgOut->setSyndicated( true );
 
 		$list = ChangesList::newFromUser( $wgUser );
+		
+		if ( $wgAllowCategorizedRecentChanges ) {
+			$categories = trim ( $wgRequest->getVal ( 'categories' , "" ) ) ;
+			$categories = str_replace ( "|" , "\n" , $categories ) ;
+			$categories = explode ( "\n" , $categories ) ;
+			rcFilterByCategories ( $rows , $categories , $any ) ;
+		}
 
 		$s = $list->beginRecentChangesList();
 		$counter = 1;
@@ -234,6 +245,53 @@
 	}
 }
 
+function rcFilterByCategories ( &$rows , $categories , $any ) {
+	require_once ( 'Categoryfinder.php' ) ;
+	
+	# Filter categories
+	$cats = array () ;
+	foreach ( $categories AS $cat ) {
+		$cat = trim ( $cat ) ;
+		if ( $cat == "" ) continue ;
+		$cats[] = $cat ;
+	}
+	
+	# Filter articles
+	$articles = array () ;
+	$a2r = array () ;
+	foreach ( $rows AS $k => $r ) {
+		$nt = Title::newFromText ( $r->rc_title , $r->rc_namespace ) ;
+		$id = $nt->getArticleID() ;
+		if ( $id == 0 ) continue ; # Page might have been deleted...
+		if ( !in_array ( $id , $articles ) ) {
+			$articles[] = $id ;
+		}
+		if ( !isset ( $a2r[$id] ) ) {
+			$a2r[$id] = array() ;
+		}
+		$a2r[$id][] = $k ;
+	}
+	
+	# Shortcut?
+	if ( count ( $articles ) == 0 OR count ( $cats ) == 0 )
+		return ;
+	
+	# Look up
+	$c = new Categoryfinder ;
+	$c->seed ( $articles , $cats , $any ? "OR" : "AND" ) ;
+	$match = $c->run () ;
+	
+	# Filter
+	$newrows = array () ;
+	foreach ( $match AS $id ) {
+		foreach ( $a2r[$id] AS $rev ) {
+			$k = $rev ;
+			$newrows[$k] = $rows[$k] ;
+		}
+	}
+	$rows = $newrows ;
+}
+
 function rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ) {
 	global $messageMemc, $wgDBname, $wgFeedCacheTimeout;
 	global $wgFeedClasses, $wgTitle, $wgSitename, $wgContLanguageCode;
@@ -460,13 +518,28 @@
  * @return string
  */
 function rcNamespaceForm ( $namespace, $invert, $nondefaults ) {
-	global $wgContLang, $wgScript;
+	global $wgContLang, $wgScript, $wgAllowCategorizedRecentChanges, $wgRequest;
 	$t = Title::makeTitle( NS_SPECIAL, 'Recentchanges' );
 
 	$namespaceselect = HTMLnamespaceselector($namespace, '');
 	$submitbutton = '<input type="submit" value="' . wfMsgHtml( 'allpagessubmit' ) . "\" />\n";
 	$invertbox = "<input type='checkbox' name='invert' value='1' id='nsinvert'" . ( $invert ? ' checked="checked"' : '' ) . ' />';
-
+	
+	if ( $wgAllowCategorizedRecentChanges ) {
+		$categories = trim ( $wgRequest->getVal ( 'categories' , "" ) ) ;
+		$any = $wgRequest->getBool ( 'categories_any' , true ) ;
+		$cb_arr = array( 'type' => 'checkbox', 'name' => 'categories_any', 'value' => "1" ) ;
+		if ( $any ) $cb_arr['checked'] = "checked" ;
+		$catbox = "<br/>" ;
+		$catbox .= wfMsg('rc_categories') . " ";
+		$catbox .= wfElement('input', array( 'type' => 'text', 'name' => 'categories', 'value' => $categories));
+		$catbox .= " &nbsp;" ;
+		$catbox .= wfElement('input', $cb_arr );
+		$catbox .= wfMsg('rc_categories_any');
+	} else {
+		$catbox = "" ;
+	}
+	
 	$out = "<div class='namespacesettings'><form method='get' action='{$wgScript}'>\n";
 
 	foreach ( $nondefaults as $key => $value ) {
@@ -478,7 +551,7 @@
 	$out .= "
 <div id='nsselect' class='recentchanges'>
 	<label for='namespace'>" . wfMsgHtml('namespace') . "</label>
-	{$namespaceselect}{$submitbutton}{$invertbox} <label for='nsinvert'>" . wfMsgHtml('invert') . "</label>\n</div>";
+	{$namespaceselect}{$submitbutton}{$invertbox} <label for='nsinvert'>" . wfMsgHtml('invert') . "</label>{$catbox}\n</div>";
 	$out .= '</form></div>';
 	return $out;
 }
Index: trunk/phase3/includes/DefaultSettings.php
===================================================================
--- trunk/phase3/includes/DefaultSettings.php	(revision 12545)
+++ trunk/phase3/includes/DefaultSettings.php	(revision 12546)
@@ -1807,4 +1807,9 @@
 
 $wgFilterRobotsWL = false;
 
+/**
+ * Enable filtering of categories in Recentchanges
+ */
+$wgAllowCategorizedRecentChanges = false ;
+
 ?>
Index: trunk/phase3/languages/Language.php
===================================================================
--- trunk/phase3/languages/Language.php	(revision 12545)
+++ trunk/phase3/languages/Language.php	(revision 12546)
@@ -1005,6 +1005,8 @@
 'sectionlink' => '→',
 'number_of_watching_users_RCview' 	=> '[$1]',
 'number_of_watching_users_pageview' 	=> '[$1 watching user/s]',
+'rc_categories'	=> 'Limit to categories (separate with "|")',
+'rc_categories_any'	=> 'Any',
 
 # Upload
 #

Status & tagging log

  • 01:58, 13 October 2010 ^demon (Talk | contribs) changed the status of r12546 [removed: new added: old]
Personal tools
Namespaces
Variants
Views
Actions
Site
Support
Download
Development
Communication
Toolbox