*/ $wgExtensionFunctions[] = "wfExtensionSpecialRecentchangesfilter"; $wgExtensionCredits['specialpage'][] = array( 'name' => 'Special:Recentchangesfilter', 'version' => '2006/03/11', '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', ); 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' => "
"), )); } // we 'override' the default recent changes special page with our own // first remove the default SpecialPage::removePage("Recentchanges"); // We recreate a new special page called Recentchanges // this will automatically call require_once( 'SpecialRecentchanges.php' ); // which is what we need as we use functions there // however, we want to redefine the entry point to our own filter function SpecialPage::addPage( new IncludableSpecialPage( 'Recentchanges', '', true, 'wfSpecialRecentchangesfilter')); } /** * Entrypoint * @param string $par path info, the filter criteria if set */ function wfSpecialRecentchangesfilter($par, $specialPage) { // 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, $wgLang, $wgContLang, $wgTitle, $wgMemc, $wgDBname; global $wgRequest, $wgSitename, $wgLanguageCode, $wgContLanguageCode; global $wgFeedClasses, $wgUseRCPatrol; global $wgRCShowWatchingUsers, $wgShowUpdatedMarker; global $wgLinkCache; $fname = 'wfSpecialRecentchanges'; // Get query parameters $feedFormat = $wgRequest->getVal( 'feed' ); $defaults = array( /* int */ 'days' => $wgUser->getDefaultOption('rcdays'), /* int */ 'limit' => $wgUser->getDefaultOption('rclimit'), /* bool */ 'hideminor' => false, /* bool */ 'hidebots' => true, /* bool */ 'hideliu' => false, /* bool */ 'hidepatrolled' => false, /* text */ 'from' => '', /* text */ 'namespace' => null, /* bool */ 'invert' => 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'] ); $hideliu = $wgRequest->getBool( 'hideliu', $defaults['hideliu'] ); $hidepatrolled = $wgRequest->getBool( 'hidepatrolled', $defaults['hidepatrolled'] ); $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']; 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; } } $hidem = $hideminor ? 'AND rc_minor=0' : ''; $hidem .= $hidebots ? ' AND rc_bot=0' : ''; $hidem .= $hideliu ? ' AND rc_user=0' : ''; $hidem .= $hidepatrolled ? ' AND rc_patrolled=0' : ''; $hidem .= is_null( $namespace ) ? '' : ' AND rc_namespace' . ($invert ? '!=' : '=') . $namespace; // This is the big thing! $uid = $wgUser->getID(); $notifts = ($wgShowUpdatedMarker?",wl_notificationtimestamp":""); // Perform query $sql2 = "SELECT $recentchanges.*" . ($uid ? ",wl_user".$notifts : "") . " FROM $recentchanges " . ($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 LIMIT {$limit}"; $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; // 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 ); // Run existence checks $batch->execute( $wgLinkCache ); if( $feedFormat ) { rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ); } else { // Web output... // 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( 'hideliu', $hideliu, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'hidepatrolled', $hidepatrolled, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'from', $from, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'namespace', $namespace, $defaults, $nondefaults); wfAppendToArrayIfNotDefault( 'invert', $invert, $defaults, $nondefaults); if ($filter) $nondefaults["filter"] = $filter; // Add end of the texts $wgOut->addHTML( '
' . rcOptionsPanel( $defaults, $nondefaults ) ); $wgOut->addHTML( rcNamespaceForm( $namespace, $invert, $nondefaults)); $wgOut->addHTML(rcFilterOptions($filters, $nondefaults, $filter)); $wgOut->addHTML('
'); } // And now for the content $sk = $wgUser->getSkin(); $wgOut->setSyndicated( true ); $list =& new ChangesList( $sk ); $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 ); } // debug //$wgOut->addHTML("

filter = $filter

"); //$wgOut->addHTML("

sql = $filtersql

"); } 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 ($currentfilter == $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 "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; case "n": $n = is_numeric($m[2]) ? $m[2] : $wgContLang->getNsIndex($m[2]); if (is_numeric($n)) $litsql = "rc_namespace=$n"; break; case "nn": $n = is_numeric($m[2]) ? $m[2] : $wgContLang->getNsIndex($m[2]); if (is_numeric($n)) $litsql = "rc_namespace<>$n"; break; case "u": $id = User::idFromName($m[2]); // if $id = 0, no such name, in which case we match non-logged in users $litsql = "rc_user=$id"; break; case "nu": $id = User::idFromName($m[2]); $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; } if ($litsql) $disjunctsql = $disjunctsql ? "$disjunctsql AND ($litsql)" : "($litsql)"; } if ($disjunctsql) $filtersql = $filtersql ? "$filtersql OR ( $disjunctsql) " : "($disjunctsql)"; } if ($filtersql) $filtersql = "AND ( $filtersql )"; } return $filtersql; } ?>