diff options
Diffstat (limited to 'extensions/TitleBlacklist/TitleBlacklist.list.php')
-rw-r--r-- | extensions/TitleBlacklist/TitleBlacklist.list.php | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/extensions/TitleBlacklist/TitleBlacklist.list.php b/extensions/TitleBlacklist/TitleBlacklist.list.php new file mode 100644 index 00000000..d197a9c5 --- /dev/null +++ b/extensions/TitleBlacklist/TitleBlacklist.list.php @@ -0,0 +1,510 @@ +<?php +/** + * Title Blacklist class + * @author Victor Vasiliev + * @copyright © 2007-2010 Victor Vasiliev et al + * @license GNU General Public License 2.0 or later + * @file + */ + +//@{ +/** + * @ingroup Extensions + */ + +/** + * Implements a title blacklist for MediaWiki + */ +class TitleBlacklist { + private $mBlacklist = null, $mWhitelist = null; + const VERSION = 3; // 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; + } + + /** + * Load all configured blacklist sources + */ + 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 ) ) { + $this->mBlacklist = $cachedBlacklist; + wfProfileOut( __METHOD__ ); + return; + } + + $sources = $wgTitleBlacklistSources; + $sources['local'] = array( 'type' => TBLSRC_MSG ); + $this->mBlacklist = array(); + foreach( $sources as $sourceName => $source ) { + $this->mBlacklist = array_merge( $this->mBlacklist, $this->parseBlacklist( $this->getBlacklistText( $source ), $sourceName ) ); + } + $wgMemc->set( wfMemcKey( "title_blacklist_entries" ), $this->mBlacklist, $wgTitleBlacklistCaching['expiry'] ); + wfProfileOut( __METHOD__ ); + } + + /** + * Load local whitelist + */ + 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(), 'whitelist' ); + $wgMemc->set( wfMemcKey( "title_whitelist_entries" ), $this->mWhitelist, $wgTitleBlacklistCaching['expiry'] ); + wfProfileOut( __METHOD__ ); + } + + /** + * 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 + */ + 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 ) { + 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 ''; + } + + /** + * Parse blacklist from a string + * + * @param $list string Text of a blacklist source + * @return array of TitleBlacklistEntry entries + */ + public static function parseBlacklist( $list, $sourceName ) { + wfProfileIn( __METHOD__ ); + $lines = preg_split( "/\r?\n/", $list ); + $result = array(); + foreach ( $lines as $line ) { + $line = TitleBlacklistEntry :: newFromString( $line, $sourceName ); + if ( $line ) { + $result[] = $line; + } + } + + wfProfileOut( __METHOD__ ); + return $result; + } + + /** + * 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 + * @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 + */ + public function userCannot( $title, $user, $action = 'edit', $override = true ) { + if ( $override && self::userCanOverride( $user, $action ) ) { + return false; + } else { + return $this->isBlacklisted( $title, $action ); + } + } + + /** + * Check whether the blacklist restricts + * performing a specific action on the given Title + * + * @param $title Title to check + * @param $action string Action to check; 'edit' if unspecified + * @return TitleBlacklistEntry|bool The corresponding TitleBlacklistEntry if blacklisted; + * otherwise FALSE + */ + 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; + } + + /** + * Check whether it has been explicitly whitelisted that the + * current User may perform a specific action on the given Title + * + * @param $title Title to check + * @param $action string Action to check; 'edit' if unspecified + * @return bool True if whitelisted; otherwise false + */ + 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; + } + + /** + * Get the current blacklist + * + * @return Array of TitleBlacklistEntry items + */ + public function getBlacklist() { + if ( is_null( $this->mBlacklist ) ) { + $this->load(); + } + return $this->mBlacklist; + } + + /** + * Get the current whitelist + * + * @return Array of TitleBlacklistEntry items + */ + public function getWhitelist() { + if ( is_null( $this->mWhitelist ) ) { + $this->loadWhitelist(); + } + return $this->mWhitelist; + } + + /** + * Get the text of a blacklist source via HTTP + * + * @param $url string URL of the blacklist source + * @return string The content of the blacklist source as a string + */ + private static function getHttp( $url ) { + global $messageMemc, $wgTitleBlacklistCaching; + $key = "title_blacklist_source:" . md5( $url ); // Global shared + $warnkey = wfMemcKey( "titleblacklistwarning", md5( $url ) ); + $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; + } + + /** + * Invalidate the blacklist cache + */ + public function invalidate() { + global $wgMemc; + $wgMemc->delete( wfMemcKey( "title_blacklist_entries" ) ); + } + + /** + * Validate a new blacklist + * + * @param $blacklist array + * @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. + * + * @param $action Action + * + * @return bool + */ + public static function userCanOverride( $user, $action ) { + return $user->isAllowed( 'tboverride' ) || + ( $action == 'new-account' && $user->isAllowed( 'tboverride-account' ) ); + } +} + + +/** + * 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 + $mSource; ///< Source of this entry + + /** + * 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 + */ + private function __construct( $regex, $params, $raw, $source ) { + $this->mRaw = $raw; + $this->mRegex = $regex; + $this->mParams = $params; + $this->mFormatVersion = TitleBlacklist::VERSION; + $this->mSource = $source; + } + + /** + * Returns whether this entry is capable of filtering new accounts. + */ + private function filtersNewAccounts() { + global $wgTitleBlacklistUsernameSources; + + if( $wgTitleBlacklistUsernameSources === '*' ) { + return true; + } + + if( !$wgTitleBlacklistUsernameSources ) { + return false; + } + + if( !is_array( $wgTitleBlacklistUsernameSources ) ) { + throw new MWException( + '$wgTitleBlacklistUsernameSources must be "*", false or an array' ); + } + + return in_array( $this->mSource, $wgTitleBlacklistUsernameSources, true ); + } + + /** + * Check whether a user can perform the specified action + * on the specified Title + * + * @param $title Title to check + * @param $action %Action to check + * @return bool TRUE if the the regex matches the title, and is not overridden + * else false if it doesn't match (or was overridden) + */ + public function matches( $title, $action ) { + if ( !$title ) { + return false; + } + + if( $action == 'new-account' && !$this->filtersNewAccounts() ) { + 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; + } + 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; + } + + /** + * Create a new TitleBlacklistEntry from a line of text + * + * @param $line String containing a line of blacklist text + * @return TitleBlacklistEntry + */ + public static function newFromString( $line, $source ) { + $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' ) { + $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, $source ); + } else { + return null; + } + } + + /** + * @return string This entry's regular expression + */ + public function getRegex() { + return $this->mRegex; + } + + /** + * @return string This entry's raw line + */ + public function getRaw() { + return $this->mRaw; + } + + /** + * @return array This entry's options + */ + public function getOptions() { + return $this->mOptions; + } + + /** + * @return string Custom message for this entry + */ + public function getCustomMessage() { + return isset( $this->mParams['errmsg'] ) ? $this->mParams['errmsg'] : null; + } + + /** + * @return string The format version + */ + public function getFormatVersion() { return $this->mFormatVersion; } + + /** + * Set the format version + * + * @param $v string New version to set + */ + public function setFormatVersion( $v ) { $this->mFormatVersion = $v; } + + /** + * Return the error message name for the blacklist entry. + * + * @param $operation string Operation name (as in titleblacklist-forbidden message name) + * + * @return string The error message name + */ + public function getErrorMessage( $operation ) { + $message = $this->getCustomMessage(); + return $message ? $message : "titleblacklist-forbidden-{$operation}"; + } +} + +//@} |