SpamFerret/Special

From HTYP, the free directory anyone can edit

Jump to: navigation, search

[edit] Code

<?php
/*
 NAME: SpecialSpamFerret
 PURPOSE: Special page for administering the SpamFerret database
 REQUIRES: SpamFerret (for now...)
 AUTHOR: Woozle (Nick) Staddon
 VERSION:
	2009-08-04 0.0 (Wzl) Started writing
	2009-10-01 0.1 (Wzl) incremental improvements; clsMenu now a separate file
	2009-10-06 0.2 (Wzl) text-check now shows if matching filters are deactivated
	2010-02-23 0.3 (Wzl) finally gave regular users some brief summary text to look at
	2010-08-17 0.31 (Wzl) a bit of debugging display in the regex checker
*/
$wgSpecialPages['SpamFerret'] = 'SpecialSpamFerret'; # Let MediaWiki know about your new special page.
$wgExtensionCredits['other'][] = array(
        'name' => 'Special:SpamFerret',
	'url' => 'http://htyp.org/SpamFerret',
        'description' => 'special page for SpamFerret administration',
        'author' => 'Woozle (Nick) Staddon',
	'version' => '0.31 2010-08-17 alpha'
);
define('KS_CHAR_URL_ASSIGN',':');	// character used for encoding values in wiki-internal URLs
 
function wfSpecialSpamFerret() {
// This registers the page's class. I think.
	global $wgRequest;
 
	$app = new SpecialSpamFerret($wgRequest);
}
 
require_once( $wgScriptPath.'includes/SpecialPage.php' );
if (!defined('LIBMGR')) {
    require('libmgr.php');
}
clsLibMgr::Add('menus',		KFP_MW_PLUGINS.'/menu.php',__FILE__,__LINE__);
clsLibMgr::Load('menus'		,__FILE__,__LINE__);
 
class SpecialSpamFerret extends SpecialPage {
//=======
// STATIC
    static private $objDB;
 
    static public function Setting($iName) {
	global $wgSpamFerretSettings;
 
	return $wgSpamFerretSettings[$iName];
    }
    static public function DB() {
	if (!isset(self::$objDB)) {
	    self::$objDB = new clsDatabase(self::Setting('dbspec'));
	    self::$objDB->Open();
	}
	return self::$objDB;
    }
 
//=======
// DYNAMIC
 
  protected $args;
 
