[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/extensions/TitleBlacklist/ -> TitleBlacklist.list.php (source)

   1  <?php
   2  /**
   3   * Title Blacklist class
   4   * @author Victor Vasiliev
   5   * @copyright © 2007-2010 Victor Vasiliev et al
   6   * @license GNU General Public License 2.0 or later
   7   * @file
   8   */
   9  
  10  //@{
  11  /**
  12   * @ingroup Extensions
  13   */
  14  
  15  /**
  16   * Implements a title blacklist for MediaWiki
  17   */
  18  class TitleBlacklist {
  19      private $mBlacklist = null, $mWhitelist = null;
  20      const VERSION = 3;    // Blacklist format
  21  
  22      /**
  23       * Get an instance of this class
  24       *
  25       * @return TitleBlacklist
  26       */
  27  	public static function singleton() {
  28          static $instance = null;
  29  
  30          if ( $instance === null ) {
  31              $instance = new self;
  32          }
  33          return $instance;
  34      }
  35  
  36      /**
  37       * Load all configured blacklist sources
  38       */
  39  	public function load() {
  40          global $wgTitleBlacklistSources, $wgMemc, $wgTitleBlacklistCaching;
  41          wfProfileIn( __METHOD__ );
  42          // Try to find something in the cache
  43          $cachedBlacklist = $wgMemc->get( wfMemcKey( "title_blacklist_entries" ) );
  44          if ( is_array( $cachedBlacklist ) && count( $cachedBlacklist ) > 0 && ( $cachedBlacklist[0]->getFormatVersion() == self::VERSION ) ) {
  45              $this->mBlacklist = $cachedBlacklist;
  46              wfProfileOut( __METHOD__ );
  47              return;
  48          }
  49  
  50          $sources = $wgTitleBlacklistSources;
  51          $sources['local'] = array( 'type' => TBLSRC_MSG );
  52          $this->mBlacklist = array();
  53          foreach( $sources as $sourceName => $source ) {
  54              $this->mBlacklist = array_merge( $this->mBlacklist, $this->parseBlacklist( $this->getBlacklistText( $source ), $sourceName ) );
  55          }
  56          $wgMemc->set( wfMemcKey( "title_blacklist_entries" ), $this->mBlacklist, $wgTitleBlacklistCaching['expiry'] );
  57          wfProfileOut( __METHOD__ );
  58      }
  59  
  60      /**
  61       * Load local whitelist
  62       */
  63  	public function loadWhitelist() {
  64          global $wgMemc, $wgTitleBlacklistCaching;
  65          wfProfileIn( __METHOD__ );
  66          $cachedWhitelist = $wgMemc->get( wfMemcKey( "title_whitelist_entries" ) );
  67          if ( is_array( $cachedWhitelist ) && count( $cachedWhitelist ) > 0 && ( $cachedWhitelist[0]->getFormatVersion() != self::VERSION ) ) {
  68              $this->mWhitelist = $cachedWhitelist;
  69              wfProfileOut( __METHOD__ );
  70              return;
  71          }
  72          $this->mWhitelist = $this->parseBlacklist( wfMessage( 'titlewhitelist' )
  73                  ->inContentLanguage()->text(), 'whitelist' );
  74          $wgMemc->set( wfMemcKey( "title_whitelist_entries" ), $this->mWhitelist, $wgTitleBlacklistCaching['expiry'] );
  75          wfProfileOut( __METHOD__ );
  76      }
  77  
  78      /**
  79       * Get the text of a blacklist from a specified source
  80       *
  81       * @param $source A blacklist source from $wgTitleBlacklistSources
  82       * @return The content of the blacklist source as a string
  83       */
  84  	private static function getBlacklistText( $source ) {
  85          if ( !is_array( $source ) || count( $source ) <= 0 ) {
  86              return '';    // Return empty string in error case
  87          }
  88  
  89          if ( $source['type'] == TBLSRC_MSG ) {
  90              return wfMessage( 'titleblacklist' )->inContentLanguage()->text();
  91          } elseif ( $source['type'] == TBLSRC_LOCALPAGE && count( $source ) >= 2 ) {
  92              $title = Title::newFromText( $source['src'] );
  93              if ( is_null( $title ) ) {
  94                  return '';
  95              }
  96              if ( $title->getNamespace() == NS_MEDIAWIKI ) {
  97                  $msg = wfMessage( $title->getText() )->inContentLanguage()->text();
  98                  if ( !wfMessage( 'titleblacklist', $msg )->isDisabled() ) {
  99                      return $msg;
 100                  } else {
 101                      return '';
 102                  }
 103              } else {
 104                  $article = new Article( $title );
 105                  if ( $article->exists() ) {
 106                      $article->followRedirect();
 107                      return $article->getContent();
 108                  }
 109              }
 110          } elseif ( $source['type'] == TBLSRC_URL && count( $source ) >= 2 ) {
 111              return self::getHttp( $source['src'] );
 112          } elseif ( $source['type'] == TBLSRC_FILE && count( $source ) >= 2 ) {
 113              if ( file_exists( $source['src'] ) ) {
 114                  return file_get_contents( $source['src'] );
 115              } else {
 116                  return '';
 117              }
 118          }
 119  
 120          return '';
 121      }
 122  
 123      /**
 124       * Parse blacklist from a string
 125       *
 126       * @param $list string Text of a blacklist source
 127       * @return array of TitleBlacklistEntry entries
 128       */
 129  	public static function parseBlacklist( $list, $sourceName ) {
 130          wfProfileIn( __METHOD__ );
 131          $lines = preg_split( "/\r?\n/", $list );
 132          $result = array();
 133          foreach ( $lines as $line ) {
 134              $line = TitleBlacklistEntry :: newFromString( $line, $sourceName );
 135              if ( $line ) {
 136                  $result[] = $line;
 137              }
 138          }
 139  
 140          wfProfileOut( __METHOD__ );
 141          return $result;
 142      }
 143  
 144      /**
 145       * Check whether the blacklist restricts giver nuser
 146       * performing a specific action on the given Title
 147       *
 148       * @param $title Title to check
 149       * @param $user User to check
 150       * @param $action string Action to check; 'edit' if unspecified
 151       * @param $override bool If set to true, overrides work
 152       * @return TitleBlacklistEntry|bool The corresponding TitleBlacklistEntry if
 153       * blacklisted; otherwise false
 154       */
 155  	public function userCannot( $title, $user, $action = 'edit', $override = true ) {
 156          if ( $override && self::userCanOverride( $user, $action ) ) {
 157              return false;
 158          } else {
 159              $entry = $this->isBlacklisted( $title, $action );
 160              if ( !$entry ) {
 161                  return false;
 162              }
 163              $params = $entry->getParams();
 164              return isset( $params['autoconfirmed'] ) && $user->isAllowed( 'autoconfirmed' ) ? false : $entry;
 165          }
 166      }
 167  
 168      /**
 169       * Check whether the blacklist restricts
 170       * performing a specific action on the given Title
 171       *
 172       * @param $title Title to check
 173       * @param $action string Action to check; 'edit' if unspecified
 174       * @return TitleBlacklistEntry|bool The corresponding TitleBlacklistEntry if blacklisted;
 175       *         otherwise FALSE
 176       */
 177  	public function isBlacklisted( $title, $action = 'edit' ) {
 178          if ( !( $title instanceof Title ) ) {
 179              $title = Title::newFromText( $title );
 180              if ( !( $title instanceof Title ) ) {
 181                  // The fact that the page name is invalid will stop whatever
 182                  // action is going through. No sense in doing more work here.
 183                  return false;
 184              }
 185          }
 186          $blacklist = $this->getBlacklist();
 187          $autoconfirmedItem = false;
 188          foreach ( $blacklist as $item ) {
 189              if ( $item->matches( $title->getFullText(), $action ) ) {
 190                  if ( $this->isWhitelisted( $title, $action ) ) {
 191                      return false;
 192                  }
 193                  $params = $item->getParams();
 194                  if ( !isset( $params['autoconfirmed'] ) ) {
 195                      return $item;
 196                  }
 197                  if ( !$autoconfirmedItem ) {
 198                      $autoconfirmedItem = $item;
 199                  }
 200              }
 201          }
 202          return $autoconfirmedItem;
 203      }
 204  
 205      /**
 206       * Check whether it has been explicitly whitelisted that the
 207       * current User may perform a specific action on the given Title
 208       *
 209       * @param $title Title to check
 210       * @param $action string Action to check; 'edit' if unspecified
 211       * @return bool True if whitelisted; otherwise false
 212       */
 213  	public function isWhitelisted( $title, $action = 'edit' ) {
 214          if ( !( $title instanceof Title ) ) {
 215              $title = Title::newFromText( $title );
 216          }
 217          $whitelist = $this->getWhitelist();
 218          foreach ( $whitelist as $item ) {
 219              if ( $item->matches( $title->getFullText(), $action ) ) {
 220                  return true;
 221              }
 222          }
 223          return false;
 224      }
 225  
 226      /**
 227       * Get the current blacklist
 228       *
 229       * @return Array of TitleBlacklistEntry items
 230       */
 231  	public function getBlacklist() {
 232          if ( is_null( $this->mBlacklist ) ) {
 233              $this->load();
 234          }
 235          return $this->mBlacklist;
 236      }
 237  
 238      /**
 239       * Get the current whitelist
 240       *
 241       * @return Array of TitleBlacklistEntry items
 242       */
 243  	public function getWhitelist() {
 244          if ( is_null( $this->mWhitelist ) ) {
 245              $this->loadWhitelist();
 246          }
 247          return $this->mWhitelist;
 248      }
 249  
 250      /**
 251       * Get the text of a blacklist source via HTTP
 252       *
 253       * @param $url string URL of the blacklist source
 254       * @return string The content of the blacklist source as a string
 255       */
 256  	private static function getHttp( $url ) {
 257          global $messageMemc, $wgTitleBlacklistCaching;
 258          $key = "title_blacklist_source:" . md5( $url ); // Global shared
 259          $warnkey = wfMemcKey( "titleblacklistwarning", md5( $url ) );
 260          $result = $messageMemc->get( $key );
 261          $warn = $messageMemc->get( $warnkey );
 262          if ( !is_string( $result ) || ( !$warn && !mt_rand( 0, $wgTitleBlacklistCaching['warningchance'] ) ) ) {
 263              $result = Http::get( $url );
 264              $messageMemc->set( $warnkey, 1, $wgTitleBlacklistCaching['warningexpiry'] );
 265              $messageMemc->set( $key, $result, $wgTitleBlacklistCaching['expiry'] );
 266          }
 267          return $result;
 268      }
 269  
 270      /**
 271       * Invalidate the blacklist cache
 272       */
 273  	public function invalidate() {
 274          global $wgMemc;
 275          $wgMemc->delete( wfMemcKey( "title_blacklist_entries" ) );
 276      }
 277  
 278      /**
 279       * Validate a new blacklist
 280       *
 281       * @param $blacklist array
 282       * @return Array of bad entries; empty array means blacklist is valid
 283       */
 284  	public function validate( $blacklist ) {
 285          $badEntries = array();
 286          foreach ( $blacklist as $e ) {
 287              wfSuppressWarnings();
 288              $regex = $e->getRegex();
 289              if ( preg_match( "/{$regex}/u", '' ) === false ) {
 290                  $badEntries[] = $e->getRaw();
 291              }
 292              wfRestoreWarnings();
 293          }
 294          return $badEntries;
 295      }
 296  
 297      /**
 298       * Inidcates whether user can override blacklist on certain action.
 299       *
 300       * @param $action Action
 301       *
 302       * @return bool
 303       */
 304  	public static function userCanOverride( $user, $action ) {
 305          return $user->isAllowed( 'tboverride' ) ||
 306              ( $action == 'new-account' && $user->isAllowed( 'tboverride-account' ) );
 307      }
 308  }
 309  
 310  
 311  /**
 312   * Represents a title blacklist entry
 313   */
 314  class TitleBlacklistEntry {
 315      private
 316          $mRaw,           ///< Raw line
 317          $mRegex,         ///< Regular expression to match
 318          $mParams,        ///< Parameters for this entry
 319          $mFormatVersion, ///< Entry format version
 320          $mSource;        ///< Source of this entry
 321  
 322      /**
 323       * Construct a new TitleBlacklistEntry.
 324       *
 325       * @param $regex string Regular expression to match
 326       * @param $params array Parameters for this entry
 327       * @param $raw string Raw contents of this line
 328       */
 329  	private function __construct( $regex, $params, $raw, $source ) {
 330          $this->mRaw = $raw;
 331          $this->mRegex = $regex;
 332          $this->mParams = $params;
 333          $this->mFormatVersion = TitleBlacklist::VERSION;
 334          $this->mSource = $source;
 335      }
 336  
 337      /**
 338       * Returns whether this entry is capable of filtering new accounts.
 339       */
 340  	private function filtersNewAccounts() {
 341          global $wgTitleBlacklistUsernameSources;
 342  
 343          if( $wgTitleBlacklistUsernameSources === '*' ) {
 344              return true;
 345          }
 346  
 347          if( !$wgTitleBlacklistUsernameSources ) {
 348              return false;
 349          }
 350  
 351          if( !is_array( $wgTitleBlacklistUsernameSources ) ) {
 352              throw new MWException(
 353                  '$wgTitleBlacklistUsernameSources must be "*", false or an array' );
 354          }
 355  
 356          return in_array( $this->mSource, $wgTitleBlacklistUsernameSources, true );
 357      }
 358  
 359      /**
 360       * Check whether a user can perform the specified action
 361       * on the specified Title
 362       *
 363       * @param $title string to check
 364       * @param $action %Action to check
 365       * @return bool TRUE if the the regex matches the title, and is not overridden
 366       * else false if it doesn't match (or was overridden)
 367       */
 368  	public function matches( $title, $action ) {
 369          if ( !$title ) {
 370              return false;
 371          }
 372  
 373          if( $action == 'new-account' && !$this->filtersNewAccounts() ) {
 374              return false;
 375          }
 376  
 377          if ( isset( $this->mParams['antispoof'] ) && is_callable( 'AntiSpoof::checkUnicodeString' ) ) {
 378              list( $ok, $norm ) = AntiSpoof::checkUnicodeString( $title );
 379              if ( $ok == "OK" ) {
 380                  list( $ver, $title ) = explode( ':', $norm, 2 );
 381              } else {
 382                  wfDebugLog( 'TitleBlacklist', 'AntiSpoof could not normalize "' . $title . '".' );
 383              }
 384          }
 385  
 386          wfSuppressWarnings();
 387          $match = preg_match( "/^(?:{$this->mRegex})$/us" . ( isset( $this->mParams['casesensitive'] ) ? '' : 'i' ), $title );
 388          wfRestoreWarnings();
 389  
 390          if ( $match ) {
 391              if ( isset( $this->mParams['moveonly'] ) && $action != 'move' ) {
 392                  return false;
 393              }
 394              if ( isset( $this->mParams['newaccountonly'] ) && $action != 'new-account' ) {
 395                  return false;
 396              }
 397              if ( !isset( $this->mParams['noedit'] ) && $action == 'edit' ) {
 398                  return false;
 399              }
 400              if ( isset( $this->mParams['reupload'] ) && $action == 'upload' ) {
 401                  // Special:Upload also checks 'create' permissions when not reuploading
 402                  return false;
 403              }
 404              return true;
 405          }
 406          return false;
 407      }
 408  
 409      /**
 410       * Create a new TitleBlacklistEntry from a line of text
 411       *
 412       * @param $line String containing a line of blacklist text
 413       * @return TitleBlacklistEntry
 414       */
 415  	public static function newFromString( $line, $source ) {
 416          $raw = $line; // Keep line for raw data
 417          $options = array();
 418          // Strip comments
 419          $line = preg_replace( "/^\\s*([^#]*)\\s*((.*)?)$/", "\\1", $line );
 420          $line = trim( $line );
 421          // Parse the rest of message
 422          preg_match( '/^(.*?)(\s*<([^<>]*)>)?$/', $line, $pockets );
 423          @list( $full, $regex, $null, $opts_str ) = $pockets;
 424          $regex = trim( $regex );
 425          $regex = str_replace( '_', ' ', $regex ); // We'll be matching against text form
 426          $opts_str = trim( $opts_str );
 427          // Parse opts
 428          $opts = preg_split( '/\s*\|\s*/', $opts_str );
 429          foreach ( $opts as $opt ) {
 430              $opt2 = strtolower( $opt );
 431              if ( $opt2 == 'autoconfirmed' ) {
 432                  $options['autoconfirmed'] = true;
 433              }
 434              if ( $opt2 == 'moveonly' ) {
 435                  $options['moveonly'] = true;
 436              }
 437              if ( $opt2 == 'newaccountonly' ) {
 438                  $options['newaccountonly'] = true;
 439              }
 440              if ( $opt2 == 'noedit' ) {
 441                  $options['noedit'] = true;
 442              }
 443              if ( $opt2 == 'casesensitive' ) {
 444                  $options['casesensitive'] = true;
 445              }
 446              if ( $opt2 == 'reupload' ) {
 447                  $options['reupload'] = true;
 448              }
 449              if ( preg_match( '/errmsg\s*=\s*(.+)/i', $opt, $matches ) ) {
 450                  $options['errmsg'] = $matches[1];
 451              }
 452              if ( $opt2 == 'antispoof' ) {
 453                  $options['antispoof'] = true;
 454              }
 455          }
 456          // Process magic words
 457          preg_match_all( '/{{\s*([a-z]+)\s*:\s*(.+?)\s*}}/', $regex, $magicwords, PREG_SET_ORDER );
 458          foreach ( $magicwords as $mword ) {
 459              global $wgParser;    // Functions we're calling don't need, nevertheless let's use it
 460              switch( strtolower( $mword[1] ) ) {
 461                  case 'ns':
 462                      $cpf_result = CoreParserFunctions::ns( $wgParser, $mword[2] );
 463                      if ( is_string( $cpf_result ) ) {
 464                          $regex = str_replace( $mword[0], $cpf_result, $regex );    // All result will have the same value, so we can just use str_seplace()
 465                      }
 466                      break;
 467                  case 'int':
 468                      $cpf_result = wfMessage( $mword[2] )->inContentLanguage()->text();
 469                      if ( is_string( $cpf_result ) ) {
 470                          $regex = str_replace( $mword[0], $cpf_result, $regex );
 471                      }
 472              }
 473          }
 474          // Return result
 475          if( $regex ) {
 476              return new TitleBlacklistEntry( $regex, $options, $raw, $source );
 477          } else {
 478              return null;
 479          }
 480      }
 481  
 482      /**
 483       * @return string This entry's regular expression
 484       */
 485  	public function getRegex() {
 486          return $this->mRegex;
 487      }
 488  
 489      /**
 490       * @return string This entry's raw line
 491       */
 492  	public function getRaw() {
 493          return $this->mRaw;
 494      }
 495  
 496      /**
 497       * @return array This entry's parameters
 498       */
 499  	public function getParams() {
 500          return $this->mParams;
 501      }
 502  
 503      /**
 504       * @return string Custom message for this entry
 505       */
 506  	public function getCustomMessage() {
 507          return isset( $this->mParams['errmsg'] ) ? $this->mParams['errmsg'] : null;
 508      }
 509  
 510      /**
 511       * @return string The format version
 512       */
 513  	public function getFormatVersion() { return $this->mFormatVersion; }
 514  
 515      /**
 516       * Set the format version
 517       *
 518       * @param $v string New version to set
 519       */
 520  	public function setFormatVersion( $v ) { $this->mFormatVersion = $v; }
 521  
 522      /**
 523       * Return the error message name for the blacklist entry.
 524       *
 525       * @param $operation string Operation name (as in titleblacklist-forbidden message name)
 526       *
 527       * @return string The error message name
 528       */
 529  	public function getErrorMessage( $operation ) {
 530          $message = $this->getCustomMessage();
 531          return $message ? $message : "titleblacklist-forbidden-{$operation}";
 532      }
 533  }
 534  
 535  //@}


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1