*/ $wgExtensionFunctions[] = "wfExtensionSpecialRecentchangesfilter"; $wgExtensionCredits['specialpage'][] = array( 'name' => 'Special:Recentchangesfilter', 'version' => '2006/11/16', 'author' => 'Austin Che', 'url' => 'http://openwetware.org/wiki/User:Austin/Extensions/Recentchangesfilter', 'description' => 'Enables filtering the items that show up in the recent changes list', ); $wgHooks['SpecialPage_initList'][] = 'wfOverrideSpecialRecentchanges'; function wfExtensionSpecialRecentchangesfilter() { global $wgMessageCache; $wgMessageCache->addMessages(array('rcfilters' => 'Filters:', 'rcfiltertitle' => 'Recent changes filtering', 'rcfilter' => 'Filter list', 'rcfilterdefault' => 'Default filter number', 'tog-rcfilterenable' => 'Enable user-specific filtered recent changes', 'defaultrcfilter' => '[None];[Watchlist]w=1', )); // use the preferences extension only if it has been loaded if (function_exists("wfAddPreferences")) { wfAddPreferences(array(array('section' => "prefs-rc", 'html' => "
" . wfMsg('rcfiltertitle') . ""), array('name' => "rcfilterenable", 'section' => "prefs-rc", 'type' => PREF_TOGGLE_T), array('name' => "rcfilter", 'section' => "prefs-rc", 'type' => PREF_TEXT_T, 'default' => wfMsg('defaultrcfilter'), 'size' => 50), array('name' => "rcfilterdefault", 'section' => "prefs-rc", 'type' => PREF_INT_T, 'size' => 5, 'default' => 0), array('section' => "prefs-rc", 'html' => "
"), )); } } function wfOverrideSpecialRecentchanges(&$list) { // we 'override' the default recent changes special page with our own // we redefine the entry point to our own filter function $list['Recentchanges'] = array('IncludableSpecialPage', 'Recentchanges', '', true, 'wfSpecialRecentchangesfilter'); return true; } /** * Entrypoint * @param string $par path info, the filter criteria if set */ function wfSpecialRecentchangesfilter($par, $specialPage) { require_once( 'SpecialRecentchanges.php' ); // decide whether to filter or not // in default case, we call the normal recent changes global $wgRequest, $wgUser; if ($par || $wgRequest->getVal('filter') || $wgUser->getOption('rcfilterenable')) return filterRecentchanges($par, $specialPage); else return wfSpecialRecentchanges($par, $specialPage); } /** * Our filtering version of recent changes * Much of this function was copied from the default SpecialRecentchanges.php */ function filterRecentchanges( $par, $specialPage ) { global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol, $wgDBtype; global $wgRCShowWatchingUsers, $wgShowUpdatedMarker; global $wgAllowCategorizedRecentChanges ; $fname = 'wfSpecialRecentchanges'; // Get query parameters $feedFormat = $wgRequest->getVal( 'feed' ); /* Checkbox values can't be true be default, because * we cannot differentiate between unset and not set at all */ $defaults = array( /* int */ 'days' => $wgUser->getDefaultOption('rcdays'), /* int */ 'limit' => $wgUser->getDefaultOption('rclimit'), /* bool */ 'hideminor' => false, /* bool */ 'hidebots' => true, /* bool */ 'hideanons' => false, /* bool */ 'hideliu' => false, /* bool */ 'hidepatrolled' => false, /* bool */ 'hidemyself' => false, /* text */ 'from' => '', /* text */ 'namespace' => null, /* bool */ 'invert' => false, /* bool */ 'categories_any' => false, ); extract($defaults); $days = $wgUser->getOption( 'rcdays' ); if ( !$days ) { $days = $defaults['days']; } $days = $wgRequest->getInt( 'days', $days ); $limit = $wgUser->getOption( 'rclimit' ); if ( !$limit ) { $limit = $defaults['limit']; } $limit = $wgRequest->getInt( 'limit', $limit ); /* order of selection: url > preferences > default */ $hideminor = $wgRequest->getBool( 'hideminor', $wgUser->getOption( 'hideminor') ? true : $defaults['hideminor'] ); $filter = $wgRequest->getVal( 'filter' ); // As a feed, use limited settings only if ( $feedFormat ) { global $wgFeedLimit; if( $limit > $wgFeedLimit ) { $options['limit'] = $wgFeedLimit; } } else { $namespace = $wgRequest->getIntOrNull( 'namespace' ); $invert = $wgRequest->getBool( 'invert', $defaults['invert'] ); $hidebots = $wgRequest->getBool( 'hidebots', $defaults['hidebots'] ); $hideanons = $wgRequest->getBool( 'hideanons', $defaults['hideanons'] ); $hideliu = $wgRequest->getBool( 'hideliu', $defaults['hideliu'] ); $hidepatrolled = $wgRequest->getBool( 'hidepatrolled', $defaults['hidepatrolled'] ); $hidemyself = $wgRequest->getBool ( 'hidemyself', $defaults['hidemyself'] ); $from = $wgRequest->getVal( 'from', $defaults['from'] ); // Get query parameters from path // This is treated differently from the default Recentchanges so isn't completely compatible // Only things allowed other than filter criteria are limit/days separated by & (instead of ,) // everything else is treated as part of the filter criteria if ( $par ) { $bits = preg_split( '/\s*&\s*/', trim( $par ) ); foreach ( $bits as $bit ) { if (preg_match('/^limit=(\d+)$/', $bit, $m)) $limit = $m[1]; else if (preg_match('/^days=(\d+)$/', $bit, $m)) $days = $m[1]; else $filter .= $bit; } } } if ( $limit < 0 || $limit > 5000 ) $limit = $defaults['limit']; $filters = ""; if ($wgUser->getOption('rcfilterenable')) $filters = getUserFilters($wgUser->getOption('rcfilter')); if (! $filter && $filters) $filter = $filters[$wgUser->getOption('rcfilterdefault')][1]; $filtersql = filterCondition($filter); // Database connection and caching $dbr =& wfGetDB( DB_SLAVE ); extract( $dbr->tableNames( 'recentchanges', 'watchlist' ) ); $cutoff_unixtime = time() - ( $days * 86400 ); $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); $cutoff = $dbr->timestamp( $cutoff_unixtime ); if(preg_match('/^[0-9]{14}$/', $from) and $from > wfTimestamp(TS_MW,$cutoff)) { $cutoff = $dbr->timestamp($from); } else { $from = $defaults['from']; } // 10 seconds server-side caching max $wgOut->setSquidMaxage( 10 ); // Get last modified date, for client caching // Don't use this if we are using the patrol feature, patrol changes don't update the timestamp $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, $fname ); if ( $feedFormat || !$wgUseRCPatrol ) { if( $lastmod && $wgOut->checkLastModified( $lastmod ) ){ // Client cache fresh and headers sent, nothing more to do. return; } } # It makes no sense to hide both anons and logged-in users # Where this occurs, force anons to be shown if( $hideanons && $hideliu ) $hideanons = false; # Form WHERE fragments for all the options $hidem = $hideminor ? 'AND rc_minor = 0' : ''; $hidem .= $hidebots ? ' AND rc_bot = 0' : ''; $hidem .= $hideliu ? ' AND rc_user = 0' : ''; $hidem .= ( $wgUseRCPatrol && $hidepatrolled ) ? ' AND rc_patrolled = 0' : ''; $hidem .= $hideanons ? ' AND rc_user != 0' : ''; if( $hidemyself ) { if( $wgUser->getID() ) { $hidem .= ' AND rc_user != ' . $wgUser->getID(); } else { $hidem .= ' AND rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() ); } } # Namespace filtering $hidem .= is_null( $namespace ) ? '' : ' AND rc_namespace' . ($invert ? '!=' : '=') . $namespace; // This is the big thing! $uid = $wgUser->getID(); // Perform query $forceclause = $dbr->useIndexClause("rc_timestamp"); $sql2 = "SELECT * FROM $recentchanges $forceclause". ($uid ? "LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . "WHERE rc_timestamp >= '{$cutoff}' {$hidem} {$filtersql} " . "ORDER BY rc_timestamp DESC"; $sql2 = $dbr->limitResult($sql2, $limit, 0); $res = $dbr->query( $sql2, $fname ); // Fetch results, prepare a batch link existence check query $rows = array(); $batch = new LinkBatch; while( $row = $dbr->fetchObject( $res ) ){ $rows[] = $row; if ( !$feedFormat ) { // User page link $title = Title::makeTitleSafe( NS_USER, $row->rc_user_text ); $batch->addObj( $title ); // User talk $title = Title::makeTitleSafe( NS_USER_TALK, $row->rc_user_text ); $batch->addObj( $title ); } } $dbr->freeResult( $res ); if( $feedFormat ) { rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ); } else { // Web output... // Run existence checks $batch->execute(); $any = $wgRequest->getBool( 'categories_any', $defaults['categories_any']); // Output header if ( !$specialPage->including() ) { $wgOut->addWikiText( wfMsgForContent( "recentchangestext" ) ); // Dump everything here $nondefaults = array(); wfAppendToArrayIfNotDefault( 'days', $days, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'limit', $limit , $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'hideminor', $hideminor, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'hidebots', $hidebots, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'hideanons', $hideanons, $defaults, $nondefaults ); wfAppendToArrayIfNotDefault( 'hideliu', $hideliu, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'hidepatrolled', $hidepatrolled, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'hidemyself', $hidemyself, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'from', $from, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'namespace', $namespace, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'invert', $invert, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'categories_any', $any, $defaults, $nondefaults); if ($filter) $nondefaults["filter"] = $filter; // Add end of the texts $wgOut->addHTML( '
' . rcOptionsPanel( $defaults, $nondefaults ) ); $wgOut->addHTML( rcNamespaceForm( $namespace, $invert, $nondefaults, $any)); $wgOut->addHTML(rcFilterOptions($filters, $nondefaults, $filter)); $wgOut->addHTML('
'); } // And now for the content $sk = $wgUser->getSkin(); $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; foreach( $rows as $obj ){ if( $limit == 0) { break; } if ( ! ( $hideminor && $obj->rc_minor ) && ! ( $hidepatrolled && $obj->rc_patrolled ) ) { $rc = RecentChange::newFromRow( $obj ); $rc->counter = $counter++; if ($wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) && ($obj->rc_timestamp >= $obj->wl_notificationtimestamp)) { $rc->notificationtimestamp = true; } else { $rc->notificationtimestamp = false; } if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) { $sql3 = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" . $dbr->strencode($obj->rc_title) ."' AND wl_namespace=$obj->rc_namespace" ; $res3 = $dbr->query( $sql3, 'wfSpecialRecentChanges'); $x = $dbr->fetchObject( $res3 ); $rc->numberofWatchingusers = $x->n; } else { $rc->numberofWatchingusers = 0; } $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ) ); --$limit; } } $s .= $list->endRecentChangesList(); $wgOut->addHTML( $s ); //$wgOut->addHTML( $filtersql ); // debug } } function rcFilterOptions($filters, $nondefaults, $currentfilter) { $str = ""; if (! $filters) return ""; foreach ($filters as $filter) { if ($str) $str .= " | "; // if a filter is empty (i.e. no filter) it doesn't get included in the cgi // string by makeOptionsLink which calls wfArrayToCGI // we need it to be present however (to distinguish it from no specified filter) // so we set it to a space which is equivalent to no filter if ($filter[1] == '') $filter[1] = ' '; $link = makeOptionsLink( $filter[0], array( 'filter' => $filter[1] ), $nondefaults); if (trim($currentfilter) == trim($filter[1])) $link = "$link"; $str .= $link; } return wfMsg('rcfilters') . " " . $str; } function getUserFilters($filterstr) { // given a string representing the list of filters, parses and returns an array of filters // each element is 2 elements (name, filter) $filterlist = explode(';', trim($filterstr)); $filters = array(); $num = 0; global $wgOut; foreach ($filterlist as $filter) { if (preg_match('/^\[(.*)\](.*)$/', $filter, $m)) { $name = $m[1]; $filter = $m[2]; } else $name = $num; $filters[] = array($name, $filter); $num++; } return $filters; } function filterCondition($filter) { global $wgUser, $wgContLang; $uid = $wgUser->getID(); $filtersql = ""; if ($filter) { $disjuncts = preg_split( '/\s*,\s*/', trim( $filter ) ); foreach ( $disjuncts as $disjunct ) { $disjunctsql = ""; $literals = preg_split( '/\s*\^\s*/', trim( $disjunct ) ); foreach ( $literals as $literal ) { $litsql = ""; if (! preg_match( '/^(.*)=(.*)$/', $literal, $m ) ) { // default is match type 'b' $m[1] = 'b'; $m[2] = $literal; } // all our matching conditions don't need spaces (which aren't valid in titles) // also some browsers convert spaces to + // convert both to underscores //$m[2] = str_replace('+', ' ', $m[2]); //$m[2] = str_replace(' ', '_', $m[2]); switch ($m[1]) { case "n": case "nn": $n = is_numeric($m[2]) ? $m[2] : $wgContLang->getNsIndex($m[2]); if (is_numeric($n)) { if ($m[1] == "n") $litsql = "rc_namespace=$n"; else $litsql = "rc_namespace<>$n"; } break; case "u": case "nu": $id = User::idFromName($m[2]); // if $id = 0, no such name, in which case we match non-logged in users if ($m[1] == "u") $litsql = "rc_user=$id"; else $litsql = "rc_user<>$id"; break; case "w": if ($uid) $litsql = "wl_user IS NOT NULL"; break; case "nw": if ($uid) $litsql = "wl_user IS NULL"; break; case "p": case "np": $t = Title::newfromURL($m[2]); if (! $t) break; $page = $t->getDBkey(); $ns = $t->getNamespace(); if ($m[1] == "p") $litsql = "((rc_title='$page') AND (rc_namespace=$ns))"; else $litsql = "((rc_title<>'$page') OR (rc_namespace<>$ns))"; break; case "b": $litsql = "rc_title LIKE '$m[2]%'"; break; case "nb": $litsql = "rc_title NOT LIKE '$m[2]%'"; break; case "e": $litsql = "rc_title LIKE '%$m[2]'"; break; case "ne": $litsql = "rc_title NOT LIKE '%$m[2]'"; break; case "c": $litsql = "rc_title LIKE '%$m[2]%'"; break; case "nc": $litsql = "rc_title NOT LIKE '%$m[2]%'"; break; case "m": $litsql = "rc_title LIKE '$m[2]'"; break; case "nm": $litsql = "rc_title NOT LIKE '$m[2]'"; break; } if ($litsql) $disjunctsql = $disjunctsql ? "$disjunctsql AND ($litsql)" : "($litsql)"; } if ($disjunctsql) $filtersql = $filtersql ? "$filtersql OR ( $disjunctsql) " : "($disjunctsql)"; } if ($filtersql) $filtersql = "AND ( $filtersql )"; } return $filtersql; } ?>