  public function __construct() {
	global $wgMessageCache;
 
	parent::__construct( 'SpamFerret' );
	$this->includable( false );
        $wgMessageCache->addMessage('spamferret', 'SpamFerret administration');
  }
  function execute( $par ) {
	global $wgUser;
 
	$this->setHeaders();
	$this->GetArgs($par);
 
	if ($wgUser->isAllowed('editinterface')) {
		$this->doAdmin();
	} else {
		$this->doUser();
	}
  }
  private function GetArgs($par) {
 
/*
 PURPOSE: Parses variable arguments from the URL
  The URL is formatted as a series of arguments /arg=val/arg=val/..., so that we can always refer directly
    to any particular item as a wiki page title while also not worrying about hierarchy/order.
*/
    $args_raw = split('/',$par);
    foreach($args_raw as $arg_raw) {
	if (strpos($arg_raw,KS_CHAR_URL_ASSIGN) !== FALSE) {
	    list($key,$val) = split(KS_CHAR_URL_ASSIGN,$arg_raw);
	    $this->args[$key] = $val;
	}
    }
  }
  public function doAdmin() {
	global $wgOut;
/*
	PURPOSE: do stuff that only admins are allowed to do
*/
	if (isset($this->args['page'])) {
	    $page = $this->args['page'];
	} else {
	    $page = NULL;
	}
// display menu
	$wtSelf = 'Special:'.$this->name();
 
	$objMenu = new clsMenu($wtSelf);
	$objMenu->Add($objRow = new clsMenuRow('Utilities','menu.util'));
	  $objRow->Add(new clsMenuItem('check expression','ckexpr'));
	  $objRow->Add(new clsMenuItem('check text','cktext'));
	$objMenu->Add($objRow = new clsMenuRow('Inspect','menu.insp'));
	  $objRow->Add(new clsMenuItem('recent edits','edits'));
	  $objRow->Add(new clsMenuItem('filters','filters'));
 
	$out = $objMenu->WikiText($page);
	$out .= $objMenu->Execute();
 
	$wgOut->addHTML('<table style="background: #ccffcc;"><tr><td>');
	$wgOut->addWikiText($out,TRUE);	$out = '';
	$wgOut->addHTML('</td></tr></table>');
 
	if (!is_null($page)) {
	    $id = isset($this->args['id'])?$this->args['id']:NULL;
	    switch ($page) {
	      case 'ckexpr':
		$this->doRegexChecker();
		break;
	      case 'cktext':
		$this->doTextChecker();
		break;
	      case 'edits':
		$this->doInspectEdits();
		break;
	      case 'filters':
		$this->doInspectFilters();
		break;
	      case 'filter':
		$this->doEditFilter($id);
	    }
	}
  }
  public function doUser() {
	global $wgOut;
/*
	PURPOSE: do only stuff that regular users are allowed to do
*/
	//$wgOut->AddWikiText('Hello regular user! I haven\'t written anything for you yet, but eventually.');
	$sql = 'SELECT MIN(`When`) AS WhenEarly, COUNT(ID) AS Count, didAllow FROM attempt GROUP BY didAllow';
	$objRows = self::DB()->DataSet($sql);
	if ($objRows->HasRows()) {
	    $utOldest = NULL;
	    while ($objRows->NextRow()) {
		$intCount = $objRows->Row['Count'];
		$utWhen = strtotime($objRows->Row['WhenEarly']);
		if (is_null($utOldest) || ($utWhen < $utOldest)) {
		    $utOldest = $utWhen;
		}
		if ($objRows->didAllow) {
		    $outAllowed = $intCount.' edit'.Pluralize($intCount).' allowed';
		} else {
		    $outRejected = $intCount.' attempted spam'.Pluralize($intCount).' rejected';
		}
	    }
	    $out = $outRejected.' and '.$outAllowed.' since '.date('F j, Y',$utOldest).'.';
	} else {
	    $out = 'No edit attempts recorded by SpamFerret yet!';
	}
	$wgOut->AddHTML($out);
	// URL needs to be broken up in order not to get filtered as spam >.<
	$wgOut->AddHTML('<br>See <a href="http'.'://htyp.org/SpamFerret">HTYP</a> for all available documentation.');
    }
// individual admin functions
    public function doInspectEdits() {
	global $wgOut,$wgRequest;
 
	$arOpts['filt'] = $wgRequest->getVal('sqlFilt','');
	$arOpts['sort'] = $wgRequest->getVal('sqlSort','ID DESC');
	$arOpts['rows'] = $wgRequest->getVal('sqlRows',50);
	// LATER: display form for changing these defaults
 
	//$objTbl = 
	//$objRows->
 
    }
    public function doEditFilter($iID) {
	global $wgOut;
 
	$dbSP = self::DB();
	$tblFilt = new clsTable($dbSP);
	  $tblFilt->Name('patterns');
	  $tblFilt->KeyName('ID');
	$objRows = $tblFilt->GetItem($iID);
	if ($objRows->hasRows()) {
	    $htPattern = htmlspecialchars($objRows->Pattern);
	    $out = '<table>';
	    $out .= "\n<tr><td align=right><b>ID</b>:</td><td>{$objRows->ID}</td></tr>";
	    $out .= "\n<tr><td align=right><b>Pattern</b>:</td><td>{$htPattern}</td></tr>";
	    $out .= "\n<tr><td align=right><b>Added</b>:</td><td>{$objRows->WhenAdded}</td></tr>";
	    $out .= "\n<tr><td align=right><b>Tried</b>:</td><td>{$objRows->WhenTried}</td></tr>";
	    $out .= "\n<tr><td align=right><b>flags</b>:</td><td>";
	    $out .= ShowFlag('active',$objRows->isActive,'Active');
	    $out .= ShowFlag('regex',$objRows->isRegex,'Regex');
	    $out .= ShowFlag('diff',$objRows->isDiff,'Diff');
	    $out .= ShowFlag('isurl',$objRows->isURL,'URL');
	    $out .= '</td></tr></table>';
	    $wgOut->AddHTML($out);	$out = '';
	}
    }
    public function doInspectFilters() {
	global $wgOut;
 
	$dbSP = self::DB();
	$tblFilt = new clsTable($dbSP);
	  $tblFilt->Name('patterns');
	  $tblFilt->KeyName('ID');
	$objRows = $tblFilt->GetData();
	if ($objRows->hasRows()) {
	    $out = "{| class=sortable\n|-\n! ID || Pattern || A? || U? || R? || D? || Added || Tried || Count";
	    $isOdd = TRUE;
	    while ($objRows->NextRow()) {
		$wtStyle = $isOdd?'background:#ffffff;':'background:#eeeeee;';
		$isOdd = !$isOdd;
 
		$id = $objRows->ID;
		$wtID = SpIDSelfLink('filter','id',$id,$id);
		$strPatt = '<nowiki>'.$objRows->Pattern.'</nowiki>';
		$isActive = $objRows->isActive;
		$isURL = $objRows->isURL;
		$isRegex = $objRows->isRegex;
		$isDiff = $objRows->isDiff;
		$wtAdded = TimeStamp_HideTime($objRows->WhenAdded);
		$wtTried = TimeStamp_HideTime($objRows->WhenTried);
		$intCount = $objRows->Count;
 
		$out .= "\n|- style=\"$wtStyle\"\n|$wtID||$strPatt||$isActive||$isURL||$isRegex||$isDiff||$wtAdded||$wtTried||align=right|$intCount";
	    }
	    $out .= "\n|}\n";
	    $wgOut->AddWikiText($out);
	} else {
	    $wgOut->AddHTML('No filters have been defined.');
	    if (!$dbSP->isOk()) {
		$wgOut->AddHTML('<br><b>Database Error</b>: '.$dbSP->getError());
	    }
	}
    }
    public function doRegexChecker() {
	global $wgOut,$wgRequest;
 
	$inExpr = $wgRequest->getVal('expr');
	$inText = $wgRequest->getVal('sample');
 
	$wgOut->AddHTML('<form method=post action="">');
	$wgOut->AddHTML('Expression: <input name=expr size=80 value="'.htmlspecialchars($inExpr).'">');
	$wgOut->AddHTML('<br>Text to check:<br><textarea name=sample cols=50 rows=10>'.htmlspecialchars($inText).'</textarea>');
	$wgOut->AddHTML('<input name=go type=submit value="Evaluate">');
	$wgOut->AddHTML('</form>');
	$wgOut->AddWikiText('==Results==');
	$wgOut->AddWikiText("'''checked''': <nowiki>".htmlspecialchars($inExpr),TRUE).'</nowiki>';
/*
	// prefix any '/' characters with an escape ('\') because we are using the format which requires '/' at either end
	$chDelim = '/';
	$strPattCk = str_replace($chDelim,'\\'.$chDelim,$inExpr);
	$isMatch = @preg_match('/'.$strPattCk.'/',$inText,$matches);
*/
	// first 2 lines are a kluge until CheckRegex() is static, returning results in array
	$objSF = new SpamFerret();
 
	global $gRegexMatches,$strDbg;
	$isMatch = $objSF->CheckRegex($inExpr,$inText);
	$wgOut->AddWikiText($strDbg,TRUE);
	$matches = $gRegexMatches;
 
	if (isset($php_errormsg)) {
	      $outErr .= "===Error===\n$php_errormsg";
	      $wgOut->AddWikiText($outErr);
	} else {
	      $wgOut->AddWikiText("===Matches===");
	      $wgOut->AddHTML('<pre>'.htmlspecialchars(var_export($matches,TRUE)).'</pre>');
	}
    }
    public function doTextChecker() {
	global $wgOut,$wgRequest;
	global $gFilterMatches,$gFilterCount,$gFilterRows;
	global $debug;
 
	$inText = $wgRequest->getVal('sample');
	$doEcho = $wgRequest->getVal('doShowText');
	$htDoEcho = $doEcho?' checked':'';
 
	$wgOut->AddHTML('Check sample text against all defined filters.');
	$wgOut->AddHTML('<form method=post action="">');
	$wgOut->AddHTML('<br>Text to check:<br><textarea name=sample cols=50 rows=10>'.$inText.'</textarea>');
	$wgOut->AddHTML('<input name=go type=submit value="Evaluate">');
	$wgOut->AddHTML('<input name=doShowText type=checkbox'.$htDoEcho.'>Echo input text');
	$wgOut->AddHTML('</form>');
	if ($inText != '') {
	    $wgOut->AddWikiText('==Results==');
 
	    $objSF = new SpamFerret();
	    $objSF->OpenDatabase();
	    $objSF->txtEditRaw = $inText;
	    $arArgs['doAll'] = TRUE;
	  // LATER: allow user to enter title of existing page for generating diff
	    $arArgs['diff'] = '!!NEW: '.$inText;
	    if ($doEcho) {
		$out = "* '''checked''':";
		$out .= "\n** '''plain''': ".htmlspecialchars($inText);
		$out .= "\n** '''diff''': ".htmlspecialchars($arArgs['diff']);
		$wgOut->AddWikiText($out,TRUE);
	    }
	    $objSF->CheckFilters($arArgs);
	    $out =
	      "\n* ".$gFilterRows.' filter'.Pluralize($gFilterRows).' defined'.
	      "\n* ".$gFilterCount.' filter'.Pluralize($gFilterCount).' checked';
	    $wgOut->AddWikiText($out);
	    if (is_array($gFilterMatches)) {
		$out = "{|\n|-\n! ID || length || filter";
		$isOdd = FALSE;
		foreach ($gFilterMatches as $id=>$text) {
 
		    $wtStyle = $isOdd?'background:#ffffff;':'background:#eeeeee;';
		    $isOdd = !$isOdd;
 
		    $objFilt = $objSF->FiltTbl()->GetItem($id);
 
		    $isActive = $objFilt->isActive;
		    if (!$isActive) {
			$wtStyle .= ' color: #888888;';
			$wtStyle .= ' text-decoration: line-through;';
		    }
 
		    $out .= "\n|- style=\"$wtStyle\"\n| $id || ".strlen($text)." || <nowiki>{$objFilt->Pattern}</nowiki>";
		}
		$out .= "\n|}";
	    } else {
		$out = "* No filter matches";
	    }
	    $wgOut->AddWikiText($out);
	    //$wgOut->AddWikiText('* DEBUG: '.$debug);
	}
    }
/*
 LATER: for debugging why a particular string doesn't seem to be triggering the filter it should trigger
    public function CheckFiltersLocal($iCheckAll) {
	global $gRegexMatches,$gFilterMatches,$gFilterRows,$gFilterCount;
	global $debug;
 
	$this->PatternTbl = new clsTable($this->dbSpam);
	  $this->PatternTbl->Name('patterns');
	  $this->PatternTbl->KeyName('ID');
 
	$this->PatternRows = $this->PatternTbl->GetData();
	$objRow = $this->PatternRows;
 
	$strTextEdit = strtolower($this->txtEditRaw);
	$this->txtEditChk = $strTextEdit;	// text after being massaged for checking
	$this->isMatch = FALSE;
	$gFilterCount = 0;
	$gFilterRows = $this->PatternRows->RowCount();
	//$objDataPatterns->StartRows();
	while($objRow->NextRow() && (!$this->isMatch || $iCheckAll)) {
	    $isMatch = FALSE;
	    if ($objRow->isDiff) {
		if (isset($this->txtDiff)) {
		    $strTextCk = $this->txtDiff;
		} else {
		    $strTextCk = NULL;
		}
	    } else {
		$strTextCk = $strTextEdit;
	    }
	    if (!is_null($strTextCk)) {
		$gFilterCount++;
		$strPattern = $objRow->Pattern;
		$isRegex = $objRow->isRegex;
		$this->idPattern = $objRow->ID;
		if ($isRegex) {
		    $isMatch = $this->CheckRegex($strPattern,$strTextCk);
 
		    if (isset($php_errormsg)) {
			    $this->AddErrorLine('Filter #'.$this->idPattern.' generated error "'.$php_errormsg);
		    }
 
		    if ($isMatch) {
			    $this->strMatch = $gRegexMatches[0];
		    }
		} else {
		    if (empty($strPattern)) {
			$isMatch = FALSE;
		    } else {
			$this->strMatch = stristr($strTextCk,$strPattern);
			$isMatch = ($this->strMatch != '');
		    }
		}
		if ($isMatch) {
		    $this->isMatch = TRUE;
		    if ($iCheckAll) {
			$gFilterMatches[$this->idPattern] = $this->strMatch;
		    }
		}
	    }
	}
    }
*/
}
class clsAttempts extends clsTable {
    public function __construct($iDB) {
	parent::__construct($iDB);
	  $this->Name('attempt');
	  $this->KeyName('ID');
	  $this->ClassSng('clsAttempt');
    }
    public function GetRecent(array $iarOpts) {
	if (empty($iarOpts['filt'])) {
	    $sqlFilt = '';
	} else {
	    $sqlFilt = ' WHERE '.$iarOpts['filt'];
	}
 
	if (empty($iarOpts['sort'])) {
	    $sqlSort = '';
	} else {
	    $sqlSort = ' ORDER BY '.$iarOpts['sort'];
	}
 
	if (empty($iarOpts['rows'])) {
	    $sqlRows = '';
	} else {
	    $sqlRows = ' LIMIT '.$iarOpts['rows'];
	}
	$sql = 'SELECT * FROM attempts'.$sqlFilt.$sqlSort.$sqlRows;
	$objRows = $this->DataSet($sql);
	return $objRows;
    }
}
class clsAttempt extends clsDataSet {
}
 
