mediawiki-extensions-TitleB.../TitleBlacklist.list.php

482 lines
13 KiB
PHP
Raw Normal View History

<?php
/**
* Title Blacklist class
* @author Victor Vasiliev
* @copyright © 2007-2010 Victor Vasiliev et al
2008-08-09 01:54:39 +00:00
* @license GNU General Public License 2.0 or later
* @file
*/
//@{
/**
* @ingroup Extensions
2008-08-09 01:54:39 +00:00
*/
/**
* Implements a title blacklist for MediaWiki
*/
class TitleBlacklist {
private $mBlacklist = null, $mWhitelist = null;
const VERSION = 2; // Blacklist format
/**
* Get an instance of this class
*
* @return TitleBlacklist
*/
public static function singleton() {
static $instance = null;
if ( $instance === null ) {
$instance = new self;
}
return $instance;
}
2008-08-09 01:54:39 +00:00
/**
* Load all configured blacklist sources
2011-03-14 23:44:41 +00:00
*/
public function load() {
global $wgTitleBlacklistSources, $wgMemc, $wgTitleBlacklistCaching;
wfProfileIn( __METHOD__ );
// Try to find something in the cache
$cachedBlacklist = $wgMemc->get( wfMemcKey( "title_blacklist_entries" ) );
if ( is_array( $cachedBlacklist ) && count( $cachedBlacklist ) > 0 && ( $cachedBlacklist[0]->getFormatVersion() == self::VERSION ) ) {
2007-12-19 18:35:44 +00:00
$this->mBlacklist = $cachedBlacklist;
wfProfileOut( __METHOD__ );
2007-12-19 18:35:44 +00:00
return;
}
$sources = $wgTitleBlacklistSources;
$sources[] = array( 'type' => TBLSRC_MSG );
$this->mBlacklist = array();
foreach ( $sources as $source ) {
$this->mBlacklist = array_merge( $this->mBlacklist, $this->parseBlacklist( $this->getBlacklistText( $source ) ) );
}
$wgMemc->set( wfMemcKey( "title_blacklist_entries" ), $this->mBlacklist, $wgTitleBlacklistCaching['expiry'] );
wfProfileOut( __METHOD__ );
}
2008-08-09 01:54:39 +00:00
/**
* Load all configured whitelist sources
2011-03-14 23:44:41 +00:00
*/
public function loadWhitelist() {
global $wgMemc, $wgTitleBlacklistCaching;
wfProfileIn( __METHOD__ );
$cachedWhitelist = $wgMemc->get( wfMemcKey( "title_whitelist_entries" ) );
if ( is_array( $cachedWhitelist ) && count( $cachedWhitelist ) > 0 && ( $cachedWhitelist[0]->getFormatVersion() != self::VERSION ) ) {
$this->mWhitelist = $cachedWhitelist;
wfProfileOut( __METHOD__ );
return;
}
$this->mWhitelist = $this->parseBlacklist( wfMessage( 'titlewhitelist' )
->inContentLanguage()->text() );
$wgMemc->set( wfMemcKey( "title_whitelist_entries" ), $this->mWhitelist, $wgTitleBlacklistCaching['expiry'] );
wfProfileOut( __METHOD__ );
}
2008-08-09 01:54:39 +00:00
/**
* Get the text of a blacklist from a specified source
*
* @param $source A blacklist source from $wgTitleBlacklistSources
* @return The content of the blacklist source as a string
2011-03-14 23:44:41 +00:00
*/
2008-08-09 01:54:39 +00:00
private static function getBlacklistText( $source ) {
if ( !is_array( $source ) || count( $source ) <= 0 ) {
return ''; // Return empty string in error case
}
if ( $source['type'] == TBLSRC_MSG ) {
return wfMessage( 'titleblacklist' )->inContentLanguage()->text();
} elseif ( $source['type'] == TBLSRC_LOCALPAGE && count( $source ) >= 2 ) {
$title = Title::newFromText( $source['src'] );
if ( is_null( $title ) ) {
return '';
}
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
$msg = wfMessage( $title->getText() )->inContentLanguage()->text();
if ( !wfMessage( 'titleblacklist', $msg )->isDisabled() ) {
return $msg;
} else {
return '';
}
} else {
$article = new Article( $title );
if ( $article->exists() ) {
$article->followRedirect();
return $article->getContent();
}
}
} elseif ( $source['type'] == TBLSRC_URL && count( $source ) >= 2 ) {
2008-08-09 01:54:39 +00:00
return self::getHttp( $source['src'] );
} elseif ( $source['type'] == TBLSRC_FILE && count( $source ) >= 2 ) {
if ( file_exists( $source['src'] ) ) {
return file_get_contents( $source['src'] );
} else {
return '';
}
}
return '';
}
2008-08-09 01:54:39 +00:00
/**
* Parse blacklist from a string
*
* @param $list string Text of a blacklist source
* @return array of TitleBlacklistEntry entries
2011-03-14 23:44:41 +00:00
*/
2008-08-09 01:54:39 +00:00
public static function parseBlacklist( $list ) {
wfProfileIn( __METHOD__ );
$lines = preg_split( "/\r?\n/", $list );
$result = array();
foreach ( $lines as $line ) {
$line = TitleBlacklistEntry :: newFromString( $line );
if ( $line ) {
$result[] = $line;
}
}
wfProfileOut( __METHOD__ );
return $result;
}
2008-08-09 01:54:39 +00:00
/**
* Check whether the blacklist restricts giver nuser
* performing a specific action on the given Title
*
* @param $title Title to check
* @param $user User to check
2011-08-22 20:37:26 +00:00
* @param $action string Action to check; 'edit' if unspecified
* @param $override bool If set to true, overrides work
* @return TitleBlacklistEntry|bool The corresponding TitleBlacklistEntry if
* blacklisted; otherwise false
2011-03-14 23:44:41 +00:00
*/
public function userCannot( $title, $user, $action = 'edit', $override = true ) {
if ( $override && self::userCanOverride( $user, $action ) ) {
return false;
2011-08-22 20:37:26 +00:00
} else {
return $this->isBlacklisted( $title, $action );
2011-08-22 20:37:26 +00:00
}
}
/**
* Check whether the blacklist restricts
2008-08-09 01:54:39 +00:00
* performing a specific action on the given Title
*
* @param $title Title to check
2011-08-22 20:37:26 +00:00
* @param $action string Action to check; 'edit' if unspecified
* @return TitleBlacklistEntry|bool The corresponding TitleBlacklistEntry if blacklisted;
2008-08-09 01:54:39 +00:00
* otherwise FALSE
2011-03-14 23:44:41 +00:00
*/
public function isBlacklisted( $title, $action = 'edit' ) {
if ( !( $title instanceof Title ) ) {
$title = Title::newFromText( $title );
}
$blacklist = $this->getBlacklist();
foreach ( $blacklist as $item ) {
if ( $item->matches( $title, $action ) ) {
if ( $this->isWhitelisted( $title, $action ) ) {
return false;
}
return $item; // "returning true"
}
}
return false;
}
2008-08-09 01:54:39 +00:00
/**
* Check whether it has been explicitly whitelisted that the
2008-08-09 01:54:39 +00:00
* current User may perform a specific action on the given Title
*
* @param $title Title to check
2011-08-22 20:37:26 +00:00
* @param $action string Action to check; 'edit' if unspecified
* @return bool True if whitelisted; otherwise false
2011-03-14 23:44:41 +00:00
*/
public function isWhitelisted( $title, $action = 'edit' ) {
if ( !( $title instanceof Title ) ) {
$title = Title::newFromText( $title );
}
$whitelist = $this->getWhitelist();
foreach ( $whitelist as $item ) {
if ( $item->matches( $title, $action ) ) {
return true;
}
}
return false;
}
2008-08-09 01:54:39 +00:00
/**
* Get the current blacklist
*
* @return Array of TitleBlacklistEntry items
2011-03-14 23:44:41 +00:00
*/
public function getBlacklist() {
if ( is_null( $this->mBlacklist ) ) {
$this->load();
}
return $this->mBlacklist;
}
2011-08-22 20:37:26 +00:00
/**
2008-08-09 01:54:39 +00:00
* Get the current whitelist
*
* @return Array of TitleBlacklistEntry items
2011-03-14 23:44:41 +00:00
*/
public function getWhitelist() {
if ( is_null( $this->mWhitelist ) ) {
$this->loadWhitelist();
}
return $this->mWhitelist;
}
2008-08-09 01:54:39 +00:00
/**
* Get the text of a blacklist source via HTTP
*
2011-08-22 20:37:26 +00:00
* @param $url string URL of the blacklist source
* @return string The content of the blacklist source as a string
2011-03-14 23:44:41 +00:00
*/
2008-08-09 01:54:39 +00:00
private static function getHttp( $url ) {
global $messageMemc, $wgTitleBlacklistCaching;
$key = "title_blacklist_source:" . md5( $url ); // Global shared
$warnkey = wfMemcKey( "titleblacklistwarning", md5( $url ) );
2007-12-19 18:35:44 +00:00
$result = $messageMemc->get( $key );
$warn = $messageMemc->get( $warnkey );
if ( !is_string( $result ) || ( !$warn && !mt_rand( 0, $wgTitleBlacklistCaching['warningchance'] ) ) ) {
$result = Http::get( $url );
$messageMemc->set( $warnkey, 1, $wgTitleBlacklistCaching['warningexpiry'] );
$messageMemc->set( $key, $result, $wgTitleBlacklistCaching['expiry'] );
}
return $result;
}
2008-08-09 01:54:39 +00:00
/**
* Invalidate the blacklist cache
*/
public function invalidate() {
global $wgMemc;
$wgMemc->delete( wfMemcKey( "title_blacklist_entries" ) );
}
2011-08-22 20:37:26 +00:00
2008-08-09 01:54:39 +00:00
/**
* Validate a new blacklist
*
2011-08-22 20:37:26 +00:00
* @param $blacklist array
2008-08-09 01:54:39 +00:00
* @return Array of bad entries; empty array means blacklist is valid
*/
public function validate( $blacklist ) {
$badEntries = array();
foreach ( $blacklist as $e ) {
wfSuppressWarnings();
$regex = $e->getRegex();
if ( preg_match( "/{$regex}/u", '' ) === false ) {
$badEntries[] = $e->getRaw();
}
wfRestoreWarnings();
}
return $badEntries;
}
/**
* Inidcates whether user can override blacklist on certain action.
2011-08-22 20:37:26 +00:00
*
* @param $action Action
2011-08-22 20:37:26 +00:00
*
* @return bool
*/
public static function userCanOverride( $user, $action ) {
return $user->isAllowed( 'tboverride' ) ||
( $action == 'new-account' && $user->isAllowed( 'tboverride-account' ) );
}
}
2008-08-09 01:54:39 +00:00
/**
* Represents a title blacklist entry
*/
class TitleBlacklistEntry {
private
$mRaw, /// < Raw line
$mRegex, /// < Regular expression to match
$mParams, /// < Parameters for this entry
$mFormatVersion; /// < Entry format version
2008-08-09 01:54:39 +00:00
/**
* Construct a new TitleBlacklistEntry.
*
* @param $regex string Regular expression to match
* @param $params array Parameters for this entry
* @param $raw string Raw contents of this line
2008-08-09 01:54:39 +00:00
*/
private function __construct( $regex, $params, $raw ) {
$this->mRaw = $raw;
$this->mRegex = $regex;
$this->mParams = $params;
$this->mFormatVersion = TitleBlacklist::VERSION;
}
2008-08-09 01:54:39 +00:00
/**
2011-04-12 00:16:04 +00:00
* Check whether a user can perform the specified action
2008-08-09 01:54:39 +00:00
* on the specified Title
*
* @param $title Title to check
* @param $action %Action to check
2011-08-22 20:37:26 +00:00
* @return bool TRUE if the the regex matches the title, and is not overridden
* else false if it doesn't match (or was overridden)
2008-08-09 01:54:39 +00:00
*/
public function matches( $title, $action ) {
if ( !$title ) {
return false;
}
wfSuppressWarnings();
$match = preg_match( "/^(?:{$this->mRegex})$/us" . ( isset( $this->mParams['casesensitive'] ) ? '' : 'i' ), $title->getFullText() );
wfRestoreWarnings();
global $wgUser;
if ( $match ) {
if ( isset( $this->mParams['autoconfirmed'] ) && $wgUser->isAllowed( 'autoconfirmed' ) ) {
return false;
}
if ( isset( $this->mParams['moveonly'] ) && $action != 'move' ) {
return false;
}
if ( isset( $this->mParams['newaccountonly'] ) && $action != 'new-account' ) {
return false;
2008-08-09 01:54:39 +00:00
}
if ( !isset( $this->mParams['noedit'] ) && $action == 'edit' ) {
return false;
}
if ( isset( $this->mParams['reupload'] ) && $action == 'upload' ) {
// Special:Upload also checks 'create' permissions when not reuploading
return false;
}
return true;
}
return false;
}
2008-08-09 01:54:39 +00:00
/**
* Create a new TitleBlacklistEntry from a line of text
*
* @param $line String containing a line of blacklist text
* @return TitleBlacklistEntry
2008-08-09 01:54:39 +00:00
*/
public static function newFromString( $line ) {
$raw = $line; // Keep line for raw data
$options = array();
// Strip comments
$line = preg_replace( "/^\\s*([^#]*)\\s*((.*)?)$/", "\\1", $line );
$line = trim( $line );
// Parse the rest of message
preg_match( '/^(.*?)(\s*<([^<>]*)>)?$/', $line, $pockets );
@list( $full, $regex, $null, $opts_str ) = $pockets;
$regex = trim( $regex );
$regex = str_replace( '_', ' ', $regex ); // We'll be matching against text form
$opts_str = trim( $opts_str );
// Parse opts
$opts = preg_split( '/\s*\|\s*/', $opts_str );
foreach ( $opts as $opt ) {
$opt2 = strtolower( $opt );
if ( $opt2 == 'autoconfirmed' ) {
$options['autoconfirmed'] = true;
}
if ( $opt2 == 'moveonly' ) {
$options['moveonly'] = true;
}
if ( $opt2 == 'newaccountonly' ) {
2008-08-09 01:54:39 +00:00
$options['newaccountonly'] = true;
}
if ( $opt2 == 'noedit' ) {
$options['noedit'] = true;
}
if ( $opt2 == 'casesensitive' ) {
$options['casesensitive'] = true;
}
if ( $opt2 == 'reupload' ) {
$options['reupload'] = true;
}
if ( preg_match( '/errmsg\s*=\s*(.+)/i', $opt, $matches ) ) {
$options['errmsg'] = $matches[1];
}
}
// Process magic words
preg_match_all( '/{{\s*([a-z]+)\s*:\s*(.+?)\s*}}/', $regex, $magicwords, PREG_SET_ORDER );
foreach ( $magicwords as $mword ) {
global $wgParser; // Functions we're calling don't need, nevertheless let's use it
switch( strtolower( $mword[1] ) ) {
case 'ns':
$cpf_result = CoreParserFunctions::ns( $wgParser, $mword[2] );
if ( is_string( $cpf_result ) ) {
$regex = str_replace( $mword[0], $cpf_result, $regex ); // All result will have the same value, so we can just use str_seplace()
}
break;
case 'int':
$cpf_result = wfMessage( $mword[2] )->inContentLanguage()->text();
if ( is_string( $cpf_result ) ) {
$regex = str_replace( $mword[0], $cpf_result, $regex );
}
}
}
// Return result
if ( $regex ) {
return new TitleBlacklistEntry( $regex, $options, $raw );
} else {
return null;
}
}
2011-04-12 00:16:04 +00:00
2008-08-09 01:54:39 +00:00
/**
* @return string This entry's regular expression
2008-08-09 01:54:39 +00:00
*/
public function getRegex() {
return $this->mRegex;
}
2008-08-09 01:54:39 +00:00
/**
* @return string This entry's raw line
2008-08-09 01:54:39 +00:00
*/
public function getRaw() {
return $this->mRaw;
}
2008-08-09 01:54:39 +00:00
/**
* @return array This entry's options
2008-08-09 01:54:39 +00:00
*/
public function getOptions() {
return $this->mOptions;
}
2008-08-09 01:54:39 +00:00
/**
* @return string Custom message for this entry
2008-08-09 01:54:39 +00:00
*/
public function getCustomMessage() {
return isset( $this->mParams['errmsg'] ) ? $this->mParams['errmsg'] : null;
}
2011-04-12 00:16:04 +00:00
2008-08-09 01:54:39 +00:00
/**
* @return string The format version
2008-08-09 01:54:39 +00:00
*/
2007-12-19 18:35:44 +00:00
public function getFormatVersion() { return $this->mFormatVersion; }
2008-08-09 01:54:39 +00:00
/**
* Set the format version
*
* @param $v string New version to set
2008-08-09 01:54:39 +00:00
*/
2007-12-19 18:35:44 +00:00
public function setFormatVersion( $v ) { $this->mFormatVersion = $v; }
2011-04-12 00:16:04 +00:00
/**
* Return the error message name for the blacklist entry.
2011-08-22 20:37:26 +00:00
*
* @param $operation string Operation name (as in titleblacklist-forbidden message name)
2011-08-22 20:37:26 +00:00
*
* @return string The error message name
*/
public function getErrorMessage( $operation ) {
$message = $this->getCustomMessage();
return $message ? $message : "titleblacklist-forbidden-{$operation}";
}
}
2008-08-09 01:54:39 +00:00
//@}