From 6cfcc8475f93c318c0ae951d4a51b0a1cf97ea7f Mon Sep 17 00:00:00 2001 From: Magnus Manske Date: Mon, 9 Jan 2006 14:20:26 +0000 Subject: [PATCH] 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 --- includes/Categoryfinder.php | 191 ++++++++++++++++++++++++++++++ includes/DefaultSettings.php | 5 + includes/SpecialRecentchanges.php | 79 +++++++++++- languages/Language.php | 2 + 4 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 includes/Categoryfinder.php diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php new file mode 100644 index 0000000000..f9c4aa3677 --- /dev/null +++ b/includes/Categoryfinder.php @@ -0,0 +1,191 @@ +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 diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 834bbee0ce..feb430e0cf 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1807,4 +1807,9 @@ $wgUseTrackbacks = false; $wgFilterRobotsWL = false; +/** + * Enable filtering of categories in Recentchanges + */ +$wgAllowCategorizedRecentChanges = false ; + ?> diff --git a/includes/SpecialRecentchanges.php b/includes/SpecialRecentchanges.php index 3346b7a803..f48a253cb2 100644 --- a/includes/SpecialRecentchanges.php +++ b/includes/SpecialRecentchanges.php @@ -18,6 +18,7 @@ require_once( 'Revision.php' ); function wfSpecialRecentchanges( $par, $specialPage ) { global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol; global $wgRCShowWatchingUsers, $wgShowUpdatedMarker; + global $wgAllowCategorizedRecentChanges ; $fname = 'wfSpecialRecentchanges'; # Get query parameters @@ -33,6 +34,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) { /* text */ 'from' => '', /* text */ 'namespace' => null, /* bool */ 'invert' => false, + /* bool */ 'categories_any' => true, ); extract($defaults); @@ -168,6 +170,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) { // Run existence checks $batch->execute(); + $any = $wgRequest->getBool ( 'categories_any' , false ) ; // Output header if ( !$specialPage->including() ) { @@ -185,6 +188,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) { 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( '
' . rcOptionsPanel( $defaults, $nondefaults ) . "\n" ); @@ -196,6 +200,13 @@ function wfSpecialRecentchanges( $par, $specialPage ) { $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 wfSpecialRecentchanges( $par, $specialPage ) { } } +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 @@ function rcOptionsPanel( $defaults, $nondefaults ) { * @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 = '\n"; $invertbox = "'; - + + 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 = "
" ; + $catbox .= wfMsg('rc_categories') . " "; + $catbox .= wfElement('input', array( 'type' => 'text', 'name' => 'categories', 'value' => $categories)); + $catbox .= "  " ; + $catbox .= wfElement('input', $cb_arr ); + $catbox .= wfMsg('rc_categories_any'); + } else { + $catbox = "" ; + } + $out = "
\n"; foreach ( $nondefaults as $key => $value ) { @@ -478,7 +551,7 @@ function rcNamespaceForm ( $namespace, $invert, $nondefaults ) { $out .= "
- {$namespaceselect}{$submitbutton}{$invertbox} \n
"; + {$namespaceselect}{$submitbutton}{$invertbox} {$catbox}\n
"; $out .= '
'; return $out; } diff --git a/languages/Language.php b/languages/Language.php index 6956f134b4..765c6f3664 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -1005,6 +1005,8 @@ Unselected groups will not be changed. You can deselect a group with CTRL + Left '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 # -- 2.20.1