// UTILITY FUNCTIONS //
function SpIDSelfLink($iDo,$iKey,$iVal,$iText) {
//    return '[[{{FULLPAGENAME}}/do'.KS_CHAR_URL_ASSIGN.$iDo.'/'.$iKey.KS_CHAR_URL_ASSIGN.$iVal.'|'.$iText.']]';
    return '[[Special:AudioFerret/do'.KS_CHAR_URL_ASSIGN.$iDo.'/'.$iKey.KS_CHAR_URL_ASSIGN.$iVal.'|'.$iText.']]';
}
/*
function SelfLink($iPage,$iKey,$iVal,$iText) {
    return '[[{{FULLPAGENAME}}/page'.KS_CHAR_URL_ASSIGN.$iPage.'/'.$iKey.KS_CHAR_URL_ASSIGN.$iVal.'|'.$iText.']]';
}
*/
function ShowFlag($iName,$iVal,$iText) {
    $out = '<input type=checkbox name="'.$iName.'" value='.$iVal.'>'.$iText;
    return $out;
}
 
if (!function_exists('TimeStamp_HideTime')) {
//--
function TimeStamp_HideTime($iStamp) {
 
    if (is_string($iStamp)) {
	$intStamp = strtotime($iStamp);
    } else if (is_int($iStamp)) {
	$intStamp = $iStamp;
    } else {
	$intStamp = NULL;
    }
    if (!is_null($intStamp)) {
	return date('Y-m-d',$intStamp);
    } else {
	return NULL;
    }
}
//--
}
Personal tools