diff options
Diffstat (limited to 'includes')
113 files changed, 1080 insertions, 42188 deletions
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 4f36784a..de75b41d 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -91,6 +91,7 @@ class AutoLoader { 'HTMLFileCache' => 'includes/HTMLFileCache.php', 'Http' => 'includes/HttpFunctions.php', '_HWLDF_WordAccumulator' => 'includes/DifferenceEngine.php', + 'IEContentAnalyzer' => 'includes/IEContentAnalyzer.php', 'ImageGallery' => 'includes/ImageGallery.php', 'ImageHistoryList' => 'includes/ImagePage.php', 'ImagePage' => 'includes/ImagePage.php', diff --git a/includes/CacheManager.php b/includes/CacheManager.php deleted file mode 100644 index b9e307f4..00000000 --- a/includes/CacheManager.php +++ /dev/null @@ -1,159 +0,0 @@ -<?php -/** - * Contain the CacheManager class - * @package MediaWiki - * @subpackage Cache - */ - -/** - * Handles talking to the file cache, putting stuff in and taking it back out. - * Mostly called from Article.php, also from DatabaseFunctions.php for the - * emergency abort/fallback to cache. - * - * Global options that affect this module: - * $wgCachePages - * $wgCacheEpoch - * $wgUseFileCache - * $wgFileCacheDirectory - * $wgUseGzip - * @package MediaWiki - */ -class CacheManager { - var $mTitle, $mFileCache; - - function CacheManager( &$title ) { - $this->mTitle =& $title; - $this->mFileCache = ''; - } - - function fileCacheName() { - global $wgFileCacheDirectory; - if( !$this->mFileCache ) { - $key = $this->mTitle->getPrefixedDbkey(); - $hash = md5( $key ); - $key = str_replace( '.', '%2E', urlencode( $key ) ); - - $hash1 = substr( $hash, 0, 1 ); - $hash2 = substr( $hash, 0, 2 ); - $this->mFileCache = "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$key}.html"; - - if($this->useGzip()) - $this->mFileCache .= '.gz'; - - wfDebug( " fileCacheName() - {$this->mFileCache}\n" ); - } - return $this->mFileCache; - } - - function isFileCached() { - return file_exists( $this->fileCacheName() ); - } - - function fileCacheTime() { - return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) ); - } - - function isFileCacheGood( $timestamp ) { - global $wgCacheEpoch; - - if( !$this->isFileCached() ) return false; - - $cachetime = $this->fileCacheTime(); - $good = (( $timestamp <= $cachetime ) && - ( $wgCacheEpoch <= $cachetime )); - - wfDebug(" isFileCacheGood() - cachetime $cachetime, touched {$timestamp} epoch {$wgCacheEpoch}, good $good\n"); - return $good; - } - - function useGzip() { - global $wgUseGzip; - return $wgUseGzip; - } - - /* In handy string packages */ - function fetchRawText() { - return file_get_contents( $this->fileCacheName() ); - } - - function fetchPageText() { - if( $this->useGzip() ) { - /* Why is there no gzfile_get_contents() or gzdecode()? */ - return implode( '', gzfile( $this->fileCacheName() ) ); - } else { - return $this->fetchRawText(); - } - } - - /* Working directory to/from output */ - function loadFromFileCache() { - global $wgOut, $wgMimeType, $wgOutputEncoding, $wgContLanguageCode; - wfDebug(" loadFromFileCache()\n"); - - $filename=$this->fileCacheName(); - $wgOut->sendCacheControl(); - - header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" ); - header( "Content-language: $wgContLanguageCode" ); - - if( $this->useGzip() ) { - if( wfClientAcceptsGzip() ) { - header( 'Content-Encoding: gzip' ); - } else { - /* Send uncompressed */ - readgzfile( $filename ); - return; - } - } - readfile( $filename ); - } - - function checkCacheDirs() { - $filename = $this->fileCacheName(); - $mydir2=substr($filename,0,strrpos($filename,'/')); # subdirectory level 2 - $mydir1=substr($mydir2,0,strrpos($mydir2,'/')); # subdirectory level 1 - - if(!file_exists($mydir1)) { mkdir($mydir1,0775); } # create if necessary - if(!file_exists($mydir2)) { mkdir($mydir2,0775); } - } - - function saveToFileCache( $origtext ) { - $text = $origtext; - if(strcmp($text,'') == 0) return ''; - - wfDebug(" saveToFileCache()\n", false); - - $this->checkCacheDirs(); - - $f = fopen( $this->fileCacheName(), 'w' ); - if($f) { - $now = wfTimestampNow(); - if( $this->useGzip() ) { - $rawtext = str_replace( '</html>', - '<!-- Cached/compressed '.$now." -->\n</html>", - $text ); - $text = gzencode( $rawtext ); - } else { - $text = str_replace( '</html>', - '<!-- Cached '.$now." -->\n</html>", - $text ); - } - fwrite( $f, $text ); - fclose( $f ); - if( $this->useGzip() ) { - if( wfClientAcceptsGzip() ) { - header( 'Content-Encoding: gzip' ); - return $text; - } else { - return $rawtext; - } - } else { - return $text; - } - } - return $text; - } - -} - -?> diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php deleted file mode 100644 index 61dbafe5..00000000 --- a/includes/CoreParserFunctions.php +++ /dev/null @@ -1,268 +0,0 @@ -<?php - -/** - * Various core parser functions, registered in Parser::firstCallInit() - * @addtogroup Parser - */ -class CoreParserFunctions { - static function intFunction( $parser, $part1 = '' /*, ... */ ) { - if ( strval( $part1 ) !== '' ) { - $args = array_slice( func_get_args(), 2 ); - return wfMsgReal( $part1, $args, true ); - } else { - return array( 'found' => false ); - } - } - - static function ns( $parser, $part1 = '' ) { - global $wgContLang; - $found = false; - if ( intval( $part1 ) || $part1 == "0" ) { - $text = $wgContLang->getNsText( intval( $part1 ) ); - $found = true; - } else { - $param = str_replace( ' ', '_', strtolower( $part1 ) ); - $index = Namespace::getCanonicalIndex( strtolower( $param ) ); - if ( !is_null( $index ) ) { - $text = $wgContLang->getNsText( $index ); - $found = true; - } - } - if ( $found ) { - return $text; - } else { - return array( 'found' => false ); - } - } - - static function urlencode( $parser, $s = '' ) { - return urlencode( $s ); - } - - static function lcfirst( $parser, $s = '' ) { - global $wgContLang; - return $wgContLang->lcfirst( $s ); - } - - static function ucfirst( $parser, $s = '' ) { - global $wgContLang; - return $wgContLang->ucfirst( $s ); - } - - static function lc( $parser, $s = '' ) { - global $wgContLang; - if ( is_callable( array( $parser, 'markerSkipCallback' ) ) ) { - return $parser->markerSkipCallback( $s, array( $wgContLang, 'lc' ) ); - } else { - return $wgContLang->lc( $s ); - } - } - - static function uc( $parser, $s = '' ) { - global $wgContLang; - if ( is_callable( array( $parser, 'markerSkipCallback' ) ) ) { - return $parser->markerSkipCallback( $s, array( $wgContLang, 'uc' ) ); - } else { - return $wgContLang->uc( $s ); - } - } - - static function localurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getLocalURL', $s, $arg ); } - static function localurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeLocalURL', $s, $arg ); } - static function fullurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getFullURL', $s, $arg ); } - static function fullurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeFullURL', $s, $arg ); } - - static function urlFunction( $func, $s = '', $arg = null ) { - $title = Title::newFromText( $s ); - # Due to order of execution of a lot of bits, the values might be encoded - # before arriving here; if that's true, then the title can't be created - # and the variable will fail. If we can't get a decent title from the first - # attempt, url-decode and try for a second. - if( is_null( $title ) ) - $title = Title::newFromUrl( urldecode( $s ) ); - if ( !is_null( $title ) ) { - if ( !is_null( $arg ) ) { - $text = $title->$func( $arg ); - } else { - $text = $title->$func(); - } - return $text; - } else { - return array( 'found' => false ); - } - } - - static function formatNum( $parser, $num = '' ) { - return $parser->getFunctionLang()->formatNum( $num ); - } - - static function grammar( $parser, $case = '', $word = '' ) { - return $parser->getFunctionLang()->convertGrammar( $word, $case ); - } - - static function plural( $parser, $text = '') { - $forms = array_slice( func_get_args(), 2); - $text = $parser->getFunctionLang()->parseFormattedNumber( $text ); - return $parser->getFunctionLang()->convertPlural( $text, $forms ); - } - - /** - * Override the title of the page when viewed, - * provided we've been given a title which - * will normalise to the canonical title - * - * @param Parser $parser Parent parser - * @param string $text Desired title text - * @return string - */ - static function displaytitle( $parser, $text = '' ) { - $text = trim( Sanitizer::decodeCharReferences( $text ) ); - $title = Title::newFromText( $text ); - if( $title instanceof Title && $title->getFragment() == '' && $title->equals( $parser->mTitle ) ) - $parser->mOutput->setDisplayTitle( $text ); - return ''; - } - - static function isRaw( $param ) { - static $mwRaw; - if ( !$mwRaw ) { - $mwRaw =& MagicWord::get( 'rawsuffix' ); - } - if ( is_null( $param ) ) { - return false; - } else { - return $mwRaw->match( $param ); - } - } - - static function statisticsFunction( $func, $raw = null ) { - if ( self::isRaw( $raw ) ) { - return call_user_func( array( 'SiteStats', $func ) ); - } else { - global $wgContLang; - return $wgContLang->formatNum( call_user_func( array( 'SiteStats', $func ) ) ); - } - } - - static function numberofpages( $parser, $raw = null ) { return self::statisticsFunction( 'pages', $raw ); } - static function numberofusers( $parser, $raw = null ) { return self::statisticsFunction( 'users', $raw ); } - static function numberofarticles( $parser, $raw = null ) { return self::statisticsFunction( 'articles', $raw ); } - static function numberoffiles( $parser, $raw = null ) { return self::statisticsFunction( 'images', $raw ); } - static function numberofadmins( $parser, $raw = null ) { return self::statisticsFunction( 'admins', $raw ); } - static function numberofedits( $parser, $raw = null ) { return self::statisticsFunction( 'edits', $raw ); } - - static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) { - $count = SiteStats::pagesInNs( intval( $namespace ) ); - if ( self::isRaw( $raw ) ) { - global $wgContLang; - return $wgContLang->formatNum( $count ); - } else { - return $count; - } - } - - static function language( $parser, $arg = '' ) { - global $wgContLang; - $lang = $wgContLang->getLanguageName( strtolower( $arg ) ); - return $lang != '' ? $lang : $arg; - } - - static function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) { - $length = min( max( $length, 0 ), 500 ); - $char = substr( $char, 0, 1 ); - return ( $string !== '' && (int)$length > 0 && strlen( trim( (string)$char ) ) > 0 ) - ? str_pad( $string, $length, (string)$char, $direction ) - : $string; - } - - static function padleft( $parser, $string = '', $length = 0, $char = 0 ) { - return self::pad( $string, $length, $char, STR_PAD_LEFT ); - } - - static function padright( $parser, $string = '', $length = 0, $char = 0 ) { - return self::pad( $string, $length, $char ); - } - - static function anchorencode( $parser, $text ) { - $a = urlencode( $text ); - $a = strtr( $a, array( '%' => '.', '+' => '_' ) ); - # leave colons alone, however - $a = str_replace( '.3A', ':', $a ); - return $a; - } - - static function special( $parser, $text ) { - $title = SpecialPage::getTitleForAlias( $text ); - if ( $title ) { - return $title->getPrefixedText(); - } else { - return wfMsgForContent( 'nosuchspecialpage' ); - } - } - - public static function defaultsort( $parser, $text ) { - $text = trim( $text ); - if( strlen( $text ) > 0 ) - $parser->setDefaultSort( $text ); - return ''; - } - - public static function filepath( $parser, $name='', $option='' ) { - $file = wfFindFile( $name ); - if( $file ) { - $url = $file->getFullUrl(); - if( $option == 'nowiki' ) { - return "<nowiki>$url</nowiki>"; - } - return $url; - } else { - return ''; - } - } - - /** - * Parser function to extension tag adaptor - */ - public static function tagObj( $parser, $frame, $args ) { - $xpath = false; - if ( !count( $args ) ) { - return ''; - } - $tagName = strtolower( trim( $frame->expand( array_shift( $args ) ) ) ); - - if ( count( $args ) ) { - $inner = $frame->expand( array_shift( $args ) ); - } else { - $inner = null; - } - - $stripList = $parser->getStripList(); - if ( !in_array( $tagName, $stripList ) ) { - return '<span class="error">' . - wfMsg( 'unknown_extension_tag', $tagName ) . - '</span>'; - } - - $attributes = array(); - foreach ( $args as $arg ) { - $bits = $arg->splitArg(); - if ( strval( $bits['index'] ) === '' ) { - $name = $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ); - $value = trim( $frame->expand( $bits['value'] ) ); - if ( preg_match( '/^(?:["\'](.+)["\']|""|\'\')$/s', $value, $m ) ) { - $value = isset( $m[1] ) ? $m[1] : ''; - } - $attributes[$name] = $value; - } - } - - $params = array( - 'name' => $tagName, - 'inner' => $inner, - 'attributes' => $attributes, - 'close' => "</$tagName>", - ); - return $parser->extensionSubstitution( $params, $frame ); - } -} - diff --git a/includes/Database.php b/includes/Database.php deleted file mode 100644 index f8738288..00000000 --- a/includes/Database.php +++ /dev/null @@ -1,2443 +0,0 @@ -<?php -/** - * This file deals with MySQL interface functions - * and query specifics/optimisations - */ - -/** Number of times to re-try an operation in case of deadlock */ -define( 'DEADLOCK_TRIES', 4 ); -/** Minimum time to wait before retry, in microseconds */ -define( 'DEADLOCK_DELAY_MIN', 500000 ); -/** Maximum time to wait before retry */ -define( 'DEADLOCK_DELAY_MAX', 1500000 ); - -/****************************************************************************** - * Utility classes - *****************************************************************************/ - -/** - * Utility class. - * @addtogroup Database - */ -class DBObject { - public $mData; - - function DBObject($data) { - $this->mData = $data; - } - - function isLOB() { - return false; - } - - function data() { - return $this->mData; - } -}; - -/** - * Utility class - * @addtogroup Database - * - * This allows us to distinguish a blob from a normal string and an array of strings - */ -class Blob { - private $mData; - function __construct($data) { - $this->mData = $data; - } - function fetch() { - return $this->mData; - } -}; - -/** - * Utility class. - * @addtogroup Database - */ -class MySQLField { - private $name, $tablename, $default, $max_length, $nullable, - $is_pk, $is_unique, $is_key, $type; - function __construct ($info) { - $this->name = $info->name; - $this->tablename = $info->table; - $this->default = $info->def; - $this->max_length = $info->max_length; - $this->nullable = !$info->not_null; - $this->is_pk = $info->primary_key; - $this->is_unique = $info->unique_key; - $this->is_multiple = $info->multiple_key; - $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple); - $this->type = $info->type; - } - - function name() { - return $this->name; - } - - function tableName() { - return $this->tableName; - } - - function defaultValue() { - return $this->default; - } - - function maxLength() { - return $this->max_length; - } - - function nullable() { - return $this->nullable; - } - - function isKey() { - return $this->is_key; - } - - function isMultipleKey() { - return $this->is_multiple; - } - - function type() { - return $this->type; - } -} - -/****************************************************************************** - * Error classes - *****************************************************************************/ - -/** - * Database error base class - * @addtogroup Database - */ -class DBError extends MWException { - public $db; - - /** - * Construct a database error - * @param Database $db The database object which threw the error - * @param string $error A simple error message to be used for debugging - */ - function __construct( Database &$db, $error ) { - $this->db =& $db; - parent::__construct( $error ); - } -} - -/** - * @addtogroup Database - */ -class DBConnectionError extends DBError { - public $error; - - function __construct( Database &$db, $error = 'unknown error' ) { - $msg = 'DB connection error'; - if ( trim( $error ) != '' ) { - $msg .= ": $error"; - } - $this->error = $error; - parent::__construct( $db, $msg ); - } - - function useOutputPage() { - // Not likely to work - return false; - } - - function useMessageCache() { - // Not likely to work - return false; - } - - function getText() { - return $this->getMessage() . "\n"; - } - - function getLogMessage() { - # Don't send to the exception log - return false; - } - - function getPageTitle() { - global $wgSitename; - return "$wgSitename has a problem"; - } - - function getHTML() { - global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding; - global $wgSitename, $wgServer, $wgMessageCache; - - # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky. - # Hard coding strings instead. - - $noconnect = "<p><strong>Sorry! This site is experiencing technical difficulties.</strong></p><p>Try waiting a few minutes and reloading.</p><p><small>(Can't contact the database server: $1)</small></p>"; - $mainpage = 'Main Page'; - $searchdisabled = <<<EOT -<p style="margin: 1.5em 2em 1em">$wgSitename search is disabled for performance reasons. You can search via Google in the meantime. -<span style="font-size: 89%; display: block; margin-left: .2em">Note that their indexes of $wgSitename content may be out of date.</span></p>', -EOT; - - $googlesearch = " -<!-- SiteSearch Google --> -<FORM method=GET action=\"http://www.google.com/search\"> -<TABLE bgcolor=\"#FFFFFF\"><tr><td> -<A HREF=\"http://www.google.com/\"> -<IMG SRC=\"http://www.google.com/logos/Logo_40wht.gif\" -border=\"0\" ALT=\"Google\"></A> -</td> -<td> -<INPUT TYPE=text name=q size=31 maxlength=255 value=\"$1\"> -<INPUT type=submit name=btnG VALUE=\"Google Search\"> -<font size=-1> -<input type=hidden name=domains value=\"$wgServer\"><br /><input type=radio name=sitesearch value=\"\"> WWW <input type=radio name=sitesearch value=\"$wgServer\" checked> $wgServer <br /> -<input type='hidden' name='ie' value='$2'> -<input type='hidden' name='oe' value='$2'> -</font> -</td></tr></TABLE> -</FORM> -<!-- SiteSearch Google -->"; - $cachederror = "The following is a cached copy of the requested page, and may not be up to date. "; - - # No database access - if ( is_object( $wgMessageCache ) ) { - $wgMessageCache->disable(); - } - - if ( trim( $this->error ) == '' ) { - $this->error = $this->db->getProperty('mServer'); - } - - $text = str_replace( '$1', $this->error, $noconnect ); - $text .= wfGetSiteNotice(); - - if($wgUseFileCache) { - if($wgTitle) { - $t =& $wgTitle; - } else { - if($title) { - $t = Title::newFromURL( $title ); - } elseif (@/**/$_REQUEST['search']) { - $search = $_REQUEST['search']; - return $searchdisabled . - str_replace( array( '$1', '$2' ), array( htmlspecialchars( $search ), - $wgInputEncoding ), $googlesearch ); - } else { - $t = Title::newFromText( $mainpage ); - } - } - - $cache = new HTMLFileCache( $t ); - if( $cache->isFileCached() ) { - // @todo, FIXME: $msg is not defined on the next line. - $msg = '<p style="color: red"><b>'.$msg."<br />\n" . - $cachederror . "</b></p>\n"; - - $tag = '<div id="article">'; - $text = str_replace( - $tag, - $tag . $msg, - $cache->fetchPageText() ); - } - } - - return $text; - } -} - -/** - * @addtogroup Database - */ -class DBQueryError extends DBError { - public $error, $errno, $sql, $fname; - - function __construct( Database &$db, $error, $errno, $sql, $fname ) { - $message = "A database error has occurred\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - - parent::__construct( $db, $message ); - $this->error = $error; - $this->errno = $errno; - $this->sql = $sql; - $this->fname = $fname; - } - - function getText() { - if ( $this->useMessageCache() ) { - return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ), - htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n"; - } else { - return $this->getMessage(); - } - } - - function getSQL() { - global $wgShowSQLErrors; - if( !$wgShowSQLErrors ) { - return $this->msg( 'sqlhidden', 'SQL hidden' ); - } else { - return $this->sql; - } - } - - function getLogMessage() { - # Don't send to the exception log - return false; - } - - function getPageTitle() { - return $this->msg( 'databaseerror', 'Database error' ); - } - - function getHTML() { - if ( $this->useMessageCache() ) { - return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ), - htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ); - } else { - return nl2br( htmlspecialchars( $this->getMessage() ) ); - } - } -} - -/** - * @addtogroup Database - */ -class DBUnexpectedError extends DBError {} - -/******************************************************************************/ - -/** - * Database abstraction object - * @addtogroup Database - */ -class Database { - -#------------------------------------------------------------------------------ -# Variables -#------------------------------------------------------------------------------ - - protected $mLastQuery = ''; - - protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; - protected $mOut, $mOpened = false; - - protected $mFailFunction; - protected $mTablePrefix; - protected $mFlags; - protected $mTrxLevel = 0; - protected $mErrorCount = 0; - protected $mLBInfo = array(); - -#------------------------------------------------------------------------------ -# Accessors -#------------------------------------------------------------------------------ - # These optionally set a variable and return the previous state - - /** - * Fail function, takes a Database as a parameter - * Set to false for default, 1 for ignore errors - */ - function failFunction( $function = NULL ) { - return wfSetVar( $this->mFailFunction, $function ); - } - - /** - * Output page, used for reporting errors - * FALSE means discard output - */ - function setOutputPage( $out ) { - $this->mOut = $out; - } - - /** - * Boolean, controls output of large amounts of debug information - */ - function debug( $debug = NULL ) { - return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); - } - - /** - * Turns buffering of SQL result sets on (true) or off (false). - * Default is "on" and it should not be changed without good reasons. - */ - function bufferResults( $buffer = NULL ) { - if ( is_null( $buffer ) ) { - return !(bool)( $this->mFlags & DBO_NOBUFFER ); - } else { - return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); - } - } - - /** - * Turns on (false) or off (true) the automatic generation and sending - * of a "we're sorry, but there has been a database error" page on - * database errors. Default is on (false). When turned off, the - * code should use lastErrno() and lastError() to handle the - * situation as appropriate. - */ - function ignoreErrors( $ignoreErrors = NULL ) { - return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); - } - - /** - * The current depth of nested transactions - * @param $level Integer: , default NULL. - */ - function trxLevel( $level = NULL ) { - return wfSetVar( $this->mTrxLevel, $level ); - } - - /** - * Number of errors logged, only useful when errors are ignored - */ - function errorCount( $count = NULL ) { - return wfSetVar( $this->mErrorCount, $count ); - } - - /** - * Properties passed down from the server info array of the load balancer - */ - function getLBInfo( $name = NULL ) { - if ( is_null( $name ) ) { - return $this->mLBInfo; - } else { - if ( array_key_exists( $name, $this->mLBInfo ) ) { - return $this->mLBInfo[$name]; - } else { - return NULL; - } - } - } - - function setLBInfo( $name, $value = NULL ) { - if ( is_null( $value ) ) { - $this->mLBInfo = $name; - } else { - $this->mLBInfo[$name] = $value; - } - } - - /** - * Returns true if this database supports (and uses) cascading deletes - */ - function cascadingDeletes() { - return false; - } - - /** - * Returns true if this database supports (and uses) triggers (e.g. on the page table) - */ - function cleanupTriggers() { - return false; - } - - /** - * Returns true if this database is strict about what can be put into an IP field. - * Specifically, it uses a NULL value instead of an empty string. - */ - function strictIPs() { - return false; - } - - /** - * Returns true if this database uses timestamps rather than integers - */ - function realTimestamps() { - return false; - } - - /** - * Returns true if this database does an implicit sort when doing GROUP BY - */ - function implicitGroupby() { - return true; - } - - /** - * Returns true if this database does an implicit order by when the column has an index - * For example: SELECT page_title FROM page LIMIT 1 - */ - function implicitOrderby() { - return true; - } - - /** - * Returns true if this database can do a native search on IP columns - * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32'; - */ - function searchableIPs() { - return false; - } - - /** - * Returns true if this database can use functional indexes - */ - function functionalIndexes() { - return false; - } - - /**#@+ - * Get function - */ - function lastQuery() { return $this->mLastQuery; } - function isOpen() { return $this->mOpened; } - /**#@-*/ - - function setFlag( $flag ) { - $this->mFlags |= $flag; - } - - function clearFlag( $flag ) { - $this->mFlags &= ~$flag; - } - - function getFlag( $flag ) { - return !!($this->mFlags & $flag); - } - - /** - * General read-only accessor - */ - function getProperty( $name ) { - return $this->$name; - } - -#------------------------------------------------------------------------------ -# Other functions -#------------------------------------------------------------------------------ - - /**@{{ - * Constructor. - * @param string $server database server host - * @param string $user database user name - * @param string $password database user password - * @param string $dbname database name - * @param failFunction - * @param $flags - * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php - */ - function __construct( $server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) { - - global $wgOut, $wgDBprefix, $wgCommandLineMode; - # Can't get a reference if it hasn't been set yet - if ( !isset( $wgOut ) ) { - $wgOut = NULL; - } - $this->mOut =& $wgOut; - - $this->mFailFunction = $failFunction; - $this->mFlags = $flags; - - if ( $this->mFlags & DBO_DEFAULT ) { - if ( $wgCommandLineMode ) { - $this->mFlags &= ~DBO_TRX; - } else { - $this->mFlags |= DBO_TRX; - } - } - - /* - // Faster read-only access - if ( wfReadOnly() ) { - $this->mFlags |= DBO_PERSISTENT; - $this->mFlags &= ~DBO_TRX; - }*/ - - /** Get the default table prefix*/ - if ( $tablePrefix == 'get from global' ) { - $this->mTablePrefix = $wgDBprefix; - } else { - $this->mTablePrefix = $tablePrefix; - } - - if ( $server ) { - $this->open( $server, $user, $password, $dbName ); - } - } - - /** - * @static - * @param failFunction - * @param $flags - */ - static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) - { - return new Database( $server, $user, $password, $dbName, $failFunction, $flags ); - } - - /** - * Usually aborts on failure - * If the failFunction is set to a non-zero integer, returns success - */ - function open( $server, $user, $password, $dbName ) { - global $wguname; - wfProfileIn( __METHOD__ ); - - # Test for missing mysql.so - # First try to load it - if (!@extension_loaded('mysql')) { - @dl('mysql.so'); - } - - # Fail now - # Otherwise we get a suppressed fatal error, which is very hard to track down - if ( !function_exists( 'mysql_connect' ) ) { - throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" ); - } - - $this->close(); - $this->mServer = $server; - $this->mUser = $user; - $this->mPassword = $password; - $this->mDBname = $dbName; - - $success = false; - - wfProfileIn("dbconnect-$server"); - - # LIVE PATCH by Tim, ask Domas for why: retry loop - $this->mConn = false; - $max = 3; - for ( $i = 0; $i < $max && !$this->mConn; $i++ ) { - if ( $i > 1 ) { - usleep( 1000 ); - } - if ( $this->mFlags & DBO_PERSISTENT ) { - @/**/$this->mConn = mysql_pconnect( $server, $user, $password ); - } else { - # Create a new connection... - @/**/$this->mConn = mysql_connect( $server, $user, $password, true ); - } - if ($this->mConn === false) { - #$iplus = $i + 1; - #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n"); - } - } - - wfProfileOut("dbconnect-$server"); - - if ( $dbName != '' ) { - if ( $this->mConn !== false ) { - $success = @/**/mysql_select_db( $dbName, $this->mConn ); - if ( !$success ) { - $error = "Error selecting database $dbName on server {$this->mServer} " . - "from client host {$wguname['nodename']}\n"; - wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n"); - wfDebug( $error ); - } - } else { - wfDebug( "DB connection error\n" ); - wfDebug( "Server: $server, User: $user, Password: " . - substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" ); - $success = false; - } - } else { - # Delay USE query - $success = (bool)$this->mConn; - } - - if ( $success ) { - $version = $this->getServerVersion(); - if ( version_compare( $version, '4.1' ) >= 0 ) { - // Tell the server we're communicating with it in UTF-8. - // This may engage various charset conversions. - global $wgDBmysql5; - if( $wgDBmysql5 ) { - $this->query( 'SET NAMES utf8', __METHOD__ ); - } - // Turn off strict mode - $this->query( "SET sql_mode = ''", __METHOD__ ); - } - - // Turn off strict mode if it is on - } else { - $this->reportConnectionError(); - } - - $this->mOpened = $success; - wfProfileOut( __METHOD__ ); - return $success; - } - /**@}}*/ - - /** - * Closes a database connection. - * if it is open : commits any open transactions - * - * @return bool operation success. true if already closed. - */ - function close() - { - $this->mOpened = false; - if ( $this->mConn ) { - if ( $this->trxLevel() ) { - $this->immediateCommit(); - } - return mysql_close( $this->mConn ); - } else { - return true; - } - } - - /** - * @param string $error fallback error message, used if none is given by MySQL - */ - function reportConnectionError( $error = 'Unknown error' ) { - $myError = $this->lastError(); - if ( $myError ) { - $error = $myError; - } - - if ( $this->mFailFunction ) { - # Legacy error handling method - if ( !is_int( $this->mFailFunction ) ) { - $ff = $this->mFailFunction; - $ff( $this, $error ); - } - } else { - # New method - wfLogDBError( "Connection error: $error\n" ); - throw new DBConnectionError( $this, $error ); - } - } - - /** - * Usually aborts on failure. If errors are explicitly ignored, returns success. - * - * @param $sql String: SQL query - * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST - * comment (you can use __METHOD__ or add some extra info) - * @param $tempIgnore Bool: Whether to avoid throwing an exception on errors... - * maybe best to catch the exception instead? - * @return true for a successful write query, ResultWrapper object for a successful read query, - * or false on failure if $tempIgnore set - * @throws DBQueryError Thrown when the database returns an error of any kind - */ - public function query( $sql, $fname = '', $tempIgnore = false ) { - global $wgProfiling; - - if ( $wgProfiling ) { - # generalizeSQL will probably cut down the query to reasonable - # logging size most of the time. The substr is really just a sanity check. - - # Who's been wasting my precious column space? -- TS - #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); - - if ( is_null( $this->getLBInfo( 'master' ) ) ) { - $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); - $totalProf = 'Database::query'; - } else { - $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 ); - $totalProf = 'Database::query-master'; - } - wfProfileIn( $totalProf ); - wfProfileIn( $queryProf ); - } - - $this->mLastQuery = $sql; - - # Add a comment for easy SHOW PROCESSLIST interpretation - #if ( $fname ) { - global $wgUser; - if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) { - $userName = $wgUser->getName(); - if ( mb_strlen( $userName ) > 15 ) { - $userName = mb_substr( $userName, 0, 15 ) . '...'; - } - $userName = str_replace( '/', '', $userName ); - } else { - $userName = ''; - } - $commentedSql = preg_replace('/\s/', " /* $fname $userName */ ", $sql, 1); - #} else { - # $commentedSql = $sql; - #} - - # If DBO_TRX is set, start a transaction - if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() && - $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') { - // avoid establishing transactions for SHOW and SET statements too - - // that would delay transaction initializations to once connection - // is really used by application - $sqlstart = substr($sql,0,10); // very much worth it, benchmark certified(tm) - if (strpos($sqlstart,"SHOW ")!==0 and strpos($sqlstart,"SET ")!==0) - $this->begin(); - } - - if ( $this->debug() ) { - $sqlx = substr( $commentedSql, 0, 500 ); - $sqlx = strtr( $sqlx, "\t\n", ' ' ); - wfDebug( "SQL: $sqlx\n" ); - } - - # Do the query and handle errors - $ret = $this->doQuery( $commentedSql ); - - # Try reconnecting if the connection was lost - if ( false === $ret && ( $this->lastErrno() == 2013 || $this->lastErrno() == 2006 ) ) { - # Transaction is gone, like it or not - $this->mTrxLevel = 0; - wfDebug( "Connection lost, reconnecting...\n" ); - if ( $this->ping() ) { - wfDebug( "Reconnected\n" ); - $sqlx = substr( $commentedSql, 0, 500 ); - $sqlx = strtr( $sqlx, "\t\n", ' ' ); - global $wgRequestTime; - $elapsed = round( microtime(true) - $wgRequestTime, 3 ); - wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" ); - $ret = $this->doQuery( $commentedSql ); - } else { - wfDebug( "Failed\n" ); - } - } - - if ( false === $ret ) { - $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); - } - - if ( $wgProfiling ) { - wfProfileOut( $queryProf ); - wfProfileOut( $totalProf ); - } - return $this->resultObject( $ret ); - } - - /** - * The DBMS-dependent part of query() - * @param $sql String: SQL query. - * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure - * @access private - */ - /*private*/ function doQuery( $sql ) { - if( $this->bufferResults() ) { - $ret = mysql_query( $sql, $this->mConn ); - } else { - $ret = mysql_unbuffered_query( $sql, $this->mConn ); - } - return $ret; - } - - /** - * @param $error - * @param $errno - * @param $sql - * @param string $fname - * @param bool $tempIgnore - */ - function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - global $wgCommandLineMode; - # Ignore errors during error handling to avoid infinite recursion - $ignore = $this->ignoreErrors( true ); - ++$this->mErrorCount; - - if( $ignore || $tempIgnore ) { - wfDebug("SQL ERROR (ignored): $error\n"); - $this->ignoreErrors( $ignore ); - } else { - $sql1line = str_replace( "\n", "\\n", $sql ); - wfLogDBError("$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n"); - wfDebug("SQL ERROR: " . $error . "\n"); - throw new DBQueryError( $this, $error, $errno, $sql, $fname ); - } - } - - - /** - * Intended to be compatible with the PEAR::DB wrapper functions. - * http://pear.php.net/manual/en/package.database.db.intro-execute.php - * - * ? = scalar value, quoted as necessary - * ! = raw SQL bit (a function for instance) - * & = filename; reads the file and inserts as a blob - * (we don't use this though...) - */ - function prepare( $sql, $func = 'Database::prepare' ) { - /* MySQL doesn't support prepared statements (yet), so just - pack up the query for reference. We'll manually replace - the bits later. */ - return array( 'query' => $sql, 'func' => $func ); - } - - function freePrepared( $prepared ) { - /* No-op for MySQL */ - } - - /** - * Execute a prepared query with the various arguments - * @param string $prepared the prepared sql - * @param mixed $args Either an array here, or put scalars as varargs - */ - function execute( $prepared, $args = null ) { - if( !is_array( $args ) ) { - # Pull the var args - $args = func_get_args(); - array_shift( $args ); - } - $sql = $this->fillPrepared( $prepared['query'], $args ); - return $this->query( $sql, $prepared['func'] ); - } - - /** - * Prepare & execute an SQL statement, quoting and inserting arguments - * in the appropriate places. - * @param string $query - * @param string $args ... - */ - function safeQuery( $query, $args = null ) { - $prepared = $this->prepare( $query, 'Database::safeQuery' ); - if( !is_array( $args ) ) { - # Pull the var args - $args = func_get_args(); - array_shift( $args ); - } - $retval = $this->execute( $prepared, $args ); - $this->freePrepared( $prepared ); - return $retval; - } - - /** - * For faking prepared SQL statements on DBs that don't support - * it directly. - * @param string $preparedSql - a 'preparable' SQL statement - * @param array $args - array of arguments to fill it with - * @return string executable SQL - */ - function fillPrepared( $preparedQuery, $args ) { - reset( $args ); - $this->preparedArgs =& $args; - return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', - array( &$this, 'fillPreparedArg' ), $preparedQuery ); - } - - /** - * preg_callback func for fillPrepared() - * The arguments should be in $this->preparedArgs and must not be touched - * while we're doing this. - * - * @param array $matches - * @return string - * @private - */ - function fillPreparedArg( $matches ) { - switch( $matches[1] ) { - case '\\?': return '?'; - case '\\!': return '!'; - case '\\&': return '&'; - } - list( /* $n */ , $arg ) = each( $this->preparedArgs ); - switch( $matches[1] ) { - case '?': return $this->addQuotes( $arg ); - case '!': return $arg; - case '&': - # return $this->addQuotes( file_get_contents( $arg ) ); - throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' ); - default: - throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' ); - } - } - - /**#@+ - * @param mixed $res A SQL result - */ - /** - * Free a result object - */ - function freeResult( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - if ( !@/**/mysql_free_result( $res ) ) { - throw new DBUnexpectedError( $this, "Unable to free MySQL result" ); - } - } - - /** - * Fetch the next row from the given result object, in object form. - * Fields can be retrieved with $row->fieldname, with fields acting like - * member variables. - * - * @param $res SQL result object as returned from Database::query(), etc. - * @return MySQL row object - * @throws DBUnexpectedError Thrown if the database returns an error - */ - function fetchObject( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - @/**/$row = mysql_fetch_object( $res ); - if( $this->lastErrno() ) { - throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) ); - } - return $row; - } - - /** - * Fetch the next row from the given result object, in associative array - * form. Fields are retrieved with $row['fieldname']. - * - * @param $res SQL result object as returned from Database::query(), etc. - * @return MySQL row object - * @throws DBUnexpectedError Thrown if the database returns an error - */ - function fetchRow( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - @/**/$row = mysql_fetch_array( $res ); - if ( $this->lastErrno() ) { - throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) ); - } - return $row; - } - - /** - * Get the number of rows in a result object - */ - function numRows( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - @/**/$n = mysql_num_rows( $res ); - if( $this->lastErrno() ) { - throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) ); - } - return $n; - } - - /** - * Get the number of fields in a result object - * See documentation for mysql_num_fields() - */ - function numFields( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - return mysql_num_fields( $res ); - } - - /** - * Get a field name in a result object - * See documentation for mysql_field_name(): - * http://www.php.net/mysql_field_name - */ - function fieldName( $res, $n ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - return mysql_field_name( $res, $n ); - } - - /** - * Get the inserted value of an auto-increment row - * - * The value inserted should be fetched from nextSequenceValue() - * - * Example: - * $id = $dbw->nextSequenceValue('page_page_id_seq'); - * $dbw->insert('page',array('page_id' => $id)); - * $id = $dbw->insertId(); - */ - function insertId() { return mysql_insert_id( $this->mConn ); } - - /** - * Change the position of the cursor in a result object - * See mysql_data_seek() - */ - function dataSeek( $res, $row ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - return mysql_data_seek( $res, $row ); - } - - /** - * Get the last error number - * See mysql_errno() - */ - function lastErrno() { - if ( $this->mConn ) { - return mysql_errno( $this->mConn ); - } else { - return mysql_errno(); - } - } - - /** - * Get a description of the last error - * See mysql_error() for more details - */ - function lastError() { - if ( $this->mConn ) { - # Even if it's non-zero, it can still be invalid - wfSuppressWarnings(); - $error = mysql_error( $this->mConn ); - if ( !$error ) { - $error = mysql_error(); - } - wfRestoreWarnings(); - } else { - $error = mysql_error(); - } - if( $error ) { - $error .= ' (' . $this->mServer . ')'; - } - return $error; - } - /** - * Get the number of rows affected by the last write query - * See mysql_affected_rows() for more details - */ - function affectedRows() { return mysql_affected_rows( $this->mConn ); } - /**#@-*/ // end of template : @param $result - - /** - * Simple UPDATE wrapper - * Usually aborts on failure - * If errors are explicitly ignored, returns success - * - * This function exists for historical reasons, Database::update() has a more standard - * calling convention and feature set - */ - function set( $table, $var, $value, $cond, $fname = 'Database::set' ) - { - $table = $this->tableName( $table ); - $sql = "UPDATE $table SET $var = '" . - $this->strencode( $value ) . "' WHERE ($cond)"; - return (bool)$this->query( $sql, $fname ); - } - - /** - * Simple SELECT wrapper, returns a single field, input must be encoded - * Usually aborts on failure - * If errors are explicitly ignored, returns FALSE on failure - */ - function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) { - if ( !is_array( $options ) ) { - $options = array( $options ); - } - $options['LIMIT'] = 1; - - $res = $this->select( $table, $var, $cond, $fname, $options ); - if ( $res === false || !$this->numRows( $res ) ) { - return false; - } - $row = $this->fetchRow( $res ); - if ( $row !== false ) { - $this->freeResult( $res ); - return $row[0]; - } else { - return false; - } - } - - /** - * Returns an optional USE INDEX clause to go after the table, and a - * string to go at the end of the query - * - * @private - * - * @param array $options an associative array of options to be turned into - * an SQL query, valid keys are listed in the function. - * @return array - */ - function makeSelectOptions( $options ) { - $preLimitTail = $postLimitTail = ''; - $startOpts = ''; - - $noKeyOptions = array(); - foreach ( $options as $key => $option ) { - if ( is_numeric( $key ) ) { - $noKeyOptions[$option] = true; - } - } - - if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; - if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}"; - if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; - - //if (isset($options['LIMIT'])) { - // $tailOpts .= $this->limitResult('', $options['LIMIT'], - // isset($options['OFFSET']) ? $options['OFFSET'] - // : false); - //} - - if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE'; - if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE'; - if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; - - # Various MySQL extensions - if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */'; - if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY'; - if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT'; - if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT'; - if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT'; - if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS'; - if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE'; - if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE'; - - if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { - $useIndex = $this->useIndexClause( $options['USE INDEX'] ); - } else { - $useIndex = ''; - } - - return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); - } - - /** - * SELECT wrapper - * - * @param mixed $table Array or string, table name(s) (prefix auto-added) - * @param mixed $vars Array or string, field name(s) to be retrieved - * @param mixed $conds Array or string, condition(s) for WHERE - * @param string $fname Calling function name (use __METHOD__) for logs/profiling - * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')), - * see Database::makeSelectOptions code for list of supported stuff - * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure - */ - function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() ) - { - if( is_array( $vars ) ) { - $vars = implode( ',', $vars ); - } - if( !is_array( $options ) ) { - $options = array( $options ); - } - if( is_array( $table ) ) { - if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) - $from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] ); - else - $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) ); - } elseif ($table!='') { - if ($table{0}==' ') { - $from = ' FROM ' . $table; - } else { - $from = ' FROM ' . $this->tableName( $table ); - } - } else { - $from = ''; - } - - list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options ); - - if( !empty( $conds ) ) { - if ( is_array( $conds ) ) { - $conds = $this->makeList( $conds, LIST_AND ); - } - $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; - } else { - $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; - } - - if (isset($options['LIMIT'])) - $sql = $this->limitResult($sql, $options['LIMIT'], - isset($options['OFFSET']) ? $options['OFFSET'] : false); - $sql = "$sql $postLimitTail"; - - if (isset($options['EXPLAIN'])) { - $sql = 'EXPLAIN ' . $sql; - } - return $this->query( $sql, $fname ); - } - - /** - * Single row SELECT wrapper - * Aborts or returns FALSE on error - * - * $vars: the selected variables - * $conds: a condition map, terms are ANDed together. - * Items with numeric keys are taken to be literal conditions - * Takes an array of selected variables, and a condition map, which is ANDed - * e.g: selectRow( "page", array( "page_id" ), array( "page_namespace" => - * NS_MAIN, "page_title" => "Astronomy" ) ) would return an object where - * $obj- >page_id is the ID of the Astronomy article - * - * @todo migrate documentation to phpdocumentor format - */ - function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array() ) { - $options['LIMIT'] = 1; - $res = $this->select( $table, $vars, $conds, $fname, $options ); - if ( $res === false ) - return false; - if ( !$this->numRows($res) ) { - $this->freeResult($res); - return false; - } - $obj = $this->fetchObject( $res ); - $this->freeResult( $res ); - return $obj; - - } - - /** - * Estimate rows in dataset - * Returns estimated count, based on EXPLAIN output - * Takes same arguments as Database::select() - */ - - function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { - $options['EXPLAIN']=true; - $res = $this->select ($table, $vars, $conds, $fname, $options ); - if ( $res === false ) - return false; - if (!$this->numRows($res)) { - $this->freeResult($res); - return 0; - } - - $rows=1; - - while( $plan = $this->fetchObject( $res ) ) { - $rows *= ($plan->rows > 0)?$plan->rows:1; // avoid resetting to zero - } - - $this->freeResult($res); - return $rows; - } - - - /** - * Removes most variables from an SQL query and replaces them with X or N for numbers. - * It's only slightly flawed. Don't use for anything important. - * - * @param string $sql A SQL Query - * @static - */ - static function generalizeSQL( $sql ) { - # This does the same as the regexp below would do, but in such a way - # as to avoid crashing php on some large strings. - # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql); - - $sql = str_replace ( "\\\\", '', $sql); - $sql = str_replace ( "\\'", '', $sql); - $sql = str_replace ( "\\\"", '', $sql); - $sql = preg_replace ("/'.*'/s", "'X'", $sql); - $sql = preg_replace ('/".*"/s', "'X'", $sql); - - # All newlines, tabs, etc replaced by single space - $sql = preg_replace ( '/\s+/', ' ', $sql); - - # All numbers => N - $sql = preg_replace ('/-?[0-9]+/s', 'N', $sql); - - return $sql; - } - - /** - * Determines whether a field exists in a table - * Usually aborts on failure - * If errors are explicitly ignored, returns NULL on failure - */ - function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) { - $table = $this->tableName( $table ); - $res = $this->query( 'DESCRIBE '.$table, $fname ); - if ( !$res ) { - return NULL; - } - - $found = false; - - while ( $row = $this->fetchObject( $res ) ) { - if ( $row->Field == $field ) { - $found = true; - break; - } - } - return $found; - } - - /** - * Determines whether an index exists - * Usually aborts on failure - * If errors are explicitly ignored, returns NULL on failure - */ - function indexExists( $table, $index, $fname = 'Database::indexExists' ) { - $info = $this->indexInfo( $table, $index, $fname ); - if ( is_null( $info ) ) { - return NULL; - } else { - return $info !== false; - } - } - - - /** - * Get information about an index into an object - * Returns false if the index does not exist - */ - function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) { - # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not. - # SHOW INDEX should work for 3.x and up: - # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html - $table = $this->tableName( $table ); - $sql = 'SHOW INDEX FROM '.$table; - $res = $this->query( $sql, $fname ); - if ( !$res ) { - return NULL; - } - - $result = array(); - while ( $row = $this->fetchObject( $res ) ) { - if ( $row->Key_name == $index ) { - $result[] = $row; - } - } - $this->freeResult($res); - - return empty($result) ? false : $result; - } - - /** - * Query whether a given table exists - */ - function tableExists( $table ) { - $table = $this->tableName( $table ); - $old = $this->ignoreErrors( true ); - $res = $this->query( "SELECT 1 FROM $table LIMIT 1" ); - $this->ignoreErrors( $old ); - if( $res ) { - $this->freeResult( $res ); - return true; - } else { - return false; - } - } - - /** - * mysql_fetch_field() wrapper - * Returns false if the field doesn't exist - * - * @param $table - * @param $field - */ - function fieldInfo( $table, $field ) { - $table = $this->tableName( $table ); - $res = $this->query( "SELECT * FROM $table LIMIT 1" ); - $n = mysql_num_fields( $res->result ); - for( $i = 0; $i < $n; $i++ ) { - $meta = mysql_fetch_field( $res->result, $i ); - if( $field == $meta->name ) { - return new MySQLField($meta); - } - } - return false; - } - - /** - * mysql_field_type() wrapper - */ - function fieldType( $res, $index ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - return mysql_field_type( $res, $index ); - } - - /** - * Determines if a given index is unique - */ - function indexUnique( $table, $index ) { - $indexInfo = $this->indexInfo( $table, $index ); - if ( !$indexInfo ) { - return NULL; - } - return !$indexInfo[0]->Non_unique; - } - - /** - * INSERT wrapper, inserts an array into a table - * - * $a may be a single associative array, or an array of these with numeric keys, for - * multi-row insert. - * - * Usually aborts on failure - * If errors are explicitly ignored, returns success - */ - function insert( $table, $a, $fname = 'Database::insert', $options = array() ) { - # No rows to insert, easy just return now - if ( !count( $a ) ) { - return true; - } - - $table = $this->tableName( $table ); - if ( !is_array( $options ) ) { - $options = array( $options ); - } - if ( isset( $a[0] ) && is_array( $a[0] ) ) { - $multi = true; - $keys = array_keys( $a[0] ); - } else { - $multi = false; - $keys = array_keys( $a ); - } - - $sql = 'INSERT ' . implode( ' ', $options ) . - " INTO $table (" . implode( ',', $keys ) . ') VALUES '; - - if ( $multi ) { - $first = true; - foreach ( $a as $row ) { - if ( $first ) { - $first = false; - } else { - $sql .= ','; - } - $sql .= '(' . $this->makeList( $row ) . ')'; - } - } else { - $sql .= '(' . $this->makeList( $a ) . ')'; - } - return (bool)$this->query( $sql, $fname ); - } - - /** - * Make UPDATE options for the Database::update function - * - * @private - * @param array $options The options passed to Database::update - * @return string - */ - function makeUpdateOptions( $options ) { - if( !is_array( $options ) ) { - $options = array( $options ); - } - $opts = array(); - if ( in_array( 'LOW_PRIORITY', $options ) ) - $opts[] = $this->lowPriorityOption(); - if ( in_array( 'IGNORE', $options ) ) - $opts[] = 'IGNORE'; - return implode(' ', $opts); - } - - /** - * UPDATE wrapper, takes a condition array and a SET array - * - * @param string $table The table to UPDATE - * @param array $values An array of values to SET - * @param array $conds An array of conditions (WHERE). Use '*' to update all rows. - * @param string $fname The Class::Function calling this function - * (for the log) - * @param array $options An array of UPDATE options, can be one or - * more of IGNORE, LOW_PRIORITY - * @return bool - */ - function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) { - $table = $this->tableName( $table ); - $opts = $this->makeUpdateOptions( $options ); - $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); - if ( $conds != '*' ) { - $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); - } - return $this->query( $sql, $fname ); - } - - /** - * Makes an encoded list of strings from an array - * $mode: - * LIST_COMMA - comma separated, no field names - * LIST_AND - ANDed WHERE clause (without the WHERE) - * LIST_OR - ORed WHERE clause (without the WHERE) - * LIST_SET - comma separated with field names, like a SET clause - * LIST_NAMES - comma separated field names - */ - function makeList( $a, $mode = LIST_COMMA ) { - if ( !is_array( $a ) ) { - throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); - } - - $first = true; - $list = ''; - foreach ( $a as $field => $value ) { - if ( !$first ) { - if ( $mode == LIST_AND ) { - $list .= ' AND '; - } elseif($mode == LIST_OR) { - $list .= ' OR '; - } else { - $list .= ','; - } - } else { - $first = false; - } - if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { - $list .= "($value)"; - } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) { - $list .= "$value"; - } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) { - if( count( $value ) == 0 ) { - // Empty input... or should this throw an error? - $list .= '0'; - } elseif( count( $value ) == 1 ) { - // Special-case single values, as IN isn't terribly efficient - $list .= $field." = ".$this->addQuotes( $value[0] ); - } else { - $list .= $field." IN (".$this->makeList($value).") "; - } - } elseif( is_null($value) ) { - if ( $mode == LIST_AND || $mode == LIST_OR ) { - $list .= "$field IS "; - } elseif ( $mode == LIST_SET ) { - $list .= "$field = "; - } - $list .= 'NULL'; - } else { - if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { - $list .= "$field = "; - } - $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); - } - } - return $list; - } - - /** - * Change the current database - */ - function selectDB( $db ) { - $this->mDBname = $db; - return mysql_select_db( $db, $this->mConn ); - } - - /** - * Format a table name ready for use in constructing an SQL query - * - * This does two important things: it quotes table names which as necessary, - * and it adds a table prefix if there is one. - * - * All functions of this object which require a table name call this function - * themselves. Pass the canonical name to such functions. This is only needed - * when calling query() directly. - * - * @param string $name database table name - */ - function tableName( $name ) { - global $wgSharedDB; - # Skip quoted literals - if ( $name{0} != '`' ) { - if ( $this->mTablePrefix !== '' && strpos( $name, '.' ) === false ) { - $name = "{$this->mTablePrefix}$name"; - } - if ( isset( $wgSharedDB ) && "{$this->mTablePrefix}user" == $name ) { - $name = "`$wgSharedDB`.`$name`"; - } else { - # Standard quoting - $name = "`$name`"; - } - } - return $name; - } - - /** - * Fetch a number of table names into an array - * This is handy when you need to construct SQL for joins - * - * Example: - * extract($dbr->tableNames('user','watchlist')); - * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user - * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; - */ - public function tableNames() { - $inArray = func_get_args(); - $retVal = array(); - foreach ( $inArray as $name ) { - $retVal[$name] = $this->tableName( $name ); - } - return $retVal; - } - - /** - * Fetch a number of table names into an zero-indexed numerical array - * This is handy when you need to construct SQL for joins - * - * Example: - * list( $user, $watchlist ) = $dbr->tableNamesN('user','watchlist'); - * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user - * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; - */ - public function tableNamesN() { - $inArray = func_get_args(); - $retVal = array(); - foreach ( $inArray as $name ) { - $retVal[] = $this->tableName( $name ); - } - return $retVal; - } - - /** - * @private - */ - function tableNamesWithUseIndex( $tables, $use_index ) { - $ret = array(); - - foreach ( $tables as $table ) - if ( @$use_index[$table] !== null ) - $ret[] = $this->tableName( $table ) . ' ' . $this->useIndexClause( implode( ',', (array)$use_index[$table] ) ); - else - $ret[] = $this->tableName( $table ); - - return implode( ',', $ret ); - } - - /** - * Wrapper for addslashes() - * @param string $s String to be slashed. - * @return string slashed string. - */ - function strencode( $s ) { - return mysql_real_escape_string( $s, $this->mConn ); - } - - /** - * If it's a string, adds quotes and backslashes - * Otherwise returns as-is - */ - function addQuotes( $s ) { - if ( is_null( $s ) ) { - return 'NULL'; - } else { - # This will also quote numeric values. This should be harmless, - # and protects against weird problems that occur when they really - # _are_ strings such as article titles and string->number->string - # conversion is not 1:1. - return "'" . $this->strencode( $s ) . "'"; - } - } - - /** - * Escape string for safe LIKE usage - */ - function escapeLike( $s ) { - $s=$this->strencode( $s ); - $s=str_replace(array('%','_'),array('\%','\_'),$s); - return $s; - } - - /** - * Returns an appropriately quoted sequence value for inserting a new row. - * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL - * subclass will return an integer, and save the value for insertId() - */ - function nextSequenceValue( $seqName ) { - return NULL; - } - - /** - * USE INDEX clause - * PostgreSQL doesn't have them and returns "" - */ - function useIndexClause( $index ) { - return "FORCE INDEX ($index)"; - } - - /** - * REPLACE query wrapper - * PostgreSQL simulates this with a DELETE followed by INSERT - * $row is the row to insert, an associative array - * $uniqueIndexes is an array of indexes. Each element may be either a - * field name or an array of field names - * - * It may be more efficient to leave off unique indexes which are unlikely to collide. - * However if you do this, you run the risk of encountering errors which wouldn't have - * occurred in MySQL - * - * @todo migrate comment to phodocumentor format - */ - function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) { - $table = $this->tableName( $table ); - - # Single row case - if ( !is_array( reset( $rows ) ) ) { - $rows = array( $rows ); - } - - $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES '; - $first = true; - foreach ( $rows as $row ) { - if ( $first ) { - $first = false; - } else { - $sql .= ','; - } - $sql .= '(' . $this->makeList( $row ) . ')'; - } - return $this->query( $sql, $fname ); - } - - /** - * DELETE where the condition is a join - * MySQL does this with a multi-table DELETE syntax, PostgreSQL does it with sub-selects - * - * For safety, an empty $conds will not delete everything. If you want to delete all rows where the - * join condition matches, set $conds='*' - * - * DO NOT put the join condition in $conds - * - * @param string $delTable The table to delete from. - * @param string $joinTable The other table. - * @param string $delVar The variable to join on, in the first table. - * @param string $joinVar The variable to join on, in the second table. - * @param array $conds Condition array of field names mapped to variables, ANDed together in the WHERE clause - */ - function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) { - if ( !$conds ) { - throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' ); - } - - $delTable = $this->tableName( $delTable ); - $joinTable = $this->tableName( $joinTable ); - $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar "; - if ( $conds != '*' ) { - $sql .= ' AND ' . $this->makeList( $conds, LIST_AND ); - } - - return $this->query( $sql, $fname ); - } - - /** - * Returns the size of a text field, or -1 for "unlimited" - */ - function textFieldSize( $table, $field ) { - $table = $this->tableName( $table ); - $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; - $res = $this->query( $sql, 'Database::textFieldSize' ); - $row = $this->fetchObject( $res ); - $this->freeResult( $res ); - - $m = array(); - if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { - $size = $m[1]; - } else { - $size = -1; - } - return $size; - } - - /** - * @return string Returns the text of the low priority option if it is supported, or a blank string otherwise - */ - function lowPriorityOption() { - return 'LOW_PRIORITY'; - } - - /** - * DELETE query wrapper - * - * Use $conds == "*" to delete all rows - */ - function delete( $table, $conds, $fname = 'Database::delete' ) { - if ( !$conds ) { - throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' ); - } - $table = $this->tableName( $table ); - $sql = "DELETE FROM $table"; - if ( $conds != '*' ) { - $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); - } - return $this->query( $sql, $fname ); - } - - /** - * INSERT SELECT wrapper - * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...) - * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes() - * $conds may be "*" to copy the whole table - * srcTable may be an array of tables. - */ - function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect', - $insertOptions = array(), $selectOptions = array() ) - { - $destTable = $this->tableName( $destTable ); - if ( is_array( $insertOptions ) ) { - $insertOptions = implode( ' ', $insertOptions ); - } - if( !is_array( $selectOptions ) ) { - $selectOptions = array( $selectOptions ); - } - list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); - if( is_array( $srcTable ) ) { - $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); - } else { - $srcTable = $this->tableName( $srcTable ); - } - $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . - " SELECT $startOpts " . implode( ',', $varMap ) . - " FROM $srcTable $useIndex "; - if ( $conds != '*' ) { - $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); - } - $sql .= " $tailOpts"; - return $this->query( $sql, $fname ); - } - - /** - * Construct a LIMIT query with optional offset - * This is used for query pages - * $sql string SQL query we will append the limit too - * $limit integer the SQL limit - * $offset integer the SQL offset (default false) - */ - function limitResult($sql, $limit, $offset=false) { - if( !is_numeric($limit) ) { - throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); - } - return " $sql LIMIT " - . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" ) - . "{$limit} "; - } - function limitResultForUpdate($sql, $num) { - return $this->limitResult($sql, $num, 0); - } - - /** - * Returns an SQL expression for a simple conditional. - * Uses IF on MySQL. - * - * @param string $cond SQL expression which will result in a boolean value - * @param string $trueVal SQL expression to return if true - * @param string $falseVal SQL expression to return if false - * @return string SQL fragment - */ - function conditional( $cond, $trueVal, $falseVal ) { - return " IF($cond, $trueVal, $falseVal) "; - } - - /** - * Determines if the last failure was due to a deadlock - */ - function wasDeadlock() { - return $this->lastErrno() == 1213; - } - - /** - * Perform a deadlock-prone transaction. - * - * This function invokes a callback function to perform a set of write - * queries. If a deadlock occurs during the processing, the transaction - * will be rolled back and the callback function will be called again. - * - * Usage: - * $dbw->deadlockLoop( callback, ... ); - * - * Extra arguments are passed through to the specified callback function. - * - * Returns whatever the callback function returned on its successful, - * iteration, or false on error, for example if the retry limit was - * reached. - */ - function deadlockLoop() { - $myFname = 'Database::deadlockLoop'; - - $this->begin(); - $args = func_get_args(); - $function = array_shift( $args ); - $oldIgnore = $this->ignoreErrors( true ); - $tries = DEADLOCK_TRIES; - if ( is_array( $function ) ) { - $fname = $function[0]; - } else { - $fname = $function; - } - do { - $retVal = call_user_func_array( $function, $args ); - $error = $this->lastError(); - $errno = $this->lastErrno(); - $sql = $this->lastQuery(); - - if ( $errno ) { - if ( $this->wasDeadlock() ) { - # Retry - usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) ); - } else { - $this->reportQueryError( $error, $errno, $sql, $fname ); - } - } - } while( $this->wasDeadlock() && --$tries > 0 ); - $this->ignoreErrors( $oldIgnore ); - if ( $tries <= 0 ) { - $this->query( 'ROLLBACK', $myFname ); - $this->reportQueryError( $error, $errno, $sql, $fname ); - return false; - } else { - $this->query( 'COMMIT', $myFname ); - return $retVal; - } - } - - /** - * Do a SELECT MASTER_POS_WAIT() - * - * @param string $file the binlog file - * @param string $pos the binlog position - * @param integer $timeout the maximum number of seconds to wait for synchronisation - */ - function masterPosWait( $file, $pos, $timeout ) { - $fname = 'Database::masterPosWait'; - wfProfileIn( $fname ); - - - # Commit any open transactions - $this->immediateCommit(); - - # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set - $encFile = $this->strencode( $file ); - $sql = "SELECT MASTER_POS_WAIT('$encFile', $pos, $timeout)"; - $res = $this->doQuery( $sql ); - if ( $res && $row = $this->fetchRow( $res ) ) { - $this->freeResult( $res ); - wfProfileOut( $fname ); - return $row[0]; - } else { - wfProfileOut( $fname ); - return false; - } - } - - /** - * Get the position of the master from SHOW SLAVE STATUS - */ - function getSlavePos() { - $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' ); - $row = $this->fetchObject( $res ); - if ( $row ) { - return array( $row->Master_Log_File, $row->Read_Master_Log_Pos ); - } else { - return array( false, false ); - } - } - - /** - * Get the position of the master from SHOW MASTER STATUS - */ - function getMasterPos() { - $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' ); - $row = $this->fetchObject( $res ); - if ( $row ) { - return array( $row->File, $row->Position ); - } else { - return array( false, false ); - } - } - - /** - * Begin a transaction, committing any previously open transaction - */ - function begin( $fname = 'Database::begin' ) { - $this->query( 'BEGIN', $fname ); - $this->mTrxLevel = 1; - } - - /** - * End a transaction - */ - function commit( $fname = 'Database::commit' ) { - $this->query( 'COMMIT', $fname ); - $this->mTrxLevel = 0; - } - - /** - * Rollback a transaction. - * No-op on non-transactional databases. - */ - function rollback( $fname = 'Database::rollback' ) { - $this->query( 'ROLLBACK', $fname, true ); - $this->mTrxLevel = 0; - } - - /** - * Begin a transaction, committing any previously open transaction - * @deprecated use begin() - */ - function immediateBegin( $fname = 'Database::immediateBegin' ) { - $this->begin(); - } - - /** - * Commit transaction, if one is open - * @deprecated use commit() - */ - function immediateCommit( $fname = 'Database::immediateCommit' ) { - $this->commit(); - } - - /** - * Return MW-style timestamp used for MySQL schema - */ - function timestamp( $ts=0 ) { - return wfTimestamp(TS_MW,$ts); - } - - /** - * Local database timestamp format or null - */ - function timestampOrNull( $ts = null ) { - if( is_null( $ts ) ) { - return null; - } else { - return $this->timestamp( $ts ); - } - } - - /** - * @todo document - */ - function resultObject( $result ) { - if( empty( $result ) ) { - return false; - } elseif ( $result instanceof ResultWrapper ) { - return $result; - } elseif ( $result === true ) { - // Successful write query - return $result; - } else { - return new ResultWrapper( $this, $result ); - } - } - - /** - * Return aggregated value alias - */ - function aggregateValue ($valuedata,$valuename='value') { - return $valuename; - } - - /** - * @return string wikitext of a link to the server software's web site - */ - function getSoftwareLink() { - return "[http://www.mysql.com/ MySQL]"; - } - - /** - * @return string Version information from the database - */ - function getServerVersion() { - return mysql_get_server_info( $this->mConn ); - } - - /** - * Ping the server and try to reconnect if it there is no connection - */ - function ping() { - if( function_exists( 'mysql_ping' ) ) { - return mysql_ping( $this->mConn ); - } else { - wfDebug( "Tried to call mysql_ping but this is ancient PHP version. Faking it!\n" ); - return true; - } - } - - /** - * Get slave lag. - * At the moment, this will only work if the DB user has the PROCESS privilege - */ - function getLag() { - $res = $this->query( 'SHOW PROCESSLIST' ); - # Find slave SQL thread - while ( $row = $this->fetchObject( $res ) ) { - /* This should work for most situations - when default db - * for thread is not specified, it had no events executed, - * and therefore it doesn't know yet how lagged it is. - * - * Relay log I/O thread does not select databases. - */ - if ( $row->User == 'system user' && - $row->State != 'Waiting for master to send event' && - $row->State != 'Connecting to master' && - $row->State != 'Queueing master event to the relay log' && - $row->State != 'Waiting for master update' && - $row->State != 'Requesting binlog dump' - ) { - # This is it, return the time (except -ve) - if ( $row->Time > 0x7fffffff ) { - return false; - } else { - return $row->Time; - } - } - } - return false; - } - - /** - * Get status information from SHOW STATUS in an associative array - */ - function getStatus($which="%") { - $res = $this->query( "SHOW STATUS LIKE '{$which}'" ); - $status = array(); - while ( $row = $this->fetchObject( $res ) ) { - $status[$row->Variable_name] = $row->Value; - } - return $status; - } - - /** - * Return the maximum number of items allowed in a list, or 0 for unlimited. - */ - function maxListLen() { - return 0; - } - - function encodeBlob($b) { - return $b; - } - - function decodeBlob($b) { - return $b; - } - - /** - * Override database's default connection timeout. - * May be useful for very long batch queries such as - * full-wiki dumps, where a single query reads out - * over hours or days. - * @param int $timeout in seconds - */ - public function setTimeout( $timeout ) { - $this->query( "SET net_read_timeout=$timeout" ); - $this->query( "SET net_write_timeout=$timeout" ); - } - - /** - * Read and execute SQL commands from a file. - * Returns true on success, error string on failure - * @param string $filename File name to open - * @param callback $lineCallback Optional function called before reading each line - * @param callback $resultCallback Optional function called for each MySQL result - */ - function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) { - $fp = fopen( $filename, 'r' ); - if ( false === $fp ) { - return "Could not open \"{$filename}\".\n"; - } - $error = $this->sourceStream( $fp, $lineCallback, $resultCallback ); - fclose( $fp ); - return $error; - } - - /** - * Read and execute commands from an open file handle - * Returns true on success, error string on failure - * @param string $fp File handle - * @param callback $lineCallback Optional function called before reading each line - * @param callback $resultCallback Optional function called for each MySQL result - */ - function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) { - $cmd = ""; - $done = false; - $dollarquote = false; - - while ( ! feof( $fp ) ) { - if ( $lineCallback ) { - call_user_func( $lineCallback ); - } - $line = trim( fgets( $fp, 1024 ) ); - $sl = strlen( $line ) - 1; - - if ( $sl < 0 ) { continue; } - if ( '-' == $line{0} && '-' == $line{1} ) { continue; } - - ## Allow dollar quoting for function declarations - if (substr($line,0,4) == '$mw$') { - if ($dollarquote) { - $dollarquote = false; - $done = true; - } - else { - $dollarquote = true; - } - } - else if (!$dollarquote) { - if ( ';' == $line{$sl} && ($sl < 2 || ';' != $line{$sl - 1})) { - $done = true; - $line = substr( $line, 0, $sl ); - } - } - - if ( '' != $cmd ) { $cmd .= ' '; } - $cmd .= "$line\n"; - - if ( $done ) { - $cmd = str_replace(';;', ";", $cmd); - $cmd = $this->replaceVars( $cmd ); - $res = $this->query( $cmd, __METHOD__, true ); - if ( $resultCallback ) { - call_user_func( $resultCallback, $res ); - } - - if ( false === $res ) { - $err = $this->lastError(); - return "Query \"{$cmd}\" failed with error code \"$err\".\n"; - } - - $cmd = ''; - $done = false; - } - } - return true; - } - - - /** - * Replace variables in sourced SQL - */ - protected function replaceVars( $ins ) { - $varnames = array( - 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser', - 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword', - 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions', - ); - - // Ordinary variables - foreach ( $varnames as $var ) { - if( isset( $GLOBALS[$var] ) ) { - $val = addslashes( $GLOBALS[$var] ); // FIXME: safety check? - $ins = str_replace( '{$' . $var . '}', $val, $ins ); - $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins ); - $ins = str_replace( '/*$' . $var . '*/', $val, $ins ); - } - } - - // Table prefixes - $ins = preg_replace_callback( '/\/\*(?:\$wgDBprefix|_)\*\/([a-z_]*)/', - array( &$this, 'tableNameCallback' ), $ins ); - return $ins; - } - - /** - * Table name callback - * @private - */ - protected function tableNameCallback( $matches ) { - return $this->tableName( $matches[1] ); - } - - /* - * Build a concatenation list to feed into a SQL query - */ - function buildConcat( $stringList ) { - return 'CONCAT(' . implode( ',', $stringList ) . ')'; - } - -} - -/** - * Database abstraction object for mySQL - * Inherit all methods and properties of Database::Database() - * - * @addtogroup Database - * @see Database - */ -class DatabaseMysql extends Database { - # Inherit all -} - - -/** - * Result wrapper for grabbing data queried by someone else - * @addtogroup Database - */ -class ResultWrapper implements Iterator { - var $db, $result, $pos = 0, $currentRow = null; - - /** - * Create a new result object from a result resource and a Database object - */ - function ResultWrapper( $database, $result ) { - $this->db = $database; - if ( $result instanceof ResultWrapper ) { - $this->result = $result->result; - } else { - $this->result = $result; - } - } - - /** - * Get the number of rows in a result object - */ - function numRows() { - return $this->db->numRows( $this->result ); - } - - /** - * Fetch the next row from the given result object, in object form. - * Fields can be retrieved with $row->fieldname, with fields acting like - * member variables. - * - * @param $res SQL result object as returned from Database::query(), etc. - * @return MySQL row object - * @throws DBUnexpectedError Thrown if the database returns an error - */ - function fetchObject() { - return $this->db->fetchObject( $this->result ); - } - - /** - * Fetch the next row from the given result object, in associative array - * form. Fields are retrieved with $row['fieldname']. - * - * @param $res SQL result object as returned from Database::query(), etc. - * @return MySQL row object - * @throws DBUnexpectedError Thrown if the database returns an error - */ - function fetchRow() { - return $this->db->fetchRow( $this->result ); - } - - /** - * Free a result object - */ - function free() { - $this->db->freeResult( $this->result ); - unset( $this->result ); - unset( $this->db ); - } - - /** - * Change the position of the cursor in a result object - * See mysql_data_seek() - */ - function seek( $row ) { - $this->db->dataSeek( $this->result, $row ); - } - - /********************* - * Iterator functions - * Note that using these in combination with the non-iterator functions - * above may cause rows to be skipped or repeated. - */ - - function rewind() { - if ($this->numRows()) { - $this->db->dataSeek($this->result, 0); - } - $this->pos = 0; - $this->currentRow = null; - } - - function current() { - if ( is_null( $this->currentRow ) ) { - $this->next(); - } - return $this->currentRow; - } - - function key() { - return $this->pos; - } - - function next() { - $this->pos++; - $this->currentRow = $this->fetchObject(); - return $this->currentRow; - } - - function valid() { - return $this->current() !== false; - } -} - - diff --git a/includes/DatabaseMysql.php b/includes/DatabaseMysql.php deleted file mode 100644 index 79e917b3..00000000 --- a/includes/DatabaseMysql.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php -/* - * Stub database class for MySQL. - */ -require_once('Database.php'); -?> diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php deleted file mode 100644 index 38485481..00000000 --- a/includes/DatabaseOracle.php +++ /dev/null @@ -1,697 +0,0 @@ -<?php - -/** - * This is the Oracle database abstraction layer. - * @addtogroup Database - */ -class ORABlob { - var $mData; - - function __construct($data) { - $this->mData = $data; - } - - function getData() { - return $this->mData; - } -} - -/** - * The oci8 extension is fairly weak and doesn't support oci_num_rows, among - * other things. We use a wrapper class to handle that and other - * Oracle-specific bits, like converting column names back to lowercase. - * @addtogroup Database - */ -class ORAResult { - private $rows; - private $cursor; - private $stmt; - private $nrows; - private $db; - - function __construct(&$db, $stmt) { - $this->db =& $db; - if (($this->nrows = oci_fetch_all($stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM)) === false) { - $e = oci_error($stmt); - $db->reportQueryError($e['message'], $e['code'], '', __FUNCTION__); - return; - } - - $this->cursor = 0; - $this->stmt = $stmt; - } - - function free() { - oci_free_statement($this->stmt); - } - - function seek($row) { - $this->cursor = min($row, $this->nrows); - } - - function numRows() { - return $this->nrows; - } - - function numFields() { - return oci_num_fields($this->stmt); - } - - function fetchObject() { - if ($this->cursor >= $this->nrows) - return false; - - $row = $this->rows[$this->cursor++]; - $ret = new stdClass(); - foreach ($row as $k => $v) { - $lc = strtolower(oci_field_name($this->stmt, $k + 1)); - $ret->$lc = $v; - } - - return $ret; - } - - function fetchAssoc() { - if ($this->cursor >= $this->nrows) - return false; - - $row = $this->rows[$this->cursor++]; - $ret = array(); - foreach ($row as $k => $v) { - $lc = strtolower(oci_field_name($this->stmt, $k + 1)); - $ret[$lc] = $v; - $ret[$k] = $v; - } - return $ret; - } -} - -/** - * @addtogroup Database - */ -class DatabaseOracle extends Database { - var $mInsertId = NULL; - var $mLastResult = NULL; - var $numeric_version = NULL; - var $lastResult = null; - var $cursor = 0; - var $mAffectedRows; - - function DatabaseOracle($server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0 ) - { - - global $wgOut; - # Can't get a reference if it hasn't been set yet - if ( !isset( $wgOut ) ) { - $wgOut = NULL; - } - $this->mOut =& $wgOut; - $this->mFailFunction = $failFunction; - $this->mFlags = $flags; - $this->open( $server, $user, $password, $dbName); - - } - - function cascadingDeletes() { - return true; - } - function cleanupTriggers() { - return true; - } - function strictIPs() { - return true; - } - function realTimestamps() { - return true; - } - function implicitGroupby() { - return false; - } - function implicitOrderby() { - return false; - } - function searchableIPs() { - return true; - } - - static function newFromParams( $server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0) - { - return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags ); - } - - /** - * Usually aborts on failure - * If the failFunction is set to a non-zero integer, returns success - */ - function open( $server, $user, $password, $dbName ) { - if ( !function_exists( 'oci_connect' ) ) { - throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" ); - } - - # Needed for proper UTF-8 functionality - putenv("NLS_LANG=AMERICAN_AMERICA.AL32UTF8"); - - $this->close(); - $this->mServer = $server; - $this->mUser = $user; - $this->mPassword = $password; - $this->mDBname = $dbName; - - if (!strlen($user)) { ## e.g. the class is being loaded - return; - } - - error_reporting( E_ALL ); - $this->mConn = oci_connect($user, $password, $dbName); - - if ($this->mConn == false) { - wfDebug("DB connection error\n"); - wfDebug("Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n"); - wfDebug($this->lastError()."\n"); - return false; - } - - $this->mOpened = true; - return $this->mConn; - } - - /** - * Closes a database connection, if it is open - * Returns success, true if already closed - */ - function close() { - $this->mOpened = false; - if ( $this->mConn ) { - return oci_close( $this->mConn ); - } else { - return true; - } - } - - function execFlags() { - return $this->mTrxLevel ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; - } - - function doQuery($sql) { - wfDebug("SQL: [$sql]\n"); - if (!mb_check_encoding($sql)) { - throw new MWException("SQL encoding is invalid"); - } - - if (($this->mLastResult = $stmt = oci_parse($this->mConn, $sql)) === false) { - $e = oci_error($this->mConn); - $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__); - } - - if (oci_execute($stmt, $this->execFlags()) == false) { - $e = oci_error($stmt); - $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__); - } - if (oci_statement_type($stmt) == "SELECT") - return new ORAResult($this, $stmt); - else { - $this->mAffectedRows = oci_num_rows($stmt); - return true; - } - } - - function queryIgnore($sql, $fname = '') { - return $this->query($sql, $fname, true); - } - - function freeResult($res) { - $res->free(); - } - - function fetchObject($res) { - return $res->fetchObject(); - } - - function fetchRow($res) { - return $res->fetchAssoc(); - } - - function numRows($res) { - return $res->numRows(); - } - - function numFields($res) { - return $res->numFields(); - } - - function fieldName($stmt, $n) { - return pg_field_name($stmt, $n); - } - - /** - * This must be called after nextSequenceVal - */ - function insertId() { - return $this->mInsertId; - } - - function dataSeek($res, $row) { - $res->seek($row); - } - - function lastError() { - if ($this->mConn === false) - $e = oci_error(); - else - $e = oci_error($this->mConn); - return $e['message']; - } - - function lastErrno() { - if ($this->mConn === false) - $e = oci_error(); - else - $e = oci_error($this->mConn); - return $e['code']; - } - - function affectedRows() { - return $this->mAffectedRows; - } - - /** - * Returns information about an index - * If errors are explicitly ignored, returns NULL on failure - */ - function indexInfo( $table, $index, $fname = 'Database::indexExists' ) { - return false; - } - - function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { - return false; - } - - function insert( $table, $a, $fname = 'Database::insert', $options = array() ) { - if (!is_array($options)) - $options = array($options); - - #if (in_array('IGNORE', $options)) - # $oldIgnore = $this->ignoreErrors(true); - - # IGNORE is performed using single-row inserts, ignoring errors in each - # FIXME: need some way to distiguish between key collision and other types of error - //$oldIgnore = $this->ignoreErrors(true); - if (!is_array(reset($a))) { - $a = array($a); - } - foreach ($a as $row) { - $this->insertOneRow($table, $row, $fname); - } - //$this->ignoreErrors($oldIgnore); - $retVal = true; - - //if (in_array('IGNORE', $options)) - // $this->ignoreErrors($oldIgnore); - - return $retVal; - } - - function insertOneRow($table, $row, $fname) { - // "INSERT INTO tables (a, b, c)" - $sql = "INSERT INTO " . $this->tableName($table) . " (" . join(',', array_keys($row)) . ')'; - $sql .= " VALUES ("; - - // for each value, append ":key" - $first = true; - $returning = ''; - foreach ($row as $col => $val) { - if (is_object($val)) { - $what = "EMPTY_BLOB()"; - assert($returning === ''); - $returning = " RETURNING $col INTO :bval"; - $blobcol = $col; - } else - $what = ":$col"; - - if ($first) - $sql .= "$what"; - else - $sql.= ", $what"; - $first = false; - } - $sql .= ") $returning"; - - $stmt = oci_parse($this->mConn, $sql); - foreach ($row as $col => $val) { - if (!is_object($val)) { - if (oci_bind_by_name($stmt, ":$col", $row[$col]) === false) - $this->reportQueryError($this->lastErrno(), $this->lastError(), $sql, __METHOD__); - } - } - - if (($bval = oci_new_descriptor($this->mConn, OCI_D_LOB)) === false) { - $e = oci_error($stmt); - throw new DBUnexpectedError($this, "Cannot create LOB descriptor: " . $e['message']); - } - - if (strlen($returning)) - oci_bind_by_name($stmt, ":bval", $bval, -1, SQLT_BLOB); - - if (oci_execute($stmt, OCI_DEFAULT) === false) { - $e = oci_error($stmt); - $this->reportQueryError($e['message'], $e['code'], $sql, __METHOD__); - } - if (strlen($returning)) { - $bval->save($row[$blobcol]->getData()); - $bval->free(); - } - if (!$this->mTrxLevel) - oci_commit($this->mConn); - - oci_free_statement($stmt); - } - - function tableName( $name ) { - # Replace reserved words with better ones - switch( $name ) { - case 'user': - return 'mwuser'; - case 'text': - return 'pagecontent'; - default: - return $name; - } - } - - /** - * Return the next in a sequence, save the value for retrieval via insertId() - */ - function nextSequenceValue($seqName) { - $res = $this->query("SELECT $seqName.nextval FROM dual"); - $row = $this->fetchRow($res); - $this->mInsertId = $row[0]; - $this->freeResult($res); - return $this->mInsertId; - } - - /** - * Oracle does not have a "USE INDEX" clause, so return an empty string - */ - function useIndexClause($index) { - return ''; - } - - # REPLACE query wrapper - # Oracle simulates this with a DELETE followed by INSERT - # $row is the row to insert, an associative array - # $uniqueIndexes is an array of indexes. Each element may be either a - # field name or an array of field names - # - # It may be more efficient to leave off unique indexes which are unlikely to collide. - # However if you do this, you run the risk of encountering errors which wouldn't have - # occurred in MySQL - function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) { - $table = $this->tableName($table); - - if (count($rows)==0) { - return; - } - - # Single row case - if (!is_array(reset($rows))) { - $rows = array($rows); - } - - foreach( $rows as $row ) { - # Delete rows which collide - if ( $uniqueIndexes ) { - $sql = "DELETE FROM $table WHERE "; - $first = true; - foreach ( $uniqueIndexes as $index ) { - if ( $first ) { - $first = false; - $sql .= "("; - } else { - $sql .= ') OR ('; - } - if ( is_array( $index ) ) { - $first2 = true; - foreach ( $index as $col ) { - if ( $first2 ) { - $first2 = false; - } else { - $sql .= ' AND '; - } - $sql .= $col.'=' . $this->addQuotes( $row[$col] ); - } - } else { - $sql .= $index.'=' . $this->addQuotes( $row[$index] ); - } - } - $sql .= ')'; - $this->query( $sql, $fname ); - } - - # Now insert the row - $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' . - $this->makeList( $row, LIST_COMMA ) . ')'; - $this->query($sql, $fname); - } - } - - # DELETE where the condition is a join - function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) { - if ( !$conds ) { - throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); - } - - $delTable = $this->tableName( $delTable ); - $joinTable = $this->tableName( $joinTable ); - $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; - if ( $conds != '*' ) { - $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); - } - $sql .= ')'; - - $this->query( $sql, $fname ); - } - - # Returns the size of a text field, or -1 for "unlimited" - function textFieldSize( $table, $field ) { - $table = $this->tableName( $table ); - $sql = "SELECT t.typname as ftype,a.atttypmod as size - FROM pg_class c, pg_attribute a, pg_type t - WHERE relname='$table' AND a.attrelid=c.oid AND - a.atttypid=t.oid and a.attname='$field'"; - $res =$this->query($sql); - $row=$this->fetchObject($res); - if ($row->ftype=="varchar") { - $size=$row->size-4; - } else { - $size=$row->size; - } - $this->freeResult( $res ); - return $size; - } - - function lowPriorityOption() { - return ''; - } - - function limitResult($sql, $limit, $offset) { - if ($offset === false) - $offset = 0; - return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < 1 + $limit + $offset"; - } - - /** - * Returns an SQL expression for a simple conditional. - * Uses CASE on Oracle - * - * @param string $cond SQL expression which will result in a boolean value - * @param string $trueVal SQL expression to return if true - * @param string $falseVal SQL expression to return if false - * @return string SQL fragment - */ - function conditional( $cond, $trueVal, $falseVal ) { - return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; - } - - function wasDeadlock() { - return $this->lastErrno() == 'OCI-00060'; - } - - function timestamp($ts = 0) { - return wfTimestamp(TS_ORACLE, $ts); - } - - /** - * Return aggregated value function call - */ - function aggregateValue ($valuedata,$valuename='value') { - return $valuedata; - } - - function reportQueryError($error, $errno, $sql, $fname, $tempIgnore = false) { - # Ignore errors during error handling to avoid infinite - # recursion - $ignore = $this->ignoreErrors(true); - ++$this->mErrorCount; - - if ($ignore || $tempIgnore) { -echo "error ignored! query = [$sql]\n"; - wfDebug("SQL ERROR (ignored): $error\n"); - $this->ignoreErrors( $ignore ); - } - else { -echo "error!\n"; - $message = "A database error has occurred\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - throw new DBUnexpectedError($this, $message); - } - } - - /** - * @return string wikitext of a link to the server software's web site - */ - function getSoftwareLink() { - return "[http://www.oracle.com/ Oracle]"; - } - - /** - * @return string Version information from the database - */ - function getServerVersion() { - return oci_server_version($this->mConn); - } - - /** - * Query whether a given table exists (in the given schema, or the default mw one if not given) - */ - function tableExists($table) { - $etable= $this->addQuotes($table); - $SQL = "SELECT 1 FROM user_tables WHERE table_name='$etable'"; - $res = $this->query($SQL); - $count = $res ? oci_num_rows($res) : 0; - if ($res) - $this->freeResult($res); - return $count; - } - - /** - * Query whether a given column exists in the mediawiki schema - */ - function fieldExists( $table, $field ) { - return true; // XXX - } - - function fieldInfo( $table, $field ) { - return false; // XXX - } - - function begin( $fname = '' ) { - $this->mTrxLevel = 1; - } - function immediateCommit( $fname = '' ) { - return true; - } - function commit( $fname = '' ) { - oci_commit($this->mConn); - $this->mTrxLevel = 0; - } - - /* Not even sure why this is used in the main codebase... */ - function limitResultForUpdate($sql, $num) { - return $sql; - } - - function strencode($s) { - return str_replace("'", "''", $s); - } - - function encodeBlob($b) { - return new ORABlob($b); - } - function decodeBlob($b) { - return $b; //return $b->load(); - } - - function addQuotes( $s ) { - global $wgLang; - $s = $wgLang->checkTitleEncoding($s); - return "'" . $this->strencode($s) . "'"; - } - - function quote_ident( $s ) { - return $s; - } - - /* For now, does nothing */ - function selectDB( $db ) { - return true; - } - - /** - * Returns an optional USE INDEX clause to go after the table, and a - * string to go at the end of the query - * - * @private - * - * @param array $options an associative array of options to be turned into - * an SQL query, valid keys are listed in the function. - * @return array - */ - function makeSelectOptions( $options ) { - $preLimitTail = $postLimitTail = ''; - $startOpts = ''; - - $noKeyOptions = array(); - foreach ( $options as $key => $option ) { - if ( is_numeric( $key ) ) { - $noKeyOptions[$option] = true; - } - } - - if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; - if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; - - if (isset($options['LIMIT'])) { - // $tailOpts .= $this->limitResult('', $options['LIMIT'], - // isset($options['OFFSET']) ? $options['OFFSET'] - // : false); - } - - #if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; - #if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; - if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; - - if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { - $useIndex = $this->useIndexClause( $options['USE INDEX'] ); - } else { - $useIndex = ''; - } - - return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); - } - - public function setTimeout( $timeout ) { - // @todo fixme no-op - } - - function ping() { - wfDebug( "Function ping() not written for DatabaseOracle.php yet"); - return true; - } - - /** - * How lagged is this slave? - * - * @return int - */ - public function getLag() { - # Not implemented for Oracle - return 0; - } - -} // end DatabaseOracle class - - diff --git a/includes/DatabasePostgres.php b/includes/DatabasePostgres.php deleted file mode 100644 index 01213715..00000000 --- a/includes/DatabasePostgres.php +++ /dev/null @@ -1,1313 +0,0 @@ -<?php - -/** - * This is the Postgres database abstraction layer. - * - * As it includes more generic version for DB functions, - * than MySQL ones, some of them should be moved to parent - * Database class. - * - * @addtogroup Database - */ -class PostgresField { - private $name, $tablename, $type, $nullable, $max_length; - - static function fromText($db, $table, $field) { - global $wgDBmwschema; - - $q = <<<END -SELECT -CASE WHEN typname = 'int2' THEN 'smallint' -WHEN typname = 'int4' THEN 'integer' -WHEN typname = 'int8' THEN 'bigint' -WHEN typname = 'bpchar' THEN 'char' -ELSE typname END AS typname, -attnotnull, attlen -FROM pg_class, pg_namespace, pg_attribute, pg_type -WHERE relnamespace=pg_namespace.oid -AND relkind='r' -AND attrelid=pg_class.oid -AND atttypid=pg_type.oid -AND nspname=%s -AND relname=%s -AND attname=%s; -END; - $res = $db->query(sprintf($q, - $db->addQuotes($wgDBmwschema), - $db->addQuotes($table), - $db->addQuotes($field))); - $row = $db->fetchObject($res); - if (!$row) - return null; - $n = new PostgresField; - $n->type = $row->typname; - $n->nullable = ($row->attnotnull == 'f'); - $n->name = $field; - $n->tablename = $table; - $n->max_length = $row->attlen; - return $n; - } - - function name() { - return $this->name; - } - - function tableName() { - return $this->tablename; - } - - function type() { - return $this->type; - } - - function nullable() { - return $this->nullable; - } - - function maxLength() { - return $this->max_length; - } -} - -/** - * @addtogroup Database - */ -class DatabasePostgres extends Database { - var $mInsertId = NULL; - var $mLastResult = NULL; - var $numeric_version = NULL; - - function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0 ) - { - - global $wgOut; - # Can't get a reference if it hasn't been set yet - if ( !isset( $wgOut ) ) { - $wgOut = NULL; - } - $this->mOut =& $wgOut; - $this->mFailFunction = $failFunction; - $this->mFlags = $flags; - $this->open( $server, $user, $password, $dbName); - - } - - function cascadingDeletes() { - return true; - } - function cleanupTriggers() { - return true; - } - function strictIPs() { - return true; - } - function realTimestamps() { - return true; - } - function implicitGroupby() { - return false; - } - function implicitOrderby() { - return false; - } - function searchableIPs() { - return true; - } - function functionalIndexes() { - return true; - } - - function hasConstraint( $name ) { - global $wgDBmwschema; - $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" . pg_escape_string( $name ) . "' AND n.nspname = '" . pg_escape_string($wgDBmwschema) ."'"; - return $this->numRows($res = $this->doQuery($SQL)); - } - - static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0) - { - return new DatabasePostgres( $server, $user, $password, $dbName, $failFunction, $flags ); - } - - /** - * Usually aborts on failure - * If the failFunction is set to a non-zero integer, returns success - */ - function open( $server, $user, $password, $dbName ) { - # Test for Postgres support, to avoid suppressed fatal error - if ( !function_exists( 'pg_connect' ) ) { - throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" ); - } - - global $wgDBport; - - if (!strlen($user)) { ## e.g. the class is being loaded - return; - } - - $this->close(); - $this->mServer = $server; - $this->mPort = $port = $wgDBport; - $this->mUser = $user; - $this->mPassword = $password; - $this->mDBname = $dbName; - - $hstring=""; - if ($server!=false && $server!="") { - $hstring="host=$server "; - } - if ($port!=false && $port!="") { - $hstring .= "port=$port "; - } - - error_reporting( E_ALL ); - @$this->mConn = pg_connect("$hstring dbname=$dbName user=$user password=$password"); - - if ( $this->mConn == false ) { - wfDebug( "DB connection error\n" ); - wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); - wfDebug( $this->lastError()."\n" ); - return false; - } - - $this->mOpened = true; - - global $wgCommandLineMode; - ## If called from the command-line (e.g. importDump), only show errors - if ($wgCommandLineMode) { - $this->doQuery("SET client_min_messages = 'ERROR'"); - } - - global $wgDBmwschema, $wgDBts2schema; - if (isset( $wgDBmwschema ) && isset( $wgDBts2schema ) - && $wgDBmwschema !== 'mediawiki' - && preg_match( '/^\w+$/', $wgDBmwschema ) - && preg_match( '/^\w+$/', $wgDBts2schema ) - ) { - $safeschema = $this->quote_ident($wgDBmwschema); - $safeschema2 = $this->quote_ident($wgDBts2schema); - $this->doQuery("SET search_path = $safeschema, $wgDBts2schema, public"); - } - - return $this->mConn; - } - - - function initial_setup($password, $dbName) { - // If this is the initial connection, setup the schema stuff and possibly create the user - global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema, $wgDBts2schema; - - print "<li>Checking the version of Postgres..."; - $version = $this->getServerVersion(); - $PGMINVER = '8.1'; - if ($this->numeric_version < $PGMINVER) { - print "<b>FAILED</b>. Required version is $PGMINVER. You have $this->numeric_version ($version)</li>\n"; - dieout("</ul>"); - } - print "version $this->numeric_version is OK.</li>\n"; - - $safeuser = $this->quote_ident($wgDBuser); - // Are we connecting as a superuser for the first time? - if ($wgDBsuperuser) { - // Are we really a superuser? Check out our rights - $SQL = "SELECT - CASE WHEN usesuper IS TRUE THEN - CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END - ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END - END AS rights - FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBsuperuser); - $rows = $this->numRows($res = $this->doQuery($SQL)); - if (!$rows) { - print "<li>ERROR: Could not read permissions for user \"$wgDBsuperuser\"</li>\n"; - dieout('</ul>'); - } - $perms = pg_fetch_result($res, 0, 0); - - $SQL = "SELECT 1 FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBuser); - $rows = $this->numRows($this->doQuery($SQL)); - if ($rows) { - print "<li>User \"$wgDBuser\" already exists, skipping account creation.</li>"; - } - else { - if ($perms != 1 and $perms != 3) { - print "<li>ERROR: the user \"$wgDBsuperuser\" cannot create other users. "; - print 'Please use a different Postgres user.</li>'; - dieout('</ul>'); - } - print "<li>Creating user <b>$wgDBuser</b>..."; - $safepass = $this->addQuotes($wgDBpassword); - $SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass"; - $this->doQuery($SQL); - print "OK</li>\n"; - } - // User now exists, check out the database - if ($dbName != $wgDBname) { - $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $this->addQuotes($wgDBname); - $rows = $this->numRows($this->doQuery($SQL)); - if ($rows) { - print "<li>Database \"$wgDBname\" already exists, skipping database creation.</li>"; - } - else { - if ($perms < 2) { - print "<li>ERROR: the user \"$wgDBsuperuser\" cannot create databases. "; - print 'Please use a different Postgres user.</li>'; - dieout('</ul>'); - } - print "<li>Creating database <b>$wgDBname</b>..."; - $safename = $this->quote_ident($wgDBname); - $SQL = "CREATE DATABASE $safename OWNER $safeuser "; - $this->doQuery($SQL); - print "OK</li>\n"; - // Hopefully tsearch2 and plpgsql are in template1... - } - - // Reconnect to check out tsearch2 rights for this user - print "<li>Connecting to \"$wgDBname\" as superuser \"$wgDBsuperuser\" to check rights..."; - - $hstring=""; - if ($this->mServer!=false && $this->mServer!="") { - $hstring="host=$this->mServer "; - } - if ($this->mPort!=false && $this->mPort!="") { - $hstring .= "port=$this->mPort "; - } - - @$this->mConn = pg_connect("$hstring dbname=$wgDBname user=$wgDBsuperuser password=$password"); - if ( $this->mConn == false ) { - print "<b>FAILED TO CONNECT!</b></li>"; - dieout("</ul>"); - } - print "OK</li>\n"; - } - - if ($this->numeric_version < 8.3) { - // Tsearch2 checks - print "<li>Checking that tsearch2 is installed in the database \"$wgDBname\"..."; - if (! $this->tableExists("pg_ts_cfg", $wgDBts2schema)) { - print "<b>FAILED</b>. tsearch2 must be installed in the database \"$wgDBname\"."; - print "Please see <a href='http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>"; - print " for instructions or ask on #postgresql on irc.freenode.net</li>\n"; - dieout("</ul>"); - } - print "OK</li>\n"; - print "<li>Ensuring that user \"$wgDBuser\" has select rights on the tsearch2 tables..."; - foreach (array('cfg','cfgmap','dict','parser') as $table) { - $SQL = "GRANT SELECT ON pg_ts_$table TO $safeuser"; - $this->doQuery($SQL); - } - print "OK</li>\n"; - } - - // Setup the schema for this user if needed - $result = $this->schemaExists($wgDBmwschema); - $safeschema = $this->quote_ident($wgDBmwschema); - if (!$result) { - print "<li>Creating schema <b>$wgDBmwschema</b> ..."; - $result = $this->doQuery("CREATE SCHEMA $safeschema AUTHORIZATION $safeuser"); - if (!$result) { - print "<b>FAILED</b>.</li>\n"; - dieout("</ul>"); - } - print "OK</li>\n"; - } - else { - print "<li>Schema already exists, explicitly granting rights...\n"; - $safeschema2 = $this->addQuotes($wgDBmwschema); - $SQL = "SELECT 'GRANT ALL ON '||pg_catalog.quote_ident(relname)||' TO $safeuser;'\n". - "FROM pg_catalog.pg_class p, pg_catalog.pg_namespace n\n". - "WHERE relnamespace = n.oid AND n.nspname = $safeschema2\n". - "AND p.relkind IN ('r','S','v')\n"; - $SQL .= "UNION\n"; - $SQL .= "SELECT 'GRANT ALL ON FUNCTION '||pg_catalog.quote_ident(proname)||'('||\n". - "pg_catalog.oidvectortypes(p.proargtypes)||') TO $safeuser;'\n". - "FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n\n". - "WHERE p.pronamespace = n.oid AND n.nspname = $safeschema2"; - $res = $this->doQuery($SQL); - if (!$res) { - print "<b>FAILED</b>. Could not set rights for the user.</li>\n"; - dieout("</ul>"); - } - $this->doQuery("SET search_path = $safeschema"); - $rows = $this->numRows($res); - while ($rows) { - $rows--; - $this->doQuery(pg_fetch_result($res, $rows, 0)); - } - print "OK</li>"; - } - - // Install plpgsql if needed - $this->setup_plpgsql(); - - $wgDBsuperuser = ''; - return true; // Reconnect as regular user - - } // end superuser - - if (!defined('POSTGRES_SEARCHPATH')) { - - if ($this->numeric_version < 8.3) { - // Do we have the basic tsearch2 table? - print "<li>Checking for tsearch2 in the schema \"$wgDBts2schema\"..."; - if (! $this->tableExists("pg_ts_dict", $wgDBts2schema)) { - print "<b>FAILED</b>. Make sure tsearch2 is installed. See <a href="; - print "'http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>"; - print " for instructions.</li>\n"; - dieout("</ul>"); - } - print "OK</li>\n"; - - // Does this user have the rights to the tsearch2 tables? - $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0); - print "<li>Checking tsearch2 permissions..."; - // Let's check all four, just to be safe - error_reporting( 0 ); - $ts2tables = array('cfg','cfgmap','dict','parser'); - $safetsschema = $this->quote_ident($wgDBts2schema); - foreach ( $ts2tables AS $tname ) { - $SQL = "SELECT count(*) FROM $safetsschema.pg_ts_$tname"; - $res = $this->doQuery($SQL); - if (!$res) { - print "<b>FAILED</b> to access pg_ts_$tname. Make sure that the user ". - "\"$wgDBuser\" has SELECT access to all four tsearch2 tables</li>\n"; - dieout("</ul>"); - } - } - $SQL = "SELECT ts_name FROM $safetsschema.pg_ts_cfg WHERE locale = '$ctype'"; - $SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END"; - $res = $this->doQuery($SQL); - error_reporting( E_ALL ); - if (!$res) { - print "<b>FAILED</b>. Could not determine the tsearch2 locale information</li>\n"; - dieout("</ul>"); - } - print "OK</li>"; - - // Will the current locale work? Can we force it to? - print "<li>Verifying tsearch2 locale with $ctype..."; - $rows = $this->numRows($res); - $resetlocale = 0; - if (!$rows) { - print "<b>not found</b></li>\n"; - print "<li>Attempting to set default tsearch2 locale to \"$ctype\"..."; - $resetlocale = 1; - } - else { - $tsname = pg_fetch_result($res, 0, 0); - if ($tsname != 'default') { - print "<b>not set to default ($tsname)</b>"; - print "<li>Attempting to change tsearch2 default locale to \"$ctype\"..."; - $resetlocale = 1; - } - } - if ($resetlocale) { - $SQL = "UPDATE $safetsschema.pg_ts_cfg SET locale = '$ctype' WHERE ts_name = 'default'"; - $res = $this->doQuery($SQL); - if (!$res) { - print "<b>FAILED</b>. "; - print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"$ctype\"</li>\n"; - dieout("</ul>"); - } - print "OK</li>"; - } - - // Final test: try out a simple tsearch2 query - $SQL = "SELECT $safetsschema.to_tsvector('default','MediaWiki tsearch2 testing')"; - $res = $this->doQuery($SQL); - if (!$res) { - print "<b>FAILED</b>. Specifically, \"$SQL\" did not work.</li>"; - dieout("</ul>"); - } - print "OK</li>"; - } - - // Install plpgsql if needed - $this->setup_plpgsql(); - - // Does the schema already exist? Who owns it? - $result = $this->schemaExists($wgDBmwschema); - if (!$result) { - print "<li>Creating schema <b>$wgDBmwschema</b> ..."; - error_reporting( 0 ); - $safeschema = $this->quote_ident($wgDBmwschema); - $result = $this->doQuery("CREATE SCHEMA $safeschema"); - error_reporting( E_ALL ); - if (!$result) { - print "<b>FAILED</b>. The user \"$wgDBuser\" must be able to access the schema. ". - "You can try making them the owner of the database, or try creating the schema with a ". - "different user, and then grant access to the \"$wgDBuser\" user.</li>\n"; - dieout("</ul>"); - } - print "OK</li>\n"; - } - else if ($result != $wgDBuser) { - print "<li>Schema \"$wgDBmwschema\" exists but is not owned by \"$wgDBuser\". Not ideal.</li>\n"; - } - else { - print "<li>Schema \"$wgDBmwschema\" exists and is owned by \"$wgDBuser\". Excellent.</li>\n"; - } - - // Always return GMT time to accomodate the existing integer-based timestamp assumption - print "<li>Setting the timezone to GMT for user \"$wgDBuser\" ..."; - $SQL = "ALTER USER $safeuser SET timezone = 'GMT'"; - $result = pg_query($this->mConn, $SQL); - if (!$result) { - print "<b>FAILED</b>.</li>\n"; - dieout("</ul>"); - } - print "OK</li>\n"; - // Set for the rest of this session - $SQL = "SET timezone = 'GMT'"; - $result = pg_query($this->mConn, $SQL); - if (!$result) { - print "<li>Failed to set timezone</li>\n"; - dieout("</ul>"); - } - - print "<li>Setting the datestyle to ISO, YMD for user \"$wgDBuser\" ..."; - $SQL = "ALTER USER $safeuser SET datestyle = 'ISO, YMD'"; - $result = pg_query($this->mConn, $SQL); - if (!$result) { - print "<b>FAILED</b>.</li>\n"; - dieout("</ul>"); - } - print "OK</li>\n"; - // Set for the rest of this session - $SQL = "SET datestyle = 'ISO, YMD'"; - $result = pg_query($this->mConn, $SQL); - if (!$result) { - print "<li>Failed to set datestyle</li>\n"; - dieout("</ul>"); - } - - // Fix up the search paths if needed - print "<li>Setting the search path for user \"$wgDBuser\" ..."; - $path = $this->quote_ident($wgDBmwschema); - if ($wgDBts2schema !== $wgDBmwschema) - $path .= ", ". $this->quote_ident($wgDBts2schema); - if ($wgDBmwschema !== 'public' and $wgDBts2schema !== 'public') - $path .= ", public"; - $SQL = "ALTER USER $safeuser SET search_path = $path"; - $result = pg_query($this->mConn, $SQL); - if (!$result) { - print "<b>FAILED</b>.</li>\n"; - dieout("</ul>"); - } - print "OK</li>\n"; - // Set for the rest of this session - $SQL = "SET search_path = $path"; - $result = pg_query($this->mConn, $SQL); - if (!$result) { - print "<li>Failed to set search_path</li>\n"; - dieout("</ul>"); - } - define( "POSTGRES_SEARCHPATH", $path ); - } - } - - - function setup_plpgsql() { - print "<li>Checking for Pl/Pgsql ..."; - $SQL = "SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql'"; - $rows = $this->numRows($this->doQuery($SQL)); - if ($rows < 1) { - // plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it - print "not installed. Attempting to install Pl/Pgsql ..."; - $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ". - "WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog'"; - $rows = $this->numRows($this->doQuery($SQL)); - if ($rows >= 1) { - $olde = error_reporting(0); - error_reporting($olde - E_WARNING); - $result = $this->doQuery("CREATE LANGUAGE plpgsql"); - error_reporting($olde); - if (!$result) { - print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>$wgDBname</tt></li>"; - dieout("</ul>"); - } - } - else { - print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>$wgDBname</tt></li>"; - dieout("</ul>"); - } - } - print "OK</li>\n"; - } - - - /** - * Closes a database connection, if it is open - * Returns success, true if already closed - */ - function close() { - $this->mOpened = false; - if ( $this->mConn ) { - return pg_close( $this->mConn ); - } else { - return true; - } - } - - function doQuery( $sql ) { - if (function_exists('mb_convert_encoding')) { - return $this->mLastResult=pg_query( $this->mConn , mb_convert_encoding($sql,'UTF-8') ); - } - return $this->mLastResult=pg_query( $this->mConn , $sql); - } - - function queryIgnore( $sql, $fname = '' ) { - return $this->query( $sql, $fname, true ); - } - - function freeResult( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - if ( !@pg_free_result( $res ) ) { - throw new DBUnexpectedError($this, "Unable to free Postgres result\n" ); - } - } - - function fetchObject( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - @$row = pg_fetch_object( $res ); - # FIXME: HACK HACK HACK HACK debug - - # TODO: - # hashar : not sure if the following test really trigger if the object - # fetching failed. - if( pg_last_error($this->mConn) ) { - throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); - } - return $row; - } - - function fetchRow( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - @$row = pg_fetch_array( $res ); - if( pg_last_error($this->mConn) ) { - throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); - } - return $row; - } - - function numRows( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - @$n = pg_num_rows( $res ); - if( pg_last_error($this->mConn) ) { - throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) ); - } - return $n; - } - function numFields( $res ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - return pg_num_fields( $res ); - } - function fieldName( $res, $n ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - return pg_field_name( $res, $n ); - } - - /** - * This must be called after nextSequenceVal - */ - function insertId() { - return $this->mInsertId; - } - - function dataSeek( $res, $row ) { - if ( $res instanceof ResultWrapper ) { - $res = $res->result; - } - return pg_result_seek( $res, $row ); - } - - function lastError() { - if ( $this->mConn ) { - return pg_last_error(); - } - else { - return "No database connection"; - } - } - function lastErrno() { - return pg_last_error() ? 1 : 0; - } - - function affectedRows() { - if( !isset( $this->mLastResult ) or ! $this->mLastResult ) - return 0; - - return pg_affected_rows( $this->mLastResult ); - } - - /** - * Estimate rows in dataset - * Returns estimated count, based on EXPLAIN output - * This is not necessarily an accurate estimate, so use sparingly - * Returns -1 if count cannot be found - * Takes same arguments as Database::select() - */ - - function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { - $options['EXPLAIN'] = true; - $res = $this->select( $table, $vars, $conds, $fname, $options ); - $rows = -1; - if ( $res ) { - $row = $this->fetchRow( $res ); - $count = array(); - if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) { - $rows = $count[1]; - } - $this->freeResult($res); - } - return $rows; - } - - - /** - * Returns information about an index - * If errors are explicitly ignored, returns NULL on failure - */ - function indexInfo( $table, $index, $fname = 'Database::indexExists' ) { - $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'"; - $res = $this->query( $sql, $fname ); - if ( !$res ) { - return NULL; - } - while ( $row = $this->fetchObject( $res ) ) { - if ( $row->indexname == $index ) { - return $row; - } - } - return false; - } - - function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { - $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'". - " AND indexdef LIKE 'CREATE UNIQUE%({$index})'"; - $res = $this->query( $sql, $fname ); - if ( !$res ) - return NULL; - while ($row = $this->fetchObject( $res )) - return true; - return false; - - } - - /** - * INSERT wrapper, inserts an array into a table - * - * $args may be a single associative array, or an array of these with numeric keys, - * for multi-row insert (Postgres version 8.2 and above only). - * - * @param array $table String: Name of the table to insert to. - * @param array $args Array: Items to insert into the table. - * @param array $fname String: Name of the function, for profiling - * @param mixed $options String or Array. Valid options: IGNORE - * - * @return bool Success of insert operation. IGNORE always returns true. - */ - function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) { - global $wgDBversion; - - $table = $this->tableName( $table ); - if (! isset( $wgDBversion ) ) { - $this->getServerVersion(); - $wgDBversion = $this->numeric_version; - } - - if ( !is_array( $options ) ) - $options = array( $options ); - - if ( isset( $args[0] ) && is_array( $args[0] ) ) { - $multi = true; - $keys = array_keys( $args[0] ); - } - else { - $multi = false; - $keys = array_keys( $args ); - } - - $ignore = in_array( 'IGNORE', $options ) ? 1 : 0; - if ( $ignore ) - $olde = error_reporting( 0 ); - - $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; - - if ( $multi ) { - if ( $wgDBversion >= 8.2 ) { - $first = true; - foreach ( $args as $row ) { - if ( $first ) { - $first = false; - } else { - $sql .= ','; - } - $sql .= '(' . $this->makeList( $row ) . ')'; - } - $res = (bool)$this->query( $sql, $fname, $ignore ); - } - else { - $res = true; - $origsql = $sql; - foreach ( $args as $row ) { - $tempsql = $origsql; - $tempsql .= '(' . $this->makeList( $row ) . ')'; - $tempres = (bool)$this->query( $tempsql, $fname, $ignore ); - if (! $tempres) - $res = false; - } - } - } - else { - $sql .= '(' . $this->makeList( $args ) . ')'; - $res = (bool)$this->query( $sql, $fname, $ignore ); - } - - if ( $ignore ) { - $olde = error_reporting( $olde ); - return true; - } - - return $res; - - } - - function tableName( $name ) { - # Replace reserved words with better ones - switch( $name ) { - case 'user': - return 'mwuser'; - case 'text': - return 'pagecontent'; - default: - return $name; - } - } - - /** - * Return the next in a sequence, save the value for retrieval via insertId() - */ - function nextSequenceValue( $seqName ) { - $safeseq = preg_replace( "/'/", "''", $seqName ); - $res = $this->query( "SELECT nextval('$safeseq')" ); - $row = $this->fetchRow( $res ); - $this->mInsertId = $row[0]; - $this->freeResult( $res ); - return $this->mInsertId; - } - - /** - * Return the current value of a sequence. Assumes it has ben nextval'ed in this session. - */ - function currentSequenceValue( $seqName ) { - $safeseq = preg_replace( "/'/", "''", $seqName ); - $res = $this->query( "SELECT currval('$safeseq')" ); - $row = $this->fetchRow( $res ); - $currval = $row[0]; - $this->freeResult( $res ); - return $currval; - } - - /** - * Postgres does not have a "USE INDEX" clause, so return an empty string - */ - function useIndexClause( $index ) { - return ''; - } - - # REPLACE query wrapper - # Postgres simulates this with a DELETE followed by INSERT - # $row is the row to insert, an associative array - # $uniqueIndexes is an array of indexes. Each element may be either a - # field name or an array of field names - # - # It may be more efficient to leave off unique indexes which are unlikely to collide. - # However if you do this, you run the risk of encountering errors which wouldn't have - # occurred in MySQL - function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) { - $table = $this->tableName( $table ); - - if (count($rows)==0) { - return; - } - - # Single row case - if ( !is_array( reset( $rows ) ) ) { - $rows = array( $rows ); - } - - foreach( $rows as $row ) { - # Delete rows which collide - if ( $uniqueIndexes ) { - $sql = "DELETE FROM $table WHERE "; - $first = true; - foreach ( $uniqueIndexes as $index ) { - if ( $first ) { - $first = false; - $sql .= "("; - } else { - $sql .= ') OR ('; - } - if ( is_array( $index ) ) { - $first2 = true; - foreach ( $index as $col ) { - if ( $first2 ) { - $first2 = false; - } else { - $sql .= ' AND '; - } - $sql .= $col.'=' . $this->addQuotes( $row[$col] ); - } - } else { - $sql .= $index.'=' . $this->addQuotes( $row[$index] ); - } - } - $sql .= ')'; - $this->query( $sql, $fname ); - } - - # Now insert the row - $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' . - $this->makeList( $row, LIST_COMMA ) . ')'; - $this->query( $sql, $fname ); - } - } - - # DELETE where the condition is a join - function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) { - if ( !$conds ) { - throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); - } - - $delTable = $this->tableName( $delTable ); - $joinTable = $this->tableName( $joinTable ); - $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; - if ( $conds != '*' ) { - $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); - } - $sql .= ')'; - - $this->query( $sql, $fname ); - } - - # Returns the size of a text field, or -1 for "unlimited" - function textFieldSize( $table, $field ) { - $table = $this->tableName( $table ); - $sql = "SELECT t.typname as ftype,a.atttypmod as size - FROM pg_class c, pg_attribute a, pg_type t - WHERE relname='$table' AND a.attrelid=c.oid AND - a.atttypid=t.oid and a.attname='$field'"; - $res =$this->query($sql); - $row=$this->fetchObject($res); - if ($row->ftype=="varchar") { - $size=$row->size-4; - } else { - $size=$row->size; - } - $this->freeResult( $res ); - return $size; - } - - function lowPriorityOption() { - return ''; - } - - function limitResult($sql, $limit,$offset=false) { - return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":""); - } - - /** - * Returns an SQL expression for a simple conditional. - * Uses CASE on Postgres - * - * @param string $cond SQL expression which will result in a boolean value - * @param string $trueVal SQL expression to return if true - * @param string $falseVal SQL expression to return if false - * @return string SQL fragment - */ - function conditional( $cond, $trueVal, $falseVal ) { - return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; - } - - function wasDeadlock() { - return $this->lastErrno() == '40P01'; - } - - function timestamp( $ts=0 ) { - return wfTimestamp(TS_POSTGRES,$ts); - } - - /** - * Return aggregated value function call - */ - function aggregateValue ($valuedata,$valuename='value') { - return $valuedata; - } - - - function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - // Ignore errors during error handling to avoid infinite recursion - $ignore = $this->ignoreErrors( true ); - $this->mErrorCount++; - - if ($ignore || $tempIgnore) { - wfDebug("SQL ERROR (ignored): $error\n"); - $this->ignoreErrors( $ignore ); - } - else { - $message = "A database error has occurred\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - throw new DBUnexpectedError($this, $message); - } - } - - /** - * @return string wikitext of a link to the server software's web site - */ - function getSoftwareLink() { - return "[http://www.postgresql.org/ PostgreSQL]"; - } - - /** - * @return string Version information from the database - */ - function getServerVersion() { - $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0); - $thisver = array(); - if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) { - die("Could not determine the numeric version from $version!"); - } - $this->numeric_version = $thisver[1]; - return $version; - } - - - /** - * Query whether a given relation exists (in the given schema, or the - * default mw one if not given) - */ - function relationExists( $table, $types, $schema = false ) { - global $wgDBmwschema; - if (!is_array($types)) - $types = array($types); - if (! $schema ) - $schema = $wgDBmwschema; - $etable = $this->addQuotes($table); - $eschema = $this->addQuotes($schema); - $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n " - . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema " - . "AND c.relkind IN ('" . implode("','", $types) . "')"; - $res = $this->query( $SQL ); - $count = $res ? $res->numRows() : 0; - if ($res) - $this->freeResult( $res ); - return $count ? true : false; - } - - /* - * For backward compatibility, this function checks both tables and - * views. - */ - function tableExists ($table, $schema = false) { - return $this->relationExists($table, array('r', 'v'), $schema); - } - - function sequenceExists ($sequence, $schema = false) { - return $this->relationExists($sequence, 'S', $schema); - } - - function triggerExists($table, $trigger) { - global $wgDBmwschema; - - $q = <<<END - SELECT 1 FROM pg_class, pg_namespace, pg_trigger - WHERE relnamespace=pg_namespace.oid AND relkind='r' - AND tgrelid=pg_class.oid - AND nspname=%s AND relname=%s AND tgname=%s -END; - $res = $this->query(sprintf($q, - $this->addQuotes($wgDBmwschema), - $this->addQuotes($table), - $this->addQuotes($trigger))); - if (!$res) - return NULL; - $rows = $res->numRows(); - $this->freeResult($res); - return $rows; - } - - function ruleExists($table, $rule) { - global $wgDBmwschema; - $exists = $this->selectField("pg_rules", "rulename", - array( "rulename" => $rule, - "tablename" => $table, - "schemaname" => $wgDBmwschema)); - return $exists === $rule; - } - - function constraintExists($table, $constraint) { - global $wgDBmwschema; - $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ". - "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", - $this->addQuotes($wgDBmwschema), - $this->addQuotes($table), - $this->addQuotes($constraint)); - $res = $this->query($SQL); - if (!$res) - return NULL; - $rows = $res->numRows(); - $this->freeResult($res); - return $rows; - } - - /** - * Query whether a given schema exists. Returns the name of the owner - */ - function schemaExists( $schema ) { - $eschema = preg_replace("/'/", "''", $schema); - $SQL = "SELECT rolname FROM pg_catalog.pg_namespace n, pg_catalog.pg_roles r " - ."WHERE n.nspowner=r.oid AND n.nspname = '$eschema'"; - $res = $this->query( $SQL ); - if ( $res && $res->numRows() ) { - $row = $res->fetchObject(); - $owner = $row->rolname; - } else { - $owner = false; - } - if ($res) - $this->freeResult($res); - return $owner; - } - - /** - * Query whether a given column exists in the mediawiki schema - */ - function fieldExists( $table, $field, $fname = 'DatabasePostgres::fieldExists' ) { - global $wgDBmwschema; - $etable = preg_replace("/'/", "''", $table); - $eschema = preg_replace("/'/", "''", $wgDBmwschema); - $ecol = preg_replace("/'/", "''", $field); - $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a " - . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' " - . "AND a.attrelid = c.oid AND a.attname = '$ecol'"; - $res = $this->query( $SQL, $fname ); - $count = $res ? $res->numRows() : 0; - if ($res) - $this->freeResult( $res ); - return $count; - } - - function fieldInfo( $table, $field ) { - return PostgresField::fromText($this, $table, $field); - } - - function begin( $fname = 'DatabasePostgres::begin' ) { - $this->query( 'BEGIN', $fname ); - $this->mTrxLevel = 1; - } - function immediateCommit( $fname = 'DatabasePostgres::immediateCommit' ) { - return true; - } - function commit( $fname = 'DatabasePostgres::commit' ) { - $this->query( 'COMMIT', $fname ); - $this->mTrxLevel = 0; - } - - /* Not even sure why this is used in the main codebase... */ - function limitResultForUpdate($sql, $num) { - return $sql; - } - - function setup_database() { - global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser; - - // Make sure that we can write to the correct schema - // If not, Postgres will happily and silently go to the next search_path item - $ctest = "mediawiki_test_table"; - $safeschema = $this->quote_ident($wgDBmwschema); - if ($this->tableExists($ctest, $wgDBmwschema)) { - $this->doQuery("DROP TABLE $safeschema.$ctest"); - } - $SQL = "CREATE TABLE $safeschema.$ctest(a int)"; - $olde = error_reporting( 0 ); - $res = $this->doQuery($SQL); - error_reporting( $olde ); - if (!$res) { - print "<b>FAILED</b>. Make sure that the user \"$wgDBuser\" can write to the schema \"$wgDBmwschema\"</li>\n"; - dieout("</ul>"); - } - $this->doQuery("DROP TABLE $safeschema.$ctest"); - - $res = dbsource( "../maintenance/postgres/tables.sql", $this); - - ## Update version information - $mwv = $this->addQuotes($wgVersion); - $pgv = $this->addQuotes($this->getServerVersion()); - $pgu = $this->addQuotes($this->mUser); - $mws = $this->addQuotes($wgDBmwschema); - $tss = $this->addQuotes($wgDBts2schema); - $pgp = $this->addQuotes($wgDBport); - $dbn = $this->addQuotes($this->mDBname); - $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0); - - $SQL = "UPDATE mediawiki_version SET mw_version=$mwv, pg_version=$pgv, pg_user=$pgu, ". - "mw_schema = $mws, ts2_schema = $tss, pg_port=$pgp, pg_dbname=$dbn, ". - "ctype = '$ctype' ". - "WHERE type = 'Creation'"; - $this->query($SQL); - - ## Avoid the non-standard "REPLACE INTO" syntax - $f = fopen( "../maintenance/interwiki.sql", 'r' ); - if ($f == false ) { - dieout( "<li>Could not find the interwiki.sql file"); - } - ## We simply assume it is already empty as we have just created it - $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES "; - while ( ! feof( $f ) ) { - $line = fgets($f,1024); - $matches = array(); - if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) { - continue; - } - $this->query("$SQL $matches[1],$matches[2])"); - } - print " (table interwiki successfully populated)...\n"; - - $this->doQuery("COMMIT"); - } - - function encodeBlob( $b ) { - return new Blob ( pg_escape_bytea( $b ) ) ; - } - - function decodeBlob( $b ) { - if ($b instanceof Blob) { - $b = $b->fetch(); - } - return pg_unescape_bytea( $b ); - } - - function strencode( $s ) { ## Should not be called by us - return pg_escape_string( $s ); - } - - function addQuotes( $s ) { - if ( is_null( $s ) ) { - return 'NULL'; - } else if ($s instanceof Blob) { - return "'".$s->fetch($s)."'"; - } - return "'" . pg_escape_string($s) . "'"; - } - - function quote_ident( $s ) { - return '"' . preg_replace( '/"/', '""', $s) . '"'; - } - - /* For now, does nothing */ - function selectDB( $db ) { - return true; - } - - /** - * Postgres specific version of replaceVars. - * Calls the parent version in Database.php - * - * @private - * - * @param string $com SQL string, read from a stream (usually tables.sql) - * - * @return string SQL string - */ - protected function replaceVars( $ins ) { - - $ins = parent::replaceVars( $ins ); - - if ($this->numeric_version >= 8.3) { - // Thanks for not providing backwards-compatibility, 8.3 - $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins ); - } - - if ($this->numeric_version <= 8.1) { // Our minimum version - $ins = str_replace( 'USING gin', 'USING gist', $ins ); - } - - return $ins; - } - - /** - * Various select options - * - * @private - * - * @param array $options an associative array of options to be turned into - * an SQL query, valid keys are listed in the function. - * @return array - */ - function makeSelectOptions( $options ) { - $preLimitTail = $postLimitTail = ''; - $startOpts = $useIndex = ''; - - $noKeyOptions = array(); - foreach ( $options as $key => $option ) { - if ( is_numeric( $key ) ) { - $noKeyOptions[$option] = true; - } - } - - if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY " . $options['GROUP BY']; - if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}"; - if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY " . $options['ORDER BY']; - - //if (isset($options['LIMIT'])) { - // $tailOpts .= $this->limitResult('', $options['LIMIT'], - // isset($options['OFFSET']) ? $options['OFFSET'] - // : false); - //} - - if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE'; - if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE'; - if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; - - return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); - } - - public function setTimeout( $timeout ) { - // @todo fixme no-op - } - - function ping() { - wfDebug( "Function ping() not written for DatabasePostgres.php yet"); - return true; - } - - /** - * How lagged is this slave? - * - */ - public function getLag() { - # Not implemented for PostgreSQL - return false; - } - - function buildConcat( $stringList ) { - return implode( ' || ', $stringList ); - } - -} // end DatabasePostgres class - - diff --git a/includes/DateFormatter.php b/includes/DateFormatter.php deleted file mode 100644 index bbad6d15..00000000 --- a/includes/DateFormatter.php +++ /dev/null @@ -1,285 +0,0 @@ -<?php - -/** - * Date formatter, recognises dates in plain text and formats them accoding to user preferences. - * @todo preferences, OutputPage - * @addtogroup Parser - */ -class DateFormatter -{ - var $mSource, $mTarget; - var $monthNames = '', $rxDM, $rxMD, $rxDMY, $rxYDM, $rxMDY, $rxYMD; - - var $regexes, $pDays, $pMonths, $pYears; - var $rules, $xMonths, $preferences; - - const ALL = -1; - const NONE = 0; - const MDY = 1; - const DMY = 2; - const YMD = 3; - const ISO1 = 4; - const LASTPREF = 4; - const ISO2 = 5; - const YDM = 6; - const DM = 7; - const MD = 8; - const LAST = 8; - - /** - * @todo document - */ - function DateFormatter() { - global $wgContLang; - - $this->monthNames = $this->getMonthRegex(); - for ( $i=1; $i<=12; $i++ ) { - $this->xMonths[$wgContLang->lc( $wgContLang->getMonthName( $i ) )] = $i; - $this->xMonths[$wgContLang->lc( $wgContLang->getMonthAbbreviation( $i ) )] = $i; - } - - $this->regexTrail = '(?![a-z])/iu'; - - # Partial regular expressions - $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')]]'; - $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})]]'; - $this->prxY = '\[\[(\d{1,4}([ _]BC|))]]'; - $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})]]'; - $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})]]'; - - # Real regular expressions - $this->regexes[self::DMY] = "/{$this->prxDM} *,? *{$this->prxY}{$this->regexTrail}"; - $this->regexes[self::YDM] = "/{$this->prxY} *,? *{$this->prxDM}{$this->regexTrail}"; - $this->regexes[self::MDY] = "/{$this->prxMD} *,? *{$this->prxY}{$this->regexTrail}"; - $this->regexes[self::YMD] = "/{$this->prxY} *,? *{$this->prxMD}{$this->regexTrail}"; - $this->regexes[self::DM] = "/{$this->prxDM}{$this->regexTrail}"; - $this->regexes[self::MD] = "/{$this->prxMD}{$this->regexTrail}"; - $this->regexes[self::ISO1] = "/{$this->prxISO1}{$this->regexTrail}"; - $this->regexes[self::ISO2] = "/{$this->prxISO2}{$this->regexTrail}"; - - # Extraction keys - # See the comments in replace() for the meaning of the letters - $this->keys[self::DMY] = 'jFY'; - $this->keys[self::YDM] = 'Y jF'; - $this->keys[self::MDY] = 'FjY'; - $this->keys[self::YMD] = 'Y Fj'; - $this->keys[self::DM] = 'jF'; - $this->keys[self::MD] = 'Fj'; - $this->keys[self::ISO1] = 'ymd'; # y means ISO year - $this->keys[self::ISO2] = 'ymd'; - - # Target date formats - $this->targets[self::DMY] = '[[F j|j F]] [[Y]]'; - $this->targets[self::YDM] = '[[Y]], [[F j|j F]]'; - $this->targets[self::MDY] = '[[F j]], [[Y]]'; - $this->targets[self::YMD] = '[[Y]] [[F j]]'; - $this->targets[self::DM] = '[[F j|j F]]'; - $this->targets[self::MD] = '[[F j]]'; - $this->targets[self::ISO1] = '[[Y|y]]-[[F j|m-d]]'; - $this->targets[self::ISO2] = '[[y-m-d]]'; - - # Rules - # pref source target - $this->rules[self::DMY][self::MD] = self::DM; - $this->rules[self::ALL][self::MD] = self::MD; - $this->rules[self::MDY][self::DM] = self::MD; - $this->rules[self::ALL][self::DM] = self::DM; - $this->rules[self::NONE][self::ISO2] = self::ISO1; - - $this->preferences = array( - 'default' => self::NONE, - 'dmy' => self::DMY, - 'mdy' => self::MDY, - 'ymd' => self::YMD, - 'ISO 8601' => self::ISO1, - ); - } - - /** - * @static - */ - function &getInstance() { - global $wgMemc; - static $dateFormatter = false; - if ( !$dateFormatter ) { - $dateFormatter = $wgMemc->get( wfMemcKey( 'dateformatter' ) ); - if ( !$dateFormatter ) { - $dateFormatter = new DateFormatter; - $wgMemc->set( wfMemcKey( 'dateformatter' ), $dateFormatter, 3600 ); - } - } - return $dateFormatter; - } - - /** - * @param string $preference User preference - * @param string $text Text to reformat - */ - function reformat( $preference, $text ) { - if ( isset( $this->preferences[$preference] ) ) { - $preference = $this->preferences[$preference]; - } else { - $preference = self::NONE; - } - for ( $i=1; $i<=self::LAST; $i++ ) { - $this->mSource = $i; - if ( isset ( $this->rules[$preference][$i] ) ) { - # Specific rules - $this->mTarget = $this->rules[$preference][$i]; - } elseif ( isset ( $this->rules[self::ALL][$i] ) ) { - # General rules - $this->mTarget = $this->rules[self::ALL][$i]; - } elseif ( $preference ) { - # User preference - $this->mTarget = $preference; - } else { - # Default - $this->mTarget = $i; - } - $text = preg_replace_callback( $this->regexes[$i], array( &$this, 'replace' ), $text ); - } - return $text; - } - - /** - * @param $matches - */ - function replace( $matches ) { - # Extract information from $matches - $bits = array(); - $key = $this->keys[$this->mSource]; - for ( $p=0; $p < strlen($key); $p++ ) { - if ( $key{$p} != ' ' ) { - $bits[$key{$p}] = $matches[$p+1]; - } - } - - $format = $this->targets[$this->mTarget]; - - # Construct new date - $text = ''; - $fail = false; - - for ( $p=0; $p < strlen( $format ); $p++ ) { - $char = $format{$p}; - switch ( $char ) { - case 'd': # ISO day of month - if ( !isset($bits['d']) ) { - $text .= sprintf( '%02d', $bits['j'] ); - } else { - $text .= $bits['d']; - } - break; - case 'm': # ISO month - if ( !isset($bits['m']) ) { - $m = $this->makeIsoMonth( $bits['F'] ); - if ( !$m || $m == '00' ) { - $fail = true; - } else { - $text .= $m; - } - } else { - $text .= $bits['m']; - } - break; - case 'y': # ISO year - if ( !isset( $bits['y'] ) ) { - $text .= $this->makeIsoYear( $bits['Y'] ); - } else { - $text .= $bits['y']; - } - break; - case 'j': # ordinary day of month - if ( !isset($bits['j']) ) { - $text .= intval( $bits['d'] ); - } else { - $text .= $bits['j']; - } - break; - case 'F': # long month - if ( !isset( $bits['F'] ) ) { - $m = intval($bits['m']); - if ( $m > 12 || $m < 1 ) { - $fail = true; - } else { - global $wgContLang; - $text .= $wgContLang->getMonthName( $m ); - } - } else { - $text .= ucfirst( $bits['F'] ); - } - break; - case 'Y': # ordinary (optional BC) year - if ( !isset( $bits['Y'] ) ) { - $text .= $this->makeNormalYear( $bits['y'] ); - } else { - $text .= $bits['Y']; - } - break; - default: - $text .= $char; - } - } - if ( $fail ) { - $text = $matches[0]; - } - return $text; - } - - /** - * @todo document - */ - function getMonthRegex() { - global $wgContLang; - $names = array(); - for( $i = 1; $i <= 12; $i++ ) { - $names[] = $wgContLang->getMonthName( $i ); - $names[] = $wgContLang->getMonthAbbreviation( $i ); - } - return implode( '|', $names ); - } - - /** - * Makes an ISO month, e.g. 02, from a month name - * @param $monthName String: month name - * @return string ISO month name - */ - function makeIsoMonth( $monthName ) { - global $wgContLang; - - $n = $this->xMonths[$wgContLang->lc( $monthName )]; - return sprintf( '%02d', $n ); - } - - /** - * @todo document - * @param $year String: Year name - * @return string ISO year name - */ - function makeIsoYear( $year ) { - # Assumes the year is in a nice format, as enforced by the regex - if ( substr( $year, -2 ) == 'BC' ) { - $num = intval(substr( $year, 0, -3 )) - 1; - # PHP bug note: sprintf( "%04d", -1 ) fails poorly - $text = sprintf( '-%04d', $num ); - - } else { - $text = sprintf( '%04d', $year ); - } - return $text; - } - - /** - * @todo document - */ - function makeNormalYear( $iso ) { - if ( $iso{0} == '-' ) { - $text = (intval( substr( $iso, 1 ) ) + 1) . ' BC'; - } else { - $text = intval( $iso ); - } - return $text; - } -} - - diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index d1d04a45..cb8bb001 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -31,7 +31,7 @@ require_once( "$IP/includes/SiteConfiguration.php" ); $wgConf = new SiteConfiguration; /** MediaWiki version number */ -$wgVersion = '1.13.2'; +$wgVersion = '1.13.3'; /** Name of the site. It must be changed in LocalSettings.php */ $wgSitename = 'MediaWiki'; @@ -1794,6 +1794,8 @@ $wgMimeTypeBlacklist= array( 'application/x-php', 'text/x-php', # Other types that may be interpreted by some servers 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', + # Client-side hazards on Internet Explorer + 'text/scriptlet', 'application/x-msdownload', # Windows metafile, client-side vulnerability on some systems 'application/x-msmetafile' ); @@ -2288,7 +2290,7 @@ $wgAutoloadClasses = array(); * $wgExtensionCredits[$type][] = array( * 'name' => 'Example extension', * 'version' => 1.9, - * 'svn-revision' => '$LastChangedRevision: 41545 $', + * 'svn-revision' => '$LastChangedRevision: 44568 $', * 'author' => 'Foo Barstein', * 'url' => 'http://wwww.example.com/Example%20Extension/', * 'description' => 'An example extension', diff --git a/includes/Exception.php b/includes/Exception.php index 74820204..ab25f0b8 100644 --- a/includes/Exception.php +++ b/includes/Exception.php @@ -274,7 +274,16 @@ function wfReportException( Exception $e ) { } } } else { - echo $e->__toString(); + $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" . + $e->__toString() . "\n"; + if ( $GLOBALS['wgShowExceptionDetails'] ) { + $message .= "\n" . $e->getTraceAsString() ."\n"; + } + if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) { + wfPrintError( $message ); + } else { + echo nl2br( htmlspecialchars( $message ) ). "\n"; + } } } diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php deleted file mode 100644 index 69ec1007..00000000 --- a/includes/HTMLForm.php +++ /dev/null @@ -1,107 +0,0 @@ -<?php -/** - * This file contain a class to easily build HTML forms - */ - -/** - * Class to build various forms - * - * @author jeluf, hashar - */ -class HTMLForm { - /** name of our form. Used as prefix for labels */ - var $mName, $mRequest; - - function HTMLForm( &$request ) { - $this->mRequest = $request; - } - - /** - * @private - * @param $name String: name of the fieldset. - * @param $content String: HTML content to put in. - * @return string HTML fieldset - */ - function fieldset( $name, $content ) { - return "<fieldset><legend>".wfMsg($this->mName.'-'.$name)."</legend>\n" . - $content . "\n</fieldset>\n"; - } - - /** - * @private - * @param $varname String: name of the checkbox. - * @param $checked Boolean: set true to check the box (default False). - */ - function checkbox( $varname, $checked=false ) { - if ( $this->mRequest->wasPosted() && !is_null( $this->mRequest->getVal( $varname ) ) ) { - $checked = $this->mRequest->getCheck( $varname ); - } - return "<div><input type='checkbox' value=\"1\" id=\"{$varname}\" name=\"wpOp{$varname}\"" . - ( $checked ? ' checked="checked"' : '' ) . - " /><label for=\"{$varname}\">". wfMsg( $this->mName.'-'.$varname ) . - "</label></div>\n"; - } - - /** - * @private - * @param $varname String: name of the textbox. - * @param $value String: optional value (default empty) - * @param $size Integer: optional size of the textbox (default 20) - */ - function textbox( $varname, $value='', $size=20 ) { - if ( $this->mRequest->wasPosted() ) { - $value = $this->mRequest->getText( $varname, $value ); - } - $value = htmlspecialchars( $value ); - return "<div><label>". wfMsg( $this->mName.'-'.$varname ) . - "<input type='text' name=\"{$varname}\" value=\"{$value}\" size=\"{$size}\" /></label></div>\n"; - } - - /** - * @private - * @param $varname String: name of the radiobox. - * @param $fields Array: Various fields. - */ - function radiobox( $varname, $fields ) { - foreach ( $fields as $value => $checked ) { - $s .= "<div><label><input type='radio' name=\"{$varname}\" value=\"{$value}\"" . - ( $checked ? ' checked="checked"' : '' ) . " />" . wfMsg( $this->mName.'-'.$varname.'-'.$value ) . - "</label></div>\n"; - } - return $this->fieldset( $varname, $s ); - } - - /** - * @private - * @param $varname String: name of the textareabox. - * @param $value String: optional value (default empty) - * @param $size Integer: optional size of the textarea (default 20) - */ - function textareabox ( $varname, $value='', $size=20 ) { - if ( $this->mRequest->wasPosted() ) { - $value = $this->mRequest->getText( $varname, $value ); - } - $value = htmlspecialchars( $value ); - return '<div><label>'.wfMsg( $this->mName.'-'.$varname ). - "<textarea name=\"{$varname}\" rows=\"5\" cols=\"{$size}\">$value</textarea></label></div>\n"; - } - - /** - * @private - * @param $varname String: name of the arraybox. - * @param $size Integer: Optional size of the textarea (default 20) - */ - function arraybox( $varname , $size=20 ) { - $s = ''; - if ( $this->mRequest->wasPosted() ) { - $arr = $this->mRequest->getArray( $varname ); - if ( is_array( $arr ) ) { - foreach ( $_POST[$varname] as $element ) { - $s .= htmlspecialchars( $element )."\n"; - } - } - } - return "<div><label>".wfMsg( $this->mName.'-'.$varname ). - "<textarea name=\"{$varname}\" rows=\"5\" cols=\"{$size}\">{$s}</textarea>\n"; - } -} // end class diff --git a/includes/IEContentAnalyzer.php b/includes/IEContentAnalyzer.php new file mode 100644 index 00000000..59abc6a6 --- /dev/null +++ b/includes/IEContentAnalyzer.php @@ -0,0 +1,823 @@ +<?php + +/** + * This class simulates Microsoft Internet Explorer's terribly broken and + * insecure MIME type detection algorithm. It can be used to check web uploads + * with an apparently safe type, to see if IE will reinterpret them to produce + * something dangerous. + * + * It is full of bugs and strange design choices should not under any + * circumstances be used to determine a MIME type to present to a user or + * client. (Apple Safari developers, this means you too.) + * + * This class is based on a disassembly of IE 5.0, 6.0 and 7.0. Although I have + * attempted to ensure that this code works in exactly the same way as Internet + * Explorer, it does not share any source code, or creative choices such as + * variable names, thus I (Tim Starling) claim copyright on it. + * + * It may be redistributed without restriction. To aid reuse, this class does + * not depend on any MediaWiki module. + */ +class IEContentAnalyzer { + /** + * Relevant data taken from the type table in IE 5 + */ + protected $baseTypeTable = array( + 'ambiguous' /*1*/ => array( + 'text/plain', + 'application/octet-stream', + 'application/x-netcdf', // [sic] + ), + 'text' /*3*/ => array( + 'text/richtext', 'image/x-bitmap', 'application/postscript', 'application/base64', + 'application/macbinhex40', 'application/x-cdf', 'text/scriptlet' + ), + 'binary' /*4*/ => array( + 'application/pdf', 'audio/x-aiff', 'audio/basic', 'audio/wav', 'image/gif', + 'image/pjpeg', 'image/jpeg', 'image/tiff', 'image/x-png', 'image/png', 'image/bmp', + 'image/x-jg', 'image/x-art', 'image/x-emf', 'image/x-wmf', 'video/avi', + 'video/x-msvideo', 'video/mpeg', 'application/x-compressed', + 'application/x-zip-compressed', 'application/x-gzip-compressed', 'application/java', + 'application/x-msdownload' + ), + 'html' /*5*/ => array( 'text/html' ), + ); + + /** + * Changes to the type table in later versions of IE + */ + protected $addedTypes = array( + 'ie07' => array( + 'text' => array( 'text/xml', 'application/xml' ) + ), + ); + + /** + * An approximation of the "Content Type" values in HKEY_CLASSES_ROOT in a + * typical Windows installation. + * + * Used for extension to MIME type mapping if detection fails. + */ + protected $registry = array( + '.323' => 'text/h323', + '.3g2' => 'video/3gpp2', + '.3gp' => 'video/3gpp', + '.3gp2' => 'video/3gpp2', + '.3gpp' => 'video/3gpp', + '.aac' => 'audio/aac', + '.ac3' => 'audio/ac3', + '.accda' => 'application/msaccess', + '.accdb' => 'application/msaccess', + '.accdc' => 'application/msaccess', + '.accde' => 'application/msaccess', + '.accdr' => 'application/msaccess', + '.accdt' => 'application/msaccess', + '.ade' => 'application/msaccess', + '.adp' => 'application/msaccess', + '.adts' => 'audio/aac', + '.ai' => 'application/postscript', + '.aif' => 'audio/aiff', + '.aifc' => 'audio/aiff', + '.aiff' => 'audio/aiff', + '.amc' => 'application/x-mpeg', + '.application' => 'application/x-ms-application', + '.asf' => 'video/x-ms-asf', + '.asx' => 'video/x-ms-asf', + '.au' => 'audio/basic', + '.avi' => 'video/avi', + '.bmp' => 'image/bmp', + '.caf' => 'audio/x-caf', + '.cat' => 'application/vnd.ms-pki.seccat', + '.cbo' => 'application/sha', + '.cdda' => 'audio/aiff', + '.cer' => 'application/x-x509-ca-cert', + '.conf' => 'text/plain', + '.crl' => 'application/pkix-crl', + '.crt' => 'application/x-x509-ca-cert', + '.css' => 'text/css', + '.csv' => 'application/vnd.ms-excel', + '.der' => 'application/x-x509-ca-cert', + '.dib' => 'image/bmp', + '.dif' => 'video/x-dv', + '.dll' => 'application/x-msdownload', + '.doc' => 'application/msword', + '.docm' => 'application/vnd.ms-word.document.macroEnabled.12', + '.docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.dot' => 'application/msword', + '.dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + '.dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + '.dv' => 'video/x-dv', + '.dwfx' => 'model/vnd.dwfx+xps', + '.edn' => 'application/vnd.adobe.edn', + '.eml' => 'message/rfc822', + '.eps' => 'application/postscript', + '.etd' => 'application/x-ebx', + '.exe' => 'application/x-msdownload', + '.fdf' => 'application/vnd.fdf', + '.fif' => 'application/fractals', + '.gif' => 'image/gif', + '.gsm' => 'audio/x-gsm', + '.hqx' => 'application/mac-binhex40', + '.hta' => 'application/hta', + '.htc' => 'text/x-component', + '.htm' => 'text/html', + '.html' => 'text/html', + '.htt' => 'text/webviewhtml', + '.hxa' => 'application/xml', + '.hxc' => 'application/xml', + '.hxd' => 'application/octet-stream', + '.hxe' => 'application/xml', + '.hxf' => 'application/xml', + '.hxh' => 'application/octet-stream', + '.hxi' => 'application/octet-stream', + '.hxk' => 'application/xml', + '.hxq' => 'application/octet-stream', + '.hxr' => 'application/octet-stream', + '.hxs' => 'application/octet-stream', + '.hxt' => 'application/xml', + '.hxv' => 'application/xml', + '.hxw' => 'application/octet-stream', + '.ico' => 'image/x-icon', + '.iii' => 'application/x-iphone', + '.ins' => 'application/x-internet-signup', + '.iqy' => 'text/x-ms-iqy', + '.isp' => 'application/x-internet-signup', + '.jfif' => 'image/jpeg', + '.jnlp' => 'application/x-java-jnlp-file', + '.jpe' => 'image/jpeg', + '.jpeg' => 'image/jpeg', + '.jpg' => 'image/jpeg', + '.jtx' => 'application/x-jtx+xps', + '.latex' => 'application/x-latex', + '.log' => 'text/plain', + '.m1v' => 'video/mpeg', + '.m2v' => 'video/mpeg', + '.m3u' => 'audio/x-mpegurl', + '.mac' => 'image/x-macpaint', + '.man' => 'application/x-troff-man', + '.mda' => 'application/msaccess', + '.mdb' => 'application/msaccess', + '.mde' => 'application/msaccess', + '.mfp' => 'application/x-shockwave-flash', + '.mht' => 'message/rfc822', + '.mhtml' => 'message/rfc822', + '.mid' => 'audio/mid', + '.midi' => 'audio/mid', + '.mod' => 'video/mpeg', + '.mov' => 'video/quicktime', + '.mp2' => 'video/mpeg', + '.mp2v' => 'video/mpeg', + '.mp3' => 'audio/mpeg', + '.mp4' => 'video/mp4', + '.mpa' => 'video/mpeg', + '.mpe' => 'video/mpeg', + '.mpeg' => 'video/mpeg', + '.mpf' => 'application/vnd.ms-mediapackage', + '.mpg' => 'video/mpeg', + '.mpv2' => 'video/mpeg', + '.mqv' => 'video/quicktime', + '.NMW' => 'application/nmwb', + '.nws' => 'message/rfc822', + '.odc' => 'text/x-ms-odc', + '.ols' => 'application/vnd.ms-publisher', + '.p10' => 'application/pkcs10', + '.p12' => 'application/x-pkcs12', + '.p7b' => 'application/x-pkcs7-certificates', + '.p7c' => 'application/pkcs7-mime', + '.p7m' => 'application/pkcs7-mime', + '.p7r' => 'application/x-pkcs7-certreqresp', + '.p7s' => 'application/pkcs7-signature', + '.pct' => 'image/pict', + '.pdf' => 'application/pdf', + '.pdx' => 'application/vnd.adobe.pdx', + '.pfx' => 'application/x-pkcs12', + '.pic' => 'image/pict', + '.pict' => 'image/pict', + '.pinstall' => 'application/x-picasa-detect', + '.pko' => 'application/vnd.ms-pki.pko', + '.png' => 'image/png', + '.pnt' => 'image/x-macpaint', + '.pntg' => 'image/x-macpaint', + '.pot' => 'application/vnd.ms-powerpoint', + '.potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + '.potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + '.ppa' => 'application/vnd.ms-powerpoint', + '.ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + '.pps' => 'application/vnd.ms-powerpoint', + '.ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + '.ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + '.ppt' => 'application/vnd.ms-powerpoint', + '.pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + '.pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.prf' => 'application/pics-rules', + '.ps' => 'application/postscript', + '.pub' => 'application/vnd.ms-publisher', + '.pwz' => 'application/vnd.ms-powerpoint', + '.py' => 'text/plain', + '.pyw' => 'text/plain', + '.qht' => 'text/x-html-insertion', + '.qhtm' => 'text/x-html-insertion', + '.qt' => 'video/quicktime', + '.qti' => 'image/x-quicktime', + '.qtif' => 'image/x-quicktime', + '.qtl' => 'application/x-quicktimeplayer', + '.rat' => 'application/rat-file', + '.rmf' => 'application/vnd.adobe.rmf', + '.rmi' => 'audio/mid', + '.rqy' => 'text/x-ms-rqy', + '.rtf' => 'application/msword', + '.sct' => 'text/scriptlet', + '.sd2' => 'audio/x-sd2', + '.sdp' => 'application/sdp', + '.shtml' => 'text/html', + '.sit' => 'application/x-stuffit', + '.sldm' => 'application/vnd.ms-powerpoint.slide.macroEnabled.12', + '.sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + '.slk' => 'application/vnd.ms-excel', + '.snd' => 'audio/basic', + '.so' => 'application/x-apachemodule', + '.sol' => 'text/plain', + '.sor' => 'text/plain', + '.spc' => 'application/x-pkcs7-certificates', + '.spl' => 'application/futuresplash', + '.sst' => 'application/vnd.ms-pki.certstore', + '.stl' => 'application/vnd.ms-pki.stl', + '.swf' => 'application/x-shockwave-flash', + '.thmx' => 'application/vnd.ms-officetheme', + '.tif' => 'image/tiff', + '.tiff' => 'image/tiff', + '.txt' => 'text/plain', + '.uls' => 'text/iuls', + '.vcf' => 'text/x-vcard', + '.vdx' => 'application/vnd.ms-visio.viewer', + '.vsd' => 'application/vnd.ms-visio.viewer', + '.vss' => 'application/vnd.ms-visio.viewer', + '.vst' => 'application/vnd.ms-visio.viewer', + '.vsx' => 'application/vnd.ms-visio.viewer', + '.vtx' => 'application/vnd.ms-visio.viewer', + '.wav' => 'audio/wav', + '.wax' => 'audio/x-ms-wax', + '.wbk' => 'application/msword', + '.wdp' => 'image/vnd.ms-photo', + '.wiz' => 'application/msword', + '.wm' => 'video/x-ms-wm', + '.wma' => 'audio/x-ms-wma', + '.wmd' => 'application/x-ms-wmd', + '.wmv' => 'video/x-ms-wmv', + '.wmx' => 'video/x-ms-wmx', + '.wmz' => 'application/x-ms-wmz', + '.wpl' => 'application/vnd.ms-wpl', + '.wsc' => 'text/scriptlet', + '.wvx' => 'video/x-ms-wvx', + '.xaml' => 'application/xaml+xml', + '.xbap' => 'application/x-ms-xbap', + '.xdp' => 'application/vnd.adobe.xdp+xml', + '.xfdf' => 'application/vnd.adobe.xfdf', + '.xht' => 'application/xhtml+xml', + '.xhtml' => 'application/xhtml+xml', + '.xla' => 'application/vnd.ms-excel', + '.xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + '.xlk' => 'application/vnd.ms-excel', + '.xll' => 'application/vnd.ms-excel', + '.xlm' => 'application/vnd.ms-excel', + '.xls' => 'application/vnd.ms-excel', + '.xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + '.xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + '.xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xlt' => 'application/vnd.ms-excel', + '.xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + '.xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + '.xlw' => 'application/vnd.ms-excel', + '.xml' => 'text/xml', + '.xps' => 'application/vnd.ms-xpsdocument', + '.xsl' => 'text/xml', + ); + + /** + * IE versions which have been analysed to bring you this class, and for + * which some substantive difference exists. These will appear as keys + * in the return value of getRealMimesFromData(). The names are chosen to sort correctly. + */ + protected $versions = array( 'ie05', 'ie06', 'ie07', 'ie07.strict', 'ie07.nohtml' ); + + /** + * Type table with versions expanded + */ + protected $typeTable = array(); + + /** constructor */ + function __construct() { + // Construct versioned type arrays from the base type array plus additions + $types = $this->baseTypeTable; + foreach ( $this->versions as $version ) { + if ( isset( $this->addedTypes[$version] ) ) { + foreach ( $this->addedTypes[$version] as $format => $addedTypes ) { + $types[$format] = array_merge( $types[$format], $addedTypes ); + } + } + $this->typeTable[$version] = $types; + } + } + + /** + * Get the MIME types from getMimesFromData(), but convert the result from IE's + * idiosyncratic private types into something other apps will understand. + * + * @param string $fileName The file name (unused at present) + * @param string $chunk The first 256 bytes of the file + * @param string $proposed The MIME type proposed by the server + * + * @return array Map of IE version to detected mime type + */ + public function getRealMimesFromData( $fileName, $chunk, $proposed ) { + $types = $this->getMimesFromData( $fileName, $chunk, $proposed ); + $types = array_map( array( $this, 'translateMimeType' ), $types ); + return $types; + } + + /** + * Translate a MIME type from IE's idiosyncratic private types into + * more commonly understood type strings + */ + public function translateMimeType( $type ) { + static $table = array( + 'image/pjpeg' => 'image/jpeg', + 'image/x-png' => 'image/png', + 'image/x-wmf' => 'application/x-msmetafile', + 'image/bmp' => 'image/x-bmp', + 'application/x-zip-compressed' => 'application/zip', + 'application/x-compressed' => 'application/x-compress', + 'application/x-gzip-compressed' => 'application/x-gzip', + 'audio/mid' => 'audio/midi', + ); + if ( isset( $table[$type] ) ) { + $type = $table[$type]; + } + return $type; + } + + /** + * Get the untranslated MIME types for all known versions + * + * @param string $fileName The file name (unused at present) + * @param string $chunk The first 256 bytes of the file + * @param string $proposed The MIME type proposed by the server + * + * @return array Map of IE version to detected mime type + */ + public function getMimesFromData( $fileName, $chunk, $proposed ) { + $types = array(); + foreach ( $this->versions as $version ) { + $types[$version] = $this->getMimeTypeForVersion( $version, $fileName, $chunk, $proposed ); + } + return $types; + } + + /** + * Get the MIME type for a given named version + */ + protected function getMimeTypeForVersion( $version, $fileName, $chunk, $proposed ) { + // Strip text after a semicolon + $semiPos = strpos( $proposed, ';' ); + if ( $semiPos !== false ) { + $proposed = substr( $proposed, 0, $semiPos ); + } + + $proposedFormat = $this->getDataFormat( $version, $proposed ); + if ( $proposedFormat == 'unknown' + && $proposed != 'multipart/mixed' + && $proposed != 'multipart/x-mixed-replace' ) + { + return $proposed; + } + if ( strval( $chunk ) === '' ) { + return $proposed; + } + + // Truncate chunk at 255 bytes + $chunk = substr( $chunk, 0, 255 ); + + // IE does the Check*Headers() calls last, and instead does the following image + // type checks by directly looking for the magic numbers. What I do here should + // have the same effect since the magic number checks are identical in both cases. + $result = $this->sampleData( $version, $chunk ); + $sampleFound = $result['found']; + $counters = $result['counters']; + $binaryType = $this->checkBinaryHeaders( $version, $chunk ); + $textType = $this->checkTextHeaders( $version, $chunk ); + + if ( $proposed == 'text/html' && isset( $sampleFound['html'] ) ) { + return 'text/html'; + } + if ( $proposed == 'image/gif' && $binaryType == 'image/gif' ) { + return 'image/gif'; + } + if ( ( $proposed == 'image/pjpeg' || $proposed == 'image/jpeg' ) + && $binaryType == 'image/pjpeg' ) + { + return $proposed; + } + // PNG check added in IE 7 + if ( $version >= 'ie07' + && ( $proposed == 'image/x-png' || $proposed == 'image/png' ) + && $binaryType == 'image/x-png' ) + { + return $proposed; + } + + // CDF was removed in IE 7 so it won't be in $sampleFound for later versions + if ( isset( $sampleFound['cdf'] ) ) { + return 'application/x-cdf'; + } + + // RSS and Atom were added in IE 7 so they won't be in $sampleFound for + // previous versions + if ( isset( $sampleFound['rss'] ) ) { + return 'application/rss+xml'; + } + if ( isset( $sampleFound['rdf-tag'] ) + && isset( $sampleFound['rdf-url'] ) + && isset( $sampleFound['rdf-purl'] ) ) + { + return 'application/rss+xml'; + } + if ( isset( $sampleFound['atom'] ) ) { + return 'application/atom+xml'; + } + + if ( isset( $sampleFound['xml'] ) ) { + // TODO: I'm not sure under what circumstances this flag is enabled + if ( strpos( $version, 'strict' ) !== false ) { + if ( $proposed == 'text/html' || $proposed == 'text/xml' ) { + return 'text/xml'; + } + } else { + return 'text/xml'; + } + } + if ( isset( $sampleFound['html'] ) ) { + // TODO: I'm not sure under what circumstances this flag is enabled + if ( strpos( $version, 'nohtml' ) !== false ) { + if ( $proposed == 'text/plain' ) { + return 'text/html'; + } + } else { + return 'text/html'; + } + } + if ( isset( $sampleFound['xbm'] ) ) { + return 'image/x-bitmap'; + } + if ( isset( $sampleFound['binhex'] ) ) { + return 'application/macbinhex40'; + } + if ( isset( $sampleFound['scriptlet'] ) ) { + if ( strpos( $version, 'strict' ) !== false ) { + if ( $proposed == 'text/plain' || $proposed == 'text/scriptlet' ) { + return 'text/scriptlet'; + } + } else { + return 'text/scriptlet'; + } + } + + // Freaky heuristics to determine if the data is text or binary + // The heuristic is of course broken for non-ASCII text + if ( $counters['ctrl'] != 0 && ( $counters['ff'] + $counters['low'] ) + < ( $counters['ctrl'] + $counters['high'] ) * 16 ) + { + $kindOfBinary = true; + $type = $binaryType ? $binaryType : $textType; + if ( $type === false ) { + $type = 'application/octet-stream'; + } + } else { + $kindOfBinary = false; + $type = $textType ? $textType : $binaryType; + if ( $type === false ) { + $type = 'text/plain'; + } + } + + // Check if the output format is ambiguous + // This generally means that detection failed, real types aren't ambiguous + $detectedFormat = $this->getDataFormat( $version, $type ); + if ( $detectedFormat != 'ambiguous' ) { + return $type; + } + + if ( $proposedFormat != 'ambiguous' ) { + // FormatAgreesWithData() + if ( $proposedFormat == 'text' && !$kindOfBinary ) { + return $proposed; + } + if ( $proposedFormat == 'binary' && $kindOfBinary ) { + return $proposed; + } + if ( $proposedFormat == 'html' ) { + return $proposed; + } + } + + // Find a MIME type by searching the registry for the file extension. + $dotPos = strrpos( $fileName, '.' ); + if ( $dotPos === false ) { + return $type; + } + $ext = substr( $fileName, $dotPos ); + if ( isset( $this->registry[$ext] ) ) { + return $this->registry[$ext]; + } + + // TODO: If the extension has an application registered to it, IE will return + // application/octet-stream. We'll skip that, so we could erroneously + // return text/plain or application/x-netcdf where application/octet-stream + // would be correct. + + return $type; + } + + /** + * Check for text headers at the start of the chunk + * Confirmed same in 5 and 7. + */ + private function checkTextHeaders( $version, $chunk ) { + $chunk2 = substr( $chunk, 0, 2 ); + $chunk4 = substr( $chunk, 0, 4 ); + $chunk5 = substr( $chunk, 0, 5 ); + if ( $chunk4 == '%PDF' ) { + return 'application/pdf'; + } + if ( $chunk2 == '%!' ) { + return 'application/postscript'; + } + if ( $chunk5 == '{\\rtf' ) { + return 'text/richtext'; + } + if ( $chunk5 == 'begin' ) { + return 'application/base64'; + } + return false; + } + + /** + * Check for binary headers at the start of the chunk + * Confirmed same in 5 and 7. + */ + private function checkBinaryHeaders( $version, $chunk ) { + $chunk2 = substr( $chunk, 0, 2 ); + $chunk3 = substr( $chunk, 0, 3 ); + $chunk4 = substr( $chunk, 0, 4 ); + $chunk5 = substr( $chunk, 0, 5 ); + $chunk8 = substr( $chunk, 0, 8 ); + if ( $chunk5 == 'GIF87' || $chunk5 == 'GIF89' ) { + return 'image/gif'; + } + if ( $chunk2 == "\xff\xd8" ) { + return 'image/pjpeg'; // actually plain JPEG but this is what IE returns + } + + if ( $chunk2 == 'BM' + && substr( $chunk, 6, 2 ) == "\000\000" + && substr( $chunk, 8, 2 ) != "\000\000" ) + { + return 'image/bmp'; // another non-standard MIME + } + if ( $chunk4 == 'RIFF' + && substr( $chunk, 8, 4 ) == 'WAVE' ) + { + return 'audio/wav'; + } + // These were integer literals in IE + // Perhaps the author was not sure what the target endianness was + if ( $chunk4 == ".sd\000" + || $chunk4 == ".snd" + || $chunk4 == "\000ds." + || $chunk4 == "dns." ) + { + return 'audio/basic'; + } + if ( $chunk3 == "MM\000" ) { + return 'image/tiff'; + } + if ( $chunk2 == 'MZ' ) { + return 'application/x-msdownload'; + } + if ( $chunk8 == "\x89PNG\x0d\x0a\x1a\x0a" ) { + return 'image/x-png'; // [sic] + } + if ( strlen( $chunk ) >= 5 ) { + $byte2 = ord( $chunk[2] ); + $byte4 = ord( $chunk[4] ); + if ( $byte2 >= 3 && $byte2 <= 31 && $byte4 == 0 && $chunk2 == 'JG' ) { + return 'image/x-jg'; + } + } + // More endian confusion? + if ( $chunk4 == 'MROF' ) { + return 'audio/x-aiff'; + } + $chunk4_8 = substr( $chunk, 8, 4 ); + if ( $chunk4 == 'FORM' && ( $chunk4_8 == 'AIFF' || $chunk4_8 == 'AIFC' ) ) { + return 'audio/x-aiff'; + } + if ( $chunk4 == 'RIFF' && $chunk4_8 == 'AVI ' ) { + return 'video/avi'; + } + if ( $chunk4 == "\x00\x00\x01\xb3" || $chunk4 == "\x00\x00\x01\xba" ) { + return 'video/mpeg'; + } + if ( $chunk4 == "\001\000\000\000" + && substr( $chunk, 40, 4 ) == ' EMF' ) + { + return 'image/x-emf'; + } + if ( $chunk4 == "\xd7\xcd\xc6\x9a" ) { + return 'image/x-wmf'; + } + if ( $chunk4 == "\xca\xfe\xba\xbe" ) { + return 'application/java'; + } + if ( $chunk2 == 'PK' ) { + return 'application/x-zip-compressed'; + } + if ( $chunk2 == "\x1f\x9d" ) { + return 'application/x-compressed'; + } + if ( $chunk2 == "\x1f\x8b" ) { + return 'application/x-gzip-compressed'; + } + // Skip redundant check for ZIP + if ( $chunk5 == "MThd\000" ) { + return 'audio/mid'; + } + if ( $chunk4 == '%PDF' ) { + return 'application/pdf'; + } + return false; + } + + /** + * Do heuristic checks on the bulk of the data sample. + * Search for HTML tags. + */ + protected function sampleData( $version, $chunk ) { + $found = array(); + $counters = array( + 'ctrl' => 0, + 'high' => 0, + 'low' => 0, + 'lf' => 0, + 'cr' => 0, + 'ff' => 0 + ); + $htmlTags = array( + 'html', + 'head', + 'title', + 'body', + 'script', + 'a href', + 'pre', + 'img', + 'plaintext', + 'table' + ); + $rdfUrl = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $rdfPurl = 'http://purl.org/rss/1.0/'; + $xbmMagic1 = '#define'; + $xbmMagic2 = '_width'; + $xbmMagic3 = '_bits'; + $binhexMagic = 'converted with BinHex'; + + for ( $offset = 0; $offset < strlen( $chunk ); $offset++ ) { + $curChar = $chunk[$offset]; + if ( $curChar == "\x0a" ) { + $counters['lf']++; + continue; + } elseif ( $curChar == "\x0d" ) { + $counters['cr']++; + continue; + } elseif ( $curChar == "\x0c" ) { + $counters['ff']++; + continue; + } elseif ( $curChar == "\t" ) { + $counters['low']++; + continue; + } elseif ( ord( $curChar ) < 32 ) { + $counters['ctrl']++; + continue; + } elseif ( ord( $curChar ) >= 128 ) { + $counters['high']++; + continue; + } + + $counters['low']++; + if ( $curChar == '<' ) { + // XML + $remainder = substr( $chunk, $offset + 1 ); + if ( !strncasecmp( $remainder, '?XML', 4 ) ) { + $nextChar = substr( $chunk, $offset + 5, 1 ); + if ( $nextChar == ':' || $nextChar == ' ' || $nextChar == "\t" ) { + $found['xml'] = true; + } + } + // Scriptlet (JSP) + if ( !strncasecmp( $remainder, 'SCRIPTLET', 9 ) ) { + $found['scriptlet'] = true; + break; + } + // HTML + foreach ( $htmlTags as $tag ) { + if ( !strncasecmp( $remainder, $tag, strlen( $tag ) ) ) { + $found['html'] = true; + } + } + // Skip broken check for additional tags (HR etc.) + + // CHANNEL replaced by RSS, RDF and FEED in IE 7 + if ( $version < 'ie07' ) { + if ( !strncasecmp( $remainder, 'CHANNEL', 7 ) ) { + $found['cdf'] = true; + } + } else { + // RSS + if ( !strncasecmp( $remainder, 'RSS', 3 ) ) { + $found['rss'] = true; + break; // return from SampleData + } + if ( !strncasecmp( $remainder, 'rdf:RDF', 7 ) ) { + $found['rdf-tag'] = true; + // no break + } + if ( !strncasecmp( $remainder, 'FEED', 4 ) ) { + $found['atom'] = true; + break; + } + } + continue; + } + // Skip broken check for --> + + // RSS URL checks + // For some reason both URLs must appear before it is recognised + $remainder = substr( $chunk, $offset ); + if ( !strncasecmp( $remainder, $rdfUrl, strlen( $rdfUrl ) ) ) { + $found['rdf-url'] = true; + if ( isset( $found['rdf-tag'] ) + && isset( $found['rdf-purl'] ) ) // [sic] + { + break; + } + continue; + } + + if ( !strncasecmp( $remainder, $rdfPurl, strlen( $rdfPurl ) ) ) { + if ( isset( $found['rdf-tag'] ) + && isset( $found['rdf-url'] ) ) // [sic] + { + break; + } + continue; + } + + // XBM checks + if ( !strncasecmp( $remainder, $xbmMagic1, strlen( $xbmMagic1 ) ) ) { + $found['xbm1'] = true; + continue; + } + if ( $curChar == '_' ) { + if ( isset( $found['xbm2'] ) ) { + if ( !strncasecmp( $remainder, $xbmMagic3, strlen( $xbmMagic3 ) ) ) { + $found['xbm'] = true; + break; + } + } elseif ( isset( $found['xbm1'] ) ) { + if ( !strncasecmp( $remainder, $xbmMagic2, strlen( $xbmMagic2 ) ) ) { + $found['xbm2'] = true; + } + } + } + + // BinHex + if ( !strncasecmp( $remainder, $binhexMagic, strlen( $binhexMagic ) ) ) { + $found['binhex'] = true; + } + } + return array( 'found' => $found, 'counters' => $counters ); + } + + protected function getDataFormat( $version, $type ) { + $types = $this->typeTable[$version]; + if ( $type == '(null)' || strval( $type ) === '' ) { + return 'ambiguous'; + } + foreach ( $types as $format => $list ) { + if ( in_array( $type, $list ) ) { + return $format; + } + } + return 'unknown'; + } +} + diff --git a/includes/Image.php b/includes/Image.php deleted file mode 100644 index e085936c..00000000 --- a/includes/Image.php +++ /dev/null @@ -1,2142 +0,0 @@ -<?php -/** - */ - -/** - * NOTE FOR WINDOWS USERS: - * To enable EXIF functions, add the folloing lines to the - * "Windows extensions" section of php.ini: - * - * extension=extensions/php_mbstring.dll - * extension=extensions/php_exif.dll - */ - -/** - * Bump this number when serialized cache records may be incompatible. - */ -define( 'MW_IMAGE_VERSION', 2 ); - -/** - * Class to represent an image - * - * Provides methods to retrieve paths (physical, logical, URL), - * to generate thumbnails or for uploading. - * - * @addtogroup Media - */ -class Image -{ - const DELETED_FILE = 1; - const DELETED_COMMENT = 2; - const DELETED_USER = 4; - const DELETED_RESTRICTED = 8; - const RENDER_NOW = 1; - - /**#@+ - * @private - */ - var $name, # name of the image (constructor) - $imagePath, # Path of the image (loadFromXxx) - $url, # Image URL (accessor) - $title, # Title object for this image (constructor) - $fileExists, # does the image file exist on disk? (loadFromXxx) - $fromSharedDirectory, # load this image from $wgSharedUploadDirectory (loadFromXxx) - $historyLine, # Number of line to return by nextHistoryLine() (constructor) - $historyRes, # result of the query for the image's history (nextHistoryLine) - $width, # \ - $height, # | - $bits, # --- returned by getimagesize (loadFromXxx) - $attr, # / - $type, # MEDIATYPE_xxx (bitmap, drawing, audio...) - $mime, # MIME type, determined by MimeMagic::guessMimeType - $extension, # The file extension (constructor) - $size, # Size in bytes (loadFromXxx) - $metadata, # Metadata - $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx) - $page, # Page to render when creating thumbnails - $lastError; # Error string associated with a thumbnail display error - - - /**#@-*/ - - /** - * Create an Image object from an image name - * - * @param string $name name of the image, used to create a title object using Title::makeTitleSafe - * @public - */ - public static function newFromName( $name ) { - $title = Title::makeTitleSafe( NS_IMAGE, $name ); - if ( is_object( $title ) ) { - return new Image( $title ); - } else { - return NULL; - } - } - - /** - * Obsolete factory function, use constructor - * @deprecated - */ - function newFromTitle( $title ) { - return new Image( $title ); - } - - function Image( $title ) { - if( !is_object( $title ) ) { - throw new MWException( 'Image constructor given bogus title.' ); - } - $this->title =& $title; - $this->name = $title->getDBkey(); - $this->metadata = ''; - - $n = strrpos( $this->name, '.' ); - $this->extension = Image::normalizeExtension( $n ? - substr( $this->name, $n + 1 ) : '' ); - $this->historyLine = 0; - - $this->dataLoaded = false; - } - - /** - * Normalize a file extension to the common form, and ensure it's clean. - * Extensions with non-alphanumeric characters will be discarded. - * - * @param $ext string (without the .) - * @return string - */ - static function normalizeExtension( $ext ) { - $lower = strtolower( $ext ); - $squish = array( - 'htm' => 'html', - 'jpeg' => 'jpg', - 'mpeg' => 'mpg', - 'tiff' => 'tif' ); - if( isset( $squish[$lower] ) ) { - return $squish[$lower]; - } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) { - return $lower; - } else { - return ''; - } - } - - /** - * Get the memcached keys - * Returns an array, first element is the local cache key, second is the shared cache key, if there is one - */ - function getCacheKeys( ) { - global $wgUseSharedUploads, $wgSharedUploadDBname, $wgCacheSharedUploads; - - $hashedName = md5($this->name); - $keys = array( wfMemcKey( 'Image', $hashedName ) ); - if ( $wgUseSharedUploads && $wgSharedUploadDBname && $wgCacheSharedUploads ) { - $keys[] = wfForeignMemcKey( $wgSharedUploadDBname, false, 'Image', $hashedName ); - } - return $keys; - } - - /** - * Try to load image metadata from memcached. Returns true on success. - */ - function loadFromCache() { - global $wgUseSharedUploads, $wgMemc; - wfProfileIn( __METHOD__ ); - $this->dataLoaded = false; - $keys = $this->getCacheKeys(); - $cachedValues = $wgMemc->get( $keys[0] ); - - // Check if the key existed and belongs to this version of MediaWiki - if (!empty($cachedValues) && is_array($cachedValues) - && isset($cachedValues['version']) && ( $cachedValues['version'] == MW_IMAGE_VERSION ) - && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) ) - { - if ( $wgUseSharedUploads && $cachedValues['fromShared']) { - # if this is shared file, we need to check if image - # in shared repository has not changed - if ( isset( $keys[1] ) ) { - $commonsCachedValues = $wgMemc->get( $keys[1] ); - if (!empty($commonsCachedValues) && is_array($commonsCachedValues) - && isset($commonsCachedValues['version']) - && ( $commonsCachedValues['version'] == MW_IMAGE_VERSION ) - && isset($commonsCachedValues['mime'])) { - wfDebug( "Pulling image metadata from shared repository cache\n" ); - $this->name = $commonsCachedValues['name']; - $this->imagePath = $commonsCachedValues['imagePath']; - $this->fileExists = $commonsCachedValues['fileExists']; - $this->width = $commonsCachedValues['width']; - $this->height = $commonsCachedValues['height']; - $this->bits = $commonsCachedValues['bits']; - $this->type = $commonsCachedValues['type']; - $this->mime = $commonsCachedValues['mime']; - $this->metadata = $commonsCachedValues['metadata']; - $this->size = $commonsCachedValues['size']; - $this->fromSharedDirectory = true; - $this->dataLoaded = true; - $this->imagePath = $this->getFullPath(true); - } - } - } else { - wfDebug( "Pulling image metadata from local cache\n" ); - $this->name = $cachedValues['name']; - $this->imagePath = $cachedValues['imagePath']; - $this->fileExists = $cachedValues['fileExists']; - $this->width = $cachedValues['width']; - $this->height = $cachedValues['height']; - $this->bits = $cachedValues['bits']; - $this->type = $cachedValues['type']; - $this->mime = $cachedValues['mime']; - $this->metadata = $cachedValues['metadata']; - $this->size = $cachedValues['size']; - $this->fromSharedDirectory = false; - $this->dataLoaded = true; - $this->imagePath = $this->getFullPath(); - } - } - if ( $this->dataLoaded ) { - wfIncrStats( 'image_cache_hit' ); - } else { - wfIncrStats( 'image_cache_miss' ); - } - - wfProfileOut( __METHOD__ ); - return $this->dataLoaded; - } - - /** - * Save the image metadata to memcached - */ - function saveToCache() { - global $wgMemc, $wgUseSharedUploads; - $this->load(); - $keys = $this->getCacheKeys(); - // We can't cache negative metadata for non-existent files, - // because if the file later appears in commons, the local - // keys won't be purged. - if ( $this->fileExists || !$wgUseSharedUploads ) { - $cachedValues = array( - 'version' => MW_IMAGE_VERSION, - 'name' => $this->name, - 'imagePath' => $this->imagePath, - 'fileExists' => $this->fileExists, - 'fromShared' => $this->fromSharedDirectory, - 'width' => $this->width, - 'height' => $this->height, - 'bits' => $this->bits, - 'type' => $this->type, - 'mime' => $this->mime, - 'metadata' => $this->metadata, - 'size' => $this->size ); - - $wgMemc->set( $keys[0], $cachedValues, 60 * 60 * 24 * 7 ); // A week - } else { - // However we should clear them, so they aren't leftover - // if we've deleted the file. - $wgMemc->delete( $keys[0] ); - } - } - - /** - * Load metadata from the file itself - */ - function loadFromFile() { - global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang; - wfProfileIn( __METHOD__ ); - $this->imagePath = $this->getFullPath(); - $this->fileExists = file_exists( $this->imagePath ); - $this->fromSharedDirectory = false; - $gis = array(); - - if (!$this->fileExists) wfDebug(__METHOD__.': '.$this->imagePath." not found locally!\n"); - - # If the file is not found, and a shared upload directory is used, look for it there. - if (!$this->fileExists && $wgUseSharedUploads && $wgSharedUploadDirectory) { - # In case we're on a wgCapitalLinks=false wiki, we - # capitalize the first letter of the filename before - # looking it up in the shared repository. - $sharedImage = Image::newFromName( $wgContLang->ucfirst($this->name) ); - $this->fileExists = $sharedImage && file_exists( $sharedImage->getFullPath(true) ); - if ( $this->fileExists ) { - $this->name = $sharedImage->name; - $this->imagePath = $this->getFullPath(true); - $this->fromSharedDirectory = true; - } - } - - - if ( $this->fileExists ) { - $magic=& MimeMagic::singleton(); - - $this->mime = $magic->guessMimeType($this->imagePath,true); - $this->type = $magic->getMediaType($this->imagePath,$this->mime); - $handler = MediaHandler::getHandler( $this->mime ); - - # Get size in bytes - $this->size = filesize( $this->imagePath ); - - # Height, width and metadata - if ( $handler ) { - $gis = $handler->getImageSize( $this, $this->imagePath ); - $this->metadata = $handler->getMetadata( $this, $this->imagePath ); - } else { - $gis = false; - $this->metadata = ''; - } - - wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n"); - } - else { - $this->mime = NULL; - $this->type = MEDIATYPE_UNKNOWN; - $this->metadata = ''; - wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n"); - } - - if( $gis ) { - $this->width = $gis[0]; - $this->height = $gis[1]; - } else { - $this->width = 0; - $this->height = 0; - } - - #NOTE: $gis[2] contains a code for the image type. This is no longer used. - - #NOTE: we have to set this flag early to avoid load() to be called - # be some of the functions below. This may lead to recursion or other bad things! - # as ther's only one thread of execution, this should be safe anyway. - $this->dataLoaded = true; - - if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits']; - else $this->bits = 0; - - wfProfileOut( __METHOD__ ); - } - - /** - * Load image metadata from the DB - */ - function loadFromDB() { - global $wgUseSharedUploads, $wgSharedUploadDBname, $wgSharedUploadDBprefix, $wgContLang; - wfProfileIn( __METHOD__ ); - - $dbr = wfGetDB( DB_SLAVE ); - $this->checkDBSchema($dbr); - - $row = $dbr->selectRow( 'image', - array( 'img_size', 'img_width', 'img_height', 'img_bits', - 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata' ), - array( 'img_name' => $this->name ), __METHOD__ ); - if ( $row ) { - $this->fromSharedDirectory = false; - $this->fileExists = true; - $this->loadFromRow( $row ); - $this->imagePath = $this->getFullPath(); - // Check for rows from a previous schema, quietly upgrade them - $this->maybeUpgradeRow(); - } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) { - # In case we're on a wgCapitalLinks=false wiki, we - # capitalize the first letter of the filename before - # looking it up in the shared repository. - $name = $wgContLang->ucfirst($this->name); - $dbc = Image::getCommonsDB(); - - $row = $dbc->selectRow( "`$wgSharedUploadDBname`.{$wgSharedUploadDBprefix}image", - array( - 'img_size', 'img_width', 'img_height', 'img_bits', - 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata' ), - array( 'img_name' => $name ), __METHOD__ ); - if ( $row ) { - $this->fromSharedDirectory = true; - $this->fileExists = true; - $this->imagePath = $this->getFullPath(true); - $this->name = $name; - $this->loadFromRow( $row ); - - // Check for rows from a previous schema, quietly upgrade them - $this->maybeUpgradeRow(); - } - } - - if ( !$row ) { - $this->size = 0; - $this->width = 0; - $this->height = 0; - $this->bits = 0; - $this->type = 0; - $this->fileExists = false; - $this->fromSharedDirectory = false; - $this->metadata = ''; - $this->mime = false; - } - - # Unconditionally set loaded=true, we don't want the accessors constantly rechecking - $this->dataLoaded = true; - wfProfileOut( __METHOD__ ); - } - - /* - * Load image metadata from a DB result row - */ - function loadFromRow( &$row ) { - $this->size = $row->img_size; - $this->width = $row->img_width; - $this->height = $row->img_height; - $this->bits = $row->img_bits; - $this->type = $row->img_media_type; - - $major= $row->img_major_mime; - $minor= $row->img_minor_mime; - - if (!$major) $this->mime = "unknown/unknown"; - else { - if (!$minor) $minor= "unknown"; - $this->mime = $major.'/'.$minor; - } - $this->metadata = $row->img_metadata; - - $this->dataLoaded = true; - } - - /** - * Load image metadata from cache or DB, unless already loaded - */ - function load() { - global $wgSharedUploadDBname, $wgUseSharedUploads; - if ( !$this->dataLoaded ) { - if ( !$this->loadFromCache() ) { - $this->loadFromDB(); - if ( !$wgSharedUploadDBname && $wgUseSharedUploads ) { - $this->loadFromFile(); - } elseif ( $this->fileExists || !$wgUseSharedUploads ) { - // We can do negative caching for local images, because the cache - // will be purged on upload. But we can't do it when shared images - // are enabled, since updates to that won't purge foreign caches. - $this->saveToCache(); - } - } - $this->dataLoaded = true; - } - } - - /** - * Upgrade a row if it needs it - */ - function maybeUpgradeRow() { - if ( is_null($this->type) || $this->mime == 'image/svg' ) { - $this->upgradeRow(); - } else { - $handler = $this->getHandler(); - if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) { - $this->upgradeRow(); - } - } - } - - /** - * Fix assorted version-related problems with the image row by reloading it from the file - */ - function upgradeRow() { - global $wgDBname, $wgSharedUploadDBname; - wfProfileIn( __METHOD__ ); - - $this->loadFromFile(); - - if ( $this->fromSharedDirectory ) { - if ( !$wgSharedUploadDBname ) { - wfProfileOut( __METHOD__ ); - return; - } - - // Write to the other DB using selectDB, not database selectors - // This avoids breaking replication in MySQL - $dbw = Image::getCommonsDB(); - } else { - $dbw = wfGetDB( DB_MASTER ); - } - - $this->checkDBSchema($dbw); - - list( $major, $minor ) = self::splitMime( $this->mime ); - - wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n"); - - $dbw->update( 'image', - array( - 'img_width' => $this->width, - 'img_height' => $this->height, - 'img_bits' => $this->bits, - 'img_media_type' => $this->type, - 'img_major_mime' => $major, - 'img_minor_mime' => $minor, - 'img_metadata' => $this->metadata, - ), array( 'img_name' => $this->name ), __METHOD__ - ); - if ( $this->fromSharedDirectory ) { - $dbw->selectDB( $wgDBname ); - } - wfProfileOut( __METHOD__ ); - } - - /** - * Split an internet media type into its two components; if not - * a two-part name, set the minor type to 'unknown'. - * - * @param $mime "text/html" etc - * @return array ("text", "html") etc - */ - static function splitMime( $mime ) { - if( strpos( $mime, '/' ) !== false ) { - return explode( '/', $mime, 2 ); - } else { - return array( $mime, 'unknown' ); - } - } - - /** - * Return the name of this image - * @public - */ - function getName() { - return $this->name; - } - - /** - * Return the associated title object - * @public - */ - function getTitle() { - return $this->title; - } - - /** - * Return the URL of the image file - * @public - */ - function getURL() { - if ( !$this->url ) { - $this->load(); - if($this->fileExists) { - $this->url = Image::imageUrl( $this->name, $this->fromSharedDirectory ); - } else { - $this->url = ''; - } - } - return $this->url; - } - - function getViewURL() { - if( $this->mustRender()) { - if( $this->canRender() ) { - return $this->createThumb( $this->getWidth() ); - } - else { - wfDebug('Image::getViewURL(): supposed to render '.$this->name.' ('.$this->mime."), but can't!\n"); - return $this->getURL(); #hm... return NULL? - } - } else { - return $this->getURL(); - } - } - - /** - * Return the image path of the image in the - * local file system as an absolute path - * @public - */ - function getImagePath() { - $this->load(); - return $this->imagePath; - } - - /** - * Return the width of the image - * - * Returns false on error - * @public - */ - function getWidth( $page = 1 ) { - $this->load(); - if ( $this->isMultipage() ) { - $dim = $this->getHandler()->getPageDimensions( $this, $page ); - if ( $dim ) { - return $dim['width']; - } else { - return false; - } - } else { - return $this->width; - } - } - - /** - * Return the height of the image - * - * Returns false on error - * @public - */ - function getHeight( $page = 1 ) { - $this->load(); - if ( $this->isMultipage() ) { - $dim = $this->getHandler()->getPageDimensions( $this, $page ); - if ( $dim ) { - return $dim['height']; - } else { - return false; - } - } else { - return $this->height; - } - } - - /** - * Get handler-specific metadata - */ - function getMetadata() { - $this->load(); - return $this->metadata; - } - - /** - * Return the size of the image file, in bytes - * @public - */ - function getSize() { - $this->load(); - return $this->size; - } - - /** - * Returns the mime type of the file. - */ - function getMimeType() { - $this->load(); - return $this->mime; - } - - /** - * Return the type of the media in the file. - * Use the value returned by this function with the MEDIATYPE_xxx constants. - */ - function getMediaType() { - $this->load(); - return $this->type; - } - - /** - * Checks if the file can be presented to the browser as a bitmap. - * - * Currently, this checks if the file is an image format - * that can be converted to a format - * supported by all browsers (namely GIF, PNG and JPEG), - * or if it is an SVG image and SVG conversion is enabled. - * - * @todo remember the result of this check. - */ - function canRender() { - $handler = $this->getHandler(); - return $handler && $handler->canRender(); - } - - /** - * Return true if the file is of a type that can't be directly - * rendered by typical browsers and needs to be re-rasterized. - * - * This returns true for everything but the bitmap types - * supported by all browsers, i.e. JPEG; GIF and PNG. It will - * also return true for any non-image formats. - * - * @return bool - */ - function mustRender() { - $handler = $this->getHandler(); - return $handler && $handler->mustRender(); - } - - /** - * Determines if this media file may be shown inline on a page. - * - * This is currently synonymous to canRender(), but this could be - * extended to also allow inline display of other media, - * like flash animations or videos. If you do so, please keep in mind that - * that could be a security risk. - */ - function allowInlineDisplay() { - return $this->canRender(); - } - - /** - * Determines if this media file is in a format that is unlikely to - * contain viruses or malicious content. It uses the global - * $wgTrustedMediaFormats list to determine if the file is safe. - * - * This is used to show a warning on the description page of non-safe files. - * It may also be used to disallow direct [[media:...]] links to such files. - * - * Note that this function will always return true if allowInlineDisplay() - * or isTrustedFile() is true for this file. - */ - function isSafeFile() { - if ($this->allowInlineDisplay()) return true; - if ($this->isTrustedFile()) return true; - - global $wgTrustedMediaFormats; - - $type= $this->getMediaType(); - $mime= $this->getMimeType(); - #wfDebug("Image::isSafeFile: type= $type, mime= $mime\n"); - - if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted - if ( in_array( $type, $wgTrustedMediaFormats) ) return true; - - if ($mime==="unknown/unknown") return false; #unknown type, not trusted - if ( in_array( $mime, $wgTrustedMediaFormats) ) return true; - - return false; - } - - /** Returns true if the file is flagged as trusted. Files flagged that way - * can be linked to directly, even if that is not allowed for this type of - * file normally. - * - * This is a dummy function right now and always returns false. It could be - * implemented to extract a flag from the database. The trusted flag could be - * set on upload, if the user has sufficient privileges, to bypass script- - * and html-filters. It may even be coupled with cryptographics signatures - * or such. - */ - function isTrustedFile() { - #this could be implemented to check a flag in the databas, - #look for signatures, etc - return false; - } - - /** - * Return the escapeLocalURL of this image - * @public - */ - function getEscapeLocalURL( $query=false) { - return $this->getTitle()->escapeLocalURL( $query ); - } - - /** - * Return the escapeFullURL of this image - * @public - */ - function getEscapeFullURL() { - $this->getTitle(); - return $this->title->escapeFullURL(); - } - - /** - * Return the URL of an image, provided its name. - * - * @param string $name Name of the image, without the leading "Image:" - * @param boolean $fromSharedDirectory Should this be in $wgSharedUploadPath? - * @return string URL of $name image - * @public - * @static - */ - function imageUrl( $name, $fromSharedDirectory = false ) { - global $wgUploadPath,$wgUploadBaseUrl,$wgSharedUploadPath; - if($fromSharedDirectory) { - $base = ''; - $path = $wgSharedUploadPath; - } else { - $base = $wgUploadBaseUrl; - $path = $wgUploadPath; - } - $url = "{$base}{$path}" . wfGetHashPath($name, $fromSharedDirectory) . "{$name}"; - return wfUrlencode( $url ); - } - - /** - * Returns true if the image file exists on disk. - * @return boolean Whether image file exist on disk. - * @public - */ - function exists() { - $this->load(); - return $this->fileExists; - } - - /** - * @todo document - * @private - */ - function thumbUrlFromName( $thumbName, $subdir = 'thumb' ) { - global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath; - if($this->fromSharedDirectory) { - $base = ''; - $path = $wgSharedUploadPath; - } else { - $base = $wgUploadBaseUrl; - $path = $wgUploadPath; - } - if ( Image::isHashed( $this->fromSharedDirectory ) ) { - $hashdir = wfGetHashPath($this->name, $this->fromSharedDirectory) . - wfUrlencode( $this->name ); - } else { - $hashdir = ''; - } - $url = "{$base}{$path}/{$subdir}{$hashdir}/" . wfUrlencode( $thumbName ); - return $url; - } - - /** - * @deprecated Use $image->transform()->getUrl() or thumbUrlFromName() - */ - function thumbUrl( $width, $subdir = 'thumb' ) { - $name = $this->thumbName( array( 'width' => $width ) ); - if ( strval( $name ) !== '' ) { - return array( false, $this->thumbUrlFromName( $name, $subdir ) ); - } else { - return array( false, false ); - } - } - - function getTransformScript() { - global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath; - if ( $this->fromSharedDirectory ) { - $script = $wgSharedThumbnailScriptPath; - } else { - $script = $wgThumbnailScriptPath; - } - if ( $script ) { - return "$script?f=" . urlencode( $this->name ); - } else { - return false; - } - } - - /** - * Get a ThumbnailImage which is the same size as the source - */ - function getUnscaledThumb( $page = false ) { - if ( $page ) { - $params = array( - 'page' => $page, - 'width' => $this->getWidth( $page ) - ); - } else { - $params = array( 'width' => $this->getWidth() ); - } - return $this->transform( $params ); - } - - /** - * Return the file name of a thumbnail with the specified parameters - * - * @param array $params Handler-specific parameters - * @private - */ - function thumbName( $params ) { - $handler = $this->getHandler(); - if ( !$handler ) { - return null; - } - list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime ); - $thumbName = $handler->makeParamString( $params ) . '-' . $this->name; - if ( $thumbExt != $this->extension ) { - $thumbName .= ".$thumbExt"; - } - return $thumbName; - } - - /** - * Create a thumbnail of the image having the specified width/height. - * The thumbnail will not be created if the width is larger than the - * image's width. Let the browser do the scaling in this case. - * The thumbnail is stored on disk and is only computed if the thumbnail - * file does not exist OR if it is older than the image. - * Returns the URL. - * - * Keeps aspect ratio of original image. If both width and height are - * specified, the generated image will be no bigger than width x height, - * and will also have correct aspect ratio. - * - * @param integer $width maximum width of the generated thumbnail - * @param integer $height maximum height of the image (optional) - * @public - */ - function createThumb( $width, $height = -1 ) { - $params = array( 'width' => $width ); - if ( $height != -1 ) { - $params['height'] = $height; - } - $thumb = $this->transform( $params ); - if( is_null( $thumb ) || $thumb->isError() ) return ''; - return $thumb->getUrl(); - } - - /** - * As createThumb, but returns a ThumbnailImage object. This can - * provide access to the actual file, the real size of the thumb, - * and can produce a convenient <img> tag for you. - * - * For non-image formats, this may return a filetype-specific icon. - * - * @param integer $width maximum width of the generated thumbnail - * @param integer $height maximum height of the image (optional) - * @param boolean $render True to render the thumbnail if it doesn't exist, - * false to just return the URL - * - * @return ThumbnailImage or null on failure - * @public - * - * @deprecated use transform() - */ - function getThumbnail( $width, $height=-1, $render = true ) { - $params = array( 'width' => $width ); - if ( $height != -1 ) { - $params['height'] = $height; - } - $flags = $render ? self::RENDER_NOW : 0; - return $this->transform( $params, $flags ); - } - - /** - * Transform a media file - * - * @param array $params An associative array of handler-specific parameters. Typical - * keys are width, height and page. - * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering - * @return MediaTransformOutput - */ - function transform( $params, $flags = 0 ) { - global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors; - - wfProfileIn( __METHOD__ ); - do { - $handler = $this->getHandler(); - if ( !$handler || !$handler->canRender() ) { - // not a bitmap or renderable image, don't try. - $thumb = $this->iconThumb(); - break; - } - - $script = $this->getTransformScript(); - if ( $script && !($flags & self::RENDER_NOW) ) { - // Use a script to transform on client request - $thumb = $handler->getScriptedTransform( $this, $script, $params ); - break; - } - - $normalisedParams = $params; - $handler->normaliseParams( $this, $normalisedParams ); - list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime ); - $thumbName = $this->thumbName( $normalisedParams ); - $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName"; - $thumbUrl = $this->thumbUrlFromName( $thumbName ); - - - if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) { - $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); - break; - } - - wfDebug( "Doing stat for $thumbPath\n" ); - $this->migrateThumbFile( $thumbName ); - if ( file_exists( $thumbPath ) ) { - $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); - break; - } - - $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params ); - - // Ignore errors if requested - if ( !$thumb ) { - $thumb = null; - } elseif ( $thumb->isError() ) { - $this->lastError = $thumb->toText(); - if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) { - $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); - } - } - - if ( $wgUseSquid ) { - wfPurgeSquidServers( array( $thumbUrl ) ); - } - } while (false); - - wfProfileOut( __METHOD__ ); - return $thumb; - } - - /** - * Fix thumbnail files from 1.4 or before, with extreme prejudice - */ - function migrateThumbFile( $thumbName ) { - $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory ); - $thumbPath = "$thumbDir/$thumbName"; - if ( is_dir( $thumbPath ) ) { - // Directory where file should be - // This happened occasionally due to broken migration code in 1.5 - // Rename to broken-* - global $wgUploadDirectory; - for ( $i = 0; $i < 100 ; $i++ ) { - $broken = "$wgUploadDirectory/broken-$i-$thumbName"; - if ( !file_exists( $broken ) ) { - rename( $thumbPath, $broken ); - break; - } - } - // Doesn't exist anymore - clearstatcache(); - } - if ( is_file( $thumbDir ) ) { - // File where directory should be - unlink( $thumbDir ); - // Doesn't exist anymore - clearstatcache(); - } - } - - /** - * Get a MediaHandler instance for this image - */ - function getHandler() { - return MediaHandler::getHandler( $this->getMimeType() ); - } - - /** - * Get a ThumbnailImage representing a file type icon - * @return ThumbnailImage - */ - function iconThumb() { - global $wgStylePath, $wgStyleDirectory; - - $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' ); - foreach( $try as $icon ) { - $path = '/common/images/icons/' . $icon; - $filepath = $wgStyleDirectory . $path; - if( file_exists( $filepath ) ) { - return new ThumbnailImage( $wgStylePath . $path, 120, 120 ); - } - } - return null; - } - - /** - * Get last thumbnailing error. - * Largely obsolete. - */ - function getLastError() { - return $this->lastError; - } - - /** - * Get all thumbnail names previously generated for this image - */ - function getThumbnails( $shared = false ) { - if ( Image::isHashed( $shared ) ) { - $this->load(); - $files = array(); - $dir = wfImageThumbDir( $this->name, $shared ); - - if ( is_dir( $dir ) ) { - $handle = opendir( $dir ); - - if ( $handle ) { - while ( false !== ( $file = readdir($handle) ) ) { - if ( $file{0} != '.' ) { - $files[] = $file; - } - } - closedir( $handle ); - } - } - } else { - $files = array(); - } - - return $files; - } - - /** - * Refresh metadata in memcached, but don't touch thumbnails or squid - */ - function purgeMetadataCache() { - clearstatcache(); - $this->loadFromFile(); - $this->saveToCache(); - } - - /** - * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid - */ - function purgeCache( $archiveFiles = array(), $shared = false ) { - global $wgUseSquid; - - // Refresh metadata cache - $this->purgeMetadataCache(); - - // Delete thumbnails - $files = $this->getThumbnails( $shared ); - $dir = wfImageThumbDir( $this->name, $shared ); - $urls = array(); - foreach ( $files as $file ) { - $m = array(); - # Check that the base image name is part of the thumb name - # This is a basic sanity check to avoid erasing unrelated directories - if ( strpos( $file, $this->name ) !== false ) { - $url = $this->thumbUrlFromName( $file ); - $urls[] = $url; - @unlink( "$dir/$file" ); - } - } - - // Purge the squid - if ( $wgUseSquid ) { - $urls[] = $this->getURL(); - foreach ( $archiveFiles as $file ) { - $urls[] = wfImageArchiveUrl( $file ); - } - wfPurgeSquidServers( $urls ); - } - } - - /** - * Purge the image description page, but don't go after - * pages using the image. Use when modifying file history - * but not the current data. - */ - function purgeDescription() { - $page = Title::makeTitle( NS_IMAGE, $this->name ); - $page->invalidateCache(); - $page->purgeSquid(); - } - - /** - * Purge metadata and all affected pages when the image is created, - * deleted, or majorly updated. A set of additional URLs may be - * passed to purge, such as specific image files which have changed. - * @param $urlArray array - */ - function purgeEverything( $urlArr=array() ) { - // Delete thumbnails and refresh image metadata cache - $this->purgeCache(); - $this->purgeDescription(); - - // Purge cache of all pages using this image - $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ); - $update->doUpdate(); - } - - /** - * Check the image table schema on the given connection for subtle problems - */ - function checkDBSchema(&$db) { - static $checkDone = false; - global $wgCheckDBSchema; - if (!$wgCheckDBSchema || $checkDone) { - return; - } - # img_name must be unique - if ( !$db->indexUnique( 'image', 'img_name' ) && !$db->indexExists('image','PRIMARY') ) { - throw new MWException( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' ); - } - $checkDone = true; - - # new fields must exist - # - # Not really, there's hundreds of checks like this that we could do and they're all pointless, because - # if the fields are missing, the database will loudly report a query error, the first time you try to do - # something. The only reason I put the above schema check in was because the absence of that particular - # index would lead to an annoying subtle bug. No error message, just some very odd behaviour on duplicate - # uploads. -- TS - /* - if ( !$db->fieldExists( 'image', 'img_media_type' ) - || !$db->fieldExists( 'image', 'img_metadata' ) - || !$db->fieldExists( 'image', 'img_width' ) ) { - - throw new MWException( 'Database schema not up to date, please run maintenance/update.php' ); - } - */ - } - - /** - * Return the image history of this image, line by line. - * starts with current version, then old versions. - * uses $this->historyLine to check which line to return: - * 0 return line for current version - * 1 query for old versions, return first one - * 2, ... return next old version from above query - * - * @public - */ - function nextHistoryLine() { - $dbr = wfGetDB( DB_SLAVE ); - - $this->checkDBSchema($dbr); - - if ( $this->historyLine == 0 ) {// called for the first time, return line from cur - $this->historyRes = $dbr->select( 'image', - array( - 'img_size', - 'img_description', - 'img_user','img_user_text', - 'img_timestamp', - 'img_width', - 'img_height', - "'' AS oi_archive_name" - ), - array( 'img_name' => $this->title->getDBkey() ), - __METHOD__ - ); - if ( 0 == $dbr->numRows( $this->historyRes ) ) { - return FALSE; - } - } else if ( $this->historyLine == 1 ) { - $this->historyRes = $dbr->select( 'oldimage', - array( - 'oi_size AS img_size', - 'oi_description AS img_description', - 'oi_user AS img_user', - 'oi_user_text AS img_user_text', - 'oi_timestamp AS img_timestamp', - 'oi_width as img_width', - 'oi_height as img_height', - 'oi_archive_name' - ), - array( 'oi_name' => $this->title->getDBkey() ), - __METHOD__, - array( 'ORDER BY' => 'oi_timestamp DESC' ) - ); - } - $this->historyLine ++; - - return $dbr->fetchObject( $this->historyRes ); - } - - /** - * Reset the history pointer to the first element of the history - * @public - */ - function resetHistory() { - $this->historyLine = 0; - } - - /** - * Return the full filesystem path to the file. Note that this does - * not mean that a file actually exists under that location. - * - * This path depends on whether directory hashing is active or not, - * i.e. whether the images are all found in the same directory, - * or in hashed paths like /images/3/3c. - * - * @public - * @param boolean $fromSharedDirectory Return the path to the file - * in a shared repository (see $wgUseSharedRepository and related - * options in DefaultSettings.php) instead of a local one. - * - */ - function getFullPath( $fromSharedRepository = false ) { - global $wgUploadDirectory, $wgSharedUploadDirectory; - - $dir = $fromSharedRepository ? $wgSharedUploadDirectory : - $wgUploadDirectory; - - // $wgSharedUploadDirectory may be false, if thumb.php is used - if ( $dir ) { - $fullpath = $dir . wfGetHashPath($this->name, $fromSharedRepository) . $this->name; - } else { - $fullpath = false; - } - - return $fullpath; - } - - /** - * @return bool - * @static - */ - public static function isHashed( $shared ) { - global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory; - return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory; - } - - /** - * Record an image upload in the upload log and the image table - */ - function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) { - global $wgUser, $wgUseCopyrightUpload; - - $dbw = wfGetDB( DB_MASTER ); - - $this->checkDBSchema($dbw); - - // Delete thumbnails and refresh the metadata cache - $this->purgeCache(); - - // Fail now if the image isn't there - if ( !$this->fileExists || $this->fromSharedDirectory ) { - wfDebug( "Image::recordUpload: File ".$this->imagePath." went missing!\n" ); - return false; - } - - if ( $wgUseCopyrightUpload ) { - if ( $license != '' ) { - $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; - } - $textdesc = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n" . - '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" . - "$licensetxt" . - '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ; - } else { - if ( $license != '' ) { - $filedesc = $desc == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n"; - $textdesc = $filedesc . - '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; - } else { - $textdesc = $desc; - } - } - - $now = $dbw->timestamp(); - - #split mime type - if (strpos($this->mime,'/')!==false) { - list($major,$minor)= explode('/',$this->mime,2); - } - else { - $major= $this->mime; - $minor= "unknown"; - } - - # Test to see if the row exists using INSERT IGNORE - # This avoids race conditions by locking the row until the commit, and also - # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. - $dbw->insert( 'image', - array( - 'img_name' => $this->name, - 'img_size'=> $this->size, - 'img_width' => intval( $this->width ), - 'img_height' => intval( $this->height ), - 'img_bits' => $this->bits, - 'img_media_type' => $this->type, - 'img_major_mime' => $major, - 'img_minor_mime' => $minor, - 'img_timestamp' => $now, - 'img_description' => $desc, - 'img_user' => $wgUser->getID(), - 'img_user_text' => $wgUser->getName(), - 'img_metadata' => $this->metadata, - ), - __METHOD__, - 'IGNORE' - ); - - if( $dbw->affectedRows() == 0 ) { - # Collision, this is an update of an image - # Insert previous contents into oldimage - $dbw->insertSelect( 'oldimage', 'image', - array( - 'oi_name' => 'img_name', - 'oi_archive_name' => $dbw->addQuotes( $oldver ), - 'oi_size' => 'img_size', - 'oi_width' => 'img_width', - 'oi_height' => 'img_height', - 'oi_bits' => 'img_bits', - 'oi_timestamp' => 'img_timestamp', - 'oi_description' => 'img_description', - 'oi_user' => 'img_user', - 'oi_user_text' => 'img_user_text', - ), array( 'img_name' => $this->name ), __METHOD__ - ); - - # Update the current image row - $dbw->update( 'image', - array( /* SET */ - 'img_size' => $this->size, - 'img_width' => intval( $this->width ), - 'img_height' => intval( $this->height ), - 'img_bits' => $this->bits, - 'img_media_type' => $this->type, - 'img_major_mime' => $major, - 'img_minor_mime' => $minor, - 'img_timestamp' => $now, - 'img_description' => $desc, - 'img_user' => $wgUser->getID(), - 'img_user_text' => $wgUser->getName(), - 'img_metadata' => $this->metadata, - ), array( /* WHERE */ - 'img_name' => $this->name - ), __METHOD__ - ); - } else { - # This is a new image - # Update the image count - $site_stats = $dbw->tableName( 'site_stats' ); - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ ); - } - - $descTitle = $this->getTitle(); - $article = new Article( $descTitle ); - $minor = false; - $watch = $watch || $wgUser->isWatched( $descTitle ); - $suppressRC = true; // There's already a log entry, so don't double the RC load - - if( $descTitle->exists() ) { - // TODO: insert a null revision into the page history for this update. - if( $watch ) { - $wgUser->addWatch( $descTitle ); - } - - # Invalidate the cache for the description page - $descTitle->invalidateCache(); - $descTitle->purgeSquid(); - } else { - // New image; create the description page. - $article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC ); - } - - # Hooks, hooks, the magic of hooks... - wfRunHooks( 'FileUpload', array( $this ) ); - - # Add the log entry - $log = new LogPage( 'upload' ); - $log->addEntry( 'upload', $descTitle, $desc ); - - # Commit the transaction now, in case something goes wrong later - # The most important thing is that images don't get lost, especially archives - $dbw->immediateCommit(); - - # Invalidate cache for all pages using this image - $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ); - $update->doUpdate(); - - return true; - } - - /** - * Get an array of Title objects which are articles which use this image - * Also adds their IDs to the link cache - * - * This is mostly copied from Title::getLinksTo() - * - * @deprecated Use HTMLCacheUpdate, this function uses too much memory - */ - function getLinksTo( $options = '' ) { - wfProfileIn( __METHOD__ ); - - if ( $options ) { - $db = wfGetDB( DB_MASTER ); - } else { - $db = wfGetDB( DB_SLAVE ); - } - $linkCache =& LinkCache::singleton(); - - list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' ); - $encName = $db->addQuotes( $this->name ); - $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options"; - $res = $db->query( $sql, __METHOD__ ); - - $retVal = array(); - if ( $db->numRows( $res ) ) { - while ( $row = $db->fetchObject( $res ) ) { - if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) { - $linkCache->addGoodLinkObj( $row->page_id, $titleObj ); - $retVal[] = $titleObj; - } - } - } - $db->freeResult( $res ); - wfProfileOut( __METHOD__ ); - return $retVal; - } - - function getExifData() { - global $wgRequest; - $handler = $this->getHandler(); - if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) { - return array(); - } - if ( !$this->metadata ) { - return array(); - } - $exif = unserialize( $this->metadata ); - if ( !$exif ) { - return array(); - } - unset( $exif['MEDIAWIKI_EXIF_VERSION'] ); - $format = new FormatExif( $exif ); - - return $format->getFormattedData(); - } - - /** - * Returns true if the image does not come from the shared - * image repository. - * - * @return bool - */ - function isLocal() { - return !$this->fromSharedDirectory; - } - - /** - * Was this image ever deleted from the wiki? - * - * @return bool - */ - function wasDeleted() { - $title = Title::makeTitle( NS_IMAGE, $this->name ); - return ( $title->isDeleted() > 0 ); - } - - /** - * Delete all versions of the image. - * - * Moves the files into an archive directory (or deletes them) - * and removes the database rows. - * - * Cache purging is done; logging is caller's responsibility. - * - * @param $reason - * @return true on success, false on some kind of failure - */ - function delete( $reason, $suppress=false ) { - $transaction = new FSTransaction(); - $urlArr = array( $this->getURL() ); - - if( !FileStore::lock() ) { - wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); - return false; - } - - try { - $dbw = wfGetDB( DB_MASTER ); - $dbw->begin(); - - // Delete old versions - $result = $dbw->select( 'oldimage', - array( 'oi_archive_name' ), - array( 'oi_name' => $this->name ) ); - - while( $row = $dbw->fetchObject( $result ) ) { - $oldName = $row->oi_archive_name; - - $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) ); - - // We'll need to purge this URL from caches... - $urlArr[] = wfImageArchiveUrl( $oldName ); - } - $dbw->freeResult( $result ); - - // And the current version... - $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) ); - - $dbw->immediateCommit(); - } catch( MWException $e ) { - wfDebug( __METHOD__.": db error, rolling back file transactions\n" ); - $transaction->rollback(); - FileStore::unlock(); - throw $e; - } - - wfDebug( __METHOD__.": deleted db items, applying file transactions\n" ); - $transaction->commit(); - FileStore::unlock(); - - - // Update site_stats - $site_stats = $dbw->tableName( 'site_stats' ); - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ ); - - $this->purgeEverything( $urlArr ); - - return true; - } - - - /** - * Delete an old version of the image. - * - * Moves the file into an archive directory (or deletes it) - * and removes the database row. - * - * Cache purging is done; logging is caller's responsibility. - * - * @param $reason - * @throws MWException or FSException on database or filestore failure - * @return true on success, false on some kind of failure - */ - function deleteOld( $archiveName, $reason, $suppress=false ) { - $transaction = new FSTransaction(); - $urlArr = array(); - - if( !FileStore::lock() ) { - wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); - return false; - } - - $transaction = new FSTransaction(); - try { - $dbw = wfGetDB( DB_MASTER ); - $dbw->begin(); - $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) ); - $dbw->immediateCommit(); - } catch( MWException $e ) { - wfDebug( __METHOD__.": db error, rolling back file transaction\n" ); - $transaction->rollback(); - FileStore::unlock(); - throw $e; - } - - wfDebug( __METHOD__.": deleted db items, applying file transaction\n" ); - $transaction->commit(); - FileStore::unlock(); - - $this->purgeDescription(); - - // Squid purging - global $wgUseSquid; - if ( $wgUseSquid ) { - $urlArr = array( - wfImageArchiveUrl( $archiveName ), - ); - wfPurgeSquidServers( $urlArr ); - } - return true; - } - - /** - * Delete the current version of a file. - * May throw a database error. - * @return true on success, false on failure - */ - private function prepareDeleteCurrent( $reason, $suppress=false ) { - return $this->prepareDeleteVersion( - $this->getFullPath(), - $reason, - 'image', - array( - 'fa_name' => 'img_name', - 'fa_archive_name' => 'NULL', - 'fa_size' => 'img_size', - 'fa_width' => 'img_width', - 'fa_height' => 'img_height', - 'fa_metadata' => 'img_metadata', - 'fa_bits' => 'img_bits', - 'fa_media_type' => 'img_media_type', - 'fa_major_mime' => 'img_major_mime', - 'fa_minor_mime' => 'img_minor_mime', - 'fa_description' => 'img_description', - 'fa_user' => 'img_user', - 'fa_user_text' => 'img_user_text', - 'fa_timestamp' => 'img_timestamp' ), - array( 'img_name' => $this->name ), - $suppress, - __METHOD__ ); - } - - /** - * Delete a given older version of a file. - * May throw a database error. - * @return true on success, false on failure - */ - private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) { - $oldpath = wfImageArchiveDir( $this->name ) . - DIRECTORY_SEPARATOR . $archiveName; - return $this->prepareDeleteVersion( - $oldpath, - $reason, - 'oldimage', - array( - 'fa_name' => 'oi_name', - 'fa_archive_name' => 'oi_archive_name', - 'fa_size' => 'oi_size', - 'fa_width' => 'oi_width', - 'fa_height' => 'oi_height', - 'fa_metadata' => 'NULL', - 'fa_bits' => 'oi_bits', - 'fa_media_type' => 'NULL', - 'fa_major_mime' => 'NULL', - 'fa_minor_mime' => 'NULL', - 'fa_description' => 'oi_description', - 'fa_user' => 'oi_user', - 'fa_user_text' => 'oi_user_text', - 'fa_timestamp' => 'oi_timestamp' ), - array( - 'oi_name' => $this->name, - 'oi_archive_name' => $archiveName ), - $suppress, - __METHOD__ ); - } - - /** - * Do the dirty work of backing up an image row and its file - * (if $wgSaveDeletedFiles is on) and removing the originals. - * - * Must be run while the file store is locked and a database - * transaction is open to avoid race conditions. - * - * @return FSTransaction - */ - private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) { - global $wgUser, $wgSaveDeletedFiles; - - // Dupe the file into the file store - if( file_exists( $path ) ) { - if( $wgSaveDeletedFiles ) { - $group = 'deleted'; - - $store = FileStore::get( $group ); - $key = FileStore::calculateKey( $path, $this->extension ); - $transaction = $store->insert( $key, $path, - FileStore::DELETE_ORIGINAL ); - } else { - $group = null; - $key = null; - $transaction = FileStore::deleteFile( $path ); - } - } else { - wfDebug( __METHOD__." deleting already-missing '$path'; moving on to database\n" ); - $group = null; - $key = null; - $transaction = new FSTransaction(); // empty - } - - if( $transaction === false ) { - // Fail to restore? - wfDebug( __METHOD__.": import to file store failed, aborting\n" ); - throw new MWException( "Could not archive and delete file $path" ); - return false; - } - - // Bitfields to further supress the image content - // Note that currently, live images are stored elsewhere - // and cannot be partially deleted - $bitfield = 0; - if ( $suppress ) { - $bitfield |= self::DELETED_FILE; - $bitfield |= self::DELETED_COMMENT; - $bitfield |= self::DELETED_USER; - $bitfield |= self::DELETED_RESTRICTED; - } - - $dbw = wfGetDB( DB_MASTER ); - $storageMap = array( - 'fa_storage_group' => $dbw->addQuotes( $group ), - 'fa_storage_key' => $dbw->addQuotes( $key ), - - 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ), - 'fa_deleted_timestamp' => $dbw->timestamp(), - 'fa_deleted_reason' => $dbw->addQuotes( $reason ), - 'fa_deleted' => $bitfield); - $allFields = array_merge( $storageMap, $fieldMap ); - - try { - if( $wgSaveDeletedFiles ) { - $dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname ); - } - $dbw->delete( $table, $where, $fname ); - } catch( DBQueryError $e ) { - // Something went horribly wrong! - // Leave the file as it was... - wfDebug( __METHOD__.": database error, rolling back file transaction\n" ); - $transaction->rollback(); - throw $e; - } - - return $transaction; - } - - /** - * Restore all or specified deleted revisions to the given file. - * Permissions and logging are left to the caller. - * - * May throw database exceptions on error. - * - * @param $versions set of record ids of deleted items to restore, - * or empty to restore all revisions. - * @return the number of file revisions restored if successful, - * or false on failure - */ - function restore( $versions=array(), $Unsuppress=false ) { - global $wgUser; - - if( !FileStore::lock() ) { - wfDebug( __METHOD__." could not acquire filestore lock\n" ); - return false; - } - - $transaction = new FSTransaction(); - try { - $dbw = wfGetDB( DB_MASTER ); - $dbw->begin(); - - // Re-confirm whether this image presently exists; - // if no we'll need to create an image record for the - // first item we restore. - $exists = $dbw->selectField( 'image', '1', - array( 'img_name' => $this->name ), - __METHOD__ ); - - // Fetch all or selected archived revisions for the file, - // sorted from the most recent to the oldest. - $conditions = array( 'fa_name' => $this->name ); - if( $versions ) { - $conditions['fa_id'] = $versions; - } - - $result = $dbw->select( 'filearchive', '*', - $conditions, - __METHOD__, - array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - - if( $dbw->numRows( $result ) < count( $versions ) ) { - // There's some kind of conflict or confusion; - // we can't restore everything we were asked to. - wfDebug( __METHOD__.": couldn't find requested items\n" ); - $dbw->rollback(); - FileStore::unlock(); - return false; - } - - if( $dbw->numRows( $result ) == 0 ) { - // Nothing to do. - wfDebug( __METHOD__.": nothing to do\n" ); - $dbw->rollback(); - FileStore::unlock(); - return true; - } - - $revisions = 0; - while( $row = $dbw->fetchObject( $result ) ) { - if ( $Unsuppress ) { - // Currently, fa_deleted flags fall off upon restore, lets be careful about this - } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) { - // Skip restoring file revisions that the user cannot restore - continue; - } - $revisions++; - $store = FileStore::get( $row->fa_storage_group ); - if( !$store ) { - wfDebug( __METHOD__.": skipping row with no file.\n" ); - continue; - } - - if( $revisions == 1 && !$exists ) { - $destDir = wfImageDir( $row->fa_name ); - if ( !is_dir( $destDir ) ) { - wfMkdirParents( $destDir ); - } - $destPath = $destDir . DIRECTORY_SEPARATOR . $row->fa_name; - - // We may have to fill in data if this was originally - // an archived file revision. - if( is_null( $row->fa_metadata ) ) { - $tempFile = $store->filePath( $row->fa_storage_key ); - - $magic = MimeMagic::singleton(); - $mime = $magic->guessMimeType( $tempFile, true ); - $media_type = $magic->getMediaType( $tempFile, $mime ); - list( $major_mime, $minor_mime ) = self::splitMime( $mime ); - $handler = MediaHandler::getHandler( $mime ); - if ( $handler ) { - $metadata = $handler->getMetadata( false, $tempFile ); - } else { - $metadata = ''; - } - } else { - $metadata = $row->fa_metadata; - $major_mime = $row->fa_major_mime; - $minor_mime = $row->fa_minor_mime; - $media_type = $row->fa_media_type; - } - - $table = 'image'; - $fields = array( - 'img_name' => $row->fa_name, - 'img_size' => $row->fa_size, - 'img_width' => $row->fa_width, - 'img_height' => $row->fa_height, - 'img_metadata' => $metadata, - 'img_bits' => $row->fa_bits, - 'img_media_type' => $media_type, - 'img_major_mime' => $major_mime, - 'img_minor_mime' => $minor_mime, - 'img_description' => $row->fa_description, - 'img_user' => $row->fa_user, - 'img_user_text' => $row->fa_user_text, - 'img_timestamp' => $row->fa_timestamp ); - } else { - $archiveName = $row->fa_archive_name; - if( $archiveName == '' ) { - // This was originally a current version; we - // have to devise a new archive name for it. - // Format is <timestamp of archiving>!<name> - $archiveName = - wfTimestamp( TS_MW, $row->fa_deleted_timestamp ) . - '!' . $row->fa_name; - } - $destDir = wfImageArchiveDir( $row->fa_name ); - if ( !is_dir( $destDir ) ) { - wfMkdirParents( $destDir ); - } - $destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName; - - $table = 'oldimage'; - $fields = array( - 'oi_name' => $row->fa_name, - 'oi_archive_name' => $archiveName, - 'oi_size' => $row->fa_size, - 'oi_width' => $row->fa_width, - 'oi_height' => $row->fa_height, - 'oi_bits' => $row->fa_bits, - 'oi_description' => $row->fa_description, - 'oi_user' => $row->fa_user, - 'oi_user_text' => $row->fa_user_text, - 'oi_timestamp' => $row->fa_timestamp ); - } - - $dbw->insert( $table, $fields, __METHOD__ ); - // @todo this delete is not totally safe, potentially - $dbw->delete( 'filearchive', - array( 'fa_id' => $row->fa_id ), - __METHOD__ ); - - // Check if any other stored revisions use this file; - // if so, we shouldn't remove the file from the deletion - // archives so they will still work. - $useCount = $dbw->selectField( 'filearchive', - 'COUNT(*)', - array( - 'fa_storage_group' => $row->fa_storage_group, - 'fa_storage_key' => $row->fa_storage_key ), - __METHOD__ ); - if( $useCount == 0 ) { - wfDebug( __METHOD__.": nothing else using {$row->fa_storage_key}, will deleting after\n" ); - $flags = FileStore::DELETE_ORIGINAL; - } else { - $flags = 0; - } - - $transaction->add( $store->export( $row->fa_storage_key, - $destPath, $flags ) ); - } - - $dbw->immediateCommit(); - } catch( MWException $e ) { - wfDebug( __METHOD__." caught error, aborting\n" ); - $transaction->rollback(); - throw $e; - } - - $transaction->commit(); - FileStore::unlock(); - - if( $revisions > 0 ) { - if( !$exists ) { - wfDebug( __METHOD__." restored $revisions items, creating a new current\n" ); - - // Update site_stats - $site_stats = $dbw->tableName( 'site_stats' ); - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ ); - - $this->purgeEverything(); - } else { - wfDebug( __METHOD__." restored $revisions as archived versions\n" ); - $this->purgeDescription(); - } - } - - return $revisions; - } - - /** - * Returns 'true' if this image is a multipage document, e.g. a DJVU - * document. - * - * @return Bool - */ - function isMultipage() { - $handler = $this->getHandler(); - return $handler && $handler->isMultiPage(); - } - - /** - * Returns the number of pages of a multipage document, or NULL for - * documents which aren't multipage documents - */ - function pageCount() { - $handler = $this->getHandler(); - if ( $handler && $handler->isMultiPage() ) { - return $handler->pageCount( $this ); - } else { - return null; - } - } - - static function getCommonsDB() { - static $dbc; - global $wgLoadBalancer, $wgSharedUploadDBname; - if ( !isset( $dbc ) ) { - $i = $wgLoadBalancer->getGroupIndex( 'commons' ); - $dbinfo = $wgLoadBalancer->mServers[$i]; - $dbc = new Database( $dbinfo['host'], $dbinfo['user'], - $dbinfo['password'], $wgSharedUploadDBname ); - } - return $dbc; - } - - /** - * Calculate the height of a thumbnail using the source and destination width - */ - static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) { - // Exact integer multiply followed by division - if ( $srcWidth == 0 ) { - return 0; - } else { - return round( $srcHeight * $dstWidth / $srcWidth ); - } - } - - /** - * Get an image size array like that returned by getimagesize(), or false if it - * can't be determined. - * - * @param string $fileName The filename - * @return array - */ - function getImageSize( $fileName ) { - $handler = $this->getHandler(); - return $handler->getImageSize( $this, $fileName ); - } - - /** - * Get the thumbnail extension and MIME type for a given source MIME type - * @return array thumbnail extension and MIME type - */ - static function getThumbType( $ext, $mime ) { - $handler = MediaHandler::getHandler( $mime ); - if ( $handler ) { - return $handler->getThumbType( $ext, $mime ); - } else { - return array( $ext, $mime ); - } - } - -} //class - - -/** - * @addtogroup Media - */ -class ArchivedFile -{ - /** - * Returns a file object from the filearchive table - * In the future, all current and old image storage - * may use FileStore. There will be a "old" storage - * for current and previous file revisions as well as - * the "deleted" group for archived revisions - * @param $title, the corresponding image page title - * @param $id, the image id, a unique key - * @param $key, optional storage key - * @return ResultWrapper - */ - function ArchivedFile( $title, $id=0, $key='' ) { - if( !is_object( $title ) ) { - throw new MWException( 'Image constructor given bogus title.' ); - } - $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'"; - if( $title->getNamespace() == NS_IMAGE ) { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'filearchive', - array( - 'fa_id', - 'fa_name', - 'fa_storage_key', - 'fa_storage_group', - 'fa_size', - 'fa_bits', - 'fa_width', - 'fa_height', - 'fa_metadata', - 'fa_media_type', - 'fa_major_mime', - 'fa_minor_mime', - 'fa_description', - 'fa_user', - 'fa_user_text', - 'fa_timestamp', - 'fa_deleted' ), - array( - 'fa_name' => $title->getDbKey(), - $conds ), - __METHOD__, - array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - - if ( $dbr->numRows( $res ) == 0 ) { - // this revision does not exist? - return; - } - $ret = $dbr->resultObject( $res ); - $row = $ret->fetchObject(); - - // initialize fields for filestore image object - $this->mId = intval($row->fa_id); - $this->mName = $row->fa_name; - $this->mGroup = $row->fa_storage_group; - $this->mKey = $row->fa_storage_key; - $this->mSize = $row->fa_size; - $this->mBits = $row->fa_bits; - $this->mWidth = $row->fa_width; - $this->mHeight = $row->fa_height; - $this->mMetaData = $row->fa_metadata; - $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime"; - $this->mType = $row->fa_media_type; - $this->mDescription = $row->fa_description; - $this->mUser = $row->fa_user; - $this->mUserText = $row->fa_user_text; - $this->mTimestamp = $row->fa_timestamp; - $this->mDeleted = $row->fa_deleted; - } else { - throw new MWException( 'This title does not correspond to an image page.' ); - return; - } - return true; - } - - /** - * int $field one of DELETED_* bitfield constants - * for file or revision rows - * @return bool - */ - function isDeleted( $field ) { - return ($this->mDeleted & $field) == $field; - } - - /** - * Determine if the current user is allowed to view a particular - * field of this FileStore image file, if it's marked as deleted. - * @param int $field - * @return bool - */ - function userCan( $field ) { - if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) { - // images - global $wgUser; - $permission = ( $this->mDeleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED - ? 'hiderevision' - : 'deleterevision'; - wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" ); - return $wgUser->isAllowed( $permission ); - } else { - return true; - } - } -} - -/** - * Aliases for backwards compatibility with 1.6 - */ -define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE ); -define( 'MW_IMG_DELETED_COMMENT', Image::DELETED_COMMENT ); -define( 'MW_IMG_DELETED_USER', Image::DELETED_USER ); -define( 'MW_IMG_DELETED_RESTRICTED', Image::DELETED_RESTRICTED ); - -?> diff --git a/includes/LoadBalancer.php b/includes/LoadBalancer.php deleted file mode 100644 index 0cdadd1e..00000000 --- a/includes/LoadBalancer.php +++ /dev/null @@ -1,653 +0,0 @@ -<?php -/** - * - */ - - -/** - * Database load balancing object - * - * @todo document - */ -class LoadBalancer { - /* private */ var $mServers, $mConnections, $mLoads, $mGroupLoads; - /* private */ var $mFailFunction, $mErrorConnection; - /* private */ var $mForce, $mReadIndex, $mLastIndex, $mAllowLagged; - /* private */ var $mWaitForFile, $mWaitForPos, $mWaitTimeout; - /* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error'; - - function __construct( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false ) - { - $this->mServers = $servers; - $this->mFailFunction = $failFunction; - $this->mReadIndex = -1; - $this->mWriteIndex = -1; - $this->mForce = -1; - $this->mConnections = array(); - $this->mLastIndex = -1; - $this->mLoads = array(); - $this->mWaitForFile = false; - $this->mWaitForPos = false; - $this->mWaitTimeout = $waitTimeout; - $this->mLaggedSlaveMode = false; - $this->mErrorConnection = false; - $this->mAllowLag = false; - - foreach( $servers as $i => $server ) { - $this->mLoads[$i] = $server['load']; - if ( isset( $server['groupLoads'] ) ) { - foreach ( $server['groupLoads'] as $group => $ratio ) { - if ( !isset( $this->mGroupLoads[$group] ) ) { - $this->mGroupLoads[$group] = array(); - } - $this->mGroupLoads[$group][$i] = $ratio; - } - } - } - if ( $waitForMasterNow ) { - $this->loadMasterPos(); - } - } - - static function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 ) - { - return new LoadBalancer( $servers, $failFunction, $waitTimeout ); - } - - /** - * Given an array of non-normalised probabilities, this function will select - * an element and return the appropriate key - */ - function pickRandom( $weights ) - { - if ( !is_array( $weights ) || count( $weights ) == 0 ) { - return false; - } - - $sum = array_sum( $weights ); - if ( $sum == 0 ) { - # No loads on any of them - # In previous versions, this triggered an unweighted random selection, - # but this feature has been removed as of April 2006 to allow for strict - # separation of query groups. - return false; - } - $max = mt_getrandmax(); - $rand = mt_rand(0, $max) / $max * $sum; - - $sum = 0; - foreach ( $weights as $i => $w ) { - $sum += $w; - if ( $sum >= $rand ) { - break; - } - } - return $i; - } - - function getRandomNonLagged( $loads ) { - # Unset excessively lagged servers - $lags = $this->getLagTimes(); - foreach ( $lags as $i => $lag ) { - if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) && - ( $lag === false || $lag > $this->mServers[$i]['max lag'] ) ) - { - unset( $loads[$i] ); - } - } - - # Find out if all the slaves with non-zero load are lagged - $sum = 0; - foreach ( $loads as $load ) { - $sum += $load; - } - if ( $sum == 0 ) { - # No appropriate DB servers except maybe the master and some slaves with zero load - # Do NOT use the master - # Instead, this function will return false, triggering read-only mode, - # and a lagged slave will be used instead. - return false; - } - - if ( count( $loads ) == 0 ) { - return false; - } - - #wfDebugLog( 'connect', var_export( $loads, true ) ); - - # Return a random representative of the remainder - return $this->pickRandom( $loads ); - } - - /** - * Get the index of the reader connection, which may be a slave - * This takes into account load ratios and lag times. It should - * always return a consistent index during a given invocation - * - * Side effect: opens connections to databases - */ - function getReaderIndex() { - global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll; - - $fname = 'LoadBalancer::getReaderIndex'; - wfProfileIn( $fname ); - - $i = false; - if ( $this->mForce >= 0 ) { - $i = $this->mForce; - } elseif ( count( $this->mServers ) == 1 ) { - # Skip the load balancing if there's only one server - $i = 0; - } else { - if ( $this->mReadIndex >= 0 ) { - $i = $this->mReadIndex; - } else { - # $loads is $this->mLoads except with elements knocked out if they - # don't work - $loads = $this->mLoads; - $done = false; - $totalElapsed = 0; - do { - if ( $wgReadOnly or $this->mAllowLagged ) { - $i = $this->pickRandom( $loads ); - } else { - $i = $this->getRandomNonLagged( $loads ); - if ( $i === false && count( $loads ) != 0 ) { - # All slaves lagged. Switch to read-only mode - $wgReadOnly = wfMsgNoDBForContent( 'readonly_lag' ); - $i = $this->pickRandom( $loads ); - } - } - $serverIndex = $i; - if ( $i !== false ) { - wfDebugLog( 'connect', "$fname: Using reader #$i: {$this->mServers[$i]['host']}...\n" ); - $this->openConnection( $i ); - - if ( !$this->isOpen( $i ) ) { - wfDebug( "$fname: Failed\n" ); - unset( $loads[$i] ); - $sleepTime = 0; - } else { - if ( isset( $this->mServers[$i]['max threads'] ) ) { - $status = $this->mConnections[$i]->getStatus("Thread%"); - if ( $status['Threads_running'] > $this->mServers[$i]['max threads'] ) { - # Too much load, back off and wait for a while. - # The sleep time is scaled by the number of threads connected, - # to produce a roughly constant global poll rate. - $sleepTime = $wgDBAvgStatusPoll * $status['Threads_connected']; - - # If we reach the timeout and exit the loop, don't use it - $i = false; - } else { - $done = true; - $sleepTime = 0; - } - } else { - $done = true; - $sleepTime = 0; - } - } - } else { - $sleepTime = 500000; - } - if ( $sleepTime ) { - $totalElapsed += $sleepTime; - $x = "{$this->mServers[$serverIndex]['host']} [$serverIndex]"; - wfProfileIn( "$fname-sleep $x" ); - usleep( $sleepTime ); - wfProfileOut( "$fname-sleep $x" ); - } - } while ( count( $loads ) && !$done && $totalElapsed / 1e6 < $wgDBClusterTimeout ); - - if ( $totalElapsed / 1e6 >= $wgDBClusterTimeout ) { - $this->mErrorConnection = false; - $this->mLastError = 'All servers busy'; - } - - if ( $i !== false && $this->isOpen( $i ) ) { - # Wait for the session master pos for a short time - if ( $this->mWaitForFile ) { - if ( !$this->doWait( $i ) ) { - $this->mServers[$i]['slave pos'] = $this->mConnections[$i]->getSlavePos(); - } - } - if ( $i !== false ) { - $this->mReadIndex = $i; - } - } else { - $i = false; - } - } - } - wfProfileOut( $fname ); - return $i; - } - - /** - * Get a random server to use in a query group - */ - function getGroupIndex( $group ) { - if ( isset( $this->mGroupLoads[$group] ) ) { - $i = $this->pickRandom( $this->mGroupLoads[$group] ); - } else { - $i = false; - } - wfDebug( "Query group $group => $i\n" ); - return $i; - } - - /** - * Set the master wait position - * If a DB_SLAVE connection has been opened already, waits - * Otherwise sets a variable telling it to wait if such a connection is opened - */ - function waitFor( $file, $pos ) { - $fname = 'LoadBalancer::waitFor'; - wfProfileIn( $fname ); - - wfDebug( "User master pos: $file $pos\n" ); - $this->mWaitForFile = false; - $this->mWaitForPos = false; - - if ( count( $this->mServers ) > 1 ) { - $this->mWaitForFile = $file; - $this->mWaitForPos = $pos; - $i = $this->mReadIndex; - - if ( $i > 0 ) { - if ( !$this->doWait( $i ) ) { - $this->mServers[$i]['slave pos'] = $this->mConnections[$i]->getSlavePos(); - $this->mLaggedSlaveMode = true; - } - } - } - wfProfileOut( $fname ); - } - - /** - * Wait for a given slave to catch up to the master pos stored in $this - */ - function doWait( $index ) { - global $wgMemc; - - $retVal = false; - - # Debugging hacks - if ( isset( $this->mServers[$index]['lagged slave'] ) ) { - return false; - } elseif ( isset( $this->mServers[$index]['fake slave'] ) ) { - return true; - } - - $key = 'masterpos:' . $index; - $memcPos = $wgMemc->get( $key ); - if ( $memcPos ) { - list( $file, $pos ) = explode( ' ', $memcPos ); - # If the saved position is later than the requested position, return now - if ( $file == $this->mWaitForFile && $this->mWaitForPos <= $pos ) { - $retVal = true; - } - } - - if ( !$retVal && $this->isOpen( $index ) ) { - $conn =& $this->mConnections[$index]; - wfDebug( "Waiting for slave #$index to catch up...\n" ); - $result = $conn->masterPosWait( $this->mWaitForFile, $this->mWaitForPos, $this->mWaitTimeout ); - - if ( $result == -1 || is_null( $result ) ) { - # Timed out waiting for slave, use master instead - wfDebug( "Timed out waiting for slave #$index pos {$this->mWaitForFile} {$this->mWaitForPos}\n" ); - $retVal = false; - } else { - $retVal = true; - wfDebug( "Done\n" ); - } - } - return $retVal; - } - - /** - * Get a connection by index - */ - function &getConnection( $i, $fail = true, $groups = array() ) - { - global $wgDBtype; - $fname = 'LoadBalancer::getConnection'; - wfProfileIn( $fname ); - - - # Query groups - if ( !is_array( $groups ) ) { - $groupIndex = $this->getGroupIndex( $groups ); - if ( $groupIndex !== false ) { - $i = $groupIndex; - } - } else { - foreach ( $groups as $group ) { - $groupIndex = $this->getGroupIndex( $group ); - if ( $groupIndex !== false ) { - $i = $groupIndex; - break; - } - } - } - - # For now, only go through all this for mysql databases - if ($wgDBtype != 'mysql') { - $i = $this->getWriterIndex(); - } - # Operation-based index - elseif ( $i == DB_SLAVE ) { - $i = $this->getReaderIndex(); - } elseif ( $i == DB_MASTER ) { - $i = $this->getWriterIndex(); - } elseif ( $i == DB_LAST ) { - # Just use $this->mLastIndex, which should already be set - $i = $this->mLastIndex; - if ( $i === -1 ) { - # Oh dear, not set, best to use the writer for safety - wfDebug( "Warning: DB_LAST used when there was no previous index\n" ); - $i = $this->getWriterIndex(); - } - } - # Couldn't find a working server in getReaderIndex()? - if ( $i === false ) { - $this->reportConnectionError( $this->mErrorConnection ); - } - # Now we have an explicit index into the servers array - $this->openConnection( $i, $fail ); - - wfProfileOut( $fname ); - return $this->mConnections[$i]; - } - - /** - * Open a connection to the server given by the specified index - * Index must be an actual index into the array - * Returns success - * @access private - */ - function openConnection( $i, $fail = false ) { - $fname = 'LoadBalancer::openConnection'; - wfProfileIn( $fname ); - $success = true; - - if ( !$this->isOpen( $i ) ) { - $this->mConnections[$i] = $this->reallyOpenConnection( $this->mServers[$i] ); - } - - if ( !$this->isOpen( $i ) ) { - wfDebug( "Failed to connect to database $i at {$this->mServers[$i]['host']}\n" ); - if ( $fail ) { - $this->reportConnectionError( $this->mConnections[$i] ); - } - $this->mErrorConnection = $this->mConnections[$i]; - $this->mConnections[$i] = false; - $success = false; - } - $this->mLastIndex = $i; - wfProfileOut( $fname ); - return $success; - } - - /** - * Test if the specified index represents an open connection - * @access private - */ - function isOpen( $index ) { - if( !is_integer( $index ) ) { - return false; - } - if ( array_key_exists( $index, $this->mConnections ) && is_object( $this->mConnections[$index] ) && - $this->mConnections[$index]->isOpen() ) - { - return true; - } else { - return false; - } - } - - /** - * Really opens a connection - * @access private - */ - function reallyOpenConnection( &$server ) { - if( !is_array( $server ) ) { - throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' ); - } - - extract( $server ); - # Get class for this database type - $class = 'Database' . ucfirst( $type ); - - # Create object - $db = new $class( $host, $user, $password, $dbname, 1, $flags ); - $db->setLBInfo( $server ); - return $db; - } - - function reportConnectionError( &$conn ) { - $fname = 'LoadBalancer::reportConnectionError'; - wfProfileIn( $fname ); - # Prevent infinite recursion - - static $reporting = false; - if ( !$reporting ) { - $reporting = true; - if ( !is_object( $conn ) ) { - // No last connection, probably due to all servers being too busy - $conn = new Database; - if ( $this->mFailFunction ) { - $conn->failFunction( $this->mFailFunction ); - $conn->reportConnectionError( $this->mLastError ); - } else { - // If all servers were busy, mLastError will contain something sensible - throw new DBConnectionError( $conn, $this->mLastError ); - } - } else { - if ( $this->mFailFunction ) { - $conn->failFunction( $this->mFailFunction ); - } else { - $conn->failFunction( false ); - } - $server = $conn->getProperty( 'mServer' ); - $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); - } - $reporting = false; - } - wfProfileOut( $fname ); - } - - function getWriterIndex() { - return 0; - } - - /** - * Force subsequent calls to getConnection(DB_SLAVE) to return the - * given index. Set to -1 to restore the original load balancing - * behaviour. I thought this was a good idea when I originally - * wrote this class, but it has never been used. - */ - function force( $i ) { - $this->mForce = $i; - } - - /** - * Returns true if the specified index is a valid server index - */ - function haveIndex( $i ) { - return array_key_exists( $i, $this->mServers ); - } - - /** - * Returns true if the specified index is valid and has non-zero load - */ - function isNonZeroLoad( $i ) { - return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0; - } - - /** - * Get the number of defined servers (not the number of open connections) - */ - function getServerCount() { - return count( $this->mServers ); - } - - /** - * Save master pos to the session and to memcached, if the session exists - */ - function saveMasterPos() { - if ( session_id() != '' && count( $this->mServers ) > 1 ) { - # If this entire request was served from a slave without opening a connection to the - # master (however unlikely that may be), then we can fetch the position from the slave. - if ( empty( $this->mConnections[0] ) ) { - $conn =& $this->getConnection( DB_SLAVE ); - list( $file, $pos ) = $conn->getSlavePos(); - wfDebug( "Saving master pos fetched from slave: $file $pos\n" ); - } else { - $conn =& $this->getConnection( 0 ); - list( $file, $pos ) = $conn->getMasterPos(); - wfDebug( "Saving master pos: $file $pos\n" ); - } - if ( $file !== false ) { - $_SESSION['master_log_file'] = $file; - $_SESSION['master_pos'] = $pos; - } - } - } - - /** - * Loads the master pos from the session, waits for it if necessary - */ - function loadMasterPos() { - if ( isset( $_SESSION['master_log_file'] ) && isset( $_SESSION['master_pos'] ) ) { - $this->waitFor( $_SESSION['master_log_file'], $_SESSION['master_pos'] ); - } - } - - /** - * Close all open connections - */ - function closeAll() { - foreach( $this->mConnections as $i => $conn ) { - if ( $this->isOpen( $i ) ) { - // Need to use this syntax because $conn is a copy not a reference - $this->mConnections[$i]->close(); - } - } - } - - function commitAll() { - foreach( $this->mConnections as $i => $conn ) { - if ( $this->isOpen( $i ) ) { - // Need to use this syntax because $conn is a copy not a reference - $this->mConnections[$i]->immediateCommit(); - } - } - } - - /* Issue COMMIT only on master, only if queries were done on connection */ - function commitMasterChanges() { - // Always 0, but who knows.. :) - $i = $this->getWriterIndex(); - if (array_key_exists($i,$this->mConnections)) { - if ($this->mConnections[$i]->lastQuery() != '') { - $this->mConnections[$i]->immediateCommit(); - } - } - } - - function waitTimeout( $value = NULL ) { - return wfSetVar( $this->mWaitTimeout, $value ); - } - - function getLaggedSlaveMode() { - return $this->mLaggedSlaveMode; - } - - /* Disables/enables lag checks */ - function allowLagged($mode=null) { - if ($mode===null) - return $this->mAllowLagged; - $this->mAllowLagged=$mode; - } - - function pingAll() { - $success = true; - foreach ( $this->mConnections as $i => $conn ) { - if ( $this->isOpen( $i ) ) { - if ( !$this->mConnections[$i]->ping() ) { - $success = false; - } - } - } - return $success; - } - - /** - * Get the hostname and lag time of the most-lagged slave - * This is useful for maintenance scripts that need to throttle their updates - */ - function getMaxLag() { - $maxLag = -1; - $host = ''; - foreach ( $this->mServers as $i => $conn ) { - if ( $this->openConnection( $i ) ) { - $lag = $this->mConnections[$i]->getLag(); - if ( $lag > $maxLag ) { - $maxLag = $lag; - $host = $this->mServers[$i]['host']; - } - } - } - return array( $host, $maxLag ); - } - - /** - * Get lag time for each DB - * Results are cached for a short time in memcached - */ - function getLagTimes() { - wfProfileIn( __METHOD__ ); - $expiry = 5; - $requestRate = 10; - - global $wgMemc; - $times = $wgMemc->get( wfMemcKey( 'lag_times' ) ); - if ( $times ) { - # Randomly recache with probability rising over $expiry - $elapsed = time() - $times['timestamp']; - $chance = max( 0, ( $expiry - $elapsed ) * $requestRate ); - if ( mt_rand( 0, $chance ) != 0 ) { - unset( $times['timestamp'] ); - wfProfileOut( __METHOD__ ); - return $times; - } - wfIncrStats( 'lag_cache_miss_expired' ); - } else { - wfIncrStats( 'lag_cache_miss_absent' ); - } - - # Cache key missing or expired - - $times = array(); - foreach ( $this->mServers as $i => $conn ) { - if ($i==0) { # Master - $times[$i] = 0; - } elseif ( $this->openConnection( $i ) ) { - $times[$i] = $this->mConnections[$i]->getLag(); - } - } - - # Add a timestamp key so we know when it was cached - $times['timestamp'] = time(); - $wgMemc->set( wfMemcKey( 'lag_times' ), $times, $expiry ); - - # But don't give the timestamp to the caller - unset($times['timestamp']); - wfProfileOut( __METHOD__ ); - return $times; - } -} - - diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php index ec4505ab..e33b1c0a 100644 --- a/includes/MimeMagic.php +++ b/includes/MimeMagic.php @@ -100,6 +100,10 @@ class MimeMagic { */ var $mExtToMime= NULL; + /** IEContentAnalyzer instance + */ + var $mIEAnalyzer; + /** The singleton instance */ private static $instance; @@ -726,4 +730,27 @@ class MimeMagic { return MEDIATYPE_UNKNOWN; } + + /** + * Get the MIME types that various versions of Internet Explorer would + * detect from a chunk of the content. + * + * @param string $fileName The file name (unused at present) + * @param string $chunk The first 256 bytes of the file + * @param string $proposed The MIME type proposed by the server + */ + public function getIEMimeTypes( $fileName, $chunk, $proposed ) { + $ca = $this->getIEContentAnalyzer(); + return $ca->getRealMimesFromData( $fileName, $chunk, $proposed ); + } + + /** + * Get a cached instance of IEContentAnalyzer + */ + protected function getIEContentAnalyzer() { + if ( is_null( $this->mIEAnalyzer ) ) { + $this->mIEAnalyzer = new IEContentAnalyzer; + } + return $this->mIEAnalyzer; + } } diff --git a/includes/Parser.php b/includes/Parser.php deleted file mode 100644 index 41eabe4f..00000000 --- a/includes/Parser.php +++ /dev/null @@ -1,4913 +0,0 @@ -<?php - -/** - * - * File for Parser and related classes - * - * @addtogroup Parser - */ - - -/** - * PHP Parser - Processes wiki markup (which uses a more user-friendly - * syntax, such as "[[link]]" for making links), and provides a one-way - * transformation of that wiki markup it into XHTML output / markup - * (which in turn the browser understands, and can display). - * - * <pre> - * There are five main entry points into the Parser class: - * parse() - * produces HTML output - * preSaveTransform(). - * produces altered wiki markup. - * preprocess() - * removes HTML comments and expands templates - * cleanSig() - * Cleans a signature before saving it to preferences - * extractSections() - * Extracts sections from an article for section editing - * - * Globals used: - * objects: $wgLang, $wgContLang - * - * NOT $wgArticle, $wgUser or $wgTitle. Keep them away! - * - * settings: - * $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*, - * $wgNamespacesWithSubpages, $wgAllowExternalImages*, - * $wgLocaltimezone, $wgAllowSpecialInclusion*, - * $wgMaxArticleSize* - * - * * only within ParserOptions - * </pre> - * - * @addtogroup Parser - */ -class Parser -{ - /** - * Update this version number when the ParserOutput format - * changes in an incompatible way, so the parser cache - * can automatically discard old data. - */ - const VERSION = '1.6.4'; - - # Flags for Parser::setFunctionHook - # Also available as global constants from Defines.php - const SFH_NO_HASH = 1; - const SFH_OBJECT_ARGS = 2; - - # Constants needed for external link processing - # Everything except bracket, space, or control characters - const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F]'; - const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+) - \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sx'; - - // State constants for the definition list colon extraction - const COLON_STATE_TEXT = 0; - const COLON_STATE_TAG = 1; - const COLON_STATE_TAGSTART = 2; - const COLON_STATE_CLOSETAG = 3; - const COLON_STATE_TAGSLASH = 4; - const COLON_STATE_COMMENT = 5; - const COLON_STATE_COMMENTDASH = 6; - const COLON_STATE_COMMENTDASHDASH = 7; - - // Flags for preprocessToDom - const PTD_FOR_INCLUSION = 1; - - // Allowed values for $this->mOutputType - // Parameter to startExternalParse(). - const OT_HTML = 1; - const OT_WIKI = 2; - const OT_PREPROCESS = 3; - const OT_MSG = 3; - - /**#@+ - * @private - */ - # Persistent: - var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables, - $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerSuffix, $mMarkerIndex, - $mExtLinkBracketedRegex, $mPreprocessor, $mDefaultStripList, $mVarCache, $mConf; - - - # Cleared with clearState(): - var $mOutput, $mAutonumber, $mDTopen, $mStripState; - var $mIncludeCount, $mArgStack, $mLastSection, $mInPre; - var $mInterwikiLinkHolders, $mLinkHolders; - var $mIncludeSizes, $mPPNodeCount, $mDefaultSort; - var $mTplExpandCache; // empty-frame expansion cache - var $mTplRedirCache, $mTplDomCache, $mHeadings; - - # Temporary - # These are variables reset at least once per parse regardless of $clearState - var $mOptions, // ParserOptions object - $mTitle, // Title context, used for self-link rendering and similar things - $mOutputType, // Output type, one of the OT_xxx constants - $ot, // Shortcut alias, see setOutputType() - $mRevisionId, // ID to display in {{REVISIONID}} tags - $mRevisionTimestamp, // The timestamp of the specified revision ID - $mRevIdForTs; // The revision ID which was used to fetch the timestamp - - /**#@-*/ - - /** - * Constructor - * - * @public - */ - function __construct( $conf = array() ) { - $this->mConf = $conf; - $this->mTagHooks = array(); - $this->mTransparentTagHooks = array(); - $this->mFunctionHooks = array(); - $this->mFunctionSynonyms = array( 0 => array(), 1 => array() ); - $this->mDefaultStripList = $this->mStripList = array( 'nowiki', 'gallery' ); - $this->mMarkerSuffix = "-QINU\x7f"; - $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'. - '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S'; - $this->mVarCache = array(); - if ( isset( $conf['preprocessorClass'] ) ) { - $this->mPreprocessorClass = $conf['preprocessorClass']; - } else { - $this->mPreprocessorClass = 'Preprocessor_DOM'; - } - $this->mMarkerIndex = 0; - $this->mFirstCall = true; - } - - /** - * Do various kinds of initialisation on the first call of the parser - */ - function firstCallInit() { - if ( !$this->mFirstCall ) { - return; - } - $this->mFirstCall = false; - - wfProfileIn( __METHOD__ ); - global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions; - - $this->setHook( 'pre', array( $this, 'renderPreTag' ) ); - - # Syntax for arguments (see self::setFunctionHook): - # "name for lookup in localized magic words array", - # function callback, - # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...} - # instead of {{#int:...}}) - $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH ); - $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH ); - $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH ); - $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH ); - $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH ); - $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH ); - $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH ); - $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH ); - $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH ); - $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH ); - $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH ); - $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH ); - $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH ); - $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH ); - $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH ); - $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH ); - $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH ); - $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH ); - $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) ); - $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH ); - $this->setFunctionHook( 'filepath', array( 'CoreParserFunctions', 'filepath' ), SFH_NO_HASH ); - $this->setFunctionHook( 'tag', array( 'CoreParserFunctions', 'tagObj' ), SFH_OBJECT_ARGS ); - - if ( $wgAllowDisplayTitle ) { - $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH ); - } - if ( $wgAllowSlowParserFunctions ) { - $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH ); - } - - $this->initialiseVariables(); - - wfRunHooks( 'ParserFirstCallInit', array( &$this ) ); - wfProfileOut( __METHOD__ ); - } - - /** - * Clear Parser state - * - * @private - */ - function clearState() { - wfProfileIn( __METHOD__ ); - if ( $this->mFirstCall ) { - $this->firstCallInit(); - } - $this->mOutput = new ParserOutput; - $this->mAutonumber = 0; - $this->mLastSection = ''; - $this->mDTopen = false; - $this->mIncludeCount = array(); - $this->mStripState = new StripState; - $this->mArgStack = false; - $this->mInPre = false; - $this->mInterwikiLinkHolders = array( - 'texts' => array(), - 'titles' => array() - ); - $this->mLinkHolders = array( - 'namespaces' => array(), - 'dbkeys' => array(), - 'queries' => array(), - 'texts' => array(), - 'titles' => array() - ); - $this->mRevisionTimestamp = $this->mRevisionId = null; - - /** - * Prefix for temporary replacement strings for the multipass parser. - * \x07 should never appear in input as it's disallowed in XML. - * Using it at the front also gives us a little extra robustness - * since it shouldn't match when butted up against identifier-like - * string constructs. - * - * Must not consist of all title characters, or else it will change - * the behaviour of <nowiki> in a link. - */ - #$this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString(); - # Changed to \x7f to allow XML double-parsing -- TS - $this->mUniqPrefix = "\x7fUNIQ" . Parser::getRandomString(); - - - # Clear these on every parse, bug 4549 - $this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array(); - - $this->mShowToc = true; - $this->mForceTocPosition = false; - $this->mIncludeSizes = array( - 'post-expand' => 0, - 'arg' => 0, - ); - $this->mPPNodeCount = 0; - $this->mDefaultSort = false; - $this->mHeadings = array(); - - # Fix cloning - if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) { - $this->mPreprocessor = null; - } - - wfRunHooks( 'ParserClearState', array( &$this ) ); - wfProfileOut( __METHOD__ ); - } - - function setOutputType( $ot ) { - $this->mOutputType = $ot; - // Shortcut alias - $this->ot = array( - 'html' => $ot == self::OT_HTML, - 'wiki' => $ot == self::OT_WIKI, - 'pre' => $ot == self::OT_PREPROCESS, - ); - } - - /** - * Set the context title - */ - function setTitle( $t ) { - if ( !$t || $t instanceof FakeTitle ) { - $t = Title::newFromText( 'NO TITLE' ); - } - if ( strval( $t->getFragment() ) !== '' ) { - # Strip the fragment to avoid various odd effects - $this->mTitle = clone $t; - $this->mTitle->setFragment( '' ); - } else { - $this->mTitle = $t; - } - } - - /** - * Accessor for mUniqPrefix. - * - * @public - */ - function uniqPrefix() { - if( !isset( $this->mUniqPrefix ) ) { - // @fixme this is probably *horribly wrong* - // LanguageConverter seems to want $wgParser's uniqPrefix, however - // if this is called for a parser cache hit, the parser may not - // have ever been initialized in the first place. - // Not really sure what the heck is supposed to be going on here. - return ''; - //throw new MWException( "Accessing uninitialized mUniqPrefix" ); - } - return $this->mUniqPrefix; - } - - /** - * Convert wikitext to HTML - * Do not call this function recursively. - * - * @param string $text Text we want to parse - * @param Title &$title A title object - * @param array $options - * @param boolean $linestart - * @param boolean $clearState - * @param int $revid number to pass in {{REVISIONID}} - * @return ParserOutput a ParserOutput - */ - public function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) { - /** - * First pass--just handle <nowiki> sections, pass the rest off - * to internalParse() which does all the real work. - */ - - global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang; - $fname = 'Parser::parse-' . wfGetCaller(); - wfProfileIn( __METHOD__ ); - wfProfileIn( $fname ); - - if ( $clearState ) { - $this->clearState(); - } - - $this->mOptions = $options; - $this->setTitle( $title ); - $oldRevisionId = $this->mRevisionId; - $oldRevisionTimestamp = $this->mRevisionTimestamp; - if( $revid !== null ) { - $this->mRevisionId = $revid; - $this->mRevisionTimestamp = null; - } - $this->setOutputType( self::OT_HTML ); - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); - # No more strip! - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->internalParse( $text ); - $text = $this->mStripState->unstripGeneral( $text ); - - # Clean up special characters, only run once, next-to-last before doBlockLevels - $fixtags = array( - # french spaces, last one Guillemet-left - # only if there is something before the space - '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1 \\2', - # french spaces, Guillemet-right - '/(\\302\\253) /' => '\\1 ', - ); - $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text ); - - # only once and last - $text = $this->doBlockLevels( $text, $linestart ); - - $this->replaceLinkHolders( $text ); - - # the position of the parserConvert() call should not be changed. it - # assumes that the links are all replaced and the only thing left - # is the <nowiki> mark. - # Side-effects: this calls $this->mOutput->setTitleText() - $text = $wgContLang->parserConvert( $text, $this ); - - $text = $this->mStripState->unstripNoWiki( $text ); - - wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) ); - -//!JF Move to its own function - - $uniq_prefix = $this->mUniqPrefix; - $matches = array(); - $elements = array_keys( $this->mTransparentTagHooks ); - $text = Parser::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix ); - - foreach( $matches as $marker => $data ) { - list( $element, $content, $params, $tag ) = $data; - $tagName = strtolower( $element ); - if( isset( $this->mTransparentTagHooks[$tagName] ) ) { - $output = call_user_func_array( $this->mTransparentTagHooks[$tagName], - array( $content, $params, $this ) ); - } else { - $output = $tag; - } - $this->mStripState->general->setPair( $marker, $output ); - } - $text = $this->mStripState->unstripGeneral( $text ); - - $text = Sanitizer::normalizeCharReferences( $text ); - - if (($wgUseTidy and $this->mOptions->mTidy) or $wgAlwaysUseTidy) { - $text = Parser::tidy($text); - } else { - # attempt to sanitize at least some nesting problems - # (bug #2702 and quite a few others) - $tidyregs = array( - # ''Something [http://www.cool.com cool''] --> - # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a> - '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' => - '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9', - # fix up an anchor inside another anchor, only - # at least for a single single nested link (bug 3695) - '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' => - '\\1\\2</a>\\3</a>\\1\\4</a>', - # fix div inside inline elements- doBlockLevels won't wrap a line which - # contains a div, so fix it up here; replace - # div with escaped text - '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' => - '\\1\\3<div\\5>\\6</div>\\8\\9', - # remove empty italic or bold tag pairs, some - # introduced by rules above - '/<([bi])><\/\\1>/' => '', - ); - - $text = preg_replace( - array_keys( $tidyregs ), - array_values( $tidyregs ), - $text ); - } - - wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) ); - - # Information on include size limits, for the benefit of users who try to skirt them - if ( $this->mOptions->getEnableLimitReport() ) { - $max = $this->mOptions->getMaxIncludeSize(); - $limitReport = - "NewPP limit report\n" . - "Preprocessor node count: {$this->mPPNodeCount}/{$this->mOptions->mMaxPPNodeCount}\n" . - "Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" . - "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n"; - wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) ); - $text .= "\n<!-- \n$limitReport-->\n"; - } - $this->mOutput->setText( $text ); - $this->mRevisionId = $oldRevisionId; - $this->mRevisionTimestamp = $oldRevisionTimestamp; - wfProfileOut( $fname ); - wfProfileOut( __METHOD__ ); - - return $this->mOutput; - } - - /** - * Recursive parser entry point that can be called from an extension tag - * hook. - */ - function recursiveTagParse( $text ) { - wfProfileIn( __METHOD__ ); - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->internalParse( $text ); - wfProfileOut( __METHOD__ ); - return $text; - } - - /** - * Expand templates and variables in the text, producing valid, static wikitext. - * Also removes comments. - */ - function preprocess( $text, $title, $options, $revid = null ) { - wfProfileIn( __METHOD__ ); - $this->clearState(); - $this->setOutputType( self::OT_PREPROCESS ); - $this->mOptions = $options; - $this->setTitle( $title ); - if( $revid !== null ) { - $this->mRevisionId = $revid; - } - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->replaceVariables( $text ); - $text = $this->mStripState->unstripBoth( $text ); - wfProfileOut( __METHOD__ ); - return $text; - } - - /** - * Get a random string - * - * @private - * @static - */ - function getRandomString() { - return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff)); - } - - function &getTitle() { return $this->mTitle; } - function getOptions() { return $this->mOptions; } - - function getFunctionLang() { - global $wgLang, $wgContLang; - return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang; - } - - /** - * Get a preprocessor object - */ - function getPreprocessor() { - if ( !isset( $this->mPreprocessor ) ) { - $class = $this->mPreprocessorClass; - $this->mPreprocessor = new $class( $this ); - } - return $this->mPreprocessor; - } - - /** - * Replaces all occurrences of HTML-style comments and the given tags - * in the text with a random marker and returns the next text. The output - * parameter $matches will be an associative array filled with data in - * the form: - * 'UNIQ-xxxxx' => array( - * 'element', - * 'tag content', - * array( 'param' => 'x' ), - * '<element param="x">tag content</element>' ) ) - * - * @param $elements list of element names. Comments are always extracted. - * @param $text Source text string. - * @param $uniq_prefix - * - * @public - * @static - */ - function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = ''){ - static $n = 1; - $stripped = ''; - $matches = array(); - - $taglist = implode( '|', $elements ); - $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i"; - - while ( '' != $text ) { - $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE ); - $stripped .= $p[0]; - if( count( $p ) < 5 ) { - break; - } - if( count( $p ) > 5 ) { - // comment - $element = $p[4]; - $attributes = ''; - $close = ''; - $inside = $p[5]; - } else { - // tag - $element = $p[1]; - $attributes = $p[2]; - $close = $p[3]; - $inside = $p[4]; - } - - $marker = "$uniq_prefix-$element-" . sprintf('%08X', $n++) . $this->mMarkerSuffix; - $stripped .= $marker; - - if ( $close === '/>' ) { - // Empty element tag, <tag /> - $content = null; - $text = $inside; - $tail = null; - } else { - if( $element == '!--' ) { - $end = '/(-->)/'; - } else { - $end = "/(<\\/$element\\s*>)/i"; - } - $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE ); - $content = $q[0]; - if( count( $q ) < 3 ) { - # No end tag -- let it run out to the end of the text. - $tail = ''; - $text = ''; - } else { - $tail = $q[1]; - $text = $q[2]; - } - } - - $matches[$marker] = array( $element, - $content, - Sanitizer::decodeTagAttributes( $attributes ), - "<$element$attributes$close$content$tail" ); - } - return $stripped; - } - - /** - * Get a list of strippable XML-like elements - */ - function getStripList() { - global $wgRawHtml; - $elements = $this->mStripList; - if( $wgRawHtml ) { - $elements[] = 'html'; - } - if( $this->mOptions->getUseTeX() ) { - $elements[] = 'math'; - } - return $elements; - } - - /** - * @deprecated use replaceVariables - */ - function strip( $text, $state, $stripcomments = false , $dontstrip = array () ) { - return $text; - } - - /** - * Restores pre, math, and other extensions removed by strip() - * - * always call unstripNoWiki() after this one - * @private - * @deprecated use $this->mStripState->unstrip() - */ - function unstrip( $text, $state ) { - return $state->unstripGeneral( $text ); - } - - /** - * Always call this after unstrip() to preserve the order - * - * @private - * @deprecated use $this->mStripState->unstrip() - */ - function unstripNoWiki( $text, $state ) { - return $state->unstripNoWiki( $text ); - } - - /** - * @deprecated use $this->mStripState->unstripBoth() - */ - function unstripForHTML( $text ) { - return $this->mStripState->unstripBoth( $text ); - } - - /** - * Add an item to the strip state - * Returns the unique tag which must be inserted into the stripped text - * The tag will be replaced with the original text in unstrip() - * - * @private - */ - function insertStripItem( $text ) { - $rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-{$this->mMarkerSuffix}"; - $this->mMarkerIndex++; - $this->mStripState->general->setPair( $rnd, $text ); - return $rnd; - } - - /** - * Interface with html tidy, used if $wgUseTidy = true. - * If tidy isn't able to correct the markup, the original will be - * returned in all its glory with a warning comment appended. - * - * Either the external tidy program or the in-process tidy extension - * will be used depending on availability. Override the default - * $wgTidyInternal setting to disable the internal if it's not working. - * - * @param string $text Hideous HTML input - * @return string Corrected HTML output - * @public - * @static - */ - function tidy( $text ) { - global $wgTidyInternal; - $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'. -' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'. -'<head><title>test</title></head><body>'.$text.'</body></html>'; - if( $wgTidyInternal ) { - $correctedtext = Parser::internalTidy( $wrappedtext ); - } else { - $correctedtext = Parser::externalTidy( $wrappedtext ); - } - if( is_null( $correctedtext ) ) { - wfDebug( "Tidy error detected!\n" ); - return $text . "\n<!-- Tidy found serious XHTML errors -->\n"; - } - return $correctedtext; - } - - /** - * Spawn an external HTML tidy process and get corrected markup back from it. - * - * @private - * @static - */ - function externalTidy( $text ) { - global $wgTidyConf, $wgTidyBin, $wgTidyOpts; - $fname = 'Parser::externalTidy'; - wfProfileIn( $fname ); - - $cleansource = ''; - $opts = ' -utf8'; - - $descriptorspec = array( - 0 => array('pipe', 'r'), - 1 => array('pipe', 'w'), - 2 => array('file', wfGetNull(), 'a') - ); - $pipes = array(); - $process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes); - if (is_resource($process)) { - // Theoretically, this style of communication could cause a deadlock - // here. If the stdout buffer fills up, then writes to stdin could - // block. This doesn't appear to happen with tidy, because tidy only - // writes to stdout after it's finished reading from stdin. Search - // for tidyParseStdin and tidySaveStdout in console/tidy.c - fwrite($pipes[0], $text); - fclose($pipes[0]); - while (!feof($pipes[1])) { - $cleansource .= fgets($pipes[1], 1024); - } - fclose($pipes[1]); - proc_close($process); - } - - wfProfileOut( $fname ); - - if( $cleansource == '' && $text != '') { - // Some kind of error happened, so we couldn't get the corrected text. - // Just give up; we'll use the source text and append a warning. - return null; - } else { - return $cleansource; - } - } - - /** - * Use the HTML tidy PECL extension to use the tidy library in-process, - * saving the overhead of spawning a new process. - * - * 'pear install tidy' should be able to compile the extension module. - * - * @private - * @static - */ - function internalTidy( $text ) { - global $wgTidyConf, $IP, $wgDebugTidy; - $fname = 'Parser::internalTidy'; - wfProfileIn( $fname ); - - $tidy = new tidy; - $tidy->parseString( $text, $wgTidyConf, 'utf8' ); - $tidy->cleanRepair(); - if( $tidy->getStatus() == 2 ) { - // 2 is magic number for fatal error - // http://www.php.net/manual/en/function.tidy-get-status.php - $cleansource = null; - } else { - $cleansource = tidy_get_output( $tidy ); - } - if ( $wgDebugTidy && $tidy->getStatus() > 0 ) { - $cleansource .= "<!--\nTidy reports:\n" . - str_replace( '-->', '-->', $tidy->errorBuffer ) . - "\n-->"; - } - - wfProfileOut( $fname ); - return $cleansource; - } - - /** - * parse the wiki syntax used to render tables - * - * @private - */ - function doTableStuff ( $text ) { - $fname = 'Parser::doTableStuff'; - wfProfileIn( $fname ); - - $lines = explode ( "\n" , $text ); - $td_history = array (); // Is currently a td tag open? - $last_tag_history = array (); // Save history of last lag activated (td, th or caption) - $tr_history = array (); // Is currently a tr tag open? - $tr_attributes = array (); // history of tr attributes - $has_opened_tr = array(); // Did this table open a <tr> element? - $indent_level = 0; // indent level of the table - foreach ( $lines as $key => $line ) - { - $line = trim ( $line ); - - if( $line == '' ) { // empty line, go to next line - continue; - } - $first_character = $line{0}; - $matches = array(); - - if ( preg_match( '/^(:*)\{\|(.*)$/' , $line , $matches ) ) { - // First check if we are starting a new table - $indent_level = strlen( $matches[1] ); - - $attributes = $this->mStripState->unstripBoth( $matches[2] ); - $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' ); - - $lines[$key] = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>"; - array_push ( $td_history , false ); - array_push ( $last_tag_history , '' ); - array_push ( $tr_history , false ); - array_push ( $tr_attributes , '' ); - array_push ( $has_opened_tr , false ); - } else if ( count ( $td_history ) == 0 ) { - // Don't do any of the following - continue; - } else if ( substr ( $line , 0 , 2 ) == '|}' ) { - // We are ending a table - $line = '</table>' . substr ( $line , 2 ); - $last_tag = array_pop ( $last_tag_history ); - - if ( !array_pop ( $has_opened_tr ) ) { - $line = "<tr><td></td></tr>{$line}"; - } - - if ( array_pop ( $tr_history ) ) { - $line = "</tr>{$line}"; - } - - if ( array_pop ( $td_history ) ) { - $line = "</{$last_tag}>{$line}"; - } - array_pop ( $tr_attributes ); - $lines[$key] = $line . str_repeat( '</dd></dl>' , $indent_level ); - } else if ( substr ( $line , 0 , 2 ) == '|-' ) { - // Now we have a table row - $line = preg_replace( '#^\|-+#', '', $line ); - - // Whats after the tag is now only attributes - $attributes = $this->mStripState->unstripBoth( $line ); - $attributes = Sanitizer::fixTagAttributes ( $attributes , 'tr' ); - array_pop ( $tr_attributes ); - array_push ( $tr_attributes , $attributes ); - - $line = ''; - $last_tag = array_pop ( $last_tag_history ); - array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ); - - if ( array_pop ( $tr_history ) ) { - $line = '</tr>'; - } - - if ( array_pop ( $td_history ) ) { - $line = "</{$last_tag}>{$line}"; - } - - $lines[$key] = $line; - array_push ( $tr_history , false ); - array_push ( $td_history , false ); - array_push ( $last_tag_history , '' ); - } - else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) { - // This might be cell elements, td, th or captions - if ( substr ( $line , 0 , 2 ) == '|+' ) { - $first_character = '+'; - $line = substr ( $line , 1 ); - } - - $line = substr ( $line , 1 ); - - if ( $first_character == '!' ) { - $line = str_replace ( '!!' , '||' , $line ); - } - - // Split up multiple cells on the same line. - // FIXME : This can result in improper nesting of tags processed - // by earlier parser steps, but should avoid splitting up eg - // attribute values containing literal "||". - $cells = StringUtils::explodeMarkup( '||' , $line ); - - $lines[$key] = ''; - - // Loop through each table cell - foreach ( $cells as $cell ) - { - $previous = ''; - if ( $first_character != '+' ) - { - $tr_after = array_pop ( $tr_attributes ); - if ( !array_pop ( $tr_history ) ) { - $previous = "<tr{$tr_after}>\n"; - } - array_push ( $tr_history , true ); - array_push ( $tr_attributes , '' ); - array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ); - } - - $last_tag = array_pop ( $last_tag_history ); - - if ( array_pop ( $td_history ) ) { - $previous = "</{$last_tag}>{$previous}"; - } - - if ( $first_character == '|' ) { - $last_tag = 'td'; - } else if ( $first_character == '!' ) { - $last_tag = 'th'; - } else if ( $first_character == '+' ) { - $last_tag = 'caption'; - } else { - $last_tag = ''; - } - - array_push ( $last_tag_history , $last_tag ); - - // A cell could contain both parameters and data - $cell_data = explode ( '|' , $cell , 2 ); - - // Bug 553: Note that a '|' inside an invalid link should not - // be mistaken as delimiting cell parameters - if ( strpos( $cell_data[0], '[[' ) !== false ) { - $cell = "{$previous}<{$last_tag}>{$cell}"; - } else if ( count ( $cell_data ) == 1 ) - $cell = "{$previous}<{$last_tag}>{$cell_data[0]}"; - else { - $attributes = $this->mStripState->unstripBoth( $cell_data[0] ); - $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag ); - $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}"; - } - - $lines[$key] .= $cell; - array_push ( $td_history , true ); - } - } - } - - // Closing open td, tr && table - while ( count ( $td_history ) > 0 ) - { - if ( array_pop ( $td_history ) ) { - $lines[] = '</td>' ; - } - if ( array_pop ( $tr_history ) ) { - $lines[] = '</tr>' ; - } - if ( !array_pop ( $has_opened_tr ) ) { - $lines[] = "<tr><td></td></tr>" ; - } - - $lines[] = '</table>' ; - } - - $output = implode ( "\n" , $lines ) ; - - // special case: don't return empty table - if( $output == "<table>\n<tr><td></td></tr>\n</table>" ) { - $output = ''; - } - - wfProfileOut( $fname ); - - return $output; - } - - /** - * Helper function for parse() that transforms wiki markup into - * HTML. Only called for $mOutputType == self::OT_HTML. - * - * @private - */ - function internalParse( $text ) { - $isMain = true; - $fname = 'Parser::internalParse'; - wfProfileIn( $fname ); - - # Hook to suspend the parser in this state - if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) { - wfProfileOut( $fname ); - return $text ; - } - - $text = $this->replaceVariables( $text ); - $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) ); - wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) ); - - // Tables need to come after variable replacement for things to work - // properly; putting them before other transformations should keep - // exciting things like link expansions from showing up in surprising - // places. - $text = $this->doTableStuff( $text ); - - $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text ); - - $text = $this->stripToc( $text ); - $this->stripNoGallery( $text ); - $text = $this->doHeadings( $text ); - if($this->mOptions->getUseDynamicDates()) { - $df =& DateFormatter::getInstance(); - $text = $df->reformat( $this->mOptions->getDateFormat(), $text ); - } - $text = $this->doAllQuotes( $text ); - $text = $this->replaceInternalLinks( $text ); - $text = $this->replaceExternalLinks( $text ); - - # replaceInternalLinks may sometimes leave behind - # absolute URLs, which have to be masked to hide them from replaceExternalLinks - $text = str_replace($this->mUniqPrefix."NOPARSE", "", $text); - - $text = $this->doMagicLinks( $text ); - $text = $this->formatHeadings( $text, $isMain ); - - wfProfileOut( $fname ); - return $text; - } - - /** - * Replace special strings like "ISBN xxx" and "RFC xxx" with - * magic external links. - * - * @private - */ - function doMagicLinks( $text ) { - wfProfileIn( __METHOD__ ); - $text = preg_replace_callback( - '!(?: # Start cases - <a.*?</a> | # Skip link text - <.*?> | # Skip stuff inside HTML elements - (?:RFC|PMID)\s+([0-9]+) | # RFC or PMID, capture number as m[1] - ISBN\s+(\b # ISBN, capture number as m[2] - (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix - (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters - [0-9Xx] # check digit - \b) - )!x', array( &$this, 'magicLinkCallback' ), $text ); - wfProfileOut( __METHOD__ ); - return $text; - } - - function magicLinkCallback( $m ) { - if ( substr( $m[0], 0, 1 ) == '<' ) { - # Skip HTML element - return $m[0]; - } elseif ( substr( $m[0], 0, 4 ) == 'ISBN' ) { - $isbn = $m[2]; - $num = strtr( $isbn, array( - '-' => '', - ' ' => '', - 'x' => 'X', - )); - $titleObj = SpecialPage::getTitleFor( 'Booksources' ); - $text = '<a href="' . - $titleObj->escapeLocalUrl( "isbn=$num" ) . - "\" class=\"internal\">ISBN $isbn</a>"; - } else { - if ( substr( $m[0], 0, 3 ) == 'RFC' ) { - $keyword = 'RFC'; - $urlmsg = 'rfcurl'; - $id = $m[1]; - } elseif ( substr( $m[0], 0, 4 ) == 'PMID' ) { - $keyword = 'PMID'; - $urlmsg = 'pubmedurl'; - $id = $m[1]; - } else { - throw new MWException( __METHOD__.': unrecognised match type "' . - substr($m[0], 0, 20 ) . '"' ); - } - - $url = wfMsg( $urlmsg, $id); - $sk = $this->mOptions->getSkin(); - $la = $sk->getExternalLinkAttributes( $url, $keyword.$id ); - $text = "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>"; - } - return $text; - } - - /** - * Parse headers and return html - * - * @private - */ - function doHeadings( $text ) { - $fname = 'Parser::doHeadings'; - wfProfileIn( $fname ); - for ( $i = 6; $i >= 1; --$i ) { - $h = str_repeat( '=', $i ); - $text = preg_replace( "/^$h(.+)$h\\s*$/m", - "<h$i>\\1</h$i>", $text ); - } - wfProfileOut( $fname ); - return $text; - } - - /** - * Replace single quotes with HTML markup - * @private - * @return string the altered text - */ - function doAllQuotes( $text ) { - $fname = 'Parser::doAllQuotes'; - wfProfileIn( $fname ); - $outtext = ''; - $lines = explode( "\n", $text ); - foreach ( $lines as $line ) { - $outtext .= $this->doQuotes ( $line ) . "\n"; - } - $outtext = substr($outtext, 0,-1); - wfProfileOut( $fname ); - return $outtext; - } - - /** - * Helper function for doAllQuotes() - */ - public function doQuotes( $text ) { - $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE ); - if ( count( $arr ) == 1 ) - return $text; - else - { - # First, do some preliminary work. This may shift some apostrophes from - # being mark-up to being text. It also counts the number of occurrences - # of bold and italics mark-ups. - $i = 0; - $numbold = 0; - $numitalics = 0; - foreach ( $arr as $r ) - { - if ( ( $i % 2 ) == 1 ) - { - # If there are ever four apostrophes, assume the first is supposed to - # be text, and the remaining three constitute mark-up for bold text. - if ( strlen( $arr[$i] ) == 4 ) - { - $arr[$i-1] .= "'"; - $arr[$i] = "'''"; - } - # If there are more than 5 apostrophes in a row, assume they're all - # text except for the last 5. - else if ( strlen( $arr[$i] ) > 5 ) - { - $arr[$i-1] .= str_repeat( "'", strlen( $arr[$i] ) - 5 ); - $arr[$i] = "'''''"; - } - # Count the number of occurrences of bold and italics mark-ups. - # We are not counting sequences of five apostrophes. - if ( strlen( $arr[$i] ) == 2 ) { $numitalics++; } - else if ( strlen( $arr[$i] ) == 3 ) { $numbold++; } - else if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; } - } - $i++; - } - - # If there is an odd number of both bold and italics, it is likely - # that one of the bold ones was meant to be an apostrophe followed - # by italics. Which one we cannot know for certain, but it is more - # likely to be one that has a single-letter word before it. - if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) - { - $i = 0; - $firstsingleletterword = -1; - $firstmultiletterword = -1; - $firstspace = -1; - foreach ( $arr as $r ) - { - if ( ( $i % 2 == 1 ) and ( strlen( $r ) == 3 ) ) - { - $x1 = substr ($arr[$i-1], -1); - $x2 = substr ($arr[$i-1], -2, 1); - if ($x1 == ' ') { - if ($firstspace == -1) $firstspace = $i; - } else if ($x2 == ' ') { - if ($firstsingleletterword == -1) $firstsingleletterword = $i; - } else { - if ($firstmultiletterword == -1) $firstmultiletterword = $i; - } - } - $i++; - } - - # If there is a single-letter word, use it! - if ($firstsingleletterword > -1) - { - $arr [ $firstsingleletterword ] = "''"; - $arr [ $firstsingleletterword-1 ] .= "'"; - } - # If not, but there's a multi-letter word, use that one. - else if ($firstmultiletterword > -1) - { - $arr [ $firstmultiletterword ] = "''"; - $arr [ $firstmultiletterword-1 ] .= "'"; - } - # ... otherwise use the first one that has neither. - # (notice that it is possible for all three to be -1 if, for example, - # there is only one pentuple-apostrophe in the line) - else if ($firstspace > -1) - { - $arr [ $firstspace ] = "''"; - $arr [ $firstspace-1 ] .= "'"; - } - } - - # Now let's actually convert our apostrophic mush to HTML! - $output = ''; - $buffer = ''; - $state = ''; - $i = 0; - foreach ($arr as $r) - { - if (($i % 2) == 0) - { - if ($state == 'both') - $buffer .= $r; - else - $output .= $r; - } - else - { - if (strlen ($r) == 2) - { - if ($state == 'i') - { $output .= '</i>'; $state = ''; } - else if ($state == 'bi') - { $output .= '</i>'; $state = 'b'; } - else if ($state == 'ib') - { $output .= '</b></i><b>'; $state = 'b'; } - else if ($state == 'both') - { $output .= '<b><i>'.$buffer.'</i>'; $state = 'b'; } - else # $state can be 'b' or '' - { $output .= '<i>'; $state .= 'i'; } - } - else if (strlen ($r) == 3) - { - if ($state == 'b') - { $output .= '</b>'; $state = ''; } - else if ($state == 'bi') - { $output .= '</i></b><i>'; $state = 'i'; } - else if ($state == 'ib') - { $output .= '</b>'; $state = 'i'; } - else if ($state == 'both') - { $output .= '<i><b>'.$buffer.'</b>'; $state = 'i'; } - else # $state can be 'i' or '' - { $output .= '<b>'; $state .= 'b'; } - } - else if (strlen ($r) == 5) - { - if ($state == 'b') - { $output .= '</b><i>'; $state = 'i'; } - else if ($state == 'i') - { $output .= '</i><b>'; $state = 'b'; } - else if ($state == 'bi') - { $output .= '</i></b>'; $state = ''; } - else if ($state == 'ib') - { $output .= '</b></i>'; $state = ''; } - else if ($state == 'both') - { $output .= '<i><b>'.$buffer.'</b></i>'; $state = ''; } - else # ($state == '') - { $buffer = ''; $state = 'both'; } - } - } - $i++; - } - # Now close all remaining tags. Notice that the order is important. - if ($state == 'b' || $state == 'ib') - $output .= '</b>'; - if ($state == 'i' || $state == 'bi' || $state == 'ib') - $output .= '</i>'; - if ($state == 'bi') - $output .= '</b>'; - # There might be lonely ''''', so make sure we have a buffer - if ($state == 'both' && $buffer) - $output .= '<b><i>'.$buffer.'</i></b>'; - return $output; - } - } - - /** - * Replace external links - * - * Note: this is all very hackish and the order of execution matters a lot. - * Make sure to run maintenance/parserTests.php if you change this code. - * - * @private - */ - function replaceExternalLinks( $text ) { - global $wgContLang; - $fname = 'Parser::replaceExternalLinks'; - wfProfileIn( $fname ); - - $sk = $this->mOptions->getSkin(); - - $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE ); - - $s = $this->replaceFreeExternalLinks( array_shift( $bits ) ); - - $i = 0; - while ( $i<count( $bits ) ) { - $url = $bits[$i++]; - $protocol = $bits[$i++]; - $text = $bits[$i++]; - $trail = $bits[$i++]; - - # The characters '<' and '>' (which were escaped by - # removeHTMLtags()) should not be included in - # URLs, per RFC 2396. - $m2 = array(); - if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) { - $text = substr($url, $m2[0][1]) . ' ' . $text; - $url = substr($url, 0, $m2[0][1]); - } - - # If the link text is an image URL, replace it with an <img> tag - # This happened by accident in the original parser, but some people used it extensively - $img = $this->maybeMakeExternalImage( $text ); - if ( $img !== false ) { - $text = $img; - } - - $dtrail = ''; - - # Set linktype for CSS - if URL==text, link is essentially free - $linktype = ($text == $url) ? 'free' : 'text'; - - # No link text, e.g. [http://domain.tld/some.link] - if ( $text == '' ) { - # Autonumber if allowed. See bug #5918 - if ( strpos( wfUrlProtocols(), substr($protocol, 0, strpos($protocol, ':')) ) !== false ) { - $text = '[' . ++$this->mAutonumber . ']'; - $linktype = 'autonumber'; - } else { - # Otherwise just use the URL - $text = htmlspecialchars( $url ); - $linktype = 'free'; - } - } else { - # Have link text, e.g. [http://domain.tld/some.link text]s - # Check for trail - list( $dtrail, $trail ) = Linker::splitTrail( $trail ); - } - - $text = $wgContLang->markNoConversion($text); - - $url = Sanitizer::cleanUrl( $url ); - - # Process the trail (i.e. everything after this link up until start of the next link), - # replacing any non-bracketed links - $trail = $this->replaceFreeExternalLinks( $trail ); - - # Use the encoded URL - # This means that users can paste URLs directly into the text - # Funny characters like ö aren't valid in URLs anyway - # This was changed in August 2004 - $s .= $sk->makeExternalLink( $url, $text, false, $linktype, $this->mTitle->getNamespace() ) . $dtrail . $trail; - - # Register link in the output object. - # Replace unnecessary URL escape codes with the referenced character - # This prevents spammers from hiding links from the filters - $pasteurized = Parser::replaceUnusualEscapes( $url ); - $this->mOutput->addExternalLink( $pasteurized ); - } - - wfProfileOut( $fname ); - return $s; - } - - /** - * Replace anything that looks like a URL with a link - * @private - */ - function replaceFreeExternalLinks( $text ) { - global $wgContLang; - $fname = 'Parser::replaceFreeExternalLinks'; - wfProfileIn( $fname ); - - $bits = preg_split( '/(\b(?:' . wfUrlProtocols() . '))/S', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); - $s = array_shift( $bits ); - $i = 0; - - $sk = $this->mOptions->getSkin(); - - while ( $i < count( $bits ) ){ - $protocol = $bits[$i++]; - $remainder = $bits[$i++]; - - $m = array(); - if ( preg_match( '/^('.self::EXT_LINK_URL_CLASS.'+)(.*)$/s', $remainder, $m ) ) { - # Found some characters after the protocol that look promising - $url = $protocol . $m[1]; - $trail = $m[2]; - - # special case: handle urls as url args: - # http://www.example.com/foo?=http://www.example.com/bar - if(strlen($trail) == 0 && - isset($bits[$i]) && - preg_match('/^'. wfUrlProtocols() . '$/S', $bits[$i]) && - preg_match( '/^('.self::EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m )) - { - # add protocol, arg - $url .= $bits[$i] . $m[1]; # protocol, url as arg to previous link - $i += 2; - $trail = $m[2]; - } - - # The characters '<' and '>' (which were escaped by - # removeHTMLtags()) should not be included in - # URLs, per RFC 2396. - $m2 = array(); - if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) { - $trail = substr($url, $m2[0][1]) . $trail; - $url = substr($url, 0, $m2[0][1]); - } - - # Move trailing punctuation to $trail - $sep = ',;\.:!?'; - # If there is no left bracket, then consider right brackets fair game too - if ( strpos( $url, '(' ) === false ) { - $sep .= ')'; - } - - $numSepChars = strspn( strrev( $url ), $sep ); - if ( $numSepChars ) { - $trail = substr( $url, -$numSepChars ) . $trail; - $url = substr( $url, 0, -$numSepChars ); - } - - $url = Sanitizer::cleanUrl( $url ); - - # Is this an external image? - $text = $this->maybeMakeExternalImage( $url ); - if ( $text === false ) { - # Not an image, make a link - $text = $sk->makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free', $this->mTitle->getNamespace() ); - # Register it in the output object... - # Replace unnecessary URL escape codes with their equivalent characters - $pasteurized = Parser::replaceUnusualEscapes( $url ); - $this->mOutput->addExternalLink( $pasteurized ); - } - $s .= $text . $trail; - } else { - $s .= $protocol . $remainder; - } - } - wfProfileOut( $fname ); - return $s; - } - - /** - * Replace unusual URL escape codes with their equivalent characters - * @param string - * @return string - * @static - * @todo This can merge genuinely required bits in the path or query string, - * breaking legit URLs. A proper fix would treat the various parts of - * the URL differently; as a workaround, just use the output for - * statistical records, not for actual linking/output. - */ - static function replaceUnusualEscapes( $url ) { - return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', - array( 'Parser', 'replaceUnusualEscapesCallback' ), $url ); - } - - /** - * Callback function used in replaceUnusualEscapes(). - * Replaces unusual URL escape codes with their equivalent character - * @static - * @private - */ - private static function replaceUnusualEscapesCallback( $matches ) { - $char = urldecode( $matches[0] ); - $ord = ord( $char ); - // Is it an unsafe or HTTP reserved character according to RFC 1738? - if ( $ord > 32 && $ord < 127 && strpos( '<>"#{}|\^~[]`;/?', $char ) === false ) { - // No, shouldn't be escaped - return $char; - } else { - // Yes, leave it escaped - return $matches[0]; - } - } - - /** - * make an image if it's allowed, either through the global - * option or through the exception - * @private - */ - function maybeMakeExternalImage( $url ) { - $sk = $this->mOptions->getSkin(); - $imagesfrom = $this->mOptions->getAllowExternalImagesFrom(); - $imagesexception = !empty($imagesfrom); - $text = false; - if ( $this->mOptions->getAllowExternalImages() - || ( $imagesexception && strpos( $url, $imagesfrom ) === 0 ) ) { - if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) { - # Image found - $text = $sk->makeExternalImage( htmlspecialchars( $url ) ); - } - } - return $text; - } - - /** - * Process [[ ]] wikilinks - * - * @private - */ - function replaceInternalLinks( $s ) { - global $wgContLang; - static $fname = 'Parser::replaceInternalLinks' ; - - wfProfileIn( $fname ); - - wfProfileIn( $fname.'-setup' ); - static $tc = FALSE; - # the % is needed to support urlencoded titles as well - if ( !$tc ) { $tc = Title::legalChars() . '#%'; } - - $sk = $this->mOptions->getSkin(); - - #split the entire text string on occurences of [[ - $a = explode( '[[', ' ' . $s ); - #get the first element (all text up to first [[), and remove the space we added - $s = array_shift( $a ); - $s = substr( $s, 1 ); - - # Match a link having the form [[namespace:link|alternate]]trail - static $e1 = FALSE; - if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD"; } - # Match cases where there is no "]]", which might still be images - static $e1_img = FALSE; - if ( !$e1_img ) { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; } - - $useLinkPrefixExtension = $wgContLang->linkPrefixExtension(); - $e2 = null; - if ( $useLinkPrefixExtension ) { - # Match the end of a line for a word that's not followed by whitespace, - # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched - $e2 = wfMsgForContent( 'linkprefix' ); - } - - if( is_null( $this->mTitle ) ) { - throw new MWException( __METHOD__.": \$this->mTitle is null\n" ); - } - $nottalk = !$this->mTitle->isTalkPage(); - - if ( $useLinkPrefixExtension ) { - $m = array(); - if ( preg_match( $e2, $s, $m ) ) { - $first_prefix = $m[2]; - } else { - $first_prefix = false; - } - } else { - $prefix = ''; - } - - if($wgContLang->hasVariants()) { - $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText()); - } else { - $selflink = array($this->mTitle->getPrefixedText()); - } - $useSubpages = $this->areSubpagesAllowed(); - wfProfileOut( $fname.'-setup' ); - - # Loop for each link - for ($k = 0; isset( $a[$k] ); $k++) { - $line = $a[$k]; - if ( $useLinkPrefixExtension ) { - wfProfileIn( $fname.'-prefixhandling' ); - if ( preg_match( $e2, $s, $m ) ) { - $prefix = $m[2]; - $s = $m[1]; - } else { - $prefix=''; - } - # first link - if($first_prefix) { - $prefix = $first_prefix; - $first_prefix = false; - } - wfProfileOut( $fname.'-prefixhandling' ); - } - - $might_be_img = false; - - wfProfileIn( "$fname-e1" ); - if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt - $text = $m[2]; - # If we get a ] at the beginning of $m[3] that means we have a link that's something like: - # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up, - # the real problem is with the $e1 regex - # See bug 1300. - # - # Still some problems for cases where the ] is meant to be outside punctuation, - # and no image is in sight. See bug 2095. - # - if( $text !== '' && - substr( $m[3], 0, 1 ) === ']' && - strpos($text, '[') !== false - ) - { - $text .= ']'; # so that replaceExternalLinks($text) works later - $m[3] = substr( $m[3], 1 ); - } - # fix up urlencoded title texts - if( strpos( $m[1], '%' ) !== false ) { - # Should anchors '#' also be rejected? - $m[1] = str_replace( array('<', '>'), array('<', '>'), urldecode($m[1]) ); - } - $trail = $m[3]; - } elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption - $might_be_img = true; - $text = $m[2]; - if ( strpos( $m[1], '%' ) !== false ) { - $m[1] = urldecode($m[1]); - } - $trail = ""; - } else { # Invalid form; output directly - $s .= $prefix . '[[' . $line ; - wfProfileOut( "$fname-e1" ); - continue; - } - wfProfileOut( "$fname-e1" ); - wfProfileIn( "$fname-misc" ); - - # Don't allow internal links to pages containing - # PROTO: where PROTO is a valid URL protocol; these - # should be external links. - if (preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $m[1])) { - $s .= $prefix . '[[' . $line ; - continue; - } - - # Make subpage if necessary - if( $useSubpages ) { - $link = $this->maybeDoSubpageLink( $m[1], $text ); - } else { - $link = $m[1]; - } - - $noforce = (substr($m[1], 0, 1) != ':'); - if (!$noforce) { - # Strip off leading ':' - $link = substr($link, 1); - } - - wfProfileOut( "$fname-misc" ); - wfProfileIn( "$fname-title" ); - $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) ); - if( !$nt ) { - $s .= $prefix . '[[' . $line; - wfProfileOut( "$fname-title" ); - continue; - } - - $ns = $nt->getNamespace(); - $iw = $nt->getInterWiki(); - wfProfileOut( "$fname-title" ); - - if ($might_be_img) { # if this is actually an invalid link - wfProfileIn( "$fname-might_be_img" ); - if ($ns == NS_IMAGE && $noforce) { #but might be an image - $found = false; - while (isset ($a[$k+1]) ) { - #look at the next 'line' to see if we can close it there - $spliced = array_splice( $a, $k + 1, 1 ); - $next_line = array_shift( $spliced ); - $m = explode( ']]', $next_line, 3 ); - if ( count( $m ) == 3 ) { - # the first ]] closes the inner link, the second the image - $found = true; - $text .= "[[{$m[0]}]]{$m[1]}"; - $trail = $m[2]; - break; - } elseif ( count( $m ) == 2 ) { - #if there's exactly one ]] that's fine, we'll keep looking - $text .= "[[{$m[0]}]]{$m[1]}"; - } else { - #if $next_line is invalid too, we need look no further - $text .= '[[' . $next_line; - break; - } - } - if ( !$found ) { - # we couldn't find the end of this imageLink, so output it raw - #but don't ignore what might be perfectly normal links in the text we've examined - $text = $this->replaceInternalLinks($text); - $s .= "{$prefix}[[$link|$text"; - # note: no $trail, because without an end, there *is* no trail - wfProfileOut( "$fname-might_be_img" ); - continue; - } - } else { #it's not an image, so output it raw - $s .= "{$prefix}[[$link|$text"; - # note: no $trail, because without an end, there *is* no trail - wfProfileOut( "$fname-might_be_img" ); - continue; - } - wfProfileOut( "$fname-might_be_img" ); - } - - $wasblank = ( '' == $text ); - if( $wasblank ) $text = $link; - - # Link not escaped by : , create the various objects - if( $noforce ) { - - # Interwikis - wfProfileIn( "$fname-interwiki" ); - if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) { - $this->mOutput->addLanguageLink( $nt->getFullText() ); - $s = rtrim($s . $prefix); - $s .= trim($trail, "\n") == '' ? '': $prefix . $trail; - wfProfileOut( "$fname-interwiki" ); - continue; - } - wfProfileOut( "$fname-interwiki" ); - - if ( $ns == NS_IMAGE ) { - wfProfileIn( "$fname-image" ); - if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) { - # recursively parse links inside the image caption - # actually, this will parse them in any other parameters, too, - # but it might be hard to fix that, and it doesn't matter ATM - $text = $this->replaceExternalLinks($text); - $text = $this->replaceInternalLinks($text); - - # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them - $s .= $prefix . $this->armorLinks( $this->makeImage( $nt, $text ) ) . $trail; - $this->mOutput->addImage( $nt->getDBkey() ); - - wfProfileOut( "$fname-image" ); - continue; - } else { - # We still need to record the image's presence on the page - $this->mOutput->addImage( $nt->getDBkey() ); - } - wfProfileOut( "$fname-image" ); - - } - - if ( $ns == NS_CATEGORY ) { - wfProfileIn( "$fname-category" ); - $s = rtrim($s . "\n"); # bug 87 - - if ( $wasblank ) { - $sortkey = $this->getDefaultSort(); - } else { - $sortkey = $text; - } - $sortkey = Sanitizer::decodeCharReferences( $sortkey ); - $sortkey = str_replace( "\n", '', $sortkey ); - $sortkey = $wgContLang->convertCategoryKey( $sortkey ); - $this->mOutput->addCategory( $nt->getDBkey(), $sortkey ); - - /** - * Strip the whitespace Category links produce, see bug 87 - * @todo We might want to use trim($tmp, "\n") here. - */ - $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail; - - wfProfileOut( "$fname-category" ); - continue; - } - } - - # Self-link checking - if( $nt->getFragment() === '' ) { - if( in_array( $nt->getPrefixedText(), $selflink, true ) ) { - $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail ); - continue; - } - } - - # Special and Media are pseudo-namespaces; no pages actually exist in them - if( $ns == NS_MEDIA ) { - $link = $sk->makeMediaLinkObj( $nt, $text ); - # Cloak with NOPARSE to avoid replacement in replaceExternalLinks - $s .= $prefix . $this->armorLinks( $link ) . $trail; - $this->mOutput->addImage( $nt->getDBkey() ); - continue; - } elseif( $ns == NS_SPECIAL ) { - if( SpecialPage::exists( $nt->getDBkey() ) ) { - $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix ); - } else { - $s .= $this->makeLinkHolder( $nt, $text, '', $trail, $prefix ); - } - continue; - } elseif( $ns == NS_IMAGE ) { - $img = wfFindFile( $nt ); - if( $img ) { - // Force a blue link if the file exists; may be a remote - // upload on the shared repository, and we want to see its - // auto-generated page. - $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix ); - $this->mOutput->addLink( $nt ); - continue; - } - } - $s .= $this->makeLinkHolder( $nt, $text, '', $trail, $prefix ); - } - wfProfileOut( $fname ); - return $s; - } - - /** - * Make a link placeholder. The text returned can be later resolved to a real link with - * replaceLinkHolders(). This is done for two reasons: firstly to avoid further - * parsing of interwiki links, and secondly to allow all existence checks and - * article length checks (for stub links) to be bundled into a single query. - * - */ - function makeLinkHolder( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) { - wfProfileIn( __METHOD__ ); - if ( ! is_object($nt) ) { - # Fail gracefully - $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}"; - } else { - # Separate the link trail from the rest of the link - list( $inside, $trail ) = Linker::splitTrail( $trail ); - - if ( $nt->isExternal() ) { - $nr = array_push( $this->mInterwikiLinkHolders['texts'], $prefix.$text.$inside ); - $this->mInterwikiLinkHolders['titles'][] = $nt; - $retVal = '<!--IWLINK '. ($nr-1) ."-->{$trail}"; - } else { - $nr = array_push( $this->mLinkHolders['namespaces'], $nt->getNamespace() ); - $this->mLinkHolders['dbkeys'][] = $nt->getDBkey(); - $this->mLinkHolders['queries'][] = $query; - $this->mLinkHolders['texts'][] = $prefix.$text.$inside; - $this->mLinkHolders['titles'][] = $nt; - - $retVal = '<!--LINK '. ($nr-1) ."-->{$trail}"; - } - } - wfProfileOut( __METHOD__ ); - return $retVal; - } - - /** - * Render a forced-blue link inline; protect against double expansion of - * URLs if we're in a mode that prepends full URL prefixes to internal links. - * Since this little disaster has to split off the trail text to avoid - * breaking URLs in the following text without breaking trails on the - * wiki links, it's been made into a horrible function. - * - * @param Title $nt - * @param string $text - * @param string $query - * @param string $trail - * @param string $prefix - * @return string HTML-wikitext mix oh yuck - */ - function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { - list( $inside, $trail ) = Linker::splitTrail( $trail ); - $sk = $this->mOptions->getSkin(); - $link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix ); - return $this->armorLinks( $link ) . $trail; - } - - /** - * Insert a NOPARSE hacky thing into any inline links in a chunk that's - * going to go through further parsing steps before inline URL expansion. - * - * In particular this is important when using action=render, which causes - * full URLs to be included. - * - * Oh man I hate our multi-layer parser! - * - * @param string more-or-less HTML - * @return string less-or-more HTML with NOPARSE bits - */ - function armorLinks( $text ) { - return preg_replace( '/\b(' . wfUrlProtocols() . ')/', - "{$this->mUniqPrefix}NOPARSE$1", $text ); - } - - /** - * Return true if subpage links should be expanded on this page. - * @return bool - */ - function areSubpagesAllowed() { - # Some namespaces don't allow subpages - global $wgNamespacesWithSubpages; - return !empty($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]); - } - - /** - * Handle link to subpage if necessary - * @param string $target the source of the link - * @param string &$text the link text, modified as necessary - * @return string the full name of the link - * @private - */ - function maybeDoSubpageLink($target, &$text) { - # Valid link forms: - # Foobar -- normal - # :Foobar -- override special treatment of prefix (images, language links) - # /Foobar -- convert to CurrentPage/Foobar - # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text - # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage - # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage - - $fname = 'Parser::maybeDoSubpageLink'; - wfProfileIn( $fname ); - $ret = $target; # default return value is no change - - # Some namespaces don't allow subpages, - # so only perform processing if subpages are allowed - if( $this->areSubpagesAllowed() ) { - $hash = strpos( $target, '#' ); - if( $hash !== false ) { - $suffix = substr( $target, $hash ); - $target = substr( $target, 0, $hash ); - } else { - $suffix = ''; - } - # bug 7425 - $target = trim( $target ); - # Look at the first character - if( $target != '' && $target{0} == '/' ) { - # / at end means we don't want the slash to be shown - $m = array(); - $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m ); - if( $trailingSlashes ) { - $noslash = $target = substr( $target, 1, -strlen($m[0][0]) ); - } else { - $noslash = substr( $target, 1 ); - } - - $ret = $this->mTitle->getPrefixedText(). '/' . trim($noslash) . $suffix; - if( '' === $text ) { - $text = $target . $suffix; - } # this might be changed for ugliness reasons - } else { - # check for .. subpage backlinks - $dotdotcount = 0; - $nodotdot = $target; - while( strncmp( $nodotdot, "../", 3 ) == 0 ) { - ++$dotdotcount; - $nodotdot = substr( $nodotdot, 3 ); - } - if($dotdotcount > 0) { - $exploded = explode( '/', $this->mTitle->GetPrefixedText() ); - if( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page - $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) ); - # / at the end means don't show full path - if( substr( $nodotdot, -1, 1 ) == '/' ) { - $nodotdot = substr( $nodotdot, 0, -1 ); - if( '' === $text ) { - $text = $nodotdot . $suffix; - } - } - $nodotdot = trim( $nodotdot ); - if( $nodotdot != '' ) { - $ret .= '/' . $nodotdot; - } - $ret .= $suffix; - } - } - } - } - - wfProfileOut( $fname ); - return $ret; - } - - /**#@+ - * Used by doBlockLevels() - * @private - */ - /* private */ function closeParagraph() { - $result = ''; - if ( '' != $this->mLastSection ) { - $result = '</' . $this->mLastSection . ">\n"; - } - $this->mInPre = false; - $this->mLastSection = ''; - return $result; - } - # getCommon() returns the length of the longest common substring - # of both arguments, starting at the beginning of both. - # - /* private */ function getCommon( $st1, $st2 ) { - $fl = strlen( $st1 ); - $shorter = strlen( $st2 ); - if ( $fl < $shorter ) { $shorter = $fl; } - - for ( $i = 0; $i < $shorter; ++$i ) { - if ( $st1{$i} != $st2{$i} ) { break; } - } - return $i; - } - # These next three functions open, continue, and close the list - # element appropriate to the prefix character passed into them. - # - /* private */ function openList( $char ) { - $result = $this->closeParagraph(); - - if ( '*' == $char ) { $result .= '<ul><li>'; } - else if ( '#' == $char ) { $result .= '<ol><li>'; } - else if ( ':' == $char ) { $result .= '<dl><dd>'; } - else if ( ';' == $char ) { - $result .= '<dl><dt>'; - $this->mDTopen = true; - } - else { $result = '<!-- ERR 1 -->'; } - - return $result; - } - - /* private */ function nextItem( $char ) { - if ( '*' == $char || '#' == $char ) { return '</li><li>'; } - else if ( ':' == $char || ';' == $char ) { - $close = '</dd>'; - if ( $this->mDTopen ) { $close = '</dt>'; } - if ( ';' == $char ) { - $this->mDTopen = true; - return $close . '<dt>'; - } else { - $this->mDTopen = false; - return $close . '<dd>'; - } - } - return '<!-- ERR 2 -->'; - } - - /* private */ function closeList( $char ) { - if ( '*' == $char ) { $text = '</li></ul>'; } - else if ( '#' == $char ) { $text = '</li></ol>'; } - else if ( ':' == $char ) { - if ( $this->mDTopen ) { - $this->mDTopen = false; - $text = '</dt></dl>'; - } else { - $text = '</dd></dl>'; - } - } - else { return '<!-- ERR 3 -->'; } - return $text."\n"; - } - /**#@-*/ - - /** - * Make lists from lines starting with ':', '*', '#', etc. - * - * @private - * @return string the lists rendered as HTML - */ - function doBlockLevels( $text, $linestart ) { - $fname = 'Parser::doBlockLevels'; - wfProfileIn( $fname ); - - # Parsing through the text line by line. The main thing - # happening here is handling of block-level elements p, pre, - # and making lists from lines starting with * # : etc. - # - $textLines = explode( "\n", $text ); - - $lastPrefix = $output = ''; - $this->mDTopen = $inBlockElem = false; - $prefixLength = 0; - $paragraphStack = false; - - if ( !$linestart ) { - $output .= array_shift( $textLines ); - } - foreach ( $textLines as $oLine ) { - $lastPrefixLength = strlen( $lastPrefix ); - $preCloseMatch = preg_match('/<\\/pre/i', $oLine ); - $preOpenMatch = preg_match('/<pre/i', $oLine ); - if ( !$this->mInPre ) { - # Multiple prefixes may abut each other for nested lists. - $prefixLength = strspn( $oLine, '*#:;' ); - $pref = substr( $oLine, 0, $prefixLength ); - - # eh? - $pref2 = str_replace( ';', ':', $pref ); - $t = substr( $oLine, $prefixLength ); - $this->mInPre = !empty($preOpenMatch); - } else { - # Don't interpret any other prefixes in preformatted text - $prefixLength = 0; - $pref = $pref2 = ''; - $t = $oLine; - } - - # List generation - if( $prefixLength && 0 == strcmp( $lastPrefix, $pref2 ) ) { - # Same as the last item, so no need to deal with nesting or opening stuff - $output .= $this->nextItem( substr( $pref, -1 ) ); - $paragraphStack = false; - - if ( substr( $pref, -1 ) == ';') { - # The one nasty exception: definition lists work like this: - # ; title : definition text - # So we check for : in the remainder text to split up the - # title and definition, without b0rking links. - $term = $t2 = ''; - if ($this->findColonNoLinks($t, $term, $t2) !== false) { - $t = $t2; - $output .= $term . $this->nextItem( ':' ); - } - } - } elseif( $prefixLength || $lastPrefixLength ) { - # Either open or close a level... - $commonPrefixLength = $this->getCommon( $pref, $lastPrefix ); - $paragraphStack = false; - - while( $commonPrefixLength < $lastPrefixLength ) { - $output .= $this->closeList( $lastPrefix{$lastPrefixLength-1} ); - --$lastPrefixLength; - } - if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) { - $output .= $this->nextItem( $pref{$commonPrefixLength-1} ); - } - while ( $prefixLength > $commonPrefixLength ) { - $char = substr( $pref, $commonPrefixLength, 1 ); - $output .= $this->openList( $char ); - - if ( ';' == $char ) { - # FIXME: This is dupe of code above - if ($this->findColonNoLinks($t, $term, $t2) !== false) { - $t = $t2; - $output .= $term . $this->nextItem( ':' ); - } - } - ++$commonPrefixLength; - } - $lastPrefix = $pref2; - } - if( 0 == $prefixLength ) { - wfProfileIn( "$fname-paragraph" ); - # No prefix (not in list)--go to paragraph mode - // XXX: use a stack for nestable elements like span, table and div - $openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t ); - $closematch = preg_match( - '/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'. - '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t ); - if ( $openmatch or $closematch ) { - $paragraphStack = false; - # TODO bug 5718: paragraph closed - $output .= $this->closeParagraph(); - if ( $preOpenMatch and !$preCloseMatch ) { - $this->mInPre = true; - } - if ( $closematch ) { - $inBlockElem = false; - } else { - $inBlockElem = true; - } - } else if ( !$inBlockElem && !$this->mInPre ) { - if ( ' ' == $t{0} and ( $this->mLastSection == 'pre' or trim($t) != '' ) ) { - // pre - if ($this->mLastSection != 'pre') { - $paragraphStack = false; - $output .= $this->closeParagraph().'<pre>'; - $this->mLastSection = 'pre'; - } - $t = substr( $t, 1 ); - } else { - // paragraph - if ( '' == trim($t) ) { - if ( $paragraphStack ) { - $output .= $paragraphStack.'<br />'; - $paragraphStack = false; - $this->mLastSection = 'p'; - } else { - if ($this->mLastSection != 'p' ) { - $output .= $this->closeParagraph(); - $this->mLastSection = ''; - $paragraphStack = '<p>'; - } else { - $paragraphStack = '</p><p>'; - } - } - } else { - if ( $paragraphStack ) { - $output .= $paragraphStack; - $paragraphStack = false; - $this->mLastSection = 'p'; - } else if ($this->mLastSection != 'p') { - $output .= $this->closeParagraph().'<p>'; - $this->mLastSection = 'p'; - } - } - } - } - wfProfileOut( "$fname-paragraph" ); - } - // somewhere above we forget to get out of pre block (bug 785) - if($preCloseMatch && $this->mInPre) { - $this->mInPre = false; - } - if ($paragraphStack === false) { - $output .= $t."\n"; - } - } - while ( $prefixLength ) { - $output .= $this->closeList( $pref2{$prefixLength-1} ); - --$prefixLength; - } - if ( '' != $this->mLastSection ) { - $output .= '</' . $this->mLastSection . '>'; - $this->mLastSection = ''; - } - - wfProfileOut( $fname ); - return $output; - } - - /** - * Split up a string on ':', ignoring any occurences inside tags - * to prevent illegal overlapping. - * @param string $str the string to split - * @param string &$before set to everything before the ':' - * @param string &$after set to everything after the ':' - * return string the position of the ':', or false if none found - */ - function findColonNoLinks($str, &$before, &$after) { - $fname = 'Parser::findColonNoLinks'; - wfProfileIn( $fname ); - - $pos = strpos( $str, ':' ); - if( $pos === false ) { - // Nothing to find! - wfProfileOut( $fname ); - return false; - } - - $lt = strpos( $str, '<' ); - if( $lt === false || $lt > $pos ) { - // Easy; no tag nesting to worry about - $before = substr( $str, 0, $pos ); - $after = substr( $str, $pos+1 ); - wfProfileOut( $fname ); - return $pos; - } - - // Ugly state machine to walk through avoiding tags. - $state = self::COLON_STATE_TEXT; - $stack = 0; - $len = strlen( $str ); - for( $i = 0; $i < $len; $i++ ) { - $c = $str{$i}; - - switch( $state ) { - // (Using the number is a performance hack for common cases) - case 0: // self::COLON_STATE_TEXT: - switch( $c ) { - case "<": - // Could be either a <start> tag or an </end> tag - $state = self::COLON_STATE_TAGSTART; - break; - case ":": - if( $stack == 0 ) { - // We found it! - $before = substr( $str, 0, $i ); - $after = substr( $str, $i + 1 ); - wfProfileOut( $fname ); - return $i; - } - // Embedded in a tag; don't break it. - break; - default: - // Skip ahead looking for something interesting - $colon = strpos( $str, ':', $i ); - if( $colon === false ) { - // Nothing else interesting - wfProfileOut( $fname ); - return false; - } - $lt = strpos( $str, '<', $i ); - if( $stack === 0 ) { - if( $lt === false || $colon < $lt ) { - // We found it! - $before = substr( $str, 0, $colon ); - $after = substr( $str, $colon + 1 ); - wfProfileOut( $fname ); - return $i; - } - } - if( $lt === false ) { - // Nothing else interesting to find; abort! - // We're nested, but there's no close tags left. Abort! - break 2; - } - // Skip ahead to next tag start - $i = $lt; - $state = self::COLON_STATE_TAGSTART; - } - break; - case 1: // self::COLON_STATE_TAG: - // In a <tag> - switch( $c ) { - case ">": - $stack++; - $state = self::COLON_STATE_TEXT; - break; - case "/": - // Slash may be followed by >? - $state = self::COLON_STATE_TAGSLASH; - break; - default: - // ignore - } - break; - case 2: // self::COLON_STATE_TAGSTART: - switch( $c ) { - case "/": - $state = self::COLON_STATE_CLOSETAG; - break; - case "!": - $state = self::COLON_STATE_COMMENT; - break; - case ">": - // Illegal early close? This shouldn't happen D: - $state = self::COLON_STATE_TEXT; - break; - default: - $state = self::COLON_STATE_TAG; - } - break; - case 3: // self::COLON_STATE_CLOSETAG: - // In a </tag> - if( $c == ">" ) { - $stack--; - if( $stack < 0 ) { - wfDebug( "Invalid input in $fname; too many close tags\n" ); - wfProfileOut( $fname ); - return false; - } - $state = self::COLON_STATE_TEXT; - } - break; - case self::COLON_STATE_TAGSLASH: - if( $c == ">" ) { - // Yes, a self-closed tag <blah/> - $state = self::COLON_STATE_TEXT; - } else { - // Probably we're jumping the gun, and this is an attribute - $state = self::COLON_STATE_TAG; - } - break; - case 5: // self::COLON_STATE_COMMENT: - if( $c == "-" ) { - $state = self::COLON_STATE_COMMENTDASH; - } - break; - case self::COLON_STATE_COMMENTDASH: - if( $c == "-" ) { - $state = self::COLON_STATE_COMMENTDASHDASH; - } else { - $state = self::COLON_STATE_COMMENT; - } - break; - case self::COLON_STATE_COMMENTDASHDASH: - if( $c == ">" ) { - $state = self::COLON_STATE_TEXT; - } else { - $state = self::COLON_STATE_COMMENT; - } - break; - default: - throw new MWException( "State machine error in $fname" ); - } - } - if( $stack > 0 ) { - wfDebug( "Invalid input in $fname; not enough close tags (stack $stack, state $state)\n" ); - return false; - } - wfProfileOut( $fname ); - return false; - } - - /** - * Return value of a magic variable (like PAGENAME) - * - * @private - */ - function getVariableValue( $index ) { - global $wgContLang, $wgSitename, $wgServer, $wgServerName, $wgScriptPath; - - /** - * Some of these require message or data lookups and can be - * expensive to check many times. - */ - if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) { - if ( isset( $this->mVarCache[$index] ) ) { - return $this->mVarCache[$index]; - } - } - - $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() ); - wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) ); - - # Use the time zone - global $wgLocaltimezone; - if ( isset( $wgLocaltimezone ) ) { - $oldtz = getenv( 'TZ' ); - putenv( 'TZ='.$wgLocaltimezone ); - } - - wfSuppressWarnings(); // E_STRICT system time bitching - $localTimestamp = date( 'YmdHis', $ts ); - $localMonth = date( 'm', $ts ); - $localMonthName = date( 'n', $ts ); - $localDay = date( 'j', $ts ); - $localDay2 = date( 'd', $ts ); - $localDayOfWeek = date( 'w', $ts ); - $localWeek = date( 'W', $ts ); - $localYear = date( 'Y', $ts ); - $localHour = date( 'H', $ts ); - if ( isset( $wgLocaltimezone ) ) { - putenv( 'TZ='.$oldtz ); - } - wfRestoreWarnings(); - - switch ( $index ) { - case 'currentmonth': - return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) ); - case 'currentmonthname': - return $this->mVarCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) ); - case 'currentmonthnamegen': - return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) ); - case 'currentmonthabbrev': - return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) ); - case 'currentday': - return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) ); - case 'currentday2': - return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) ); - case 'localmonth': - return $this->mVarCache[$index] = $wgContLang->formatNum( $localMonth ); - case 'localmonthname': - return $this->mVarCache[$index] = $wgContLang->getMonthName( $localMonthName ); - case 'localmonthnamegen': - return $this->mVarCache[$index] = $wgContLang->getMonthNameGen( $localMonthName ); - case 'localmonthabbrev': - return $this->mVarCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName ); - case 'localday': - return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay ); - case 'localday2': - return $this->mVarCache[$index] = $wgContLang->formatNum( $localDay2 ); - case 'pagename': - return wfEscapeWikiText( $this->mTitle->getText() ); - case 'pagenamee': - return $this->mTitle->getPartialURL(); - case 'fullpagename': - return wfEscapeWikiText( $this->mTitle->getPrefixedText() ); - case 'fullpagenamee': - return $this->mTitle->getPrefixedURL(); - case 'subpagename': - return wfEscapeWikiText( $this->mTitle->getSubpageText() ); - case 'subpagenamee': - return $this->mTitle->getSubpageUrlForm(); - case 'basepagename': - return wfEscapeWikiText( $this->mTitle->getBaseText() ); - case 'basepagenamee': - return wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) ); - case 'talkpagename': - if( $this->mTitle->canTalk() ) { - $talkPage = $this->mTitle->getTalkPage(); - return wfEscapeWikiText( $talkPage->getPrefixedText() ); - } else { - return ''; - } - case 'talkpagenamee': - if( $this->mTitle->canTalk() ) { - $talkPage = $this->mTitle->getTalkPage(); - return $talkPage->getPrefixedUrl(); - } else { - return ''; - } - case 'subjectpagename': - $subjPage = $this->mTitle->getSubjectPage(); - return wfEscapeWikiText( $subjPage->getPrefixedText() ); - case 'subjectpagenamee': - $subjPage = $this->mTitle->getSubjectPage(); - return $subjPage->getPrefixedUrl(); - case 'revisionid': - // Let the edit saving system know we should parse the page - // *after* a revision ID has been assigned. - $this->mOutput->setFlag( 'vary-revision' ); - wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision...\n" ); - return $this->mRevisionId; - case 'revisionday': - // Let the edit saving system know we should parse the page - // *after* a revision ID has been assigned. This is for null edits. - $this->mOutput->setFlag( 'vary-revision' ); - wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" ); - return intval( substr( $this->getRevisionTimestamp(), 6, 2 ) ); - case 'revisionday2': - // Let the edit saving system know we should parse the page - // *after* a revision ID has been assigned. This is for null edits. - $this->mOutput->setFlag( 'vary-revision' ); - wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" ); - return substr( $this->getRevisionTimestamp(), 6, 2 ); - case 'revisionmonth': - // Let the edit saving system know we should parse the page - // *after* a revision ID has been assigned. This is for null edits. - $this->mOutput->setFlag( 'vary-revision' ); - wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" ); - return intval( substr( $this->getRevisionTimestamp(), 4, 2 ) ); - case 'revisionyear': - // Let the edit saving system know we should parse the page - // *after* a revision ID has been assigned. This is for null edits. - $this->mOutput->setFlag( 'vary-revision' ); - wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" ); - return substr( $this->getRevisionTimestamp(), 0, 4 ); - case 'revisiontimestamp': - // Let the edit saving system know we should parse the page - // *after* a revision ID has been assigned. This is for null edits. - $this->mOutput->setFlag( 'vary-revision' ); - wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" ); - return $this->getRevisionTimestamp(); - case 'namespace': - return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) ); - case 'namespacee': - return wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) ); - case 'talkspace': - return $this->mTitle->canTalk() ? str_replace('_',' ',$this->mTitle->getTalkNsText()) : ''; - case 'talkspacee': - return $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : ''; - case 'subjectspace': - return $this->mTitle->getSubjectNsText(); - case 'subjectspacee': - return( wfUrlencode( $this->mTitle->getSubjectNsText() ) ); - case 'currentdayname': - return $this->mVarCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 ); - case 'currentyear': - return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true ); - case 'currenttime': - return $this->mVarCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false ); - case 'currenthour': - return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true ); - case 'currentweek': - // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to - // int to remove the padding - return $this->mVarCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) ); - case 'currentdow': - return $this->mVarCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) ); - case 'localdayname': - return $this->mVarCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 ); - case 'localyear': - return $this->mVarCache[$index] = $wgContLang->formatNum( $localYear, true ); - case 'localtime': - return $this->mVarCache[$index] = $wgContLang->time( $localTimestamp, false, false ); - case 'localhour': - return $this->mVarCache[$index] = $wgContLang->formatNum( $localHour, true ); - case 'localweek': - // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to - // int to remove the padding - return $this->mVarCache[$index] = $wgContLang->formatNum( (int)$localWeek ); - case 'localdow': - return $this->mVarCache[$index] = $wgContLang->formatNum( $localDayOfWeek ); - case 'numberofarticles': - return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::articles() ); - case 'numberoffiles': - return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::images() ); - case 'numberofusers': - return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::users() ); - case 'numberofpages': - return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::pages() ); - case 'numberofadmins': - return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::admins() ); - case 'numberofedits': - return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::edits() ); - case 'currenttimestamp': - return $this->mVarCache[$index] = wfTimestamp( TS_MW, $ts ); - case 'localtimestamp': - return $this->mVarCache[$index] = $localTimestamp; - case 'currentversion': - return $this->mVarCache[$index] = SpecialVersion::getVersion(); - case 'sitename': - return $wgSitename; - case 'server': - return $wgServer; - case 'servername': - return $wgServerName; - case 'scriptpath': - return $wgScriptPath; - case 'directionmark': - return $wgContLang->getDirMark(); - case 'contentlanguage': - global $wgContLanguageCode; - return $wgContLanguageCode; - default: - $ret = null; - if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret ) ) ) - return $ret; - else - return null; - } - } - - /** - * initialise the magic variables (like CURRENTMONTHNAME) - * - * @private - */ - function initialiseVariables() { - $fname = 'Parser::initialiseVariables'; - wfProfileIn( $fname ); - $variableIDs = MagicWord::getVariableIDs(); - - $this->mVariables = new MagicWordArray( $variableIDs ); - wfProfileOut( $fname ); - } - - /** - * Preprocess some wikitext and return the document tree. - * This is the ghost of replace_variables(). - * - * @param string $text The text to parse - * @param integer flags Bitwise combination of: - * self::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being - * included. Default is to assume a direct page view. - * - * The generated DOM tree must depend only on the input text and the flags. - * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. - * - * Any flag added to the $flags parameter here, or any other parameter liable to cause a - * change in the DOM tree for a given text, must be passed through the section identifier - * in the section edit link and thus back to extractSections(). - * - * The output of this function is currently only cached in process memory, but a persistent - * cache may be implemented at a later date which takes further advantage of these strict - * dependency requirements. - * - * @private - */ - function preprocessToDom ( $text, $flags = 0 ) { - $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags ); - return $dom; - } - - /* - * Return a three-element array: leading whitespace, string contents, trailing whitespace - */ - public static function splitWhitespace( $s ) { - $ltrimmed = ltrim( $s ); - $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) ); - $trimmed = rtrim( $ltrimmed ); - $diff = strlen( $ltrimmed ) - strlen( $trimmed ); - if ( $diff > 0 ) { - $w2 = substr( $ltrimmed, -$diff ); - } else { - $w2 = ''; - } - return array( $w1, $trimmed, $w2 ); - } - - /** - * Replace magic variables, templates, and template arguments - * with the appropriate text. Templates are substituted recursively, - * taking care to avoid infinite loops. - * - * Note that the substitution depends on value of $mOutputType: - * self::OT_WIKI: only {{subst:}} templates - * self::OT_PREPROCESS: templates but not extension tags - * self::OT_HTML: all templates and extension tags - * - * @param string $tex The text to transform - * @param PPFrame $frame Object describing the arguments passed to the template - * @param bool $argsOnly Only do argument (triple-brace) expansion, not double-brace expansion - * @private - */ - function replaceVariables( $text, $frame = false, $argsOnly = false ) { - # Prevent too big inclusions - if( strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) { - return $text; - } - - $fname = __METHOD__; - wfProfileIn( $fname ); - - if ( $frame === false ) { - $frame = $this->getPreprocessor()->newFrame(); - } elseif ( !( $frame instanceof PPFrame ) ) { - throw new MWException( __METHOD__ . ' called using the old argument format' ); - } - - $dom = $this->preprocessToDom( $text ); - $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0; - $text = $frame->expand( $dom, $flags ); - - wfProfileOut( $fname ); - return $text; - } - - /// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. - static function createAssocArgs( $args ) { - $assocArgs = array(); - $index = 1; - foreach( $args as $arg ) { - $eqpos = strpos( $arg, '=' ); - if ( $eqpos === false ) { - $assocArgs[$index++] = $arg; - } else { - $name = trim( substr( $arg, 0, $eqpos ) ); - $value = trim( substr( $arg, $eqpos+1 ) ); - if ( $value === false ) { - $value = ''; - } - if ( $name !== false ) { - $assocArgs[$name] = $value; - } - } - } - - return $assocArgs; - } - - /** - * Return the text of a template, after recursively - * replacing any variables or templates within the template. - * - * @param array $piece The parts of the template - * $piece['title']: the title, i.e. the part before the | - * $piece['parts']: the parameter array - * $piece['lineStart']: whether the brace was at the start of a line - * @param PPFrame The current frame, contains template arguments - * @return string the text of the template - * @private - */ - function braceSubstitution( $piece, $frame ) { - global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces; - $fname = __METHOD__; - wfProfileIn( $fname ); - wfProfileIn( __METHOD__.'-setup' ); - - # Flags - $found = false; # $text has been filled - $nowiki = false; # wiki markup in $text should be escaped - $isHTML = false; # $text is HTML, armour it against wikitext transformation - $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered - $isChildObj = false; # $text is a DOM node needing expansion in a child frame - $isLocalObj = false; # $text is a DOM node needing expansion in the current frame - - # Title object, where $text came from - $title = NULL; - - # $part1 is the bit before the first |, and must contain only title characters. - # Various prefixes will be stripped from it later. - $titleWithSpaces = $frame->expand( $piece['title'] ); - $part1 = trim( $titleWithSpaces ); - $titleText = false; - - # Original title text preserved for various purposes - $originalTitle = $part1; - - # $args is a list of argument nodes, starting from index 0, not including $part1 - $args = (null == $piece['parts']) ? array() : $piece['parts']; - wfProfileOut( __METHOD__.'-setup' ); - - # SUBST - wfProfileIn( __METHOD__.'-modifiers' ); - if ( !$found ) { - $mwSubst =& MagicWord::get( 'subst' ); - if ( $mwSubst->matchStartAndRemove( $part1 ) xor $this->ot['wiki'] ) { - # One of two possibilities is true: - # 1) Found SUBST but not in the PST phase - # 2) Didn't find SUBST and in the PST phase - # In either case, return without further processing - $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args ); - $isLocalObj = true; - $found = true; - } - } - - # Variables - if ( !$found && $args->getLength() == 0 ) { - $id = $this->mVariables->matchStartToEnd( $part1 ); - if ( $id !== false ) { - $text = $this->getVariableValue( $id ); - if (MagicWord::getCacheTTL($id)>-1) - $this->mOutput->mContainsOldMagic = true; - $found = true; - } - } - - # MSG, MSGNW and RAW - if ( !$found ) { - # Check for MSGNW: - $mwMsgnw =& MagicWord::get( 'msgnw' ); - if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) { - $nowiki = true; - } else { - # Remove obsolete MSG: - $mwMsg =& MagicWord::get( 'msg' ); - $mwMsg->matchStartAndRemove( $part1 ); - } - - # Check for RAW: - $mwRaw =& MagicWord::get( 'raw' ); - if ( $mwRaw->matchStartAndRemove( $part1 ) ) { - $forceRawInterwiki = true; - } - } - wfProfileOut( __METHOD__.'-modifiers' ); - - # Parser functions - if ( !$found ) { - wfProfileIn( __METHOD__ . '-pfunc' ); - - $colonPos = strpos( $part1, ':' ); - if ( $colonPos !== false ) { - # Case sensitive functions - $function = substr( $part1, 0, $colonPos ); - if ( isset( $this->mFunctionSynonyms[1][$function] ) ) { - $function = $this->mFunctionSynonyms[1][$function]; - } else { - # Case insensitive functions - $function = strtolower( $function ); - if ( isset( $this->mFunctionSynonyms[0][$function] ) ) { - $function = $this->mFunctionSynonyms[0][$function]; - } else { - $function = false; - } - } - if ( $function ) { - list( $callback, $flags ) = $this->mFunctionHooks[$function]; - $initialArgs = array( &$this ); - $funcArgs = array( trim( substr( $part1, $colonPos + 1 ) ) ); - if ( $flags & SFH_OBJECT_ARGS ) { - # Add a frame parameter, and pass the arguments as an array - $allArgs = $initialArgs; - $allArgs[] = $frame; - for ( $i = 0; $i < $args->getLength(); $i++ ) { - $funcArgs[] = $args->item( $i ); - } - $allArgs[] = $funcArgs; - } else { - # Convert arguments to plain text - for ( $i = 0; $i < $args->getLength(); $i++ ) { - $funcArgs[] = trim( $frame->expand( $args->item( $i ) ) ); - } - $allArgs = array_merge( $initialArgs, $funcArgs ); - } - - # Workaround for PHP bug 35229 and similar - if ( !is_callable( $callback ) ) { - throw new MWException( "Tag hook for $name is not callable\n" ); - } - $result = call_user_func_array( $callback, $allArgs ); - $found = true; - - if ( is_array( $result ) ) { - if ( isset( $result[0] ) ) { - $text = $result[0]; - unset( $result[0] ); - } - - // Extract flags into the local scope - // This allows callers to set flags such as nowiki, found, etc. - extract( $result ); - } else { - $text = $result; - } - } - } - wfProfileOut( __METHOD__ . '-pfunc' ); - } - - # Finish mangling title and then check for loops. - # Set $title to a Title object and $titleText to the PDBK - if ( !$found ) { - $ns = NS_TEMPLATE; - # Split the title into page and subpage - $subpage = ''; - $part1 = $this->maybeDoSubpageLink( $part1, $subpage ); - if ($subpage !== '') { - $ns = $this->mTitle->getNamespace(); - } - $title = Title::newFromText( $part1, $ns ); - if ( $title ) { - $titleText = $title->getPrefixedText(); - # Check for language variants if the template is not found - if($wgContLang->hasVariants() && $title->getArticleID() == 0){ - $wgContLang->findVariantLink($part1, $title); - } - # Do infinite loop check - if ( !$frame->loopCheck( $title ) ) { - $found = true; - $text = "<span class=\"error\">Template loop detected: [[$titleText]]</span>"; - wfDebug( __METHOD__.": template loop broken at '$titleText'\n" ); - } - # Do recursion depth check - $limit = $this->mOptions->getMaxTemplateDepth(); - if ( $frame->depth >= $limit ) { - $found = true; - $text = "<span class=\"error\">Template recursion depth limit exceeded ($limit)</span>"; - } - } - } - - # Load from database - if ( !$found && $title ) { - wfProfileIn( __METHOD__ . '-loadtpl' ); - if ( !$title->isExternal() ) { - if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) { - $text = SpecialPage::capturePath( $title ); - if ( is_string( $text ) ) { - $found = true; - $isHTML = true; - $this->disableCache(); - } - } else if ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) { - $found = false; //access denied - wfDebug( "$fname: template inclusion denied for " . $title->getPrefixedDBkey() ); - } else { - list( $text, $title ) = $this->getTemplateDom( $title ); - if ( $text !== false ) { - $found = true; - $isChildObj = true; - } - } - - # If the title is valid but undisplayable, make a link to it - if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) { - $text = "[[:$titleText]]"; - $found = true; - } - } elseif ( $title->isTrans() ) { - // Interwiki transclusion - if ( $this->ot['html'] && !$forceRawInterwiki ) { - $text = $this->interwikiTransclude( $title, 'render' ); - $isHTML = true; - } else { - $text = $this->interwikiTransclude( $title, 'raw' ); - // Preprocess it like a template - $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION ); - $isChildObj = true; - } - $found = true; - } - wfProfileOut( __METHOD__ . '-loadtpl' ); - } - - # If we haven't found text to substitute by now, we're done - # Recover the source wikitext and return it - if ( !$found ) { - $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args ); - wfProfileOut( $fname ); - return array( 'object' => $text ); - } - - # Expand DOM-style return values in a child frame - if ( $isChildObj ) { - # Clean up argument array - $newFrame = $frame->newChild( $args, $title ); - - if ( $nowiki ) { - $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG ); - } elseif ( $titleText !== false && $newFrame->isEmpty() ) { - # Expansion is eligible for the empty-frame cache - if ( isset( $this->mTplExpandCache[$titleText] ) ) { - $text = $this->mTplExpandCache[$titleText]; - } else { - $text = $newFrame->expand( $text ); - $this->mTplExpandCache[$titleText] = $text; - } - } else { - # Uncached expansion - $text = $newFrame->expand( $text ); - } - } - if ( $isLocalObj && $nowiki ) { - $text = $frame->expand( $text, PPFrame::RECOVER_ORIG ); - $isLocalObj = false; - } - - # Replace raw HTML by a placeholder - # Add a blank line preceding, to prevent it from mucking up - # immediately preceding headings - if ( $isHTML ) { - $text = "\n\n" . $this->insertStripItem( $text ); - } - # Escape nowiki-style return values - elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) { - $text = wfEscapeWikiText( $text ); - } - # Bug 529: if the template begins with a table or block-level - # element, it should be treated as beginning a new line. - # This behaviour is somewhat controversial. - elseif ( is_string( $text ) && !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{ - $text = "\n" . $text; - } - - if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) { - # Error, oversize inclusion - $text = "[[$originalTitle]]" . - $this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' ); - } - - if ( $isLocalObj ) { - $ret = array( 'object' => $text ); - } else { - $ret = array( 'text' => $text ); - } - - wfProfileOut( $fname ); - return $ret; - } - - /** - * Get the semi-parsed DOM representation of a template with a given title, - * and its redirect destination title. Cached. - */ - function getTemplateDom( $title ) { - $cacheTitle = $title; - $titleText = $title->getPrefixedDBkey(); - - if ( isset( $this->mTplRedirCache[$titleText] ) ) { - list( $ns, $dbk ) = $this->mTplRedirCache[$titleText]; - $title = Title::makeTitle( $ns, $dbk ); - $titleText = $title->getPrefixedDBkey(); - } - if ( isset( $this->mTplDomCache[$titleText] ) ) { - return array( $this->mTplDomCache[$titleText], $title ); - } - - // Cache miss, go to the database - list( $text, $title ) = $this->fetchTemplateAndTitle( $title ); - - if ( $text === false ) { - $this->mTplDomCache[$titleText] = false; - return array( false, $title ); - } - - $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION ); - $this->mTplDomCache[ $titleText ] = $dom; - - if (! $title->equals($cacheTitle)) { - $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] = - array( $title->getNamespace(),$cdb = $title->getDBkey() ); - } - - return array( $dom, $title ); - } - - /** - * Fetch the unparsed text of a template and register a reference to it. - */ - function fetchTemplateAndTitle( $title ) { - $templateCb = $this->mOptions->getTemplateCallback(); - $stuff = call_user_func( $templateCb, $title ); - $text = $stuff['text']; - $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title; - if ( isset( $stuff['deps'] ) ) { - foreach ( $stuff['deps'] as $dep ) { - $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] ); - } - } - return array($text,$finalTitle); - } - - function fetchTemplate( $title ) { - $rv = $this->fetchTemplateAndTitle($title); - return $rv[0]; - } - - /** - * Static function to get a template - * Can be overridden via ParserOptions::setTemplateCallback(). - */ - static function statelessFetchTemplate( $title ) { - $text = $skip = false; - $finalTitle = $title; - $deps = array(); - - // Loop to fetch the article, with up to 1 redirect - for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) { - # Give extensions a chance to select the revision instead - $id = false; // Assume current - wfRunHooks( 'BeforeParserFetchTemplateAndtitle', array( false, &$title, &$skip, &$id ) ); - - if( $skip ) { - $text = false; - $deps[] = array( - 'title' => $title, - 'page_id' => $title->getArticleID(), - 'rev_id' => null ); - break; - } - $rev = $id ? Revision::newFromId( $id ) : Revision::newFromTitle( $title ); - $rev_id = $rev ? $rev->getId() : 0; - - $deps[] = array( - 'title' => $title, - 'page_id' => $title->getArticleID(), - 'rev_id' => $rev_id ); - - if( $rev ) { - $text = $rev->getText(); - } elseif( $title->getNamespace() == NS_MEDIAWIKI ) { - global $wgLang; - $message = $wgLang->lcfirst( $title->getText() ); - $text = wfMsgForContentNoTrans( $message ); - if( wfEmptyMsg( $message, $text ) ) { - $text = false; - break; - } - } else { - break; - } - if ( $text === false ) { - break; - } - // Redirect? - $finalTitle = $title; - $title = Title::newFromRedirect( $text ); - } - return array( - 'text' => $text, - 'finalTitle' => $finalTitle, - 'deps' => $deps ); - } - - /** - * Transclude an interwiki link. - */ - function interwikiTransclude( $title, $action ) { - global $wgEnableScaryTranscluding; - - if (!$wgEnableScaryTranscluding) - return wfMsg('scarytranscludedisabled'); - - $url = $title->getFullUrl( "action=$action" ); - - if (strlen($url) > 255) - return wfMsg('scarytranscludetoolong'); - return $this->fetchScaryTemplateMaybeFromCache($url); - } - - function fetchScaryTemplateMaybeFromCache($url) { - global $wgTranscludeCacheExpiry; - $dbr = wfGetDB(DB_SLAVE); - $obj = $dbr->selectRow('transcache', array('tc_time', 'tc_contents'), - array('tc_url' => $url)); - if ($obj) { - $time = $obj->tc_time; - $text = $obj->tc_contents; - if ($time && time() < $time + $wgTranscludeCacheExpiry ) { - return $text; - } - } - - $text = Http::get($url); - if (!$text) - return wfMsg('scarytranscludefailed', $url); - - $dbw = wfGetDB(DB_MASTER); - $dbw->replace('transcache', array('tc_url'), array( - 'tc_url' => $url, - 'tc_time' => time(), - 'tc_contents' => $text)); - return $text; - } - - - /** - * Triple brace replacement -- used for template arguments - * @private - */ - function argSubstitution( $piece, $frame ) { - wfProfileIn( __METHOD__ ); - - $error = false; - $parts = $piece['parts']; - $nameWithSpaces = $frame->expand( $piece['title'] ); - $argName = trim( $nameWithSpaces ); - $object = false; - $text = $frame->getArgument( $argName ); - if ( $text === false && $parts->getLength() > 0 - && ( - $this->ot['html'] - || $this->ot['pre'] - || ( $this->ot['wiki'] && $frame->isTemplate() ) - ) - ) { - # No match in frame, use the supplied default - $object = $parts->item( 0 )->getChildren(); - } - if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) { - $error = '<!-- WARNING: argument omitted, expansion size too large -->'; - } - - if ( $text === false && $object === false ) { - # No match anywhere - $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts ); - } - if ( $error !== false ) { - $text .= $error; - } - if ( $object !== false ) { - $ret = array( 'object' => $object ); - } else { - $ret = array( 'text' => $text ); - } - - wfProfileOut( __METHOD__ ); - return $ret; - } - - /** - * Return the text to be used for a given extension tag. - * This is the ghost of strip(). - * - * @param array $params Associative array of parameters: - * name PPNode for the tag name - * attr PPNode for unparsed text where tag attributes are thought to be - * attributes Optional associative array of parsed attributes - * inner Contents of extension element - * noClose Original text did not have a close tag - * @param PPFrame $frame - */ - function extensionSubstitution( $params, $frame ) { - global $wgRawHtml, $wgContLang; - - $name = $frame->expand( $params['name'] ); - $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] ); - $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] ); - - $marker = "{$this->mUniqPrefix}-$name-" . sprintf('%08X', $this->mMarkerIndex++) . $this->mMarkerSuffix; - - if ( $this->ot['html'] ) { - $name = strtolower( $name ); - - $attributes = Sanitizer::decodeTagAttributes( $attrText ); - if ( isset( $params['attributes'] ) ) { - $attributes = $attributes + $params['attributes']; - } - switch ( $name ) { - case 'html': - if( $wgRawHtml ) { - $output = $content; - break; - } else { - throw new MWException( '<html> extension tag encountered unexpectedly' ); - } - case 'nowiki': - $output = Xml::escapeTagsOnly( $content ); - break; - case 'math': - $output = $wgContLang->armourMath( - MathRenderer::renderMath( $content, $attributes ) ); - break; - case 'gallery': - $output = $this->renderImageGallery( $content, $attributes ); - break; - default: - if( isset( $this->mTagHooks[$name] ) ) { - # Workaround for PHP bug 35229 and similar - if ( !is_callable( $this->mTagHooks[$name] ) ) { - throw new MWException( "Tag hook for $name is not callable\n" ); - } - $output = call_user_func_array( $this->mTagHooks[$name], - array( $content, $attributes, $this ) ); - } else { - throw new MWException( "Invalid call hook $name" ); - } - } - } else { - if ( is_null( $attrText ) ) { - $attrText = ''; - } - if ( isset( $params['attributes'] ) ) { - foreach ( $params['attributes'] as $attrName => $attrValue ) { - $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' . - htmlspecialchars( $attrValue ) . '"'; - } - } - if ( $content === null ) { - $output = "<$name$attrText/>"; - } else { - $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] ); - $output = "<$name$attrText>$content$close"; - } - } - - if ( $name == 'html' || $name == 'nowiki' ) { - $this->mStripState->nowiki->setPair( $marker, $output ); - } else { - $this->mStripState->general->setPair( $marker, $output ); - } - return $marker; - } - - /** - * Increment an include size counter - * - * @param string $type The type of expansion - * @param integer $size The size of the text - * @return boolean False if this inclusion would take it over the maximum, true otherwise - */ - function incrementIncludeSize( $type, $size ) { - if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize( $type ) ) { - return false; - } else { - $this->mIncludeSizes[$type] += $size; - return true; - } - } - - /** - * Detect __NOGALLERY__ magic word and set a placeholder - */ - function stripNoGallery( &$text ) { - # if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML, - # do not add TOC - $mw = MagicWord::get( 'nogallery' ); - $this->mOutput->mNoGallery = $mw->matchAndRemove( $text ) ; - } - - /** - * Find the first __TOC__ magic word and set a <!--MWTOC--> - * placeholder that will then be replaced by the real TOC in - * ->formatHeadings, this works because at this points real - * comments will have already been discarded by the sanitizer. - * - * Any additional __TOC__ magic words left over will be discarded - * as there can only be one TOC on the page. - */ - function stripToc( $text ) { - # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, - # do not add TOC - $mw = MagicWord::get( 'notoc' ); - if( $mw->matchAndRemove( $text ) ) { - $this->mShowToc = false; - } - - $mw = MagicWord::get( 'toc' ); - if( $mw->match( $text ) ) { - $this->mShowToc = true; - $this->mForceTocPosition = true; - - // Set a placeholder. At the end we'll fill it in with the TOC. - $text = $mw->replace( '<!--MWTOC-->', $text, 1 ); - - // Only keep the first one. - $text = $mw->replace( '', $text ); - } - return $text; - } - - /** - * This function accomplishes several tasks: - * 1) Auto-number headings if that option is enabled - * 2) Add an [edit] link to sections for users who have enabled the option and can edit the page - * 3) Add a Table of contents on the top for users who have enabled the option - * 4) Auto-anchor headings - * - * It loops through all headlines, collects the necessary data, then splits up the - * string and re-inserts the newly formatted headlines. - * - * @param string $text - * @param boolean $isMain - * @private - */ - function formatHeadings( $text, $isMain=true ) { - global $wgMaxTocLevel, $wgContLang; - - $doNumberHeadings = $this->mOptions->getNumberHeadings(); - if( !$this->mTitle->quickUserCan( 'edit' ) ) { - $showEditLink = 0; - } else { - $showEditLink = $this->mOptions->getEditSection(); - } - - # Inhibit editsection links if requested in the page - $esw =& MagicWord::get( 'noeditsection' ); - if( $esw->matchAndRemove( $text ) ) { - $showEditLink = 0; - } - - # Get all headlines for numbering them and adding funky stuff like [edit] - # links - this is for later, but we need the number of headlines right now - $matches = array(); - $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?'.'>)(?P<header>.*?)<\/H[1-6] *>/i', $text, $matches ); - - # if there are fewer than 4 headlines in the article, do not show TOC - # unless it's been explicitly enabled. - $enoughToc = $this->mShowToc && - (($numMatches >= 4) || $this->mForceTocPosition); - - # Allow user to stipulate that a page should have a "new section" - # link added via __NEWSECTIONLINK__ - $mw =& MagicWord::get( 'newsectionlink' ); - if( $mw->matchAndRemove( $text ) ) - $this->mOutput->setNewSection( true ); - - # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, - # override above conditions and always show TOC above first header - $mw =& MagicWord::get( 'forcetoc' ); - if ($mw->matchAndRemove( $text ) ) { - $this->mShowToc = true; - $enoughToc = true; - } - - # We need this to perform operations on the HTML - $sk = $this->mOptions->getSkin(); - - # headline counter - $headlineCount = 0; - $numVisible = 0; - - # Ugh .. the TOC should have neat indentation levels which can be - # passed to the skin functions. These are determined here - $toc = ''; - $full = ''; - $head = array(); - $sublevelCount = array(); - $levelCount = array(); - $toclevel = 0; - $level = 0; - $prevlevel = 0; - $toclevel = 0; - $prevtoclevel = 0; - $markerRegex = "{$this->mUniqPrefix}-h-(\d+)-{$this->mMarkerSuffix}"; - $baseTitleText = $this->mTitle->getPrefixedDBkey(); - $tocraw = array(); - - foreach( $matches[3] as $headline ) { - $isTemplate = false; - $titleText = false; - $sectionIndex = false; - $numbering = ''; - $markerMatches = array(); - if (preg_match("/^$markerRegex/", $headline, $markerMatches)) { - $serial = $markerMatches[1]; - list( $titleText, $sectionIndex ) = $this->mHeadings[$serial]; - $isTemplate = ($titleText != $baseTitleText); - $headline = preg_replace("/^$markerRegex/", "", $headline); - } - - if( $toclevel ) { - $prevlevel = $level; - $prevtoclevel = $toclevel; - } - $level = $matches[1][$headlineCount]; - - if( $doNumberHeadings || $enoughToc ) { - - if ( $level > $prevlevel ) { - # Increase TOC level - $toclevel++; - $sublevelCount[$toclevel] = 0; - if( $toclevel<$wgMaxTocLevel ) { - $prevtoclevel = $toclevel; - $toc .= $sk->tocIndent(); - $numVisible++; - } - } - elseif ( $level < $prevlevel && $toclevel > 1 ) { - # Decrease TOC level, find level to jump to - - if ( $toclevel == 2 && $level <= $levelCount[1] ) { - # Can only go down to level 1 - $toclevel = 1; - } else { - for ($i = $toclevel; $i > 0; $i--) { - if ( $levelCount[$i] == $level ) { - # Found last matching level - $toclevel = $i; - break; - } - elseif ( $levelCount[$i] < $level ) { - # Found first matching level below current level - $toclevel = $i + 1; - break; - } - } - } - if( $toclevel<$wgMaxTocLevel ) { - if($prevtoclevel < $wgMaxTocLevel) { - # Unindent only if the previous toc level was shown :p - $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel ); - } else { - $toc .= $sk->tocLineEnd(); - } - } - } - else { - # No change in level, end TOC line - if( $toclevel<$wgMaxTocLevel ) { - $toc .= $sk->tocLineEnd(); - } - } - - $levelCount[$toclevel] = $level; - - # count number of headlines for each level - @$sublevelCount[$toclevel]++; - $dot = 0; - for( $i = 1; $i <= $toclevel; $i++ ) { - if( !empty( $sublevelCount[$i] ) ) { - if( $dot ) { - $numbering .= '.'; - } - $numbering .= $wgContLang->formatNum( $sublevelCount[$i] ); - $dot = 1; - } - } - } - - # The safe header is a version of the header text safe to use for links - # Avoid insertion of weird stuff like <math> by expanding the relevant sections - $safeHeadline = $this->mStripState->unstripBoth( $headline ); - - # Remove link placeholders by the link text. - # <!--LINK number--> - # turns into - # link text with suffix - $safeHeadline = preg_replace( '/<!--LINK ([0-9]*)-->/e', - "\$this->mLinkHolders['texts'][\$1]", - $safeHeadline ); - $safeHeadline = preg_replace( '/<!--IWLINK ([0-9]*)-->/e', - "\$this->mInterwikiLinkHolders['texts'][\$1]", - $safeHeadline ); - - # Strip out HTML (other than plain <sup> and <sub>: bug 8393) - $tocline = preg_replace( - array( '#<(?!/?(sup|sub)).*?'.'>#', '#<(/?(sup|sub)).*?'.'>#' ), - array( '', '<$1>'), - $safeHeadline - ); - $tocline = trim( $tocline ); - - # For the anchor, strip out HTML-y stuff period - $safeHeadline = preg_replace( '/<.*?'.'>/', '', $safeHeadline ); - $safeHeadline = trim( $safeHeadline ); - - # Save headline for section edit hint before it's escaped - $headlineHint = $safeHeadline; - $safeHeadline = Sanitizer::escapeId( $safeHeadline ); - $refers[$headlineCount] = $safeHeadline; - - # count how many in assoc. array so we can track dupes in anchors - isset( $refers[$safeHeadline] ) ? $refers[$safeHeadline]++ : $refers[$safeHeadline] = 1; - $refcount[$headlineCount] = $refers[$safeHeadline]; - - # Don't number the heading if it is the only one (looks silly) - if( $doNumberHeadings && count( $matches[3] ) > 1) { - # the two are different if the line contains a link - $headline=$numbering . ' ' . $headline; - } - - # Create the anchor for linking from the TOC to the section - $anchor = $safeHeadline; - if($refcount[$headlineCount] > 1 ) { - $anchor .= '_' . $refcount[$headlineCount]; - } - if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) { - $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel); - $tocraw[] = array( 'toclevel' => $toclevel, 'level' => $level, 'line' => $tocline, 'number' => $numbering ); - } - # give headline the correct <h#> tag - if( $showEditLink && $sectionIndex !== false ) { - if( $isTemplate ) { - # Put a T flag in the section identifier, to indicate to extractSections() - # that sections inside <includeonly> should be counted. - $editlink = $sk->editSectionLinkForOther($titleText, "T-$sectionIndex"); - } else { - $editlink = $sk->editSectionLink($this->mTitle, $sectionIndex, $headlineHint); - } - } else { - $editlink = ''; - } - $head[$headlineCount] = $sk->makeHeadline( $level, $matches['attrib'][$headlineCount], $anchor, $headline, $editlink ); - - $headlineCount++; - } - - $this->mOutput->setSections( $tocraw ); - - # Never ever show TOC if no headers - if( $numVisible < 1 ) { - $enoughToc = false; - } - - if( $enoughToc ) { - if( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) { - $toc .= $sk->tocUnindent( $prevtoclevel - 1 ); - } - $toc = $sk->tocList( $toc ); - } - - # split up and insert constructed headlines - - $blocks = preg_split( '/<H[1-6].*?' . '>.*?<\/H[1-6]>/i', $text ); - $i = 0; - - foreach( $blocks as $block ) { - if( $showEditLink && $headlineCount > 0 && $i == 0 && $block != "\n" ) { - # This is the [edit] link that appears for the top block of text when - # section editing is enabled - - # Disabled because it broke block formatting - # For example, a bullet point in the top line - # $full .= $sk->editSectionLink(0); - } - $full .= $block; - if( $enoughToc && !$i && $isMain && !$this->mForceTocPosition ) { - # Top anchor now in skin - $full = $full.$toc; - } - - if( !empty( $head[$i] ) ) { - $full .= $head[$i]; - } - $i++; - } - if( $this->mForceTocPosition ) { - return str_replace( '<!--MWTOC-->', $toc, $full ); - } else { - return $full; - } - } - - /** - * Transform wiki markup when saving a page by doing \r\n -> \n - * conversion, substitting signatures, {{subst:}} templates, etc. - * - * @param string $text the text to transform - * @param Title &$title the Title object for the current article - * @param User &$user the User object describing the current user - * @param ParserOptions $options parsing options - * @param bool $clearState whether to clear the parser state first - * @return string the altered wiki markup - * @public - */ - function preSaveTransform( $text, &$title, $user, $options, $clearState = true ) { - $this->mOptions = $options; - $this->setTitle( $title ); - $this->setOutputType( self::OT_WIKI ); - - if ( $clearState ) { - $this->clearState(); - } - - $pairs = array( - "\r\n" => "\n", - ); - $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text ); - $text = $this->pstPass2( $text, $user ); - $text = $this->mStripState->unstripBoth( $text ); - return $text; - } - - /** - * Pre-save transform helper function - * @private - */ - function pstPass2( $text, $user ) { - global $wgContLang, $wgLocaltimezone; - - /* Note: This is the timestamp saved as hardcoded wikitext to - * the database, we use $wgContLang here in order to give - * everyone the same signature and use the default one rather - * than the one selected in each user's preferences. - * - * (see also bug 12815) - */ - $ts = $this->mOptions->getTimestamp(); - $tz = 'UTC'; - if ( isset( $wgLocaltimezone ) ) { - $unixts = wfTimestamp( TS_UNIX, $ts ); - $oldtz = getenv( 'TZ' ); - putenv( 'TZ='.$wgLocaltimezone ); - $ts = date( 'YmdHis', $unixts ); - $tz = date( 'T', $unixts ); # might vary on DST changeover! - putenv( 'TZ='.$oldtz ); - } - $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tz)"; - - # Variable replacement - # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags - $text = $this->replaceVariables( $text ); - - # Signatures - $sigText = $this->getUserSig( $user ); - $text = strtr( $text, array( - '~~~~~' => $d, - '~~~~' => "$sigText $d", - '~~~' => $sigText - ) ); - - # Context links: [[|name]] and [[name (context)|]] - # - global $wgLegalTitleChars; - $tc = "[$wgLegalTitleChars]"; - $nc = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii! - - $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\))\\|]]/"; # [[ns:page (context)|]] - $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\)|)(, $tc+|)\\|]]/"; # [[ns:page (context), context|]] - $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] - - # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]" - $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text ); - $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text ); - - $t = $this->mTitle->getText(); - $m = array(); - if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) { - $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text ); - } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && '' != "$m[1]$m[2]" ) { - $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text ); - } else { - # if there's no context, don't bother duplicating the title - $text = preg_replace( $p2, '[[\\1]]', $text ); - } - - # Trim trailing whitespace - $text = rtrim( $text ); - - return $text; - } - - /** - * Fetch the user's signature text, if any, and normalize to - * validated, ready-to-insert wikitext. - * - * @param User $user - * @return string - * @private - */ - function getUserSig( &$user ) { - global $wgMaxSigChars; - - $username = $user->getName(); - $nickname = $user->getOption( 'nickname' ); - $nickname = $nickname === '' ? $username : $nickname; - - if( mb_strlen( $nickname ) > $wgMaxSigChars ) { - $nickname = $username; - wfDebug( __METHOD__ . ": $username has overlong signature.\n" ); - } elseif( $user->getBoolOption( 'fancysig' ) !== false ) { - # Sig. might contain markup; validate this - if( $this->validateSig( $nickname ) !== false ) { - # Validated; clean up (if needed) and return it - return $this->cleanSig( $nickname, true ); - } else { - # Failed to validate; fall back to the default - $nickname = $username; - wfDebug( "Parser::getUserSig: $username has bad XML tags in signature.\n" ); - } - } - - // Make sure nickname doesnt get a sig in a sig - $nickname = $this->cleanSigInSig( $nickname ); - - # If we're still here, make it a link to the user page - $userText = wfEscapeWikiText( $username ); - $nickText = wfEscapeWikiText( $nickname ); - if ( $user->isAnon() ) { - return wfMsgExt( 'signature-anon', array( 'content', 'parsemag' ), $userText, $nickText ); - } else { - return wfMsgExt( 'signature', array( 'content', 'parsemag' ), $userText, $nickText ); - } - } - - /** - * Check that the user's signature contains no bad XML - * - * @param string $text - * @return mixed An expanded string, or false if invalid. - */ - function validateSig( $text ) { - return( wfIsWellFormedXmlFragment( $text ) ? $text : false ); - } - - /** - * Clean up signature text - * - * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures @see cleanSigInSig - * 2) Substitute all transclusions - * - * @param string $text - * @param $parsing Whether we're cleaning (preferences save) or parsing - * @return string Signature text - */ - function cleanSig( $text, $parsing = false ) { - if ( !$parsing ) { - global $wgTitle; - $this->clearState(); - $this->setTitle( $wgTitle ); - $this->mOptions = new ParserOptions; - $this->setOutputType = self::OT_PREPROCESS; - } - - # FIXME: regex doesn't respect extension tags or nowiki - # => Move this logic to braceSubstitution() - $substWord = MagicWord::get( 'subst' ); - $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase(); - $substText = '{{' . $substWord->getSynonym( 0 ); - - $text = preg_replace( $substRegex, $substText, $text ); - $text = $this->cleanSigInSig( $text ); - $dom = $this->preprocessToDom( $text ); - $frame = $this->getPreprocessor()->newFrame(); - $text = $frame->expand( $dom ); - - if ( !$parsing ) { - $text = $this->mStripState->unstripBoth( $text ); - } - - return $text; - } - - /** - * Strip ~~~, ~~~~ and ~~~~~ out of signatures - * @param string $text - * @return string Signature text with /~{3,5}/ removed - */ - function cleanSigInSig( $text ) { - $text = preg_replace( '/~{3,5}/', '', $text ); - return $text; - } - - /** - * Set up some variables which are usually set up in parse() - * so that an external function can call some class members with confidence - * @public - */ - function startExternalParse( &$title, $options, $outputType, $clearState = true ) { - $this->setTitle( $title ); - $this->mOptions = $options; - $this->setOutputType( $outputType ); - if ( $clearState ) { - $this->clearState(); - } - } - - /** - * Wrapper for preprocess() - * - * @param string $text the text to preprocess - * @param ParserOptions $options options - * @return string - * @public - */ - function transformMsg( $text, $options ) { - global $wgTitle; - static $executing = false; - - $fname = "Parser::transformMsg"; - - # Guard against infinite recursion - if ( $executing ) { - return $text; - } - $executing = true; - - wfProfileIn($fname); - $text = $this->preprocess( $text, $wgTitle, $options ); - - $executing = false; - wfProfileOut($fname); - return $text; - } - - /** - * Create an HTML-style tag, e.g. <yourtag>special text</yourtag> - * The callback should have the following form: - * function myParserHook( $text, $params, &$parser ) { ... } - * - * Transform and return $text. Use $parser for any required context, e.g. use - * $parser->getTitle() and $parser->getOptions() not $wgTitle or $wgOut->mParserOptions - * - * @public - * - * @param mixed $tag The tag to use, e.g. 'hook' for <hook> - * @param mixed $callback The callback function (and object) to use for the tag - * - * @return The old value of the mTagHooks array associated with the hook - */ - function setHook( $tag, $callback ) { - $tag = strtolower( $tag ); - $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null; - $this->mTagHooks[$tag] = $callback; - $this->mStripList[] = $tag; - - return $oldVal; - } - - function setTransparentTagHook( $tag, $callback ) { - $tag = strtolower( $tag ); - $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null; - $this->mTransparentTagHooks[$tag] = $callback; - - return $oldVal; - } - - /** - * Remove all tag hooks - */ - function clearTagHooks() { - $this->mTagHooks = array(); - $this->mStripList = $this->mDefaultStripList; - } - - /** - * Create a function, e.g. {{sum:1|2|3}} - * The callback function should have the form: - * function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... } - * - * The callback may either return the text result of the function, or an array with the text - * in element 0, and a number of flags in the other elements. The names of the flags are - * specified in the keys. Valid flags are: - * found The text returned is valid, stop processing the template. This - * is on by default. - * nowiki Wiki markup in the return value should be escaped - * isHTML The returned text is HTML, armour it against wikitext transformation - * - * @public - * - * @param string $id The magic word ID - * @param mixed $callback The callback function (and object) to use - * @param integer $flags a combination of the following flags: - * SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}} - * - * @return The old callback function for this name, if any - */ - function setFunctionHook( $id, $callback, $flags = 0 ) { - $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null; - $this->mFunctionHooks[$id] = array( $callback, $flags ); - - # Add to function cache - $mw = MagicWord::get( $id ); - if( !$mw ) - throw new MWException( 'Parser::setFunctionHook() expecting a magic word identifier.' ); - - $synonyms = $mw->getSynonyms(); - $sensitive = intval( $mw->isCaseSensitive() ); - - foreach ( $synonyms as $syn ) { - # Case - if ( !$sensitive ) { - $syn = strtolower( $syn ); - } - # Add leading hash - if ( !( $flags & SFH_NO_HASH ) ) { - $syn = '#' . $syn; - } - # Remove trailing colon - if ( substr( $syn, -1, 1 ) == ':' ) { - $syn = substr( $syn, 0, -1 ); - } - $this->mFunctionSynonyms[$sensitive][$syn] = $id; - } - return $oldVal; - } - - /** - * Get all registered function hook identifiers - * - * @return array - */ - function getFunctionHooks() { - return array_keys( $this->mFunctionHooks ); - } - - /** - * Replace <!--LINK--> link placeholders with actual links, in the buffer - * Placeholders created in Skin::makeLinkObj() - * Returns an array of link CSS classes, indexed by PDBK. - * $options is a bit field, RLH_FOR_UPDATE to select for update - */ - function replaceLinkHolders( &$text, $options = 0 ) { - global $wgUser; - global $wgContLang; - - $fname = 'Parser::replaceLinkHolders'; - wfProfileIn( $fname ); - - $pdbks = array(); - $colours = array(); - $linkcolour_ids = array(); - $sk = $this->mOptions->getSkin(); - $linkCache =& LinkCache::singleton(); - - if ( !empty( $this->mLinkHolders['namespaces'] ) ) { - wfProfileIn( $fname.'-check' ); - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $threshold = $wgUser->getOption('stubthreshold'); - - # Sort by namespace - asort( $this->mLinkHolders['namespaces'] ); - - # Generate query - $query = false; - $current = null; - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { - # Make title object - $title = $this->mLinkHolders['titles'][$key]; - - # Skip invalid entries. - # Result will be ugly, but prevents crash. - if ( is_null( $title ) ) { - continue; - } - $pdbk = $pdbks[$key] = $title->getPrefixedDBkey(); - - # Check if it's a static known link, e.g. interwiki - if ( $title->isAlwaysKnown() ) { - $colours[$pdbk] = ''; - } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) { - $colours[$pdbk] = ''; - $this->mOutput->addLink( $title, $id ); - } elseif ( $linkCache->isBadLink( $pdbk ) ) { - $colours[$pdbk] = 'new'; - } elseif ( $title->getNamespace() == NS_SPECIAL && !SpecialPage::exists( $pdbk ) ) { - $colours[$pdbk] = 'new'; - } else { - # Not in the link cache, add it to the query - if ( !isset( $current ) ) { - $current = $ns; - $query = "SELECT page_id, page_namespace, page_title, page_is_redirect"; - if ( $threshold > 0 ) { - $query .= ', page_len'; - } - $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN("; - } elseif ( $current != $ns ) { - $current = $ns; - $query .= ")) OR (page_namespace=$ns AND page_title IN("; - } else { - $query .= ', '; - } - - $query .= $dbr->addQuotes( $this->mLinkHolders['dbkeys'][$key] ); - } - } - if ( $query ) { - $query .= '))'; - if ( $options & RLH_FOR_UPDATE ) { - $query .= ' FOR UPDATE'; - } - - $res = $dbr->query( $query, $fname ); - - # Fetch data and form into an associative array - # non-existent = broken - while ( $s = $dbr->fetchObject($res) ) { - $title = Title::makeTitle( $s->page_namespace, $s->page_title ); - $pdbk = $title->getPrefixedDBkey(); - $linkCache->addGoodLinkObj( $s->page_id, $title ); - $this->mOutput->addLink( $title, $s->page_id ); - $colours[$pdbk] = $sk->getLinkColour( $s, $threshold ); - //add id to the extension todolist - $linkcolour_ids[$s->page_id] = $pdbk; - } - //pass an array of page_ids to an extension - wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); - } - wfProfileOut( $fname.'-check' ); - - # Do a second query for different language variants of links and categories - if($wgContLang->hasVariants()){ - $linkBatch = new LinkBatch(); - $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) - $categoryMap = array(); // maps $category_variant => $category (dbkeys) - $varCategories = array(); // category replacements oldDBkey => newDBkey - - $categories = $this->mOutput->getCategoryLinks(); - - // Add variants of links to link batch - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { - $title = $this->mLinkHolders['titles'][$key]; - if ( is_null( $title ) ) - continue; - - $pdbk = $title->getPrefixedDBkey(); - $titleText = $title->getText(); - - // generate all variants of the link title text - $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText); - - // if link was not found (in first query), add all variants to query - if ( !isset($colours[$pdbk]) ){ - foreach($allTextVariants as $textVariant){ - if($textVariant != $titleText){ - $variantTitle = Title::makeTitle( $ns, $textVariant ); - if(is_null($variantTitle)) continue; - $linkBatch->addObj( $variantTitle ); - $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; - } - } - } - } - - // process categories, check if a category exists in some variant - foreach( $categories as $category ){ - $variants = $wgContLang->convertLinkToAllVariants($category); - foreach($variants as $variant){ - if($variant != $category){ - $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) ); - if(is_null($variantTitle)) continue; - $linkBatch->addObj( $variantTitle ); - $categoryMap[$variant] = $category; - } - } - } - - - if(!$linkBatch->isEmpty()){ - // construct query - $titleClause = $linkBatch->constructSet('page', $dbr); - - $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect"; - if ( $threshold > 0 ) { - $variantQuery .= ', page_len'; - } - - $variantQuery .= " FROM $page WHERE $titleClause"; - if ( $options & RLH_FOR_UPDATE ) { - $variantQuery .= ' FOR UPDATE'; - } - - $varRes = $dbr->query( $variantQuery, $fname ); - - // for each found variants, figure out link holders and replace - while ( $s = $dbr->fetchObject($varRes) ) { - - $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title ); - $varPdbk = $variantTitle->getPrefixedDBkey(); - $vardbk = $variantTitle->getDBkey(); - - $holderKeys = array(); - if(isset($variantMap[$varPdbk])){ - $holderKeys = $variantMap[$varPdbk]; - $linkCache->addGoodLinkObj( $s->page_id, $variantTitle ); - $this->mOutput->addLink( $variantTitle, $s->page_id ); - } - - // loop over link holders - foreach($holderKeys as $key){ - $title = $this->mLinkHolders['titles'][$key]; - if ( is_null( $title ) ) continue; - - $pdbk = $title->getPrefixedDBkey(); - - if(!isset($colours[$pdbk])){ - // found link in some of the variants, replace the link holder data - $this->mLinkHolders['titles'][$key] = $variantTitle; - $this->mLinkHolders['dbkeys'][$key] = $variantTitle->getDBkey(); - - // set pdbk and colour - $pdbks[$key] = $varPdbk; - $colours[$varPdbk] = $sk->getLinkColour( $s, $threshold ); - $linkcolour_ids[$s->page_id] = $pdbk; - } - wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) ); - } - - // check if the object is a variant of a category - if(isset($categoryMap[$vardbk])){ - $oldkey = $categoryMap[$vardbk]; - if($oldkey != $vardbk) - $varCategories[$oldkey]=$vardbk; - } - } - - // rebuild the categories in original order (if there are replacements) - if(count($varCategories)>0){ - $newCats = array(); - $originalCats = $this->mOutput->getCategories(); - foreach($originalCats as $cat => $sortkey){ - // make the replacement - if( array_key_exists($cat,$varCategories) ) - $newCats[$varCategories[$cat]] = $sortkey; - else $newCats[$cat] = $sortkey; - } - $this->mOutput->setCategoryLinks($newCats); - } - } - } - - # Construct search and replace arrays - wfProfileIn( $fname.'-construct' ); - $replacePairs = array(); - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { - $pdbk = $pdbks[$key]; - $searchkey = "<!--LINK $key-->"; - $title = $this->mLinkHolders['titles'][$key]; - if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) { - $linkCache->addBadLinkObj( $title ); - $colours[$pdbk] = 'new'; - $this->mOutput->addLink( $title, 0 ); - $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title, - $this->mLinkHolders['texts'][$key], - $this->mLinkHolders['queries'][$key] ); - } else { - $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk], - $this->mLinkHolders['texts'][$key], - $this->mLinkHolders['queries'][$key] ); - } - } - $replacer = new HashtableReplacer( $replacePairs, 1 ); - wfProfileOut( $fname.'-construct' ); - - # Do the thing - wfProfileIn( $fname.'-replace' ); - $text = preg_replace_callback( - '/(<!--LINK .*?-->)/', - $replacer->cb(), - $text); - - wfProfileOut( $fname.'-replace' ); - } - - # Now process interwiki link holders - # This is quite a bit simpler than internal links - if ( !empty( $this->mInterwikiLinkHolders['texts'] ) ) { - wfProfileIn( $fname.'-interwiki' ); - # Make interwiki link HTML - $replacePairs = array(); - foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) { - $title = $this->mInterwikiLinkHolders['titles'][$key]; - $replacePairs[$key] = $sk->makeLinkObj( $title, $link ); - } - $replacer = new HashtableReplacer( $replacePairs, 1 ); - - $text = preg_replace_callback( - '/<!--IWLINK (.*?)-->/', - $replacer->cb(), - $text ); - wfProfileOut( $fname.'-interwiki' ); - } - - wfProfileOut( $fname ); - return $colours; - } - - /** - * Replace <!--LINK--> link placeholders with plain text of links - * (not HTML-formatted). - * @param string $text - * @return string - */ - function replaceLinkHoldersText( $text ) { - $fname = 'Parser::replaceLinkHoldersText'; - wfProfileIn( $fname ); - - $text = preg_replace_callback( - '/<!--(LINK|IWLINK) (.*?)-->/', - array( &$this, 'replaceLinkHoldersTextCallback' ), - $text ); - - wfProfileOut( $fname ); - return $text; - } - - /** - * @param array $matches - * @return string - * @private - */ - function replaceLinkHoldersTextCallback( $matches ) { - $type = $matches[1]; - $key = $matches[2]; - if( $type == 'LINK' ) { - if( isset( $this->mLinkHolders['texts'][$key] ) ) { - return $this->mLinkHolders['texts'][$key]; - } - } elseif( $type == 'IWLINK' ) { - if( isset( $this->mInterwikiLinkHolders['texts'][$key] ) ) { - return $this->mInterwikiLinkHolders['texts'][$key]; - } - } - return $matches[0]; - } - - /** - * Tag hook handler for 'pre'. - */ - function renderPreTag( $text, $attribs ) { - // Backwards-compatibility hack - $content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $text, 'i' ); - - $attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' ); - return wfOpenElement( 'pre', $attribs ) . - Xml::escapeTagsOnly( $content ) . - '</pre>'; - } - - /** - * Renders an image gallery from a text with one line per image. - * text labels may be given by using |-style alternative text. E.g. - * Image:one.jpg|The number "1" - * Image:tree.jpg|A tree - * given as text will return the HTML of a gallery with two images, - * labeled 'The number "1"' and - * 'A tree'. - */ - function renderImageGallery( $text, $params ) { - $ig = new ImageGallery(); - $ig->setContextTitle( $this->mTitle ); - $ig->setShowBytes( false ); - $ig->setShowFilename( false ); - $ig->setParser( $this ); - $ig->setHideBadImages(); - $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) ); - $ig->useSkin( $this->mOptions->getSkin() ); - $ig->mRevisionId = $this->mRevisionId; - - if( isset( $params['caption'] ) ) { - $caption = $params['caption']; - $caption = htmlspecialchars( $caption ); - $caption = $this->replaceInternalLinks( $caption ); - $ig->setCaptionHtml( $caption ); - } - if( isset( $params['perrow'] ) ) { - $ig->setPerRow( $params['perrow'] ); - } - if( isset( $params['widths'] ) ) { - $ig->setWidths( $params['widths'] ); - } - if( isset( $params['heights'] ) ) { - $ig->setHeights( $params['heights'] ); - } - - wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) ); - - $lines = explode( "\n", $text ); - foreach ( $lines as $line ) { - # match lines like these: - # Image:someimage.jpg|This is some image - $matches = array(); - preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches ); - # Skip empty lines - if ( count( $matches ) == 0 ) { - continue; - } - $tp = Title::newFromText( $matches[1] ); - $nt =& $tp; - if( is_null( $nt ) ) { - # Bogus title. Ignore these so we don't bomb out later. - continue; - } - if ( isset( $matches[3] ) ) { - $label = $matches[3]; - } else { - $label = ''; - } - - $html = $this->recursiveTagParse( trim( $label ) ); - - $ig->add( $nt, $html ); - - # Only add real images (bug #5586) - if ( $nt->getNamespace() == NS_IMAGE ) { - $this->mOutput->addImage( $nt->getDBkey() ); - } - } - return $ig->toHTML(); - } - - function getImageParams( $handler ) { - if ( $handler ) { - $handlerClass = get_class( $handler ); - } else { - $handlerClass = ''; - } - if ( !isset( $this->mImageParams[$handlerClass] ) ) { - // Initialise static lists - static $internalParamNames = array( - 'horizAlign' => array( 'left', 'right', 'center', 'none' ), - 'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', - 'bottom', 'text-bottom' ), - 'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless', - 'upright', 'border' ), - ); - static $internalParamMap; - if ( !$internalParamMap ) { - $internalParamMap = array(); - foreach ( $internalParamNames as $type => $names ) { - foreach ( $names as $name ) { - $magicName = str_replace( '-', '_', "img_$name" ); - $internalParamMap[$magicName] = array( $type, $name ); - } - } - } - - // Add handler params - $paramMap = $internalParamMap; - if ( $handler ) { - $handlerParamMap = $handler->getParamMap(); - foreach ( $handlerParamMap as $magic => $paramName ) { - $paramMap[$magic] = array( 'handler', $paramName ); - } - } - $this->mImageParams[$handlerClass] = $paramMap; - $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) ); - } - return array( $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ); - } - - /** - * Parse image options text and use it to make an image - */ - function makeImage( $title, $options ) { - # @TODO: let the MediaHandler specify its transform parameters - # - # Check if the options text is of the form "options|alt text" - # Options are: - # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang - # * left no resizing, just left align. label is used for alt= only - # * right same, but right aligned - # * none same, but not aligned - # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox - # * center center the image - # * framed Keep original image size, no magnify-button. - # * frameless like 'thumb' but without a frame. Keeps user preferences for width - # * upright reduce width for upright images, rounded to full __0 px - # * border draw a 1px border around the image - # vertical-align values (no % or length right now): - # * baseline - # * sub - # * super - # * top - # * text-top - # * middle - # * bottom - # * text-bottom - - $parts = array_map( 'trim', explode( '|', $options) ); - $sk = $this->mOptions->getSkin(); - - # Give extensions a chance to select the file revision for us - $skip = $time = false; - wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$title, &$skip, &$time ) ); - - if ( $skip ) { - return $sk->makeLinkObj( $title ); - } - - # Get parameter map - $file = wfFindFile( $title, $time ); - $handler = $file ? $file->getHandler() : false; - - list( $paramMap, $mwArray ) = $this->getImageParams( $handler ); - - # Process the input parameters - $caption = ''; - $params = array( 'frame' => array(), 'handler' => array(), - 'horizAlign' => array(), 'vertAlign' => array() ); - foreach( $parts as $part ) { - list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part ); - if ( isset( $paramMap[$magicName] ) ) { - list( $type, $paramName ) = $paramMap[$magicName]; - $params[$type][$paramName] = $value; - - // Special case; width and height come in one variable together - if( $type == 'handler' && $paramName == 'width' ) { - $m = array(); - if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $value, $m ) ) { - $params[$type]['width'] = intval( $m[1] ); - $params[$type]['height'] = intval( $m[2] ); - } else { - $params[$type]['width'] = intval( $value ); - } - } - } else { - $caption = $part; - } - } - - # Process alignment parameters - if ( $params['horizAlign'] ) { - $params['frame']['align'] = key( $params['horizAlign'] ); - } - if ( $params['vertAlign'] ) { - $params['frame']['valign'] = key( $params['vertAlign'] ); - } - - # Validate the handler parameters - if ( $handler ) { - foreach ( $params['handler'] as $name => $value ) { - if ( !$handler->validateParam( $name, $value ) ) { - unset( $params['handler'][$name] ); - } - } - } - - # Strip bad stuff out of the alt text - $alt = $this->replaceLinkHoldersText( $caption ); - - # make sure there are no placeholders in thumbnail attributes - # that are later expanded to html- so expand them now and - # remove the tags - $alt = $this->mStripState->unstripBoth( $alt ); - $alt = Sanitizer::stripAllTags( $alt ); - - $params['frame']['alt'] = $alt; - $params['frame']['caption'] = $caption; - - # Linker does the rest - $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'] ); - - # Give the handler a chance to modify the parser object - if ( $handler ) { - $handler->parserTransformHook( $this, $file ); - } - - return $ret; - } - - /** - * Set a flag in the output object indicating that the content is dynamic and - * shouldn't be cached. - */ - function disableCache() { - wfDebug( "Parser output marked as uncacheable.\n" ); - $this->mOutput->mCacheTime = -1; - } - - /**#@+ - * Callback from the Sanitizer for expanding items found in HTML attribute - * values, so they can be safely tested and escaped. - * @param string $text - * @param PPFrame $frame - * @return string - * @private - */ - function attributeStripCallback( &$text, $frame = false ) { - $text = $this->replaceVariables( $text, $frame ); - $text = $this->mStripState->unstripBoth( $text ); - return $text; - } - - /**#@-*/ - - /**#@+ - * Accessor/mutator - */ - function Title( $x = NULL ) { return wfSetVar( $this->mTitle, $x ); } - function Options( $x = NULL ) { return wfSetVar( $this->mOptions, $x ); } - function OutputType( $x = NULL ) { return wfSetVar( $this->mOutputType, $x ); } - /**#@-*/ - - /**#@+ - * Accessor - */ - function getTags() { return array_merge( array_keys($this->mTransparentTagHooks), array_keys( $this->mTagHooks ) ); } - /**#@-*/ - - - /** - * Break wikitext input into sections, and either pull or replace - * some particular section's text. - * - * External callers should use the getSection and replaceSection methods. - * - * @param string $text Page wikitext - * @param string $section A section identifier string of the form: - * <flag1> - <flag2> - ... - <section number> - * - * Currently the only recognised flag is "T", which means the target section number - * was derived during a template inclusion parse, in other words this is a template - * section edit link. If no flags are given, it was an ordinary section edit link. - * This flag is required to avoid a section numbering mismatch when a section is - * enclosed by <includeonly> (bug 6563). - * - * The section number 0 pulls the text before the first heading; other numbers will - * pull the given section along with its lower-level subsections. If the section is - * not found, $mode=get will return $newtext, and $mode=replace will return $text. - * - * @param string $mode One of "get" or "replace" - * @param string $newText Replacement text for section data. - * @return string for "get", the extracted section text. - * for "replace", the whole page with the section replaced. - */ - private function extractSections( $text, $section, $mode, $newText='' ) { - global $wgTitle; - $this->clearState(); - $this->setTitle( $wgTitle ); // not generally used but removes an ugly failure mode - $this->mOptions = new ParserOptions; - $this->setOutputType( self::OT_WIKI ); - $outText = ''; - $frame = $this->getPreprocessor()->newFrame(); - - // Process section extraction flags - $flags = 0; - $sectionParts = explode( '-', $section ); - $sectionIndex = array_pop( $sectionParts ); - foreach ( $sectionParts as $part ) { - if ( $part == 'T' ) { - $flags |= self::PTD_FOR_INCLUSION; - } - } - // Preprocess the text - $root = $this->preprocessToDom( $text, $flags ); - - // <h> nodes indicate section breaks - // They can only occur at the top level, so we can find them by iterating the root's children - $node = $root->getFirstChild(); - - // Find the target section - if ( $sectionIndex == 0 ) { - // Section zero doesn't nest, level=big - $targetLevel = 1000; - } else { - while ( $node ) { - if ( $node->getName() == 'h' ) { - $bits = $node->splitHeading(); - if ( $bits['i'] == $sectionIndex ) { - $targetLevel = $bits['level']; - break; - } - } - if ( $mode == 'replace' ) { - $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG ); - } - $node = $node->getNextSibling(); - } - } - - if ( !$node ) { - // Not found - if ( $mode == 'get' ) { - return $newText; - } else { - return $text; - } - } - - // Find the end of the section, including nested sections - do { - if ( $node->getName() == 'h' ) { - $bits = $node->splitHeading(); - $curLevel = $bits['level']; - if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) { - break; - } - } - if ( $mode == 'get' ) { - $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG ); - } - $node = $node->getNextSibling(); - } while ( $node ); - - // Write out the remainder (in replace mode only) - if ( $mode == 'replace' ) { - // Output the replacement text - // Add two newlines on -- trailing whitespace in $newText is conventionally - // stripped by the editor, so we need both newlines to restore the paragraph gap - $outText .= $newText . "\n\n"; - while ( $node ) { - $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG ); - $node = $node->getNextSibling(); - } - } - - if ( is_string( $outText ) ) { - // Re-insert stripped tags - $outText = trim( $this->mStripState->unstripBoth( $outText ) ); - } - - return $outText; - } - - /** - * This function returns the text of a section, specified by a number ($section). - * A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or - * the first section before any such heading (section 0). - * - * If a section contains subsections, these are also returned. - * - * @param string $text text to look in - * @param string $section section identifier - * @param string $deftext default to return if section is not found - * @return string text of the requested section - */ - public function getSection( $text, $section, $deftext='' ) { - return $this->extractSections( $text, $section, "get", $deftext ); - } - - public function replaceSection( $oldtext, $section, $text ) { - return $this->extractSections( $oldtext, $section, "replace", $text ); - } - - /** - * Get the timestamp associated with the current revision, adjusted for - * the default server-local timestamp - */ - function getRevisionTimestamp() { - if ( is_null( $this->mRevisionTimestamp ) ) { - wfProfileIn( __METHOD__ ); - global $wgContLang; - $dbr = wfGetDB( DB_SLAVE ); - $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', - array( 'rev_id' => $this->mRevisionId ), __METHOD__ ); - - // Normalize timestamp to internal MW format for timezone processing. - // This has the added side-effect of replacing a null value with - // the current time, which gives us more sensible behavior for - // previews. - $timestamp = wfTimestamp( TS_MW, $timestamp ); - - // The cryptic '' timezone parameter tells to use the site-default - // timezone offset instead of the user settings. - // - // Since this value will be saved into the parser cache, served - // to other users, and potentially even used inside links and such, - // it needs to be consistent for all visitors. - $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' ); - - wfProfileOut( __METHOD__ ); - } - return $this->mRevisionTimestamp; - } - - /** - * Mutator for $mDefaultSort - * - * @param $sort New value - */ - public function setDefaultSort( $sort ) { - $this->mDefaultSort = $sort; - } - - /** - * Accessor for $mDefaultSort - * Will use the title/prefixed title if none is set - * - * @return string - */ - public function getDefaultSort() { - if( $this->mDefaultSort !== false ) { - return $this->mDefaultSort; - } else { - return $this->mTitle->getNamespace() == NS_CATEGORY - ? $this->mTitle->getText() - : $this->mTitle->getPrefixedText(); - } - } - - /** - * Try to guess the section anchor name based on a wikitext fragment - * presumably extracted from a heading, for example "Header" from - * "== Header ==". - */ - public function guessSectionNameFromWikiText( $text ) { - # Strip out wikitext links(they break the anchor) - $text = $this->stripSectionName( $text ); - $headline = Sanitizer::decodeCharReferences( $text ); - # strip out HTML - $headline = StringUtils::delimiterReplace( '<', '>', '', $headline ); - $headline = trim( $headline ); - $sectionanchor = '#' . urlencode( str_replace( ' ', '_', $headline ) ); - $replacearray = array( - '%3A' => ':', - '%' => '.' - ); - return str_replace( - array_keys( $replacearray ), - array_values( $replacearray ), - $sectionanchor ); - } - - /** - * Strips a text string of wikitext for use in a section anchor - * - * Accepts a text string and then removes all wikitext from the - * string and leaves only the resultant text (i.e. the result of - * [[User:WikiSysop|Sysop]] would be "Sysop" and the result of - * [[User:WikiSysop]] would be "User:WikiSysop") - this is intended - * to create valid section anchors by mimicing the output of the - * parser when headings are parsed. - * - * @param $text string Text string to be stripped of wikitext - * for use in a Section anchor - * @return Filtered text string - */ - public function stripSectionName( $text ) { - # Strip internal link markup - $text = preg_replace('/\[\[:?([^[|]+)\|([^[]+)\]\]/','$2',$text); - $text = preg_replace('/\[\[:?([^[]+)\|?\]\]/','$1',$text); - - # Strip external link markup (FIXME: Not Tolerant to blank link text - # I.E. [http://www.mediawiki.org] will render as [1] or something depending - # on how many empty links there are on the page - need to figure that out. - $text = preg_replace('/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/','$2',$text); - - # Parse wikitext quotes (italics & bold) - $text = $this->doQuotes($text); - - # Strip HTML tags - $text = StringUtils::delimiterReplace( '<', '>', '', $text ); - return $text; - } - - function srvus( $text ) { - return $this->testSrvus( $text, $this->mOutputType ); - } - - /** - * strip/replaceVariables/unstrip for preprocessor regression testing - */ - function testSrvus( $text, $title, $options, $outputType = self::OT_HTML ) { - $this->clearState(); - if ( ! ( $title instanceof Title ) ) { - $title = Title::newFromText( $title ); - } - $this->mTitle = $title; - $this->mOptions = $options; - $this->setOutputType( $outputType ); - $text = $this->replaceVariables( $text ); - $text = $this->mStripState->unstripBoth( $text ); - $text = Sanitizer::removeHTMLtags( $text ); - return $text; - } - - function testPst( $text, $title, $options ) { - global $wgUser; - if ( ! ( $title instanceof Title ) ) { - $title = Title::newFromText( $title ); - } - return $this->preSaveTransform( $text, $title, $wgUser, $options ); - } - - function testPreprocess( $text, $title, $options ) { - if ( ! ( $title instanceof Title ) ) { - $title = Title::newFromText( $title ); - } - return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS ); - } - - function markerSkipCallback( $s, $callback ) { - $i = 0; - $out = ''; - while ( $i < strlen( $s ) ) { - $markerStart = strpos( $s, $this->mUniqPrefix, $i ); - if ( $markerStart === false ) { - $out .= call_user_func( $callback, substr( $s, $i ) ); - break; - } else { - $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) ); - $markerEnd = strpos( $s, $this->mMarkerSuffix, $markerStart ); - if ( $markerEnd === false ) { - $out .= substr( $s, $markerStart ); - break; - } else { - $markerEnd += strlen( $this->mMarkerSuffix ); - $out .= substr( $s, $markerStart, $markerEnd - $markerStart ); - $i = $markerEnd; - } - } - } - return $out; - } -} - -/** - * @todo document, briefly. - * @addtogroup Parser - */ -class StripState { - var $general, $nowiki; - - function __construct() { - $this->general = new ReplacementArray; - $this->nowiki = new ReplacementArray; - } - - function unstripGeneral( $text ) { - wfProfileIn( __METHOD__ ); - do { - $oldText = $text; - $text = $this->general->replace( $text ); - } while ( $text != $oldText ); - wfProfileOut( __METHOD__ ); - return $text; - } - - function unstripNoWiki( $text ) { - wfProfileIn( __METHOD__ ); - do { - $oldText = $text; - $text = $this->nowiki->replace( $text ); - } while ( $text != $oldText ); - wfProfileOut( __METHOD__ ); - return $text; - } - - function unstripBoth( $text ) { - wfProfileIn( __METHOD__ ); - do { - $oldText = $text; - $text = $this->general->replace( $text ); - $text = $this->nowiki->replace( $text ); - } while ( $text != $oldText ); - wfProfileOut( __METHOD__ ); - return $text; - } -} - -/** - * @todo document, briefly. - * @addtogroup Parser - */ -class OnlyIncludeReplacer { - var $output = ''; - - function replace( $matches ) { - if ( substr( $matches[1], -1 ) == "\n" ) { - $this->output .= substr( $matches[1], 0, -1 ); - } else { - $this->output .= $matches[1]; - } - } -} - diff --git a/includes/ParserCache.php b/includes/ParserCache.php deleted file mode 100644 index 129b7132..00000000 --- a/includes/ParserCache.php +++ /dev/null @@ -1,119 +0,0 @@ -<?php -/** - * - * @addtogroup Cache - * @todo document - */ -class ParserCache { - /** - * Get an instance of this object - */ - public static function &singleton() { - static $instance; - if ( !isset( $instance ) ) { - global $parserMemc; - $instance = new ParserCache( $parserMemc ); - } - return $instance; - } - - /** - * Setup a cache pathway with a given back-end storage mechanism. - * May be a memcached client or a BagOStuff derivative. - * - * @param object $memCached - */ - function __construct( &$memCached ) { - $this->mMemc =& $memCached; - } - - function getKey( &$article, &$user ) { - global $action; - $hash = $user->getPageRenderingHash(); - if( !$article->mTitle->quickUserCan( 'edit' ) ) { - // section edit links are suppressed even if the user has them on - $edit = '!edit=0'; - } else { - $edit = ''; - } - $pageid = intval( $article->getID() ); - $renderkey = (int)($action == 'render'); - $key = wfMemcKey( 'pcache', 'idhash', "$pageid-$renderkey!$hash$edit" ); - return $key; - } - - function getETag( &$article, &$user ) { - return 'W/"' . $this->getKey($article, $user) . "--" . $article->mTouched. '"'; - } - - function get( &$article, &$user ) { - global $wgCacheEpoch; - $fname = 'ParserCache::get'; - wfProfileIn( $fname ); - - $key = $this->getKey( $article, $user ); - - wfDebug( "Trying parser cache $key\n" ); - $value = $this->mMemc->get( $key ); - if ( is_object( $value ) ) { - wfDebug( "Found.\n" ); - # Delete if article has changed since the cache was made - $canCache = $article->checkTouched(); - $cacheTime = $value->getCacheTime(); - $touched = $article->mTouched; - if ( !$canCache || $value->expired( $touched ) ) { - if ( !$canCache ) { - wfIncrStats( "pcache_miss_invalid" ); - wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" ); - } else { - wfIncrStats( "pcache_miss_expired" ); - wfDebug( "Key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" ); - } - $this->mMemc->delete( $key ); - $value = false; - } else { - if ( isset( $value->mTimestamp ) ) { - $article->mTimestamp = $value->mTimestamp; - } - wfIncrStats( "pcache_hit" ); - } - } else { - wfDebug( "Parser cache miss.\n" ); - wfIncrStats( "pcache_miss_absent" ); - $value = false; - } - - wfProfileOut( $fname ); - return $value; - } - - function save( $parserOutput, &$article, &$user ){ - global $wgParserCacheExpireTime; - $key = $this->getKey( $article, $user ); - - if( $parserOutput->getCacheTime() != -1 ) { - - $now = wfTimestampNow(); - $parserOutput->setCacheTime( $now ); - - // Save the timestamp so that we don't have to load the revision row on view - $parserOutput->mTimestamp = $article->getTimestamp(); - - $parserOutput->mText .= "\n<!-- Saved in parser cache with key $key and timestamp $now -->\n"; - wfDebug( "Saved in parser cache with key $key and timestamp $now\n" ); - - if( $parserOutput->containsOldMagic() ){ - $expire = 3600; # 1 hour - } else { - $expire = $wgParserCacheExpireTime; - } - $this->mMemc->set( $key, $parserOutput, $expire ); - - } else { - wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" ); - } - } - -} - - diff --git a/includes/ParserOptions.php b/includes/ParserOptions.php deleted file mode 100644 index 996bba21..00000000 --- a/includes/ParserOptions.php +++ /dev/null @@ -1,145 +0,0 @@ -<?php - -/** - * Set options of the Parser - * @todo document - * @addtogroup Parser - */ -class ParserOptions -{ - # All variables are supposed to be private in theory, although in practise this is not the case. - var $mUseTeX; # Use texvc to expand <math> tags - var $mUseDynamicDates; # Use DateFormatter to format dates - var $mInterwikiMagic; # Interlanguage links are removed and returned in an array - var $mAllowExternalImages; # Allow external images inline - var $mAllowExternalImagesFrom; # If not, any exception? - var $mSkin; # Reference to the preferred skin - var $mDateFormat; # Date format index - var $mEditSection; # Create "edit section" links - var $mNumberHeadings; # Automatically number headings - var $mAllowSpecialInclusion; # Allow inclusion of special pages - var $mTidy; # Ask for tidy cleanup - var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR - var $mMaxIncludeSize; # Maximum size of template expansions, in bytes - var $mMaxPPNodeCount; # Maximum number of nodes touched by PPFrame::expand() - var $mMaxTemplateDepth; # Maximum recursion depth for templates within templates - var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS - var $mTemplateCallback; # Callback for template fetching - var $mEnableLimitReport; # Enable limit report in an HTML comment on output - var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc. - - var $mUser; # Stored user object, just used to initialise the skin - - function getUseTeX() { return $this->mUseTeX; } - function getUseDynamicDates() { return $this->mUseDynamicDates; } - function getInterwikiMagic() { return $this->mInterwikiMagic; } - function getAllowExternalImages() { return $this->mAllowExternalImages; } - function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; } - function getEditSection() { return $this->mEditSection; } - function getNumberHeadings() { return $this->mNumberHeadings; } - function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; } - function getTidy() { return $this->mTidy; } - function getInterfaceMessage() { return $this->mInterfaceMessage; } - function getMaxIncludeSize() { return $this->mMaxIncludeSize; } - function getMaxPPNodeCount() { return $this->mMaxPPNodeCount; } - function getMaxTemplateDepth() { return $this->mMaxTemplateDepth; } - function getRemoveComments() { return $this->mRemoveComments; } - function getTemplateCallback() { return $this->mTemplateCallback; } - function getEnableLimitReport() { return $this->mEnableLimitReport; } - - function getSkin() { - if ( !isset( $this->mSkin ) ) { - $this->mSkin = $this->mUser->getSkin(); - } - return $this->mSkin; - } - - function getDateFormat() { - if ( !isset( $this->mDateFormat ) ) { - $this->mDateFormat = $this->mUser->getDatePreference(); - } - return $this->mDateFormat; - } - - function getTimestamp() { - if ( !isset( $this->mTimestamp ) ) { - $this->mTimestamp = wfTimestampNow(); - } - return $this->mTimestamp; - } - - function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); } - function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); } - function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); } - function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); } - function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); } - function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); } - function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); } - function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); } - function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); } - function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); } - function setSkin( $x ) { $this->mSkin = $x; } - function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); } - function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); } - function setMaxPPNodeCount( $x ) { return wfSetVar( $this->mMaxPPNodeCount, $x ); } - function setMaxTemplateDepth( $x ) { return wfSetVar( $this->mMaxTemplateDepth, $x ); } - function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); } - function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); } - function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); } - function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); } - - function __construct( $user = null ) { - $this->initialiseFromUser( $user ); - } - - /** - * Get parser options - * @static - */ - static function newFromUser( $user ) { - return new ParserOptions( $user ); - } - - /** Get user options */ - function initialiseFromUser( $userInput ) { - global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages; - global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize; - global $wgMaxPPNodeCount, $wgMaxTemplateDepth; - $fname = 'ParserOptions::initialiseFromUser'; - wfProfileIn( $fname ); - if ( !$userInput ) { - global $wgUser; - if ( isset( $wgUser ) ) { - $user = $wgUser; - } else { - $user = new User; - } - } else { - $user =& $userInput; - } - - $this->mUser = $user; - - $this->mUseTeX = $wgUseTeX; - $this->mUseDynamicDates = $wgUseDynamicDates; - $this->mInterwikiMagic = $wgInterwikiMagic; - $this->mAllowExternalImages = $wgAllowExternalImages; - $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom; - $this->mSkin = null; # Deferred - $this->mDateFormat = null; # Deferred - $this->mEditSection = true; - $this->mNumberHeadings = $user->getOption( 'numberheadings' ); - $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion; - $this->mTidy = false; - $this->mInterfaceMessage = false; - $this->mMaxIncludeSize = $wgMaxArticleSize * 1024; - $this->mMaxPPNodeCount = $wgMaxPPNodeCount; - $this->mMaxTemplateDepth = $wgMaxTemplateDepth; - $this->mRemoveComments = true; - $this->mTemplateCallback = array( 'Parser', 'statelessFetchTemplate' ); - $this->mEnableLimitReport = false; - wfProfileOut( $fname ); - } -} - - diff --git a/includes/ParserOutput.php b/includes/ParserOutput.php deleted file mode 100644 index 9b3c12c1..00000000 --- a/includes/ParserOutput.php +++ /dev/null @@ -1,189 +0,0 @@ -<?php -/** - * @todo document - * @addtogroup Parser - */ -class ParserOutput -{ - var $mText, # The output text - $mLanguageLinks, # List of the full text of language links, in the order they appear - $mCategories, # Map of category names to sort keys - $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}} - $mCacheTime, # Time when this object was generated, or -1 for uncacheable. Used in ParserCache. - $mVersion, # Compatibility check - $mTitleText, # title text of the chosen language variant - $mLinks, # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken. - $mTemplates, # 2-D map of NS/DBK to ID for the template references. ID=zero for broken. - $mTemplateIds, # 2-D map of NS/DBK to rev ID for the template references. ID=zero for broken. - $mImages, # DB keys of the images used, in the array key only - $mExternalLinks, # External link URLs, in the key only - $mNewSection, # Show a new section link? - $mNoGallery, # No gallery on category page? (__NOGALLERY__) - $mHeadItems, # Items to put in the <head> section - $mOutputHooks, # Hook tags as per $wgParserOutputHooks - $mWarnings, # Warning text to be returned to the user. Wikitext formatted. - $mSections; # Table of contents - - /** - * Overridden title for display - */ - private $displayTitle = false; - - function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(), - $containsOldMagic = false, $titletext = '' ) - { - $this->mText = $text; - $this->mLanguageLinks = $languageLinks; - $this->mCategories = $categoryLinks; - $this->mContainsOldMagic = $containsOldMagic; - $this->mCacheTime = ''; - $this->mVersion = Parser::VERSION; - $this->mTitleText = $titletext; - $this->mSections = array(); - $this->mLinks = array(); - $this->mTemplates = array(); - $this->mImages = array(); - $this->mExternalLinks = array(); - $this->mNewSection = false; - $this->mNoGallery = false; - $this->mHeadItems = array(); - $this->mTemplateIds = array(); - $this->mOutputHooks = array(); - $this->mWarnings = array(); - } - - function getText() { return $this->mText; } - function &getLanguageLinks() { return $this->mLanguageLinks; } - function getCategoryLinks() { return array_keys( $this->mCategories ); } - function &getCategories() { return $this->mCategories; } - function getCacheTime() { return $this->mCacheTime; } - function getTitleText() { return $this->mTitleText; } - function getSections() { return $this->mSections; } - function &getLinks() { return $this->mLinks; } - function &getTemplates() { return $this->mTemplates; } - function &getImages() { return $this->mImages; } - function &getExternalLinks() { return $this->mExternalLinks; } - function getNoGallery() { return $this->mNoGallery; } - function getSubtitle() { return $this->mSubtitle; } - function getOutputHooks() { return (array)$this->mOutputHooks; } - function getWarnings() { return isset( $this->mWarnings ) ? $this->mWarnings : array(); } - - function containsOldMagic() { return $this->mContainsOldMagic; } - function setText( $text ) { return wfSetVar( $this->mText, $text ); } - function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); } - function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); } - function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); } - function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } - function setTitleText( $t ) { return wfSetVar( $this->mTitleText, $t ); } - function setSections( $toc ) { return wfSetVar( $this->mSections, $toc ); } - - function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; } - function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; } - function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; } - function addWarning( $s ) { $this->mWarnings[] = $s; } - - function addOutputHook( $hook, $data = false ) { - $this->mOutputHooks[] = array( $hook, $data ); - } - - function setNewSection( $value ) { - $this->mNewSection = (bool)$value; - } - function getNewSection() { - return (bool)$this->mNewSection; - } - - function addLink( $title, $id = null ) { - $ns = $title->getNamespace(); - $dbk = $title->getDBkey(); - if ( !isset( $this->mLinks[$ns] ) ) { - $this->mLinks[$ns] = array(); - } - if ( is_null( $id ) ) { - $id = $title->getArticleID(); - } - $this->mLinks[$ns][$dbk] = $id; - } - - function addImage( $name ) { - $this->mImages[$name] = 1; - } - - function addTemplate( $title, $page_id, $rev_id ) { - $ns = $title->getNamespace(); - $dbk = $title->getDBkey(); - if ( !isset( $this->mTemplates[$ns] ) ) { - $this->mTemplates[$ns] = array(); - } - $this->mTemplates[$ns][$dbk] = $page_id; - if ( !isset( $this->mTemplateIds[$ns] ) ) { - $this->mTemplateIds[$ns] = array(); - } - $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning - } - - /** - * Return true if this cached output object predates the global or - * per-article cache invalidation timestamps, or if it comes from - * an incompatible older version. - * - * @param string $touched the affected article's last touched timestamp - * @return bool - * @public - */ - function expired( $touched ) { - global $wgCacheEpoch; - return $this->getCacheTime() == -1 || // parser says it's uncacheable - $this->getCacheTime() < $touched || - $this->getCacheTime() <= $wgCacheEpoch || - !isset( $this->mVersion ) || - version_compare( $this->mVersion, Parser::VERSION, "lt" ); - } - - /** - * Add some text to the <head>. - * If $tag is set, the section with that tag will only be included once - * in a given page. - */ - function addHeadItem( $section, $tag = false ) { - if ( $tag !== false ) { - $this->mHeadItems[$tag] = $section; - } else { - $this->mHeadItems[] = $section; - } - } - - /** - * Override the title to be used for display - * -- this is assumed to have been validated - * (check equal normalisation, etc.) - * - * @param string $text Desired title text - */ - public function setDisplayTitle( $text ) { - $this->displayTitle = $text; - } - - /** - * Get the title to be used for display - * - * @return string - */ - public function getDisplayTitle() { - return $this->displayTitle; - } - - /** - * Fairly generic flag setter thingy. - */ - public function setFlag( $flag ) { - $this->mFlags[$flag] = true; - } - - public function getFlag( $flag ) { - return isset( $this->mFlags[$flag] ); - } - -} - - diff --git a/includes/ParserXML.php b/includes/ParserXML.php deleted file mode 100644 index e7b64f6e..00000000 --- a/includes/ParserXML.php +++ /dev/null @@ -1,643 +0,0 @@ -<?php -/** - * - * @package MediaWiki - * @subpackage Experimental - */ - -/** */ -require_once ('Parser.php'); - -/** - * This should one day become the XML->(X)HTML parser - * Based on work by Jan Hidders and Magnus Manske - * To use, set - * $wgUseXMLparser = true ; - * $wgEnableParserCache = false ; - * $wgWiki2xml to the path and executable of the command line version (cli) - * in LocalSettings.php - * @package MediaWiki - * @subpackage Experimental - */ - -/** - * the base class for an element - * @package MediaWiki - * @subpackage Experimental - */ -class element { - var $name = ''; - var $attrs = array (); - var $children = array (); - - /** - * This finds the ATTRS element and returns the ATTR sub-children as a single string - * @todo FIXME $parser always empty when calling makeXHTML() - */ - function getSourceAttrs() { - $ret = ''; - foreach ($this->children as $child) { - if (!is_string($child) AND $child->name == 'ATTRS') { - $ret = $child->makeXHTML($parser); - } - } - return $ret; - } - - /** - * This collects the ATTR thingies for getSourceAttrs() - */ - function getTheseAttrs() { - $ret = array (); - foreach ($this->children as $child) { - if (!is_string($child) AND $child->name == 'ATTR') { - $ret[] = $child->attrs["NAME"]."='".$child->children[0]."'"; - } - } - return implode(' ', $ret); - } - - function fixLinkTails(& $parser, $key) { - $k2 = $key +1; - if (!isset ($this->children[$k2])) - return; - if (!is_string($this->children[$k2])) - return; - if (is_string($this->children[$key])) - return; - if ($this->children[$key]->name != "LINK") - return; - - $n = $this->children[$k2]; - $s = ''; - while ($n != '' AND (($n[0] >= 'a' AND $n[0] <= 'z') OR $n[0] == 'ä' OR $n[0] == 'ö' OR $n[0] == 'ü' OR $n[0] == 'ß')) { - $s .= $n[0]; - $n = substr($n, 1); - } - $this->children[$k2] = $n; - - if (count($this->children[$key]->children) > 1) { - $kl = array_keys($this->children[$key]->children); - $kl = array_pop($kl); - $this->children[$key]->children[$kl]->children[] = $s; - } else { - $e = new element; - $e->name = "LINKOPTION"; - $t = $this->children[$key]->sub_makeXHTML($parser); - $e->children[] = trim($t).$s; - $this->children[$key]->children[] = $e; - } - } - - /** - * This function generates the XHTML for the entire subtree - */ - function sub_makeXHTML(& $parser, $tag = '', $attr = '') { - $ret = ''; - - $attr2 = $this->getSourceAttrs(); - if ($attr != '' AND $attr2 != '') - $attr .= ' '; - $attr .= $attr2; - - if ($tag != '') { - $ret .= '<'.$tag; - if ($attr != '') - $ret .= ' '.$attr; - $ret .= '>'; - } - - # THIS SHOULD BE DONE IN THE WIKI2XML-PARSER INSTEAD - # foreach ( array_keys ( $this->children ) AS $x ) - # $this->fixLinkTails ( $parser , $x ) ; - - foreach ($this->children as $child) { - if (is_string($child)) { - $ret .= $child; - } elseif ($child->name != 'ATTRS') { - $ret .= $child->makeXHTML($parser); - } - } - if ($tag != '') - $ret .= '</'.$tag.">\n"; - return $ret; - } - - /** - * Link functions - */ - function createInternalLink(& $parser, $target, $display_title, $options) { - global $wgUser; - $skin = $wgUser->getSkin(); - $tp = explode(':', $target); # tp = target parts - $title = ''; # The plain title - $language = ''; # The language/meta/etc. part - $namespace = ''; # The namespace, if any - $subtarget = ''; # The '#' thingy - - $nt = Title :: newFromText($target); - $fl = strtoupper($this->attrs['FORCEDLINK']) == 'YES'; - - if ($fl || count($tp) == 1) { - # Plain and simple case - $title = $target; - } else { - # There's stuff missing here... - if ($nt->getNamespace() == NS_IMAGE) { - $options[] = $display_title; - return $parser->makeImage($nt, implode('|', $options)); - } else { - # Default - $title = $target; - } - } - - if ($language != '') { - # External link within the WikiMedia project - return "{language link}"; - } else { - if ($namespace != '') { - # Link to another namespace, check for image/media stuff - return "{namespace link}"; - } else { - return $skin->makeLink($target, $display_title); - } - } - } - - /** @todo document */ - function makeInternalLink(& $parser) { - $target = ''; - $option = array (); - foreach ($this->children as $child) { - if (is_string($child)) { - # This shouldn't be the case! - } else { - if ($child->name == 'LINKTARGET') { - $target = trim($child->makeXHTML($parser)); - } else { - $option[] = trim($child->makeXHTML($parser)); - } - } - } - - if (count($option) == 0) - $option[] = $target; # Create dummy display title - $display_title = array_pop($option); - return $this->createInternalLink($parser, $target, $display_title, $option); - } - - /** @todo document */ - function getTemplateXHTML($title, $parts, & $parser) { - global $wgLang, $wgUser; - $skin = $wgUser->getSkin(); - $ot = $title; # Original title - if (count(explode(':', $title)) == 1) - $title = $wgLang->getNsText(NS_TEMPLATE).":".$title; - $nt = Title :: newFromText($title); - $id = $nt->getArticleID(); - if ($id == 0) { - # No/non-existing page - return $skin->makeBrokenLink($title, $ot); - } - - $a = 0; - $tv = array (); # Template variables - foreach ($parts AS $part) { - $a ++; - $x = explode('=', $part, 2); - if (count($x) == 1) - $key = "{$a}"; - else - $key = $x[0]; - $value = array_pop($x); - $tv[$key] = $value; - } - $art = new Article($nt); - $text = $art->getContent(false); - $parser->plain_parse($text, true, $tv); - - return $text; - } - - /** - * This function actually converts wikiXML into XHTML tags - * @todo use switch() ! - */ - function makeXHTML(& $parser) { - $ret = ''; - $n = $this->name; # Shortcut - - if ($n == 'EXTENSION') { - # Fix allowed HTML - $old_n = $n; - $ext = strtoupper($this->attrs['NAME']); - - switch($ext) { - case 'B': - case 'STRONG': - $n = 'BOLD'; - break; - case 'I': - case 'EM': - $n = 'ITALICS'; - break; - case 'U': - $n = 'UNDERLINED'; # Hey, virtual wiki tag! ;-) - break; - case 'S': - $n = 'STRIKE'; - break; - case 'P': - $n = 'PARAGRAPH'; - break; - case 'TABLE': - $n = 'TABLE'; - break; - case 'TR': - $n = 'TABLEROW'; - break; - case 'TD': - $n = 'TABLECELL'; - break; - case 'TH': - $n = 'TABLEHEAD'; - break; - case 'CAPTION': - $n = 'CAPTION'; - break; - case 'NOWIKI': - $n = 'NOWIKI'; - break; - } - if ($n != $old_n) { - unset ($this->attrs['NAME']); # Cleanup - } elseif ($parser->nowiki > 0) { - # No 'real' wiki tags allowed in nowiki section - $n = ''; - } - } // $n = 'EXTENSION' - - switch($n) { - case 'ARTICLE': - $ret .= $this->sub_makeXHTML($parser); - break; - case 'HEADING': - $ret .= $this->sub_makeXHTML($parser, 'h'.$this->attrs['LEVEL']); - break; - case 'PARAGRAPH': - $ret .= $this->sub_makeXHTML($parser, 'p'); - break; - case 'BOLD': - $ret .= $this->sub_makeXHTML($parser, 'strong'); - break; - case 'ITALICS': - $ret .= $this->sub_makeXHTML($parser, 'em'); - break; - - # These don't exist as wiki markup - case 'UNDERLINED': - $ret .= $this->sub_makeXHTML($parser, 'u'); - break; - case 'STRIKE': - $ret .= $this->sub_makeXHTML($parser, 'strike'); - break; - - # HTML comment - case 'COMMENT': - # Comments are parsed out - $ret .= ''; - break; - - - # Links - case 'LINK': - $ret .= $this->makeInternalLink($parser); - break; - case 'LINKTARGET': - case 'LINKOPTION': - $ret .= $this->sub_makeXHTML($parser); - break; - - case 'TEMPLATE': - $parts = $this->sub_makeXHTML($parser); - $parts = explode('|', $parts); - $title = array_shift($parts); - $ret .= $this->getTemplateXHTML($title, $parts, & $parser); - break; - - case 'TEMPLATEVAR': - $x = $this->sub_makeXHTML($parser); - if (isset ($parser->mCurrentTemplateOptions["{$x}"])) - $ret .= $parser->mCurrentTemplateOptions["{$x}"]; - break; - - # Internal use, not generated by wiki2xml parser - case 'IGNORE': - $ret .= $this->sub_makeXHTML($parser); - - case 'NOWIKI': - $parser->nowiki++; - $ret .= $this->sub_makeXHTML($parser, ''); - $parser->nowiki--; - - - # Unknown HTML extension - case 'EXTENSION': # This is currently a dummy!!! - $ext = $this->attrs['NAME']; - - $ret .= '<'.$ext.'>'; - $ret .= $this->sub_makeXHTML($parser); - $ret .= '</'.$ext.'> '; - break; - - - # Table stuff - - case 'TABLE': - $ret .= $this->sub_makeXHTML($parser, 'table'); - break; - case 'TABLEROW': - $ret .= $this->sub_makeXHTML($parser, 'tr'); - break; - case 'TABLECELL': - $ret .= $this->sub_makeXHTML($parser, 'td'); - break; - case 'TABLEHEAD': - $ret .= $this->sub_makeXHTML($parser, 'th'); - break; - case 'CAPTION': - $ret .= $this->sub_makeXHTML($parser, 'caption'); - break; - case 'ATTRS': # SPECIAL CASE : returning attributes - return $this->getTheseAttrs(); - - - # Lists stuff - case 'LISTITEM': - if ($parser->mListType == 'dl') - $ret .= $this->sub_makeXHTML($parser, 'dd'); - else - $ret .= $this->sub_makeXHTML($parser, 'li'); - break; - case 'LIST': - $type = 'ol'; # Default - if ($this->attrs['TYPE'] == 'bullet') - $type = 'ul'; - else - if ($this->attrs['TYPE'] == 'indent') - $type = 'dl'; - $oldtype = $parser->mListType; - $parser->mListType = $type; - $ret .= $this->sub_makeXHTML($parser, $type); - $parser->mListType = $oldtype; - break; - - # Something else entirely - default: - $ret .= '<'.$n.'>'; - $ret .= $this->sub_makeXHTML($parser); - $ret .= '</'.$n.'> '; - } // switch($n) - - $ret = "\n{$ret}\n"; - $ret = str_replace("\n\n", "\n", $ret); - return $ret; - } - - /** - * A function for additional debugging output - */ - function myPrint() { - $ret = "<ul>\n"; - $ret .= "<li> <b> Name: </b> $this->name </li>\n"; - // print attributes - $ret .= '<li> <b> Attributes: </b>'; - foreach ($this->attrs as $name => $value) { - $ret .= "$name => $value; "; - } - $ret .= " </li>\n"; - // print children - foreach ($this->children as $child) { - if (is_string($child)) { - $ret .= "<li> $child </li>\n"; - } else { - $ret .= $child->myPrint(); - } - } - $ret .= "</ul>\n"; - return $ret; - } -} - -$ancStack = array (); // the stack with ancestral elements - -// START Three global functions needed for parsing, sorry guys -/** @todo document */ -function wgXMLstartElement($parser, $name, $attrs) { - global $ancStack; - - $newElem = new element; - $newElem->name = $name; - $newElem->attrs = $attrs; - - array_push($ancStack, $newElem); -} - -/** @todo document */ -function wgXMLendElement($parser, $name) { - global $ancStack, $rootElem; - // pop element off stack - $elem = array_pop($ancStack); - if (count($ancStack) == 0) - $rootElem = $elem; - else - // add it to its parent - array_push($ancStack[count($ancStack) - 1]->children, $elem); -} - -/** @todo document */ -function wgXMLcharacterData($parser, $data) { - global $ancStack; - $data = trim($data); // Don't add blank lines, they're no use... - // add to parent if parent exists - if ($ancStack && $data != "") { - array_push($ancStack[count($ancStack) - 1]->children, $data); - } -} -// END Three global functions needed for parsing, sorry guys - -/** - * Here's the class that generates a nice tree - * @package MediaWiki - * @subpackage Experimental - */ -class xml2php { - - /** @todo document */ - function & scanFile($filename) { - global $ancStack, $rootElem; - $ancStack = array (); - - $xml_parser = xml_parser_create(); - xml_set_element_handler($xml_parser, 'wgXMLstartElement', 'wgXMLendElement'); - xml_set_character_data_handler($xml_parser, 'wgXMLcharacterData'); - if (!($fp = fopen($filename, 'r'))) { - die('could not open XML input'); - } - while ($data = fread($fp, 4096)) { - if (!xml_parse($xml_parser, $data, feof($fp))) { - die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); - } - } - xml_parser_free($xml_parser); - - // return the remaining root element we copied in the beginning - return $rootElem; - } - - /** @todo document */ - function scanString($input) { - global $ancStack, $rootElem; - $ancStack = array (); - - $xml_parser = xml_parser_create(); - xml_set_element_handler($xml_parser, 'wgXMLstartElement', 'wgXMLendElement'); - xml_set_character_data_handler($xml_parser, 'wgXMLcharacterData'); - - if (!xml_parse($xml_parser, $input, true)) { - die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); - } - xml_parser_free($xml_parser); - - // return the remaining root element we copied in the beginning - return $rootElem; - } - -} - -/** - * @todo document - * @package MediaWiki - * @subpackage Experimental - */ -class ParserXML extends Parser { - /**#@+ - * @private - */ - # Persistent: - var $mTagHooks, $mListType; - - # Cleared with clearState(): - var $mOutput, $mAutonumber, $mDTopen, $mStripState = array (); - var $mVariables, $mIncludeCount, $mArgStack, $mLastSection, $mInPre; - - # Temporary: - var $mOptions, $mTitle, $mOutputType, $mTemplates, // cache of already loaded templates, avoids - // multiple SQL queries for the same string - $mTemplatePath; // stores an unsorted hash of all the templates already loaded - // in this path. Used for loop detection. - - var $nowikicount, $mCurrentTemplateOptions; - - /**#@-*/ - - /** - * Constructor - * - * @public - */ - function ParserXML() { - $this->mTemplates = array (); - $this->mTemplatePath = array (); - $this->mTagHooks = array (); - $this->clearState(); - } - - /** - * Clear Parser state - * - * @private - */ - function clearState() { - $this->mOutput = new ParserOutput; - $this->mAutonumber = 0; - $this->mLastSection = ""; - $this->mDTopen = false; - $this->mVariables = false; - $this->mIncludeCount = array (); - $this->mStripState = array (); - $this->mArgStack = array (); - $this->mInPre = false; - } - - /** - * Turns the wikitext into XML by calling the external parser - * - */ - function html2xml(& $text) { - global $wgWiki2xml; - - # generating html2xml command path - $a = $wgWiki2xml; - $a = explode('/', $a); - array_pop($a); - $a[] = 'html2xml'; - $html2xml = implode('/', $a); - $a = array (); - - $tmpfname = tempnam( wfTempDir(), 'FOO' ); - $handle = fopen($tmpfname, 'w'); - fwrite($handle, utf8_encode($text)); - fclose($handle); - exec($html2xml.' < '.$tmpfname, $a); - $text = utf8_decode(implode("\n", $a)); - unlink($tmpfname); - } - - /** @todo document */ - function runXMLparser(& $text) { - global $wgWiki2xml; - - $this->html2xml($text); - - $tmpfname = tempnam( wfTempDir(), 'FOO'); - $handle = fopen($tmpfname, 'w'); - fwrite($handle, $text); - fclose($handle); - exec($wgWiki2xml.' < '.$tmpfname, $a); - $text = utf8_decode(implode("\n", $a)); - unlink($tmpfname); - } - - /** @todo document */ - function plain_parse(& $text, $inline = false, $templateOptions = array ()) { - $this->runXMLparser($text); - $nowikicount = 0; - $w = new xml2php; - $result = $w->scanString($text); - - $oldTemplateOptions = $this->mCurrentTemplateOptions; - $this->mCurrentTemplateOptions = $templateOptions; - - if ($inline) { # Inline rendering off for templates - if (count($result->children) == 1) - $result->children[0]->name = 'IGNORE'; - } - - if (1) - $text = $result->makeXHTML($this); # No debugging info - else - $text = $result->makeXHTML($this).'<hr>'.$text.'<hr>'.$result->myPrint(); - $this->mCurrentTemplateOptions = $oldTemplateOptions; - } - - /** @todo document */ - function parse($text, & $title, $options, $linestart = true, $clearState = true) { - $this->plain_parse($text); - $this->mOutput->setText($text); - return $this->mOutput; - } - -} -?> diff --git a/includes/Parser_DiffTest.php b/includes/Parser_DiffTest.php deleted file mode 100644 index d88709f0..00000000 --- a/includes/Parser_DiffTest.php +++ /dev/null @@ -1,85 +0,0 @@ -<?php - -class Parser_DiffTest -{ - var $parsers, $conf; - - var $dfUniqPrefix; - - function __construct( $conf ) { - if ( !isset( $conf['parsers'] ) ) { - throw new MWException( __METHOD__ . ': no parsers specified' ); - } - $this->conf = $conf; - $this->dtUniqPrefix = "\x7fUNIQ" . Parser::getRandomString(); - } - - function init() { - if ( !is_null( $this->parsers ) ) { - return; - } - - global $wgHooks; - static $doneHook = false; - if ( !$doneHook ) { - $doneHook = true; - $wgHooks['ParserClearState'][] = array( $this, 'onClearState' ); - } - - foreach ( $this->conf['parsers'] as $i => $parserConf ) { - if ( !is_array( $parserConf ) ) { - $class = $parserConf; - $parserConf = array( 'class' => $parserConf ); - } else { - $class = $parserConf['class']; - } - $this->parsers[$i] = new $class( $parserConf ); - } - } - - function __call( $name, $args ) { - $this->init(); - $results = array(); - $mismatch = false; - $lastResult = null; - $first = true; - foreach ( $this->parsers as $i => $parser ) { - $currentResult = call_user_func_array( array( &$this->parsers[$i], $name ), $args ); - if ( $first ) { - $first = false; - } else { - if ( is_object( $lastResult ) ) { - if ( $lastResult != $currentResult ) { - $mismatch = true; - } - } else { - if ( $lastResult !== $currentResult ) { - $mismatch = true; - } - } - } - $results[$i] = $currentResult; - $lastResult = $currentResult; - } - if ( $mismatch ) { - throw new MWException( "Parser_DiffTest: results mismatch on call to $name\n" . - 'Arguments: ' . var_export( $args, true ) . "\n" . - 'Results: ' . var_export( $results, true ) . "\n" ); - } - return $lastResult; - } - - function setFunctionHook( $id, $callback, $flags = 0 ) { - $this->init(); - foreach ( $this->parsers as $i => $parser ) { - $parser->setFunctionHook( $id, $callback, $flags ); - } - } - - function onClearState( &$parser ) { - // hack marker prefixes to get identical output - $parser->mUniqPrefix = $this->dtUniqPrefix; - return true; - } -} - diff --git a/includes/Parser_OldPP.php b/includes/Parser_OldPP.php deleted file mode 100644 index c10de257..00000000 --- a/includes/Parser_OldPP.php +++ /dev/null @@ -1,4942 +0,0 @@ -<?php -/** - * Parser with old preprocessor - */ -class Parser_OldPP -{ - /** - * Update this version number when the ParserOutput format - * changes in an incompatible way, so the parser cache - * can automatically discard old data. - */ - const VERSION = '1.6.4'; - - # Flags for Parser::setFunctionHook - # Also available as global constants from Defines.php - const SFH_NO_HASH = 1; - - # Constants needed for external link processing - # Everything except bracket, space, or control characters - const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F]'; - const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+)\\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/S'; - - // State constants for the definition list colon extraction - const COLON_STATE_TEXT = 0; - const COLON_STATE_TAG = 1; - const COLON_STATE_TAGSTART = 2; - const COLON_STATE_CLOSETAG = 3; - const COLON_STATE_TAGSLASH = 4; - const COLON_STATE_COMMENT = 5; - const COLON_STATE_COMMENTDASH = 6; - const COLON_STATE_COMMENTDASHDASH = 7; - - // Allowed values for $this->mOutputType - // Parameter to startExternalParse(). - const OT_HTML = 1; - const OT_WIKI = 2; - const OT_PREPROCESS = 3; - const OT_MSG = 4; - - /**#@+ - * @private - */ - # Persistent: - var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables, - $mImageParams, $mImageParamsMagicArray, $mExtLinkBracketedRegex; - - # Cleared with clearState(): - var $mOutput, $mAutonumber, $mDTopen, $mStripState; - var $mIncludeCount, $mArgStack, $mLastSection, $mInPre; - var $mInterwikiLinkHolders, $mLinkHolders, $mUniqPrefix; - var $mIncludeSizes, $mDefaultSort; - var $mTemplates, // cache of already loaded templates, avoids - // multiple SQL queries for the same string - $mTemplatePath; // stores an unsorted hash of all the templates already loaded - // in this path. Used for loop detection. - - # Temporary - # These are variables reset at least once per parse regardless of $clearState - var $mOptions, // ParserOptions object - $mTitle, // Title context, used for self-link rendering and similar things - $mOutputType, // Output type, one of the OT_xxx constants - $ot, // Shortcut alias, see setOutputType() - $mRevisionId, // ID to display in {{REVISIONID}} tags - $mRevisionTimestamp, // The timestamp of the specified revision ID - $mRevIdForTs; // The revision ID which was used to fetch the timestamp - - /**#@-*/ - - /** - * Constructor - * - * @public - */ - function __construct( $conf = array() ) { - $this->mTagHooks = array(); - $this->mTransparentTagHooks = array(); - $this->mFunctionHooks = array(); - $this->mFunctionSynonyms = array( 0 => array(), 1 => array() ); - $this->mFirstCall = true; - $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'. - '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x0a\\x0d]*?)\]/S'; - } - - /** - * Do various kinds of initialisation on the first call of the parser - */ - function firstCallInit() { - if ( !$this->mFirstCall ) { - return; - } - $this->mFirstCall = false; - - wfProfileIn( __METHOD__ ); - global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions; - - $this->setHook( 'pre', array( $this, 'renderPreTag' ) ); - - # Syntax for arguments (see self::setFunctionHook): - # "name for lookup in localized magic words array", - # function callback, - # optional SFH_NO_HASH to omit the hash from calls (e.g. {{int:...} - # instead of {{#int:...}}) - $this->setFunctionHook( 'int', array( 'CoreParserFunctions', 'intFunction' ), SFH_NO_HASH ); - $this->setFunctionHook( 'ns', array( 'CoreParserFunctions', 'ns' ), SFH_NO_HASH ); - $this->setFunctionHook( 'urlencode', array( 'CoreParserFunctions', 'urlencode' ), SFH_NO_HASH ); - $this->setFunctionHook( 'lcfirst', array( 'CoreParserFunctions', 'lcfirst' ), SFH_NO_HASH ); - $this->setFunctionHook( 'ucfirst', array( 'CoreParserFunctions', 'ucfirst' ), SFH_NO_HASH ); - $this->setFunctionHook( 'lc', array( 'CoreParserFunctions', 'lc' ), SFH_NO_HASH ); - $this->setFunctionHook( 'uc', array( 'CoreParserFunctions', 'uc' ), SFH_NO_HASH ); - $this->setFunctionHook( 'localurl', array( 'CoreParserFunctions', 'localurl' ), SFH_NO_HASH ); - $this->setFunctionHook( 'localurle', array( 'CoreParserFunctions', 'localurle' ), SFH_NO_HASH ); - $this->setFunctionHook( 'fullurl', array( 'CoreParserFunctions', 'fullurl' ), SFH_NO_HASH ); - $this->setFunctionHook( 'fullurle', array( 'CoreParserFunctions', 'fullurle' ), SFH_NO_HASH ); - $this->setFunctionHook( 'formatnum', array( 'CoreParserFunctions', 'formatnum' ), SFH_NO_HASH ); - $this->setFunctionHook( 'grammar', array( 'CoreParserFunctions', 'grammar' ), SFH_NO_HASH ); - $this->setFunctionHook( 'plural', array( 'CoreParserFunctions', 'plural' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofpages', array( 'CoreParserFunctions', 'numberofpages' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofusers', array( 'CoreParserFunctions', 'numberofusers' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH ); - $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH ); - $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH ); - $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH ); - $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH ); - $this->setFunctionHook( 'anchorencode', array( 'CoreParserFunctions', 'anchorencode' ), SFH_NO_HASH ); - $this->setFunctionHook( 'special', array( 'CoreParserFunctions', 'special' ) ); - $this->setFunctionHook( 'defaultsort', array( 'CoreParserFunctions', 'defaultsort' ), SFH_NO_HASH ); - $this->setFunctionHook( 'filepath', array( 'CoreParserFunctions', 'filepath' ), SFH_NO_HASH ); - - if ( $wgAllowDisplayTitle ) { - $this->setFunctionHook( 'displaytitle', array( 'CoreParserFunctions', 'displaytitle' ), SFH_NO_HASH ); - } - if ( $wgAllowSlowParserFunctions ) { - $this->setFunctionHook( 'pagesinnamespace', array( 'CoreParserFunctions', 'pagesinnamespace' ), SFH_NO_HASH ); - } - - $this->initialiseVariables(); - - wfRunHooks( 'ParserFirstCallInit', array( &$this ) ); - wfProfileOut( __METHOD__ ); - } - - /** - * Clear Parser state - * - * @private - */ - function clearState() { - wfProfileIn( __METHOD__ ); - if ( $this->mFirstCall ) { - $this->firstCallInit(); - } - $this->mOutput = new ParserOutput; - $this->mAutonumber = 0; - $this->mLastSection = ''; - $this->mDTopen = false; - $this->mIncludeCount = array(); - $this->mStripState = new StripState; - $this->mArgStack = array(); - $this->mInPre = false; - $this->mInterwikiLinkHolders = array( - 'texts' => array(), - 'titles' => array() - ); - $this->mLinkHolders = array( - 'namespaces' => array(), - 'dbkeys' => array(), - 'queries' => array(), - 'texts' => array(), - 'titles' => array() - ); - $this->mRevisionTimestamp = $this->mRevisionId = null; - - /** - * Prefix for temporary replacement strings for the multipass parser. - * \x07 should never appear in input as it's disallowed in XML. - * Using it at the front also gives us a little extra robustness - * since it shouldn't match when butted up against identifier-like - * string constructs. - */ - $this->mUniqPrefix = "\x07UNIQ" . self::getRandomString(); - - # Clear these on every parse, bug 4549 - $this->mTemplates = array(); - $this->mTemplatePath = array(); - - $this->mShowToc = true; - $this->mForceTocPosition = false; - $this->mIncludeSizes = array( - 'pre-expand' => 0, - 'post-expand' => 0, - 'arg' => 0 - ); - $this->mDefaultSort = false; - - wfRunHooks( 'ParserClearState', array( &$this ) ); - wfProfileOut( __METHOD__ ); - } - - function setOutputType( $ot ) { - $this->mOutputType = $ot; - // Shortcut alias - $this->ot = array( - 'html' => $ot == self::OT_HTML, - 'wiki' => $ot == self::OT_WIKI, - 'msg' => $ot == self::OT_MSG, - 'pre' => $ot == self::OT_PREPROCESS, - ); - } - - /** - * Accessor for mUniqPrefix. - * - * @public - */ - function uniqPrefix() { - return $this->mUniqPrefix; - } - - /** - * Convert wikitext to HTML - * Do not call this function recursively. - * - * @param string $text Text we want to parse - * @param Title &$title A title object - * @param array $options - * @param boolean $linestart - * @param boolean $clearState - * @param int $revid number to pass in {{REVISIONID}} - * @return ParserOutput a ParserOutput - */ - public function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) { - /** - * First pass--just handle <nowiki> sections, pass the rest off - * to internalParse() which does all the real work. - */ - - global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang; - $fname = 'Parser::parse-' . wfGetCaller(); - wfProfileIn( __METHOD__ ); - wfProfileIn( $fname ); - - if ( $clearState ) { - $this->clearState(); - } - - $this->mOptions = $options; - $this->mTitle =& $title; - $oldRevisionId = $this->mRevisionId; - $oldRevisionTimestamp = $this->mRevisionTimestamp; - if( $revid !== null ) { - $this->mRevisionId = $revid; - $this->mRevisionTimestamp = null; - } - $this->setOutputType( self::OT_HTML ); - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->strip( $text, $this->mStripState ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->internalParse( $text ); - $text = $this->mStripState->unstripGeneral( $text ); - - # Clean up special characters, only run once, next-to-last before doBlockLevels - $fixtags = array( - # french spaces, last one Guillemet-left - # only if there is something before the space - '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1 \\2', - # french spaces, Guillemet-right - '/(\\302\\253) /' => '\\1 ', - ); - $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text ); - - # only once and last - $text = $this->doBlockLevels( $text, $linestart ); - - $this->replaceLinkHolders( $text ); - - # the position of the parserConvert() call should not be changed. it - # assumes that the links are all replaced and the only thing left - # is the <nowiki> mark. - # Side-effects: this calls $this->mOutput->setTitleText() - $text = $wgContLang->parserConvert( $text, $this ); - - $text = $this->mStripState->unstripNoWiki( $text ); - - wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) ); - -//!JF Move to its own function - - $uniq_prefix = $this->mUniqPrefix; - $matches = array(); - $elements = array_keys( $this->mTransparentTagHooks ); - $text = self::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix ); - - foreach( $matches as $marker => $data ) { - list( $element, $content, $params, $tag ) = $data; - $tagName = strtolower( $element ); - if( isset( $this->mTransparentTagHooks[$tagName] ) ) { - $output = call_user_func_array( $this->mTransparentTagHooks[$tagName], - array( $content, $params, $this ) ); - } else { - $output = $tag; - } - $this->mStripState->general->setPair( $marker, $output ); - } - $text = $this->mStripState->unstripGeneral( $text ); - - $text = Sanitizer::normalizeCharReferences( $text ); - - if (($wgUseTidy and $this->mOptions->mTidy) or $wgAlwaysUseTidy) { - $text = self::tidy($text); - } else { - # attempt to sanitize at least some nesting problems - # (bug #2702 and quite a few others) - $tidyregs = array( - # ''Something [http://www.cool.com cool''] --> - # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a> - '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' => - '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9', - # fix up an anchor inside another anchor, only - # at least for a single single nested link (bug 3695) - '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' => - '\\1\\2</a>\\3</a>\\1\\4</a>', - # fix div inside inline elements- doBlockLevels won't wrap a line which - # contains a div, so fix it up here; replace - # div with escaped text - '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' => - '\\1\\3<div\\5>\\6</div>\\8\\9', - # remove empty italic or bold tag pairs, some - # introduced by rules above - '/<([bi])><\/\\1>/' => '', - ); - - $text = preg_replace( - array_keys( $tidyregs ), - array_values( $tidyregs ), - $text ); - } - - wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) ); - - # Information on include size limits, for the benefit of users who try to skirt them - if ( $this->mOptions->getEnableLimitReport() ) { - $max = $this->mOptions->getMaxIncludeSize(); - $limitReport = - "Pre-expand include size: {$this->mIncludeSizes['pre-expand']}/$max bytes\n" . - "Post-expand include size: {$this->mIncludeSizes['post-expand']}/$max bytes\n" . - "Template argument size: {$this->mIncludeSizes['arg']}/$max bytes\n"; - wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) ); - $text .= "<!-- \n$limitReport-->\n"; - } - $this->mOutput->setText( $text ); - $this->mRevisionId = $oldRevisionId; - $this->mRevisionTimestamp = $oldRevisionTimestamp; - wfProfileOut( $fname ); - wfProfileOut( __METHOD__ ); - - return $this->mOutput; - } - - /** - * Recursive parser entry point that can be called from an extension tag - * hook. - */ - function recursiveTagParse( $text ) { - wfProfileIn( __METHOD__ ); - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->strip( $text, $this->mStripState ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->internalParse( $text ); - wfProfileOut( __METHOD__ ); - return $text; - } - - /** - * Expand templates and variables in the text, producing valid, static wikitext. - * Also removes comments. - */ - function preprocess( $text, $title, $options, $revid = null ) { - wfProfileIn( __METHOD__ ); - $this->clearState(); - $this->setOutputType( self::OT_PREPROCESS ); - $this->mOptions = $options; - $this->mTitle = $title; - if( $revid !== null ) { - $this->mRevisionId = $revid; - } - wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) ); - $text = $this->strip( $text, $this->mStripState ); - wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) ); - if ( $this->mOptions->getRemoveComments() ) { - $text = Sanitizer::removeHTMLcomments( $text ); - } - $text = $this->replaceVariables( $text ); - $text = $this->mStripState->unstripBoth( $text ); - wfProfileOut( __METHOD__ ); - return $text; - } - - /** - * Get a random string - * - * @private - * @static - */ - function getRandomString() { - return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff)); - } - - function &getTitle() { return $this->mTitle; } - function getOptions() { return $this->mOptions; } - - function getFunctionLang() { - global $wgLang, $wgContLang; - return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang; - } - - /** - * Replaces all occurrences of HTML-style comments and the given tags - * in the text with a random marker and returns teh next text. The output - * parameter $matches will be an associative array filled with data in - * the form: - * 'UNIQ-xxxxx' => array( - * 'element', - * 'tag content', - * array( 'param' => 'x' ), - * '<element param="x">tag content</element>' ) ) - * - * @param $elements list of element names. Comments are always extracted. - * @param $text Source text string. - * @param $uniq_prefix - * - * @public - * @static - */ - function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = ''){ - static $n = 1; - $stripped = ''; - $matches = array(); - - $taglist = implode( '|', $elements ); - $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i"; - - while ( '' != $text ) { - $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE ); - $stripped .= $p[0]; - if( count( $p ) < 5 ) { - break; - } - if( count( $p ) > 5 ) { - // comment - $element = $p[4]; - $attributes = ''; - $close = ''; - $inside = $p[5]; - } else { - // tag - $element = $p[1]; - $attributes = $p[2]; - $close = $p[3]; - $inside = $p[4]; - } - - $marker = "$uniq_prefix-$element-" . sprintf('%08X', $n++) . "-QINU\x07"; - $stripped .= $marker; - - if ( $close === '/>' ) { - // Empty element tag, <tag /> - $content = null; - $text = $inside; - $tail = null; - } else { - if( $element == '!--' ) { - $end = '/(-->)/'; - } else { - $end = "/(<\\/$element\\s*>)/i"; - } - $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE ); - $content = $q[0]; - if( count( $q ) < 3 ) { - # No end tag -- let it run out to the end of the text. - $tail = ''; - $text = ''; - } else { - $tail = $q[1]; - $text = $q[2]; - } - } - - $matches[$marker] = array( $element, - $content, - Sanitizer::decodeTagAttributes( $attributes ), - "<$element$attributes$close$content$tail" ); - } - return $stripped; - } - - /** - * Strips and renders nowiki, pre, math, hiero - * If $render is set, performs necessary rendering operations on plugins - * Returns the text, and fills an array with data needed in unstrip() - * - * @param StripState $state - * - * @param bool $stripcomments when set, HTML comments <!-- like this --> - * will be stripped in addition to other tags. This is important - * for section editing, where these comments cause confusion when - * counting the sections in the wikisource - * - * @param array dontstrip contains tags which should not be stripped; - * used to prevent stipping of <gallery> when saving (fixes bug 2700) - * - * @private - */ - function strip( $text, $state, $stripcomments = false , $dontstrip = array () ) { - global $wgContLang; - wfProfileIn( __METHOD__ ); - $render = ($this->mOutputType == self::OT_HTML); - - $uniq_prefix = $this->mUniqPrefix; - $commentState = new ReplacementArray; - $nowikiItems = array(); - $generalItems = array(); - - $elements = array_merge( - array( 'nowiki', 'gallery' ), - array_keys( $this->mTagHooks ) ); - global $wgRawHtml; - if( $wgRawHtml ) { - $elements[] = 'html'; - } - if( $this->mOptions->getUseTeX() ) { - $elements[] = 'math'; - } - - # Removing $dontstrip tags from $elements list (currently only 'gallery', fixing bug 2700) - foreach ( $elements AS $k => $v ) { - if ( !in_array ( $v , $dontstrip ) ) continue; - unset ( $elements[$k] ); - } - - $matches = array(); - $text = self::extractTagsAndParams( $elements, $text, $matches, $uniq_prefix ); - - foreach( $matches as $marker => $data ) { - list( $element, $content, $params, $tag ) = $data; - if( $render ) { - $tagName = strtolower( $element ); - wfProfileIn( __METHOD__."-render-$tagName" ); - switch( $tagName ) { - case '!--': - // Comment - if( substr( $tag, -3 ) == '-->' ) { - $output = $tag; - } else { - // Unclosed comment in input. - // Close it so later stripping can remove it - $output = "$tag-->"; - } - break; - case 'html': - if( $wgRawHtml ) { - $output = $content; - break; - } - // Shouldn't happen otherwise. :) - case 'nowiki': - $output = Xml::escapeTagsOnly( $content ); - break; - case 'math': - $output = $wgContLang->armourMath( - MathRenderer::renderMath( $content, $params ) ); - break; - case 'gallery': - $output = $this->renderImageGallery( $content, $params ); - break; - default: - if( isset( $this->mTagHooks[$tagName] ) ) { - $output = call_user_func_array( $this->mTagHooks[$tagName], - array( $content, $params, $this ) ); - } else { - throw new MWException( "Invalid call hook $element" ); - } - } - wfProfileOut( __METHOD__."-render-$tagName" ); - } else { - // Just stripping tags; keep the source - $output = $tag; - } - - // Unstrip the output, to support recursive strip() calls - $output = $state->unstripBoth( $output ); - - if( !$stripcomments && $element == '!--' ) { - $commentState->setPair( $marker, $output ); - } elseif ( $element == 'html' || $element == 'nowiki' ) { - $nowikiItems[$marker] = $output; - } else { - $generalItems[$marker] = $output; - } - } - # Add the new items to the state - # We do this after the loop instead of during it to avoid slowing - # down the recursive unstrip - $state->nowiki->mergeArray( $nowikiItems ); - $state->general->mergeArray( $generalItems ); - - # Unstrip comments unless explicitly told otherwise. - # (The comments are always stripped prior to this point, so as to - # not invoke any extension tags / parser hooks contained within - # a comment.) - if ( !$stripcomments ) { - // Put them all back and forget them - $text = $commentState->replace( $text ); - } - - wfProfileOut( __METHOD__ ); - return $text; - } - - /** - * Restores pre, math, and other extensions removed by strip() - * - * always call unstripNoWiki() after this one - * @private - * @deprecated use $this->mStripState->unstrip() - */ - function unstrip( $text, $state ) { - return $state->unstripGeneral( $text ); - } - - /** - * Always call this after unstrip() to preserve the order - * - * @private - * @deprecated use $this->mStripState->unstrip() - */ - function unstripNoWiki( $text, $state ) { - return $state->unstripNoWiki( $text ); - } - - /** - * @deprecated use $this->mStripState->unstripBoth() - */ - function unstripForHTML( $text ) { - return $this->mStripState->unstripBoth( $text ); - } - - /** - * Add an item to the strip state - * Returns the unique tag which must be inserted into the stripped text - * The tag will be replaced with the original text in unstrip() - * - * @private - */ - function insertStripItem( $text, &$state ) { - $rnd = $this->mUniqPrefix . '-item' . self::getRandomString(); - $state->general->setPair( $rnd, $text ); - return $rnd; - } - - /** - * Interface with html tidy, used if $wgUseTidy = true. - * If tidy isn't able to correct the markup, the original will be - * returned in all its glory with a warning comment appended. - * - * Either the external tidy program or the in-process tidy extension - * will be used depending on availability. Override the default - * $wgTidyInternal setting to disable the internal if it's not working. - * - * @param string $text Hideous HTML input - * @return string Corrected HTML output - * @public - * @static - */ - function tidy( $text ) { - global $wgTidyInternal; - $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'. -' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'. -'<head><title>test</title></head><body>'.$text.'</body></html>'; - if( $wgTidyInternal ) { - $correctedtext = self::internalTidy( $wrappedtext ); - } else { - $correctedtext = self::externalTidy( $wrappedtext ); - } - if( is_null( $correctedtext ) ) { - wfDebug( "Tidy error detected!\n" ); - return $text . "\n<!-- Tidy found serious XHTML errors -->\n"; - } - return $correctedtext; - } - - /** - * Spawn an external HTML tidy process and get corrected markup back from it. - * - * @private - * @static - */ - function externalTidy( $text ) { - global $wgTidyConf, $wgTidyBin, $wgTidyOpts; - $fname = 'Parser::externalTidy'; - wfProfileIn( $fname ); - - $cleansource = ''; - $opts = ' -utf8'; - - $descriptorspec = array( - 0 => array('pipe', 'r'), - 1 => array('pipe', 'w'), - 2 => array('file', wfGetNull(), 'a') - ); - $pipes = array(); - $process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes); - if (is_resource($process)) { - // Theoretically, this style of communication could cause a deadlock - // here. If the stdout buffer fills up, then writes to stdin could - // block. This doesn't appear to happen with tidy, because tidy only - // writes to stdout after it's finished reading from stdin. Search - // for tidyParseStdin and tidySaveStdout in console/tidy.c - fwrite($pipes[0], $text); - fclose($pipes[0]); - while (!feof($pipes[1])) { - $cleansource .= fgets($pipes[1], 1024); - } - fclose($pipes[1]); - proc_close($process); - } - - wfProfileOut( $fname ); - - if( $cleansource == '' && $text != '') { - // Some kind of error happened, so we couldn't get the corrected text. - // Just give up; we'll use the source text and append a warning. - return null; - } else { - return $cleansource; - } - } - - /** - * Use the HTML tidy PECL extension to use the tidy library in-process, - * saving the overhead of spawning a new process. - * - * 'pear install tidy' should be able to compile the extension module. - * - * @private - * @static - */ - function internalTidy( $text ) { - global $wgTidyConf, $IP; - $fname = 'Parser::internalTidy'; - wfProfileIn( $fname ); - - $tidy = new tidy; - $tidy->parseString( $text, $wgTidyConf, 'utf8' ); - $tidy->cleanRepair(); - if( $tidy->getStatus() == 2 ) { - // 2 is magic number for fatal error - // http://www.php.net/manual/en/function.tidy-get-status.php - $cleansource = null; - } else { - $cleansource = tidy_get_output( $tidy ); - } - wfProfileOut( $fname ); - return $cleansource; - } - - /** - * parse the wiki syntax used to render tables - * - * @private - */ - function doTableStuff ( $text ) { - $fname = 'Parser::doTableStuff'; - wfProfileIn( $fname ); - - $lines = explode ( "\n" , $text ); - $td_history = array (); // Is currently a td tag open? - $last_tag_history = array (); // Save history of last lag activated (td, th or caption) - $tr_history = array (); // Is currently a tr tag open? - $tr_attributes = array (); // history of tr attributes - $has_opened_tr = array(); // Did this table open a <tr> element? - $indent_level = 0; // indent level of the table - foreach ( $lines as $key => $line ) - { - $line = trim ( $line ); - - if( $line == '' ) { // empty line, go to next line - continue; - } - $first_character = $line{0}; - $matches = array(); - - if ( preg_match( '/^(:*)\{\|(.*)$/' , $line , $matches ) ) { - // First check if we are starting a new table - $indent_level = strlen( $matches[1] ); - - $attributes = $this->mStripState->unstripBoth( $matches[2] ); - $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' ); - - $lines[$key] = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>"; - array_push ( $td_history , false ); - array_push ( $last_tag_history , '' ); - array_push ( $tr_history , false ); - array_push ( $tr_attributes , '' ); - array_push ( $has_opened_tr , false ); - } else if ( count ( $td_history ) == 0 ) { - // Don't do any of the following - continue; - } else if ( substr ( $line , 0 , 2 ) == '|}' ) { - // We are ending a table - $line = '</table>' . substr ( $line , 2 ); - $last_tag = array_pop ( $last_tag_history ); - - if ( !array_pop ( $has_opened_tr ) ) { - $line = "<tr><td></td></tr>{$line}"; - } - - if ( array_pop ( $tr_history ) ) { - $line = "</tr>{$line}"; - } - - if ( array_pop ( $td_history ) ) { - $line = "</{$last_tag}>{$line}"; - } - array_pop ( $tr_attributes ); - $lines[$key] = $line . str_repeat( '</dd></dl>' , $indent_level ); - } else if ( substr ( $line , 0 , 2 ) == '|-' ) { - // Now we have a table row - $line = preg_replace( '#^\|-+#', '', $line ); - - // Whats after the tag is now only attributes - $attributes = $this->mStripState->unstripBoth( $line ); - $attributes = Sanitizer::fixTagAttributes ( $attributes , 'tr' ); - array_pop ( $tr_attributes ); - array_push ( $tr_attributes , $attributes ); - - $line = ''; - $last_tag = array_pop ( $last_tag_history ); - array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ); - - if ( array_pop ( $tr_history ) ) { - $line = '</tr>'; - } - - if ( array_pop ( $td_history ) ) { - $line = "</{$last_tag}>{$line}"; - } - - $lines[$key] = $line; - array_push ( $tr_history , false ); - array_push ( $td_history , false ); - array_push ( $last_tag_history , '' ); - } - else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) { - // This might be cell elements, td, th or captions - if ( substr ( $line , 0 , 2 ) == '|+' ) { - $first_character = '+'; - $line = substr ( $line , 1 ); - } - - $line = substr ( $line , 1 ); - - if ( $first_character == '!' ) { - $line = str_replace ( '!!' , '||' , $line ); - } - - // Split up multiple cells on the same line. - // FIXME : This can result in improper nesting of tags processed - // by earlier parser steps, but should avoid splitting up eg - // attribute values containing literal "||". - $cells = StringUtils::explodeMarkup( '||' , $line ); - - $lines[$key] = ''; - - // Loop through each table cell - foreach ( $cells as $cell ) - { - $previous = ''; - if ( $first_character != '+' ) - { - $tr_after = array_pop ( $tr_attributes ); - if ( !array_pop ( $tr_history ) ) { - $previous = "<tr{$tr_after}>\n"; - } - array_push ( $tr_history , true ); - array_push ( $tr_attributes , '' ); - array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ); - } - - $last_tag = array_pop ( $last_tag_history ); - - if ( array_pop ( $td_history ) ) { - $previous = "</{$last_tag}>{$previous}"; - } - - if ( $first_character == '|' ) { - $last_tag = 'td'; - } else if ( $first_character == '!' ) { - $last_tag = 'th'; - } else if ( $first_character == '+' ) { - $last_tag = 'caption'; - } else { - $last_tag = ''; - } - - array_push ( $last_tag_history , $last_tag ); - - // A cell could contain both parameters and data - $cell_data = explode ( '|' , $cell , 2 ); - - // Bug 553: Note that a '|' inside an invalid link should not - // be mistaken as delimiting cell parameters - if ( strpos( $cell_data[0], '[[' ) !== false ) { - $cell = "{$previous}<{$last_tag}>{$cell}"; - } else if ( count ( $cell_data ) == 1 ) - $cell = "{$previous}<{$last_tag}>{$cell_data[0]}"; - else { - $attributes = $this->mStripState->unstripBoth( $cell_data[0] ); - $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag ); - $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}"; - } - - $lines[$key] .= $cell; - array_push ( $td_history , true ); - } - } - } - - // Closing open td, tr && table - while ( count ( $td_history ) > 0 ) - { - if ( array_pop ( $td_history ) ) { - $lines[] = '</td>' ; - } - if ( array_pop ( $tr_history ) ) { - $lines[] = '</tr>' ; - } - if ( !array_pop ( $has_opened_tr ) ) { - $lines[] = "<tr><td></td></tr>" ; - } - - $lines[] = '</table>' ; - } - - $output = implode ( "\n" , $lines ) ; - - // special case: don't return empty table - if( $output == "<table>\n<tr><td></td></tr>\n</table>" ) { - $output = ''; - } - - wfProfileOut( $fname ); - - return $output; - } - - /** - * Helper function for parse() that transforms wiki markup into - * HTML. Only called for $mOutputType == OT_HTML. - * - * @private - */ - function internalParse( $text ) { - $args = array(); - $isMain = true; - $fname = 'Parser::internalParse'; - wfProfileIn( $fname ); - - # Hook to suspend the parser in this state - if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) { - wfProfileOut( $fname ); - return $text ; - } - - # Remove <noinclude> tags and <includeonly> sections - $text = strtr( $text, array( '<onlyinclude>' => '' , '</onlyinclude>' => '' ) ); - $text = strtr( $text, array( '<noinclude>' => '', '</noinclude>' => '') ); - $text = StringUtils::delimiterReplace( '<includeonly>', '</includeonly>', '', $text ); - - $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), array(), array_keys( $this->mTransparentTagHooks ) ); - - $text = $this->replaceVariables( $text, $args ); - wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) ); - - // Tables need to come after variable replacement for things to work - // properly; putting them before other transformations should keep - // exciting things like link expansions from showing up in surprising - // places. - $text = $this->doTableStuff( $text ); - - $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text ); - - $text = $this->stripToc( $text ); - $this->stripNoGallery( $text ); - $text = $this->doHeadings( $text ); - if($this->mOptions->getUseDynamicDates()) { - $df =& DateFormatter::getInstance(); - $text = $df->reformat( $this->mOptions->getDateFormat(), $text ); - } - $text = $this->doAllQuotes( $text ); - $text = $this->replaceInternalLinks( $text ); - $text = $this->replaceExternalLinks( $text ); - - # replaceInternalLinks may sometimes leave behind - # absolute URLs, which have to be masked to hide them from replaceExternalLinks - $text = str_replace($this->mUniqPrefix."NOPARSE", "", $text); - - $text = $this->doMagicLinks( $text ); - $text = $this->formatHeadings( $text, $isMain ); - - wfProfileOut( $fname ); - return $text; - } - - /** - * Replace special strings like "ISBN xxx" and "RFC xxx" with - * magic external links. - * - * @private - */ - function &doMagicLinks( &$text ) { - wfProfileIn( __METHOD__ ); - $text = preg_replace_callback( - '!(?: # Start cases - <a.*?</a> | # Skip link text - <.*?> | # Skip stuff inside HTML elements - (?:RFC|PMID)\s+([0-9]+) | # RFC or PMID, capture number as m[1] - ISBN\s+(\b # ISBN, capture number as m[2] - (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix - (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters - [0-9Xx] # check digit - \b) - )!x', array( &$this, 'magicLinkCallback' ), $text ); - wfProfileOut( __METHOD__ ); - return $text; - } - - function magicLinkCallback( $m ) { - if ( substr( $m[0], 0, 1 ) == '<' ) { - # Skip HTML element - return $m[0]; - } elseif ( substr( $m[0], 0, 4 ) == 'ISBN' ) { - $isbn = $m[2]; - $num = strtr( $isbn, array( - '-' => '', - ' ' => '', - 'x' => 'X', - )); - $titleObj = SpecialPage::getTitleFor( 'Booksources' ); - $text = '<a href="' . - $titleObj->escapeLocalUrl( "isbn=$num" ) . - "\" class=\"internal\">ISBN $isbn</a>"; - } else { - if ( substr( $m[0], 0, 3 ) == 'RFC' ) { - $keyword = 'RFC'; - $urlmsg = 'rfcurl'; - $id = $m[1]; - } elseif ( substr( $m[0], 0, 4 ) == 'PMID' ) { - $keyword = 'PMID'; - $urlmsg = 'pubmedurl'; - $id = $m[1]; - } else { - throw new MWException( __METHOD__.': unrecognised match type "' . - substr($m[0], 0, 20 ) . '"' ); - } - - $url = wfMsg( $urlmsg, $id); - $sk = $this->mOptions->getSkin(); - $la = $sk->getExternalLinkAttributes( $url, $keyword.$id ); - $text = "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>"; - } - return $text; - } - - /** - * Parse headers and return html - * - * @private - */ - function doHeadings( $text ) { - $fname = 'Parser::doHeadings'; - wfProfileIn( $fname ); - for ( $i = 6; $i >= 1; --$i ) { - $h = str_repeat( '=', $i ); - $text = preg_replace( "/^{$h}(.+){$h}\\s*$/m", - "<h{$i}>\\1</h{$i}>\\2", $text ); - } - wfProfileOut( $fname ); - return $text; - } - - /** - * Replace single quotes with HTML markup - * @private - * @return string the altered text - */ - function doAllQuotes( $text ) { - $fname = 'Parser::doAllQuotes'; - wfProfileIn( $fname ); - $outtext = ''; - $lines = explode( "\n", $text ); - foreach ( $lines as $line ) { - $outtext .= $this->doQuotes ( $line ) . "\n"; - } - $outtext = substr($outtext, 0,-1); - wfProfileOut( $fname ); - return $outtext; - } - - /** - * Helper function for doAllQuotes() - */ - public function doQuotes( $text ) { - $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE ); - if ( count( $arr ) == 1 ) - return $text; - else - { - # First, do some preliminary work. This may shift some apostrophes from - # being mark-up to being text. It also counts the number of occurrences - # of bold and italics mark-ups. - $i = 0; - $numbold = 0; - $numitalics = 0; - foreach ( $arr as $r ) - { - if ( ( $i % 2 ) == 1 ) - { - # If there are ever four apostrophes, assume the first is supposed to - # be text, and the remaining three constitute mark-up for bold text. - if ( strlen( $arr[$i] ) == 4 ) - { - $arr[$i-1] .= "'"; - $arr[$i] = "'''"; - } - # If there are more than 5 apostrophes in a row, assume they're all - # text except for the last 5. - else if ( strlen( $arr[$i] ) > 5 ) - { - $arr[$i-1] .= str_repeat( "'", strlen( $arr[$i] ) - 5 ); - $arr[$i] = "'''''"; - } - # Count the number of occurrences of bold and italics mark-ups. - # We are not counting sequences of five apostrophes. - if ( strlen( $arr[$i] ) == 2 ) { $numitalics++; } - else if ( strlen( $arr[$i] ) == 3 ) { $numbold++; } - else if ( strlen( $arr[$i] ) == 5 ) { $numitalics++; $numbold++; } - } - $i++; - } - - # If there is an odd number of both bold and italics, it is likely - # that one of the bold ones was meant to be an apostrophe followed - # by italics. Which one we cannot know for certain, but it is more - # likely to be one that has a single-letter word before it. - if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) - { - $i = 0; - $firstsingleletterword = -1; - $firstmultiletterword = -1; - $firstspace = -1; - foreach ( $arr as $r ) - { - if ( ( $i % 2 == 1 ) and ( strlen( $r ) == 3 ) ) - { - $x1 = substr ($arr[$i-1], -1); - $x2 = substr ($arr[$i-1], -2, 1); - if ($x1 == ' ') { - if ($firstspace == -1) $firstspace = $i; - } else if ($x2 == ' ') { - if ($firstsingleletterword == -1) $firstsingleletterword = $i; - } else { - if ($firstmultiletterword == -1) $firstmultiletterword = $i; - } - } - $i++; - } - - # If there is a single-letter word, use it! - if ($firstsingleletterword > -1) - { - $arr [ $firstsingleletterword ] = "''"; - $arr [ $firstsingleletterword-1 ] .= "'"; - } - # If not, but there's a multi-letter word, use that one. - else if ($firstmultiletterword > -1) - { - $arr [ $firstmultiletterword ] = "''"; - $arr [ $firstmultiletterword-1 ] .= "'"; - } - # ... otherwise use the first one that has neither. - # (notice that it is possible for all three to be -1 if, for example, - # there is only one pentuple-apostrophe in the line) - else if ($firstspace > -1) - { - $arr [ $firstspace ] = "''"; - $arr [ $firstspace-1 ] .= "'"; - } - } - - # Now let's actually convert our apostrophic mush to HTML! - $output = ''; - $buffer = ''; - $state = ''; - $i = 0; - foreach ($arr as $r) - { - if (($i % 2) == 0) - { - if ($state == 'both') - $buffer .= $r; - else - $output .= $r; - } - else - { - if (strlen ($r) == 2) - { - if ($state == 'i') - { $output .= '</i>'; $state = ''; } - else if ($state == 'bi') - { $output .= '</i>'; $state = 'b'; } - else if ($state == 'ib') - { $output .= '</b></i><b>'; $state = 'b'; } - else if ($state == 'both') - { $output .= '<b><i>'.$buffer.'</i>'; $state = 'b'; } - else # $state can be 'b' or '' - { $output .= '<i>'; $state .= 'i'; } - } - else if (strlen ($r) == 3) - { - if ($state == 'b') - { $output .= '</b>'; $state = ''; } - else if ($state == 'bi') - { $output .= '</i></b><i>'; $state = 'i'; } - else if ($state == 'ib') - { $output .= '</b>'; $state = 'i'; } - else if ($state == 'both') - { $output .= '<i><b>'.$buffer.'</b>'; $state = 'i'; } - else # $state can be 'i' or '' - { $output .= '<b>'; $state .= 'b'; } - } - else if (strlen ($r) == 5) - { - if ($state == 'b') - { $output .= '</b><i>'; $state = 'i'; } - else if ($state == 'i') - { $output .= '</i><b>'; $state = 'b'; } - else if ($state == 'bi') - { $output .= '</i></b>'; $state = ''; } - else if ($state == 'ib') - { $output .= '</b></i>'; $state = ''; } - else if ($state == 'both') - { $output .= '<i><b>'.$buffer.'</b></i>'; $state = ''; } - else # ($state == '') - { $buffer = ''; $state = 'both'; } - } - } - $i++; - } - # Now close all remaining tags. Notice that the order is important. - if ($state == 'b' || $state == 'ib') - $output .= '</b>'; - if ($state == 'i' || $state == 'bi' || $state == 'ib') - $output .= '</i>'; - if ($state == 'bi') - $output .= '</b>'; - # There might be lonely ''''', so make sure we have a buffer - if ($state == 'both' && $buffer) - $output .= '<b><i>'.$buffer.'</i></b>'; - return $output; - } - } - - /** - * Replace external links - * - * Note: this is all very hackish and the order of execution matters a lot. - * Make sure to run maintenance/parserTests.php if you change this code. - * - * @private - */ - function replaceExternalLinks( $text ) { - global $wgContLang; - $fname = 'Parser::replaceExternalLinks'; - wfProfileIn( $fname ); - - $sk = $this->mOptions->getSkin(); - - $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE ); - - $s = $this->replaceFreeExternalLinks( array_shift( $bits ) ); - - $i = 0; - while ( $i<count( $bits ) ) { - $url = $bits[$i++]; - $protocol = $bits[$i++]; - $text = $bits[$i++]; - $trail = $bits[$i++]; - - # The characters '<' and '>' (which were escaped by - # removeHTMLtags()) should not be included in - # URLs, per RFC 2396. - $m2 = array(); - if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) { - $text = substr($url, $m2[0][1]) . ' ' . $text; - $url = substr($url, 0, $m2[0][1]); - } - - # If the link text is an image URL, replace it with an <img> tag - # This happened by accident in the original parser, but some people used it extensively - $img = $this->maybeMakeExternalImage( $text ); - if ( $img !== false ) { - $text = $img; - } - - $dtrail = ''; - - # Set linktype for CSS - if URL==text, link is essentially free - $linktype = ($text == $url) ? 'free' : 'text'; - - # No link text, e.g. [http://domain.tld/some.link] - if ( $text == '' ) { - # Autonumber if allowed. See bug #5918 - if ( strpos( wfUrlProtocols(), substr($protocol, 0, strpos($protocol, ':')) ) !== false ) { - $text = '[' . ++$this->mAutonumber . ']'; - $linktype = 'autonumber'; - } else { - # Otherwise just use the URL - $text = htmlspecialchars( $url ); - $linktype = 'free'; - } - } else { - # Have link text, e.g. [http://domain.tld/some.link text]s - # Check for trail - list( $dtrail, $trail ) = Linker::splitTrail( $trail ); - } - - $text = $wgContLang->markNoConversion($text); - - $url = Sanitizer::cleanUrl( $url ); - - # Process the trail (i.e. everything after this link up until start of the next link), - # replacing any non-bracketed links - $trail = $this->replaceFreeExternalLinks( $trail ); - - # Use the encoded URL - # This means that users can paste URLs directly into the text - # Funny characters like ö aren't valid in URLs anyway - # This was changed in August 2004 - $s .= $sk->makeExternalLink( $url, $text, false, $linktype, $this->mTitle->getNamespace() ) . $dtrail . $trail; - - # Register link in the output object. - # Replace unnecessary URL escape codes with the referenced character - # This prevents spammers from hiding links from the filters - $pasteurized = self::replaceUnusualEscapes( $url ); - $this->mOutput->addExternalLink( $pasteurized ); - } - - wfProfileOut( $fname ); - return $s; - } - - /** - * Replace anything that looks like a URL with a link - * @private - */ - function replaceFreeExternalLinks( $text ) { - global $wgContLang; - $fname = 'Parser::replaceFreeExternalLinks'; - wfProfileIn( $fname ); - - $bits = preg_split( '/(\b(?:' . wfUrlProtocols() . '))/S', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); - $s = array_shift( $bits ); - $i = 0; - - $sk = $this->mOptions->getSkin(); - - while ( $i < count( $bits ) ){ - $protocol = $bits[$i++]; - $remainder = $bits[$i++]; - - $m = array(); - if ( preg_match( '/^('.self::EXT_LINK_URL_CLASS.'+)(.*)$/s', $remainder, $m ) ) { - # Found some characters after the protocol that look promising - $url = $protocol . $m[1]; - $trail = $m[2]; - - # special case: handle urls as url args: - # http://www.example.com/foo?=http://www.example.com/bar - if(strlen($trail) == 0 && - isset($bits[$i]) && - preg_match('/^'. wfUrlProtocols() . '$/S', $bits[$i]) && - preg_match( '/^('.self::EXT_LINK_URL_CLASS.'+)(.*)$/s', $bits[$i + 1], $m )) - { - # add protocol, arg - $url .= $bits[$i] . $m[1]; # protocol, url as arg to previous link - $i += 2; - $trail = $m[2]; - } - - # The characters '<' and '>' (which were escaped by - # removeHTMLtags()) should not be included in - # URLs, per RFC 2396. - $m2 = array(); - if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) { - $trail = substr($url, $m2[0][1]) . $trail; - $url = substr($url, 0, $m2[0][1]); - } - - # Move trailing punctuation to $trail - $sep = ',;\.:!?'; - # If there is no left bracket, then consider right brackets fair game too - if ( strpos( $url, '(' ) === false ) { - $sep .= ')'; - } - - $numSepChars = strspn( strrev( $url ), $sep ); - if ( $numSepChars ) { - $trail = substr( $url, -$numSepChars ) . $trail; - $url = substr( $url, 0, -$numSepChars ); - } - - $url = Sanitizer::cleanUrl( $url ); - - # Is this an external image? - $text = $this->maybeMakeExternalImage( $url ); - if ( $text === false ) { - # Not an image, make a link - $text = $sk->makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free', $this->mTitle->getNamespace() ); - # Register it in the output object... - # Replace unnecessary URL escape codes with their equivalent characters - $pasteurized = self::replaceUnusualEscapes( $url ); - $this->mOutput->addExternalLink( $pasteurized ); - } - $s .= $text . $trail; - } else { - $s .= $protocol . $remainder; - } - } - wfProfileOut( $fname ); - return $s; - } - - /** - * Replace unusual URL escape codes with their equivalent characters - * @param string - * @return string - * @static - * @todo This can merge genuinely required bits in the path or query string, - * breaking legit URLs. A proper fix would treat the various parts of - * the URL differently; as a workaround, just use the output for - * statistical records, not for actual linking/output. - */ - static function replaceUnusualEscapes( $url ) { - return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', - array( __CLASS__, 'replaceUnusualEscapesCallback' ), $url ); - } - - /** - * Callback function used in replaceUnusualEscapes(). - * Replaces unusual URL escape codes with their equivalent character - * @static - * @private - */ - private static function replaceUnusualEscapesCallback( $matches ) { - $char = urldecode( $matches[0] ); - $ord = ord( $char ); - // Is it an unsafe or HTTP reserved character according to RFC 1738? - if ( $ord > 32 && $ord < 127 && strpos( '<>"#{}|\^~[]`;/?', $char ) === false ) { - // No, shouldn't be escaped - return $char; - } else { - // Yes, leave it escaped - return $matches[0]; - } - } - - /** - * make an image if it's allowed, either through the global - * option or through the exception - * @private - */ - function maybeMakeExternalImage( $url ) { - $sk = $this->mOptions->getSkin(); - $imagesfrom = $this->mOptions->getAllowExternalImagesFrom(); - $imagesexception = !empty($imagesfrom); - $text = false; - if ( $this->mOptions->getAllowExternalImages() - || ( $imagesexception && strpos( $url, $imagesfrom ) === 0 ) ) { - if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) { - # Image found - $text = $sk->makeExternalImage( htmlspecialchars( $url ) ); - } - } - return $text; - } - - /** - * Process [[ ]] wikilinks - * - * @private - */ - function replaceInternalLinks( $s ) { - global $wgContLang; - static $fname = 'Parser::replaceInternalLinks' ; - - wfProfileIn( $fname ); - - wfProfileIn( $fname.'-setup' ); - static $tc = FALSE; - # the % is needed to support urlencoded titles as well - if ( !$tc ) { $tc = Title::legalChars() . '#%'; } - - $sk = $this->mOptions->getSkin(); - - #split the entire text string on occurences of [[ - $a = explode( '[[', ' ' . $s ); - #get the first element (all text up to first [[), and remove the space we added - $s = array_shift( $a ); - $s = substr( $s, 1 ); - - # Match a link having the form [[namespace:link|alternate]]trail - static $e1 = FALSE; - if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD"; } - # Match cases where there is no "]]", which might still be images - static $e1_img = FALSE; - if ( !$e1_img ) { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; } - # Match the end of a line for a word that's not followed by whitespace, - # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched - $e2 = wfMsgForContent( 'linkprefix' ); - - $useLinkPrefixExtension = $wgContLang->linkPrefixExtension(); - if( is_null( $this->mTitle ) ) { - throw new MWException( __METHOD__.": \$this->mTitle is null\n" ); - } - $nottalk = !$this->mTitle->isTalkPage(); - - if ( $useLinkPrefixExtension ) { - $m = array(); - if ( preg_match( $e2, $s, $m ) ) { - $first_prefix = $m[2]; - } else { - $first_prefix = false; - } - } else { - $prefix = ''; - } - - if($wgContLang->hasVariants()) { - $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText()); - } else { - $selflink = array($this->mTitle->getPrefixedText()); - } - $useSubpages = $this->areSubpagesAllowed(); - wfProfileOut( $fname.'-setup' ); - - # Loop for each link - for ($k = 0; isset( $a[$k] ); $k++) { - $line = $a[$k]; - if ( $useLinkPrefixExtension ) { - wfProfileIn( $fname.'-prefixhandling' ); - if ( preg_match( $e2, $s, $m ) ) { - $prefix = $m[2]; - $s = $m[1]; - } else { - $prefix=''; - } - # first link - if($first_prefix) { - $prefix = $first_prefix; - $first_prefix = false; - } - wfProfileOut( $fname.'-prefixhandling' ); - } - - $might_be_img = false; - - wfProfileIn( "$fname-e1" ); - if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt - $text = $m[2]; - # If we get a ] at the beginning of $m[3] that means we have a link that's something like: - # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up, - # the real problem is with the $e1 regex - # See bug 1300. - # - # Still some problems for cases where the ] is meant to be outside punctuation, - # and no image is in sight. See bug 2095. - # - if( $text !== '' && - substr( $m[3], 0, 1 ) === ']' && - strpos($text, '[') !== false - ) - { - $text .= ']'; # so that replaceExternalLinks($text) works later - $m[3] = substr( $m[3], 1 ); - } - # fix up urlencoded title texts - if( strpos( $m[1], '%' ) !== false ) { - # Should anchors '#' also be rejected? - $m[1] = str_replace( array('<', '>'), array('<', '>'), urldecode($m[1]) ); - } - $trail = $m[3]; - } elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption - $might_be_img = true; - $text = $m[2]; - if ( strpos( $m[1], '%' ) !== false ) { - $m[1] = urldecode($m[1]); - } - $trail = ""; - } else { # Invalid form; output directly - $s .= $prefix . '[[' . $line ; - wfProfileOut( "$fname-e1" ); - continue; - } - wfProfileOut( "$fname-e1" ); - wfProfileIn( "$fname-misc" ); - - # Don't allow internal links to pages containing - # PROTO: where PROTO is a valid URL protocol; these - # should be external links. - if (preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $m[1])) { - $s .= $prefix . '[[' . $line ; - continue; - } - - # Make subpage if necessary - if( $useSubpages ) { - $link = $this->maybeDoSubpageLink( $m[1], $text ); - } else { - $link = $m[1]; - } - - $noforce = (substr($m[1], 0, 1) != ':'); - if (!$noforce) { - # Strip off leading ':' - $link = substr($link, 1); - } - - wfProfileOut( "$fname-misc" ); - wfProfileIn( "$fname-title" ); - $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) ); - if( !$nt ) { - $s .= $prefix . '[[' . $line; - wfProfileOut( "$fname-title" ); - continue; - } - - $ns = $nt->getNamespace(); - $iw = $nt->getInterWiki(); - wfProfileOut( "$fname-title" ); - - if ($might_be_img) { # if this is actually an invalid link - wfProfileIn( "$fname-might_be_img" ); - if ($ns == NS_IMAGE && $noforce) { #but might be an image - $found = false; - while (isset ($a[$k+1]) ) { - #look at the next 'line' to see if we can close it there - $spliced = array_splice( $a, $k + 1, 1 ); - $next_line = array_shift( $spliced ); - $m = explode( ']]', $next_line, 3 ); - if ( count( $m ) == 3 ) { - # the first ]] closes the inner link, the second the image - $found = true; - $text .= "[[{$m[0]}]]{$m[1]}"; - $trail = $m[2]; - break; - } elseif ( count( $m ) == 2 ) { - #if there's exactly one ]] that's fine, we'll keep looking - $text .= "[[{$m[0]}]]{$m[1]}"; - } else { - #if $next_line is invalid too, we need look no further - $text .= '[[' . $next_line; - break; - } - } - if ( !$found ) { - # we couldn't find the end of this imageLink, so output it raw - #but don't ignore what might be perfectly normal links in the text we've examined - $text = $this->replaceInternalLinks($text); - $s .= "{$prefix}[[$link|$text"; - # note: no $trail, because without an end, there *is* no trail - wfProfileOut( "$fname-might_be_img" ); - continue; - } - } else { #it's not an image, so output it raw - $s .= "{$prefix}[[$link|$text"; - # note: no $trail, because without an end, there *is* no trail - wfProfileOut( "$fname-might_be_img" ); - continue; - } - wfProfileOut( "$fname-might_be_img" ); - } - - $wasblank = ( '' == $text ); - if( $wasblank ) $text = $link; - - # Link not escaped by : , create the various objects - if( $noforce ) { - - # Interwikis - wfProfileIn( "$fname-interwiki" ); - if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) { - $this->mOutput->addLanguageLink( $nt->getFullText() ); - $s = rtrim($s . $prefix); - $s .= trim($trail, "\n") == '' ? '': $prefix . $trail; - wfProfileOut( "$fname-interwiki" ); - continue; - } - wfProfileOut( "$fname-interwiki" ); - - if ( $ns == NS_IMAGE ) { - wfProfileIn( "$fname-image" ); - if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) { - # recursively parse links inside the image caption - # actually, this will parse them in any other parameters, too, - # but it might be hard to fix that, and it doesn't matter ATM - $text = $this->replaceExternalLinks($text); - $text = $this->replaceInternalLinks($text); - - # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them - $s .= $prefix . $this->armorLinks( $this->makeImage( $nt, $text ) ) . $trail; - $this->mOutput->addImage( $nt->getDBkey() ); - - wfProfileOut( "$fname-image" ); - continue; - } else { - # We still need to record the image's presence on the page - $this->mOutput->addImage( $nt->getDBkey() ); - } - wfProfileOut( "$fname-image" ); - - } - - if ( $ns == NS_CATEGORY ) { - wfProfileIn( "$fname-category" ); - $s = rtrim($s . "\n"); # bug 87 - - if ( $wasblank ) { - $sortkey = $this->getDefaultSort(); - } else { - $sortkey = $text; - } - $sortkey = Sanitizer::decodeCharReferences( $sortkey ); - $sortkey = str_replace( "\n", '', $sortkey ); - $sortkey = $wgContLang->convertCategoryKey( $sortkey ); - $this->mOutput->addCategory( $nt->getDBkey(), $sortkey ); - - /** - * Strip the whitespace Category links produce, see bug 87 - * @todo We might want to use trim($tmp, "\n") here. - */ - $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail; - - wfProfileOut( "$fname-category" ); - continue; - } - } - - # Self-link checking - if( $nt->getFragment() === '' ) { - if( in_array( $nt->getPrefixedText(), $selflink, true ) ) { - $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail ); - continue; - } - } - - # Special and Media are pseudo-namespaces; no pages actually exist in them - if( $ns == NS_MEDIA ) { - $link = $sk->makeMediaLinkObj( $nt, $text ); - # Cloak with NOPARSE to avoid replacement in replaceExternalLinks - $s .= $prefix . $this->armorLinks( $link ) . $trail; - $this->mOutput->addImage( $nt->getDBkey() ); - continue; - } elseif( $ns == NS_SPECIAL ) { - if( SpecialPage::exists( $nt->getDBkey() ) ) { - $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix ); - } else { - $s .= $this->makeLinkHolder( $nt, $text, '', $trail, $prefix ); - } - continue; - } elseif( $ns == NS_IMAGE ) { - $img = wfFindFile( $nt ); - if( $img ) { - // Force a blue link if the file exists; may be a remote - // upload on the shared repository, and we want to see its - // auto-generated page. - $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix ); - $this->mOutput->addLink( $nt ); - continue; - } - } - $s .= $this->makeLinkHolder( $nt, $text, '', $trail, $prefix ); - } - wfProfileOut( $fname ); - return $s; - } - - /** - * Make a link placeholder. The text returned can be later resolved to a real link with - * replaceLinkHolders(). This is done for two reasons: firstly to avoid further - * parsing of interwiki links, and secondly to allow all existence checks and - * article length checks (for stub links) to be bundled into a single query. - * - */ - function makeLinkHolder( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) { - wfProfileIn( __METHOD__ ); - if ( ! is_object($nt) ) { - # Fail gracefully - $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}"; - } else { - # Separate the link trail from the rest of the link - list( $inside, $trail ) = Linker::splitTrail( $trail ); - - if ( $nt->isExternal() ) { - $nr = array_push( $this->mInterwikiLinkHolders['texts'], $prefix.$text.$inside ); - $this->mInterwikiLinkHolders['titles'][] = $nt; - $retVal = '<!--IWLINK '. ($nr-1) ."-->{$trail}"; - } else { - $nr = array_push( $this->mLinkHolders['namespaces'], $nt->getNamespace() ); - $this->mLinkHolders['dbkeys'][] = $nt->getDBkey(); - $this->mLinkHolders['queries'][] = $query; - $this->mLinkHolders['texts'][] = $prefix.$text.$inside; - $this->mLinkHolders['titles'][] = $nt; - - $retVal = '<!--LINK '. ($nr-1) ."-->{$trail}"; - } - } - wfProfileOut( __METHOD__ ); - return $retVal; - } - - /** - * Render a forced-blue link inline; protect against double expansion of - * URLs if we're in a mode that prepends full URL prefixes to internal links. - * Since this little disaster has to split off the trail text to avoid - * breaking URLs in the following text without breaking trails on the - * wiki links, it's been made into a horrible function. - * - * @param Title $nt - * @param string $text - * @param string $query - * @param string $trail - * @param string $prefix - * @return string HTML-wikitext mix oh yuck - */ - function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { - list( $inside, $trail ) = Linker::splitTrail( $trail ); - $sk = $this->mOptions->getSkin(); - $link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix ); - return $this->armorLinks( $link ) . $trail; - } - - /** - * Insert a NOPARSE hacky thing into any inline links in a chunk that's - * going to go through further parsing steps before inline URL expansion. - * - * In particular this is important when using action=render, which causes - * full URLs to be included. - * - * Oh man I hate our multi-layer parser! - * - * @param string more-or-less HTML - * @return string less-or-more HTML with NOPARSE bits - */ - function armorLinks( $text ) { - return preg_replace( '/\b(' . wfUrlProtocols() . ')/', - "{$this->mUniqPrefix}NOPARSE$1", $text ); - } - - /** - * Return true if subpage links should be expanded on this page. - * @return bool - */ - function areSubpagesAllowed() { - # Some namespaces don't allow subpages - global $wgNamespacesWithSubpages; - return !empty($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]); - } - - /** - * Handle link to subpage if necessary - * @param string $target the source of the link - * @param string &$text the link text, modified as necessary - * @return string the full name of the link - * @private - */ - function maybeDoSubpageLink($target, &$text) { - # Valid link forms: - # Foobar -- normal - # :Foobar -- override special treatment of prefix (images, language links) - # /Foobar -- convert to CurrentPage/Foobar - # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text - # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage - # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage - - $fname = 'Parser::maybeDoSubpageLink'; - wfProfileIn( $fname ); - $ret = $target; # default return value is no change - - # Some namespaces don't allow subpages, - # so only perform processing if subpages are allowed - if( $this->areSubpagesAllowed() ) { - $hash = strpos( $target, '#' ); - if( $hash !== false ) { - $suffix = substr( $target, $hash ); - $target = substr( $target, 0, $hash ); - } else { - $suffix = ''; - } - # bug 7425 - $target = trim( $target ); - # Look at the first character - if( $target != '' && $target{0} == '/' ) { - # / at end means we don't want the slash to be shown - $m = array(); - $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m ); - if( $trailingSlashes ) { - $noslash = $target = substr( $target, 1, -strlen($m[0][0]) ); - } else { - $noslash = substr( $target, 1 ); - } - - $ret = $this->mTitle->getPrefixedText(). '/' . trim($noslash) . $suffix; - if( '' === $text ) { - $text = $target . $suffix; - } # this might be changed for ugliness reasons - } else { - # check for .. subpage backlinks - $dotdotcount = 0; - $nodotdot = $target; - while( strncmp( $nodotdot, "../", 3 ) == 0 ) { - ++$dotdotcount; - $nodotdot = substr( $nodotdot, 3 ); - } - if($dotdotcount > 0) { - $exploded = explode( '/', $this->mTitle->GetPrefixedText() ); - if( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page - $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) ); - # / at the end means don't show full path - if( substr( $nodotdot, -1, 1 ) == '/' ) { - $nodotdot = substr( $nodotdot, 0, -1 ); - if( '' === $text ) { - $text = $nodotdot . $suffix; - } - } - $nodotdot = trim( $nodotdot ); - if( $nodotdot != '' ) { - $ret .= '/' . $nodotdot; - } - $ret .= $suffix; - } - } - } - } - - wfProfileOut( $fname ); - return $ret; - } - - /**#@+ - * Used by doBlockLevels() - * @private - */ - /* private */ function closeParagraph() { - $result = ''; - if ( '' != $this->mLastSection ) { - $result = '</' . $this->mLastSection . ">\n"; - } - $this->mInPre = false; - $this->mLastSection = ''; - return $result; - } - # getCommon() returns the length of the longest common substring - # of both arguments, starting at the beginning of both. - # - /* private */ function getCommon( $st1, $st2 ) { - $fl = strlen( $st1 ); - $shorter = strlen( $st2 ); - if ( $fl < $shorter ) { $shorter = $fl; } - - for ( $i = 0; $i < $shorter; ++$i ) { - if ( $st1{$i} != $st2{$i} ) { break; } - } - return $i; - } - # These next three functions open, continue, and close the list - # element appropriate to the prefix character passed into them. - # - /* private */ function openList( $char ) { - $result = $this->closeParagraph(); - - if ( '*' == $char ) { $result .= '<ul><li>'; } - else if ( '#' == $char ) { $result .= '<ol><li>'; } - else if ( ':' == $char ) { $result .= '<dl><dd>'; } - else if ( ';' == $char ) { - $result .= '<dl><dt>'; - $this->mDTopen = true; - } - else { $result = '<!-- ERR 1 -->'; } - - return $result; - } - - /* private */ function nextItem( $char ) { - if ( '*' == $char || '#' == $char ) { return '</li><li>'; } - else if ( ':' == $char || ';' == $char ) { - $close = '</dd>'; - if ( $this->mDTopen ) { $close = '</dt>'; } - if ( ';' == $char ) { - $this->mDTopen = true; - return $close . '<dt>'; - } else { - $this->mDTopen = false; - return $close . '<dd>'; - } - } - return '<!-- ERR 2 -->'; - } - - /* private */ function closeList( $char ) { - if ( '*' == $char ) { $text = '</li></ul>'; } - else if ( '#' == $char ) { $text = '</li></ol>'; } - else if ( ':' == $char ) { - if ( $this->mDTopen ) { - $this->mDTopen = false; - $text = '</dt></dl>'; - } else { - $text = '</dd></dl>'; - } - } - else { return '<!-- ERR 3 -->'; } - return $text."\n"; - } - /**#@-*/ - - /** - * Make lists from lines starting with ':', '*', '#', etc. - * - * @private - * @return string the lists rendered as HTML - */ - function doBlockLevels( $text, $linestart ) { - $fname = 'Parser::doBlockLevels'; - wfProfileIn( $fname ); - - # Parsing through the text line by line. The main thing - # happening here is handling of block-level elements p, pre, - # and making lists from lines starting with * # : etc. - # - $textLines = explode( "\n", $text ); - - $lastPrefix = $output = ''; - $this->mDTopen = $inBlockElem = false; - $prefixLength = 0; - $paragraphStack = false; - - if ( !$linestart ) { - $output .= array_shift( $textLines ); - } - foreach ( $textLines as $oLine ) { - $lastPrefixLength = strlen( $lastPrefix ); - $preCloseMatch = preg_match('/<\\/pre/i', $oLine ); - $preOpenMatch = preg_match('/<pre/i', $oLine ); - if ( !$this->mInPre ) { - # Multiple prefixes may abut each other for nested lists. - $prefixLength = strspn( $oLine, '*#:;' ); - $pref = substr( $oLine, 0, $prefixLength ); - - # eh? - $pref2 = str_replace( ';', ':', $pref ); - $t = substr( $oLine, $prefixLength ); - $this->mInPre = !empty($preOpenMatch); - } else { - # Don't interpret any other prefixes in preformatted text - $prefixLength = 0; - $pref = $pref2 = ''; - $t = $oLine; - } - - # List generation - if( $prefixLength && 0 == strcmp( $lastPrefix, $pref2 ) ) { - # Same as the last item, so no need to deal with nesting or opening stuff - $output .= $this->nextItem( substr( $pref, -1 ) ); - $paragraphStack = false; - - if ( substr( $pref, -1 ) == ';') { - # The one nasty exception: definition lists work like this: - # ; title : definition text - # So we check for : in the remainder text to split up the - # title and definition, without b0rking links. - $term = $t2 = ''; - if ($this->findColonNoLinks($t, $term, $t2) !== false) { - $t = $t2; - $output .= $term . $this->nextItem( ':' ); - } - } - } elseif( $prefixLength || $lastPrefixLength ) { - # Either open or close a level... - $commonPrefixLength = $this->getCommon( $pref, $lastPrefix ); - $paragraphStack = false; - - while( $commonPrefixLength < $lastPrefixLength ) { - $output .= $this->closeList( $lastPrefix{$lastPrefixLength-1} ); - --$lastPrefixLength; - } - if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) { - $output .= $this->nextItem( $pref{$commonPrefixLength-1} ); - } - while ( $prefixLength > $commonPrefixLength ) { - $char = substr( $pref, $commonPrefixLength, 1 ); - $output .= $this->openList( $char ); - - if ( ';' == $char ) { - # FIXME: This is dupe of code above - if ($this->findColonNoLinks($t, $term, $t2) !== false) { - $t = $t2; - $output .= $term . $this->nextItem( ':' ); - } - } - ++$commonPrefixLength; - } - $lastPrefix = $pref2; - } - if( 0 == $prefixLength ) { - wfProfileIn( "$fname-paragraph" ); - # No prefix (not in list)--go to paragraph mode - // XXX: use a stack for nestable elements like span, table and div - $openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t ); - $closematch = preg_match( - '/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'. - '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t ); - if ( $openmatch or $closematch ) { - $paragraphStack = false; - # TODO bug 5718: paragraph closed - $output .= $this->closeParagraph(); - if ( $preOpenMatch and !$preCloseMatch ) { - $this->mInPre = true; - } - if ( $closematch ) { - $inBlockElem = false; - } else { - $inBlockElem = true; - } - } else if ( !$inBlockElem && !$this->mInPre ) { - if ( ' ' == $t{0} and ( $this->mLastSection == 'pre' or trim($t) != '' ) ) { - // pre - if ($this->mLastSection != 'pre') { - $paragraphStack = false; - $output .= $this->closeParagraph().'<pre>'; - $this->mLastSection = 'pre'; - } - $t = substr( $t, 1 ); - } else { - // paragraph - if ( '' == trim($t) ) { - if ( $paragraphStack ) { - $output .= $paragraphStack.'<br />'; - $paragraphStack = false; - $this->mLastSection = 'p'; - } else { - if ($this->mLastSection != 'p' ) { - $output .= $this->closeParagraph(); - $this->mLastSection = ''; - $paragraphStack = '<p>'; - } else { - $paragraphStack = '</p><p>'; - } - } - } else { - if ( $paragraphStack ) { - $output .= $paragraphStack; - $paragraphStack = false; - $this->mLastSection = 'p'; - } else if ($this->mLastSection != 'p') { - $output .= $this->closeParagraph().'<p>'; - $this->mLastSection = 'p'; - } - } - } - } - wfProfileOut( "$fname-paragraph" ); - } - // somewhere above we forget to get out of pre block (bug 785) - if($preCloseMatch && $this->mInPre) { - $this->mInPre = false; - } - if ($paragraphStack === false) { - $output .= $t."\n"; - } - } - while ( $prefixLength ) { - $output .= $this->closeList( $pref2{$prefixLength-1} ); - --$prefixLength; - } - if ( '' != $this->mLastSection ) { - $output .= '</' . $this->mLastSection . '>'; - $this->mLastSection = ''; - } - - wfProfileOut( $fname ); - return $output; - } - - /** - * Split up a string on ':', ignoring any occurences inside tags - * to prevent illegal overlapping. - * @param string $str the string to split - * @param string &$before set to everything before the ':' - * @param string &$after set to everything after the ':' - * return string the position of the ':', or false if none found - */ - function findColonNoLinks($str, &$before, &$after) { - $fname = 'Parser::findColonNoLinks'; - wfProfileIn( $fname ); - - $pos = strpos( $str, ':' ); - if( $pos === false ) { - // Nothing to find! - wfProfileOut( $fname ); - return false; - } - - $lt = strpos( $str, '<' ); - if( $lt === false || $lt > $pos ) { - // Easy; no tag nesting to worry about - $before = substr( $str, 0, $pos ); - $after = substr( $str, $pos+1 ); - wfProfileOut( $fname ); - return $pos; - } - - // Ugly state machine to walk through avoiding tags. - $state = self::COLON_STATE_TEXT; - $stack = 0; - $len = strlen( $str ); - for( $i = 0; $i < $len; $i++ ) { - $c = $str{$i}; - - switch( $state ) { - // (Using the number is a performance hack for common cases) - case 0: // self::COLON_STATE_TEXT: - switch( $c ) { - case "<": - // Could be either a <start> tag or an </end> tag - $state = self::COLON_STATE_TAGSTART; - break; - case ":": - if( $stack == 0 ) { - // We found it! - $before = substr( $str, 0, $i ); - $after = substr( $str, $i + 1 ); - wfProfileOut( $fname ); - return $i; - } - // Embedded in a tag; don't break it. - break; - default: - // Skip ahead looking for something interesting - $colon = strpos( $str, ':', $i ); - if( $colon === false ) { - // Nothing else interesting - wfProfileOut( $fname ); - return false; - } - $lt = strpos( $str, '<', $i ); - if( $stack === 0 ) { - if( $lt === false || $colon < $lt ) { - // We found it! - $before = substr( $str, 0, $colon ); - $after = substr( $str, $colon + 1 ); - wfProfileOut( $fname ); - return $i; - } - } - if( $lt === false ) { - // Nothing else interesting to find; abort! - // We're nested, but there's no close tags left. Abort! - break 2; - } - // Skip ahead to next tag start - $i = $lt; - $state = self::COLON_STATE_TAGSTART; - } - break; - case 1: // self::COLON_STATE_TAG: - // In a <tag> - switch( $c ) { - case ">": - $stack++; - $state = self::COLON_STATE_TEXT; - break; - case "/": - // Slash may be followed by >? - $state = self::COLON_STATE_TAGSLASH; - break; - default: - // ignore - } - break; - case 2: // self::COLON_STATE_TAGSTART: - switch( $c ) { - case "/": - $state = self::COLON_STATE_CLOSETAG; - break; - case "!": - $state = self::COLON_STATE_COMMENT; - break; - case ">": - // Illegal early close? This shouldn't happen D: - $state = self::COLON_STATE_TEXT; - break; - default: - $state = self::COLON_STATE_TAG; - } - break; - case 3: // self::COLON_STATE_CLOSETAG: - // In a </tag> - if( $c == ">" ) { - $stack--; - if( $stack < 0 ) { - wfDebug( "Invalid input in $fname; too many close tags\n" ); - wfProfileOut( $fname ); - return false; - } - $state = self::COLON_STATE_TEXT; - } - break; - case self::COLON_STATE_TAGSLASH: - if( $c == ">" ) { - // Yes, a self-closed tag <blah/> - $state = self::COLON_STATE_TEXT; - } else { - // Probably we're jumping the gun, and this is an attribute - $state = self::COLON_STATE_TAG; - } - break; - case 5: // self::COLON_STATE_COMMENT: - if( $c == "-" ) { - $state = self::COLON_STATE_COMMENTDASH; - } - break; - case self::COLON_STATE_COMMENTDASH: - if( $c == "-" ) { - $state = self::COLON_STATE_COMMENTDASHDASH; - } else { - $state = self::COLON_STATE_COMMENT; - } - break; - case self::COLON_STATE_COMMENTDASHDASH: - if( $c == ">" ) { - $state = self::COLON_STATE_TEXT; - } else { - $state = self::COLON_STATE_COMMENT; - } - break; - default: - throw new MWException( "State machine error in $fname" ); - } - } - if( $stack > 0 ) { - wfDebug( "Invalid input in $fname; not enough close tags (stack $stack, state $state)\n" ); - return false; - } - wfProfileOut( $fname ); - return false; - } - - /** - * Return value of a magic variable (like PAGENAME) - * - * @private - */ - function getVariableValue( $index ) { - global $wgContLang, $wgSitename, $wgServer, $wgServerName, $wgScriptPath; - - /** - * Some of these require message or data lookups and can be - * expensive to check many times. - */ - static $varCache = array(); - if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$varCache ) ) ) { - if ( isset( $varCache[$index] ) ) { - return $varCache[$index]; - } - } - - $ts = time(); - wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) ); - - # Use the time zone - global $wgLocaltimezone; - if ( isset( $wgLocaltimezone ) ) { - $oldtz = getenv( 'TZ' ); - putenv( 'TZ='.$wgLocaltimezone ); - } - - wfSuppressWarnings(); // E_STRICT system time bitching - $localTimestamp = date( 'YmdHis', $ts ); - $localMonth = date( 'm', $ts ); - $localMonthName = date( 'n', $ts ); - $localDay = date( 'j', $ts ); - $localDay2 = date( 'd', $ts ); - $localDayOfWeek = date( 'w', $ts ); - $localWeek = date( 'W', $ts ); - $localYear = date( 'Y', $ts ); - $localHour = date( 'H', $ts ); - if ( isset( $wgLocaltimezone ) ) { - putenv( 'TZ='.$oldtz ); - } - wfRestoreWarnings(); - - switch ( $index ) { - case 'currentmonth': - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'm', $ts ) ); - case 'currentmonthname': - return $varCache[$index] = $wgContLang->getMonthName( gmdate( 'n', $ts ) ); - case 'currentmonthnamegen': - return $varCache[$index] = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) ); - case 'currentmonthabbrev': - return $varCache[$index] = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) ); - case 'currentday': - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'j', $ts ) ); - case 'currentday2': - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'd', $ts ) ); - case 'localmonth': - return $varCache[$index] = $wgContLang->formatNum( $localMonth ); - case 'localmonthname': - return $varCache[$index] = $wgContLang->getMonthName( $localMonthName ); - case 'localmonthnamegen': - return $varCache[$index] = $wgContLang->getMonthNameGen( $localMonthName ); - case 'localmonthabbrev': - return $varCache[$index] = $wgContLang->getMonthAbbreviation( $localMonthName ); - case 'localday': - return $varCache[$index] = $wgContLang->formatNum( $localDay ); - case 'localday2': - return $varCache[$index] = $wgContLang->formatNum( $localDay2 ); - case 'pagename': - return wfEscapeWikiText( $this->mTitle->getText() ); - case 'pagenamee': - return $this->mTitle->getPartialURL(); - case 'fullpagename': - return wfEscapeWikiText( $this->mTitle->getPrefixedText() ); - case 'fullpagenamee': - return $this->mTitle->getPrefixedURL(); - case 'subpagename': - return wfEscapeWikiText( $this->mTitle->getSubpageText() ); - case 'subpagenamee': - return $this->mTitle->getSubpageUrlForm(); - case 'basepagename': - return wfEscapeWikiText( $this->mTitle->getBaseText() ); - case 'basepagenamee': - return wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) ); - case 'talkpagename': - if( $this->mTitle->canTalk() ) { - $talkPage = $this->mTitle->getTalkPage(); - return wfEscapeWikiText( $talkPage->getPrefixedText() ); - } else { - return ''; - } - case 'talkpagenamee': - if( $this->mTitle->canTalk() ) { - $talkPage = $this->mTitle->getTalkPage(); - return $talkPage->getPrefixedUrl(); - } else { - return ''; - } - case 'subjectpagename': - $subjPage = $this->mTitle->getSubjectPage(); - return wfEscapeWikiText( $subjPage->getPrefixedText() ); - case 'subjectpagenamee': - $subjPage = $this->mTitle->getSubjectPage(); - return $subjPage->getPrefixedUrl(); - case 'revisionid': - return $this->mRevisionId; - case 'revisionday': - return intval( substr( $this->getRevisionTimestamp(), 6, 2 ) ); - case 'revisionday2': - return substr( $this->getRevisionTimestamp(), 6, 2 ); - case 'revisionmonth': - return intval( substr( $this->getRevisionTimestamp(), 4, 2 ) ); - case 'revisionyear': - return substr( $this->getRevisionTimestamp(), 0, 4 ); - case 'revisiontimestamp': - return $this->getRevisionTimestamp(); - case 'namespace': - return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) ); - case 'namespacee': - return wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) ); - case 'talkspace': - return $this->mTitle->canTalk() ? str_replace('_',' ',$this->mTitle->getTalkNsText()) : ''; - case 'talkspacee': - return $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : ''; - case 'subjectspace': - return $this->mTitle->getSubjectNsText(); - case 'subjectspacee': - return( wfUrlencode( $this->mTitle->getSubjectNsText() ) ); - case 'currentdayname': - return $varCache[$index] = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 ); - case 'currentyear': - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'Y', $ts ), true ); - case 'currenttime': - return $varCache[$index] = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false ); - case 'currenthour': - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'H', $ts ), true ); - case 'currentweek': - // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to - // int to remove the padding - return $varCache[$index] = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) ); - case 'currentdow': - return $varCache[$index] = $wgContLang->formatNum( gmdate( 'w', $ts ) ); - case 'localdayname': - return $varCache[$index] = $wgContLang->getWeekdayName( $localDayOfWeek + 1 ); - case 'localyear': - return $varCache[$index] = $wgContLang->formatNum( $localYear, true ); - case 'localtime': - return $varCache[$index] = $wgContLang->time( $localTimestamp, false, false ); - case 'localhour': - return $varCache[$index] = $wgContLang->formatNum( $localHour, true ); - case 'localweek': - // @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to - // int to remove the padding - return $varCache[$index] = $wgContLang->formatNum( (int)$localWeek ); - case 'localdow': - return $varCache[$index] = $wgContLang->formatNum( $localDayOfWeek ); - case 'numberofarticles': - return $varCache[$index] = $wgContLang->formatNum( SiteStats::articles() ); - case 'numberoffiles': - return $varCache[$index] = $wgContLang->formatNum( SiteStats::images() ); - case 'numberofusers': - return $varCache[$index] = $wgContLang->formatNum( SiteStats::users() ); - case 'numberofpages': - return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() ); - case 'numberofadmins': - return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() ); - case 'numberofedits': - return $varCache[$index] = $wgContLang->formatNum( SiteStats::edits() ); - case 'currenttimestamp': - return $varCache[$index] = wfTimestampNow(); - case 'localtimestamp': - return $varCache[$index] = $localTimestamp; - case 'currentversion': - return $varCache[$index] = SpecialVersion::getVersion(); - case 'sitename': - return $wgSitename; - case 'server': - return $wgServer; - case 'servername': - return $wgServerName; - case 'scriptpath': - return $wgScriptPath; - case 'directionmark': - return $wgContLang->getDirMark(); - case 'contentlanguage': - global $wgContLanguageCode; - return $wgContLanguageCode; - default: - $ret = null; - if ( wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$varCache, &$index, &$ret ) ) ) - return $ret; - else - return null; - } - } - - /** - * initialise the magic variables (like CURRENTMONTHNAME) - * - * @private - */ - function initialiseVariables() { - $fname = 'Parser::initialiseVariables'; - wfProfileIn( $fname ); - $variableIDs = MagicWord::getVariableIDs(); - - $this->mVariables = array(); - foreach ( $variableIDs as $id ) { - $mw =& MagicWord::get( $id ); - $mw->addToArray( $this->mVariables, $id ); - } - wfProfileOut( $fname ); - } - - /** - * parse any parentheses in format ((title|part|part)) - * and call callbacks to get a replacement text for any found piece - * - * @param string $text The text to parse - * @param array $callbacks rules in form: - * '{' => array( # opening parentheses - * 'end' => '}', # closing parentheses - * 'cb' => array(2 => callback, # replacement callback to call if {{..}} is found - * 3 => callback # replacement callback to call if {{{..}}} is found - * ) - * ) - * 'min' => 2, # Minimum parenthesis count in cb - * 'max' => 3, # Maximum parenthesis count in cb - * @private - */ - function replace_callback ($text, $callbacks) { - wfProfileIn( __METHOD__ ); - $openingBraceStack = array(); # this array will hold a stack of parentheses which are not closed yet - $lastOpeningBrace = -1; # last not closed parentheses - - $validOpeningBraces = implode( '', array_keys( $callbacks ) ); - - $i = 0; - while ( $i < strlen( $text ) ) { - # Find next opening brace, closing brace or pipe - if ( $lastOpeningBrace == -1 ) { - $currentClosing = ''; - $search = $validOpeningBraces; - } else { - $currentClosing = $openingBraceStack[$lastOpeningBrace]['braceEnd']; - $search = $validOpeningBraces . '|' . $currentClosing; - } - $rule = null; - $i += strcspn( $text, $search, $i ); - if ( $i < strlen( $text ) ) { - if ( $text[$i] == '|' ) { - $found = 'pipe'; - } elseif ( $text[$i] == $currentClosing ) { - $found = 'close'; - } elseif ( isset( $callbacks[$text[$i]] ) ) { - $found = 'open'; - $rule = $callbacks[$text[$i]]; - } else { - # Some versions of PHP have a strcspn which stops on null characters - # Ignore and continue - ++$i; - continue; - } - } else { - # All done - break; - } - - if ( $found == 'open' ) { - # found opening brace, let's add it to parentheses stack - $piece = array('brace' => $text[$i], - 'braceEnd' => $rule['end'], - 'title' => '', - 'parts' => null); - - # count opening brace characters - $piece['count'] = strspn( $text, $piece['brace'], $i ); - $piece['startAt'] = $piece['partStart'] = $i + $piece['count']; - $i += $piece['count']; - - # we need to add to stack only if opening brace count is enough for one of the rules - if ( $piece['count'] >= $rule['min'] ) { - $lastOpeningBrace ++; - $openingBraceStack[$lastOpeningBrace] = $piece; - } - } elseif ( $found == 'close' ) { - # lets check if it is enough characters for closing brace - $maxCount = $openingBraceStack[$lastOpeningBrace]['count']; - $count = strspn( $text, $text[$i], $i, $maxCount ); - - # check for maximum matching characters (if there are 5 closing - # characters, we will probably need only 3 - depending on the rules) - $matchingCount = 0; - $matchingCallback = null; - $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace]['brace']]; - if ( $count > $cbType['max'] ) { - # The specified maximum exists in the callback array, unless the caller - # has made an error - $matchingCount = $cbType['max']; - } else { - # Count is less than the maximum - # Skip any gaps in the callback array to find the true largest match - # Need to use array_key_exists not isset because the callback can be null - $matchingCount = $count; - while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $cbType['cb'] ) ) { - --$matchingCount; - } - } - - if ($matchingCount <= 0) { - $i += $count; - continue; - } - $matchingCallback = $cbType['cb'][$matchingCount]; - - # let's set a title or last part (if '|' was found) - if (null === $openingBraceStack[$lastOpeningBrace]['parts']) { - $openingBraceStack[$lastOpeningBrace]['title'] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], - $i - $openingBraceStack[$lastOpeningBrace]['partStart']); - } else { - $openingBraceStack[$lastOpeningBrace]['parts'][] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], - $i - $openingBraceStack[$lastOpeningBrace]['partStart']); - } - - $pieceStart = $openingBraceStack[$lastOpeningBrace]['startAt'] - $matchingCount; - $pieceEnd = $i + $matchingCount; - - if( is_callable( $matchingCallback ) ) { - $cbArgs = array ( - 'text' => substr($text, $pieceStart, $pieceEnd - $pieceStart), - 'title' => trim($openingBraceStack[$lastOpeningBrace]['title']), - 'parts' => $openingBraceStack[$lastOpeningBrace]['parts'], - 'lineStart' => (($pieceStart > 0) && ($text[$pieceStart-1] == "\n")), - ); - # finally we can call a user callback and replace piece of text - $replaceWith = call_user_func( $matchingCallback, $cbArgs ); - $text = substr($text, 0, $pieceStart) . $replaceWith . substr($text, $pieceEnd); - $i = $pieceStart + strlen($replaceWith); - } else { - # null value for callback means that parentheses should be parsed, but not replaced - $i += $matchingCount; - } - - # reset last opening parentheses, but keep it in case there are unused characters - $piece = array('brace' => $openingBraceStack[$lastOpeningBrace]['brace'], - 'braceEnd' => $openingBraceStack[$lastOpeningBrace]['braceEnd'], - 'count' => $openingBraceStack[$lastOpeningBrace]['count'], - 'title' => '', - 'parts' => null, - 'startAt' => $openingBraceStack[$lastOpeningBrace]['startAt']); - $openingBraceStack[$lastOpeningBrace--] = null; - - if ($matchingCount < $piece['count']) { - $piece['count'] -= $matchingCount; - $piece['startAt'] -= $matchingCount; - $piece['partStart'] = $piece['startAt']; - # do we still qualify for any callback with remaining count? - $currentCbList = $callbacks[$piece['brace']]['cb']; - while ( $piece['count'] ) { - if ( array_key_exists( $piece['count'], $currentCbList ) ) { - $lastOpeningBrace++; - $openingBraceStack[$lastOpeningBrace] = $piece; - break; - } - --$piece['count']; - } - } - } elseif ( $found == 'pipe' ) { - # lets set a title if it is a first separator, or next part otherwise - if (null === $openingBraceStack[$lastOpeningBrace]['parts']) { - $openingBraceStack[$lastOpeningBrace]['title'] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], - $i - $openingBraceStack[$lastOpeningBrace]['partStart']); - $openingBraceStack[$lastOpeningBrace]['parts'] = array(); - } else { - $openingBraceStack[$lastOpeningBrace]['parts'][] = - substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'], - $i - $openingBraceStack[$lastOpeningBrace]['partStart']); - } - $openingBraceStack[$lastOpeningBrace]['partStart'] = ++$i; - } - } - - wfProfileOut( __METHOD__ ); - return $text; - } - - /** - * Replace magic variables, templates, and template arguments - * with the appropriate text. Templates are substituted recursively, - * taking care to avoid infinite loops. - * - * Note that the substitution depends on value of $mOutputType: - * self::OT_WIKI: only {{subst:}} templates - * self::OT_MSG: only magic variables - * self::OT_HTML: all templates and magic variables - * - * @param string $tex The text to transform - * @param array $args Key-value pairs representing template parameters to substitute - * @param bool $argsOnly Only do argument (triple-brace) expansion, not double-brace expansion - * @private - */ - function replaceVariables( $text, $args = array(), $argsOnly = false ) { - # Prevent too big inclusions - if( strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) { - return $text; - } - - $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/; - wfProfileIn( $fname ); - - # This function is called recursively. To keep track of arguments we need a stack: - array_push( $this->mArgStack, $args ); - - $braceCallbacks = array(); - if ( !$argsOnly ) { - $braceCallbacks[2] = array( &$this, 'braceSubstitution' ); - } - if ( $this->mOutputType != self::OT_MSG ) { - $braceCallbacks[3] = array( &$this, 'argSubstitution' ); - } - if ( $braceCallbacks ) { - $callbacks = array( - '{' => array( - 'end' => '}', - 'cb' => $braceCallbacks, - 'min' => $argsOnly ? 3 : 2, - 'max' => isset( $braceCallbacks[3] ) ? 3 : 2, - ), - '[' => array( - 'end' => ']', - 'cb' => array(2=>null), - 'min' => 2, - 'max' => 2, - ) - ); - $text = $this->replace_callback ($text, $callbacks); - - array_pop( $this->mArgStack ); - } - wfProfileOut( $fname ); - return $text; - } - - /** - * Replace magic variables - * @private - */ - function variableSubstitution( $matches ) { - global $wgContLang; - $fname = 'Parser::variableSubstitution'; - $varname = $wgContLang->lc($matches[1]); - wfProfileIn( $fname ); - $skip = false; - if ( $this->mOutputType == self::OT_WIKI ) { - # Do only magic variables prefixed by SUBST - $mwSubst =& MagicWord::get( 'subst' ); - if (!$mwSubst->matchStartAndRemove( $varname )) - $skip = true; - # Note that if we don't substitute the variable below, - # we don't remove the {{subst:}} magic word, in case - # it is a template rather than a magic variable. - } - if ( !$skip && array_key_exists( $varname, $this->mVariables ) ) { - $id = $this->mVariables[$varname]; - # Now check if we did really match, case sensitive or not - $mw =& MagicWord::get( $id ); - if ($mw->match($matches[1])) { - $text = $this->getVariableValue( $id ); - if (MagicWord::getCacheTTL($id)>-1) - $this->mOutput->mContainsOldMagic = true; - } else { - $text = $matches[0]; - } - } else { - $text = $matches[0]; - } - wfProfileOut( $fname ); - return $text; - } - - - /// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. - static function createAssocArgs( $args ) { - $assocArgs = array(); - $index = 1; - foreach( $args as $arg ) { - $eqpos = strpos( $arg, '=' ); - if ( $eqpos === false ) { - $assocArgs[$index++] = $arg; - } else { - $name = trim( substr( $arg, 0, $eqpos ) ); - $value = trim( substr( $arg, $eqpos+1 ) ); - if ( $value === false ) { - $value = ''; - } - if ( $name !== false ) { - $assocArgs[$name] = $value; - } - } - } - - return $assocArgs; - } - - /** - * Return the text of a template, after recursively - * replacing any variables or templates within the template. - * - * @param array $piece The parts of the template - * $piece['text']: matched text - * $piece['title']: the title, i.e. the part before the | - * $piece['parts']: the parameter array - * @return string the text of the template - * @private - */ - function braceSubstitution( $piece ) { - global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces; - $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/; - wfProfileIn( $fname ); - wfProfileIn( __METHOD__.'-setup' ); - - # Flags - $found = false; # $text has been filled - $nowiki = false; # wiki markup in $text should be escaped - $noparse = false; # Unsafe HTML tags should not be stripped, etc. - $noargs = false; # Don't replace triple-brace arguments in $text - $replaceHeadings = false; # Make the edit section links go to the template not the article - $headingOffset = 0; # Skip headings when number, to account for those that weren't transcluded. - $isHTML = false; # $text is HTML, armour it against wikitext transformation - $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered - - # Title object, where $text came from - $title = NULL; - - $linestart = ''; - - - # $part1 is the bit before the first |, and must contain only title characters - # $args is a list of arguments, starting from index 0, not including $part1 - - $titleText = $part1 = $piece['title']; - # If the third subpattern matched anything, it will start with | - - if (null == $piece['parts']) { - $replaceWith = $this->variableSubstitution (array ($piece['text'], $piece['title'])); - if ($replaceWith != $piece['text']) { - $text = $replaceWith; - $found = true; - $noparse = true; - $noargs = true; - } - } - - $args = (null == $piece['parts']) ? array() : $piece['parts']; - wfProfileOut( __METHOD__.'-setup' ); - - # SUBST - wfProfileIn( __METHOD__.'-modifiers' ); - if ( !$found ) { - $mwSubst =& MagicWord::get( 'subst' ); - if ( $mwSubst->matchStartAndRemove( $part1 ) xor $this->ot['wiki'] ) { - # One of two possibilities is true: - # 1) Found SUBST but not in the PST phase - # 2) Didn't find SUBST and in the PST phase - # In either case, return without further processing - $text = $piece['text']; - $found = true; - $noparse = true; - $noargs = true; - } - } - - # MSG, MSGNW and RAW - if ( !$found ) { - # Check for MSGNW: - $mwMsgnw =& MagicWord::get( 'msgnw' ); - if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) { - $nowiki = true; - } else { - # Remove obsolete MSG: - $mwMsg =& MagicWord::get( 'msg' ); - $mwMsg->matchStartAndRemove( $part1 ); - } - - # Check for RAW: - $mwRaw =& MagicWord::get( 'raw' ); - if ( $mwRaw->matchStartAndRemove( $part1 ) ) { - $forceRawInterwiki = true; - } - } - wfProfileOut( __METHOD__.'-modifiers' ); - - //save path level before recursing into functions & templates. - $lastPathLevel = $this->mTemplatePath; - - # Parser functions - if ( !$found ) { - wfProfileIn( __METHOD__ . '-pfunc' ); - - $colonPos = strpos( $part1, ':' ); - if ( $colonPos !== false ) { - # Case sensitive functions - $function = substr( $part1, 0, $colonPos ); - if ( isset( $this->mFunctionSynonyms[1][$function] ) ) { - $function = $this->mFunctionSynonyms[1][$function]; - } else { - # Case insensitive functions - $function = strtolower( $function ); - if ( isset( $this->mFunctionSynonyms[0][$function] ) ) { - $function = $this->mFunctionSynonyms[0][$function]; - } else { - $function = false; - } - } - if ( $function ) { - $funcArgs = array_map( 'trim', $args ); - $funcArgs = array_merge( array( &$this, trim( substr( $part1, $colonPos + 1 ) ) ), $funcArgs ); - $result = call_user_func_array( $this->mFunctionHooks[$function], $funcArgs ); - $found = true; - - // The text is usually already parsed, doesn't need triple-brace tags expanded, etc. - //$noargs = true; - //$noparse = true; - - if ( is_array( $result ) ) { - if ( isset( $result[0] ) ) { - $text = $linestart . $result[0]; - unset( $result[0] ); - } - - // Extract flags into the local scope - // This allows callers to set flags such as nowiki, noparse, found, etc. - extract( $result ); - } else { - $text = $linestart . $result; - } - } - } - wfProfileOut( __METHOD__ . '-pfunc' ); - } - - # Template table test - - # Did we encounter this template already? If yes, it is in the cache - # and we need to check for loops. - if ( !$found && isset( $this->mTemplates[$piece['title']] ) ) { - $found = true; - - # Infinite loop test - if ( isset( $this->mTemplatePath[$part1] ) ) { - $noparse = true; - $noargs = true; - $found = true; - $text = $linestart . - "[[$part1]]<!-- WARNING: template loop detected -->"; - wfDebug( __METHOD__.": template loop broken at '$part1'\n" ); - } else { - # set $text to cached message. - $text = $linestart . $this->mTemplates[$piece['title']]; - #treat title for cached page the same as others - $ns = NS_TEMPLATE; - $subpage = ''; - $part1 = $this->maybeDoSubpageLink( $part1, $subpage ); - if ($subpage !== '') { - $ns = $this->mTitle->getNamespace(); - } - $title = Title::newFromText( $part1, $ns ); - //used by include size checking - $titleText = $title->getPrefixedText(); - //used by edit section links - $replaceHeadings = true; - - } - } - - # Load from database - if ( !$found ) { - wfProfileIn( __METHOD__ . '-loadtpl' ); - $ns = NS_TEMPLATE; - # declaring $subpage directly in the function call - # does not work correctly with references and breaks - # {{/subpage}}-style inclusions - $subpage = ''; - $part1 = $this->maybeDoSubpageLink( $part1, $subpage ); - if ($subpage !== '') { - $ns = $this->mTitle->getNamespace(); - } - $title = Title::newFromText( $part1, $ns ); - - - if ( !is_null( $title ) ) { - $titleText = $title->getPrefixedText(); - # Check for language variants if the template is not found - if($wgContLang->hasVariants() && $title->getArticleID() == 0){ - $wgContLang->findVariantLink($part1, $title); - } - - if ( !$title->isExternal() ) { - if ( $title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) { - $text = SpecialPage::capturePath( $title ); - if ( is_string( $text ) ) { - $found = true; - $noparse = true; - $noargs = true; - $isHTML = true; - $this->disableCache(); - } - } else if ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) { - $found = false; //access denied - wfDebug( "$fname: template inclusion denied for " . $title->getPrefixedDBkey() ); - } else { - list($articleContent,$title) = $this->fetchTemplateAndtitle( $title ); - if ( $articleContent !== false ) { - $found = true; - $text = $articleContent; - $replaceHeadings = true; - } - } - - # If the title is valid but undisplayable, make a link to it - if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) { - $text = "[[:$titleText]]"; - $found = true; - } - } elseif ( $title->isTrans() ) { - // Interwiki transclusion - if ( $this->ot['html'] && !$forceRawInterwiki ) { - $text = $this->interwikiTransclude( $title, 'render' ); - $isHTML = true; - $noparse = true; - } else { - $text = $this->interwikiTransclude( $title, 'raw' ); - $replaceHeadings = true; - } - $found = true; - } - - # Template cache array insertion - # Use the original $piece['title'] not the mangled $part1, so that - # modifiers such as RAW: produce separate cache entries - if( $found ) { - if( $isHTML ) { - // A special page; don't store it in the template cache. - } else { - $this->mTemplates[$piece['title']] = $text; - } - $text = $linestart . $text; - } - } - wfProfileOut( __METHOD__ . '-loadtpl' ); - } - - if ( $found && !$this->incrementIncludeSize( 'pre-expand', strlen( $text ) ) ) { - # Error, oversize inclusion - $text = $linestart . - "[[$titleText]]<!-- WARNING: template omitted, pre-expand include size too large -->"; - $noparse = true; - $noargs = true; - } - - # Recursive parsing, escaping and link table handling - # Only for HTML output - if ( $nowiki && $found && ( $this->ot['html'] || $this->ot['pre'] ) ) { - $text = wfEscapeWikiText( $text ); - } elseif ( !$this->ot['msg'] && $found ) { - if ( $noargs ) { - $assocArgs = array(); - } else { - # Clean up argument array - $assocArgs = self::createAssocArgs($args); - # Add a new element to the templace recursion path - $this->mTemplatePath[$part1] = 1; - } - - if ( !$noparse ) { - # If there are any <onlyinclude> tags, only include them - if ( in_string( '<onlyinclude>', $text ) && in_string( '</onlyinclude>', $text ) ) { - $replacer = new OnlyIncludeReplacer; - StringUtils::delimiterReplaceCallback( '<onlyinclude>', '</onlyinclude>', - array( &$replacer, 'replace' ), $text ); - $text = $replacer->output; - } - # Remove <noinclude> sections and <includeonly> tags - $text = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $text ); - $text = strtr( $text, array( '<includeonly>' => '' , '</includeonly>' => '' ) ); - - if( $this->ot['html'] || $this->ot['pre'] ) { - # Strip <nowiki>, <pre>, etc. - $text = $this->strip( $text, $this->mStripState ); - if ( $this->ot['html'] ) { - $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'replaceVariables' ), $assocArgs ); - } elseif ( $this->ot['pre'] && $this->mOptions->getRemoveComments() ) { - $text = Sanitizer::removeHTMLcomments( $text ); - } - } - $text = $this->replaceVariables( $text, $assocArgs ); - - # If the template begins with a table or block-level - # element, it should be treated as beginning a new line. - if (!$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{ - $text = "\n" . $text; - } - } elseif ( !$noargs ) { - # $noparse and !$noargs - # Just replace the arguments, not any double-brace items - # This is used for rendered interwiki transclusion - $text = $this->replaceVariables( $text, $assocArgs, true ); - } - } - # Prune lower levels off the recursion check path - $this->mTemplatePath = $lastPathLevel; - - if ( $found && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) { - # Error, oversize inclusion - $text = $linestart . - "[[$titleText]]<!-- WARNING: template omitted, post-expand include size too large -->"; - $noparse = true; - $noargs = true; - } - - if ( !$found ) { - wfProfileOut( $fname ); - return $piece['text']; - } else { - wfProfileIn( __METHOD__ . '-placeholders' ); - if ( $isHTML ) { - # Replace raw HTML by a placeholder - # Add a blank line preceding, to prevent it from mucking up - # immediately preceding headings - $text = "\n\n" . $this->insertStripItem( $text, $this->mStripState ); - } else { - # replace ==section headers== - # XXX this needs to go away once we have a better parser. - if ( !$this->ot['wiki'] && !$this->ot['pre'] && $replaceHeadings ) { - if( !is_null( $title ) ) - $encodedname = base64_encode($title->getPrefixedDBkey()); - else - $encodedname = base64_encode(""); - $m = preg_split('/(^={1,6}.*?={1,6}\s*?$)/m', $text, -1, - PREG_SPLIT_DELIM_CAPTURE); - $text = ''; - $nsec = $headingOffset; - - for( $i = 0; $i < count($m); $i += 2 ) { - $text .= $m[$i]; - if (!isset($m[$i + 1]) || $m[$i + 1] == "") continue; - $hl = $m[$i + 1]; - if( strstr($hl, "<!--MWTEMPLATESECTION") ) { - $text .= $hl; - continue; - } - $m2 = array(); - preg_match('/^(={1,6})(.*?)(={1,6}\s*?)$/m', $hl, $m2); - $text .= $m2[1] . $m2[2] . "<!--MWTEMPLATESECTION=" - . $encodedname . "&" . base64_encode("$nsec") . "-->" . $m2[3]; - - $nsec++; - } - } - } - wfProfileOut( __METHOD__ . '-placeholders' ); - } - - # Prune lower levels off the recursion check path - $this->mTemplatePath = $lastPathLevel; - - if ( !$found ) { - wfProfileOut( $fname ); - return $piece['text']; - } else { - wfProfileOut( $fname ); - return $text; - } - } - - /** - * Fetch the unparsed text of a template and register a reference to it. - */ - function fetchTemplateAndTitle( $title ) { - $templateCb = $this->mOptions->getTemplateCallback(); - $stuff = call_user_func( $templateCb, $title ); - $text = $stuff['text']; - $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title; - if ( isset( $stuff['deps'] ) ) { - foreach ( $stuff['deps'] as $dep ) { - $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] ); - } - } - return array($text,$finalTitle); - } - - function fetchTemplate( $title ) { - $rv = $this->fetchTemplateAndtitle($title); - return $rv[0]; - } - - /** - * Static function to get a template - * Can be overridden via ParserOptions::setTemplateCallback(). - * - * Returns an associative array: - * text The unparsed template text - * finalTitle (Optional) The title after following redirects - * deps (Optional) An array of associative array dependencies: - * title: The dependency title, to be registered in templatelinks - * page_id: The page_id of the title - * rev_id: The revision ID loaded - */ - static function statelessFetchTemplate( $title ) { - $text = $skip = false; - $finalTitle = $title; - $deps = array(); - - // Loop to fetch the article, with up to 1 redirect - for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) { - # Give extensions a chance to select the revision instead - $id = false; // Assume current - wfRunHooks( 'BeforeParserFetchTemplateAndtitle', array( false, &$title, &$skip, &$id ) ); - - if( $skip ) { - $text = false; - $deps[] = array( - 'title' => $title, - 'page_id' => $title->getArticleID(), - 'rev_id' => null ); - break; - } - $rev = $id ? Revision::newFromId( $id ) : Revision::newFromTitle( $title ); - $rev_id = $rev ? $rev->getId() : 0; - - $deps[] = array( - 'title' => $title, - 'page_id' => $title->getArticleID(), - 'rev_id' => $rev_id ); - - if( $rev ) { - $text = $rev->getText(); - } elseif( $title->getNamespace() == NS_MEDIAWIKI ) { - global $wgLang; - $message = $wgLang->lcfirst( $title->getText() ); - $text = wfMsgForContentNoTrans( $message ); - if( wfEmptyMsg( $message, $text ) ) { - $text = false; - break; - } - } else { - break; - } - if ( $text === false ) { - break; - } - // Redirect? - $finalTitle = $title; - $title = Title::newFromRedirect( $text ); - } - return array( - 'text' => $text, - 'finalTitle' => $finalTitle, - 'deps' => $deps ); - } - - /** - * Transclude an interwiki link. - */ - function interwikiTransclude( $title, $action ) { - global $wgEnableScaryTranscluding; - - if (!$wgEnableScaryTranscluding) - return wfMsg('scarytranscludedisabled'); - - $url = $title->getFullUrl( "action=$action" ); - - if (strlen($url) > 255) - return wfMsg('scarytranscludetoolong'); - return $this->fetchScaryTemplateMaybeFromCache($url); - } - - function fetchScaryTemplateMaybeFromCache($url) { - global $wgTranscludeCacheExpiry; - $dbr = wfGetDB(DB_SLAVE); - $obj = $dbr->selectRow('transcache', array('tc_time', 'tc_contents'), - array('tc_url' => $url)); - if ($obj) { - $time = $obj->tc_time; - $text = $obj->tc_contents; - if ($time && time() < $time + $wgTranscludeCacheExpiry ) { - return $text; - } - } - - $text = Http::get($url); - if (!$text) - return wfMsg('scarytranscludefailed', $url); - - $dbw = wfGetDB(DB_MASTER); - $dbw->replace('transcache', array('tc_url'), array( - 'tc_url' => $url, - 'tc_time' => time(), - 'tc_contents' => $text)); - return $text; - } - - - /** - * Triple brace replacement -- used for template arguments - * @private - */ - function argSubstitution( $matches ) { - $arg = trim( $matches['title'] ); - $text = $matches['text']; - $inputArgs = end( $this->mArgStack ); - - if ( array_key_exists( $arg, $inputArgs ) ) { - $text = $inputArgs[$arg]; - } else if (($this->mOutputType == self::OT_HTML || $this->mOutputType == self::OT_PREPROCESS ) && - null != $matches['parts'] && count($matches['parts']) > 0) { - $text = $matches['parts'][0]; - } - if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) { - $text = $matches['text'] . - '<!-- WARNING: argument omitted, expansion size too large -->'; - } - - return $text; - } - - /** - * Increment an include size counter - * - * @param string $type The type of expansion - * @param integer $size The size of the text - * @return boolean False if this inclusion would take it over the maximum, true otherwise - */ - function incrementIncludeSize( $type, $size ) { - if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) { - return false; - } else { - $this->mIncludeSizes[$type] += $size; - return true; - } - } - - /** - * Detect __NOGALLERY__ magic word and set a placeholder - */ - function stripNoGallery( &$text ) { - # if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML, - # do not add TOC - $mw = MagicWord::get( 'nogallery' ); - $this->mOutput->mNoGallery = $mw->matchAndRemove( $text ) ; - } - - /** - * Find the first __TOC__ magic word and set a <!--MWTOC--> - * placeholder that will then be replaced by the real TOC in - * ->formatHeadings, this works because at this points real - * comments will have already been discarded by the sanitizer. - * - * Any additional __TOC__ magic words left over will be discarded - * as there can only be one TOC on the page. - */ - function stripToc( $text ) { - # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, - # do not add TOC - $mw = MagicWord::get( 'notoc' ); - if( $mw->matchAndRemove( $text ) ) { - $this->mShowToc = false; - } - - $mw = MagicWord::get( 'toc' ); - if( $mw->match( $text ) ) { - $this->mShowToc = true; - $this->mForceTocPosition = true; - - // Set a placeholder. At the end we'll fill it in with the TOC. - $text = $mw->replace( '<!--MWTOC-->', $text, 1 ); - - // Only keep the first one. - $text = $mw->replace( '', $text ); - } - return $text; - } - - /** - * This function accomplishes several tasks: - * 1) Auto-number headings if that option is enabled - * 2) Add an [edit] link to sections for users who have enabled the option and can edit the page - * 3) Add a Table of contents on the top for users who have enabled the option - * 4) Auto-anchor headings - * - * It loops through all headlines, collects the necessary data, then splits up the - * string and re-inserts the newly formatted headlines. - * - * @param string $text - * @param boolean $isMain - * @private - */ - function formatHeadings( $text, $isMain=true ) { - global $wgMaxTocLevel, $wgContLang; - - $doNumberHeadings = $this->mOptions->getNumberHeadings(); - if( !$this->mTitle->quickUserCan( 'edit' ) ) { - $showEditLink = 0; - } else { - $showEditLink = $this->mOptions->getEditSection(); - } - - # Inhibit editsection links if requested in the page - $esw =& MagicWord::get( 'noeditsection' ); - if( $esw->matchAndRemove( $text ) ) { - $showEditLink = 0; - } - - # Get all headlines for numbering them and adding funky stuff like [edit] - # links - this is for later, but we need the number of headlines right now - $matches = array(); - $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?'.'>)(?P<header>.*?)<\/H[1-6] *>/i', $text, $matches ); - - # if there are fewer than 4 headlines in the article, do not show TOC - # unless it's been explicitly enabled. - $enoughToc = $this->mShowToc && - (($numMatches >= 4) || $this->mForceTocPosition); - - # Allow user to stipulate that a page should have a "new section" - # link added via __NEWSECTIONLINK__ - $mw =& MagicWord::get( 'newsectionlink' ); - if( $mw->matchAndRemove( $text ) ) - $this->mOutput->setNewSection( true ); - - # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, - # override above conditions and always show TOC above first header - $mw =& MagicWord::get( 'forcetoc' ); - if ($mw->matchAndRemove( $text ) ) { - $this->mShowToc = true; - $enoughToc = true; - } - - # We need this to perform operations on the HTML - $sk = $this->mOptions->getSkin(); - - # headline counter - $headlineCount = 0; - $sectionCount = 0; # headlineCount excluding template sections - $numVisible = 0; - - # Ugh .. the TOC should have neat indentation levels which can be - # passed to the skin functions. These are determined here - $toc = ''; - $full = ''; - $head = array(); - $sublevelCount = array(); - $levelCount = array(); - $toclevel = 0; - $level = 0; - $prevlevel = 0; - $toclevel = 0; - $prevtoclevel = 0; - $tocraw = array(); - - foreach( $matches[3] as $headline ) { - $istemplate = 0; - $templatetitle = ''; - $templatesection = 0; - $numbering = ''; - $mat = array(); - if (preg_match("/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", $headline, $mat)) { - $istemplate = 1; - $templatetitle = base64_decode($mat[1]); - $templatesection = 1 + (int)base64_decode($mat[2]); - $headline = preg_replace("/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", "", $headline); - } - - if( $toclevel ) { - $prevlevel = $level; - $prevtoclevel = $toclevel; - } - $level = $matches[1][$headlineCount]; - - if( $doNumberHeadings || $enoughToc ) { - - if ( $level > $prevlevel ) { - # Increase TOC level - $toclevel++; - $sublevelCount[$toclevel] = 0; - if( $toclevel<$wgMaxTocLevel ) { - $prevtoclevel = $toclevel; - $toc .= $sk->tocIndent(); - $numVisible++; - } - } - elseif ( $level < $prevlevel && $toclevel > 1 ) { - # Decrease TOC level, find level to jump to - - if ( $toclevel == 2 && $level <= $levelCount[1] ) { - # Can only go down to level 1 - $toclevel = 1; - } else { - for ($i = $toclevel; $i > 0; $i--) { - if ( $levelCount[$i] == $level ) { - # Found last matching level - $toclevel = $i; - break; - } - elseif ( $levelCount[$i] < $level ) { - # Found first matching level below current level - $toclevel = $i + 1; - break; - } - } - } - if( $toclevel<$wgMaxTocLevel ) { - if($prevtoclevel < $wgMaxTocLevel) { - # Unindent only if the previous toc level was shown :p - $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel ); - } else { - $toc .= $sk->tocLineEnd(); - } - } - } - else { - # No change in level, end TOC line - if( $toclevel<$wgMaxTocLevel ) { - $toc .= $sk->tocLineEnd(); - } - } - - $levelCount[$toclevel] = $level; - - # count number of headlines for each level - @$sublevelCount[$toclevel]++; - $dot = 0; - for( $i = 1; $i <= $toclevel; $i++ ) { - if( !empty( $sublevelCount[$i] ) ) { - if( $dot ) { - $numbering .= '.'; - } - $numbering .= $wgContLang->formatNum( $sublevelCount[$i] ); - $dot = 1; - } - } - } - - # The canonized header is a version of the header text safe to use for links - # Avoid insertion of weird stuff like <math> by expanding the relevant sections - $canonized_headline = $this->mStripState->unstripBoth( $headline ); - - # Remove link placeholders by the link text. - # <!--LINK number--> - # turns into - # link text with suffix - $canonized_headline = preg_replace( '/<!--LINK ([0-9]*)-->/e', - "\$this->mLinkHolders['texts'][\$1]", - $canonized_headline ); - $canonized_headline = preg_replace( '/<!--IWLINK ([0-9]*)-->/e', - "\$this->mInterwikiLinkHolders['texts'][\$1]", - $canonized_headline ); - - # Strip out HTML (other than plain <sup> and <sub>: bug 8393) - $tocline = preg_replace( - array( '#<(?!/?(sup|sub)).*?'.'>#', '#<(/?(sup|sub)).*?'.'>#' ), - array( '', '<$1>'), - $canonized_headline - ); - $tocline = trim( $tocline ); - - # For the anchor, strip out HTML-y stuff period - $canonized_headline = preg_replace( '/<.*?'.'>/', '', $canonized_headline ); - $canonized_headline = trim( $canonized_headline ); - - # Save headline for section edit hint before it's escaped - $headline_hint = $canonized_headline; - $canonized_headline = Sanitizer::escapeId( $canonized_headline ); - $refers[$headlineCount] = $canonized_headline; - - # count how many in assoc. array so we can track dupes in anchors - isset( $refers[$canonized_headline] ) ? $refers[$canonized_headline]++ : $refers[$canonized_headline] = 1; - $refcount[$headlineCount]=$refers[$canonized_headline]; - - # Don't number the heading if it is the only one (looks silly) - if( $doNumberHeadings && count( $matches[3] ) > 1) { - # the two are different if the line contains a link - $headline=$numbering . ' ' . $headline; - } - - # Create the anchor for linking from the TOC to the section - $anchor = $canonized_headline; - if($refcount[$headlineCount] > 1 ) { - $anchor .= '_' . $refcount[$headlineCount]; - } - if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) { - $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel); - $tocraw[] = array( 'toclevel' => $toclevel, 'level' => $level, 'line' => $tocline, 'number' => $numbering ); - } - # give headline the correct <h#> tag - if( $showEditLink && ( !$istemplate || $templatetitle !== "" ) ) { - if( $istemplate ) - $editlink = $sk->editSectionLinkForOther($templatetitle, $templatesection); - else - $editlink = $sk->editSectionLink($this->mTitle, $sectionCount+1, $headline_hint); - } else { - $editlink = ''; - } - $head[$headlineCount] = $sk->makeHeadline( $level, $matches['attrib'][$headlineCount], $anchor, $headline, $editlink ); - - $headlineCount++; - if( !$istemplate ) - $sectionCount++; - } - - $this->mOutput->setSections( $tocraw ); - - # Never ever show TOC if no headers - if( $numVisible < 1 ) { - $enoughToc = false; - } - - if( $enoughToc ) { - if( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) { - $toc .= $sk->tocUnindent( $prevtoclevel - 1 ); - } - $toc = $sk->tocList( $toc ); - } - - # split up and insert constructed headlines - - $blocks = preg_split( '/<H[1-6].*?' . '>.*?<\/H[1-6]>/i', $text ); - $i = 0; - - foreach( $blocks as $block ) { - if( $showEditLink && $headlineCount > 0 && $i == 0 && $block != "\n" ) { - # This is the [edit] link that appears for the top block of text when - # section editing is enabled - - # Disabled because it broke block formatting - # For example, a bullet point in the top line - # $full .= $sk->editSectionLink(0); - } - $full .= $block; - if( $enoughToc && !$i && $isMain && !$this->mForceTocPosition ) { - # Top anchor now in skin - $full = $full.$toc; - } - - if( !empty( $head[$i] ) ) { - $full .= $head[$i]; - } - $i++; - } - if( $this->mForceTocPosition ) { - return str_replace( '<!--MWTOC-->', $toc, $full ); - } else { - return $full; - } - } - - /** - * Transform wiki markup when saving a page by doing \r\n -> \n - * conversion, substitting signatures, {{subst:}} templates, etc. - * - * @param string $text the text to transform - * @param Title &$title the Title object for the current article - * @param User &$user the User object describing the current user - * @param ParserOptions $options parsing options - * @param bool $clearState whether to clear the parser state first - * @return string the altered wiki markup - * @public - */ - function preSaveTransform( $text, &$title, $user, $options, $clearState = true ) { - $this->mOptions = $options; - $this->mTitle =& $title; - $this->setOutputType( self::OT_WIKI ); - - if ( $clearState ) { - $this->clearState(); - } - - $stripState = new StripState; - $pairs = array( - "\r\n" => "\n", - ); - $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text ); - $text = $this->strip( $text, $stripState, true, array( 'gallery' ) ); - $text = $this->pstPass2( $text, $stripState, $user ); - $text = $stripState->unstripBoth( $text ); - return $text; - } - - /** - * Pre-save transform helper function - * @private - */ - function pstPass2( $text, &$stripState, $user ) { - global $wgContLang, $wgLocaltimezone; - - /* Note: This is the timestamp saved as hardcoded wikitext to - * the database, we use $wgContLang here in order to give - * everyone the same signature and use the default one rather - * than the one selected in each user's preferences. - */ - if ( isset( $wgLocaltimezone ) ) { - $oldtz = getenv( 'TZ' ); - putenv( 'TZ='.$wgLocaltimezone ); - } - $d = $wgContLang->timeanddate( date( 'YmdHis' ), false, false) . - ' (' . date( 'T' ) . ')'; - if ( isset( $wgLocaltimezone ) ) { - putenv( 'TZ='.$oldtz ); - } - - # Variable replacement - # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags - $text = $this->replaceVariables( $text ); - - # Strip out <nowiki> etc. added via replaceVariables - $text = $this->strip( $text, $stripState, false, array( 'gallery' ) ); - - # Signatures - $sigText = $this->getUserSig( $user ); - $text = strtr( $text, array( - '~~~~~' => $d, - '~~~~' => "$sigText $d", - '~~~' => $sigText - ) ); - - # Context links: [[|name]] and [[name (context)|]] - # - global $wgLegalTitleChars; - $tc = "[$wgLegalTitleChars]"; - $nc = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii! - - $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\))\\|]]/"; # [[ns:page (context)|]] - $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\)|)(, $tc+|)\\|]]/"; # [[ns:page (context), context|]] - $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] - - # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]" - $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text ); - $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text ); - - $t = $this->mTitle->getText(); - $m = array(); - if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) { - $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text ); - } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && '' != "$m[1]$m[2]" ) { - $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text ); - } else { - # if there's no context, don't bother duplicating the title - $text = preg_replace( $p2, '[[\\1]]', $text ); - } - - # Trim trailing whitespace - $text = rtrim( $text ); - - return $text; - } - - /** - * Fetch the user's signature text, if any, and normalize to - * validated, ready-to-insert wikitext. - * - * @param User $user - * @return string - * @private - */ - function getUserSig( &$user ) { - global $wgMaxSigChars; - - $username = $user->getName(); - $nickname = $user->getOption( 'nickname' ); - $nickname = $nickname === '' ? $username : $nickname; - - if( mb_strlen( $nickname ) > $wgMaxSigChars ) { - $nickname = $username; - wfDebug( __METHOD__ . ": $username has overlong signature.\n" ); - } elseif( $user->getBoolOption( 'fancysig' ) !== false ) { - # Sig. might contain markup; validate this - if( $this->validateSig( $nickname ) !== false ) { - # Validated; clean up (if needed) and return it - return $this->cleanSig( $nickname, true ); - } else { - # Failed to validate; fall back to the default - $nickname = $username; - wfDebug( "Parser::getUserSig: $username has bad XML tags in signature.\n" ); - } - } - - // Make sure nickname doesnt get a sig in a sig - $nickname = $this->cleanSigInSig( $nickname ); - - # If we're still here, make it a link to the user page - $userText = wfEscapeWikiText( $username ); - $nickText = wfEscapeWikiText( $nickname ); - if ( $user->isAnon() ) { - return wfMsgExt( 'signature-anon', array( 'content', 'parsemag' ), $userText, $nickText ); - } else { - return wfMsgExt( 'signature', array( 'content', 'parsemag' ), $userText, $nickText ); - } - } - - /** - * Check that the user's signature contains no bad XML - * - * @param string $text - * @return mixed An expanded string, or false if invalid. - */ - function validateSig( $text ) { - return( wfIsWellFormedXmlFragment( $text ) ? $text : false ); - } - - /** - * Clean up signature text - * - * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures @see cleanSigInSig - * 2) Substitute all transclusions - * - * @param string $text - * @param $parsing Whether we're cleaning (preferences save) or parsing - * @return string Signature text - */ - function cleanSig( $text, $parsing = false ) { - global $wgTitle; - $this->startExternalParse( $this->mTitle, new ParserOptions(), $parsing ? self::OT_WIKI : self::OT_MSG ); - - $substWord = MagicWord::get( 'subst' ); - $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase(); - $substText = '{{' . $substWord->getSynonym( 0 ); - - $text = preg_replace( $substRegex, $substText, $text ); - $text = $this->cleanSigInSig( $text ); - $text = $this->replaceVariables( $text ); - - $this->clearState(); - return $text; - } - - /** - * Strip ~~~, ~~~~ and ~~~~~ out of signatures - * @param string $text - * @return string Signature text with /~{3,5}/ removed - */ - function cleanSigInSig( $text ) { - $text = preg_replace( '/~{3,5}/', '', $text ); - return $text; - } - - /** - * Set up some variables which are usually set up in parse() - * so that an external function can call some class members with confidence - * @public - */ - function startExternalParse( &$title, $options, $outputType, $clearState = true ) { - $this->mTitle =& $title; - $this->mOptions = $options; - $this->setOutputType( $outputType ); - if ( $clearState ) { - $this->clearState(); - } - } - - /** - * Transform a MediaWiki message by replacing magic variables. - * - * @param string $text the text to transform - * @param ParserOptions $options options - * @return string the text with variables substituted - * @public - */ - function transformMsg( $text, $options ) { - global $wgTitle; - static $executing = false; - - $fname = "Parser::transformMsg"; - - # Guard against infinite recursion - if ( $executing ) { - return $text; - } - $executing = true; - - wfProfileIn($fname); - - if ( $wgTitle && !( $wgTitle instanceof FakeTitle ) ) { - $this->mTitle = $wgTitle; - } else { - $this->mTitle = Title::newFromText('msg'); - } - $this->mOptions = $options; - $this->setOutputType( self::OT_MSG ); - $this->clearState(); - $text = $this->replaceVariables( $text ); - - $executing = false; - wfProfileOut($fname); - return $text; - } - - /** - * Create an HTML-style tag, e.g. <yourtag>special text</yourtag> - * The callback should have the following form: - * function myParserHook( $text, $params, &$parser ) { ... } - * - * Transform and return $text. Use $parser for any required context, e.g. use - * $parser->getTitle() and $parser->getOptions() not $wgTitle or $wgOut->mParserOptions - * - * @public - * - * @param mixed $tag The tag to use, e.g. 'hook' for <hook> - * @param mixed $callback The callback function (and object) to use for the tag - * - * @return The old value of the mTagHooks array associated with the hook - */ - function setHook( $tag, $callback ) { - $tag = strtolower( $tag ); - $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null; - $this->mTagHooks[$tag] = $callback; - - return $oldVal; - } - - function setTransparentTagHook( $tag, $callback ) { - $tag = strtolower( $tag ); - $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null; - $this->mTransparentTagHooks[$tag] = $callback; - - return $oldVal; - } - - /** - * Create a function, e.g. {{sum:1|2|3}} - * The callback function should have the form: - * function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... } - * - * The callback may either return the text result of the function, or an array with the text - * in element 0, and a number of flags in the other elements. The names of the flags are - * specified in the keys. Valid flags are: - * found The text returned is valid, stop processing the template. This - * is on by default. - * nowiki Wiki markup in the return value should be escaped - * noparse Unsafe HTML tags should not be stripped, etc. - * noargs Don't replace triple-brace arguments in the return value - * isHTML The returned text is HTML, armour it against wikitext transformation - * - * @public - * - * @param string $id The magic word ID - * @param mixed $callback The callback function (and object) to use - * @param integer $flags a combination of the following flags: - * SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}} - * - * @return The old callback function for this name, if any - */ - function setFunctionHook( $id, $callback, $flags = 0 ) { - $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id] : null; - $this->mFunctionHooks[$id] = $callback; - - # Add to function cache - $mw = MagicWord::get( $id ); - if( !$mw ) - throw new MWException( 'Parser::setFunctionHook() expecting a magic word identifier.' ); - - $synonyms = $mw->getSynonyms(); - $sensitive = intval( $mw->isCaseSensitive() ); - - foreach ( $synonyms as $syn ) { - # Case - if ( !$sensitive ) { - $syn = strtolower( $syn ); - } - # Add leading hash - if ( !( $flags & SFH_NO_HASH ) ) { - $syn = '#' . $syn; - } - # Remove trailing colon - if ( substr( $syn, -1, 1 ) == ':' ) { - $syn = substr( $syn, 0, -1 ); - } - $this->mFunctionSynonyms[$sensitive][$syn] = $id; - } - return $oldVal; - } - - /** - * Get all registered function hook identifiers - * - * @return array - */ - function getFunctionHooks() { - return array_keys( $this->mFunctionHooks ); - } - - /** - * Replace <!--LINK--> link placeholders with actual links, in the buffer - * Placeholders created in Skin::makeLinkObj() - * Returns an array of links found, indexed by PDBK: - * 0 - broken - * 1 - normal link - * 2 - stub - * $options is a bit field, RLH_FOR_UPDATE to select for update - */ - function replaceLinkHolders( &$text, $options = 0 ) { - global $wgUser; - global $wgContLang; - - $fname = 'Parser::replaceLinkHolders'; - wfProfileIn( $fname ); - - $pdbks = array(); - $colours = array(); - $sk = $this->mOptions->getSkin(); - $linkCache =& LinkCache::singleton(); - - if ( !empty( $this->mLinkHolders['namespaces'] ) ) { - wfProfileIn( $fname.'-check' ); - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $threshold = $wgUser->getOption('stubthreshold'); - - # Sort by namespace - asort( $this->mLinkHolders['namespaces'] ); - - # Generate query - $query = false; - $current = null; - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { - # Make title object - $title = $this->mLinkHolders['titles'][$key]; - - # Skip invalid entries. - # Result will be ugly, but prevents crash. - if ( is_null( $title ) ) { - continue; - } - $pdbk = $pdbks[$key] = $title->getPrefixedDBkey(); - - # Check if it's a static known link, e.g. interwiki - if ( $title->isAlwaysKnown() ) { - $colours[$pdbk] = 1; - } elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) { - $colours[$pdbk] = 1; - $this->mOutput->addLink( $title, $id ); - } elseif ( $linkCache->isBadLink( $pdbk ) ) { - $colours[$pdbk] = 0; - } elseif ( $title->getNamespace() == NS_SPECIAL && !SpecialPage::exists( $pdbk ) ) { - $colours[$pdbk] = 0; - } else { - # Not in the link cache, add it to the query - if ( !isset( $current ) ) { - $current = $ns; - $query = "SELECT page_id, page_namespace, page_title"; - if ( $threshold > 0 ) { - $query .= ', page_len, page_is_redirect'; - } - $query .= " FROM $page WHERE (page_namespace=$ns AND page_title IN("; - } elseif ( $current != $ns ) { - $current = $ns; - $query .= ")) OR (page_namespace=$ns AND page_title IN("; - } else { - $query .= ', '; - } - - $query .= $dbr->addQuotes( $this->mLinkHolders['dbkeys'][$key] ); - } - } - if ( $query ) { - $query .= '))'; - if ( $options & RLH_FOR_UPDATE ) { - $query .= ' FOR UPDATE'; - } - - $res = $dbr->query( $query, $fname ); - - # Fetch data and form into an associative array - # non-existent = broken - # 1 = known - # 2 = stub - while ( $s = $dbr->fetchObject($res) ) { - $title = Title::makeTitle( $s->page_namespace, $s->page_title ); - $pdbk = $title->getPrefixedDBkey(); - $linkCache->addGoodLinkObj( $s->page_id, $title ); - $this->mOutput->addLink( $title, $s->page_id ); - - $colours[$pdbk] = ( $threshold == 0 || ( - $s->page_len >= $threshold || # always true if $threshold <= 0 - $s->page_is_redirect || - !Namespace::isContent( $s->page_namespace ) ) - ? 1 : 2 ); - } - } - wfProfileOut( $fname.'-check' ); - - # Do a second query for different language variants of links and categories - if($wgContLang->hasVariants()){ - $linkBatch = new LinkBatch(); - $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) - $categoryMap = array(); // maps $category_variant => $category (dbkeys) - $varCategories = array(); // category replacements oldDBkey => newDBkey - - $categories = $this->mOutput->getCategoryLinks(); - - // Add variants of links to link batch - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { - $title = $this->mLinkHolders['titles'][$key]; - if ( is_null( $title ) ) - continue; - - $pdbk = $title->getPrefixedDBkey(); - $titleText = $title->getText(); - - // generate all variants of the link title text - $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText); - - // if link was not found (in first query), add all variants to query - if ( !isset($colours[$pdbk]) ){ - foreach($allTextVariants as $textVariant){ - if($textVariant != $titleText){ - $variantTitle = Title::makeTitle( $ns, $textVariant ); - if(is_null($variantTitle)) continue; - $linkBatch->addObj( $variantTitle ); - $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; - } - } - } - } - - // process categories, check if a category exists in some variant - foreach( $categories as $category ){ - $variants = $wgContLang->convertLinkToAllVariants($category); - foreach($variants as $variant){ - if($variant != $category){ - $variantTitle = Title::newFromDBkey( Title::makeName(NS_CATEGORY,$variant) ); - if(is_null($variantTitle)) continue; - $linkBatch->addObj( $variantTitle ); - $categoryMap[$variant] = $category; - } - } - } - - - if(!$linkBatch->isEmpty()){ - // construct query - $titleClause = $linkBatch->constructSet('page', $dbr); - - $variantQuery = "SELECT page_id, page_namespace, page_title"; - if ( $threshold > 0 ) { - $variantQuery .= ', page_len, page_is_redirect'; - } - - $variantQuery .= " FROM $page WHERE $titleClause"; - if ( $options & RLH_FOR_UPDATE ) { - $variantQuery .= ' FOR UPDATE'; - } - - $varRes = $dbr->query( $variantQuery, $fname ); - - // for each found variants, figure out link holders and replace - while ( $s = $dbr->fetchObject($varRes) ) { - - $variantTitle = Title::makeTitle( $s->page_namespace, $s->page_title ); - $varPdbk = $variantTitle->getPrefixedDBkey(); - $vardbk = $variantTitle->getDBkey(); - - $holderKeys = array(); - if(isset($variantMap[$varPdbk])){ - $holderKeys = $variantMap[$varPdbk]; - $linkCache->addGoodLinkObj( $s->page_id, $variantTitle ); - $this->mOutput->addLink( $variantTitle, $s->page_id ); - } - - // loop over link holders - foreach($holderKeys as $key){ - $title = $this->mLinkHolders['titles'][$key]; - if ( is_null( $title ) ) continue; - - $pdbk = $title->getPrefixedDBkey(); - - if(!isset($colours[$pdbk])){ - // found link in some of the variants, replace the link holder data - $this->mLinkHolders['titles'][$key] = $variantTitle; - $this->mLinkHolders['dbkeys'][$key] = $variantTitle->getDBkey(); - - // set pdbk and colour - $pdbks[$key] = $varPdbk; - if ( $threshold > 0 ) { - $size = $s->page_len; - if ( $s->page_is_redirect || $s->page_namespace != 0 || $size >= $threshold ) { - $colours[$varPdbk] = 1; - } else { - $colours[$varPdbk] = 2; - } - } - else { - $colours[$varPdbk] = 1; - } - } - } - - // check if the object is a variant of a category - if(isset($categoryMap[$vardbk])){ - $oldkey = $categoryMap[$vardbk]; - if($oldkey != $vardbk) - $varCategories[$oldkey]=$vardbk; - } - } - - // rebuild the categories in original order (if there are replacements) - if(count($varCategories)>0){ - $newCats = array(); - $originalCats = $this->mOutput->getCategories(); - foreach($originalCats as $cat => $sortkey){ - // make the replacement - if( array_key_exists($cat,$varCategories) ) - $newCats[$varCategories[$cat]] = $sortkey; - else $newCats[$cat] = $sortkey; - } - $this->mOutput->setCategoryLinks($newCats); - } - } - } - - # Construct search and replace arrays - wfProfileIn( $fname.'-construct' ); - $replacePairs = array(); - foreach ( $this->mLinkHolders['namespaces'] as $key => $ns ) { - $pdbk = $pdbks[$key]; - $searchkey = "<!--LINK $key-->"; - $title = $this->mLinkHolders['titles'][$key]; - if ( empty( $colours[$pdbk] ) ) { - $linkCache->addBadLinkObj( $title ); - $colours[$pdbk] = 0; - $this->mOutput->addLink( $title, 0 ); - $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title, - $this->mLinkHolders['texts'][$key], - $this->mLinkHolders['queries'][$key] ); - } elseif ( $colours[$pdbk] == 1 ) { - $replacePairs[$searchkey] = $sk->makeKnownLinkObj( $title, - $this->mLinkHolders['texts'][$key], - $this->mLinkHolders['queries'][$key] ); - } elseif ( $colours[$pdbk] == 2 ) { - $replacePairs[$searchkey] = $sk->makeStubLinkObj( $title, - $this->mLinkHolders['texts'][$key], - $this->mLinkHolders['queries'][$key] ); - } - } - $replacer = new HashtableReplacer( $replacePairs, 1 ); - wfProfileOut( $fname.'-construct' ); - - # Do the thing - wfProfileIn( $fname.'-replace' ); - $text = preg_replace_callback( - '/(<!--LINK .*?-->)/', - $replacer->cb(), - $text); - - wfProfileOut( $fname.'-replace' ); - } - - # Now process interwiki link holders - # This is quite a bit simpler than internal links - if ( !empty( $this->mInterwikiLinkHolders['texts'] ) ) { - wfProfileIn( $fname.'-interwiki' ); - # Make interwiki link HTML - $replacePairs = array(); - foreach( $this->mInterwikiLinkHolders['texts'] as $key => $link ) { - $title = $this->mInterwikiLinkHolders['titles'][$key]; - $replacePairs[$key] = $sk->makeLinkObj( $title, $link ); - } - $replacer = new HashtableReplacer( $replacePairs, 1 ); - - $text = preg_replace_callback( - '/<!--IWLINK (.*?)-->/', - $replacer->cb(), - $text ); - wfProfileOut( $fname.'-interwiki' ); - } - - wfProfileOut( $fname ); - return $colours; - } - - /** - * Replace <!--LINK--> link placeholders with plain text of links - * (not HTML-formatted). - * @param string $text - * @return string - */ - function replaceLinkHoldersText( $text ) { - $fname = 'Parser::replaceLinkHoldersText'; - wfProfileIn( $fname ); - - $text = preg_replace_callback( - '/<!--(LINK|IWLINK) (.*?)-->/', - array( &$this, 'replaceLinkHoldersTextCallback' ), - $text ); - - wfProfileOut( $fname ); - return $text; - } - - /** - * @param array $matches - * @return string - * @private - */ - function replaceLinkHoldersTextCallback( $matches ) { - $type = $matches[1]; - $key = $matches[2]; - if( $type == 'LINK' ) { - if( isset( $this->mLinkHolders['texts'][$key] ) ) { - return $this->mLinkHolders['texts'][$key]; - } - } elseif( $type == 'IWLINK' ) { - if( isset( $this->mInterwikiLinkHolders['texts'][$key] ) ) { - return $this->mInterwikiLinkHolders['texts'][$key]; - } - } - return $matches[0]; - } - - /** - * Tag hook handler for 'pre'. - */ - function renderPreTag( $text, $attribs ) { - // Backwards-compatibility hack - $content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $text, 'i' ); - - $attribs = Sanitizer::validateTagAttributes( $attribs, 'pre' ); - return wfOpenElement( 'pre', $attribs ) . - Xml::escapeTagsOnly( $content ) . - '</pre>'; - } - - /** - * Renders an image gallery from a text with one line per image. - * text labels may be given by using |-style alternative text. E.g. - * Image:one.jpg|The number "1" - * Image:tree.jpg|A tree - * given as text will return the HTML of a gallery with two images, - * labeled 'The number "1"' and - * 'A tree'. - */ - function renderImageGallery( $text, $params ) { - $ig = new ImageGallery(); - $ig->setContextTitle( $this->mTitle ); - $ig->setShowBytes( false ); - $ig->setShowFilename( false ); - $ig->setParser( $this ); - $ig->setHideBadImages(); - $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) ); - $ig->useSkin( $this->mOptions->getSkin() ); - $ig->mRevisionId = $this->mRevisionId; - - if( isset( $params['caption'] ) ) { - $caption = $params['caption']; - $caption = htmlspecialchars( $caption ); - $caption = $this->replaceInternalLinks( $caption ); - $ig->setCaptionHtml( $caption ); - } - if( isset( $params['perrow'] ) ) { - $ig->setPerRow( $params['perrow'] ); - } - if( isset( $params['widths'] ) ) { - $ig->setWidths( $params['widths'] ); - } - if( isset( $params['heights'] ) ) { - $ig->setHeights( $params['heights'] ); - } - - wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) ); - - $lines = explode( "\n", $text ); - foreach ( $lines as $line ) { - # match lines like these: - # Image:someimage.jpg|This is some image - $matches = array(); - preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches ); - # Skip empty lines - if ( count( $matches ) == 0 ) { - continue; - } - $tp = Title::newFromText( $matches[1] ); - $nt =& $tp; - if( is_null( $nt ) ) { - # Bogus title. Ignore these so we don't bomb out later. - continue; - } - if ( isset( $matches[3] ) ) { - $label = $matches[3]; - } else { - $label = ''; - } - - $pout = $this->parse( $label, - $this->mTitle, - $this->mOptions, - false, // Strip whitespace...? - false // Don't clear state! - ); - $html = $pout->getText(); - - $ig->add( $nt, $html ); - - # Only add real images (bug #5586) - if ( $nt->getNamespace() == NS_IMAGE ) { - $this->mOutput->addImage( $nt->getDBkey() ); - } - } - return $ig->toHTML(); - } - - function getImageParams( $handler ) { - if ( $handler ) { - $handlerClass = get_class( $handler ); - } else { - $handlerClass = ''; - } - if ( !isset( $this->mImageParams[$handlerClass] ) ) { - // Initialise static lists - static $internalParamNames = array( - 'horizAlign' => array( 'left', 'right', 'center', 'none' ), - 'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', - 'bottom', 'text-bottom' ), - 'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless', - 'upright', 'border' ), - ); - static $internalParamMap; - if ( !$internalParamMap ) { - $internalParamMap = array(); - foreach ( $internalParamNames as $type => $names ) { - foreach ( $names as $name ) { - $magicName = str_replace( '-', '_', "img_$name" ); - $internalParamMap[$magicName] = array( $type, $name ); - } - } - } - - // Add handler params - $paramMap = $internalParamMap; - if ( $handler ) { - $handlerParamMap = $handler->getParamMap(); - foreach ( $handlerParamMap as $magic => $paramName ) { - $paramMap[$magic] = array( 'handler', $paramName ); - } - } - $this->mImageParams[$handlerClass] = $paramMap; - $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) ); - } - return array( $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ); - } - - /** - * Parse image options text and use it to make an image - */ - function makeImage( $title, $options ) { - # @TODO: let the MediaHandler specify its transform parameters - # - # Check if the options text is of the form "options|alt text" - # Options are: - # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang - # * left no resizing, just left align. label is used for alt= only - # * right same, but right aligned - # * none same, but not aligned - # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox - # * center center the image - # * framed Keep original image size, no magnify-button. - # * frameless like 'thumb' but without a frame. Keeps user preferences for width - # * upright reduce width for upright images, rounded to full __0 px - # * border draw a 1px border around the image - # vertical-align values (no % or length right now): - # * baseline - # * sub - # * super - # * top - # * text-top - # * middle - # * bottom - # * text-bottom - - $parts = array_map( 'trim', explode( '|', $options) ); - $sk = $this->mOptions->getSkin(); - - # Give extensions a chance to select the file revision for us - $skip = $time = false; - wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$title, &$skip, &$time ) ); - - if ( $skip ) { - return $sk->makeLinkObj( $title ); - } - - # Get parameter map - $file = wfFindFile( $title, $time ); - $handler = $file ? $file->getHandler() : false; - - list( $paramMap, $mwArray ) = $this->getImageParams( $handler ); - - # Process the input parameters - $caption = ''; - $params = array( 'frame' => array(), 'handler' => array(), - 'horizAlign' => array(), 'vertAlign' => array() ); - foreach( $parts as $part ) { - list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part ); - if ( isset( $paramMap[$magicName] ) ) { - list( $type, $paramName ) = $paramMap[$magicName]; - $params[$type][$paramName] = $value; - - // Special case; width and height come in one variable together - if( $type == 'handler' && $paramName == 'width' ) { - $m = array(); - if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $value, $m ) ) { - $params[$type]['width'] = intval( $m[1] ); - $params[$type]['height'] = intval( $m[2] ); - } else { - $params[$type]['width'] = intval( $value ); - } - } - } else { - $caption = $part; - } - } - - # Process alignment parameters - if ( $params['horizAlign'] ) { - $params['frame']['align'] = key( $params['horizAlign'] ); - } - if ( $params['vertAlign'] ) { - $params['frame']['valign'] = key( $params['vertAlign'] ); - } - - # Validate the handler parameters - if ( $handler ) { - foreach ( $params['handler'] as $name => $value ) { - if ( !$handler->validateParam( $name, $value ) ) { - unset( $params['handler'][$name] ); - } - } - } - - # Strip bad stuff out of the alt text - $alt = $this->replaceLinkHoldersText( $caption ); - - # make sure there are no placeholders in thumbnail attributes - # that are later expanded to html- so expand them now and - # remove the tags - $alt = $this->mStripState->unstripBoth( $alt ); - $alt = Sanitizer::stripAllTags( $alt ); - - $params['frame']['alt'] = $alt; - $params['frame']['caption'] = $caption; - - # Linker does the rest - $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'] ); - - # Give the handler a chance to modify the parser object - if ( $handler ) { - $handler->parserTransformHook( $this, $file ); - } - - return $ret; - } - - /** - * Set a flag in the output object indicating that the content is dynamic and - * shouldn't be cached. - */ - function disableCache() { - wfDebug( "Parser output marked as uncacheable.\n" ); - $this->mOutput->mCacheTime = -1; - } - - /**#@+ - * Callback from the Sanitizer for expanding items found in HTML attribute - * values, so they can be safely tested and escaped. - * @param string $text - * @param array $args - * @return string - * @private - */ - function attributeStripCallback( &$text, $args ) { - $text = $this->replaceVariables( $text, $args ); - $text = $this->mStripState->unstripBoth( $text ); - return $text; - } - - /**#@-*/ - - /**#@+ - * Accessor/mutator - */ - function Title( $x = NULL ) { return wfSetVar( $this->mTitle, $x ); } - function Options( $x = NULL ) { return wfSetVar( $this->mOptions, $x ); } - function OutputType( $x = NULL ) { return wfSetVar( $this->mOutputType, $x ); } - /**#@-*/ - - /**#@+ - * Accessor - */ - function getTags() { return array_merge( array_keys($this->mTransparentTagHooks), array_keys( $this->mTagHooks ) ); } - /**#@-*/ - - - /** - * Break wikitext input into sections, and either pull or replace - * some particular section's text. - * - * External callers should use the getSection and replaceSection methods. - * - * @param $text Page wikitext - * @param $section Numbered section. 0 pulls the text before the first - * heading; other numbers will pull the given section - * along with its lower-level subsections. - * @param $mode One of "get" or "replace" - * @param $newtext Replacement text for section data. - * @return string for "get", the extracted section text. - * for "replace", the whole page with the section replaced. - */ - private function extractSections( $text, $section, $mode, $newtext='' ) { - # I.... _hope_ this is right. - # Otherwise, sometimes we don't have things initialized properly. - $this->clearState(); - - # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML - # comments to be stripped as well) - $stripState = new StripState; - - $oldOutputType = $this->mOutputType; - $oldOptions = $this->mOptions; - $this->mOptions = new ParserOptions(); - $this->setOutputType( self::OT_WIKI ); - - $striptext = $this->strip( $text, $stripState, true ); - - $this->setOutputType( $oldOutputType ); - $this->mOptions = $oldOptions; - - # now that we can be sure that no pseudo-sections are in the source, - # split it up by section - $uniq = preg_quote( $this->uniqPrefix(), '/' ); - $comment = "(?:$uniq-!--.*?QINU\x07)"; - $secs = preg_split( - "/ - ( - ^ - (?:$comment|<\/?noinclude>)* # Initial comments will be stripped - (=+) # Should this be limited to 6? - .+? # Section title... - \\2 # Ending = count must match start - (?:$comment|<\/?noinclude>|[ \\t]+)* # Trailing whitespace ok - $ - | - <h([1-6])\b.*?> - .*? - <\/h\\3\s*> - ) - /mix", - $striptext, -1, - PREG_SPLIT_DELIM_CAPTURE); - - if( $mode == "get" ) { - if( $section == 0 ) { - // "Section 0" returns the content before any other section. - $rv = $secs[0]; - } else { - //track missing section, will replace if found. - $rv = $newtext; - } - } elseif( $mode == "replace" ) { - if( $section == 0 ) { - $rv = $newtext . "\n\n"; - $remainder = true; - } else { - $rv = $secs[0]; - $remainder = false; - } - } - $count = 0; - $sectionLevel = 0; - for( $index = 1; $index < count( $secs ); ) { - $headerLine = $secs[$index++]; - if( $secs[$index] ) { - // A wiki header - $headerLevel = strlen( $secs[$index++] ); - } else { - // An HTML header - $index++; - $headerLevel = intval( $secs[$index++] ); - } - $content = $secs[$index++]; - - $count++; - if( $mode == "get" ) { - if( $count == $section ) { - $rv = $headerLine . $content; - $sectionLevel = $headerLevel; - } elseif( $count > $section ) { - if( $sectionLevel && $headerLevel > $sectionLevel ) { - $rv .= $headerLine . $content; - } else { - // Broke out to a higher-level section - break; - } - } - } elseif( $mode == "replace" ) { - if( $count < $section ) { - $rv .= $headerLine . $content; - } elseif( $count == $section ) { - $rv .= $newtext . "\n\n"; - $sectionLevel = $headerLevel; - } elseif( $count > $section ) { - if( $headerLevel <= $sectionLevel ) { - // Passed the section's sub-parts. - $remainder = true; - } - if( $remainder ) { - $rv .= $headerLine . $content; - } - } - } - } - if (is_string($rv)) - # reinsert stripped tags - $rv = trim( $stripState->unstripBoth( $rv ) ); - - return $rv; - } - - /** - * This function returns the text of a section, specified by a number ($section). - * A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or - * the first section before any such heading (section 0). - * - * If a section contains subsections, these are also returned. - * - * @param $text String: text to look in - * @param $section Integer: section number - * @param $deftext: default to return if section is not found - * @return string text of the requested section - */ - public function getSection( $text, $section, $deftext='' ) { - return $this->extractSections( $text, $section, "get", $deftext ); - } - - public function replaceSection( $oldtext, $section, $text ) { - return $this->extractSections( $oldtext, $section, "replace", $text ); - } - - /** - * Get the timestamp associated with the current revision, adjusted for - * the default server-local timestamp - */ - function getRevisionTimestamp() { - if ( is_null( $this->mRevisionTimestamp ) ) { - wfProfileIn( __METHOD__ ); - global $wgContLang; - $dbr = wfGetDB( DB_SLAVE ); - $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', - array( 'rev_id' => $this->mRevisionId ), __METHOD__ ); - - // Normalize timestamp to internal MW format for timezone processing. - // This has the added side-effect of replacing a null value with - // the current time, which gives us more sensible behavior for - // previews. - $timestamp = wfTimestamp( TS_MW, $timestamp ); - - // The cryptic '' timezone parameter tells to use the site-default - // timezone offset instead of the user settings. - // - // Since this value will be saved into the parser cache, served - // to other users, and potentially even used inside links and such, - // it needs to be consistent for all visitors. - $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' ); - - wfProfileOut( __METHOD__ ); - } - return $this->mRevisionTimestamp; - } - - /** - * Mutator for $mDefaultSort - * - * @param $sort New value - */ - public function setDefaultSort( $sort ) { - $this->mDefaultSort = $sort; - } - - /** - * Accessor for $mDefaultSort - * Will use the title/prefixed title if none is set - * - * @return string - */ - public function getDefaultSort() { - if( $this->mDefaultSort !== false ) { - return $this->mDefaultSort; - } else { - return $this->mTitle->getNamespace() == NS_CATEGORY - ? $this->mTitle->getText() - : $this->mTitle->getPrefixedText(); - } - } - - /** - * Try to guess the section anchor name based on a wikitext fragment - * presumably extracted from a heading, for example "Header" from - * "== Header ==". - */ - public function guessSectionNameFromWikiText( $text ) { - # Strip out wikitext links(they break the anchor) - $text = $this->stripSectionName( $text ); - $headline = Sanitizer::decodeCharReferences( $text ); - # strip out HTML - $headline = StringUtils::delimiterReplace( '<', '>', '', $headline ); - $headline = trim( $headline ); - $sectionanchor = '#' . urlencode( str_replace( ' ', '_', $headline ) ); - $replacearray = array( - '%3A' => ':', - '%' => '.' - ); - return str_replace( - array_keys( $replacearray ), - array_values( $replacearray ), - $sectionanchor ); - } - - /** - * Strips a text string of wikitext for use in a section anchor - * - * Accepts a text string and then removes all wikitext from the - * string and leaves only the resultant text (i.e. the result of - * [[User:WikiSysop|Sysop]] would be "Sysop" and the result of - * [[User:WikiSysop]] would be "User:WikiSysop") - this is intended - * to create valid section anchors by mimicing the output of the - * parser when headings are parsed. - * - * @param $text string Text string to be stripped of wikitext - * for use in a Section anchor - * @return Filtered text string - */ - public function stripSectionName( $text ) { - # Strip internal link markup - $text = preg_replace('/\[\[:?([^[|]+)\|([^[]+)\]\]/','$2',$text); - $text = preg_replace('/\[\[:?([^[]+)\|?\]\]/','$1',$text); - - # Strip external link markup (FIXME: Not Tolerant to blank link text - # I.E. [http://www.mediawiki.org] will render as [1] or something depending - # on how many empty links there are on the page - need to figure that out. - $text = preg_replace('/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/','$2',$text); - - # Parse wikitext quotes (italics & bold) - $text = $this->doQuotes($text); - - # Strip HTML tags - $text = StringUtils::delimiterReplace( '<', '>', '', $text ); - return $text; - } - - /** - * strip/replaceVariables/unstrip for preprocessor regression testing - */ - function srvus( $text ) { - $text = $this->strip( $text, $this->mStripState ); - $text = Sanitizer::removeHTMLtags( $text ); - $text = $this->replaceVariables( $text ); - $text = preg_replace( '/<!--MWTEMPLATESECTION.*?-->/', '', $text ); - $text = $this->mStripState->unstripBoth( $text ); - return $text; - } -} - diff --git a/includes/Preprocessor.php b/includes/Preprocessor.php deleted file mode 100644 index 34bc1e5b..00000000 --- a/includes/Preprocessor.php +++ /dev/null @@ -1,154 +0,0 @@ -<?php - -interface Preprocessor { - /** Create a new preprocessor object based on an initialised Parser object */ - function __construct( $parser ); - - /** Create a new top-level frame for expansion of a page */ - function newFrame(); - - /** Preprocess text to a PPNode */ - function preprocessToObj( $text, $flags = 0 ); -} - -interface PPFrame { - const NO_ARGS = 1; - const NO_TEMPLATES = 2; - const STRIP_COMMENTS = 4; - const NO_IGNORE = 8; - const RECOVER_COMMENTS = 16; - - const RECOVER_ORIG = 27; // = 1|2|8|16 no constant expression support in PHP yet - - /** - * Create a child frame - */ - function newChild( $args = false, $title = false ); - - /** - * Expand a document tree node - */ - function expand( $root, $flags = 0 ); - - /** - * Implode with flags for expand() - */ - function implodeWithFlags( $sep, $flags /*, ... */ ); - - /** - * Implode with no flags specified - */ - function implode( $sep /*, ... */ ); - - /** - * Makes an object that, when expand()ed, will be the same as one obtained - * with implode() - */ - function virtualImplode( $sep /*, ... */ ); - - /** - * Virtual implode with brackets - */ - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ); - - /** - * Returns true if there are no arguments in this frame - */ - function isEmpty(); - - /** - * Get an argument to this frame by name - */ - function getArgument( $name ); - - /** - * Returns true if the infinite loop check is OK, false if a loop is detected - */ - function loopCheck( $title ); - - /** - * Return true if the frame is a template frame - */ - function isTemplate(); -} - -/** - * There are three types of nodes: - * * Tree nodes, which have a name and contain other nodes as children - * * Array nodes, which also contain other nodes but aren't considered part of a tree - * * Leaf nodes, which contain the actual data - * - * This interface provides access to the tree structure and to the contents of array nodes, - * but it does not provide access to the internal structure of leaf nodes. Access to leaf - * data is provided via two means: - * * PPFrame::expand(), which provides expanded text - * * The PPNode::split*() functions, which provide metadata about certain types of tree node - */ -interface PPNode { - /** - * Get an array-type node containing the children of this node. - * Returns false if this is not a tree node. - */ - function getChildren(); - - /** - * Get the first child of a tree node. False if there isn't one. - */ - function getFirstChild(); - - /** - * Get the next sibling of any node. False if there isn't one - */ - function getNextSibling(); - - /** - * Get all children of this tree node which have a given name. - * Returns an array-type node, or false if this is not a tree node. - */ - function getChildrenOfType( $type ); - - - /** - * Returns the length of the array, or false if this is not an array-type node - */ - function getLength(); - - /** - * Returns an item of an array-type node - */ - function item( $i ); - - /** - * Get the name of this node. The following names are defined here: - * - * h A heading node. - * template A double-brace node. - * tplarg A triple-brace node. - * title The first argument to a template or tplarg node. - * part Subsequent arguments to a template or tplarg node. - * #nodelist An array-type node - * - * The subclass may define various other names for tree and leaf nodes. - */ - function getName(); - - /** - * Split a <part> node into an associative array containing: - * name PPNode name - * index String index - * value PPNode value - */ - function splitArg(); - - /** - * Split an <ext> node into an associative array containing name, attr, inner and close - * All values in the resulting array are PPNodes. Inner and close are optional. - */ - function splitExt(); - - /** - * Split an <h> node - */ - function splitHeading(); -} - diff --git a/includes/Preprocessor_DOM.php b/includes/Preprocessor_DOM.php deleted file mode 100644 index 0e2e9a16..00000000 --- a/includes/Preprocessor_DOM.php +++ /dev/null @@ -1,1356 +0,0 @@ -<?php - -class Preprocessor_DOM implements Preprocessor { - var $parser, $memoryLimit; - - function __construct( $parser ) { - $this->parser = $parser; - $mem = ini_get( 'memory_limit' ); - $this->memoryLimit = false; - if ( strval( $mem ) !== '' && $mem != -1 ) { - if ( preg_match( '/^\d+$/', $mem ) ) { - $this->memoryLimit = $mem; - } elseif ( preg_match( '/^(\d+)M$/i', $mem, $m ) ) { - $this->memoryLimit = $m[1] * 1048576; - } - } - } - - function newFrame() { - return new PPFrame_DOM( $this ); - } - - function memCheck() { - if ( $this->memoryLimit === false ) { - return; - } - $usage = memory_get_usage(); - if ( $usage > $this->memoryLimit * 0.9 ) { - $limit = intval( $this->memoryLimit * 0.9 / 1048576 + 0.5 ); - throw new MWException( "Preprocessor hit 90% memory limit ($limit MB)" ); - } - return $usage <= $this->memoryLimit * 0.8; - } - - /** - * Preprocess some wikitext and return the document tree. - * This is the ghost of Parser::replace_variables(). - * - * @param string $text The text to parse - * @param integer flags Bitwise combination of: - * Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being - * included. Default is to assume a direct page view. - * - * The generated DOM tree must depend only on the input text and the flags. - * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. - * - * Any flag added to the $flags parameter here, or any other parameter liable to cause a - * change in the DOM tree for a given text, must be passed through the section identifier - * in the section edit link and thus back to extractSections(). - * - * The output of this function is currently only cached in process memory, but a persistent - * cache may be implemented at a later date which takes further advantage of these strict - * dependency requirements. - * - * @private - */ - function preprocessToObj( $text, $flags = 0 ) { - wfProfileIn( __METHOD__ ); - wfProfileIn( __METHOD__.'-makexml' ); - - $rules = array( - '{' => array( - 'end' => '}', - 'names' => array( - 2 => 'template', - 3 => 'tplarg', - ), - 'min' => 2, - 'max' => 3, - ), - '[' => array( - 'end' => ']', - 'names' => array( 2 => null ), - 'min' => 2, - 'max' => 2, - ) - ); - - $forInclusion = $flags & Parser::PTD_FOR_INCLUSION; - - $xmlishElements = $this->parser->getStripList(); - $enableOnlyinclude = false; - if ( $forInclusion ) { - $ignoredTags = array( 'includeonly', '/includeonly' ); - $ignoredElements = array( 'noinclude' ); - $xmlishElements[] = 'noinclude'; - if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) { - $enableOnlyinclude = true; - } - } else { - $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ); - $ignoredElements = array( 'includeonly' ); - $xmlishElements[] = 'includeonly'; - } - $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) ); - - // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset - $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA"; - - $stack = new PPDStack; - - $searchBase = "[{<\n"; #} - $revText = strrev( $text ); // For fast reverse searches - - $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start - $accum =& $stack->getAccum(); # Current accumulator - $accum = '<root>'; - $findEquals = false; # True to find equals signs in arguments - $findPipe = false; # True to take notice of pipe characters - $headingIndex = 1; - $inHeading = false; # True if $i is inside a possible heading - $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i - $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude> - $fakeLineStart = true; # Do a line-start run without outputting an LF character - - while ( true ) { - //$this->memCheck(); - - if ( $findOnlyinclude ) { - // Ignore all input up to the next <onlyinclude> - $startPos = strpos( $text, '<onlyinclude>', $i ); - if ( $startPos === false ) { - // Ignored section runs to the end - $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i ) ) . '</ignore>'; - break; - } - $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end - $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i ) ) . '</ignore>'; - $i = $tagEndPos; - $findOnlyinclude = false; - } - - if ( $fakeLineStart ) { - $found = 'line-start'; - $curChar = ''; - } else { - # Find next opening brace, closing brace or pipe - $search = $searchBase; - if ( $stack->top === false ) { - $currentClosing = ''; - } else { - $currentClosing = $stack->top->close; - $search .= $currentClosing; - } - if ( $findPipe ) { - $search .= '|'; - } - if ( $findEquals ) { - // First equals will be for the template - $search .= '='; - } - $rule = null; - # Output literal section, advance input counter - $literalLength = strcspn( $text, $search, $i ); - if ( $literalLength > 0 ) { - $accum .= htmlspecialchars( substr( $text, $i, $literalLength ) ); - $i += $literalLength; - } - if ( $i >= strlen( $text ) ) { - if ( $currentClosing == "\n" ) { - // Do a past-the-end run to finish off the heading - $curChar = ''; - $found = 'line-end'; - } else { - # All done - break; - } - } else { - $curChar = $text[$i]; - if ( $curChar == '|' ) { - $found = 'pipe'; - } elseif ( $curChar == '=' ) { - $found = 'equals'; - } elseif ( $curChar == '<' ) { - $found = 'angle'; - } elseif ( $curChar == "\n" ) { - if ( $inHeading ) { - $found = 'line-end'; - } else { - $found = 'line-start'; - } - } elseif ( $curChar == $currentClosing ) { - $found = 'close'; - } elseif ( isset( $rules[$curChar] ) ) { - $found = 'open'; - $rule = $rules[$curChar]; - } else { - # Some versions of PHP have a strcspn which stops on null characters - # Ignore and continue - ++$i; - continue; - } - } - } - - if ( $found == 'angle' ) { - $matches = false; - // Handle </onlyinclude> - if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) { - $findOnlyinclude = true; - continue; - } - - // Determine element name - if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) { - // Element name missing or not listed - $accum .= '<'; - ++$i; - continue; - } - // Handle comments - if ( isset( $matches[2] ) && $matches[2] == '!--' ) { - // To avoid leaving blank lines, when a comment is both preceded - // and followed by a newline (ignoring spaces), trim leading and - // trailing spaces and one of the newlines. - - // Find the end - $endPos = strpos( $text, '-->', $i + 4 ); - if ( $endPos === false ) { - // Unclosed comment in input, runs to end - $inner = substr( $text, $i ); - $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>'; - $i = strlen( $text ); - } else { - // Search backwards for leading whitespace - $wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0; - // Search forwards for trailing whitespace - // $wsEnd will be the position of the last space - $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 ); - // Eat the line if possible - // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at - // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but - // it's a possible beneficial b/c break. - if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" - && substr( $text, $wsEnd + 1, 1 ) == "\n" ) - { - $startPos = $wsStart; - $endPos = $wsEnd + 1; - // Remove leading whitespace from the end of the accumulator - // Sanity check first though - $wsLength = $i - $wsStart; - if ( $wsLength > 0 && substr( $accum, -$wsLength ) === str_repeat( ' ', $wsLength ) ) { - $accum = substr( $accum, 0, -$wsLength ); - } - // Do a line-start run next time to look for headings after the comment - $fakeLineStart = true; - } else { - // No line to eat, just take the comment itself - $startPos = $i; - $endPos += 2; - } - - if ( $stack->top ) { - $part = $stack->top->getCurrentPart(); - if ( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) { - // Comments abutting, no change in visual end - $part->commentEnd = $wsEnd; - } else { - $part->visualEnd = $wsStart; - $part->commentEnd = $endPos; - } - } - $i = $endPos + 1; - $inner = substr( $text, $startPos, $endPos - $startPos + 1 ); - $accum .= '<comment>' . htmlspecialchars( $inner ) . '</comment>'; - } - continue; - } - $name = $matches[1]; - $attrStart = $i + strlen( $name ) + 1; - - // Find end of tag - $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart ); - if ( $tagEndPos === false ) { - // Infinite backtrack - // Disable tag search to prevent worst-case O(N^2) performance - $noMoreGT = true; - $accum .= '<'; - ++$i; - continue; - } - - // Handle ignored tags - if ( in_array( $name, $ignoredTags ) ) { - $accum .= '<ignore>' . htmlspecialchars( substr( $text, $i, $tagEndPos - $i + 1 ) ) . '</ignore>'; - $i = $tagEndPos + 1; - continue; - } - - $tagStartPos = $i; - if ( $text[$tagEndPos-1] == '/' ) { - $attrEnd = $tagEndPos - 1; - $inner = null; - $i = $tagEndPos + 1; - $close = ''; - } else { - $attrEnd = $tagEndPos; - // Find closing tag - if ( preg_match( "/<\/$name\s*>/i", $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) { - $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); - $i = $matches[0][1] + strlen( $matches[0][0] ); - $close = '<close>' . htmlspecialchars( $matches[0][0] ) . '</close>'; - } else { - // No end tag -- let it run out to the end of the text. - $inner = substr( $text, $tagEndPos + 1 ); - $i = strlen( $text ); - $close = ''; - } - } - // <includeonly> and <noinclude> just become <ignore> tags - if ( in_array( $name, $ignoredElements ) ) { - $accum .= '<ignore>' . htmlspecialchars( substr( $text, $tagStartPos, $i - $tagStartPos ) ) - . '</ignore>'; - continue; - } - - $accum .= '<ext>'; - if ( $attrEnd <= $attrStart ) { - $attr = ''; - } else { - $attr = substr( $text, $attrStart, $attrEnd - $attrStart ); - } - $accum .= '<name>' . htmlspecialchars( $name ) . '</name>' . - // Note that the attr element contains the whitespace between name and attribute, - // this is necessary for precise reconstruction during pre-save transform. - '<attr>' . htmlspecialchars( $attr ) . '</attr>'; - if ( $inner !== null ) { - $accum .= '<inner>' . htmlspecialchars( $inner ) . '</inner>'; - } - $accum .= $close . '</ext>'; - } - - elseif ( $found == 'line-start' ) { - // Is this the start of a heading? - // Line break belongs before the heading element in any case - if ( $fakeLineStart ) { - $fakeLineStart = false; - } else { - $accum .= $curChar; - $i++; - } - - $count = strspn( $text, '=', $i, 6 ); - if ( $count == 1 && $findEquals ) { - // DWIM: This looks kind of like a name/value separator - // Let's let the equals handler have it and break the potential heading - // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex. - } elseif ( $count > 0 ) { - $piece = array( - 'open' => "\n", - 'close' => "\n", - 'parts' => array( new PPDPart( str_repeat( '=', $count ) ) ), - 'startPos' => $i, - 'count' => $count ); - $stack->push( $piece ); - $accum =& $stack->getAccum(); - extract( $stack->getFlags() ); - $i += $count; - } - } - - elseif ( $found == 'line-end' ) { - $piece = $stack->top; - // A heading must be open, otherwise \n wouldn't have been in the search list - assert( $piece->open == "\n" ); - $part = $piece->getCurrentPart(); - // Search back through the input to see if it has a proper close - // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient - $wsLength = strspn( $revText, " \t", strlen( $text ) - $i ); - $searchStart = $i - $wsLength; - if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { - // Comment found at line end - // Search for equals signs before the comment - $searchStart = $part->visualEnd; - $searchStart -= strspn( $revText, " \t", strlen( $text ) - $searchStart ); - } - $count = $piece->count; - $equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart ); - if ( $equalsLength > 0 ) { - if ( $i - $equalsLength == $piece->startPos ) { - // This is just a single string of equals signs on its own line - // Replicate the doHeadings behaviour /={count}(.+)={count}/ - // First find out how many equals signs there really are (don't stop at 6) - $count = $equalsLength; - if ( $count < 3 ) { - $count = 0; - } else { - $count = min( 6, intval( ( $count - 1 ) / 2 ) ); - } - } else { - $count = min( $equalsLength, $count ); - } - if ( $count > 0 ) { - // Normal match, output <h> - $element = "<h level=\"$count\" i=\"$headingIndex\">$accum</h>"; - $headingIndex++; - } else { - // Single equals sign on its own line, count=0 - $element = $accum; - } - } else { - // No match, no <h>, just pass down the inner text - $element = $accum; - } - // Unwind the stack - $stack->pop(); - $accum =& $stack->getAccum(); - extract( $stack->getFlags() ); - - // Append the result to the enclosing accumulator - $accum .= $element; - // Note that we do NOT increment the input pointer. - // This is because the closing linebreak could be the opening linebreak of - // another heading. Infinite loops are avoided because the next iteration MUST - // hit the heading open case above, which unconditionally increments the - // input pointer. - } - - elseif ( $found == 'open' ) { - # count opening brace characters - $count = strspn( $text, $curChar, $i ); - - # we need to add to stack only if opening brace count is enough for one of the rules - if ( $count >= $rule['min'] ) { - # Add it to the stack - $piece = array( - 'open' => $curChar, - 'close' => $rule['end'], - 'count' => $count, - 'lineStart' => ($i > 0 && $text[$i-1] == "\n"), - ); - - $stack->push( $piece ); - $accum =& $stack->getAccum(); - extract( $stack->getFlags() ); - } else { - # Add literal brace(s) - $accum .= htmlspecialchars( str_repeat( $curChar, $count ) ); - } - $i += $count; - } - - elseif ( $found == 'close' ) { - $piece = $stack->top; - # lets check if there are enough characters for closing brace - $maxCount = $piece->count; - $count = strspn( $text, $curChar, $i, $maxCount ); - - # check for maximum matching characters (if there are 5 closing - # characters, we will probably need only 3 - depending on the rules) - $matchingCount = 0; - $rule = $rules[$piece->open]; - if ( $count > $rule['max'] ) { - # The specified maximum exists in the callback array, unless the caller - # has made an error - $matchingCount = $rule['max']; - } else { - # Count is less than the maximum - # Skip any gaps in the callback array to find the true largest match - # Need to use array_key_exists not isset because the callback can be null - $matchingCount = $count; - while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) { - --$matchingCount; - } - } - - if ($matchingCount <= 0) { - # No matching element found in callback array - # Output a literal closing brace and continue - $accum .= htmlspecialchars( str_repeat( $curChar, $count ) ); - $i += $count; - continue; - } - $name = $rule['names'][$matchingCount]; - if ( $name === null ) { - // No element, just literal text - $element = $piece->breakSyntax( $matchingCount ) . str_repeat( $rule['end'], $matchingCount ); - } else { - # Create XML element - # Note: $parts is already XML, does not need to be encoded further - $parts = $piece->parts; - $title = $parts[0]->out; - unset( $parts[0] ); - - # The invocation is at the start of the line if lineStart is set in - # the stack, and all opening brackets are used up. - if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) { - $attr = ' lineStart="1"'; - } else { - $attr = ''; - } - - $element = "<$name$attr>"; - $element .= "<title>$title</title>"; - $argIndex = 1; - foreach ( $parts as $partIndex => $part ) { - if ( isset( $part->eqpos ) ) { - $argName = substr( $part->out, 0, $part->eqpos ); - $argValue = substr( $part->out, $part->eqpos + 1 ); - $element .= "<part><name>$argName</name>=<value>$argValue</value></part>"; - } else { - $element .= "<part><name index=\"$argIndex\" /><value>{$part->out}</value></part>"; - $argIndex++; - } - } - $element .= "</$name>"; - } - - # Advance input pointer - $i += $matchingCount; - - # Unwind the stack - $stack->pop(); - $accum =& $stack->getAccum(); - - # Re-add the old stack element if it still has unmatched opening characters remaining - if ($matchingCount < $piece->count) { - $piece->parts = array( new PPDPart ); - $piece->count -= $matchingCount; - # do we still qualify for any callback with remaining count? - $names = $rules[$piece->open]['names']; - $skippedBraces = 0; - $enclosingAccum =& $accum; - while ( $piece->count ) { - if ( array_key_exists( $piece->count, $names ) ) { - $stack->push( $piece ); - $accum =& $stack->getAccum(); - break; - } - --$piece->count; - $skippedBraces ++; - } - $enclosingAccum .= str_repeat( $piece->open, $skippedBraces ); - } - - extract( $stack->getFlags() ); - - # Add XML element to the enclosing accumulator - $accum .= $element; - } - - elseif ( $found == 'pipe' ) { - $findEquals = true; // shortcut for getFlags() - $stack->addPart(); - $accum =& $stack->getAccum(); - ++$i; - } - - elseif ( $found == 'equals' ) { - $findEquals = false; // shortcut for getFlags() - $stack->getCurrentPart()->eqpos = strlen( $accum ); - $accum .= '='; - ++$i; - } - } - - # Output any remaining unclosed brackets - foreach ( $stack->stack as $piece ) { - $stack->rootAccum .= $piece->breakSyntax(); - } - $stack->rootAccum .= '</root>'; - $xml = $stack->rootAccum; - - wfProfileOut( __METHOD__.'-makexml' ); - wfProfileIn( __METHOD__.'-loadXML' ); - $dom = new DOMDocument; - wfSuppressWarnings(); - $result = $dom->loadXML( $xml ); - wfRestoreWarnings(); - if ( !$result ) { - // Try running the XML through UtfNormal to get rid of invalid characters - $xml = UtfNormal::cleanUp( $xml ); - $result = $dom->loadXML( $xml ); - if ( !$result ) { - throw new MWException( __METHOD__.' generated invalid XML' ); - } - } - $obj = new PPNode_DOM( $dom->documentElement ); - wfProfileOut( __METHOD__.'-loadXML' ); - wfProfileOut( __METHOD__ ); - return $obj; - } -} - -/** - * Stack class to help Preprocessor::preprocessToObj() - */ -class PPDStack { - var $stack, $rootAccum, $top; - var $out; - var $elementClass = 'PPDStackElement'; - - static $false = false; - - function __construct() { - $this->stack = array(); - $this->top = false; - $this->rootAccum = ''; - $this->accum =& $this->rootAccum; - } - - function count() { - return count( $this->stack ); - } - - function &getAccum() { - return $this->accum; - } - - function getCurrentPart() { - if ( $this->top === false ) { - return false; - } else { - return $this->top->getCurrentPart(); - } - } - - function push( $data ) { - if ( $data instanceof $this->elementClass ) { - $this->stack[] = $data; - } else { - $class = $this->elementClass; - $this->stack[] = new $class( $data ); - } - $this->top = $this->stack[ count( $this->stack ) - 1 ]; - $this->accum =& $this->top->getAccum(); - } - - function pop() { - if ( !count( $this->stack ) ) { - throw new MWException( __METHOD__.': no elements remaining' ); - } - $temp = array_pop( $this->stack ); - - if ( count( $this->stack ) ) { - $this->top = $this->stack[ count( $this->stack ) - 1 ]; - $this->accum =& $this->top->getAccum(); - } else { - $this->top = self::$false; - $this->accum =& $this->rootAccum; - } - return $temp; - } - - function addPart( $s = '' ) { - $this->top->addPart( $s ); - $this->accum =& $this->top->getAccum(); - } - - function getFlags() { - if ( !count( $this->stack ) ) { - return array( - 'findEquals' => false, - 'findPipe' => false, - 'inHeading' => false, - ); - } else { - return $this->top->getFlags(); - } - } -} - -class PPDStackElement { - var $open, // Opening character (\n for heading) - $close, // Matching closing character - $count, // Number of opening characters found (number of "=" for heading) - $parts, // Array of PPDPart objects describing pipe-separated parts. - $lineStart; // True if the open char appeared at the start of the input line. Not set for headings. - - var $partClass = 'PPDPart'; - - function __construct( $data = array() ) { - $class = $this->partClass; - $this->parts = array( new $class ); - - foreach ( $data as $name => $value ) { - $this->$name = $value; - } - } - - function &getAccum() { - return $this->parts[count($this->parts) - 1]->out; - } - - function addPart( $s = '' ) { - $class = $this->partClass; - $this->parts[] = new $class( $s ); - } - - function getCurrentPart() { - return $this->parts[count($this->parts) - 1]; - } - - function getFlags() { - $partCount = count( $this->parts ); - $findPipe = $this->open != "\n" && $this->open != '['; - return array( - 'findPipe' => $findPipe, - 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ), - 'inHeading' => $this->open == "\n", - ); - } - - /** - * Get the output string that would result if the close is not found. - */ - function breakSyntax( $openingCount = false ) { - if ( $this->open == "\n" ) { - $s = $this->parts[0]->out; - } else { - if ( $openingCount === false ) { - $openingCount = $this->count; - } - $s = str_repeat( $this->open, $openingCount ); - $first = true; - foreach ( $this->parts as $part ) { - if ( $first ) { - $first = false; - } else { - $s .= '|'; - } - $s .= $part->out; - } - } - return $s; - } -} - -class PPDPart { - var $out; // Output accumulator string - - // Optional member variables: - // eqpos Position of equals sign in output accumulator - // commentEnd Past-the-end input pointer for the last comment encountered - // visualEnd Past-the-end input pointer for the end of the accumulator minus comments - - function __construct( $out = '' ) { - $this->out = $out; - } -} - -/** - * An expansion frame, used as a context to expand the result of preprocessToObj() - */ -class PPFrame_DOM implements PPFrame { - var $preprocessor, $parser, $title; - var $titleCache; - - /** - * Hashtable listing templates which are disallowed for expansion in this frame, - * having been encountered previously in parent frames. - */ - var $loopCheckHash; - - /** - * Recursion depth of this frame, top = 0 - */ - var $depth; - - - /** - * Construct a new preprocessor frame. - * @param Preprocessor $preprocessor The parent preprocessor - */ - function __construct( $preprocessor ) { - $this->preprocessor = $preprocessor; - $this->parser = $preprocessor->parser; - $this->title = $this->parser->mTitle; - $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); - $this->loopCheckHash = array(); - $this->depth = 0; - } - - /** - * Create a new child frame - * $args is optionally a multi-root PPNode or array containing the template arguments - */ - function newChild( $args = false, $title = false ) { - $namedArgs = array(); - $numberedArgs = array(); - if ( $title === false ) { - $title = $this->title; - } - if ( $args !== false ) { - $xpath = false; - if ( $args instanceof PPNode ) { - $args = $args->node; - } - foreach ( $args as $arg ) { - if ( !$xpath ) { - $xpath = new DOMXPath( $arg->ownerDocument ); - } - - $nameNodes = $xpath->query( 'name', $arg ); - $value = $xpath->query( 'value', $arg ); - if ( $nameNodes->item( 0 )->hasAttributes() ) { - // Numbered parameter - $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent; - $numberedArgs[$index] = $value->item( 0 ); - unset( $namedArgs[$index] ); - } else { - // Named parameter - $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) ); - $namedArgs[$name] = $value->item( 0 ); - unset( $numberedArgs[$name] ); - } - } - } - return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title ); - } - - function expand( $root, $flags = 0 ) { - if ( is_string( $root ) ) { - return $root; - } - - if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount ) - { - return '<span class="error">Node-count limit exceeded</span>'; - } - - if ( $root instanceof PPNode_DOM ) { - $root = $root->node; - } - if ( $root instanceof DOMDocument ) { - $root = $root->documentElement; - } - - $outStack = array( '', '' ); - $iteratorStack = array( false, $root ); - $indexStack = array( 0, 0 ); - - while ( count( $iteratorStack ) > 1 ) { - $level = count( $outStack ) - 1; - $iteratorNode =& $iteratorStack[ $level ]; - $out =& $outStack[$level]; - $index =& $indexStack[$level]; - - if ( $iteratorNode instanceof PPNode_DOM ) $iteratorNode = $iteratorNode->node; - - if ( is_array( $iteratorNode ) ) { - if ( $index >= count( $iteratorNode ) ) { - // All done with this iterator - $iteratorStack[$level] = false; - $contextNode = false; - } else { - $contextNode = $iteratorNode[$index]; - $index++; - } - } elseif ( $iteratorNode instanceof DOMNodeList ) { - if ( $index >= $iteratorNode->length ) { - // All done with this iterator - $iteratorStack[$level] = false; - $contextNode = false; - } else { - $contextNode = $iteratorNode->item( $index ); - $index++; - } - } else { - // Copy to $contextNode and then delete from iterator stack, - // because this is not an iterator but we do have to execute it once - $contextNode = $iteratorStack[$level]; - $iteratorStack[$level] = false; - } - - if ( $contextNode instanceof PPNode_DOM ) $contextNode = $contextNode->node; - - $newIterator = false; - - if ( $contextNode === false ) { - // nothing to do - } elseif ( is_string( $contextNode ) ) { - $out .= $contextNode; - } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) { - $newIterator = $contextNode; - } elseif ( $contextNode instanceof DOMNode ) { - if ( $contextNode->nodeType == XML_TEXT_NODE ) { - $out .= $contextNode->nodeValue; - } elseif ( $contextNode->nodeName == 'template' ) { - # Double-brace expansion - $xpath = new DOMXPath( $contextNode->ownerDocument ); - $titles = $xpath->query( 'title', $contextNode ); - $title = $titles->item( 0 ); - $parts = $xpath->query( 'part', $contextNode ); - if ( $flags & self::NO_TEMPLATES ) { - $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts ); - } else { - $lineStart = $contextNode->getAttribute( 'lineStart' ); - $params = array( - 'title' => new PPNode_DOM( $title ), - 'parts' => new PPNode_DOM( $parts ), - 'lineStart' => $lineStart ); - $ret = $this->parser->braceSubstitution( $params, $this ); - if ( isset( $ret['object'] ) ) { - $newIterator = $ret['object']; - } else { - $out .= $ret['text']; - } - } - } elseif ( $contextNode->nodeName == 'tplarg' ) { - # Triple-brace expansion - $xpath = new DOMXPath( $contextNode->ownerDocument ); - $titles = $xpath->query( 'title', $contextNode ); - $title = $titles->item( 0 ); - $parts = $xpath->query( 'part', $contextNode ); - if ( $flags & self::NO_ARGS ) { - $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts ); - } else { - $params = array( - 'title' => new PPNode_DOM( $title ), - 'parts' => new PPNode_DOM( $parts ) ); - $ret = $this->parser->argSubstitution( $params, $this ); - if ( isset( $ret['object'] ) ) { - $newIterator = $ret['object']; - } else { - $out .= $ret['text']; - } - } - } elseif ( $contextNode->nodeName == 'comment' ) { - # HTML-style comment - # Remove it in HTML, pre+remove and STRIP_COMMENTS modes - if ( $this->parser->ot['html'] - || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) - || ( $flags & self::STRIP_COMMENTS ) ) - { - $out .= ''; - } - # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result - # Not in RECOVER_COMMENTS mode (extractSections) though - elseif ( $this->parser->ot['wiki'] && ! ( $flags & self::RECOVER_COMMENTS ) ) { - $out .= $this->parser->insertStripItem( $contextNode->textContent ); - } - # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove - else { - $out .= $contextNode->textContent; - } - } elseif ( $contextNode->nodeName == 'ignore' ) { - # Output suppression used by <includeonly> etc. - # OT_WIKI will only respect <ignore> in substed templates. - # The other output types respect it unless NO_IGNORE is set. - # extractSections() sets NO_IGNORE and so never respects it. - if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) { - $out .= $contextNode->textContent; - } else { - $out .= ''; - } - } elseif ( $contextNode->nodeName == 'ext' ) { - # Extension tag - $xpath = new DOMXPath( $contextNode->ownerDocument ); - $names = $xpath->query( 'name', $contextNode ); - $attrs = $xpath->query( 'attr', $contextNode ); - $inners = $xpath->query( 'inner', $contextNode ); - $closes = $xpath->query( 'close', $contextNode ); - $params = array( - 'name' => new PPNode_DOM( $names->item( 0 ) ), - 'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null, - 'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null, - 'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null, - ); - $out .= $this->parser->extensionSubstitution( $params, $this ); - } elseif ( $contextNode->nodeName == 'h' ) { - # Heading - $s = $this->expand( $contextNode->childNodes, $flags ); - - # Insert a heading marker only for <h> children of <root> - # This is to stop extractSections from going over multiple tree levels - if ( $contextNode->parentNode->nodeName == 'root' - && $this->parser->ot['html'] ) - { - # Insert heading index marker - $headingIndex = $contextNode->getAttribute( 'i' ); - $titleText = $this->title->getPrefixedDBkey(); - $this->parser->mHeadings[] = array( $titleText, $headingIndex ); - $serial = count( $this->parser->mHeadings ) - 1; - $marker = "{$this->parser->mUniqPrefix}-h-$serial-{$this->parser->mMarkerSuffix}"; - $count = $contextNode->getAttribute( 'level' ); - $s = substr( $s, 0, $count ) . $marker . substr( $s, $count ); - $this->parser->mStripState->general->setPair( $marker, '' ); - } - $out .= $s; - } else { - # Generic recursive expansion - $newIterator = $contextNode->childNodes; - } - } else { - throw new MWException( __METHOD__.': Invalid parameter type' ); - } - - if ( $newIterator !== false ) { - if ( $newIterator instanceof PPNode_DOM ) { - $newIterator = $newIterator->node; - } - $outStack[] = ''; - $iteratorStack[] = $newIterator; - $indexStack[] = 0; - } elseif ( $iteratorStack[$level] === false ) { - // Return accumulated value to parent - // With tail recursion - while ( $iteratorStack[$level] === false && $level > 0 ) { - $outStack[$level - 1] .= $out; - array_pop( $outStack ); - array_pop( $iteratorStack ); - array_pop( $indexStack ); - $level--; - } - } - } - return $outStack[0]; - } - - function implodeWithFlags( $sep, $flags /*, ... */ ) { - $args = array_slice( func_get_args(), 2 ); - - $first = true; - $s = ''; - foreach ( $args as $root ) { - if ( $root instanceof PPNode_DOM ) $root = $root->node; - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $s .= $sep; - } - $s .= $this->expand( $node, $flags ); - } - } - return $s; - } - - /** - * Implode with no flags specified - * This previously called implodeWithFlags but has now been inlined to reduce stack depth - */ - function implode( $sep /*, ... */ ) { - $args = array_slice( func_get_args(), 1 ); - - $first = true; - $s = ''; - foreach ( $args as $root ) { - if ( $root instanceof PPNode_DOM ) $root = $root->node; - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $s .= $sep; - } - $s .= $this->expand( $node ); - } - } - return $s; - } - - /** - * Makes an object that, when expand()ed, will be the same as one obtained - * with implode() - */ - function virtualImplode( $sep /*, ... */ ) { - $args = array_slice( func_get_args(), 1 ); - $out = array(); - $first = true; - if ( $root instanceof PPNode_DOM ) $root = $root->node; - - foreach ( $args as $root ) { - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $out[] = $sep; - } - $out[] = $node; - } - } - return $out; - } - - /** - * Virtual implode with brackets - */ - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { - $args = array_slice( func_get_args(), 3 ); - $out = array( $start ); - $first = true; - - foreach ( $args as $root ) { - if ( $root instanceof PPNode_DOM ) $root = $root->node; - if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $out[] = $sep; - } - $out[] = $node; - } - } - $out[] = $end; - return $out; - } - - function __toString() { - return 'frame{}'; - } - - function getPDBK( $level = false ) { - if ( $level === false ) { - return $this->title->getPrefixedDBkey(); - } else { - return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false; - } - } - - /** - * Returns true if there are no arguments in this frame - */ - function isEmpty() { - return true; - } - - function getArgument( $name ) { - return false; - } - - /** - * Returns true if the infinite loop check is OK, false if a loop is detected - */ - function loopCheck( $title ) { - return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); - } - - /** - * Return true if the frame is a template frame - */ - function isTemplate() { - return false; - } -} - -/** - * Expansion frame with template arguments - */ -class PPTemplateFrame_DOM extends PPFrame_DOM { - var $numberedArgs, $namedArgs, $parent; - var $numberedExpansionCache, $namedExpansionCache; - - function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) { - $this->preprocessor = $preprocessor; - $this->parser = $preprocessor->parser; - $this->parent = $parent; - $this->numberedArgs = $numberedArgs; - $this->namedArgs = $namedArgs; - $this->title = $title; - $pdbk = $title ? $title->getPrefixedDBkey() : false; - $this->titleCache = $parent->titleCache; - $this->titleCache[] = $pdbk; - $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; - if ( $pdbk !== false ) { - $this->loopCheckHash[$pdbk] = true; - } - $this->depth = $parent->depth + 1; - $this->numberedExpansionCache = $this->namedExpansionCache = array(); - } - - function __toString() { - $s = 'tplframe{'; - $first = true; - $args = $this->numberedArgs + $this->namedArgs; - foreach ( $args as $name => $value ) { - if ( $first ) { - $first = false; - } else { - $s .= ', '; - } - $s .= "\"$name\":\"" . - str_replace( '"', '\\"', $value->ownerDocument->saveXML( $value ) ) . '"'; - } - $s .= '}'; - return $s; - } - /** - * Returns true if there are no arguments in this frame - */ - function isEmpty() { - return !count( $this->numberedArgs ) && !count( $this->namedArgs ); - } - - function getNumberedArgument( $index ) { - if ( !isset( $this->numberedArgs[$index] ) ) { - return false; - } - if ( !isset( $this->numberedExpansionCache[$index] ) ) { - # No trimming for unnamed arguments - $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], self::STRIP_COMMENTS ); - } - return $this->numberedExpansionCache[$index]; - } - - function getNamedArgument( $name ) { - if ( !isset( $this->namedArgs[$name] ) ) { - return false; - } - if ( !isset( $this->namedExpansionCache[$name] ) ) { - # Trim named arguments post-expand, for backwards compatibility - $this->namedExpansionCache[$name] = trim( - $this->parent->expand( $this->namedArgs[$name], self::STRIP_COMMENTS ) ); - } - return $this->namedExpansionCache[$name]; - } - - function getArgument( $name ) { - $text = $this->getNumberedArgument( $name ); - if ( $text === false ) { - $text = $this->getNamedArgument( $name ); - } - return $text; - } - - /** - * Return true if the frame is a template frame - */ - function isTemplate() { - return true; - } -} - -class PPNode_DOM implements PPNode { - var $node; - - function __construct( $node, $xpath = false ) { - $this->node = $node; - } - - function __get( $name ) { - if ( $name == 'xpath' ) { - $this->xpath = new DOMXPath( $this->node->ownerDocument ); - } - return $this->xpath; - } - - function __toString() { - if ( $this->node instanceof DOMNodeList ) { - $s = ''; - foreach ( $this->node as $node ) { - $s .= $node->ownerDocument->saveXML( $node ); - } - } else { - $s = $this->node->ownerDocument->saveXML( $this->node ); - } - return $s; - } - - function getChildren() { - return $this->node->childNodes ? new self( $this->node->childNodes ) : false; - } - - function getFirstChild() { - return $this->node->firstChild ? new self( $this->node->firstChild ) : false; - } - - function getNextSibling() { - return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false; - } - - function getChildrenOfType( $type ) { - return new self( $this->xpath->query( $type, $this->node ) ); - } - - function getLength() { - if ( $this->node instanceof DOMNodeList ) { - return $this->node->length; - } else { - return false; - } - } - - function item( $i ) { - $item = $this->node->item( $i ); - return $item ? new self( $item ) : false; - } - - function getName() { - if ( $this->node instanceof DOMNodeList ) { - return '#nodelist'; - } else { - return $this->node->nodeName; - } - } - - /** - * Split a <part> node into an associative array containing: - * name PPNode name - * index String index - * value PPNode value - */ - function splitArg() { - $names = $this->xpath->query( 'name', $this->node ); - $values = $this->xpath->query( 'value', $this->node ); - if ( !$names->length || !$values->length ) { - throw new MWException( 'Invalid brace node passed to ' . __METHOD__ ); - } - $name = $names->item( 0 ); - $index = $name->getAttribute( 'index' ); - return array( - 'name' => new self( $name ), - 'index' => $index, - 'value' => new self( $values->item( 0 ) ) ); - } - - /** - * Split an <ext> node into an associative array containing name, attr, inner and close - * All values in the resulting array are PPNodes. Inner and close are optional. - */ - function splitExt() { - $names = $this->xpath->query( 'name', $this->node ); - $attrs = $this->xpath->query( 'attr', $this->node ); - $inners = $this->xpath->query( 'inner', $this->node ); - $closes = $this->xpath->query( 'close', $this->node ); - if ( !$names->length || !$attrs->length ) { - throw new MWException( 'Invalid ext node passed to ' . __METHOD__ ); - } - $parts = array( - 'name' => new self( $names->item( 0 ) ), - 'attr' => new self( $attrs->item( 0 ) ) ); - if ( $inners->length ) { - $parts['inner'] = new self( $inners->item( 0 ) ); - } - if ( $closes->length ) { - $parts['close'] = new self( $closes->item( 0 ) ); - } - return $parts; - } - - /** - * Split a <h> node - */ - function splitHeading() { - if ( !$this->nodeName == 'h' ) { - throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); - } - return array( - 'i' => $this->node->getAttribute( 'i' ), - 'level' => $this->node->getAttribute( 'level' ), - 'contents' => $this->getChildren() - ); - } -} diff --git a/includes/Preprocessor_Hash.php b/includes/Preprocessor_Hash.php deleted file mode 100644 index 2034278d..00000000 --- a/includes/Preprocessor_Hash.php +++ /dev/null @@ -1,1471 +0,0 @@ -<?php - -/** - * Differences from DOM schema: - * * attribute nodes are children - * * <h> nodes that aren't at the top are replaced with <possible-h> - */ - -class Preprocessor_Hash implements Preprocessor { - var $parser; - - function __construct( $parser ) { - $this->parser = $parser; - } - - function newFrame() { - return new PPFrame_Hash( $this ); - } - - /** - * Preprocess some wikitext and return the document tree. - * This is the ghost of Parser::replace_variables(). - * - * @param string $text The text to parse - * @param integer flags Bitwise combination of: - * Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being - * included. Default is to assume a direct page view. - * - * The generated DOM tree must depend only on the input text and the flags. - * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899. - * - * Any flag added to the $flags parameter here, or any other parameter liable to cause a - * change in the DOM tree for a given text, must be passed through the section identifier - * in the section edit link and thus back to extractSections(). - * - * The output of this function is currently only cached in process memory, but a persistent - * cache may be implemented at a later date which takes further advantage of these strict - * dependency requirements. - * - * @private - */ - function preprocessToObj( $text, $flags = 0 ) { - wfDebug( __METHOD__."\n" . $text . "\n" ); - wfProfileIn( __METHOD__ ); - - $rules = array( - '{' => array( - 'end' => '}', - 'names' => array( - 2 => 'template', - 3 => 'tplarg', - ), - 'min' => 2, - 'max' => 3, - ), - '[' => array( - 'end' => ']', - 'names' => array( 2 => null ), - 'min' => 2, - 'max' => 2, - ) - ); - - $forInclusion = $flags & Parser::PTD_FOR_INCLUSION; - - $xmlishElements = $this->parser->getStripList(); - $enableOnlyinclude = false; - if ( $forInclusion ) { - $ignoredTags = array( 'includeonly', '/includeonly' ); - $ignoredElements = array( 'noinclude' ); - $xmlishElements[] = 'noinclude'; - if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) { - $enableOnlyinclude = true; - } - } else { - $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' ); - $ignoredElements = array( 'includeonly' ); - $xmlishElements[] = 'includeonly'; - } - $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) ); - - // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset - $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA"; - - $stack = new PPDStack_Hash; - - $searchBase = "[{<\n"; - $revText = strrev( $text ); // For fast reverse searches - - $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start - $accum =& $stack->getAccum(); # Current accumulator - $findEquals = false; # True to find equals signs in arguments - $findPipe = false; # True to take notice of pipe characters - $headingIndex = 1; - $inHeading = false; # True if $i is inside a possible heading - $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i - $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude> - $fakeLineStart = true; # Do a line-start run without outputting an LF character - - while ( true ) { - //$this->memCheck(); - - if ( $findOnlyinclude ) { - // Ignore all input up to the next <onlyinclude> - $startPos = strpos( $text, '<onlyinclude>', $i ); - if ( $startPos === false ) { - // Ignored section runs to the end - $accum->addNodeWithText( 'ignore', substr( $text, $i ) ); - break; - } - $tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end - $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i ) ); - $i = $tagEndPos; - $findOnlyinclude = false; - } - - if ( $fakeLineStart ) { - $found = 'line-start'; - $curChar = ''; - } else { - # Find next opening brace, closing brace or pipe - $search = $searchBase; - if ( $stack->top === false ) { - $currentClosing = ''; - } else { - $currentClosing = $stack->top->close; - $search .= $currentClosing; - } - if ( $findPipe ) { - $search .= '|'; - } - if ( $findEquals ) { - // First equals will be for the template - $search .= '='; - } - $rule = null; - # Output literal section, advance input counter - $literalLength = strcspn( $text, $search, $i ); - if ( $literalLength > 0 ) { - $accum->addLiteral( substr( $text, $i, $literalLength ) ); - $i += $literalLength; - } - if ( $i >= strlen( $text ) ) { - if ( $currentClosing == "\n" ) { - // Do a past-the-end run to finish off the heading - $curChar = ''; - $found = 'line-end'; - } else { - # All done - break; - } - } else { - $curChar = $text[$i]; - if ( $curChar == '|' ) { - $found = 'pipe'; - } elseif ( $curChar == '=' ) { - $found = 'equals'; - } elseif ( $curChar == '<' ) { - $found = 'angle'; - } elseif ( $curChar == "\n" ) { - if ( $inHeading ) { - $found = 'line-end'; - } else { - $found = 'line-start'; - } - } elseif ( $curChar == $currentClosing ) { - $found = 'close'; - } elseif ( isset( $rules[$curChar] ) ) { - $found = 'open'; - $rule = $rules[$curChar]; - } else { - # Some versions of PHP have a strcspn which stops on null characters - # Ignore and continue - ++$i; - continue; - } - } - } - - if ( $found == 'angle' ) { - $matches = false; - // Handle </onlyinclude> - if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) { - $findOnlyinclude = true; - continue; - } - - // Determine element name - if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) { - // Element name missing or not listed - $accum->addLiteral( '<' ); - ++$i; - continue; - } - // Handle comments - if ( isset( $matches[2] ) && $matches[2] == '!--' ) { - // To avoid leaving blank lines, when a comment is both preceded - // and followed by a newline (ignoring spaces), trim leading and - // trailing spaces and one of the newlines. - - // Find the end - $endPos = strpos( $text, '-->', $i + 4 ); - if ( $endPos === false ) { - // Unclosed comment in input, runs to end - $inner = substr( $text, $i ); - $accum->addNodeWithText( 'comment', $inner ); - $i = strlen( $text ); - } else { - // Search backwards for leading whitespace - $wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0; - // Search forwards for trailing whitespace - // $wsEnd will be the position of the last space - $wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 ); - // Eat the line if possible - // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at - // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but - // it's a possible beneficial b/c break. - if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n" - && substr( $text, $wsEnd + 1, 1 ) == "\n" ) - { - $startPos = $wsStart; - $endPos = $wsEnd + 1; - // Remove leading whitespace from the end of the accumulator - // Sanity check first though - $wsLength = $i - $wsStart; - if ( $wsLength > 0 - && $accum->lastNode instanceof PPNode_Hash_Text - && substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) ) - { - $accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength ); - } - // Do a line-start run next time to look for headings after the comment - $fakeLineStart = true; - } else { - // No line to eat, just take the comment itself - $startPos = $i; - $endPos += 2; - } - - if ( $stack->top ) { - $part = $stack->top->getCurrentPart(); - if ( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) { - // Comments abutting, no change in visual end - $part->commentEnd = $wsEnd; - } else { - $part->visualEnd = $wsStart; - $part->commentEnd = $endPos; - } - } - $i = $endPos + 1; - $inner = substr( $text, $startPos, $endPos - $startPos + 1 ); - $accum->addNodeWithText( 'comment', $inner ); - } - continue; - } - $name = $matches[1]; - $attrStart = $i + strlen( $name ) + 1; - - // Find end of tag - $tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart ); - if ( $tagEndPos === false ) { - // Infinite backtrack - // Disable tag search to prevent worst-case O(N^2) performance - $noMoreGT = true; - $accum->addLiteral( '<' ); - ++$i; - continue; - } - - // Handle ignored tags - if ( in_array( $name, $ignoredTags ) ) { - $accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i + 1 ) ); - $i = $tagEndPos + 1; - continue; - } - - $tagStartPos = $i; - if ( $text[$tagEndPos-1] == '/' ) { - // Short end tag - $attrEnd = $tagEndPos - 1; - $inner = null; - $i = $tagEndPos + 1; - $close = null; - } else { - $attrEnd = $tagEndPos; - // Find closing tag - if ( preg_match( "/<\/$name\s*>/i", $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) { - $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); - $i = $matches[0][1] + strlen( $matches[0][0] ); - $close = $matches[0][0]; - } else { - // No end tag -- let it run out to the end of the text. - $inner = substr( $text, $tagEndPos + 1 ); - $i = strlen( $text ); - $close = null; - } - } - // <includeonly> and <noinclude> just become <ignore> tags - if ( in_array( $name, $ignoredElements ) ) { - $accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) ); - continue; - } - - if ( $attrEnd <= $attrStart ) { - $attr = ''; - } else { - // Note that the attr element contains the whitespace between name and attribute, - // this is necessary for precise reconstruction during pre-save transform. - $attr = substr( $text, $attrStart, $attrEnd - $attrStart ); - } - - $extNode = new PPNode_Hash_Tree( 'ext' ); - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'name', $name ) ); - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'attr', $attr ) ); - if ( $inner !== null ) { - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'inner', $inner ) ); - } - if ( $close !== null ) { - $extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) ); - } - $accum->addNode( $extNode ); - } - - elseif ( $found == 'line-start' ) { - // Is this the start of a heading? - // Line break belongs before the heading element in any case - if ( $fakeLineStart ) { - $fakeLineStart = false; - } else { - $accum->addLiteral( $curChar ); - $i++; - } - - $count = strspn( $text, '=', $i, 6 ); - if ( $count == 1 && $findEquals ) { - // DWIM: This looks kind of like a name/value separator - // Let's let the equals handler have it and break the potential heading - // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex. - } elseif ( $count > 0 ) { - $piece = array( - 'open' => "\n", - 'close' => "\n", - 'parts' => array( new PPDPart_Hash( str_repeat( '=', $count ) ) ), - 'startPos' => $i, - 'count' => $count ); - $stack->push( $piece ); - $accum =& $stack->getAccum(); - extract( $stack->getFlags() ); - $i += $count; - } - } - - elseif ( $found == 'line-end' ) { - $piece = $stack->top; - // A heading must be open, otherwise \n wouldn't have been in the search list - assert( $piece->open == "\n" ); - $part = $piece->getCurrentPart(); - // Search back through the input to see if it has a proper close - // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient - $wsLength = strspn( $revText, " \t", strlen( $text ) - $i ); - $searchStart = $i - $wsLength; - if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) { - // Comment found at line end - // Search for equals signs before the comment - $searchStart = $part->visualEnd; - $searchStart -= strspn( $revText, " \t", strlen( $text ) - $searchStart ); - } - $count = $piece->count; - $equalsLength = strspn( $revText, '=', strlen( $text ) - $searchStart ); - if ( $equalsLength > 0 ) { - if ( $i - $equalsLength == $piece->startPos ) { - // This is just a single string of equals signs on its own line - // Replicate the doHeadings behaviour /={count}(.+)={count}/ - // First find out how many equals signs there really are (don't stop at 6) - $count = $equalsLength; - if ( $count < 3 ) { - $count = 0; - } else { - $count = min( 6, intval( ( $count - 1 ) / 2 ) ); - } - } else { - $count = min( $equalsLength, $count ); - } - if ( $count > 0 ) { - // Normal match, output <h> - $element = new PPNode_Hash_Tree( 'possible-h' ); - $element->addChild( new PPNode_Hash_Attr( 'level', $count ) ); - $element->addChild( new PPNode_Hash_Attr( 'i', $headingIndex++ ) ); - $element->lastChild->nextSibling = $accum->firstNode; - $element->lastChild = $accum->lastNode; - } else { - // Single equals sign on its own line, count=0 - $element = $accum; - } - } else { - // No match, no <h>, just pass down the inner text - $element = $accum; - } - // Unwind the stack - $stack->pop(); - $accum =& $stack->getAccum(); - extract( $stack->getFlags() ); - - // Append the result to the enclosing accumulator - if ( $element instanceof PPNode ) { - $accum->addNode( $element ); - } else { - $accum->addAccum( $element ); - } - // Note that we do NOT increment the input pointer. - // This is because the closing linebreak could be the opening linebreak of - // another heading. Infinite loops are avoided because the next iteration MUST - // hit the heading open case above, which unconditionally increments the - // input pointer. - } - - elseif ( $found == 'open' ) { - # count opening brace characters - $count = strspn( $text, $curChar, $i ); - - # we need to add to stack only if opening brace count is enough for one of the rules - if ( $count >= $rule['min'] ) { - # Add it to the stack - $piece = array( - 'open' => $curChar, - 'close' => $rule['end'], - 'count' => $count, - 'lineStart' => ($i > 0 && $text[$i-1] == "\n"), - ); - - $stack->push( $piece ); - $accum =& $stack->getAccum(); - extract( $stack->getFlags() ); - } else { - # Add literal brace(s) - $accum->addLiteral( str_repeat( $curChar, $count ) ); - } - $i += $count; - } - - elseif ( $found == 'close' ) { - $piece = $stack->top; - # lets check if there are enough characters for closing brace - $maxCount = $piece->count; - $count = strspn( $text, $curChar, $i, $maxCount ); - - # check for maximum matching characters (if there are 5 closing - # characters, we will probably need only 3 - depending on the rules) - $matchingCount = 0; - $rule = $rules[$piece->open]; - if ( $count > $rule['max'] ) { - # The specified maximum exists in the callback array, unless the caller - # has made an error - $matchingCount = $rule['max']; - } else { - # Count is less than the maximum - # Skip any gaps in the callback array to find the true largest match - # Need to use array_key_exists not isset because the callback can be null - $matchingCount = $count; - while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) { - --$matchingCount; - } - } - - if ($matchingCount <= 0) { - # No matching element found in callback array - # Output a literal closing brace and continue - $accum->addLiteral( str_repeat( $curChar, $count ) ); - $i += $count; - continue; - } - $name = $rule['names'][$matchingCount]; - if ( $name === null ) { - // No element, just literal text - $element = $piece->breakSyntax( $matchingCount ); - $element->addLiteral( str_repeat( $rule['end'], $matchingCount ) ); - } else { - # Create XML element - # Note: $parts is already XML, does not need to be encoded further - $parts = $piece->parts; - $titleAccum = $parts[0]->out; - unset( $parts[0] ); - - $element = new PPNode_Hash_Tree( $name ); - - # The invocation is at the start of the line if lineStart is set in - # the stack, and all opening brackets are used up. - if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) { - $element->addChild( new PPNode_Hash_Attr( 'lineStart', 1 ) ); - } - $titleNode = new PPNode_Hash_Tree( 'title' ); - $titleNode->firstChild = $titleAccum->firstNode; - $titleNode->lastChild = $titleAccum->lastNode; - $element->addChild( $titleNode ); - $argIndex = 1; - foreach ( $parts as $partIndex => $part ) { - if ( isset( $part->eqpos ) ) { - // Find equals - $lastNode = false; - for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) { - if ( $node === $part->eqpos ) { - break; - } - $lastNode = $node; - } - if ( !$node ) { - throw new MWException( __METHOD__. ': eqpos not found' ); - } - if ( $node->name !== 'equals' ) { - throw new MWException( __METHOD__ .': eqpos is not equals' ); - } - $equalsNode = $node; - - // Construct name node - $nameNode = new PPNode_Hash_Tree( 'name' ); - if ( $lastNode !== false ) { - $lastNode->nextSibling = false; - $nameNode->firstChild = $part->out->firstNode; - $nameNode->lastChild = $lastNode; - } - - // Construct value node - $valueNode = new PPNode_Hash_Tree( 'value' ); - if ( $equalsNode->nextSibling !== false ) { - $valueNode->firstChild = $equalsNode->nextSibling; - $valueNode->lastChild = $part->out->lastNode; - } - $partNode = new PPNode_Hash_Tree( 'part' ); - $partNode->addChild( $nameNode ); - $partNode->addChild( $equalsNode->firstChild ); - $partNode->addChild( $valueNode ); - $element->addChild( $partNode ); - } else { - $partNode = new PPNode_Hash_Tree( 'part' ); - $nameNode = new PPNode_Hash_Tree( 'name' ); - $nameNode->addChild( new PPNode_Hash_Attr( 'index', $argIndex++ ) ); - $valueNode = new PPNode_Hash_Tree( 'value' ); - $valueNode->firstChild = $part->out->firstNode; - $valueNode->lastChild = $part->out->lastNode; - $partNode->addChild( $nameNode ); - $partNode->addChild( $valueNode ); - $element->addChild( $partNode ); - } - } - } - - # Advance input pointer - $i += $matchingCount; - - # Unwind the stack - $stack->pop(); - $accum =& $stack->getAccum(); - - # Re-add the old stack element if it still has unmatched opening characters remaining - if ($matchingCount < $piece->count) { - $piece->parts = array( new PPDPart_Hash ); - $piece->count -= $matchingCount; - # do we still qualify for any callback with remaining count? - $names = $rules[$piece->open]['names']; - $skippedBraces = 0; - $enclosingAccum =& $accum; - while ( $piece->count ) { - if ( array_key_exists( $piece->count, $names ) ) { - $stack->push( $piece ); - $accum =& $stack->getAccum(); - break; - } - --$piece->count; - $skippedBraces ++; - } - $enclosingAccum->addLiteral( str_repeat( $piece->open, $skippedBraces ) ); - } - - extract( $stack->getFlags() ); - - # Add XML element to the enclosing accumulator - if ( $element instanceof PPNode ) { - $accum->addNode( $element ); - } else { - $accum->addAccum( $element ); - } - } - - elseif ( $found == 'pipe' ) { - $findEquals = true; // shortcut for getFlags() - $stack->addPart(); - $accum =& $stack->getAccum(); - ++$i; - } - - elseif ( $found == 'equals' ) { - $findEquals = false; // shortcut for getFlags() - $accum->addNodeWithText( 'equals', '=' ); - $stack->getCurrentPart()->eqpos = $accum->lastNode; - ++$i; - } - } - - # Output any remaining unclosed brackets - foreach ( $stack->stack as $piece ) { - $stack->rootAccum->addAccum( $piece->breakSyntax() ); - } - - # Enable top-level headings - for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) { - if ( isset( $node->name ) && $node->name === 'possible-h' ) { - $node->name = 'h'; - } - } - - $rootNode = new PPNode_Hash_Tree( 'root' ); - $rootNode->firstChild = $stack->rootAccum->firstNode; - $rootNode->lastChild = $stack->rootAccum->lastNode; - wfProfileOut( __METHOD__ ); - return $rootNode; - } -} - -/** - * Stack class to help Preprocessor::preprocessToObj() - */ -class PPDStack_Hash extends PPDStack { - function __construct() { - $this->elementClass = 'PPDStackElement_Hash'; - parent::__construct(); - $this->rootAccum = new PPDAccum_Hash; - } -} - -class PPDStackElement_Hash extends PPDStackElement { - function __construct( $data = array() ) { - $this->partClass = 'PPDPart_Hash'; - parent::__construct( $data ); - } - - /** - * Get the accumulator that would result if the close is not found. - */ - function breakSyntax( $openingCount = false ) { - if ( $this->open == "\n" ) { - $accum = $this->parts[0]->out; - } else { - if ( $openingCount === false ) { - $openingCount = $this->count; - } - $accum = new PPDAccum_Hash; - $accum->addLiteral( str_repeat( $this->open, $openingCount ) ); - $first = true; - foreach ( $this->parts as $part ) { - if ( $first ) { - $first = false; - } else { - $accum->addLiteral( '|' ); - } - $accum->addAccum( $part->out ); - } - } - return $accum; - } -} - -class PPDPart_Hash extends PPDPart { - function __construct( $out = '' ) { - $accum = new PPDAccum_Hash; - if ( $out !== '' ) { - $accum->addLiteral( $out ); - } - parent::__construct( $accum ); - } -} - -class PPDAccum_Hash { - var $firstNode, $lastNode; - - function __construct() { - $this->firstNode = $this->lastNode = false; - } - - /** - * Append a string literal - */ - function addLiteral( $s ) { - if ( $this->lastNode === false ) { - $this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s ); - } elseif ( $this->lastNode instanceof PPNode_Hash_Text ) { - $this->lastNode->value .= $s; - } else { - $this->lastNode->nextSibling = new PPNode_Hash_Text( $s ); - $this->lastNode = $this->lastNode->nextSibling; - } - } - - /** - * Append a PPNode - */ - function addNode( PPNode $node ) { - if ( $this->lastNode === false ) { - $this->firstNode = $this->lastNode = $node; - } else { - $this->lastNode->nextSibling = $node; - $this->lastNode = $node; - } - } - - /** - * Append a tree node with text contents - */ - function addNodeWithText( $name, $value ) { - $node = PPNode_Hash_Tree::newWithText( $name, $value ); - $this->addNode( $node ); - } - - /** - * Append a PPAccum_Hash - * Takes over ownership of the nodes in the source argument. These nodes may - * subsequently be modified, especially nextSibling. - */ - function addAccum( $accum ) { - if ( $accum->lastNode === false ) { - // nothing to add - } elseif ( $this->lastNode === false ) { - $this->firstNode = $accum->firstNode; - $this->lastNode = $accum->lastNode; - } else { - $this->lastNode->nextSibling = $accum->firstNode; - $this->lastNode = $accum->lastNode; - } - } -} - -/** - * An expansion frame, used as a context to expand the result of preprocessToObj() - */ -class PPFrame_Hash implements PPFrame { - var $preprocessor, $parser, $title; - var $titleCache; - - /** - * Hashtable listing templates which are disallowed for expansion in this frame, - * having been encountered previously in parent frames. - */ - var $loopCheckHash; - - /** - * Recursion depth of this frame, top = 0 - */ - var $depth; - - - /** - * Construct a new preprocessor frame. - * @param Preprocessor $preprocessor The parent preprocessor - */ - function __construct( $preprocessor ) { - $this->preprocessor = $preprocessor; - $this->parser = $preprocessor->parser; - $this->title = $this->parser->mTitle; - $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false ); - $this->loopCheckHash = array(); - $this->depth = 0; - } - - /** - * Create a new child frame - * $args is optionally a multi-root PPNode or array containing the template arguments - */ - function newChild( $args = false, $title = false ) { - $namedArgs = array(); - $numberedArgs = array(); - if ( $title === false ) { - $title = $this->title; - } - if ( $args !== false ) { - $xpath = false; - if ( $args instanceof PPNode_Hash_Array ) { - $args = $args->value; - } elseif ( !is_array( $args ) ) { - throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' ); - } - foreach ( $args as $arg ) { - $bits = $arg->splitArg(); - if ( $bits['index'] !== '' ) { - // Numbered parameter - $numberedArgs[$bits['index']] = $bits['value']; - unset( $namedArgs[$bits['index']] ); - } else { - // Named parameter - $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) ); - $namedArgs[$name] = $bits['value']; - unset( $numberedArgs[$name] ); - } - } - } - return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title ); - } - - function expand( $root, $flags = 0 ) { - if ( is_string( $root ) ) { - return $root; - } - - if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount ) - { - return '<span class="error">Node-count limit exceeded</span>'; - } - - $outStack = array( '', '' ); - $iteratorStack = array( false, $root ); - $indexStack = array( 0, 0 ); - - while ( count( $iteratorStack ) > 1 ) { - $level = count( $outStack ) - 1; - $iteratorNode =& $iteratorStack[ $level ]; - $out =& $outStack[$level]; - $index =& $indexStack[$level]; - - if ( is_array( $iteratorNode ) ) { - if ( $index >= count( $iteratorNode ) ) { - // All done with this iterator - $iteratorStack[$level] = false; - $contextNode = false; - } else { - $contextNode = $iteratorNode[$index]; - $index++; - } - } elseif ( $iteratorNode instanceof PPNode_Hash_Array ) { - if ( $index >= $iteratorNode->getLength() ) { - // All done with this iterator - $iteratorStack[$level] = false; - $contextNode = false; - } else { - $contextNode = $iteratorNode->item( $index ); - $index++; - } - } else { - // Copy to $contextNode and then delete from iterator stack, - // because this is not an iterator but we do have to execute it once - $contextNode = $iteratorStack[$level]; - $iteratorStack[$level] = false; - } - - $newIterator = false; - - if ( $contextNode === false ) { - // nothing to do - } elseif ( is_string( $contextNode ) ) { - $out .= $contextNode; - } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_Hash_Array ) { - $newIterator = $contextNode; - } elseif ( $contextNode instanceof PPNode_Hash_Attr ) { - // No output - } elseif ( $contextNode instanceof PPNode_Hash_Text ) { - $out .= $contextNode->value; - } elseif ( $contextNode instanceof PPNode_Hash_Tree ) { - if ( $contextNode->name == 'template' ) { - # Double-brace expansion - $bits = $contextNode->splitTemplate(); - if ( $flags & self::NO_TEMPLATES ) { - $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] ); - } else { - $ret = $this->parser->braceSubstitution( $bits, $this ); - if ( isset( $ret['object'] ) ) { - $newIterator = $ret['object']; - } else { - $out .= $ret['text']; - } - } - } elseif ( $contextNode->name == 'tplarg' ) { - # Triple-brace expansion - $bits = $contextNode->splitTemplate(); - if ( $flags & self::NO_ARGS ) { - $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] ); - } else { - $ret = $this->parser->argSubstitution( $bits, $this ); - if ( isset( $ret['object'] ) ) { - $newIterator = $ret['object']; - } else { - $out .= $ret['text']; - } - } - } elseif ( $contextNode->name == 'comment' ) { - # HTML-style comment - # Remove it in HTML, pre+remove and STRIP_COMMENTS modes - if ( $this->parser->ot['html'] - || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() ) - || ( $flags & self::STRIP_COMMENTS ) ) - { - $out .= ''; - } - # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result - # Not in RECOVER_COMMENTS mode (extractSections) though - elseif ( $this->parser->ot['wiki'] && ! ( $flags & self::RECOVER_COMMENTS ) ) { - $out .= $this->parser->insertStripItem( $contextNode->firstChild->value ); - } - # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove - else { - $out .= $contextNode->firstChild->value; - } - } elseif ( $contextNode->name == 'ignore' ) { - # Output suppression used by <includeonly> etc. - # OT_WIKI will only respect <ignore> in substed templates. - # The other output types respect it unless NO_IGNORE is set. - # extractSections() sets NO_IGNORE and so never respects it. - if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & self::NO_IGNORE ) ) { - $out .= $contextNode->firstChild->value; - } else { - //$out .= ''; - } - } elseif ( $contextNode->name == 'ext' ) { - # Extension tag - $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null ); - $out .= $this->parser->extensionSubstitution( $bits, $this ); - } elseif ( $contextNode->name == 'h' ) { - # Heading - if ( $this->parser->ot['html'] ) { - # Expand immediately and insert heading index marker - $s = ''; - for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) { - $s .= $this->expand( $node, $flags ); - } - - $bits = $contextNode->splitHeading(); - $titleText = $this->title->getPrefixedDBkey(); - $this->parser->mHeadings[] = array( $titleText, $bits['i'] ); - $serial = count( $this->parser->mHeadings ) - 1; - $marker = "{$this->parser->mUniqPrefix}-h-$serial-{$this->parser->mMarkerSuffix}"; - $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] ); - $this->parser->mStripState->general->setPair( $marker, '' ); - $out .= $s; - } else { - # Expand in virtual stack - $newIterator = $contextNode->getChildren(); - } - } else { - # Generic recursive expansion - $newIterator = $contextNode->getChildren(); - } - } else { - throw new MWException( __METHOD__.': Invalid parameter type' ); - } - - if ( $newIterator !== false ) { - $outStack[] = ''; - $iteratorStack[] = $newIterator; - $indexStack[] = 0; - } elseif ( $iteratorStack[$level] === false ) { - // Return accumulated value to parent - // With tail recursion - while ( $iteratorStack[$level] === false && $level > 0 ) { - $outStack[$level - 1] .= $out; - array_pop( $outStack ); - array_pop( $iteratorStack ); - array_pop( $indexStack ); - $level--; - } - } - } - return $outStack[0]; - } - - function implodeWithFlags( $sep, $flags /*, ... */ ) { - $args = array_slice( func_get_args(), 2 ); - - $first = true; - $s = ''; - foreach ( $args as $root ) { - if ( $root instanceof PPNode_Hash_Array ) { - $root = $root->value; - } - if ( !is_array( $root ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $s .= $sep; - } - $s .= $this->expand( $node, $flags ); - } - } - return $s; - } - - /** - * Implode with no flags specified - * This previously called implodeWithFlags but has now been inlined to reduce stack depth - */ - function implode( $sep /*, ... */ ) { - $args = array_slice( func_get_args(), 1 ); - - $first = true; - $s = ''; - foreach ( $args as $root ) { - if ( $root instanceof PPNode_Hash_Array ) { - $root = $root->value; - } - if ( !is_array( $root ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $s .= $sep; - } - $s .= $this->expand( $node ); - } - } - return $s; - } - - /** - * Makes an object that, when expand()ed, will be the same as one obtained - * with implode() - */ - function virtualImplode( $sep /*, ... */ ) { - $args = array_slice( func_get_args(), 1 ); - $out = array(); - $first = true; - - foreach ( $args as $root ) { - if ( $root instanceof PPNode_Hash_Array ) { - $root = $root->value; - } - if ( !is_array( $root ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $out[] = $sep; - } - $out[] = $node; - } - } - return new PPNode_Hash_Array( $out ); - } - - /** - * Virtual implode with brackets - */ - function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) { - $args = array_slice( func_get_args(), 3 ); - $out = array( $start ); - $first = true; - - foreach ( $args as $root ) { - if ( $root instanceof PPNode_Hash_Array ) { - $root = $root->value; - } - if ( !is_array( $root ) ) { - $root = array( $root ); - } - foreach ( $root as $node ) { - if ( $first ) { - $first = false; - } else { - $out[] = $sep; - } - $out[] = $node; - } - } - $out[] = $end; - return new PPNode_Hash_Array( $out ); - } - - function __toString() { - return 'frame{}'; - } - - function getPDBK( $level = false ) { - if ( $level === false ) { - return $this->title->getPrefixedDBkey(); - } else { - return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false; - } - } - - /** - * Returns true if there are no arguments in this frame - */ - function isEmpty() { - return true; - } - - function getArgument( $name ) { - return false; - } - - /** - * Returns true if the infinite loop check is OK, false if a loop is detected - */ - function loopCheck( $title ) { - return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] ); - } - - /** - * Return true if the frame is a template frame - */ - function isTemplate() { - return false; - } -} - -/** - * Expansion frame with template arguments - */ -class PPTemplateFrame_Hash extends PPFrame_Hash { - var $numberedArgs, $namedArgs, $parent; - var $numberedExpansionCache, $namedExpansionCache; - - function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) { - $this->preprocessor = $preprocessor; - $this->parser = $preprocessor->parser; - $this->parent = $parent; - $this->numberedArgs = $numberedArgs; - $this->namedArgs = $namedArgs; - $this->title = $title; - $pdbk = $title ? $title->getPrefixedDBkey() : false; - $this->titleCache = $parent->titleCache; - $this->titleCache[] = $pdbk; - $this->loopCheckHash = /*clone*/ $parent->loopCheckHash; - if ( $pdbk !== false ) { - $this->loopCheckHash[$pdbk] = true; - } - $this->depth = $parent->depth + 1; - $this->numberedExpansionCache = $this->namedExpansionCache = array(); - } - - function __toString() { - $s = 'tplframe{'; - $first = true; - $args = $this->numberedArgs + $this->namedArgs; - foreach ( $args as $name => $value ) { - if ( $first ) { - $first = false; - } else { - $s .= ', '; - } - $s .= "\"$name\":\"" . - str_replace( '"', '\\"', $value->__toString() ) . '"'; - } - $s .= '}'; - return $s; - } - /** - * Returns true if there are no arguments in this frame - */ - function isEmpty() { - return !count( $this->numberedArgs ) && !count( $this->namedArgs ); - } - - function getNumberedArgument( $index ) { - if ( !isset( $this->numberedArgs[$index] ) ) { - return false; - } - if ( !isset( $this->numberedExpansionCache[$index] ) ) { - # No trimming for unnamed arguments - $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], self::STRIP_COMMENTS ); - } - return $this->numberedExpansionCache[$index]; - } - - function getNamedArgument( $name ) { - if ( !isset( $this->namedArgs[$name] ) ) { - return false; - } - if ( !isset( $this->namedExpansionCache[$name] ) ) { - # Trim named arguments post-expand, for backwards compatibility - $this->namedExpansionCache[$name] = trim( - $this->parent->expand( $this->namedArgs[$name], self::STRIP_COMMENTS ) ); - } - return $this->namedExpansionCache[$name]; - } - - function getArgument( $name ) { - $text = $this->getNumberedArgument( $name ); - if ( $text === false ) { - $text = $this->getNamedArgument( $name ); - } - return $text; - } - - /** - * Return true if the frame is a template frame - */ - function isTemplate() { - return true; - } -} - -class PPNode_Hash_Tree implements PPNode { - var $name, $firstChild, $lastChild, $nextSibling; - - function __construct( $name ) { - $this->name = $name; - $this->firstChild = $this->lastChild = $this->nextSibling = false; - } - - function __toString() { - $inner = ''; - $attribs = ''; - for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) { - if ( $node instanceof PPNode_Hash_Attr ) { - $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"'; - } else { - $inner .= $node->__toString(); - } - } - if ( $inner === '' ) { - return "<{$this->name}$attribs/>"; - } else { - return "<{$this->name}$attribs>$inner</{$this->name}>"; - } - } - - function newWithText( $name, $text ) { - $obj = new self( $name ); - $obj->addChild( new PPNode_Hash_Text( $text ) ); - return $obj; - } - - function addChild( $node ) { - if ( $this->lastChild === false ) { - $this->firstChild = $this->lastChild = $node; - } else { - $this->lastChild->nextSibling = $node; - $this->lastChild = $node; - } - } - - function getChildren() { - $children = array(); - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { - $children[] = $child; - } - return new PPNode_Hash_Array( $children ); - } - - function getFirstChild() { - return $this->firstChild; - } - - function getNextSibling() { - return $this->nextSibling; - } - - function getChildrenOfType( $name ) { - $children = array(); - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { - if ( isset( $child->name ) && $child->name === $name ) { - $children[] = $name; - } - } - return $children; - } - - function getLength() { return false; } - function item( $i ) { return false; } - - function getName() { - return $this->name; - } - - /** - * Split a <part> node into an associative array containing: - * name PPNode name - * index String index - * value PPNode value - */ - function splitArg() { - $bits = array(); - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { - if ( !isset( $child->name ) ) { - continue; - } - if ( $child->name === 'name' ) { - $bits['name'] = $child; - if ( $child->firstChild instanceof PPNode_Hash_Attr - && $child->firstChild->name === 'index' ) - { - $bits['index'] = $child->firstChild->value; - } - } elseif ( $child->name === 'value' ) { - $bits['value'] = $child; - } - } - - if ( !isset( $bits['name'] ) ) { - throw new MWException( 'Invalid brace node passed to ' . __METHOD__ ); - } - if ( !isset( $bits['index'] ) ) { - $bits['index'] = ''; - } - return $bits; - } - - /** - * Split an <ext> node into an associative array containing name, attr, inner and close - * All values in the resulting array are PPNodes. Inner and close are optional. - */ - function splitExt() { - $bits = array(); - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { - if ( !isset( $child->name ) ) { - continue; - } - if ( $child->name == 'name' ) { - $bits['name'] = $child; - } elseif ( $child->name == 'attr' ) { - $bits['attr'] = $child; - } elseif ( $child->name == 'inner' ) { - $bits['inner'] = $child; - } elseif ( $child->name == 'close' ) { - $bits['close'] = $child; - } - } - if ( !isset( $bits['name'] ) ) { - throw new MWException( 'Invalid ext node passed to ' . __METHOD__ ); - } - return $bits; - } - - /** - * Split an <h> node - */ - function splitHeading() { - if ( $this->name !== 'h' ) { - throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); - } - $bits = array(); - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { - if ( !isset( $child->name ) ) { - continue; - } - if ( $child->name == 'i' ) { - $bits['i'] = $child->value; - } elseif ( $child->name == 'level' ) { - $bits['level'] = $child->value; - } - } - if ( !isset( $bits['i'] ) ) { - throw new MWException( 'Invalid h node passed to ' . __METHOD__ ); - } - return $bits; - } - - /** - * Split a <template> or <tplarg> node - */ - function splitTemplate() { - wfDebug( 'Template: ' . var_export( $this, true ) ); - $parts = array(); - $bits = array( 'lineStart' => '' ); - for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) { - wfDebug( 'Child: ' . var_export( $child, true ) ); - if ( !isset( $child->name ) ) { - continue; - } - if ( $child->name == 'title' ) { - $bits['title'] = $child; - } - if ( $child->name == 'part' ) { - $parts[] = $child; - } - if ( $child->name == 'lineStart' ) { - $bits['lineStart'] = '1'; - } - } - if ( !isset( $bits['title'] ) ) { - throw new MWException( 'Invalid node passed to ' . __METHOD__ ); - } - $bits['parts'] = new PPNode_Hash_Array( $parts ); - return $bits; - } -} - -class PPNode_Hash_Text implements PPNode { - var $value, $nextSibling; - - function __construct( $value ) { - if ( is_object( $value ) ) { - throw new MWException( __CLASS__ . ' given object instead of string' ); - } - $this->value = $value; - } - - function __toString() { - return htmlspecialchars( $this->value ); - } - - function getNextSibling() { - return $this->nextSibling; - } - - function getChildren() { return false; } - function getFirstChild() { return false; } - function getChildrenOfType( $name ) { return false; } - function getLength() { return false; } - function item( $i ) { return false; } - function getName() { return '#text'; } - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } -} - -class PPNode_Hash_Array implements PPNode { - var $value, $nextSibling; - - function __construct( $value ) { - $this->value = $value; - } - - function __toString() { - return var_export( $this, true ); - } - - function getLength() { - return count( $this->value ); - } - - function item( $i ) { - return $this->value[$i]; - } - - function getName() { return '#nodelist'; } - - function getNextSibling() { - return $this->nextSibling; - } - - function getChildren() { return false; } - function getFirstChild() { return false; } - function getChildrenOfType( $name ) { return false; } - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } -} - -class PPNode_Hash_Attr implements PPNode { - var $name, $value, $nextSibling; - - function __construct( $name, $value ) { - $this->name = $name; - $this->value = $value; - } - - function __toString() { - return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>"; - } - - function getName() { - return $this->name; - } - - function getNextSibling() { - return $this->nextSibling; - } - - function getChildren() { return false; } - function getFirstChild() { return false; } - function getChildrenOfType( $name ) { return false; } - function getLength() { return false; } - function item( $i ) { return false; } - function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); } - function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); } -} - diff --git a/includes/Profiling.php b/includes/Profiling.php deleted file mode 100644 index edecc4f3..00000000 --- a/includes/Profiling.php +++ /dev/null @@ -1,353 +0,0 @@ -<?php -/** - * This file is only included if profiling is enabled - * @package MediaWiki - */ - -/** - * @param $functioname name of the function we will profile - */ -function wfProfileIn($functionname) { - global $wgProfiler; - $wgProfiler->profileIn($functionname); -} - -/** - * @param $functioname name of the function we have profiled - */ -function wfProfileOut($functionname = 'missing') { - global $wgProfiler; - $wgProfiler->profileOut($functionname); -} - -function wfGetProfilingOutput($start, $elapsed) { - global $wgProfiler; - return $wgProfiler->getOutput($start, $elapsed); -} - -function wfProfileClose() { - global $wgProfiler; - $wgProfiler->close(); -} - -if (!function_exists('memory_get_usage')) { - # Old PHP or --enable-memory-limit not compiled in - function memory_get_usage() { - return 0; - } -} - -/** - * @todo document - * @package MediaWiki - */ -class Profiler { - var $mStack = array (), $mWorkStack = array (), $mCollated = array (); - var $mCalls = array (), $mTotals = array (); - - function Profiler() - { - // Push an entry for the pre-profile setup time onto the stack - global $wgRequestTime; - if ( !empty( $wgRequestTime ) ) { - $this->mWorkStack[] = array( '-total', 0, $wgRequestTime, 0 ); - $this->mStack[] = array( '-setup', 1, $wgRequestTime, 0, microtime(true), 0 ); - } else { - $this->profileIn( '-total' ); - } - - } - - function profileIn($functionname) { - global $wgDebugFunctionEntry; - if ($wgDebugFunctionEntry && function_exists('wfDebug')) { - wfDebug(str_repeat(' ', count($this->mWorkStack)).'Entering '.$functionname."\n"); - } - $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), $this->getTime(), memory_get_usage()); - } - - function profileOut($functionname) { - $memory = memory_get_usage(); - $time = $this->getTime(); - - global $wgDebugFunctionEntry; - - if ($wgDebugFunctionEntry && function_exists('wfDebug')) { - wfDebug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n"); - } - - $bit = array_pop($this->mWorkStack); - - if (!$bit) { - wfDebug("Profiling error, !\$bit: $functionname\n"); - } else { - //if ($wgDebugProfiling) { - if ($functionname == 'close') { - $message = "Profile section ended by close(): {$bit[0]}"; - wfDebug( "$message\n" ); - $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 ); - } - elseif ($bit[0] != $functionname) { - $message = "Profiling error: in({$bit[0]}), out($functionname)"; - wfDebug( "$message\n" ); - $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 ); - } - //} - $bit[] = $time; - $bit[] = $memory; - $this->mStack[] = $bit; - } - } - - function close() { - while (count($this->mWorkStack)) { - $this->profileOut('close'); - } - } - - function getOutput() { - global $wgDebugFunctionEntry; - $wgDebugFunctionEntry = false; - - if (!count($this->mStack) && !count($this->mCollated)) { - return "No profiling output\n"; - } - $this->close(); - - global $wgProfileCallTree; - if ($wgProfileCallTree) { - return $this->getCallTree(); - } else { - return $this->getFunctionReport(); - } - } - - function getCallTree($start = 0) { - return implode('', array_map(array (& $this, 'getCallTreeLine'), $this->remapCallTree($this->mStack))); - } - - function remapCallTree($stack) { - if (count($stack) < 2) { - return $stack; - } - $outputs = array (); - for ($max = count($stack) - 1; $max > 0;) { - /* Find all items under this entry */ - $level = $stack[$max][1]; - $working = array (); - for ($i = $max -1; $i >= 0; $i --) { - if ($stack[$i][1] > $level) { - $working[] = $stack[$i]; - } else { - break; - } - } - $working = $this->remapCallTree(array_reverse($working)); - $output = array (); - foreach ($working as $item) { - array_push($output, $item); - } - array_unshift($output, $stack[$max]); - $max = $i; - - array_unshift($outputs, $output); - } - $final = array (); - foreach ($outputs as $output) { - foreach ($output as $item) { - $final[] = $item; - } - } - return $final; - } - - function getCallTreeLine($entry) { - list ($fname, $level, $start, $x, $end) = $entry; - $delta = $end - $start; - $space = str_repeat(' ', $level); - - # The ugly double sprintf is to work around a PHP bug, - # which has been fixed in recent releases. - return sprintf( "%10s %s %s\n", - trim( sprintf( "%7.3f", $delta * 1000.0 ) ), - $space, $fname ); - } - - function getTime() { - return microtime(true); - #return $this->getUserTime(); - } - - function getUserTime() { - $ru = getrusage(); - return $ru['ru_utime.tv_sec'].' '.$ru['ru_utime.tv_usec'] / 1e6; - } - - function getFunctionReport() { - $width = 140; - $nameWidth = $width - 65; - $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n"; - $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n"; - $prof = "\nProfiling data\n"; - $prof .= sprintf($titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem'); - $this->mCollated = array (); - $this->mCalls = array (); - $this->mMemory = array (); - - # Estimate profiling overhead - $profileCount = count($this->mStack); - wfProfileIn('-overhead-total'); - for ($i = 0; $i < $profileCount; $i ++) { - wfProfileIn('-overhead-internal'); - wfProfileOut('-overhead-internal'); - } - wfProfileOut('-overhead-total'); - - # First, subtract the overhead! - foreach ($this->mStack as $entry) { - $fname = $entry[0]; - $thislevel = $entry[1]; - $start = $entry[2]; - $end = $entry[4]; - $elapsed = $end - $start; - $memory = $entry[5] - $entry[3]; - - if ($fname == '-overhead-total') { - $overheadTotal[] = $elapsed; - $overheadMemory[] = $memory; - } - elseif ($fname == '-overhead-internal') { - $overheadInternal[] = $elapsed; - } - } - $overheadTotal = array_sum($overheadTotal) / count($overheadInternal); - $overheadMemory = array_sum($overheadMemory) / count($overheadInternal); - $overheadInternal = array_sum($overheadInternal) / count($overheadInternal); - - # Collate - foreach ($this->mStack as $index => $entry) { - $fname = $entry[0]; - $thislevel = $entry[1]; - $start = $entry[2]; - $end = $entry[4]; - $elapsed = $end - $start; - - $memory = $entry[5] - $entry[3]; - $subcalls = $this->calltreeCount($this->mStack, $index); - - if (!preg_match('/^-overhead/', $fname)) { - # Adjust for profiling overhead (except special values with elapsed=0 - if ( $elapsed ) { - $elapsed -= $overheadInternal; - $elapsed -= ($subcalls * $overheadTotal); - $memory -= ($subcalls * $overheadMemory); - } - } - - if (!array_key_exists($fname, $this->mCollated)) { - $this->mCollated[$fname] = 0; - $this->mCalls[$fname] = 0; - $this->mMemory[$fname] = 0; - $this->mMin[$fname] = 1 << 24; - $this->mMax[$fname] = 0; - $this->mOverhead[$fname] = 0; - } - - $this->mCollated[$fname] += $elapsed; - $this->mCalls[$fname]++; - $this->mMemory[$fname] += $memory; - $this->mMin[$fname] = min($this->mMin[$fname], $elapsed); - $this->mMax[$fname] = max($this->mMax[$fname], $elapsed); - $this->mOverhead[$fname] += $subcalls; - } - - $total = @ $this->mCollated['-total']; - $this->mCalls['-overhead-total'] = $profileCount; - - # Output - asort($this->mCollated, SORT_NUMERIC); - foreach ($this->mCollated as $fname => $elapsed) { - $calls = $this->mCalls[$fname]; - $percent = $total ? 100. * $elapsed / $total : 0; - $memory = $this->mMemory[$fname]; - $prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]); - - global $wgProfileToDatabase; - if ($wgProfileToDatabase) { - Profiler :: logToDB($fname, (float) ($elapsed * 1000), $calls); - } - } - $prof .= "\nTotal: $total\n\n"; - - return $prof; - } - - /** - * Counts the number of profiled function calls sitting under - * the given point in the call graph. Not the most efficient algo. - * - * @param $stack Array: - * @param $start Integer: - * @return Integer - * @private - */ - function calltreeCount(& $stack, $start) { - $level = $stack[$start][1]; - $count = 0; - for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) { - $count ++; - } - return $count; - } - - /** - * @static - */ - function logToDB($name, $timeSum, $eventCount) { - # Warning: $wguname is a live patch, it should be moved to Setup.php - global $wguname, $wgProfilePerHost; - - $fname = 'Profiler::logToDB'; - $dbw = & wfGetDB(DB_MASTER); - if (!is_object($dbw)) - return false; - $errorState = $dbw->ignoreErrors( true ); - $profiling = $dbw->tableName('profiling'); - - $name = substr($name, 0, 255); - $encname = $dbw->strencode($name); - - if ($wgProfilePerHost) { - $pfhost = $wguname['nodename']; - } else { - $pfhost = ''; - } - - $sql = "UPDATE $profiling "."SET pf_count=pf_count+{$eventCount}, "."pf_time=pf_time + {$timeSum} ". - "WHERE pf_name='{$encname}' AND pf_server='{$pfhost}'"; - $dbw->query($sql); - - $rc = $dbw->affectedRows(); - if ($rc == 0) { - $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount, - 'pf_time' => $timeSum, 'pf_server' => $pfhost ), $fname, array ('IGNORE')); - } - // When we upgrade to mysql 4.1, the insert+update - // can be merged into just a insert with this construct added: - // "ON DUPLICATE KEY UPDATE ". - // "pf_count=pf_count + VALUES(pf_count), ". - // "pf_time=pf_time + VALUES(pf_time)"; - $dbw->ignoreErrors( $errorState ); - } - - /** - * Get the function name of the current profiling section - */ - function getCurrentSection() { - $elt = end($this->mWorkStack); - return $elt[0]; - } - -} - -?> diff --git a/includes/SearchTsearch2.php b/includes/SearchTsearch2.php deleted file mode 100644 index 06eaa72d..00000000 --- a/includes/SearchTsearch2.php +++ /dev/null @@ -1,122 +0,0 @@ -<?php -# Copyright (C) 2004 Brion Vibber <brion@pobox.com>, Domas Mituzas <domas.mituzas@gmail.com> -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html - -/** - * Search engine hook for PostgreSQL / Tsearch2 - * @addtogroup Search - */ - -/** - * @todo document - * @addtogroup Search - */ -class SearchTsearch2 extends SearchEngine { - var $strictMatching = false; - - function SearchTsearch2( &$db ) { - $this->db =& $db; - $this->mRanking = true; - } - - function getIndexField( $fulltext ) { - return $fulltext ? 'si_text' : 'si_title'; - } - - function parseQuery( $filteredText, $fulltext ) { - global $wgContLang; - $lc = SearchEngine::legalSearchChars(); - $searchon = ''; - $this->searchTerms = array(); - - # FIXME: This doesn't handle parenthetical expressions. - $m = array(); - if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/', - $filteredText, $m, PREG_SET_ORDER ) ) { - foreach( $m as $terms ) { - if( $searchon !== '' ) $searchon .= ' '; - if( $this->strictMatching && ($terms[1] == '') ) { - $terms[1] = '+'; - } - $searchon .= $terms[1] . $wgContLang->stripForSearch( $terms[2] ); - if( !empty( $terms[3] ) ) { - $regexp = preg_quote( $terms[3], '/' ); - if( $terms[4] ) $regexp .= "[0-9A-Za-z_]+"; - } else { - $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' ); - } - $this->searchTerms[] = $regexp; - } - wfDebug( "Would search with '$searchon'\n" ); - wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); - } else { - wfDebug( "Can't understand search query '{$this->filteredText}'\n" ); - } - - $searchon = preg_replace('/(\s+)/','&',$searchon); - $searchon = $this->db->strencode( $searchon ); - return $searchon; - } - - function queryRanking($filteredTerm, $fulltext) { - $field = $this->getIndexField( $fulltext ); - $searchon = $this->parseQuery($filteredTerm,$fulltext); - if ($this->mRanking) - return " ORDER BY rank($field,to_tsquery('$searchon')) DESC"; - else - return ""; - } - - - function queryMain( $filteredTerm, $fulltext ) { - $match = $this->parseQuery( $filteredTerm, $fulltext ); - $field = $this->getIndexField( $fulltext ); - $cur = $this->db->tableName( 'cur' ); - $searchindex = $this->db->tableName( 'searchindex' ); - return 'SELECT cur_id, cur_namespace, cur_title, cur_text ' . - "FROM $cur,$searchindex " . - 'WHERE cur_id=si_page AND ' . - " $field @@ to_tsquery ('$match') " ; - } - - function update( $id, $title, $text ) { - $dbw = wfGetDB(DB_MASTER); - $searchindex = $dbw->tableName( 'searchindex' ); - $sql = "DELETE FROM $searchindex WHERE si_page={$id}"; - $dbw->query($sql,"SearchTsearch2:update"); - $sql = "INSERT INTO $searchindex (si_page,si_title,si_text) ". - " VALUES ( $id, to_tsvector('". - $dbw->strencode($title). - "'),to_tsvector('". - $dbw->strencode( $text)."')) "; - $dbw->query($sql,"SearchTsearch2:update"); - } - - function updateTitle($id,$title) { - $dbw = wfGetDB(DB_MASTER); - $searchindex = $dbw->tableName( 'searchindex' ); - $sql = "UPDATE $searchindex SET si_title=to_tsvector('" . - $dbw->strencode( $title ) . - "') WHERE si_page={$id}"; - - $dbw->query( $sql, "SearchMySQL4::updateTitle" ); - } - -} - - diff --git a/includes/SiteStatsUpdate.php b/includes/SiteStatsUpdate.php deleted file mode 100644 index b91dcfeb..00000000 --- a/includes/SiteStatsUpdate.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php -/** - * See deferred.txt - * - * @package MediaWiki - */ - -/** - * - * @package MediaWiki - */ -class SiteStatsUpdate { - - var $mViews, $mEdits, $mGood, $mPages, $mUsers; - - function SiteStatsUpdate( $views, $edits, $good, $pages = 0, $users = 0 ) { - $this->mViews = $views; - $this->mEdits = $edits; - $this->mGood = $good; - $this->mPages = $pages; - $this->mUsers = $users; - } - - function appendUpdate( &$sql, $field, $delta ) { - if ( $delta ) { - if ( $sql ) { - $sql .= ','; - } - if ( $delta < 0 ) { - $sql .= "$field=$field-1"; - } else { - $sql .= "$field=$field+1"; - } - } - } - - function doUpdate() { - $fname = 'SiteStatsUpdate::doUpdate'; - $dbw =& wfGetDB( DB_MASTER ); - - # First retrieve the row just to find out which schema we're in - $row = $dbw->selectRow( 'site_stats', '*', false, $fname ); - - $updates = ''; - - $this->appendUpdate( $updates, 'ss_total_views', $this->mViews ); - $this->appendUpdate( $updates, 'ss_total_edits', $this->mEdits ); - $this->appendUpdate( $updates, 'ss_good_articles', $this->mGood ); - - if ( isset( $row->ss_total_pages ) ) { - # Update schema if required - if ( $row->ss_total_pages == -1 && !$this->mViews ) { - $dbr =& wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') ); - extract( $dbr->tableNames( 'page', 'user' ) ); - - $sql = "SELECT COUNT(page_namespace) AS total FROM $page"; - $res = $dbr->query( $sql, $fname ); - $pageRow = $dbr->fetchObject( $res ); - $pages = $pageRow->total + $this->mPages; - - $sql = "SELECT COUNT(user_id) AS total FROM $user"; - $res = $dbr->query( $sql, $fname ); - $userRow = $dbr->fetchObject( $res ); - $users = $userRow->total + $this->mUsers; - - if ( $updates ) { - $updates .= ','; - } - $updates .= "ss_total_pages=$pages, ss_users=$users"; - } else { - $this->appendUpdate( $updates, 'ss_total_pages', $this->mPages ); - $this->appendUpdate( $updates, 'ss_users', $this->mUsers ); - } - } - if ( $updates ) { - $site_stats = $dbw->tableName( 'site_stats' ); - $sql = $dbw->limitResultForUpdate("UPDATE $site_stats SET $updates", 1); - $dbw->begin(); - $dbw->query( $sql, $fname ); - $dbw->commit(); - } - - /* - global $wgDBname, $wgTitle; - if ( $this->mGood && $wgDBname == 'enwiki' ) { - $good = $dbw->selectField( 'site_stats', 'ss_good_articles', '', $fname ); - error_log( $good . ' ' . $wgTitle->getPrefixedDBkey() . "\n", 3, '/home/wikipedia/logs/million.log' ); - } - */ - } -} -?> diff --git a/includes/SpecialAllmessages.php b/includes/SpecialAllmessages.php deleted file mode 100644 index ee97b48e..00000000 --- a/includes/SpecialAllmessages.php +++ /dev/null @@ -1,219 +0,0 @@ -<?php -/** - * Use this special page to get a list of the MediaWiki system messages. - * @addtogroup SpecialPage - */ - -/** - * Constructor. - */ -function wfSpecialAllmessages() { - global $wgOut, $wgRequest, $wgMessageCache, $wgTitle; - global $wgUseDatabaseMessages; - - # The page isn't much use if the MediaWiki namespace is not being used - if( !$wgUseDatabaseMessages ) { - $wgOut->addWikiMsg( 'allmessagesnotsupportedDB' ); - return; - } - - wfProfileIn( __METHOD__ ); - - wfProfileIn( __METHOD__ . '-setup' ); - $ot = $wgRequest->getText( 'ot' ); - - $navText = wfMsg( 'allmessagestext' ); - - # Make sure all extension messages are available - - $wgMessageCache->loadAllMessages(); - - $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) ); - ksort( $sortedArray ); - $messages = array(); - $wgMessageCache->disableTransform(); - - foreach ( $sortedArray as $key => $value ) { - $messages[$key]['enmsg'] = $value; - $messages[$key]['statmsg'] = wfMsgNoDb( $key ); - $messages[$key]['msg'] = wfMsg ( $key ); - } - - $wgMessageCache->enableTransform(); - wfProfileOut( __METHOD__ . '-setup' ); - - wfProfileIn( __METHOD__ . '-output' ); - if ( $ot == 'php' ) { - $navText .= wfAllMessagesMakePhp( $messages ); - $wgOut->addHTML( 'PHP | <a href="' . $wgTitle->escapeLocalUrl( 'ot=html' ) . '">HTML</a> | ' . - '<a href="' . $wgTitle->escapeLocalUrl( 'ot=xml' ) . '">XML</a>' . - '<pre>' . htmlspecialchars( $navText ) . '</pre>' ); - } else if ( $ot == 'xml' ) { - $wgOut->disable(); - header( 'Content-type: text/xml' ); - echo wfAllMessagesMakeXml( $messages ); - } else { - $wgOut->addHTML( '<a href="' . $wgTitle->escapeLocalUrl( 'ot=php' ) . '">PHP</a> | ' . - 'HTML | <a href="' . $wgTitle->escapeLocalUrl( 'ot=xml' ) . '">XML</a>' ); - $wgOut->addWikiText( $navText ); - $wgOut->addHTML( wfAllMessagesMakeHTMLText( $messages ) ); - } - wfProfileOut( __METHOD__ . '-output' ); - - wfProfileOut( __METHOD__ ); -} - -function wfAllMessagesMakeXml( $messages ) { - global $wgLang; - $lang = $wgLang->getCode(); - $txt = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; - $txt .= "<messages lang=\"$lang\">\n"; - foreach( $messages as $key => $m ) { - $txt .= "\t" . Xml::element( 'message', array( 'name' => $key ), $m['msg'] ) . "\n"; - } - $txt .= "</messages>"; - return $txt; -} - -/** - * Create the messages array, formatted in PHP to copy to language files. - * @param $messages Messages array. - * @return The PHP messages array. - * @todo Make suitable for language files. - */ -function wfAllMessagesMakePhp( $messages ) { - global $wgLang; - $txt = "\n\n\$messages = array(\n"; - foreach( $messages as $key => $m ) { - if( $wgLang->getCode() != 'en' && $m['msg'] == $m['enmsg'] ) { - continue; - } else if ( wfEmptyMsg( $key, $m['msg'] ) ) { - $m['msg'] = ''; - $comment = ' #empty'; - } else { - $comment = ''; - } - $txt .= "'$key' => '" . preg_replace( '/(?<!\\\\)\'/', "\'", $m['msg']) . "',$comment\n"; - } - $txt .= ');'; - return $txt; -} - -/** - * Create a list of messages, formatted in HTML as a list of messages and values and showing differences between the default language file message and the message in MediaWiki: namespace. - * @param $messages Messages array. - * @return The HTML list of messages. - */ -function wfAllMessagesMakeHTMLText( $messages ) { - global $wgLang, $wgContLang, $wgUser; - wfProfileIn( __METHOD__ ); - - $sk = $wgUser->getSkin(); - $talk = wfMsg( 'talkpagelinktext' ); - - $input = wfElement( 'input', array( - 'type' => 'text', - 'id' => 'allmessagesinput', - 'onkeyup' => 'allmessagesfilter()' - ), '' ); - $checkbox = wfElement( 'input', array( - 'type' => 'button', - 'value' => wfMsgHtml( 'allmessagesmodified' ), - 'id' => 'allmessagescheckbox', - 'onclick' => 'allmessagesmodified()' - ), '' ); - - $txt = '<span id="allmessagesfilter" style="display: none;">' . wfMsgHtml( 'allmessagesfilter' ) . " {$input}{$checkbox} " . '</span>'; - - $txt .= ' -<table border="1" cellspacing="0" width="100%" id="allmessagestable"> - <tr> - <th rowspan="2">' . wfMsgHtml( 'allmessagesname' ) . '</th> - <th>' . wfMsgHtml( 'allmessagesdefault' ) . '</th> - </tr> - <tr> - <th>' . wfMsgHtml( 'allmessagescurrent' ) . '</th> - </tr>'; - - wfProfileIn( __METHOD__ . "-check" ); - - # This is a nasty hack to avoid doing independent existence checks - # without sending the links and table through the slow wiki parser. - $pageExists = array( - NS_MEDIAWIKI => array(), - NS_MEDIAWIKI_TALK => array() - ); - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $sql = "SELECT page_namespace,page_title FROM $page WHERE page_namespace IN (" . NS_MEDIAWIKI . ", " . NS_MEDIAWIKI_TALK . ")"; - $res = $dbr->query( $sql ); - while( $s = $dbr->fetchObject( $res ) ) { - $pageExists[$s->page_namespace][$s->page_title] = true; - } - $dbr->freeResult( $res ); - wfProfileOut( __METHOD__ . "-check" ); - - wfProfileIn( __METHOD__ . "-output" ); - - $i = 0; - - foreach( $messages as $key => $m ) { - $title = $wgLang->ucfirst( $key ); - if( $wgLang->getCode() != $wgContLang->getCode() ) { - $title .= '/' . $wgLang->getCode(); - } - - $titleObj =& Title::makeTitle( NS_MEDIAWIKI, $title ); - $talkPage =& Title::makeTitle( NS_MEDIAWIKI_TALK, $title ); - - $changed = ( $m['statmsg'] != $m['msg'] ); - $message = htmlspecialchars( $m['statmsg'] ); - $mw = htmlspecialchars( $m['msg'] ); - - if( isset( $pageExists[NS_MEDIAWIKI][$title] ) ) { - $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' ); - } else { - $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" . htmlspecialchars( $key ) . '</span>' ); - } - if( isset( $pageExists[NS_MEDIAWIKI_TALK][$title] ) ) { - $talkLink = $sk->makeKnownLinkObj( $talkPage, htmlspecialchars( $talk ) ); - } else { - $talkLink = $sk->makeBrokenLinkObj( $talkPage, htmlspecialchars( $talk ) ); - } - - $anchor = 'msg_' . htmlspecialchars( strtolower( $title ) ); - $anchor = "<a id=\"$anchor\" name=\"$anchor\"></a>"; - - if( $changed ) { - $txt .= " - <tr class=\"orig\" id=\"sp-allmessages-r1-$i\"> - <td rowspan=\"2\"> - $anchor$pageLink<br />$talkLink - </td><td> -$message - </td> - </tr><tr class=\"new\" id=\"sp-allmessages-r2-$i\"> - <td> -$mw - </td> - </tr>"; - } else { - $txt .= " - <tr class=\"def\" id=\"sp-allmessages-r1-$i\"> - <td> - $anchor$pageLink<br />$talkLink - </td><td> -$mw - </td> - </tr>"; - } - $i++; - } - $txt .= '</table>'; - wfProfileOut( __METHOD__ . '-output' ); - - wfProfileOut( __METHOD__ ); - return $txt; -} - - diff --git a/includes/SpecialAllpages.php b/includes/SpecialAllpages.php deleted file mode 100644 index 9f5cf834..00000000 --- a/includes/SpecialAllpages.php +++ /dev/null @@ -1,395 +0,0 @@ -<?php -/** - * @addtogroup SpecialPage - */ - -/** - * Entry point : initialise variables and call subfunctions. - * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL) - * @param $specialPage See the SpecialPage object. - */ -function wfSpecialAllpages( $par=NULL, $specialPage ) { - global $wgRequest, $wgOut, $wgContLang; - - # GET values - $from = $wgRequest->getVal( 'from' ); - $namespace = $wgRequest->getInt( 'namespace' ); - - $namespaces = $wgContLang->getNamespaces(); - - $indexPage = new SpecialAllpages(); - - $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ? - wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : - wfMsg( 'allarticles' ) - ); - - if ( isset($par) ) { - $indexPage->showChunk( $namespace, $par, $specialPage->including() ); - } elseif ( isset($from) ) { - $indexPage->showChunk( $namespace, $from, $specialPage->including() ); - } else { - $indexPage->showToplevel ( $namespace, $specialPage->including() ); - } -} - -/** - * Implements Special:Allpages - * @addtogroup SpecialPage - */ -class SpecialAllpages { - /** - * Maximum number of pages to show on single subpage. - */ - protected $maxPerPage = 960; - - /** - * Name of this special page. Used to make title objects that reference back - * to this page. - */ - protected $name = 'Allpages'; - - /** - * Determines, which message describes the input field 'nsfrom'. - */ - protected $nsfromMsg = 'allpagesfrom'; - -/** - * HTML for the top form - * @param integer $namespace A namespace constant (default NS_MAIN). - * @param string $from Article name we are starting listing at. - */ -function namespaceForm ( $namespace = NS_MAIN, $from = '' ) { - global $wgScript, $wgContLang; - $t = SpecialPage::getTitleFor( $this->name ); - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); - $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $out .= Xml::hidden( 'title', $t->getPrefixedText() ); - $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); - $out .= "<tr> - <td align='$align'>" . - Xml::label( wfMsg( $this->nsfromMsg ), 'nsfrom' ) . - "</td> - <td>" . - Xml::input( 'from', 20, $from, array( 'id' => 'nsfrom' ) ) . - "</td> - </tr> - <tr> - <td align='$align'>" . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . - "</td> - <td>" . - Xml::namespaceSelector( $namespace, null ) . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - "</td> - </tr>"; - $out .= Xml::closeElement( 'table' ); - $out .= Xml::closeElement( 'form' ); - $out .= Xml::closeElement( 'div' ); - return $out; -} - -/** - * @param integer $namespace (default NS_MAIN) - */ -function showToplevel ( $namespace = NS_MAIN, $including = false ) { - global $wgOut, $wgContLang; - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - # TODO: Either make this *much* faster or cache the title index points - # in the querycache table. - - $dbr = wfGetDB( DB_SLAVE ); - $out = ""; - $where = array( 'page_namespace' => $namespace ); - - global $wgMemc; - $key = wfMemcKey( 'allpages', 'ns', $namespace ); - $lines = $wgMemc->get( $key ); - - if( !is_array( $lines ) ) { - $options = array( 'LIMIT' => 1 ); - if ( ! $dbr->implicitOrderby() ) { - $options['ORDER BY'] = 'page_title'; - } - $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options ); - $lastTitle = $firstTitle; - - # This array is going to hold the page_titles in order. - $lines = array( $firstTitle ); - - # If we are going to show n rows, we need n+1 queries to find the relevant titles. - $done = false; - for( $i = 0; !$done; ++$i ) { - // Fetch the last title of this chunk and the first of the next - $chunk = is_null( $lastTitle ) - ? '' - : 'page_title >= ' . $dbr->addQuotes( $lastTitle ); - $res = $dbr->select( - 'page', /* FROM */ - 'page_title', /* WHAT */ - $where + array( $chunk), - __METHOD__, - array ('LIMIT' => 2, 'OFFSET' => $this->maxPerPage - 1, 'ORDER BY' => 'page_title') ); - - if ( $s = $dbr->fetchObject( $res ) ) { - array_push( $lines, $s->page_title ); - } else { - // Final chunk, but ended prematurely. Go back and find the end. - $endTitle = $dbr->selectField( 'page', 'MAX(page_title)', - array( - 'page_namespace' => $namespace, - $chunk - ), __METHOD__ ); - array_push( $lines, $endTitle ); - $done = true; - } - if( $s = $dbr->fetchObject( $res ) ) { - array_push( $lines, $s->page_title ); - $lastTitle = $s->page_title; - } else { - // This was a final chunk and ended exactly at the limit. - // Rare but convenient! - $done = true; - } - $dbr->freeResult( $res ); - } - $wgMemc->add( $key, $lines, 3600 ); - } - - // If there are only two or less sections, don't even display them. - // Instead, display the first section directly. - if( count( $lines ) <= 2 ) { - $this->showChunk( $namespace, '', $including ); - return; - } - - # At this point, $lines should contain an even number of elements. - $out .= "<table class='allpageslist' style='background: inherit;'>"; - while ( count ( $lines ) > 0 ) { - $inpoint = array_shift ( $lines ); - $outpoint = array_shift ( $lines ); - $out .= $this->showline ( $inpoint, $outpoint, $namespace, false ); - } - $out .= '</table>'; - $nsForm = $this->namespaceForm ( $namespace, '', false ); - - # Is there more? - if ( $including ) { - $out2 = ''; - } else { - $morelinks = ''; - if ( $morelinks != '' ) { - $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; - $out2 .= '<tr valign="top"><td>' . $nsForm; - $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">'; - $out2 .= $morelinks . '</td></tr></table><hr />'; - } else { - $out2 = $nsForm . '<hr />'; - } - } - - $wgOut->addHtml( $out2 . $out ); -} - -/** - * @todo Document - * @param string $from - * @param integer $namespace (Default NS_MAIN) - */ -function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) { - global $wgContLang; - $align = $wgContLang->isRtl() ? 'left' : 'right'; - $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) ); - $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) ); - $queryparams = ($namespace ? "namespace=$namespace" : ''); - $special = SpecialPage::getTitleFor( $this->name, $inpoint ); - $link = $special->escapeLocalUrl( $queryparams ); - - $out = wfMsgHtml( - 'alphaindexline', - "<a href=\"$link\">$inpointf</a></td><td><a href=\"$link\">", - "</a></td><td><a href=\"$link\">$outpointf</a>" - ); - return '<tr><td align="' . $align . '">'.$out.'</td></tr>'; -} - -/** - * @param integer $namespace (Default NS_MAIN) - * @param string $from list all pages from this name (default FALSE) - */ -function showChunk( $namespace = NS_MAIN, $from, $including = false ) { - global $wgOut, $wgUser, $wgContLang; - - $sk = $wgUser->getSkin(); - - $fromList = $this->getNamespaceKeyAndText($namespace, $from); - $namespaces = $wgContLang->getNamespaces(); - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - $n = 0; - - if ( !$fromList ) { - $out = wfMsgWikiHtml( 'allpagesbadtitle' ); - } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) { - // Show errormessage and reset to NS_MAIN - $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace ); - $namespace = NS_MAIN; - } else { - list( $namespace, $fromKey, $from ) = $fromList; - - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'page', - array( 'page_namespace', 'page_title', 'page_is_redirect' ), - array( - 'page_namespace' => $namespace, - 'page_title >= ' . $dbr->addQuotes( $fromKey ) - ), - __METHOD__, - array( - 'ORDER BY' => 'page_title', - 'LIMIT' => $this->maxPerPage + 1, - 'USE INDEX' => 'name_title', - ) - ); - - $out = '<table style="background: inherit;" border="0" width="100%">'; - - while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $t = Title::makeTitle( $s->page_namespace, $s->page_title ); - if( $t ) { - $link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . - $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . - ($s->page_is_redirect ? '</div>' : '' ); - } else { - $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; - } - if( $n % 3 == 0 ) { - $out .= '<tr>'; - } - $out .= "<td width=\"33%\">$link</td>"; - $n++; - if( $n % 3 == 0 ) { - $out .= '</tr>'; - } - } - if( ($n % 3) != 0 ) { - $out .= '</tr>'; - } - $out .= '</table>'; - } - - if ( $including ) { - $out2 = ''; - } else { - if( $from == '' ) { - // First chunk; no previous link. - $prevTitle = null; - } else { - # Get the last title from previous chunk - $dbr = wfGetDB( DB_SLAVE ); - $res_prev = $dbr->select( - 'page', - 'page_title', - array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), - __METHOD__, - array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) ) - ); - - # Get first title of previous complete chunk - if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { - $pt = $dbr->fetchObject( $res_prev ); - $prevTitle = Title::makeTitle( $namespace, $pt->page_title ); - } else { - # The previous chunk is not complete, need to link to the very first title - # available in the database - $options = array( 'LIMIT' => 1 ); - if ( ! $dbr->implicitOrderby() ) { - $options['ORDER BY'] = 'page_title'; - } - $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', array( 'page_namespace' => $namespace ), __METHOD__, $options ); - # Show the previous link if it s not the current requested chunk - if( $from != $reallyFirstPage_title ) { - $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); - } else { - $prevTitle = null; - } - } - } - - $nsForm = $this->namespaceForm ( $namespace, $from ); - $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; - $out2 .= '<tr valign="top"><td>' . $nsForm; - $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . - $sk->makeKnownLink( $wgContLang->specialPage( "Allpages" ), - wfMsgHtml ( 'allpages' ) ); - - $self = SpecialPage::getTitleFor( 'Allpages' ); - - # Do we put a previous link ? - if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { - $q = 'from=' . $prevTitle->getPartialUrl() . ( $namespace ? '&namespace=' . $namespace : '' ); - $prevLink = $sk->makeKnownLinkObj( $self, wfMsgHTML( 'prevpage', $pt ), $q ); - $out2 .= ' | ' . $prevLink; - } - - if( $n == $this->maxPerPage && $s = $dbr->fetchObject($res) ) { - # $s is the first link of the next chunk - $t = Title::MakeTitle($namespace, $s->page_title); - $q = 'from=' . $t->getPartialUrl() . ( $namespace ? '&namespace=' . $namespace : '' ); - $nextLink = $sk->makeKnownLinkObj( $self, wfMsgHtml( 'nextpage', $t->getText() ), $q ); - $out2 .= ' | ' . $nextLink; - } - $out2 .= "</td></tr></table><hr />"; - } - - $wgOut->addHtml( $out2 . $out ); - if( isset($prevLink) or isset($nextLink) ) { - $wgOut->addHtml( '<hr /><p style="font-size: smaller; float: ' . $align . '">' ); - if( isset( $prevLink ) ) { - $wgOut->addHTML( $prevLink ); - } - if( isset( $prevLink ) && isset( $nextLink ) ) { - $wgOut->addHTML( ' | ' ); - } - if( isset( $nextLink ) ) { - $wgOut->addHTML( $nextLink ); - } - $wgOut->addHTML( '</p>' ); - - } - -} - -/** - * @param int $ns the namespace of the article - * @param string $text the name of the article - * @return array( int namespace, string dbkey, string pagename ) or NULL on error - * @static (sort of) - * @access private - */ -function getNamespaceKeyAndText ($ns, $text) { - if ( $text == '' ) - return array( $ns, '', '' ); # shortcut for common case - - $t = Title::makeTitleSafe($ns, $text); - if ( $t && $t->isLocal() ) { - return array( $t->getNamespace(), $t->getDBkey(), $t->getText() ); - } else if ( $t ) { - return NULL; - } - - # try again, in case the problem was an empty pagename - $text = preg_replace('/(#|$)/', 'X$1', $text); - $t = Title::makeTitleSafe($ns, $text); - if ( $t && $t->isLocal() ) { - return array( $t->getNamespace(), '', '' ); - } else { - return NULL; - } -} -} - -?> diff --git a/includes/SpecialAncientpages.php b/includes/SpecialAncientpages.php deleted file mode 100644 index dee8fbde..00000000 --- a/includes/SpecialAncientpages.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * Implements Special:Ancientpages - * @addtogroup SpecialPage - */ -class AncientPagesPage extends QueryPage { - - function getName() { - return "Ancientpages"; - } - - function isExpensive() { - return true; - } - - function isSyndicated() { return false; } - - function getSQL() { - global $wgDBtype; - $db = wfGetDB( DB_SLAVE ); - $page = $db->tableName( 'page' ); - $revision = $db->tableName( 'revision' ); - #$use_index = $db->useIndexClause( 'cur_timestamp' ); # FIXME! this is gone - $epoch = $wgDBtype == 'mysql' ? 'UNIX_TIMESTAMP(rev_timestamp)' : - 'EXTRACT(epoch FROM rev_timestamp)'; - return - "SELECT 'Ancientpages' as type, - page_namespace as namespace, - page_title as title, - $epoch as value - FROM $page, $revision - WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0 - AND page_latest=rev_id"; - } - - function sortDescending() { - return false; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $result->value ), true ); - $title = Title::makeTitle( $result->namespace, $result->title ); - $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); - return wfSpecialList($link, $d); - } -} - -function wfSpecialAncientpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $app = new AncientPagesPage(); - - $app->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialBlockip.php b/includes/SpecialBlockip.php deleted file mode 100644 index cfbef1b3..00000000 --- a/includes/SpecialBlockip.php +++ /dev/null @@ -1,476 +0,0 @@ -<?php -/** - * Constructor for Special:Blockip page - * - * @addtogroup SpecialPage - */ - -/** - * Constructor - */ -function wfSpecialBlockip( $par ) { - global $wgUser, $wgOut, $wgRequest; - - # Can't block when the database is locked - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - # Permission check - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - - $ipb = new IPBlockForm( $par ); - - $action = $wgRequest->getVal( 'action' ); - if ( 'success' == $action ) { - $ipb->showSuccess(); - } else if ( $wgRequest->wasPosted() && 'submit' == $action && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $ipb->doSubmit(); - } else { - $ipb->showForm( '' ); - } -} - -/** - * Form object for the Special:Blockip page. - * - * @addtogroup SpecialPage - */ -class IPBlockForm { - var $BlockAddress, $BlockExpiry, $BlockReason; -# var $BlockEmail; - - function IPBlockForm( $par ) { - global $wgRequest, $wgUser; - - $this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) ); - $this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' ); - $this->BlockReason = $wgRequest->getText( 'wpBlockReason' ); - $this->BlockReasonList = $wgRequest->getText( 'wpBlockReasonList' ); - $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') ); - $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' ); - - # Unchecked checkboxes are not included in the form data at all, so having one - # that is true by default is a bit tricky - $byDefault = !$wgRequest->wasPosted(); - $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault ); - $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault ); - $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault ); - $this->BlockEmail = $wgRequest->getBool( 'wpEmailBan', false ); - # Re-check user's rights to hide names, very serious, defaults to 0 - $this->BlockHideName = ( $wgRequest->getBool( 'wpHideName', 0 ) && $wgUser->isAllowed( 'hideuser' ) ) ? 1 : 0; - } - - function showForm( $err ) { - global $wgOut, $wgUser, $wgSysopUserBans, $wgContLang; - - $wgOut->setPagetitle( wfMsg( 'blockip' ) ); - $wgOut->addWikiMsg( 'blockiptext' ); - - if($wgSysopUserBans) { - $mIpaddress = Xml::label( wfMsg( 'ipadressorusername' ), 'mw-bi-target' ); - } else { - $mIpaddress = Xml::label( wfMsg( 'ipaddress' ), 'mw-bi-target' ); - } - $mIpbexpiry = Xml::label( wfMsg( 'ipbexpiry' ), 'wpBlockExpiry' ); - $mIpbother = Xml::label( wfMsg( 'ipbother' ), 'mw-bi-other' ); - $mIpbothertime = wfMsgHtml( 'ipbotheroption' ); - $mIpbreasonother = Xml::label( wfMsg( 'ipbreason' ), 'wpBlockReasonList' ); - $mIpbreason = Xml::label( wfMsg( 'ipbotherreason' ), 'mw-bi-reason' ); - - $titleObj = SpecialPage::getTitleFor( 'Blockip' ); - $action = $titleObj->escapeLocalURL( "action=submit" ); - $alignRight = $wgContLang->isRtl() ? 'left' : 'right'; - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) ); - $wgOut->addHTML( "<p class='error'>{$err}</p>\n" ); - } - - $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' ); - - $showblockoptions = $scBlockExpiryOptions != '-'; - if (!$showblockoptions) - $mIpbother = $mIpbexpiry; - - $blockExpiryFormOptions = "<option value=\"other\">$mIpbothertime</option>"; - foreach (explode(',', $scBlockExpiryOptions) as $option) { - if ( strpos($option, ":") === false ) $option = "$option:$option"; - list($show, $value) = explode(":", $option); - $show = htmlspecialchars($show); - $value = htmlspecialchars($value); - $selected = ""; - if ($this->BlockExpiry === $value) - $selected = ' selected="selected"'; - $blockExpiryFormOptions .= "<option value=\"$value\"$selected>$show</option>"; - } - - $reasonDropDown = Xml::listDropDown( 'wpBlockReasonList', - wfMsgForContent( 'ipbreason-dropdown' ), - wfMsgForContent( 'ipbreasonotherlist' ), '', 'wpBlockDropDown', 4 ); - - $token = $wgUser->editToken(); - - global $wgStylePath, $wgStyleVersion; - $wgOut->addHTML( " -<script type=\"text/javascript\" src=\"$wgStylePath/common/block.js?$wgStyleVersion\"> -</script> -<form id=\"blockip\" method=\"post\" action=\"{$action}\"> - <table border='0'> - <tr> - <td align=\"$alignRight\">{$mIpaddress}</td> - <td> - " . Xml::input( 'wpBlockAddress', 45, $this->BlockAddress, - array( - 'tabindex' => '1', - 'id' => 'mw-bi-target', - 'onchange' => 'updateBlockOptions()' ) ) . " - </td> - </tr> - <tr>"); - if ($showblockoptions) { - $wgOut->addHTML(" - <td align=\"$alignRight\">{$mIpbexpiry}</td> - <td> - <select tabindex='2' id='wpBlockExpiry' name=\"wpBlockExpiry\" onchange=\"considerChangingExpiryFocus()\"> - $blockExpiryFormOptions - </select> - </td> - "); - } - $wgOut->addHTML(" - </tr> - <tr id='wpBlockOther'> - <td align=\"$alignRight\">{$mIpbother}</td> - <td> - " . Xml::input( 'wpBlockOther', 45, $this->BlockOther, - array( 'tabindex' => '3', 'id' => 'mw-bi-other' ) ) . " - </td> - </tr>"); - $wgOut->addHTML(" - <tr> - <td align=\"$alignRight\">{$mIpbreasonother}</td> - <td> - $reasonDropDown - </td> - </tr>"); - $wgOut->addHTML(" - <tr id=\"wpBlockReason\"> - <td align=\"$alignRight\">{$mIpbreason}</td> - <td> - " . Xml::input( 'wpBlockReason', 45, $this->BlockReason, - array( 'tabindex' => '5', 'id' => 'mw-bi-reason', - 'maxlength'=> '200' ) ) . " - </td> - </tr> - <tr id='wpAnonOnlyRow'> - <td> </td> - <td> - " . wfCheckLabel( wfMsgHtml( 'ipbanononly' ), - 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly, - array( 'tabindex' => '6' ) ) . " - </td> - </tr> - <tr id='wpCreateAccountRow'> - <td> </td> - <td> - " . wfCheckLabel( wfMsgHtml( 'ipbcreateaccount' ), - 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount, - array( 'tabindex' => '7' ) ) . " - </td> - </tr> - <tr id='wpEnableAutoblockRow'> - <td> </td> - <td> - " . wfCheckLabel( wfMsgHtml( 'ipbenableautoblock' ), - 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, - array( 'tabindex' => '8' ) ) . " - </td> - </tr> - "); - - global $wgSysopEmailBans; - if ( $wgSysopEmailBans && $wgUser->isAllowed( 'blockemail' ) ) { - $wgOut->addHTML(" - <tr id='wpEnableEmailBan'> - <td> </td> - <td> - " . wfCheckLabel( wfMsgHtml( 'ipbemailban' ), - 'wpEmailBan', 'wpEmailBan', $this->BlockEmail, - array( 'tabindex' => '10' )) . " - </td> - </tr> - "); - } - - // Allow some users to hide name from block log, blocklist and listusers - if ( $wgUser->isAllowed( 'hideuser' ) ) { - $wgOut->addHTML(" - <tr id='wpEnableHideUser'> - <td> </td> - <td> - " . wfCheckLabel( wfMsgHtml( 'ipbhidename' ), - 'wpHideName', 'wpHideName', $this->BlockHideName, - array( 'tabindex' => '9' ) ) . " - </td> - </tr> - "); - } - - $wgOut->addHTML(" - <tr> - <td style='padding-top: 1em'> </td> - <td style='padding-top: 1em'> - " . Xml::submitButton( wfMsg( 'ipbsubmit' ), - array( 'name' => 'wpBlock', 'tabindex' => '11' ) ) . " - </td> - </tr> - </table>" . - Xml::hidden( 'wpEditToken', $token ) . -"</form> -<script type=\"text/javascript\">updateBlockOptions()</script> -\n" ); - - $wgOut->addHtml( $this->getConvenienceLinks() ); - - $user = User::newFromName( $this->BlockAddress ); - if( is_object( $user ) ) { - $this->showLogFragment( $wgOut, $user->getUserPage() ); - } elseif( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->BlockAddress ) ) { - $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } elseif( preg_match( '/^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/', $this->BlockAddress ) ) { - $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } - } - - /** - * Backend block code. - * $userID and $expiry will be filled accordingly - * @return array(message key, arguments) on failure, empty array on success - */ - function doBlock(&$userId = null, &$expiry = null) - { - global $wgUser, $wgSysopUserBans, $wgSysopRangeBans; - - $userId = 0; - # Expand valid IPv6 addresses, usernames are left as is - $this->BlockAddress = IP::sanitizeIP( $this->BlockAddress ); - # isIPv4() and IPv6() are used for final validation - $rxIP4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; - $rxIP6 = '\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}'; - $rxIP = "($rxIP4|$rxIP6)"; - - # Check for invalid specifications - if ( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) { - $matches = array(); - if ( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { - # IPv4 - if ( $wgSysopRangeBans ) { - if ( !IP::isIPv4( $this->BlockAddress ) || $matches[2] < 16 || $matches[2] > 32 ) { - return array('ip_range_invalid'); - } - $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); - } else { - # Range block illegal - return array('range_block_disabled'); - } - } else if ( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) { - # IPv6 - if ( $wgSysopRangeBans ) { - if ( !IP::isIPv6( $this->BlockAddress ) || $matches[2] < 64 || $matches[2] > 128 ) { - return array('ip_range_invalid'); - } - $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); - } else { - # Range block illegal - return array('range_block_disabled'); - } - } else { - # Username block - if ( $wgSysopUserBans ) { - $user = User::newFromName( $this->BlockAddress ); - if( !is_null( $user ) && $user->getID() ) { - # Use canonical name - $userId = $user->getID(); - $this->BlockAddress = $user->getName(); - } else { - return array('nosuchusershort', htmlspecialchars( $user ? $user->getName() : $this->BlockAddress ) ); - } - } else { - return array('badipaddress'); - } - } - } - - $reasonstr = $this->BlockReasonList; - if ( $reasonstr != 'other' && $this->BlockReason != '') { - // Entry from drop down menu + additional comment - $reasonstr .= ': ' . $this->BlockReason; - } elseif ( $reasonstr == 'other' ) { - $reasonstr = $this->BlockReason; - } - - $expirestr = $this->BlockExpiry; - if( $expirestr == 'other' ) - $expirestr = $this->BlockOther; - - if (strlen($expirestr) == 0) { - return array('ipb_expiry_invalid'); - } - - if ( $expirestr == 'infinite' || $expirestr == 'indefinite' ) { - $expiry = Block::infinity(); - } else { - # Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1 - $expiry = strtotime( $expirestr ); - - if ( $expiry < 0 || $expiry === false ) { - return array('ipb_expiry_invalid'); - } - - $expiry = wfTimestamp( TS_MW, $expiry ); - } - - # Create block - # Note: for a user block, ipb_address is only for display purposes - $block = new Block( $this->BlockAddress, $userId, $wgUser->getID(), - $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, - $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, - $this->BlockEmail); - - if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) { - - if ( !$block->insert() ) { - return array('ipb_already_blocked', htmlspecialchars($this->BlockAddress)); - } - - wfRunHooks('BlockIpComplete', array($block, $wgUser)); - - # Prepare log parameters - $logParams = array(); - $logParams[] = $expirestr; - $logParams[] = $this->blockLogFlags(); - - # Make log entry, if the name is hidden, put it in the oversight log - $log_type = ($this->BlockHideName) ? 'oversight' : 'block'; - $log = new LogPage( $log_type ); - $log->addEntry( 'block', Title::makeTitle( NS_USER, $this->BlockAddress ), - $reasonstr, $logParams ); - - # Report to the user - return array(); - } - else - return array('hookaborted'); - } - - /** - * UI entry point for blocking - * Wraps around doBlock() - */ - function doSubmit() - { - global $wgOut; - $retval = $this->doBlock(); - if(empty($retval)) { - $titleObj = SpecialPage::getTitleFor( 'Blockip' ); - $wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' . - urlencode( $this->BlockAddress ) ) ); - return; - } - $key = array_shift($retval); - $this->showForm(wfMsgReal($key, $retval)); - } - - function showSuccess() { - global $wgOut; - - $wgOut->setPagetitle( wfMsg( 'blockip' ) ); - $wgOut->setSubtitle( wfMsg( 'blockipsuccesssub' ) ); - $text = wfMsgExt( 'blockipsuccesstext', array( 'parse' ), $this->BlockAddress ); - $wgOut->addHtml( $text ); - } - - function showLogFragment( $out, $title ) { - $out->addHtml( wfElement( 'h2', NULL, LogPage::logName( 'block' ) ) ); - $request = new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'block' ) ); - $viewer = new LogViewer( new LogReader( $request ) ); - $viewer->showList( $out ); - } - - /** - * Return a comma-delimited list of "flags" to be passed to the log - * reader for this block, to provide more information in the logs - * - * @return array - */ - private function blockLogFlags() { - $flags = array(); - if( $this->BlockAnonOnly && IP::isIPAddress( $this->BlockAddress ) ) - // when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log - $flags[] = 'anononly'; - if( $this->BlockCreateAccount ) - $flags[] = 'nocreate'; - if( !$this->BlockEnableAutoblock ) - $flags[] = 'noautoblock'; - if ( $this->BlockEmail ) - $flags[] = 'noemail'; - return implode( ',', $flags ); - } - - /** - * Builds unblock and block list links - * - * @return string - */ - private function getConvenienceLinks() { - global $wgUser; - $skin = $wgUser->getSkin(); - $links[] = $skin->makeLink ( 'MediaWiki:Ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) ); - $links[] = $this->getUnblockLink( $skin ); - $links[] = $this->getBlockListLink( $skin ); - return '<p class="mw-ipb-conveniencelinks">' . implode( ' | ', $links ) . '</p>'; - } - - /** - * Build a convenient link to unblock the given username or IP - * address, if available; otherwise link to a blank unblock - * form - * - * @param $skin Skin to use - * @return string - */ - private function getUnblockLink( $skin ) { - $list = SpecialPage::getTitleFor( 'Ipblocklist' ); - if( $this->BlockAddress ) { - $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock-addr', $addr ), - 'action=unblock&ip=' . urlencode( $this->BlockAddress ) ); - } else { - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock' ), 'action=unblock' ); - } - } - - /** - * Build a convenience link to the block list - * - * @param $skin Skin to use - * @return string - */ - private function getBlockListLink( $skin ) { - $list = SpecialPage::getTitleFor( 'Ipblocklist' ); - if( $this->BlockAddress ) { - $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist-addr', $addr ), - 'ip=' . urlencode( $this->BlockAddress ) ); - } else { - return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist' ) ); - } - } -} - diff --git a/includes/SpecialBlockme.php b/includes/SpecialBlockme.php deleted file mode 100644 index 6c9dea06..00000000 --- a/includes/SpecialBlockme.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -function wfSpecialBlockme() { - global $wgRequest, $wgBlockOpenProxies, $wgOut, $wgProxyKey; - - $ip = wfGetIP(); - - if( !$wgBlockOpenProxies || $wgRequest->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) { - $wgOut->addWikiMsg( 'proxyblocker-disabled' ); - return; - } - - $blockerName = wfMsg( "proxyblocker" ); - $reason = wfMsg( "proxyblockreason" ); - - $u = User::newFromName( $blockerName ); - $id = $u->idForName(); - if ( !$id ) { - $u = User::newFromName( $blockerName ); - $u->addToDatabase(); - $u->setPassword( bin2hex( mt_rand(0, 0x7fffffff ) ) ); - $u->saveSettings(); - $id = $u->getID(); - } - - $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() ); - $block->insert(); - - $wgOut->addWikiMsg( "proxyblocksuccess" ); -} - diff --git a/includes/SpecialBooksources.php b/includes/SpecialBooksources.php deleted file mode 100644 index af258872..00000000 --- a/includes/SpecialBooksources.php +++ /dev/null @@ -1,113 +0,0 @@ -<?php - -/** - * Special page outputs information on sourcing a book with a particular ISBN - * The parser creates links to this page when dealing with ISBNs in wikitext - * - * @addtogroup SpecialPage - * @author Rob Church <robchur@gmail.com> - * @todo Validate ISBNs using the standard check-digit method - */ -class SpecialBookSources extends SpecialPage { - - /** - * ISBN passed to the page, if any - */ - private $isbn = ''; - - /** - * Constructor - */ - public function __construct() { - parent::__construct( 'Booksources' ); - } - - /** - * Show the special page - * - * @param $isbn ISBN passed as a subpage parameter - */ - public function execute( $isbn ) { - global $wgOut, $wgRequest; - $this->setHeaders(); - $this->isbn = $this->cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); - $wgOut->addWikiMsg( 'booksources-summary' ); - $wgOut->addHtml( $this->makeForm() ); - if( strlen( $this->isbn ) > 0 ) - $this->showList(); - } - - /** - * Trim ISBN and remove characters which aren't required - * - * @param $isbn Unclean ISBN - * @return string - */ - private function cleanIsbn( $isbn ) { - return trim( preg_replace( '![^0-9X]!', '', $isbn ) ); - } - - /** - * Generate a form to allow users to enter an ISBN - * - * @return string - */ - private function makeForm() { - global $wgScript; - $title = self::getTitleFor( 'Booksources' ); - $form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>'; - $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $form .= Xml::hidden( 'title', $title->getPrefixedText() ); - $form .= '<p>' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn ); - $form .= ' ' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>'; - $form .= Xml::closeElement( 'form' ); - $form .= '</fieldset>'; - return $form; - } - - /** - * Determine where to get the list of book sources from, - * format and output them - * - * @return string - */ - private function showList() { - global $wgOut, $wgContLang; - - # Hook to allow extensions to insert additional HTML, - # e.g. for API-interacting plugins and so on - wfRunHooks( 'BookInformation', array( $this->isbn, &$wgOut ) ); - - # Check for a local page such as Project:Book_sources and use that if available - $title = Title::makeTitleSafe( NS_PROJECT, wfMsgForContent( 'booksources' ) ); # Show list in content language - if( is_object( $title ) && $title->exists() ) { - $rev = Revision::newFromTitle( $title ); - $wgOut->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); - return true; - } - - # Fall back to the defaults given in the language file - $wgOut->addWikiMsg( 'booksources-text' ); - $wgOut->addHtml( '<ul>' ); - $items = $wgContLang->getBookstoreList(); - foreach( $items as $label => $url ) - $wgOut->addHtml( $this->makeListItem( $label, $url ) ); - $wgOut->addHtml( '</ul>' ); - return true; - } - - /** - * Format a book source list item - * - * @param $label Book source label - * @param $url Book source URL - * @return string - */ - private function makeListItem( $label, $url ) { - $url = str_replace( '$1', $this->isbn, $url ); - return '<li><a href="' . htmlspecialchars( $url ) . '">' . htmlspecialchars( $label ) . '</a></li>'; - } - -} - - diff --git a/includes/SpecialBrokenRedirects.php b/includes/SpecialBrokenRedirects.php deleted file mode 100644 index f6887741..00000000 --- a/includes/SpecialBrokenRedirects.php +++ /dev/null @@ -1,95 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * A special page listing redirects to non existent page. Those should be - * fixed to point to an existing page. - * @addtogroup SpecialPage - */ -class BrokenRedirectsPage extends PageQueryPage { - var $targets = array(); - - function getName() { - return 'BrokenRedirects'; - } - - function isExpensive( ) { return true; } - function isSyndicated() { return false; } - - function getPageHeader( ) { - return wfMsgExt( 'brokenredirectstext', array( 'parse' ) ); - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' ); - - $sql = "SELECT 'BrokenRedirects' AS type, - p1.page_namespace AS namespace, - p1.page_title AS title, - rd_namespace, - rd_title - FROM $redirect AS rd - JOIN $page p1 ON (rd.rd_from=p1.page_id) - LEFT JOIN $page AS p2 ON (rd_namespace=p2.page_namespace AND rd_title=p2.page_title ) - WHERE rd_namespace >= 0 - AND p2.page_namespace IS NULL"; - return $sql; - } - - function getOrder() { - return ''; - } - - function formatResult( $skin, $result ) { - global $wgUser, $wgContLang; - - $fromObj = Title::makeTitle( $result->namespace, $result->title ); - if ( isset( $result->rd_title ) ) { - $toObj = Title::makeTitle( $result->rd_namespace, $result->rd_title ); - } else { - $blinks = $fromObj->getBrokenLinksFrom(); # TODO: check for redirect, not for links - if ( $blinks ) { - $toObj = $blinks[0]; - } else { - $toObj = false; - } - } - - // $toObj may very easily be false if the $result list is cached - if ( !is_object( $toObj ) ) { - return '<s>' . $skin->makeLinkObj( $fromObj ) . '</s>'; - } - - $from = $skin->makeKnownLinkObj( $fromObj ,'', 'redirect=no' ); - $edit = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-edit' ), 'action=edit' ); - $to = $skin->makeBrokenLinkObj( $toObj ); - $arr = $wgContLang->getArrow(); - - $out = "{$from} {$edit}"; - - if( $wgUser->isAllowed( 'delete' ) ) { - $delete = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-delete' ), 'action=delete' ); - $out .= " {$delete}"; - } - - $out .= " {$arr} {$to}"; - return $out; - } -} - -/** - * constructor - */ -function wfSpecialBrokenRedirects() { - list( $limit, $offset ) = wfCheckLimits(); - - $sbr = new BrokenRedirectsPage(); - - return $sbr->doQuery( $offset, $limit ); - -} - diff --git a/includes/SpecialCategories.php b/includes/SpecialCategories.php deleted file mode 100644 index efe65a78..00000000 --- a/includes/SpecialCategories.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -function wfSpecialCategories() { - global $wgOut; - - $cap = new CategoryPager(); - $wgOut->addHTML( - wfMsgExt( 'categoriespagetext', array( 'parse' ) ) . - $cap->getNavigationBar() - . '<ul>' . $cap->getBody() . '</ul>' . - $cap->getNavigationBar() - ); -} - -/** - * @addtogroup SpecialPage - * @addtogroup Pager - */ -class CategoryPager extends AlphabeticPager { - function getQueryInfo() { - return array( - 'tables' => array('categorylinks'), - 'fields' => array('cl_to','count(*) AS count'), - 'options' => array('GROUP BY' => 'cl_to') - ); - } - - function getIndexField() { - return "cl_to"; - } - - /* Override getBody to apply LinksBatch on resultset before actually outputting anything. */ - function getBody() { - if (!$this->mQueryDone) { - $this->doQuery(); - } - $batch = new LinkBatch; - - $this->mResult->rewind(); - - while ( $row = $this->mResult->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_CATEGORY, $row->cl_to ) ); - } - $batch->execute(); - $this->mResult->rewind(); - return parent::getBody(); - } - - function formatRow($result) { - global $wgLang; - $title = Title::makeTitle( NS_CATEGORY, $result->cl_to ); - $titleText = $this->getSkin()->makeLinkObj( $title, htmlspecialchars( $title->getText() ) ); - $count = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->count ) ); - return Xml::tags('li', null, "$titleText ($count)" ) . "\n"; - } -} - - diff --git a/includes/SpecialConfirmemail.php b/includes/SpecialConfirmemail.php deleted file mode 100644 index c3aa53c2..00000000 --- a/includes/SpecialConfirmemail.php +++ /dev/null @@ -1,104 +0,0 @@ -<?php - -/** - * Special page allows users to request email confirmation message, and handles - * processing of the confirmation code when the link in the email is followed - * - * @addtogroup SpecialPage - * @author Brion Vibber - * @author Rob Church <robchur@gmail.com> - */ -class EmailConfirmation extends UnlistedSpecialPage { - - /** - * Constructor - */ - public function __construct() { - parent::__construct( 'Confirmemail' ); - } - - /** - * Main execution point - * - * @param $code Confirmation code passed to the page - */ - function execute( $code ) { - global $wgUser, $wgOut; - $this->setHeaders(); - if( empty( $code ) ) { - if( $wgUser->isLoggedIn() ) { - if( User::isValidEmailAddr( $wgUser->getEmail() ) ) { - $this->showRequestForm(); - } else { - $wgOut->addWikiMsg( 'confirmemail_noemail' ); - } - } else { - $title = SpecialPage::getTitleFor( 'Userlogin' ); - $self = SpecialPage::getTitleFor( 'Confirmemail' ); - $skin = $wgUser->getSkin(); - $llink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $self->getPrefixedUrl() ); - $wgOut->addHtml( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) ); - } - } else { - $this->attemptConfirm( $code ); - } - } - - /** - * Show a nice form for the user to request a confirmation mail - */ - function showRequestForm() { - global $wgOut, $wgUser, $wgLang, $wgRequest; - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) { - $ok = $wgUser->sendConfirmationMail(); - if ( WikiError::isError( $ok ) ) { - $wgOut->addWikiMsg( 'confirmemail_sendfailed', $ok->toString() ); - } else { - $wgOut->addWikiMsg( 'confirmemail_sent' ); - } - } else { - if( $wgUser->isEmailConfirmed() ) { - $time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true ); - $wgOut->addWikiMsg( 'emailauthenticated', $time ); - } - if( $wgUser->isEmailConfirmationPending() ) { - $wgOut->addWikiMsg( 'confirmemail_pending' ); - } - $wgOut->addWikiMsg( 'confirmemail_text' ); - $self = SpecialPage::getTitleFor( 'Confirmemail' ); - $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) ); - $form .= wfHidden( 'token', $wgUser->editToken() ); - $form .= wfSubmitButton( wfMsgHtml( 'confirmemail_send' ) ); - $form .= wfCloseElement( 'form' ); - $wgOut->addHtml( $form ); - } - } - - /** - * Attempt to confirm the user's email address and show success or failure - * as needed; if successful, take the user to log in - * - * @param $code Confirmation code - */ - function attemptConfirm( $code ) { - global $wgUser, $wgOut; - $user = User::newFromConfirmationCode( $code ); - if( is_object( $user ) ) { - if( $user->confirmEmail() ) { - $message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success'; - $wgOut->addWikiMsg( $message ); - if( !$wgUser->isLoggedIn() ) { - $title = SpecialPage::getTitleFor( 'Userlogin' ); - $wgOut->returnToMain( true, $title->getPrefixedText() ); - } - } else { - $wgOut->addWikiMsg( 'confirmemail_error' ); - } - } else { - $wgOut->addWikiMsg( 'confirmemail_invalid' ); - } - } - -} - - diff --git a/includes/SpecialContributions.php b/includes/SpecialContributions.php deleted file mode 100644 index 6bed7905..00000000 --- a/includes/SpecialContributions.php +++ /dev/null @@ -1,434 +0,0 @@ -<?php -/** - * Special:Contributions, show user contributions in a paged list - * @addtogroup SpecialPage - */ - -class ContribsPager extends ReverseChronologicalPager { - public $mDefaultDirection = true; - var $messages, $target; - var $namespace = '', $mDb; - - function __construct( $target, $namespace = false, $year = false, $month = false ) { - parent::__construct(); - foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist minoreditletter' ) as $msg ) { - $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') ); - } - $this->target = $target; - $this->namespace = $namespace; - - $year = intval($year); - $month = intval($month); - - $this->year = ($year > 0 && $year < 10000) ? $year : false; - $this->month = ($month > 0 && $month < 13) ? $month : false; - $this->getDateCond(); - - $this->mDb = wfGetDB( DB_SLAVE, 'contributions' ); - } - - function getDefaultQuery() { - $query = parent::getDefaultQuery(); - $query['target'] = $this->target; - return $query; - } - - function getQueryInfo() { - list( $index, $userCond ) = $this->getUserCond(); - $conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() ); - - return array( - 'tables' => array( 'page', 'revision' ), - 'fields' => array( - 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'rev_id', 'rev_page', - 'rev_text_id', 'rev_timestamp', 'rev_comment', 'rev_minor_edit', 'rev_user', - 'rev_user_text', 'rev_deleted' - ), - 'conds' => $conds, - 'options' => array( 'USE INDEX' => $index ) - ); - } - - function getUserCond() { - $condition = array(); - - if ( $this->target == 'newbies' ) { - $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ ); - $condition[] = 'rev_user >' . (int)($max - $max / 100); - $index = 'user_timestamp'; - } else { - $condition['rev_user_text'] = $this->target; - $index = 'usertext_timestamp'; - } - return array( $index, $condition ); - } - - function getNamespaceCond() { - if ( $this->namespace !== '' ) { - return array( 'page_namespace' => (int)$this->namespace ); - } else { - return array(); - } - } - - function getDateCond() { - if ( $this->year || $this->month ) { - // Assume this year if only a month is given - if ( $this->year ) { - $year_start = $this->year; - } else { - $year_start = substr( wfTimestampNow(), 0, 4 ); - $thisMonth = gmdate( 'n' ); - if( $this->month > $thisMonth ) { - // Future contributions aren't supposed to happen. :) - $year_start--; - } - } - - if ( $this->month ) { - $month_end = str_pad($this->month + 1, 2, '0', STR_PAD_LEFT); - $year_end = $year_start; - } else { - $month_end = 0; - $year_end = $year_start + 1; - } - $ts_end = str_pad($year_end . $month_end, 14, '0' ); - - $this->mOffset = $ts_end; - } - } - - function getIndexField() { - return 'rev_timestamp'; - } - - function getStartBody() { - return "<ul>\n"; - } - - function getEndBody() { - return "</ul>\n"; - } - - /** - * Generates each row in the contributions list. - * - * Contributions which are marked "top" are currently on top of the history. - * For these contributions, a [rollback] link is shown for users with roll- - * back privileges. The rollback link restores the most recent version that - * was not written by the target user. - * - * @todo This would probably look a lot nicer in a table. - */ - function formatRow( $row ) { - wfProfileIn( __METHOD__ ); - - global $wgLang, $wgUser, $wgContLang; - - $sk = $this->getSkin(); - $rev = new Revision( $row ); - - $page = Title::makeTitle( $row->page_namespace, $row->page_title ); - $link = $sk->makeKnownLinkObj( $page ); - $difftext = $topmarktext = ''; - if( $row->rev_id == $row->page_latest ) { - $topmarktext .= '<strong>' . $this->messages['uctop'] . '</strong>'; - if( !$row->page_is_new ) { - $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')'; - } else { - $difftext .= $this->messages['newarticle']; - } - - if( !$page->getUserPermissionsErrors( 'rollback', $wgUser ) - && !$page->getUserPermissionsErrors( 'edit', $wgUser ) ) { - $topmarktext .= ' '.$sk->generateRollback( $rev ); - } - - } - if( $rev->userCan( Revision::DELETED_TEXT ) ) { - $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; - } else { - $difftext = '(' . $this->messages['diff'] . ')'; - } - $histlink='('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')'; - - $comment = $wgContLang->getDirMark() . $sk->revComment( $rev ); - $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); - - if( $this->target == 'newbies' ) { - $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text ); - $userlink .= ' (' . $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) . ') '; - } else { - $userlink = ''; - } - - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $d = '<span class="history-deleted">' . $d . '</span>'; - } - - if( $row->rev_minor_edit ) { - $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> '; - } else { - $mflag = ''; - } - - $ret = "{$d} {$histlink} {$difftext} {$mflag} {$link}{$userlink}{$comment} {$topmarktext}"; - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $ret .= ' ' . wfMsgHtml( 'deletedrev' ); - } - $ret = "<li>$ret</li>\n"; - wfProfileOut( __METHOD__ ); - return $ret; - } - - /** - * Get the Database object in use - * - * @return Database - */ - public function getDatabase() { - return $this->mDb; - } - -} - -/** - * Special page "user contributions". - * Shows a list of the contributions of a user. - * - * @return none - * @param $par String: (optional) user name of the user for which to show the contributions - */ -function wfSpecialContributions( $par = null ) { - global $wgUser, $wgOut, $wgLang, $wgRequest; - - $options = array(); - - if ( isset( $par ) && $par == 'newbies' ) { - $target = 'newbies'; - $options['contribs'] = 'newbie'; - } elseif ( isset( $par ) ) { - $target = $par; - } else { - $target = $wgRequest->getVal( 'target' ); - } - - // check for radiobox - if ( $wgRequest->getVal( 'contribs' ) == 'newbie' ) { - $target = 'newbies'; - $options['contribs'] = 'newbie'; - } - - if ( !strlen( $target ) ) { - $wgOut->addHTML( contributionsForm( '' ) ); - return; - } - - $options['limit'] = $wgRequest->getInt( 'limit', 50 ); - $options['target'] = $target; - - $nt = Title::makeTitleSafe( NS_USER, $target ); - if ( !$nt ) { - $wgOut->addHTML( contributionsForm( '' ) ); - return; - } - $id = User::idFromName( $nt->getText() ); - - if ( $target != 'newbies' ) { - $target = $nt->getText(); - $wgOut->setSubtitle( contributionsSub( $nt, $id ) ); - } else { - $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); - } - - if ( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { - $options['namespace'] = intval( $ns ); - } else { - $options['namespace'] = ''; - } - if ( $wgUser->isAllowed( 'markbotedit' ) && $wgRequest->getBool( 'bot' ) ) { - $options['bot'] = '1'; - } - - $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; - # Offset overrides year/month selection - if ( ( $month = $wgRequest->getIntOrNull( 'month' ) ) !== null && $month !== -1 ) { - $options['month'] = intval( $month ); - } else { - $options['month'] = ''; - } - if ( ( $year = $wgRequest->getIntOrNull( 'year' ) ) !== null ) { - $options['year'] = intval( $year ); - } else if( $options['month'] ) { - $thisMonth = intval( gmdate( 'n' ) ); - $thisYear = intval( gmdate( 'Y' ) ); - if( intval( $options['month'] ) > $thisMonth ) { - $thisYear--; - } - $options['year'] = $thisYear; - } else { - $options['year'] = ''; - } - - wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id ); - - $wgOut->addHTML( contributionsForm( $options ) ); - # Show original selected options, don't apply them so as to allow paging - $_GET['year'] = ''; // hack for Pager - $_GET['month'] = ''; // hack for Pager - if( $skip ) { - $options['year'] = ''; - $options['month'] = ''; - } - - $pager = new ContribsPager( $target, $options['namespace'], $options['year'], $options['month'] ); - if ( !$pager->getNumRows() ) { - $wgOut->addWikiMsg( 'nocontribs' ); - return; - } - - # Show a message about slave lag, if applicable - if( ( $lag = $pager->getDatabase()->getLag() ) > 0 ) - $wgOut->showLagWarning( $lag ); - - $wgOut->addHTML( - '<p>' . $pager->getNavigationBar() . '</p>' . - $pager->getBody() . - '<p>' . $pager->getNavigationBar() . '</p>' ); - - # If there were contributions, and it was a valid user or IP, show - # the appropriate "footer" message - WHOIS tools, etc. - if( $target != 'newbies' ) { - $message = IP::isIPAddress( $target ) - ? 'sp-contributions-footer-anon' - : 'sp-contributions-footer'; - - - $text = wfMsgNoTrans( $message, $target ); - if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { - $wgOut->addHtml( '<div class="mw-contributions-footer">' ); - $wgOut->addWikiText( $text ); - $wgOut->addHtml( '</div>' ); - } - } -} - -/** - * Generates the subheading with links - * @param Title $nt Title object for the target - * @param integer $id User ID for the target - * @return String: appropriately-escaped HTML to be output literally - */ -function contributionsSub( $nt, $id ) { - global $wgSysopUserBans, $wgLang, $wgUser; - - $sk = $wgUser->getSkin(); - - if ( 0 == $id ) { - $user = $nt->getText(); - } else { - $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); - } - $talk = $nt->getTalkPage(); - if( $talk ) { - # Talk page link - $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) ); - if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) { - # Block link - if( $wgUser->isAllowed( 'block' ) ) - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) ); - # Block log link - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() ); - } - # Other logs link - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() ); - - wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); - - $links = implode( ' | ', $tools ); - } - - // Old message 'contribsub' had one parameter, but that doesn't work for - // languages that want to put the "for" bit right after $user but before - // $links. If 'contribsub' is around, use it for reverse compatibility, - // otherwise use 'contribsub2'. - if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { - return wfMsgHtml( 'contribsub2', $user, $links ); - } else { - return wfMsgHtml( 'contribsub', "$user ($links)" ); - } -} - -/** - * Generates the namespace selector form with hidden attributes. - * @param $options Array: the options to be included. - */ -function contributionsForm( $options ) { - global $wgScript, $wgTitle, $wgRequest; - - $options['title'] = $wgTitle->getPrefixedText(); - if ( !isset( $options['target'] ) ) { - $options['target'] = ''; - } else { - $options['target'] = str_replace( '_' , ' ' , $options['target'] ); - } - - if ( !isset( $options['namespace'] ) ) { - $options['namespace'] = ''; - } - - if ( !isset( $options['contribs'] ) ) { - $options['contribs'] = 'user'; - } - - if ( !isset( $options['year'] ) ) { - $options['year'] = ''; - } - - if ( !isset( $options['month'] ) ) { - $options['month'] = ''; - } - - if ( $options['contribs'] == 'newbie' ) { - $options['target'] = ''; - } - - $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - - foreach ( $options as $name => $value ) { - if ( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) { - continue; - } - $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; - } - - $f .= '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . - Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), 'contribs' , 'newbie' , 'newbie', $options['contribs'] == 'newbie' ? true : false ) . '<br />' . - Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ), 'contribs' , 'user', 'user', $options['contribs'] == 'user' ? true : false ) . ' ' . - Xml::input( 'target', 20, $options['target']) . ' '. - '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . - Xml::namespaceSelector( $options['namespace'], '' ) . - '</span>' . - Xml::openElement( 'p' ) . - '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'year' ), 'year' ) . ' '. - Xml::input( 'year', 4, $options['year'], array('id' => 'year', 'maxlength' => 4) ) . - '</span>' . - ' '. - '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'month' ), 'month' ) . ' '. - Xml::monthSelector( $options['month'], -1 ) . ' '. - '</span>' . - Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . - Xml::closeElement( 'p' ); - - $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); - if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) - $f .= "<p>{$explain}</p>"; - - $f .= '</fieldset>' . - Xml::closeElement( 'form' ); - return $f; -} diff --git a/includes/SpecialDeadendpages.php b/includes/SpecialDeadendpages.php deleted file mode 100644 index 0d94161b..00000000 --- a/includes/SpecialDeadendpages.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - * @addtogroup SpecialPage - */ -class DeadendPagesPage extends PageQueryPage { - - function getName( ) { - return "Deadendpages"; - } - - function getPageHeader() { - return wfMsgExt( 'deadendpagestext', array( 'parse' ) ); - } - - /** - * LEFT JOIN is expensive - * - * @return true - */ - function isExpensive( ) { - return 1; - } - - function isSyndicated() { return false; } - - /** - * @return false - */ - function sortDescending() { - return false; - } - - /** - * @return string an sqlquery - */ - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); - return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " . - "FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " . - "WHERE pl_from IS NULL " . - "AND page_namespace = 0 " . - "AND page_is_redirect = 0"; - } -} - -/** - * Constructor - */ -function wfSpecialDeadendpages() { - - list( $limit, $offset ) = wfCheckLimits(); - - $depp = new DeadendPagesPage(); - - return $depp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialDisambiguations.php b/includes/SpecialDisambiguations.php deleted file mode 100644 index fb1d75e9..00000000 --- a/includes/SpecialDisambiguations.php +++ /dev/null @@ -1,106 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -class DisambiguationsPage extends PageQueryPage { - - function getName() { - return 'Disambiguations'; - } - - function isExpensive( ) { return true; } - function isSyndicated() { return false; } - - - function getPageHeader( ) { - return wfMsgExt( 'disambiguations-text', array( 'parse' ) ); - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - - $dMsgText = wfMsgForContent('disambiguationspage'); - - $linkBatch = new LinkBatch; - - # If the text can be treated as a title, use it verbatim. - # Otherwise, pull the titles from the links table - $dp = Title::newFromText($dMsgText); - if( $dp ) { - if($dp->getNamespace() != NS_TEMPLATE) { - # FIXME we assume the disambiguation message is a template but - # the page can potentially be from another namespace :/ - wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); - } - $linkBatch->addObj( $dp ); - } else { - # Get all the templates linked from the Mediawiki:Disambiguationspage - $disPageObj = Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage' ); - $res = $dbr->select( - array('pagelinks', 'page'), - 'pl_title', - array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, - 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), - __METHOD__ ); - - while ( $row = $dbr->fetchObject( $res ) ) { - $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); - } - - $dbr->freeResult( $res ); - } - - $set = $linkBatch->constructSet( 'lb.tl', $dbr ); - if( $set === false ) { - # We must always return a valid sql query, but this way DB will always quicly return an empty result - $set = 'FALSE'; - wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n"); - } - - list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); - - $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," - ." pb.page_title AS title, la.pl_from AS value" - ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" - ." WHERE $set" # disambiguation template(s) - .' AND pa.page_id = la.pl_from' - .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace - .' AND pb.page_id = lb.tl_from' - .' AND pb.page_namespace = la.pl_namespace' - .' AND pb.page_title = la.pl_title' - .' ORDER BY lb.tl_namespace, lb.tl_title'; - - return $sql; - } - - function getOrder() { - return ''; - } - - function formatResult( $skin, $result ) { - global $wgContLang; - $title = Title::newFromId( $result->value ); - $dp = Title::makeTitle( $result->namespace, $result->title ); - - $from = $skin->makeKnownLinkObj( $title, '' ); - $edit = $skin->makeKnownLinkObj( $title, "(".wfMsgHtml("qbedit").")" , 'redirect=no&action=edit' ); - $arr = $wgContLang->getArrow(); - $to = $skin->makeKnownLinkObj( $dp, '' ); - - return "$from $edit $arr $to"; - } -} - -/** - * Constructor - */ -function wfSpecialDisambiguations() { - list( $limit, $offset ) = wfCheckLimits(); - - $sd = new DisambiguationsPage(); - - return $sd->doQuery( $offset, $limit ); -} - diff --git a/includes/SpecialDoubleRedirects.php b/includes/SpecialDoubleRedirects.php deleted file mode 100644 index 7e4ec360..00000000 --- a/includes/SpecialDoubleRedirects.php +++ /dev/null @@ -1,104 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * A special page listing redirects to redirecting page. - * The software will automatically not follow double redirects, to prevent loops. - * @addtogroup SpecialPage - */ -class DoubleRedirectsPage extends PageQueryPage { - - function getName() { - return 'DoubleRedirects'; - } - - function isExpensive( ) { return true; } - function isSyndicated() { return false; } - - function getPageHeader( ) { - return wfMsgExt( 'doubleredirectstext', array( 'parse' ) ); - } - - function getSQLText( &$dbr, $namespace = null, $title = null ) { - - list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' ); - - $limitToTitle = !( $namespace === null && $title === null ); - $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ; - $sql .= - " pa.page_namespace as namespace, pa.page_title as title," . - " pb.page_namespace as nsb, pb.page_title as tb," . - " pc.page_namespace as nsc, pc.page_title as tc" . - " FROM $redirect AS ra, $redirect AS rb, $page AS pa, $page AS pb, $page AS pc" . - " WHERE ra.rd_from=pa.page_id" . - " AND ra.rd_namespace=pb.page_namespace" . - " AND ra.rd_title=pb.page_title" . - " AND rb.rd_from=pb.page_id" . - " AND rb.rd_namespace=pc.page_namespace" . - " AND rb.rd_title=pc.page_title"; - - if( $limitToTitle ) { - $encTitle = $dbr->addQuotes( $title ); - $sql .= " AND pa.page_namespace=$namespace" . - " AND pa.page_title=$encTitle"; - } - - return $sql; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - return $this->getSQLText( $dbr ); - } - - function getOrder() { - return ''; - } - - function formatResult( $skin, $result ) { - global $wgContLang; - - $fname = 'DoubleRedirectsPage::formatResult'; - $titleA = Title::makeTitle( $result->namespace, $result->title ); - - if ( $result && !isset( $result->nsb ) ) { - $dbr = wfGetDB( DB_SLAVE ); - $sql = $this->getSQLText( $dbr, $result->namespace, $result->title ); - $res = $dbr->query( $sql, $fname ); - if ( $res ) { - $result = $dbr->fetchObject( $res ); - $dbr->freeResult( $res ); - } - } - if ( !$result ) { - return '<s>' . $skin->makeLinkObj( $titleA, '', 'redirect=no' ) . '</s>'; - } - - $titleB = Title::makeTitle( $result->nsb, $result->tb ); - $titleC = Title::makeTitle( $result->nsc, $result->tc ); - - $linkA = $skin->makeKnownLinkObj( $titleA, '', 'redirect=no' ); - $edit = $skin->makeBrokenLinkObj( $titleA, "(".wfMsg("qbedit").")" , 'redirect=no'); - $linkB = $skin->makeKnownLinkObj( $titleB, '', 'redirect=no' ); - $linkC = $skin->makeKnownLinkObj( $titleC ); - $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); - - return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" ); - } -} - -/** - * constructor - */ -function wfSpecialDoubleRedirects() { - list( $limit, $offset ) = wfCheckLimits(); - - $sdr = new DoubleRedirectsPage(); - - return $sdr->doQuery( $offset, $limit ); - -} - diff --git a/includes/SpecialEmailuser.php b/includes/SpecialEmailuser.php deleted file mode 100644 index 7de89dce..00000000 --- a/includes/SpecialEmailuser.php +++ /dev/null @@ -1,222 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * @todo document - */ -function wfSpecialEmailuser( $par ) { - global $wgUser, $wgOut, $wgRequest, $wgEnableEmail, $wgEnableUserEmail; - - if( !( $wgEnableEmail && $wgEnableUserEmail ) ) { - $wgOut->showErrorPage( "nosuchspecialpage", "nospecialpagetext" ); - return; - } - - if( !$wgUser->canSendEmail() ) { - wfDebug( "User can't send.\n" ); - $wgOut->showErrorPage( "mailnologin", "mailnologintext" ); - return; - } - - $action = $wgRequest->getVal( 'action' ); - $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); - if ( "" == $target ) { - wfDebug( "Target is empty.\n" ); - $wgOut->showErrorPage( "notargettitle", "notargettext" ); - return; - } - - $nt = Title::newFromURL( $target ); - if ( is_null( $nt ) ) { - wfDebug( "Target is invalid title.\n" ); - $wgOut->showErrorPage( "notargettitle", "notargettext" ); - return; - } - - $nu = User::newFromName( $nt->getText() ); - if( is_null( $nu ) || !$nu->canReceiveEmail() ) { - wfDebug( "Target is invalid user or can't receive.\n" ); - $wgOut->showErrorPage( "noemailtitle", "noemailtext" ); - return; - } - - if ( $wgUser->isBlockedFromEmailUser() ) { - // User has been blocked from sending e-mail. Show the std blocked form. - wfDebug( "User is blocked from sending e-mail.\n" ); - $wgOut->blockedPage(); - return; - } - - $f = new EmailUserForm( $nu ); - - if ( "success" == $action ) { - $f->showSuccess( $nu ); - } else if ( "submit" == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) - { - # Check against the rate limiter - if( $wgUser->pingLimiter( 'emailuser' ) ) { - $wgOut->rateLimited(); - return; - } - - $f->doSubmit(); - } else { - $f->showForm(); - } -} - -/** - * Implements the Special:Emailuser web interface, and invokes userMailer for sending the email message. - * @addtogroup SpecialPage - */ -class EmailUserForm { - - var $target; - var $text, $subject; - var $cc_me; // Whether user requested to be sent a separate copy of their email. - - /** - * @param User $target - */ - function EmailUserForm( $target ) { - global $wgRequest; - $this->target = $target; - $this->text = $wgRequest->getText( 'wpText' ); - $this->subject = $wgRequest->getText( 'wpSubject' ); - $this->cc_me = $wgRequest->getBool( 'wpCCMe' ); - } - - function showForm() { - global $wgOut, $wgUser; - - $wgOut->setPagetitle( wfMsg( "emailpage" ) ); - $wgOut->addWikiMsg( "emailpagetext" ); - - if ( $this->subject === "" ) { - $this->subject = wfMsg( "defemailsubject" ); - } - - $emf = wfMsg( "emailfrom" ); - $sender = $wgUser->getName(); - $emt = wfMsg( "emailto" ); - $rcpt = $this->target->getName(); - $emr = wfMsg( "emailsubject" ); - $emm = wfMsg( "emailmessage" ); - $ems = wfMsg( "emailsend" ); - $emc = wfMsg( "emailccme" ); - $encSubject = htmlspecialchars( $this->subject ); - - $titleObj = SpecialPage::getTitleFor( "Emailuser" ); - $action = $titleObj->escapeLocalURL( "target=" . - urlencode( $this->target->getName() ) . "&action=submit" ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( " -<form id=\"emailuser\" method=\"post\" action=\"{$action}\"> -<table border='0' id='mailheader'><tr> -<td align='right'>{$emf}:</td> -<td align='left'><strong>" . htmlspecialchars( $sender ) . "</strong></td> -</tr><tr> -<td align='right'>{$emt}:</td> -<td align='left'><strong>" . htmlspecialchars( $rcpt ) . "</strong></td> -</tr><tr> -<td align='right'>{$emr}:</td> -<td align='left'> -<input type='text' size='60' maxlength='200' name=\"wpSubject\" value=\"{$encSubject}\" /> -</td> -</tr> -</table> -<span id='wpTextLabel'><label for=\"wpText\">{$emm}:</label><br /></span> -<textarea id=\"wpText\" name=\"wpText\" rows='20' cols='80' style=\"width: 100%;\">" . htmlspecialchars( $this->text ) . -"</textarea> -" . wfCheckLabel( $emc, 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) . "<br /> -<input type='submit' name=\"wpSend\" value=\"{$ems}\" /> -<input type='hidden' name='wpEditToken' value=\"$token\" /> -</form>\n" ); - - } - - function doSubmit() { - global $wgOut, $wgUser, $wgUserEmailUseReplyTo; - - $to = new MailAddress( $this->target ); - $from = new MailAddress( $wgUser ); - $subject = $this->subject; - - if( wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$this->text ) ) ) { - - if( $wgUserEmailUseReplyTo ) { - // Put the generic wiki autogenerated address in the From: - // header and reserve the user for Reply-To. - // - // This is a bit ugly, but will serve to differentiate - // wiki-borne mails from direct mails and protects against - // SPF and bounce problems with some mailers (see below). - global $wgPasswordSender; - $mailFrom = new MailAddress( $wgPasswordSender ); - $replyTo = $from; - } else { - // Put the sending user's e-mail address in the From: header. - // - // This is clean-looking and convenient, but has issues. - // One is that it doesn't as clearly differentiate the wiki mail - // from "directly" sent mails. - // - // Another is that some mailers (like sSMTP) will use the From - // address as the envelope sender as well. For open sites this - // can cause mails to be flunked for SPF violations (since the - // wiki server isn't an authorized sender for various users' - // domains) as well as creating a privacy issue as bounces - // containing the recipient's e-mail address may get sent to - // the sending user. - $mailFrom = $from; - $replyTo = null; - } - - $mailResult = UserMailer::send( $to, $mailFrom, $subject, $this->text, $replyTo ); - - if( WikiError::isError( $mailResult ) ) { - $wgOut->addHTML( wfMsg( "usermailererror" ) . - ' ' . htmlspecialchars( $mailResult->getMessage() ) ); - } else { - - // if the user requested a copy of this mail, do this now, - // unless they are emailing themselves, in which case one copy of the message is sufficient. - if ($this->cc_me && $to != $from) { - $cc_subject = wfMsg('emailccsubject', $this->target->getName(), $subject); - if( wfRunHooks( 'EmailUser', array( &$from, &$from, &$cc_subject, &$this->text ) ) ) { - $ccResult = UserMailer::send( $from, $from, $cc_subject, $this->text ); - if( WikiError::isError( $ccResult ) ) { - // At this stage, the user's CC mail has failed, but their - // original mail has succeeded. It's unlikely, but still, what to do? - // We can either show them an error, or we can say everything was fine, - // or we can say we sort of failed AND sort of succeeded. Of these options, - // simply saying there was an error is probably best. - $wgOut->addHTML( wfMsg( "usermailererror" ) . - ' ' . htmlspecialchars( $ccResult->getMessage() ) ); - return; - } - } - } - - $titleObj = SpecialPage::getTitleFor( "Emailuser" ); - $encTarget = wfUrlencode( $this->target->getName() ); - $wgOut->redirect( $titleObj->getFullURL( "target={$encTarget}&action=success" ) ); - wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $this->text ) ); - } - } - } - - function showSuccess( &$user ) { - global $wgOut; - - $wgOut->setPagetitle( wfMsg( "emailsent" ) ); - $wgOut->addHTML( wfMsg( "emailsenttext" ) ); - - $wgOut->returnToMain( false, $user->getUserPage() ); - } -} diff --git a/includes/SpecialExport.php b/includes/SpecialExport.php deleted file mode 100644 index 1fe2e44b..00000000 --- a/includes/SpecialExport.php +++ /dev/null @@ -1,284 +0,0 @@ -<?php -# Copyright (C) 2003 Brion Vibber <brion@pobox.com> -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html -/** - * - * @addtogroup SpecialPage - */ - -function wfExportGetPagesFromCategory( $title ) { - global $wgContLang; - - $name = $title->getDBkey(); - - $dbr = wfGetDB( DB_SLAVE ); - - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $sql = "SELECT page_namespace, page_title FROM $page " . - "JOIN $categorylinks ON cl_from = page_id " . - "WHERE cl_to = " . $dbr->addQuotes( $name ); - - $pages = array(); - $res = $dbr->query( $sql, 'wfExportGetPagesFromCategory' ); - while ( $row = $dbr->fetchObject( $res ) ) { - $n = $row->page_title; - if ($row->page_namespace) { - $ns = $wgContLang->getNsText( $row->page_namespace ); - $n = $ns . ':' . $n; - } - - $pages[] = $n; - } - $dbr->freeResult($res); - - return $pages; -} - -/** - * Expand a list of pages to include templates used in those pages. - * @param $inputPages array, list of titles to look up - * @param $pageSet array, associative array indexed by titles for output - * @return array associative array index by titles - */ -function wfExportGetTemplates( $inputPages, $pageSet ) { - return wfExportGetLinks( $inputPages, $pageSet, - 'templatelinks', - array( 'tl_namespace AS namespace', 'tl_title AS title' ), - array( 'page_id=tl_from' ) ); -} - -/** - * Expand a list of pages to include images used in those pages. - * @param $inputPages array, list of titles to look up - * @param $pageSet array, associative array indexed by titles for output - * @return array associative array index by titles - */ -function wfExportGetImages( $inputPages, $pageSet ) { - return wfExportGetLinks( $inputPages, $pageSet, - 'imagelinks', - array( NS_IMAGE . ' AS namespace', 'il_to AS title' ), - array( 'page_id=il_from' ) ); -} - -/** - * Expand a list of pages to include items used in those pages. - * @private - */ -function wfExportGetLinks( $inputPages, $pageSet, $table, $fields, $join ) { - $dbr = wfGetDB( DB_SLAVE ); - foreach( $inputPages as $page ) { - $title = Title::newFromText( $page ); - if( $title ) { - $pageSet[$title->getPrefixedText()] = true; - /// @fixme May or may not be more efficient to batch these - /// by namespace when given multiple input pages. - $result = $dbr->select( - array( 'page', $table ), - $fields, - array_merge( $join, - array( - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDbKey() ) ), - __METHOD__ ); - foreach( $result as $row ) { - $template = Title::makeTitle( $row->namespace, $row->title ); - $pageSet[$template->getPrefixedText()] = true; - } - } - } - return $pageSet; -} - -/** - * Callback function to remove empty strings from the pages array. - */ -function wfFilterPage( $page ) { - return $page !== '' && $page !== null; -} - -/** - * - */ -function wfSpecialExport( $page = '' ) { - global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors; - global $wgExportAllowHistory, $wgExportMaxHistory; - - $curonly = true; - $doexport = false; - - if ( $wgRequest->getCheck( 'addcat' ) ) { - $page = $wgRequest->getText( 'pages' ); - $catname = $wgRequest->getText( 'catname' ); - - if ( $catname !== '' && $catname !== NULL && $catname !== false ) { - $t = Title::makeTitleSafe( NS_CATEGORY, $catname ); - if ( $t ) { - /** - * @fixme This can lead to hitting memory limit for very large - * categories. Ideally we would do the lookup synchronously - * during the export in a single query. - */ - $catpages = wfExportGetPagesFromCategory( $t ); - if ( $catpages ) $page .= "\n" . implode( "\n", $catpages ); - } - } - } - else if( $wgRequest->wasPosted() && $page == '' ) { - $page = $wgRequest->getText( 'pages' ); - $curonly = $wgRequest->getCheck( 'curonly' ); - $rawOffset = $wgRequest->getVal( 'offset' ); - if( $rawOffset ) { - $offset = wfTimestamp( TS_MW, $rawOffset ); - } else { - $offset = null; - } - $limit = $wgRequest->getInt( 'limit' ); - $dir = $wgRequest->getVal( 'dir' ); - $history = array( - 'dir' => 'asc', - 'offset' => false, - 'limit' => $wgExportMaxHistory, - ); - $historyCheck = $wgRequest->getCheck( 'history' ); - if ( $curonly ) { - $history = WikiExporter::CURRENT; - } elseif ( !$historyCheck ) { - if ( $limit > 0 && $limit < $wgExportMaxHistory ) { - $history['limit'] = $limit; - } - if ( !is_null( $offset ) ) { - $history['offset'] = $offset; - } - if ( strtolower( $dir ) == 'desc' ) { - $history['dir'] = 'desc'; - } - } - - if( $page != '' ) $doexport = true; - } else { - // Default to current-only for GET requests - $page = $wgRequest->getText( 'pages', $page ); - $historyCheck = $wgRequest->getCheck( 'history' ); - if( $historyCheck ) { - $history = WikiExporter::FULL; - } else { - $history = WikiExporter::CURRENT; - } - - if( $page != '' ) $doexport = true; - } - - if( !$wgExportAllowHistory ) { - // Override - $history = WikiExporter::CURRENT; - } - - $list_authors = $wgRequest->getCheck( 'listauthors' ); - if ( !$curonly || !$wgExportAllowListContributors ) $list_authors = false ; - - if ( $doexport ) { - $wgOut->disable(); - - // Cancel output buffering and gzipping if set - // This should provide safer streaming for pages with history - wfResetOutputBuffers(); - header( "Content-type: application/xml; charset=utf-8" ); - if( $wgRequest->getCheck( 'wpDownload' ) ) { - // Provide a sane filename suggestion - $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); - $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" ); - } - - /* Split up the input and look up linked pages */ - $inputPages = array_filter( explode( "\n", $page ), 'wfFilterPage' ); - $pageSet = array_flip( $inputPages ); - - if( $wgRequest->getCheck( 'templates' ) ) { - $pageSet = wfExportGetTemplates( $inputPages, $pageSet ); - } - - /* - // Enable this when we can do something useful exporting/importing image information. :) - if( $wgRequest->getCheck( 'images' ) ) { - $pageSet = wfExportGetImages( $inputPages, $pageSet ); - } - */ - - $pages = array_keys( $pageSet ); - - /* Ok, let's get to it... */ - - $db = wfGetDB( DB_SLAVE ); - $exporter = new WikiExporter( $db, $history ); - $exporter->list_authors = $list_authors ; - $exporter->openStream(); - - foreach( $pages as $page ) { - /* - if( $wgExportMaxHistory && !$curonly ) { - $title = Title::newFromText( $page ); - if( $title ) { - $count = Revision::countByTitle( $db, $title ); - if( $count > $wgExportMaxHistory ) { - wfDebug( __FUNCTION__ . - ": Skipped $page, $count revisions too big\n" ); - continue; - } - } - }*/ - - #Bug 8824: Only export pages the user can read - $title = Title::newFromText( $page ); - if( is_null( $title ) ) continue; #TODO: perhaps output an <error> tag or something. - if( !$title->userCanRead() ) continue; #TODO: perhaps output an <error> tag or something. - - $exporter->pageByTitle( $title ); - } - - $exporter->closeStream(); - return; - } - - $self = SpecialPage::getTitleFor( 'Export' ); - $wgOut->addHtml( wfMsgExt( 'exporttext', 'parse' ) ); - - $form = Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $self->getLocalUrl( 'action=submit' ) ) ); - - $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . ' '; - $form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '<br />'; - - $form .= Xml::openElement( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ) ); - $form .= htmlspecialchars( $page ); - $form .= Xml::closeElement( 'textarea' ); - $form .= '<br />'; - - if( $wgExportAllowHistory ) { - $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />'; - } else { - $wgOut->addHtml( wfMsgExt( 'exportnohistory', 'parse' ) ); - } - $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />'; - // Enable this when we can do something useful exporting/importing image information. :) - //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '<br />'; - $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '<br />'; - - $form .= Xml::submitButton( wfMsg( 'export-submit' ) ); - $form .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $form ); -}
\ No newline at end of file diff --git a/includes/SpecialFewestrevisions.php b/includes/SpecialFewestrevisions.php deleted file mode 100644 index ba6db8b6..00000000 --- a/includes/SpecialFewestrevisions.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php - -/** - * Special page for listing the articles with the fewest revisions. - * - * @package MediaWiki - * @addtogroup SpecialPage - * @author Martin Drashkov - */ -class FewestrevisionsPage extends QueryPage { - - function getName() { - return 'Fewestrevisions'; - } - - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getSql() { - $dbr = wfGetDB( DB_SLAVE ); - list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); - - return "SELECT 'Fewestrevisions' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $revision - JOIN $page ON page_id = rev_page - WHERE page_namespace = " . NS_MAIN . " - GROUP BY 1,2,3 - HAVING COUNT(*) > 1"; - } - - function sortDescending() { - return false; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitleSafe( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->makeKnownLinkObj( $nt, $text ); - - $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ); - - return wfSpecialList( $plink, $nlink ); - } -} - -function wfSpecialFewestrevisions() { - list( $limit, $offset ) = wfCheckLimits(); - $frp = new FewestrevisionsPage(); - $frp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialFilepath.php b/includes/SpecialFilepath.php deleted file mode 100644 index 4ba8fdb0..00000000 --- a/includes/SpecialFilepath.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php - -function wfSpecialFilepath( $par ) { - global $wgRequest, $wgOut; - - $file = isset( $par ) ? $par : $wgRequest->getText( 'file' ); - - $title = Title::newFromText( $file, NS_IMAGE ); - - if ( ! $title instanceof Title || $title->getNamespace() != NS_IMAGE ) { - $cform = new FilepathForm( $title ); - $cform->execute(); - } else { - $file = wfFindFile( $title ); - if ( $file && $file->exists() ) { - $wgOut->redirect( $file->getURL() ); - } else { - $wgOut->setStatusCode( 404 ); - $cform = new FilepathForm( $title ); - $cform->execute(); - } - } -} - -class FilepathForm { - var $mTitle; - - function FilepathForm( &$title ) { - $this->mTitle =& $title; - } - - function execute() { - global $wgOut, $wgTitle, $wgScript; - - $wgOut->addHTML( - wfElement( 'form', - array( - 'id' => 'specialfilepath', - 'method' => 'get', - 'action' => $wgScript, - ), - null - ) . - wfHidden( 'title', $wgTitle->getPrefixedText() ) . - wfOpenElement( 'label' ) . - wfMsgHtml( 'filepath-page' ) . - ' ' . - wfElement( 'input', - array( - 'type' => 'text', - 'size' => 25, - 'name' => 'file', - 'value' => is_object( $this->mTitle ) ? $this->mTitle->getText() : '' - ), - '' - ) . - ' ' . - wfElement( 'input', - array( - 'type' => 'submit', - 'value' => wfMsgHtml( 'filepath-submit' ) - ), - '' - ) . - wfCloseElement( 'label' ) . - wfCloseElement( 'form' ) - ); - } -} diff --git a/includes/SpecialImagelist.php b/includes/SpecialImagelist.php deleted file mode 100644 index 1688fe7c..00000000 --- a/includes/SpecialImagelist.php +++ /dev/null @@ -1,166 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -function wfSpecialImagelist() { - global $wgOut; - - $pager = new ImageListPager; - - $limit = $pager->getForm(); - $body = $pager->getBody(); - $nav = $pager->getNavigationBar(); - $wgOut->addHTML( - $limit - . '<br/>' - . $body - . '<br/>' - . $nav ); -} - -/** - * @addtogroup SpecialPage - * @addtogroup Pager - */ -class ImageListPager extends TablePager { - var $mFieldNames = null; - var $mMessages = array(); - var $mQueryConds = array(); - - function __construct() { - global $wgRequest, $wgMiserMode; - if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) { - $this->mDefaultDirection = true; - } else { - $this->mDefaultDirection = false; - } - $search = $wgRequest->getText( 'ilsearch' ); - if ( $search != '' && !$wgMiserMode ) { - $nt = Title::newFromUrl( $search ); - if( $nt ) { - $dbr = wfGetDB( DB_SLAVE ); - $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); - $m = str_replace( "%", "\\%", $m ); - $m = str_replace( "_", "\\_", $m ); - $this->mQueryConds = array( "LOWER(img_name) LIKE '%{$m}%'" ); - } - } - - parent::__construct(); - } - - function getFieldNames() { - if ( !$this->mFieldNames ) { - $this->mFieldNames = array( - 'img_timestamp' => wfMsg( 'imagelist_date' ), - 'img_name' => wfMsg( 'imagelist_name' ), - 'img_user_text' => wfMsg( 'imagelist_user' ), - 'img_size' => wfMsg( 'imagelist_size' ), - 'img_description' => wfMsg( 'imagelist_description' ), - ); - } - return $this->mFieldNames; - } - - function isFieldSortable( $field ) { - static $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); - return in_array( $field, $sortable ); - } - - function getQueryInfo() { - $fields = $this->getFieldNames(); - $fields = array_keys( $fields ); - $fields[] = 'img_user'; - return array( - 'tables' => 'image', - 'fields' => $fields, - 'conds' => $this->mQueryConds - ); - } - - function getDefaultSort() { - return 'img_timestamp'; - } - - function getStartBody() { - # Do a link batch query for user pages - if ( $this->mResult->numRows() ) { - $lb = new LinkBatch; - $this->mResult->seek( 0 ); - while ( $row = $this->mResult->fetchObject() ) { - if ( $row->img_user ) { - $lb->add( NS_USER, str_replace( ' ', '_', $row->img_user_text ) ); - } - } - $lb->execute(); - } - - # Cache messages used in each row - $this->mMessages['imgdesc'] = wfMsgHtml( 'imgdesc' ); - $this->mMessages['imgfile'] = wfMsgHtml( 'imgfile' ); - - return parent::getStartBody(); - } - - function formatValue( $field, $value ) { - global $wgLang; - switch ( $field ) { - case 'img_timestamp': - return $wgLang->timeanddate( $value, true ); - case 'img_name': - $name = $this->mCurrentRow->img_name; - $link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), $value ); - $image = wfLocalFile( $value ); - $url = $image->getURL(); - $download = Xml::element('a', array( "href" => $url ), $this->mMessages['imgfile'] ); - return "$link ($download)"; - case 'img_user_text': - if ( $this->mCurrentRow->img_user ) { - $link = $this->getSkin()->makeLinkObj( Title::makeTitle( NS_USER, $value ), - htmlspecialchars( $value ) ); - } else { - $link = htmlspecialchars( $value ); - } - return $link; - case 'img_size': - return $this->getSkin()->formatSize( $value ); - case 'img_description': - return $this->getSkin()->commentBlock( $value ); - } - } - - function getForm() { - global $wgRequest, $wgMiserMode; - $url = $this->getTitle()->escapeLocalURL(); - $search = $wgRequest->getText( 'ilsearch' ); - $s = "<form method=\"get\" action=\"$url\">\n" . - wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ); - if ( !$wgMiserMode ) { - $s .= "<br/>\n" . - Xml::inputLabel( wfMsg( 'imagelist_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search ); - } - $s .= " " . Xml::submitButton( wfMsg( 'table_pager_limit_submit' ) ) ." \n" . - $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) . - "</form>\n"; - return $s; - } - - function getTableClass() { - return 'imagelist ' . parent::getTableClass(); - } - - function getNavClass() { - return 'imagelist_nav ' . parent::getNavClass(); - } - - function getSortHeaderClass() { - return 'imagelist_sort ' . parent::getSortHeaderClass(); - } -} - - diff --git a/includes/SpecialImport.php b/includes/SpecialImport.php deleted file mode 100644 index 7a2e6221..00000000 --- a/includes/SpecialImport.php +++ /dev/null @@ -1,921 +0,0 @@ -<?php -/** - * MediaWiki page data importer - * Copyright (C) 2003,2005 Brion Vibber <brion@pobox.com> - * http://www.mediawiki.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup SpecialPage - */ - -/** - * Constructor - */ -function wfSpecialImport( $page = '' ) { - global $wgUser, $wgOut, $wgRequest, $wgTitle, $wgImportSources; - global $wgImportTargetNamespace; - - $interwiki = false; - $namespace = $wgImportTargetNamespace; - $frompage = ''; - $history = true; - - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - if( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit') { - $isUpload = false; - $namespace = $wgRequest->getIntOrNull( 'namespace' ); - - switch( $wgRequest->getVal( "source" ) ) { - case "upload": - $isUpload = true; - if( $wgUser->isAllowed( 'importupload' ) ) { - $source = ImportStreamSource::newFromUpload( "xmlimport" ); - } else { - return $wgOut->permissionRequired( 'importupload' ); - } - break; - case "interwiki": - $interwiki = $wgRequest->getVal( 'interwiki' ); - $history = $wgRequest->getCheck( 'interwikiHistory' ); - $frompage = $wgRequest->getText( "frompage" ); - $source = ImportStreamSource::newFromInterwiki( - $interwiki, - $frompage, - $history ); - break; - default: - $source = new WikiErrorMsg( "importunknownsource" ); - } - - if( WikiError::isError( $source ) ) { - $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $source->getMessage() ) ); - } else { - $wgOut->addWikiMsg( "importstart" ); - - $importer = new WikiImporter( $source ); - if( !is_null( $namespace ) ) { - $importer->setTargetNamespace( $namespace ); - } - $reporter = new ImportReporter( $importer, $isUpload, $interwiki ); - - $reporter->open(); - $result = $importer->doImport(); - $resultCount = $reporter->close(); - - if( WikiError::isError( $result ) ) { - # No source or XML parse error - $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $result->getMessage() ) ); - } elseif( WikiError::isError( $resultCount ) ) { - # Zero revisions - $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $resultCount->getMessage() ) ); - } else { - # Success! - $wgOut->addWikiMsg( 'importsuccess' ); - } - $wgOut->addWikiText( '<hr />' ); - } - } - - $action = $wgTitle->getLocalUrl( 'action=submit' ); - - if( $wgUser->isAllowed( 'importupload' ) ) { - $wgOut->addWikiMsg( "importtext" ); - $wgOut->addHTML( - Xml::openElement( 'fieldset' ). - Xml::element( 'legend', null, wfMsg( 'upload' ) ) . - Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post', 'action' => $action ) ) . - Xml::hidden( 'action', 'submit' ) . - Xml::hidden( 'source', 'upload' ) . - "<input type='file' name='xmlimport' value='' size='30' />" . // No Xml function for type=file? Todo? - Xml::submitButton( wfMsg( 'uploadbtn' ) ) . - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) - ); - } else { - if( empty( $wgImportSources ) ) { - $wgOut->addWikiMsg( 'importnosources' ); - } - } - - if( !empty( $wgImportSources ) ) { - $wgOut->addHTML( - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'importinterwiki' ) ) . - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action ) ) . - wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) . - Xml::hidden( 'action', 'submit' ) . - Xml::hidden( 'source', 'interwiki' ) . - Xml::openElement( 'table' ) . - "<tr> - <td>" . - Xml::openElement( 'select', array( 'name' => 'interwiki' ) ) - ); - foreach( $wgImportSources as $prefix ) { - $selected = ( $interwiki === $prefix ) ? ' selected="selected"' : ''; - $wgOut->addHTML( Xml::option( $prefix, $prefix, $selected ) ); - } - $wgOut->addHTML( - Xml::closeElement( 'select' ) . - "</td> - <td>" . - Xml::input( 'frompage', 50, $frompage ) . - "</td> - </tr> - <tr> - <td> - </td> - <td>" . - Xml::checkLabel( wfMsg( 'import-interwiki-history' ), 'interwikiHistory', 'interwikiHistory', $history ) . - "</td> - </tr> - <tr> - <td> - </td> - <td>" . - Xml::label( wfMsg( 'import-interwiki-namespace' ), 'namespace' ) . - Xml::namespaceSelector( $namespace, '' ) . - "</td> - </tr> - <tr> - <td> - </td> - <td>" . - Xml::submitButton( wfMsg( 'import-interwiki-submit' ) ) . - "</td> - </tr>" . - Xml::closeElement( 'table' ). - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) - ); - } -} - -/** - * Reporting callback - * @addtogroup SpecialPage - */ -class ImportReporter { - function __construct( $importer, $upload, $interwiki ) { - $importer->setPageOutCallback( array( $this, 'reportPage' ) ); - $this->mPageCount = 0; - $this->mIsUpload = $upload; - $this->mInterwiki = $interwiki; - } - - function open() { - global $wgOut; - $wgOut->addHtml( "<ul>\n" ); - } - - function reportPage( $title, $origTitle, $revisionCount, $successCount ) { - global $wgOut, $wgUser, $wgLang, $wgContLang; - - $skin = $wgUser->getSkin(); - - $this->mPageCount++; - - $localCount = $wgLang->formatNum( $successCount ); - $contentCount = $wgContLang->formatNum( $successCount ); - - if( $successCount > 0 ) { - $wgOut->addHtml( "<li>" . $skin->makeKnownLinkObj( $title ) . " " . - wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) . - "</li>\n" - ); - - $log = new LogPage( 'import' ); - if( $this->mIsUpload ) { - $detail = wfMsgExt( 'import-logentry-upload-detail', array( 'content', 'parsemag' ), - $contentCount ); - $log->addEntry( 'upload', $title, $detail ); - } else { - $interwiki = '[[:' . $this->mInterwiki . ':' . - $origTitle->getPrefixedText() . ']]'; - $detail = wfMsgExt( 'import-logentry-interwiki-detail', array( 'content', 'parsemag' ), - $contentCount, $interwiki ); - $log->addEntry( 'interwiki', $title, $detail ); - } - - $comment = $detail; // quick - $dbw = wfGetDB( DB_MASTER ); - $nullRevision = Revision::newNullRevision( - $dbw, $title->getArticleId(), $comment, true ); - $nullRevision->insertOn( $dbw ); - # Update page record - $article = new Article( $title ); - $article->updateRevisionOn( $dbw, $nullRevision ); - } else { - $wgOut->addHtml( '<li>' . wfMsgHtml( 'import-nonewrevisions' ) . '</li>' ); - } - } - - function close() { - global $wgOut; - if( $this->mPageCount == 0 ) { - $wgOut->addHtml( "</ul>\n" ); - return new WikiErrorMsg( "importnopages" ); - } - $wgOut->addHtml( "</ul>\n" ); - - return $this->mPageCount; - } -} - -/** - * - * @addtogroup SpecialPage - */ -class WikiRevision { - var $title = null; - var $id = 0; - var $timestamp = "20010115000000"; - var $user = 0; - var $user_text = ""; - var $text = ""; - var $comment = ""; - var $minor = false; - - function setTitle( $title ) { - if( is_object( $title ) ) { - $this->title = $title; - } elseif( is_null( $title ) ) { - throw new MWException( "WikiRevision given a null title in import. You may need to adjust \$wgLegalTitleChars." ); - } else { - throw new MWException( "WikiRevision given non-object title in import." ); - } - } - - function setID( $id ) { - $this->id = $id; - } - - function setTimestamp( $ts ) { - # 2003-08-05T18:30:02Z - $this->timestamp = wfTimestamp( TS_MW, $ts ); - } - - function setUsername( $user ) { - $this->user_text = $user; - } - - function setUserIP( $ip ) { - $this->user_text = $ip; - } - - function setText( $text ) { - $this->text = $text; - } - - function setComment( $text ) { - $this->comment = $text; - } - - function setMinor( $minor ) { - $this->minor = (bool)$minor; - } - - function getTitle() { - return $this->title; - } - - function getID() { - return $this->id; - } - - function getTimestamp() { - return $this->timestamp; - } - - function getUser() { - return $this->user_text; - } - - function getText() { - return $this->text; - } - - function getComment() { - return $this->comment; - } - - function getMinor() { - return $this->minor; - } - - function importOldRevision() { - $dbw = wfGetDB( DB_MASTER ); - - # Sneak a single revision into place - $user = User::newFromName( $this->getUser() ); - if( $user ) { - $userId = intval( $user->getId() ); - $userText = $user->getName(); - } else { - $userId = 0; - $userText = $this->getUser(); - } - - // avoid memory leak...? - $linkCache =& LinkCache::singleton(); - $linkCache->clear(); - - $article = new Article( $this->title ); - $pageId = $article->getId(); - if( $pageId == 0 ) { - # must create the page... - $pageId = $article->insertOn( $dbw ); - $created = true; - } else { - $created = false; - - $prior = Revision::loadFromTimestamp( $dbw, $this->title, $this->timestamp ); - if( !is_null( $prior ) ) { - // FIXME: this could fail slightly for multiple matches :P - wfDebug( __METHOD__ . ": skipping existing revision for [[" . - $this->title->getPrefixedText() . "]], timestamp " . - $this->timestamp . "\n" ); - return false; - } - } - - # FIXME: Use original rev_id optionally - # FIXME: blah blah blah - - #if( $numrows > 0 ) { - # return wfMsg( "importhistoryconflict" ); - #} - - # Insert the row - $revision = new Revision( array( - 'page' => $pageId, - 'text' => $this->getText(), - 'comment' => $this->getComment(), - 'user' => $userId, - 'user_text' => $userText, - 'timestamp' => $this->timestamp, - 'minor_edit' => $this->minor, - ) ); - $revId = $revision->insertOn( $dbw ); - $changed = $article->updateIfNewerOn( $dbw, $revision ); - - if( $created ) { - wfDebug( __METHOD__ . ": running onArticleCreate\n" ); - Article::onArticleCreate( $this->title ); - - wfDebug( __METHOD__ . ": running create updates\n" ); - $article->createUpdates( $revision ); - - } elseif( $changed ) { - wfDebug( __METHOD__ . ": running onArticleEdit\n" ); - Article::onArticleEdit( $this->title ); - - wfDebug( __METHOD__ . ": running edit updates\n" ); - $article->editUpdates( - $this->getText(), - $this->getComment(), - $this->minor, - $this->timestamp, - $revId ); - } - - return true; - } - -} - -/** - * implements Special:Import - * @addtogroup SpecialPage - */ -class WikiImporter { - var $mSource = null; - var $mPageCallback = null; - var $mPageOutCallback = null; - var $mRevisionCallback = null; - var $mTargetNamespace = null; - var $lastfield; - - function WikiImporter( $source ) { - $this->setRevisionCallback( array( &$this, "importRevision" ) ); - $this->mSource = $source; - } - - function throwXmlError( $err ) { - $this->debug( "FAILURE: $err" ); - wfDebug( "WikiImporter XML error: $err\n" ); - } - - # -------------- - - function doImport() { - if( empty( $this->mSource ) ) { - return new WikiErrorMsg( "importnotext" ); - } - - $parser = xml_parser_create( "UTF-8" ); - - # case folding violates XML standard, turn it off - xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); - - xml_set_object( $parser, $this ); - xml_set_element_handler( $parser, "in_start", "" ); - - $offset = 0; // for context extraction on error reporting - do { - $chunk = $this->mSource->readChunk(); - if( !xml_parse( $parser, $chunk, $this->mSource->atEnd() ) ) { - wfDebug( "WikiImporter::doImport encountered XML parsing error\n" ); - return new WikiXmlError( $parser, wfMsgHtml( 'import-parse-failure' ), $chunk, $offset ); - } - $offset += strlen( $chunk ); - } while( $chunk !== false && !$this->mSource->atEnd() ); - xml_parser_free( $parser ); - - return true; - } - - function debug( $data ) { - #wfDebug( "IMPORT: $data\n" ); - } - - function notice( $data ) { - global $wgCommandLineMode; - if( $wgCommandLineMode ) { - print "$data\n"; - } else { - global $wgOut; - $wgOut->addHTML( "<li>" . htmlspecialchars( $data ) . "</li>\n" ); - } - } - - /** - * Sets the action to perform as each new page in the stream is reached. - * @param callable $callback - * @return callable - */ - function setPageCallback( $callback ) { - $previous = $this->mPageCallback; - $this->mPageCallback = $callback; - return $previous; - } - - /** - * Sets the action to perform as each page in the stream is completed. - * Callback accepts the page title (as a Title object), a second object - * with the original title form (in case it's been overridden into a - * local namespace), and a count of revisions. - * - * @param callable $callback - * @return callable - */ - function setPageOutCallback( $callback ) { - $previous = $this->mPageOutCallback; - $this->mPageOutCallback = $callback; - return $previous; - } - - /** - * Sets the action to perform as each page revision is reached. - * @param callable $callback - * @return callable - */ - function setRevisionCallback( $callback ) { - $previous = $this->mRevisionCallback; - $this->mRevisionCallback = $callback; - return $previous; - } - - /** - * Set a target namespace to override the defaults - */ - function setTargetNamespace( $namespace ) { - if( is_null( $namespace ) ) { - // Don't override namespaces - $this->mTargetNamespace = null; - } elseif( $namespace >= 0 ) { - // FIXME: Check for validity - $this->mTargetNamespace = intval( $namespace ); - } else { - return false; - } - } - - /** - * Default per-revision callback, performs the import. - * @param WikiRevision $revision - * @private - */ - function importRevision( &$revision ) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->deadlockLoop( array( &$revision, 'importOldRevision' ) ); - } - - /** - * Alternate per-revision callback, for debugging. - * @param WikiRevision $revision - * @private - */ - function debugRevisionHandler( &$revision ) { - $this->debug( "Got revision:" ); - if( is_object( $revision->title ) ) { - $this->debug( "-- Title: " . $revision->title->getPrefixedText() ); - } else { - $this->debug( "-- Title: <invalid>" ); - } - $this->debug( "-- User: " . $revision->user_text ); - $this->debug( "-- Timestamp: " . $revision->timestamp ); - $this->debug( "-- Comment: " . $revision->comment ); - $this->debug( "-- Text: " . $revision->text ); - } - - /** - * Notify the callback function when a new <page> is reached. - * @param Title $title - * @private - */ - function pageCallback( $title ) { - if( is_callable( $this->mPageCallback ) ) { - call_user_func( $this->mPageCallback, $title ); - } - } - - /** - * Notify the callback function when a </page> is closed. - * @param Title $title - * @param Title $origTitle - * @param int $revisionCount - * @param int $successCount number of revisions for which callback returned true - * @private - */ - function pageOutCallback( $title, $origTitle, $revisionCount, $successCount ) { - if( is_callable( $this->mPageOutCallback ) ) { - call_user_func( $this->mPageOutCallback, $title, $origTitle, - $revisionCount, $successCount ); - } - } - - - # XML parser callbacks from here out -- beware! - function donothing( $parser, $x, $y="" ) { - #$this->debug( "donothing" ); - } - - function in_start( $parser, $name, $attribs ) { - $this->debug( "in_start $name" ); - if( $name != "mediawiki" ) { - return $this->throwXMLerror( "Expected <mediawiki>, got <$name>" ); - } - xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" ); - } - - function in_mediawiki( $parser, $name, $attribs ) { - $this->debug( "in_mediawiki $name" ); - if( $name == 'siteinfo' ) { - xml_set_element_handler( $parser, "in_siteinfo", "out_siteinfo" ); - } elseif( $name == 'page' ) { - $this->workRevisionCount = 0; - $this->workSuccessCount = 0; - xml_set_element_handler( $parser, "in_page", "out_page" ); - } else { - return $this->throwXMLerror( "Expected <page>, got <$name>" ); - } - } - function out_mediawiki( $parser, $name ) { - $this->debug( "out_mediawiki $name" ); - if( $name != "mediawiki" ) { - return $this->throwXMLerror( "Expected </mediawiki>, got </$name>" ); - } - xml_set_element_handler( $parser, "donothing", "donothing" ); - } - - - function in_siteinfo( $parser, $name, $attribs ) { - // no-ops for now - $this->debug( "in_siteinfo $name" ); - switch( $name ) { - case "sitename": - case "base": - case "generator": - case "case": - case "namespaces": - case "namespace": - break; - default: - return $this->throwXMLerror( "Element <$name> not allowed in <siteinfo>." ); - } - } - - function out_siteinfo( $parser, $name ) { - if( $name == "siteinfo" ) { - xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" ); - } - } - - - function in_page( $parser, $name, $attribs ) { - $this->debug( "in_page $name" ); - switch( $name ) { - case "id": - case "title": - case "restrictions": - $this->appendfield = $name; - $this->appenddata = ""; - $this->parenttag = "page"; - xml_set_element_handler( $parser, "in_nothing", "out_append" ); - xml_set_character_data_handler( $parser, "char_append" ); - break; - case "revision": - if( is_object( $this->pageTitle ) ) { - $this->workRevision = new WikiRevision; - $this->workRevision->setTitle( $this->pageTitle ); - $this->workRevisionCount++; - } else { - // Skipping items due to invalid page title - $this->workRevision = null; - } - xml_set_element_handler( $parser, "in_revision", "out_revision" ); - break; - default: - return $this->throwXMLerror( "Element <$name> not allowed in a <page>." ); - } - } - - function out_page( $parser, $name ) { - $this->debug( "out_page $name" ); - if( $name != "page" ) { - return $this->throwXMLerror( "Expected </page>, got </$name>" ); - } - xml_set_element_handler( $parser, "in_mediawiki", "out_mediawiki" ); - - $this->pageOutCallback( $this->pageTitle, $this->origTitle, - $this->workRevisionCount, $this->workSuccessCount ); - - $this->workTitle = null; - $this->workRevision = null; - $this->workRevisionCount = 0; - $this->workSuccessCount = 0; - $this->pageTitle = null; - $this->origTitle = null; - } - - function in_nothing( $parser, $name, $attribs ) { - $this->debug( "in_nothing $name" ); - return $this->throwXMLerror( "No child elements allowed here; got <$name>" ); - } - function char_append( $parser, $data ) { - $this->debug( "char_append '$data'" ); - $this->appenddata .= $data; - } - function out_append( $parser, $name ) { - $this->debug( "out_append $name" ); - if( $name != $this->appendfield ) { - return $this->throwXMLerror( "Expected </{$this->appendfield}>, got </$name>" ); - } - xml_set_element_handler( $parser, "in_$this->parenttag", "out_$this->parenttag" ); - xml_set_character_data_handler( $parser, "donothing" ); - - switch( $this->appendfield ) { - case "title": - $this->workTitle = $this->appenddata; - $this->origTitle = Title::newFromText( $this->workTitle ); - if( !is_null( $this->mTargetNamespace ) && !is_null( $this->origTitle ) ) { - $this->pageTitle = Title::makeTitle( $this->mTargetNamespace, - $this->origTitle->getDBkey() ); - } else { - $this->pageTitle = Title::newFromText( $this->workTitle ); - } - if( is_null( $this->pageTitle ) ) { - // Invalid page title? Ignore the page - $this->notice( "Skipping invalid page title '$this->workTitle'" ); - } else { - $this->pageCallback( $this->workTitle ); - } - break; - case "id": - if ( $this->parenttag == 'revision' ) { - if( $this->workRevision ) - $this->workRevision->setID( $this->appenddata ); - } - break; - case "text": - if( $this->workRevision ) - $this->workRevision->setText( $this->appenddata ); - break; - case "username": - if( $this->workRevision ) - $this->workRevision->setUsername( $this->appenddata ); - break; - case "ip": - if( $this->workRevision ) - $this->workRevision->setUserIP( $this->appenddata ); - break; - case "timestamp": - if( $this->workRevision ) - $this->workRevision->setTimestamp( $this->appenddata ); - break; - case "comment": - if( $this->workRevision ) - $this->workRevision->setComment( $this->appenddata ); - break; - case "minor": - if( $this->workRevision ) - $this->workRevision->setMinor( true ); - break; - default: - $this->debug( "Bad append: {$this->appendfield}" ); - } - $this->appendfield = ""; - $this->appenddata = ""; - } - - function in_revision( $parser, $name, $attribs ) { - $this->debug( "in_revision $name" ); - switch( $name ) { - case "id": - case "timestamp": - case "comment": - case "minor": - case "text": - $this->parenttag = "revision"; - $this->appendfield = $name; - xml_set_element_handler( $parser, "in_nothing", "out_append" ); - xml_set_character_data_handler( $parser, "char_append" ); - break; - case "contributor": - xml_set_element_handler( $parser, "in_contributor", "out_contributor" ); - break; - default: - return $this->throwXMLerror( "Element <$name> not allowed in a <revision>." ); - } - } - - function out_revision( $parser, $name ) { - $this->debug( "out_revision $name" ); - if( $name != "revision" ) { - return $this->throwXMLerror( "Expected </revision>, got </$name>" ); - } - xml_set_element_handler( $parser, "in_page", "out_page" ); - - if( $this->workRevision ) { - $ok = call_user_func_array( $this->mRevisionCallback, - array( &$this->workRevision, &$this ) ); - if( $ok ) { - $this->workSuccessCount++; - } - } - } - - function in_contributor( $parser, $name, $attribs ) { - $this->debug( "in_contributor $name" ); - switch( $name ) { - case "username": - case "ip": - case "id": - $this->parenttag = "contributor"; - $this->appendfield = $name; - xml_set_element_handler( $parser, "in_nothing", "out_append" ); - xml_set_character_data_handler( $parser, "char_append" ); - break; - default: - $this->throwXMLerror( "Invalid tag <$name> in <contributor>" ); - } - } - - function out_contributor( $parser, $name ) { - $this->debug( "out_contributor $name" ); - if( $name != "contributor" ) { - return $this->throwXMLerror( "Expected </contributor>, got </$name>" ); - } - xml_set_element_handler( $parser, "in_revision", "out_revision" ); - } - -} - -/** - * @todo document (e.g. one-sentence class description). - * @addtogroup SpecialPage - */ -class ImportStringSource { - function ImportStringSource( $string ) { - $this->mString = $string; - $this->mRead = false; - } - - function atEnd() { - return $this->mRead; - } - - function readChunk() { - if( $this->atEnd() ) { - return false; - } else { - $this->mRead = true; - return $this->mString; - } - } -} - -/** - * @todo document (e.g. one-sentence class description). - * @addtogroup SpecialPage - */ -class ImportStreamSource { - function ImportStreamSource( $handle ) { - $this->mHandle = $handle; - } - - function atEnd() { - return feof( $this->mHandle ); - } - - function readChunk() { - return fread( $this->mHandle, 32768 ); - } - - static function newFromFile( $filename ) { - $file = @fopen( $filename, 'rt' ); - if( !$file ) { - return new WikiErrorMsg( "importcantopen" ); - } - return new ImportStreamSource( $file ); - } - - static function newFromUpload( $fieldname = "xmlimport" ) { - $upload =& $_FILES[$fieldname]; - - if( !isset( $upload ) || !$upload['name'] ) { - return new WikiErrorMsg( 'importnofile' ); - } - if( !empty( $upload['error'] ) ) { - switch($upload['error']){ - case 1: # The uploaded file exceeds the upload_max_filesize directive in php.ini. - return new WikiErrorMsg( 'importuploaderrorsize' ); - case 2: # The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form. - return new WikiErrorMsg( 'importuploaderrorsize' ); - case 3: # The uploaded file was only partially uploaded - return new WikiErrorMsg( 'importuploaderrorpartial' ); - case 6: #Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3. - return new WikiErrorMsg( 'importuploaderrortemp' ); - # case else: # Currently impossible - } - - } - $fname = $upload['tmp_name']; - if( is_uploaded_file( $fname ) ) { - return ImportStreamSource::newFromFile( $fname ); - } else { - return new WikiErrorMsg( 'importnofile' ); - } - } - - function newFromURL( $url, $method = 'GET' ) { - wfDebug( __METHOD__ . ": opening $url\n" ); - # Use the standard HTTP fetch function; it times out - # quicker and sorts out user-agent problems which might - # otherwise prevent importing from large sites, such - # as the Wikimedia cluster, etc. - $data = Http::request( $method, $url ); - if( $data !== false ) { - $file = tmpfile(); - fwrite( $file, $data ); - fflush( $file ); - fseek( $file, 0 ); - return new ImportStreamSource( $file ); - } else { - return new WikiErrorMsg( 'importcantopen' ); - } - } - - public static function newFromInterwiki( $interwiki, $page, $history=false ) { - if( $page == '' ) { - return new WikiErrorMsg( 'import-noarticle' ); - } - $link = Title::newFromText( "$interwiki:Special:Export/$page" ); - if( is_null( $link ) || $link->getInterwiki() == '' ) { - return new WikiErrorMsg( 'importbadinterwiki' ); - } else { - $params = $history ? 'history=1' : ''; - $url = $link->getFullUrl( $params ); - # For interwikis, use POST to avoid redirects. - return ImportStreamSource::newFromURL( $url, "POST" ); - } - } -} diff --git a/includes/SpecialIpblocklist.php b/includes/SpecialIpblocklist.php deleted file mode 100644 index c2de9e2f..00000000 --- a/includes/SpecialIpblocklist.php +++ /dev/null @@ -1,430 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * @todo document - */ -function wfSpecialIpblocklist() { - global $wgUser, $wgOut, $wgRequest; - - $ip = $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) ); - $id = $wgRequest->getVal( 'id' ); - $reason = $wgRequest->getText( 'wpUnblockReason' ); - $action = $wgRequest->getText( 'action' ); - $successip = $wgRequest->getVal( 'successip' ); - - $ipu = new IPUnblockForm( $ip, $id, $reason ); - - if( $action == 'unblock' ) { - # Check permissions - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - # Check for database lock - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Show unblock form - $ipu->showForm( '' ); - } elseif( $action == 'submit' && $wgRequest->wasPosted() - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - # Check permissions - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - # Check for database lock - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Remove blocks and redirect user to success page - $ipu->doSubmit(); - } elseif( $action == 'success' ) { - # Inform the user of a successful unblock - # (No need to check permissions or locks here, - # if something was done, then it's too late!) - if ( substr( $successip, 0, 1) == '#' ) { - // A block ID was unblocked - $ipu->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) ); - } else { - // A username/IP was unblocked - $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) ); - } - } else { - # Just show the block list - $ipu->showList( '' ); - } - -} - -/** - * implements Special:ipblocklist GUI - * @addtogroup SpecialPage - */ -class IPUnblockForm { - var $ip, $reason, $id; - - function IPUnblockForm( $ip, $id, $reason ) { - $this->ip = strtr( $ip, '_', ' ' ); - $this->id = $id; - $this->reason = $reason; - } - - function showForm( $err ) { - global $wgOut, $wgUser, $wgSysopUserBans, $wgContLang; - - $wgOut->setPagetitle( wfMsg( 'unblockip' ) ); - $wgOut->addWikiMsg( 'unblockiptext' ); - - $ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ); - $ipr = wfMsgHtml( 'ipbreason' ); - $ipus = wfMsgHtml( 'ipusubmit' ); - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $action = $titleObj->getLocalURL( "action=submit" ); - $alignRight = $wgContLang->isRtl() ? 'left' : 'right'; - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsg( "formerror" ) ); - $wgOut->addWikiText( "<span class='error'>{$err}</span>\n" ); - } - $token = htmlspecialchars( $wgUser->editToken() ); - - $addressPart = false; - if ( $this->id ) { - $block = Block::newFromID( $this->id ); - if ( $block ) { - $encName = htmlspecialchars( $block->getRedactedName() ); - $encId = $this->id; - $addressPart = $encName . Xml::hidden( 'id', $encId ); - } - } - if ( !$addressPart ) { - $addressPart = Xml::input( 'wpUnblockAddress', 20, $this->ip, array( 'type' => 'text', 'tabindex' => '1' ) ); - } - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) . - Xml::openElement( 'table', array( 'border' => '0' ) ). - "<tr> - <td align='$alignRight'> - {$ipa} - </td> - <td> - {$addressPart} - </td> - </tr> - <tr> - <td align='$alignRight'> - {$ipr} - </td> - <td>" . - Xml::input( 'wpUnblockReason', 40, $this->reason, array( 'type' => 'text', 'tabindex' => '2' ) ) . - "</td> - </tr> - <tr> - <td> </td> - <td>" . - Xml::submitButton( $ipus, array( 'name' => 'wpBlock', 'tabindex' => '3' ) ) . - "</td> - </tr>" . - Xml::closeElement( 'table' ) . - Xml::hidden( 'wpEditToken', $token ) . - Xml::closeElement( 'form' ) . "\n" - ); - - } - - const UNBLOCK_SUCCESS = 0; // Success - const UNBLOCK_NO_SUCH_ID = 1; // No such block ID - const UNBLOCK_USER_NOT_BLOCKED = 2; // IP wasn't blocked - const UNBLOCK_BLOCKED_AS_RANGE = 3; // IP is part of a range block - const UNBLOCK_UNKNOWNERR = 4; // Unknown error - - /** - * Backend code for unblocking. doSubmit() wraps around this. - * $range is only used when UNBLOCK_BLOCKED_AS_RANGE is returned, in which - * case it contains the range $ip is part of. - * @return array array(message key, parameters) on failure, empty array on success - */ - - static function doUnblock(&$id, &$ip, &$reason, &$range = null) - { - if ( $id ) { - $block = Block::newFromID( $id ); - if ( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - $ip = $block->getRedactedName(); - } else { - $block = new Block(); - $ip = trim( $ip ); - if ( substr( $ip, 0, 1 ) == "#" ) { - $id = substr( $ip, 1 ); - $block = Block::newFromID( $id ); - if( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - $ip = $block->getRedactedName(); - } else { - $block = Block::newFromDB( $ip ); - if ( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - if( $block->mRangeStart != $block->mRangeEnd - && !strstr( $ip, "/" ) ) { - /* If the specified IP is a single address, and the block is - * a range block, don't unblock the range. */ - $range = $block->mAddress; - return array('ipb_blocked_as_range', $ip, $range); - } - } - } - // Yes, this is really necessary - $id = $block->mId; - - # Delete block - if ( !$block->delete() ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); - } - - # Make log entry - $log = new LogPage( 'block' ); - $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $ip ), $reason ); - return array(); - } - - function doSubmit() { - global $wgOut; - $retval = self::doUnblock($this->id, $this->ip, $this->reason, $range); - if(!empty($retval)) - { - $key = array_shift($retval); - $this->showForm(wfMsgReal($key, $retval)); - return; - } - # Report to the user - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) ); - $wgOut->redirect( $success ); - } - - function showList( $msg ) { - global $wgOut, $wgUser; - - $wgOut->setPagetitle( wfMsg( "ipblocklist" ) ); - if ( "" != $msg ) { - $wgOut->setSubtitle( $msg ); - } - - // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { - Block::purgeExpired(); - } - - $conds = array(); - $matches = array(); - // Is user allowed to see all the blocks? - if ( !$wgUser->isAllowed( 'oversight' ) ) - $conds['ipb_deleted'] = 0; - if ( $this->ip == '' ) { - // No extra conditions - } elseif ( substr( $this->ip, 0, 1 ) == '#' ) { - $conds['ipb_id'] = substr( $this->ip, 1 ); - } elseif ( IP::toUnsigned( $this->ip ) !== false ) { - $conds['ipb_address'] = $this->ip; - $conds['ipb_auto'] = 0; - } elseif( preg_match( '/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\\/(\\d{1,2})$/', $this->ip, $matches ) ) { - $conds['ipb_address'] = Block::normaliseRange( $this->ip ); - $conds['ipb_auto'] = 0; - } else { - $user = User::newFromName( $this->ip ); - if ( $user && ( $id = $user->getID() ) != 0 ) { - $conds['ipb_user'] = $id; - } else { - // Uh...? - $conds['ipb_address'] = $this->ip; - $conds['ipb_auto'] = 0; - } - } - - $pager = new IPBlocklistPager( $this, $conds ); - if ( $pager->getNumRows() ) { - $wgOut->addHTML( - $this->searchForm() . - $pager->getNavigationBar() . - Xml::tags( 'ul', null, $pager->getBody() ) . - $pager->getNavigationBar() - ); - } elseif ( $this->ip != '') { - $wgOut->addHTML( $this->searchForm() ); - $wgOut->addWikiMsg( 'ipblocklist-no-results' ); - } else { - $wgOut->addWikiMsg( 'ipblocklist-empty' ); - } - } - - function searchForm() { - global $wgTitle, $wgScript, $wgRequest; - return - Xml::tags( 'form', array( 'action' => $wgScript ), - Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) . - Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) . - ' ' . - Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . - Xml::closeElement( 'fieldset' ) - ); - } - - /** - * Callback function to output a block - */ - function formatRow( $block ) { - global $wgUser, $wgLang; - - wfProfileIn( __METHOD__ ); - - static $sk=null, $msg=null; - - if( is_null( $sk ) ) - $sk = $wgUser->getSkin(); - if( is_null( $msg ) ) { - $msg = array(); - $keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', - 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock' ); - foreach( $keys as $key ) { - $msg[$key] = wfMsgHtml( $key ); - } - $msg['blocklistline'] = wfMsg( 'blocklistline' ); - } - - # Prepare links to the blocker's user and talk pages - $blocker_id = $block->getBy(); - $blocker_name = $block->getByName(); - $blocker = $sk->userLink( $blocker_id, $blocker_name ); - $blocker .= $sk->userToolLinks( $blocker_id, $blocker_name ); - - # Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks) - if( $block->mAuto ) { - $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy - } else { - $target = $sk->userLink( $block->mUser, $block->mAddress ) - . $sk->userToolLinks( $block->mUser, $block->mAddress, false, Linker::TOOL_LINKS_NOBLOCK ); - } - - $formattedTime = $wgLang->timeanddate( $block->mTimestamp, true ); - - $properties = array(); - if ( $block->mExpiry === "" || $block->mExpiry === Block::infinity() ) { - $properties[] = $msg['infiniteblock']; - } else { - $properties[] = wfMsgReplaceArgs( $msg['expiringblock'], - array( $wgLang->timeanddate( $block->mExpiry, true ) ) ); - } - if ( $block->mAnonOnly ) { - $properties[] = $msg['anononlyblock']; - } - if ( $block->mCreateAccount ) { - $properties[] = $msg['createaccountblock']; - } - if (!$block->mEnableAutoblock && $block->mUser ) { - $properties[] = $msg['noautoblockblock']; - } - - if ( $block->mBlockEmail && $block->mUser ) { - $properties[] = $msg['emailblock']; - } - - $properties = implode( ', ', $properties ); - - $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) ); - - $unblocklink = ''; - if ( $wgUser->isAllowed('block') ) { - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $unblocklink = ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')'; - } - - $comment = $sk->commentBlock( $block->mReason ); - - $s = "{$line} $comment"; - if ( $block->mHideName ) - $s = '<span class="history-deleted">' . $s . '</span>'; - - wfProfileOut( __METHOD__ ); - return "<li>$s $unblocklink</li>\n"; - } -} - -/** - * @todo document - * @addtogroup Pager - */ -class IPBlocklistPager extends ReverseChronologicalPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array() ) { - $this->mForm = $form; - $this->mConds = $conds; - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $lb = new LinkBatch; - - /* - while ( $row = $this->mResult->fetchObject() ) { - $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) ); - }*/ - # Faster way - # Usernames and titles are in fact related by a simple substitution of space -> underscore - # The last few lines of Title::secureAndSplit() tell the story. - while ( $row = $this->mResult->fetchObject() ) { - $name = str_replace( ' ', '_', $row->user_name ); - $lb->add( NS_USER, $name ); - $lb->add( NS_USER_TALK, $name ); - $name = str_replace( ' ', '_', $row->ipb_address ); - $lb->add( NS_USER, $name ); - $lb->add( NS_USER_TALK, $name ); - } - $lb->execute(); - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - $block = new Block; - $block->initFromRow( $row ); - return $this->mForm->formatRow( $block ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - $conds[] = 'ipb_by=user_id'; - return array( - 'tables' => array( 'ipblocks', 'user' ), - 'fields' => $this->mDb->tableName( 'ipblocks' ) . '.*,user_name', - 'conds' => $conds, - ); - } - - function getIndexField() { - return 'ipb_timestamp'; - } -} - - diff --git a/includes/SpecialListredirects.php b/includes/SpecialListredirects.php deleted file mode 100644 index 92bd66e4..00000000 --- a/includes/SpecialListredirects.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * @addtogroup SpecialPage - * - * @author Rob Church <robchur@gmail.com> - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * Special:Listredirects - Lists all the redirects on the wiki. - * @addtogroup SpecialPage - */ -class ListredirectsPage extends QueryPage { - - function getName() { return( 'Listredirects' ); } - function isExpensive() { return( true ); } - function isSyndicated() { return( false ); } - function sortDescending() { return( false ); } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $sql = "SELECT 'Listredirects' AS type, page_title AS title, page_namespace AS namespace, 0 AS value FROM $page WHERE page_is_redirect = 1"; - return( $sql ); - } - - function formatResult( $skin, $result ) { - global $wgContLang; - - # Make a link to the redirect itself - $rd_title = Title::makeTitle( $result->namespace, $result->title ); - $rd_link = $skin->makeLinkObj( $rd_title, '', 'redirect=no' ); - - # Find out where the redirect leads - $revision = Revision::newFromTitle( $rd_title ); - if( $revision ) { - # Make a link to the destination page - $target = Title::newFromRedirect( $revision->getText() ); - if( $target ) { - $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); - $targetLink = $skin->makeLinkObj( $target ); - return "$rd_link $arr $targetLink"; - } else { - return "<s>$rd_link</s>"; - } - } else { - return "<s>$rd_link</s>"; - } - } - -} - -function wfSpecialListredirects() { - list( $limit, $offset ) = wfCheckLimits(); - $lrp = new ListredirectsPage(); - $lrp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialListusers.php b/includes/SpecialListusers.php deleted file mode 100644 index 460d4259..00000000 --- a/includes/SpecialListusers.php +++ /dev/null @@ -1,217 +0,0 @@ -<?php - -# Copyright (C) 2004 Brion Vibber, lcrocker, Tim Starling, -# Domas Mituzas, Ashar Voultoiz, Jens Frank, Zhengzhu. -# -# © 2006 Rob Church <robchur@gmail.com> -# -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html -/** - * - * @addtogroup SpecialPage - */ - -/** - * This class is used to get a list of user. The ones with specials - * rights (sysop, bureaucrat, developer) will have them displayed - * next to their names. - * - * @addtogroup SpecialPage - */ - -class UsersPager extends AlphabeticPager { - - function __construct($group=null) { - global $wgRequest; - $this->requestedGroup = $group != "" ? $group : $wgRequest->getVal( 'group' ); - $un = $wgRequest->getText( 'username' ); - $this->requestedUser = ''; - if ( $un != '' ) { - $username = Title::makeTitleSafe( NS_USER, $un ); - if( ! is_null( $username ) ) { - $this->requestedUser = $username->getText(); - } - } - parent::__construct(); - } - - - function getIndexField() { - return 'user_name'; - } - - function getQueryInfo() { - $conds=array(); - // don't show hidden names - $conds[]='ipb_deleted IS NULL OR ipb_deleted = 0'; - if ($this->requestedGroup != "") { - $conds['ug_group'] = $this->requestedGroup; - } - if ($this->requestedUser != "") { - $conds[] = 'user_name >= ' . wfGetDB()->addQuotes( $this->requestedUser ); - } - - list ($user,$user_groups,$ipblocks) = wfGetDB()->tableNamesN('user','user_groups','ipblocks'); - - return array( - 'tables' => " $user LEFT JOIN $user_groups ON user_id=ug_user LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_auto=0 ", - 'fields' => array('user_name', - 'MAX(user_id) AS user_id', - 'COUNT(ug_group) AS numgroups', - 'MAX(ug_group) AS singlegroup'), - 'options' => array('GROUP BY' => 'user_name'), - 'conds' => $conds - ); - - } - - function formatRow( $row ) { - $userPage = Title::makeTitle( NS_USER, $row->user_name ); - $name = $this->getSkin()->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ); - - if( $row->numgroups > 1 || ( $this->requestedGroup && $row->numgroups == 1 ) ) { - $list = array(); - foreach( self::getGroups( $row->user_id ) as $group ) - $list[] = self::buildGroupLink( $group ); - $groups = implode( ', ', $list ); - } elseif( $row->numgroups == 1 ) { - $groups = self::buildGroupLink( $row->singlegroup ); - } else { - $groups = ''; - } - - return '<li>' . wfSpecialList( $name, $groups ) . '</li>'; - } - - function getBody() { - if (!$this->mQueryDone) { - $this->doQuery(); - } - $batch = new LinkBatch; - - $this->mResult->rewind(); - - while ( $row = $this->mResult->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); - } - $batch->execute(); - $this->mResult->rewind(); - return parent::getBody(); - } - - function getPageHeader( ) { - global $wgScript, $wgRequest; - $self = $this->getTitle(); - - # Form tag - $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'listusers' ) ); - $out .= Xml::hidden( 'title', $self->getPrefixedDbKey() ); - - # Username field - $out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' . - Xml::input( 'username', 20, $this->requestedUser, array( 'id' => 'offset' ) ) . ' '; - - # Group drop-down list - $out .= Xml::label( wfMsg( 'group' ), 'group' ) . ' ' . - Xml::openElement('select', array( 'name' => 'group', 'id' => 'group' ) ) . - Xml::option( wfMsg( 'group-all' ), '' ); - foreach( User::getAllGroups() as $group ) - $out .= Xml::option( User::getGroupName( $group ), $group, $group == $this->requestedGroup ); - $out .= Xml::closeElement( 'select' ) . ' '; - - # Submit button and form bottom - if( $this->mLimit ) - $out .= Xml::hidden( 'limit', $this->mLimit ); - $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - '</fieldset>' . - Xml::closeElement( 'form' ); - - return $out; - } - - /** - * Preserve group and username offset parameters when paging - * @return array - */ - function getDefaultQuery() { - $query = parent::getDefaultQuery(); - if( $this->requestedGroup != '' ) - $query['group'] = $this->requestedGroup; - if( $this->requestedUser != '' ) - $query['username'] = $this->requestedUser; - return $query; - } - - /** - * Get a list of groups the specified user belongs to - * - * @param int $uid - * @return array - */ - private static function getGroups( $uid ) { - $dbr = wfGetDB( DB_SLAVE ); - $groups = array(); - $res = $dbr->select( 'user_groups', 'ug_group', array( 'ug_user' => $uid ), __METHOD__ ); - if( $res && $dbr->numRows( $res ) > 0 ) { - while( $row = $dbr->fetchObject( $res ) ) - $groups[] = $row->ug_group; - $dbr->freeResult( $res ); - } - return $groups; - } - - /** - * Format a link to a group description page - * - * @param string $group - * @return string - */ - private static function buildGroupLink( $group ) { - static $cache = array(); - if( !isset( $cache[$group] ) ) - $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) ); - return $cache[$group]; - } -} - -/** - * constructor - * $par string (optional) A group to list users from - */ -function wfSpecialListusers( $par = null ) { - global $wgRequest, $wgOut; - - $up = new UsersPager($par); - - # getBody() first to check, if empty - $usersbody = $up->getBody(); - $s = $up->getPageHeader(); - if( $usersbody ) { - $s .= $up->getNavigationBar(); - $s .= '<ul>' . $usersbody . '</ul>'; - $s .= $up->getNavigationBar() ; - } else { - $s .= '<p>' . wfMsgHTML('listusers-noresult') . '</p>'; - }; - - $wgOut->addHTML( $s ); -} - - diff --git a/includes/SpecialLockdb.php b/includes/SpecialLockdb.php deleted file mode 100644 index b523591c..00000000 --- a/includes/SpecialLockdb.php +++ /dev/null @@ -1,134 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * Constructor - */ -function wfSpecialLockdb() { - global $wgUser, $wgOut, $wgRequest; - - if( !$wgUser->isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); - return; - } - - # If the lock file isn't writable, we can do sweet bugger all - global $wgReadOnlyFile; - if( !is_writable( dirname( $wgReadOnlyFile ) ) ) { - DBLockForm::notWritable(); - return; - } - - $action = $wgRequest->getVal( 'action' ); - $f = new DBLockForm(); - - if ( 'success' == $action ) { - $f->showSuccess(); - } else if ( 'submit' == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( '' ); - } -} - -/** - * A form to make the database readonly (eg for maintenance purposes). - * @addtogroup SpecialPage - */ -class DBLockForm { - var $reason = ''; - - function DBLockForm() { - global $wgRequest; - $this->reason = $wgRequest->getText( 'wpLockReason' ); - } - - function showForm( $err ) { - global $wgOut, $wgUser; - - $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); - $wgOut->addWikiMsg( 'lockdbtext' ); - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsg( 'formerror' ) ); - $wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" ); - } - $lc = htmlspecialchars( wfMsg( 'lockconfirm' ) ); - $lb = htmlspecialchars( wfMsg( 'lockbtn' ) ); - $elr = htmlspecialchars( wfMsg( 'enterlockreason' ) ); - $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); - $action = $titleObj->escapeLocalURL( 'action=submit' ); - $reason = htmlspecialchars( $this->reason ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( <<<END -<form id="lockdb" method="post" action="{$action}"> -{$elr}: -<textarea name="wpLockReason" rows="10" cols="60" wrap="virtual">{$reason}</textarea> -<table border="0"> - <tr> - <td align="right"> - <input type="checkbox" name="wpLockConfirm" /> - </td> - <td align="left">{$lc}</td> - </tr> - <tr> - <td> </td> - <td align="left"> - <input type="submit" name="wpLock" value="{$lb}" /> - </td> - </tr> -</table> -<input type="hidden" name="wpEditToken" value="{$token}" /> -</form> -END -); - - } - - function doSubmit() { - global $wgOut, $wgUser, $wgLang, $wgRequest; - global $wgReadOnlyFile; - - if ( ! $wgRequest->getCheck( 'wpLockConfirm' ) ) { - $this->showForm( wfMsg( 'locknoconfirm' ) ); - return; - } - $fp = @fopen( $wgReadOnlyFile, 'w' ); - - if ( false === $fp ) { - # This used to show a file not found error, but the likeliest reason for fopen() - # to fail at this point is insufficient permission to write to the file...good old - # is_writable() is plain wrong in some cases, it seems... - $this->notWritable(); - return; - } - fwrite( $fp, $this->reason ); - fwrite( $fp, "\n<p>(by " . $wgUser->getName() . " at " . - $wgLang->timeanddate( wfTimestampNow() ) . ")\n" ); - fclose( $fp ); - - $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); - $wgOut->redirect( $titleObj->getFullURL( 'action=success' ) ); - } - - function showSuccess() { - global $wgOut; - - $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); - $wgOut->setSubtitle( wfMsg( 'lockdbsuccesssub' ) ); - $wgOut->addWikiMsg( 'lockdbsuccesstext' ); - } - - public static function notWritable() { - global $wgOut; - $wgOut->errorPage( 'lockdb', 'lockfilenotwritable' ); - } - -} - - diff --git a/includes/SpecialLog.php b/includes/SpecialLog.php deleted file mode 100644 index 5c28340f..00000000 --- a/includes/SpecialLog.php +++ /dev/null @@ -1,527 +0,0 @@ -<?php -# Copyright (C) 2004 Brion Vibber <brion@pobox.com> -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html - -/** - * - * @addtogroup SpecialPage - */ - -/** - * constructor - */ -function wfSpecialLog( $par = '' ) { - global $wgRequest; - $logReader = new LogReader( $wgRequest ); - if( $wgRequest->getVal( 'type' ) == '' && $par != '' ) { - $logReader->limitType( $par ); - } - $logViewer = new LogViewer( $logReader ); - $logViewer->show(); -} - -/** - * - * @addtogroup SpecialPage - */ -class LogReader { - var $db, $joinClauses, $whereClauses; - var $type = '', $user = '', $title = null, $pattern = false; - - /** - * @param WebRequest $request For internal use use a FauxRequest object to pass arbitrary parameters. - */ - function LogReader( $request ) { - $this->db = wfGetDB( DB_SLAVE ); - $this->setupQuery( $request ); - } - - /** - * Basic setup and applies the limiting factors from the WebRequest object. - * @param WebRequest $request - * @private - */ - function setupQuery( $request ) { - $page = $this->db->tableName( 'page' ); - $user = $this->db->tableName( 'user' ); - $this->joinClauses = array( - "LEFT OUTER JOIN $page ON log_namespace=page_namespace AND log_title=page_title", - "INNER JOIN $user ON user_id=log_user" ); - $this->whereClauses = array(); - - $this->limitType( $request->getVal( 'type' ) ); - $this->limitUser( $request->getText( 'user' ) ); - $this->limitTitle( $request->getText( 'page' ) , $request->getBool( 'pattern' ) ); - $this->limitTime( $request->getVal( 'from' ), '>=' ); - $this->limitTime( $request->getVal( 'until' ), '<=' ); - - list( $this->limit, $this->offset ) = $request->getLimitOffset(); - - // XXX This all needs to use Pager, ugly hack for now. - global $wgMiserMode; - if( $wgMiserMode ) - $this->offset = min( $this->offset, 10000 ); - } - - /** - * Set the log reader to return only entries of the given type. - * @param string $type A log type ('upload', 'delete', etc) - * @private - */ - function limitType( $type ) { - if( empty( $type ) ) { - return false; - } - $this->type = $type; - $safetype = $this->db->strencode( $type ); - $this->whereClauses[] = "log_type='$safetype'"; - } - - /** - * Set the log reader to return only entries by the given user. - * @param string $name (In)valid user name - * @private - */ - function limitUser( $name ) { - if ( $name == '' ) - return false; - $usertitle = Title::makeTitleSafe( NS_USER, $name ); - if ( is_null( $usertitle ) ) - return false; - $this->user = $usertitle->getText(); - - /* Fetch userid at first, if known, provides awesome query plan afterwards */ - $userid = $this->db->selectField('user','user_id',array('user_name'=>$this->user)); - if (!$userid) - /* It should be nicer to abort query at all, - but for now it won't pass anywhere behind the optimizer */ - $this->whereClauses[] = "NULL"; - else - $this->whereClauses[] = "log_user=$userid"; - } - - /** - * Set the log reader to return only entries affecting the given page. - * (For the block and rights logs, this is a user page.) - * @param string $page Title name as text - * @private - */ - function limitTitle( $page , $pattern ) { - global $wgMiserMode; - - $title = Title::newFromText( $page ); - - if( strlen( $page ) == 0 || !$title instanceof Title ) - return false; - - $this->title =& $title; - $this->pattern = $pattern; - $ns = $title->getNamespace(); - if ( $pattern && !$wgMiserMode ) { - $safetitle = $this->db->escapeLike( $title->getDBkey() ); // use escapeLike to avoid expensive search patterns like 't%st%' - $this->whereClauses[] = "log_namespace=$ns AND log_title LIKE '$safetitle%'"; - } else { - $safetitle = $this->db->strencode( $title->getDBkey() ); - $this->whereClauses[] = "log_namespace=$ns AND log_title = '$safetitle'"; - } - } - - /** - * Set the log reader to return only entries in a given time range. - * @param string $time Timestamp of one endpoint - * @param string $direction either ">=" or "<=" operators - * @private - */ - function limitTime( $time, $direction ) { - # Direction should be a comparison operator - if( empty( $time ) ) { - return false; - } - $safetime = $this->db->strencode( wfTimestamp( TS_MW, $time ) ); - $this->whereClauses[] = "log_timestamp $direction '$safetime'"; - } - - /** - * Build an SQL query from all the set parameters. - * @return string the SQL query - * @private - */ - function getQuery() { - $logging = $this->db->tableName( "logging" ); - $sql = "SELECT /*! STRAIGHT_JOIN */ log_type, log_action, log_timestamp, - log_user, user_name, - log_namespace, log_title, page_id, - log_comment, log_params FROM $logging "; - if( !empty( $this->joinClauses ) ) { - $sql .= implode( ' ', $this->joinClauses ); - } - if( !empty( $this->whereClauses ) ) { - $sql .= " WHERE " . implode( ' AND ', $this->whereClauses ); - } - $sql .= " ORDER BY log_timestamp DESC "; - $sql = $this->db->limitResult($sql, $this->limit, $this->offset ); - return $sql; - } - - /** - * Execute the query and start returning results. - * @return ResultWrapper result object to return the relevant rows - */ - function getRows() { - $res = $this->db->query( $this->getQuery(), __METHOD__ ); - return $this->db->resultObject( $res ); - } - - /** - * @return string The query type that this LogReader has been limited to. - */ - function queryType() { - return $this->type; - } - - /** - * @return string The username type that this LogReader has been limited to, if any. - */ - function queryUser() { - return $this->user; - } - - /** - * @return boolean The checkbox, if titles should be searched by a pattern too - */ - function queryPattern() { - return $this->pattern; - } - - /** - * @return string The text of the title that this LogReader has been limited to. - */ - function queryTitle() { - if( is_null( $this->title ) ) { - return ''; - } else { - return $this->title->getPrefixedText(); - } - } - - /** - * Is there at least one row? - * - * @return bool - */ - public function hasRows() { - # Little hack... - $limit = $this->limit; - $this->limit = 1; - $res = $this->db->query( $this->getQuery() ); - $this->limit = $limit; - $ret = $this->db->numRows( $res ) > 0; - $this->db->freeResult( $res ); - return $ret; - } - -} - -/** - * - * @addtogroup SpecialPage - */ -class LogViewer { - const NO_ACTION_LINK = 1; - - /** - * @var LogReader $reader - */ - var $reader; - var $numResults = 0; - var $flags = 0; - - /** - * @param LogReader &$reader where to get our data from - * @param integer $flags Bitwise combination of flags: - * self::NO_ACTION_LINK Don't show restore/unblock/block links - */ - function LogViewer( &$reader, $flags = 0 ) { - global $wgUser; - $this->skin = $wgUser->getSkin(); - $this->reader =& $reader; - $this->flags = $flags; - } - - /** - * Take over the whole output page in $wgOut with the log display. - */ - function show() { - global $wgOut; - $this->showHeader( $wgOut ); - $this->showOptions( $wgOut ); - $result = $this->getLogRows(); - if ( $this->numResults > 0 ) { - $this->showPrevNext( $wgOut ); - $this->doShowList( $wgOut, $result ); - $this->showPrevNext( $wgOut ); - } else { - $this->showError( $wgOut ); - } - } - - /** - * Load the data from the linked LogReader - * Preload the link cache - * Initialise numResults - * - * Must be called before calling showPrevNext - * - * @return object database result set - */ - function getLogRows() { - $result = $this->reader->getRows(); - $this->numResults = 0; - - // Fetch results and form a batch link existence query - $batch = new LinkBatch; - while ( $s = $result->fetchObject() ) { - // User link - $batch->addObj( Title::makeTitleSafe( NS_USER, $s->user_name ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $s->user_name ) ); - - // Move destination link - if ( $s->log_type == 'move' ) { - $paramArray = LogPage::extractParams( $s->log_params ); - $title = Title::newFromText( $paramArray[0] ); - $batch->addObj( $title ); - } - ++$this->numResults; - } - $batch->execute(); - - return $result; - } - - - /** - * Output just the list of entries given by the linked LogReader, - * with extraneous UI elements. Use for displaying log fragments in - * another page (eg at Special:Undelete) - * @param OutputPage $out where to send output - */ - function showList( &$out ) { - $result = $this->getLogRows(); - if ( $this->numResults > 0 ) { - $this->doShowList( $out, $result ); - } else { - $this->showError( $out ); - } - } - - function doShowList( &$out, $result ) { - // Rewind result pointer and go through it again, making the HTML - $html = "\n<ul>\n"; - $result->seek( 0 ); - while( $s = $result->fetchObject() ) { - $html .= $this->logLine( $s ); - } - $html .= "\n</ul>\n"; - $out->addHTML( $html ); - $result->free(); - } - - function showError( &$out ) { - $out->addWikiMsg( 'logempty' ); - } - - /** - * @param Object $s a single row from the result set - * @return string Formatted HTML list item - * @private - */ - function logLine( $s ) { - global $wgLang, $wgUser, $wgContLang; - $skin = $wgUser->getSkin(); - $title = Title::makeTitle( $s->log_namespace, $s->log_title ); - $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $s->log_timestamp), true ); - - // Enter the existence or non-existence of this page into the link cache, - // for faster makeLinkObj() in LogPage::actionText() - $linkCache =& LinkCache::singleton(); - if( $s->page_id ) { - $linkCache->addGoodLinkObj( $s->page_id, $title ); - } else { - $linkCache->addBadLinkObj( $title ); - } - - $userLink = $this->skin->userLink( $s->log_user, $s->user_name ) . $this->skin->userToolLinksRedContribs( $s->log_user, $s->user_name ); - $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $s->log_comment ); - $paramArray = LogPage::extractParams( $s->log_params ); - $revert = ''; - // show revertmove link - if ( !( $this->flags & self::NO_ACTION_LINK ) ) { - if ( $s->log_type == 'move' && isset( $paramArray[0] ) && $wgUser->isAllowed( 'move' ) ) { - $destTitle = Title::newFromText( $paramArray[0] ); - if ( $destTitle ) { - $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ), - wfMsg( 'revertmove' ), - 'wpOldTitle=' . urlencode( $destTitle->getPrefixedDBkey() ) . - '&wpNewTitle=' . urlencode( $title->getPrefixedDBkey() ) . - '&wpReason=' . urlencode( wfMsgForContent( 'revertmove' ) ) . - '&wpMovetalk=0' ) . ')'; - } - // show undelete link - } elseif ( $s->log_action == 'delete' && $wgUser->isAllowed( 'delete' ) ) { - $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete' ), - wfMsg( 'undeletelink' ) , - 'target='. urlencode( $title->getPrefixedDBkey() ) ) . ')'; - // show unblock link - } elseif ( $s->log_action == 'block' && $wgUser->isAllowed( 'block' ) ) { - $revert = '(' . $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Ipblocklist' ), - wfMsg( 'unblocklink' ), - 'action=unblock&ip=' . urlencode( $s->log_title ) ) . ')'; - // show change protection link - } elseif ( ( $s->log_action == 'protect' || $s->log_action == 'modify' ) && $wgUser->isAllowed( 'protect' ) ) { - $revert = '(' . $skin->makeKnownLinkObj( $title, wfMsg( 'protect_change' ), 'action=unprotect' ) . ')'; - // Show unmerge link - } elseif ( $s->log_action == 'merge' ) { - $merge = SpecialPage::getTitleFor( 'Mergehistory' ); - $revert = '(' . $this->skin->makeKnownLinkObj( $merge, wfMsg('revertmerge'), - wfArrayToCGI( - array('target' => $paramArray[0], 'dest' => $title->getPrefixedText(), 'mergepoint' => $paramArray[1] ) - ) - ) . ')'; - } elseif ( wfRunHooks( 'LogLine', array( $s->log_type, $s->log_action, $title, $paramArray, &$comment, &$revert, $s->log_timestamp ) ) ) { - // wfDebug( "Invoked LogLine hook for " $s->log_type . ", " . $s->log_action . "\n" ); - // Do nothing. The implementation is handled by the hook modifiying the passed-by-ref parameters. - } - } - - $action = LogPage::actionText( $s->log_type, $s->log_action, $title, $this->skin, $paramArray, true ); - $out = "<li>$time $userLink $action $comment $revert</li>\n"; - return $out; - } - - /** - * @param OutputPage &$out where to send output - * @private - */ - function showHeader( &$out ) { - $type = $this->reader->queryType(); - if( LogPage::isLogType( $type ) ) { - $out->setPageTitle( LogPage::logName( $type ) ); - $out->addWikiText( LogPage::logHeader( $type ) ); - } - } - - /** - * @param OutputPage &$out where to send output - * @private - */ - function showOptions( &$out ) { - global $wgScript, $wgMiserMode; - $action = htmlspecialchars( $wgScript ); - $title = SpecialPage::getTitleFor( 'Log' ); - $special = htmlspecialchars( $title->getPrefixedDBkey() ); - $out->addHTML( "<form action=\"$action\" method=\"get\">\n" . - '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'log' ) ) . - Xml::hidden( 'title', $special ) . "\n" . - $this->getTypeMenu() . "\n" . - $this->getUserInput() . "\n" . - $this->getTitleInput() . "\n" . - (!$wgMiserMode?($this->getTitlePattern()."\n"):"") . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . - "</fieldset></form>" ); - } - - /** - * @return string Formatted HTML - * @private - */ - function getTypeMenu() { - $out = "<select name='type'>\n"; - - $validTypes = LogPage::validTypes(); - $m = array(); // Temporary array - - // First pass to load the log names - foreach( $validTypes as $type ) { - $text = LogPage::logName( $type ); - $m[$text] = $type; - } - - // Second pass to sort by name - ksort($m); - - // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $this->reader->queryType()); - $out .= Xml::option( $text, $type, $selected ) . "\n"; - } - - $out .= '</select>'; - return $out; - } - - /** - * @return string Formatted HTML - * @private - */ - function getUserInput() { - $user = $this->reader->queryUser(); - return Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'user', 12, $user ); - } - - /** - * @return string Formatted HTML - * @private - */ - function getTitleInput() { - $title = $this->reader->queryTitle(); - return Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'page', 20, $title ); - } - - /** - * @return boolean Checkbox - * @private - */ - function getTitlePattern() { - $pattern = $this->reader->queryPattern(); - return Xml::checkLabel( wfMsg( 'log-title-wildcard' ), 'pattern', 'pattern', $pattern ); - } - - /** - * @param OutputPage &$out where to send output - * @private - */ - function showPrevNext( &$out ) { - global $wgContLang,$wgRequest; - $pieces = array(); - $pieces[] = 'type=' . urlencode( $this->reader->queryType() ); - $pieces[] = 'user=' . urlencode( $this->reader->queryUser() ); - $pieces[] = 'page=' . urlencode( $this->reader->queryTitle() ); - $pieces[] = 'pattern=' . urlencode( $this->reader->queryPattern() ); - $bits = implode( '&', $pieces ); - list( $limit, $offset ) = $wgRequest->getLimitOffset(); - - # TODO: use timestamps instead of offsets to make it more natural - # to go huge distances in time - $html = wfViewPrevNext( $offset, $limit, - $wgContLang->specialpage( 'Log' ), - $bits, - $this->numResults < $limit); - $out->addHTML( '<p>' . $html . '</p>' ); - } -} diff --git a/includes/SpecialLonelypages.php b/includes/SpecialLonelypages.php deleted file mode 100644 index e652f9d4..00000000 --- a/includes/SpecialLonelypages.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * A special page looking for articles with no article linking to them, - * thus being lonely. - * @addtogroup SpecialPage - */ -class LonelyPagesPage extends PageQueryPage { - - function getName() { - return "Lonelypages"; - } - function getPageHeader() { - return wfMsgExt( 'lonelypagestext', array( 'parse' ) ); - } - - function sortDescending() { - return false; - } - - function isExpensive() { - return true; - } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); - - return - "SELECT 'Lonelypages' AS type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $pagelinks - ON page_namespace=pl_namespace AND page_title=pl_title - WHERE pl_namespace IS NULL - AND page_namespace=".NS_MAIN." - AND page_is_redirect=0"; - - } -} - -/** - * Constructor - */ -function wfSpecialLonelypages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new LonelyPagesPage(); - - return $lpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialLongpages.php b/includes/SpecialLongpages.php deleted file mode 100644 index a8a1e199..00000000 --- a/includes/SpecialLongpages.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - * @addtogroup SpecialPage - */ -class LongPagesPage extends ShortPagesPage { - - function getName() { - return "Longpages"; - } - - function sortDescending() { - return true; - } -} - -/** - * constructor - */ -function wfSpecialLongpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new LongPagesPage(); - - $lpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialMIMEsearch.php b/includes/SpecialMIMEsearch.php deleted file mode 100644 index 70e44750..00000000 --- a/includes/SpecialMIMEsearch.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php -/** - * A special page to search for files by MIME type as defined in the - * img_major_mime and img_minor_mime fields in the image table - * - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * Searches the database for files of the requested MIME type, comparing this with the - * 'img_major_mime' and 'img_minor_mime' fields in the image table. - * @addtogroup SpecialPage - */ -class MIMEsearchPage extends QueryPage { - var $major, $minor; - - function MIMEsearchPage( $major, $minor ) { - $this->major = $major; - $this->minor = $minor; - } - - function getName() { return 'MIMEsearch'; } - - /** - * Due to this page relying upon extra fields being passed in the SELECT it - * will fail if it's set as expensive and misermode is on - */ - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function linkParameters() { - $arr = array( $this->major, $this->minor ); - $mime = implode( '/', $arr ); - return array( 'mime' => $mime ); - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $major = $dbr->addQuotes( $this->major ); - $minor = $dbr->addQuotes( $this->minor ); - - return - "SELECT 'MIMEsearch' AS type, - " . NS_IMAGE . " AS namespace, - img_name AS title, - img_major_mime AS value, - - img_size, - img_width, - img_height, - img_user_text, - img_timestamp - FROM $image - WHERE img_major_mime = $major AND img_minor_mime = $minor - "; - } - - function formatResult( $skin, $result ) { - global $wgContLang, $wgLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getText() ); - $plink = $skin->makeLink( $nt->getPrefixedText(), $text ); - - $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) ); - $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->img_size ) ); - $dimensions = wfMsgHtml( 'widthheight', $wgLang->formatNum( $result->img_width ), - $wgLang->formatNum( $result->img_height ) ); - $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text ); - $time = $wgLang->timeanddate( $result->img_timestamp ); - - return "($download) $plink . . $dimensions . . $bytes . . $user . . $time"; - } -} - -/** - * Output the HTML search form, and constructs the MIMEsearchPage object. - */ -function wfSpecialMIMEsearch( $par = null ) { - global $wgRequest, $wgTitle, $wgOut; - - $mime = isset( $par ) ? $par : $wgRequest->getText( 'mime' ); - - $wgOut->addHTML( - Xml::openElement( 'form', - array( - 'id' => 'specialmimesearch', - 'method' => 'get', - 'action' => $wgTitle->getLocalUrl() - ) - ) . - Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . - Xml::submitButton( wfMsg( 'ilsubmit' ) ) . - Xml::closeElement( 'form' ) - ); - - list( $major, $minor ) = wfSpecialMIMEsearchParse( $mime ); - if ( $major == '' or $minor == '' or !wfSpecialMIMEsearchValidType( $major ) ) - return; - $wpp = new MIMEsearchPage( $major, $minor ); - - list( $limit, $offset ) = wfCheckLimits(); - $wpp->doQuery( $offset, $limit ); -} - -function wfSpecialMIMEsearchParse( $str ) { - // searched for an invalid MIME type. - if( strpos( $str, '/' ) === false) { - return array ('', ''); - } - - list( $major, $minor ) = explode( '/', $str, 2 ); - - return array( - ltrim( $major, ' ' ), - rtrim( $minor, ' ' ) - ); -} - -function wfSpecialMIMEsearchValidType( $type ) { - // From maintenance/tables.sql => img_major_mime - $types = array( - 'unknown', - 'application', - 'audio', - 'image', - 'text', - 'video', - 'message', - 'model', - 'multipart' - ); - - return in_array( $type, $types ); -} - diff --git a/includes/SpecialMergeHistory.php b/includes/SpecialMergeHistory.php deleted file mode 100644 index c7f42fe9..00000000 --- a/includes/SpecialMergeHistory.php +++ /dev/null @@ -1,423 +0,0 @@ -<?php - -/** - * Special page allowing users with the appropriate permissions to - * merge article histories, with some restrictions - * - * @addtogroup SpecialPage - */ - -/** - * Constructor - */ -function wfSpecialMergehistory( $par ) { - global $wgRequest; - - $form = new MergehistoryForm( $wgRequest, $par ); - $form->execute(); -} - -/** - * The HTML form for Special:MergeHistory, which allows users with the appropriate - * permissions to view and restore deleted content. - * @addtogroup SpecialPage - */ -class MergehistoryForm { - var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment; - var $mTargetObj, $mDestObj; - - function MergehistoryForm( $request, $par = "" ) { - global $wgUser; - - $this->mAction = $request->getVal( 'action' ); - $this->mTarget = $request->getVal( 'target' ); - $this->mDest = $request->getVal( 'dest' ); - $this->mSubmitted = $request->getBool( 'submitted' ); - - $this->mTargetID = intval( $request->getVal( 'targetID' ) ); - $this->mDestID = intval( $request->getVal( 'destID' ) ); - $this->mTimestamp = $request->getVal( 'mergepoint' ); - if( !preg_match("/[0-9]{14}/",$this->mTimestamp) ) { - $this->mTimestamp = ''; - } - $this->mComment = $request->getText( 'wpComment' ); - - $this->mMerge = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - // target page - if( $this->mSubmitted ) { - $this->mTargetObj = Title::newFromURL( $this->mTarget ); - $this->mDestObj = Title::newFromURL( $this->mDest ); - } else { - $this->mTargetObj = null; - $this->mDestObj = null; - } - - $this->preCacheMessages(); - } - - /** - * As we use the same small set of messages in various methods and that - * they are called often, we call them once and save them in $this->message - */ - function preCacheMessages() { - // Precache various messages - if( !isset( $this->message ) ) { - $this->message['last'] = wfMsgExt( 'last', array( 'escape') ); - } - } - - function execute() { - global $wgOut, $wgUser; - - $wgOut->setPagetitle( wfMsgHtml( "mergehistory" ) ); - - if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) { - return $this->merge(); - } - - if ( !$this->mSubmitted ) { - $this->showMergeForm(); - return; - } - - $errors = array(); - if ( !$this->mTargetObj instanceof Title ) { - $errors[] = wfMsgExt( 'mergehistory-invalid-source', array( 'parse' ) ); - } elseif( !$this->mTargetObj->exists() ) { - $errors[] = wfMsgExt( 'mergehistory-no-source', array( 'parse' ), - wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) - ); - } - - if ( !$this->mDestObj instanceof Title) { - $errors[] = wfMsgExt( 'mergehistory-invalid-destination', array( 'parse' ) ); - } elseif( !$this->mDestObj->exists() ) { - $errors[] = wfMsgExt( 'mergehistory-no-destination', array( 'parse' ), - wfEscapeWikiText( $this->mDestObj->getPrefixedText() ) - ); - } - - // TODO: warn about target = dest? - - if ( count( $errors ) ) { - $this->showMergeForm(); - $wgOut->addHTML( implode( "\n", $errors ) ); - } else { - $this->showHistory(); - } - - } - - function showMergeForm() { - global $wgOut, $wgScript; - - $wgOut->addWikiMsg( 'mergehistory-header' ); - - $wgOut->addHtml( - Xml::openElement( 'form', array( - 'method' => 'get', - 'action' => $wgScript ) ) . - '<fieldset>' . - Xml::element( 'legend', array(), - wfMsg( 'mergehistory-box' ) ) . - Xml::hidden( 'title', - SpecialPage::getTitleFor( 'Mergehistory' )->getPrefixedDbKey() ) . - Xml::hidden( 'submitted', '1' ) . - Xml::hidden( 'mergepoint', $this->mTimestamp ) . - Xml::openElement( 'table' ) . - "<tr> - <td>".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )."</td> - <td>".Xml::input( 'target', 30, $this->mTarget, array('id'=>'target') )."</td> - </tr><tr> - <td>".Xml::label( wfMsg( 'mergehistory-into' ), 'dest' )."</td> - <td>".Xml::input( 'dest', 30, $this->mDest, array('id'=>'dest') )."</td> - </tr><tr><td>" . - Xml::submitButton( wfMsg( 'mergehistory-go' ) ) . - "</td></tr>" . - Xml::closeElement( 'table' ) . - '</fieldset>' . - '</form>' ); - } - - private function showHistory() { - global $wgLang, $wgContLang, $wgUser, $wgOut; - - $this->sk = $wgUser->getSkin(); - - $wgOut->setPagetitle( wfMsg( "mergehistory" ) ); - - $this->showMergeForm(); - - # List all stored revisions - $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj ); - $haveRevisions = $revisions && $revisions->getNumRows() > 0; - - $titleObj = SpecialPage::getTitleFor( "Mergehistory" ); - $action = $titleObj->getLocalURL( "action=submit" ); - # Start the form here - $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) ); - $wgOut->addHtml( $top ); - - if( $haveRevisions ) { - # Format the user-visible controls (comment field, submission button) - # in a nice little table - $align = $wgContLang->isRtl() ? 'left' : 'right'; - $table = - Xml::openElement( 'fieldset' ) . - Xml::openElement( 'table' ) . - "<tr> - <td colspan='2'>" . - wfMsgExt( 'mergehistory-merge', array('parseinline'), - $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) . - "</td> - </tr> - <tr> - <td align='$align'>" . - Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) . - "</td> - <td>" . - Xml::input( 'wpComment', 50, $this->mComment ) . - "</td> - </tr> - <tr> - <td> </td> - <td>" . - Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . - "</td> - </tr>" . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ); - - $wgOut->addHtml( $table ); - } - - $wgOut->addHTML( "<h2 id=\"mw-mergehistory\">" . wfMsgHtml( "mergehistory-list" ) . "</h2>\n" ); - - if( $haveRevisions ) { - $wgOut->addHTML( $revisions->getNavigationBar() ); - $wgOut->addHTML( "<ul>" ); - $wgOut->addHTML( $revisions->getBody() ); - $wgOut->addHTML( "</ul>" ); - $wgOut->addHTML( $revisions->getNavigationBar() ); - } else { - $wgOut->addWikiMsg( "mergehistory-empty" ); - } - - # Show relevant lines from the deletion log: - $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" ); - $logViewer = new LogViewer( - new LogReader( - new FauxRequest( - array( 'page' => $this->mTargetObj->getPrefixedText(), - 'type' => 'merge' ) ) ) ); - $logViewer->showList( $wgOut ); - - # Slip in the hidden controls here - # When we submit, go by page ID to avoid some nasty but unlikely collisions. - # Such would happen if a page was renamed after the form loaded, but before submit - $misc = Xml::hidden( 'targetID', $this->mTargetObj->getArticleID() ); - $misc .= Xml::hidden( 'destID', $this->mDestObj->getArticleID() ); - $misc .= Xml::hidden( 'target', $this->mTarget ); - $misc .= Xml::hidden( 'dest', $this->mDest ); - $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); - $misc .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $misc ); - - return true; - } - - function formatRevisionRow( $row ) { - global $wgUser, $wgLang; - - $rev = new Revision( $row ); - - $stxt = ''; - $last = $this->message['last']; - - $ts = wfTimestamp( TS_MW, $row->rev_timestamp ); - $checkBox = wfRadio( "mergepoint", $ts, false ); - - $pageLink = $this->sk->makeKnownLinkObj( $rev->getTitle(), - htmlspecialchars( $wgLang->timeanddate( $ts ) ), 'oldid=' . $rev->getID() ); - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $pageLink = '<span class="history-deleted">' . $pageLink . '</span>'; - } - - # Last link - if( !$rev->userCan( Revision::DELETED_TEXT ) ) - $last = $this->message['last']; - else if( isset($this->prevId[$row->rev_id]) ) - $last = $this->sk->makeKnownLinkObj( $rev->getTitle(), $this->message['last'], - "&diff=" . $row->rev_id . "&oldid=" . $this->prevId[$row->rev_id] ); - - $userLink = $this->sk->revUserTools( $rev ); - - if(!is_null($size = $row->rev_len)) { - if($size == 0) - $stxt = wfMsgHtml('historyempty'); - else - $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); - } - $comment = $this->sk->revComment( $rev ); - - return "<li>$checkBox ($last) $pageLink . . $userLink $stxt $comment</li>"; - } - - /** - * Fetch revision text link if it's available to all users - * @return string - */ - function getPageLink( $row, $titleObj, $ts, $target ) { - global $wgLang; - - if( !$this->userCan($row, Revision::DELETED_TEXT) ) { - return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>'; - } else { - $link = $this->sk->makeKnownLinkObj( $titleObj, - $wgLang->timeanddate( $ts, true ), "target=$target×tamp=$ts" ); - if( $this->isDeleted($row, Revision::DELETED_TEXT) ) - $link = '<span class="history-deleted">' . $link . '</span>'; - return $link; - } - } - - function merge() { - global $wgOut, $wgUser; - # Get the titles directly from the IDs, in case the target page params - # were spoofed. The queries are done based on the IDs, so it's best to - # keep it consistent... - $targetTitle = Title::newFromID( $this->mTargetID ); - $destTitle = Title::newFromID( $this->mDestID ); - if( is_null($targetTitle) || is_null($destTitle) ) - return false; // validate these - if( $targetTitle->getArticleID() == $destTitle->getArticleId() ) - return false; - # Verify that this timestamp is valid - # Must be older than the destination page - $dbw = wfGetDB( DB_MASTER ); - # Get timestamp into DB format - $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp($this->mTimestamp) : ''; - - $maxtimestamp = $dbw->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $this->mDestID ), - __METHOD__ ); - # Destination page must exist with revisions - if( !$maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Leave the latest version no matter what - $lasttime = $dbw->selectField( array('page','revision'), - 'rev_timestamp', - array('page_id' => $this->mTargetID, 'page_latest = rev_id' ), - __METHOD__ ); - # Take the most restrictive of the twain - $maxtimestamp = ($lasttime < $maxtimestamp) ? $lasttime : $maxtimestamp; - // $this->mTimestamp must be less than $maxtimestamp - if( $this->mTimestamp >= $maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Update the revisions - if( $this->mTimestamp ) { - $timewhere = "rev_timestamp <= {$this->mTimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$this->mTimestamp); - } else { - $timewhere = "rev_timestamp < {$maxtimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$maxtimestamp); - } - - $dbw->update( 'revision', - array( 'rev_page' => $this->mDestID ), - array( 'rev_page' => $this->mTargetID, - $timewhere ), - __METHOD__ ); - # Check if this did anything - if( !$count = $dbw->affectedRows() ) { - $wgOut->addWikiMsg('mergehistory-fail'); - return false; - } - # Update our logs - $log = new LogPage( 'merge' ); - $log->addEntry( 'merge', $targetTitle, $this->mComment, - array($destTitle->getPrefixedText(),$TimestampLimit) ); - - $wgOut->addHtml( wfMsgExt( 'mergehistory-success', array('parseinline'), - $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) ); - - wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) ); - - return true; - } -} - -class MergeHistoryPager extends ReverseChronologicalPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array(), $title, $title2 ) { - $this->mForm = $form; - $this->mConds = $conds; - $this->title = $title; - $this->articleID = $title->getArticleID(); - - $dbr = wfGetDB( DB_SLAVE ); - $maxtimestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $title2->getArticleID() ), - __METHOD__ ); - $this->maxTimestamp = $maxtimestamp; - - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $batch = new LinkBatch(); - # Give some pointers to make (last) links - $this->mForm->prevId = array(); - while( $row = $this->mResult->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) ); - - $rev_id = isset($rev_id) ? $rev_id : $row->rev_id; - if( $rev_id > $row->rev_id ) - $this->mForm->prevId[$rev_id] = $row->rev_id; - else if( $rev_id < $row->rev_id ) - $this->mForm->prevId[$row->rev_id] = $rev_id; - - $rev_id = $row->rev_id; - } - - $batch->execute(); - $this->mResult->seek( 0 ); - - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - $block = new Block; - return $this->mForm->formatRevisionRow( $row ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds['rev_page'] = $this->articleID; - $conds[] = "rev_timestamp < {$this->maxTimestamp}"; - # Skip the latest one, as that could cause problems - if( $page = $this->title->getLatestRevID() ) - $conds[] = "rev_id != {$page}"; - - return array( - 'tables' => array('revision'), - 'fields' => array( 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', 'rev_comment', - 'rev_id', 'rev_page', 'rev_text_id', 'rev_len', 'rev_deleted' ), - 'conds' => $conds - ); - } - - function getIndexField() { - return 'rev_timestamp'; - } -} diff --git a/includes/SpecialMostcategories.php b/includes/SpecialMostcategories.php deleted file mode 100644 index 589b96ee..00000000 --- a/includes/SpecialMostcategories.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php -/** - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * implements Special:Mostcategories - * @addtogroup SpecialPage - */ -class MostcategoriesPage extends QueryPage { - - function getName() { return 'Mostcategories'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' ); - return - " - SELECT - 'Mostcategories' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $categorylinks - LEFT JOIN $page ON cl_from = page_id - WHERE page_namespace = " . NS_MAIN . " - GROUP BY 1,2,3 - HAVING COUNT(*) > 1 - "; - } - - function formatResult( $skin, $result ) { - global $wgLang; - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if ( !$title instanceof Title ) { throw new MWException('Invalid title in database'); } - $count = wfMsgExt( 'ncategories', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->value ) ); - $link = $skin->makeKnownLinkObj( $title, $title->getText() ); - return wfSpecialList( $link, $count ); - } -} - -/** - * constructor - */ -function wfSpecialMostcategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostcategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialMostimages.php b/includes/SpecialMostimages.php deleted file mode 100644 index beb42fc1..00000000 --- a/includes/SpecialMostimages.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -/** - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * implements Special:Mostimages - * @addtogroup SpecialPage - */ -class MostimagesPage extends ImageQueryPage { - - function getName() { return 'Mostimages'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $imagelinks = $dbr->tableName( 'imagelinks' ); - return - " - SELECT - 'Mostimages' as type, - " . NS_IMAGE . " as namespace, - il_to as title, - COUNT(*) as value - FROM $imagelinks - GROUP BY 1,2,3 - HAVING COUNT(*) > 1 - "; - } - - function getCellHtml( $row ) { - global $wgLang; - return wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $row->value ) ) . '<br />'; - } - -} - -/** - * Constructor - */ -function wfSpecialMostimages() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostimagesPage(); - - $wpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialMostlinked.php b/includes/SpecialMostlinked.php deleted file mode 100644 index 916f219b..00000000 --- a/includes/SpecialMostlinked.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php - -/** - * A special page to show pages ordered by the number of pages linking to them. - * Implements Special:Mostlinked - * - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @author Rob Church <robchur@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class MostlinkedPage extends QueryPage { - - function getName() { return 'Mostlinked'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - /** - * Note: Getting page_namespace only works if $this->isCached() is false - */ - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' ); - return - "SELECT 'Mostlinked' AS type, - pl_namespace AS namespace, - pl_title AS title, - COUNT(*) AS value, - page_namespace - FROM $pagelinks - LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title - GROUP BY 1,2,3,5 - HAVING COUNT(*) > 1"; - } - - /** - * Pre-fill the link cache - */ - function preprocessResults( $db, $res ) { - if( $db->numRows( $res ) > 0 ) { - $linkBatch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) - $linkBatch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) ); - $db->dataSeek( $res, 0 ); - $linkBatch->execute(); - } - } - - /** - * Make a link to "what links here" for the specified title - * - * @param $title Title being queried - * @param $skin Skin to use - * @return string - */ - function makeWlhLink( &$title, $caption, &$skin ) { - $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() ); - return $skin->makeKnownLinkObj( $wlh, $caption ); - } - - /** - * Make links to the page corresponding to the item, and the "what links here" page for it - * - * @param $skin Skin to be used - * @param $result Result row - * @return string - */ - function formatResult( $skin, $result ) { - global $wgLang; - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - $link = $skin->makeLinkObj( $title ); - $wlh = $this->makeWlhLink( $title, - wfMsgExt( 'nlinks', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ), $skin ); - return wfSpecialList( $link, $wlh ); - } -} - -/** - * constructor - */ -function wfSpecialMostlinked() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostlinkedPage(); - - $wpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialMostlinkedcategories.php b/includes/SpecialMostlinkedcategories.php deleted file mode 100644 index c357c8f4..00000000 --- a/includes/SpecialMostlinkedcategories.php +++ /dev/null @@ -1,75 +0,0 @@ -<?php -/** - * A querypage to show categories ordered in descending order by the pages in them - * - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class MostlinkedCategoriesPage extends QueryPage { - - function getName() { return 'Mostlinkedcategories'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $categorylinks = $dbr->tableName( 'categorylinks' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT - $name as type, - " . NS_CATEGORY . " as namespace, - cl_to as title, - COUNT(*) as value - FROM $categorylinks - GROUP BY 1,2,3 - "; - } - - function sortDescending() { return true; } - - /** - * Fetch user page links and cache their existence - */ - function preprocessResults( $db, $res ) { - $batch = new LinkBatch; - while ( $row = $db->fetchObject( $res ) ) - $batch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) ); - $batch->execute(); - - // Back to start for display - if ( $db->numRows( $res ) > 0 ) - // If there are no rows we get an error seeking. - $db->dataSeek( $res, 0 ); - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getText() ); - - $plink = $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ); - - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - return wfSpecialList($plink, $nlinks); - } -} - -/** - * constructor - */ -function wfSpecialMostlinkedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostlinkedCategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialMostlinkedtemplates.php b/includes/SpecialMostlinkedtemplates.php deleted file mode 100644 index b0f1b196..00000000 --- a/includes/SpecialMostlinkedtemplates.php +++ /dev/null @@ -1,129 +0,0 @@ -<?php - -/** - * Special page lists templates with a large number of - * transclusion links, i.e. "most used" templates - * - * @addtogroup SpecialPage - * @author Rob Church <robchur@gmail.com> - */ -class SpecialMostlinkedtemplates extends QueryPage { - - /** - * Name of the report - * - * @return string - */ - public function getName() { - return 'Mostlinkedtemplates'; - } - - /** - * Is this report expensive, i.e should it be cached? - * - * @return bool - */ - public function isExpensive() { - return true; - } - - /** - * Is there a feed available? - * - * @return bool - */ - public function isSyndicated() { - return false; - } - - /** - * Sort the results in descending order? - * - * @return bool - */ - public function sortDescending() { - return true; - } - - /** - * Generate SQL for the report - * - * @return string - */ - public function getSql() { - $dbr = wfGetDB( DB_SLAVE ); - $templatelinks = $dbr->tableName( 'templatelinks' ); - $name = $dbr->addQuotes( $this->getName() ); - return "SELECT {$name} AS type, - " . NS_TEMPLATE . " AS namespace, - tl_title AS title, - COUNT(*) AS value - FROM {$templatelinks} - WHERE tl_namespace = " . NS_TEMPLATE . " - GROUP BY 1, 2, 3"; - } - - /** - * Pre-cache page existence to speed up link generation - * - * @param Database $dbr Database connection - * @param int $res Result pointer - */ - public function preprocessResults( $dbr, $res ) { - $batch = new LinkBatch(); - while( $row = $dbr->fetchObject( $res ) ) { - $title = Title::makeTitleSafe( $row->namespace, $row->title ); - $batch->addObj( $title ); - } - $batch->execute(); - if( $dbr->numRows( $res ) > 0 ) - $dbr->dataSeek( $res, 0 ); - } - - /** - * Format a result row - * - * @param Skin $skin Skin to use for UI elements - * @param object $result Result row - * @return string - */ - public function formatResult( $skin, $result ) { - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if( $title instanceof Title ) { - return wfSpecialList( - $skin->makeLinkObj( $title ), - $this->makeWlhLink( $title, $skin, $result ) - ); - } else { - $tsafe = htmlspecialchars( $result->title ); - return "Invalid title in result set; {$tsafe}"; - } - } - - /** - * Make a "what links here" link for a given title - * - * @param Title $title Title to make the link for - * @param Skin $skin Skin to use - * @param object $result Result row - * @return string - */ - private function makeWlhLink( $title, $skin, $result ) { - global $wgLang; - $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); - $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->value ) ); - return $skin->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() ); - } -} - -/** - * Execution function - * - * @param mixed $par Parameters passed to the page - */ -function wfSpecialMostlinkedtemplates( $par = false ) { - list( $limit, $offset ) = wfCheckLimits(); - $mlt = new SpecialMostlinkedtemplates(); - $mlt->doQuery( $offset, $limit ); -} diff --git a/includes/SpecialMostrevisions.php b/includes/SpecialMostrevisions.php deleted file mode 100644 index 9479a583..00000000 --- a/includes/SpecialMostrevisions.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php -/** - * A special page to show pages in the - * - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * @addtogroup SpecialPage - */ -class MostrevisionsPage extends QueryPage { - - function getName() { return 'Mostrevisions'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); - return - " - SELECT - 'Mostrevisions' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $revision - JOIN $page ON page_id = rev_page - WHERE page_namespace = " . NS_MAIN . " - GROUP BY 1,2,3 - HAVING COUNT(*) > 1 - "; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->makeKnownLinkObj( $nt, $text ); - - $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ); - - return wfSpecialList($plink, $nlink); - } -} - -/** - * constructor - */ -function wfSpecialMostrevisions() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostrevisionsPage(); - - $wpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialMovepage.php b/includes/SpecialMovepage.php deleted file mode 100644 index e0a89bc2..00000000 --- a/includes/SpecialMovepage.php +++ /dev/null @@ -1,327 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * Constructor - */ -function wfSpecialMovepage( $par = null ) { - global $wgUser, $wgOut, $wgRequest, $action; - - # Check rights - if ( !$wgUser->isAllowed( 'move' ) ) { - $wgOut->showPermissionsErrorPage( array( $wgUser->isAnon() ? 'movenologintext' : 'movenotallowed' ) ); - return; - } - - # Don't allow blocked users to move pages - if ( $wgUser->isBlocked() ) { - $wgOut->blockedPage(); - return; - } - - # Check for database lock - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - $f = new MovePageForm( $par ); - - if ( 'success' == $action ) { - $f->showSuccess(); - } else if ( 'submit' == $action && $wgRequest->wasPosted() - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( '' ); - } -} - -/** - * HTML form for Special:Movepage - * @addtogroup SpecialPage - */ -class MovePageForm { - var $oldTitle, $newTitle, $reason; # Text input - var $moveTalk, $deleteAndMove; - - private $watch = false; - - function MovePageForm( $par ) { - global $wgRequest; - $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); - $this->oldTitle = $wgRequest->getText( 'wpOldTitle', $target ); - $this->newTitle = $wgRequest->getText( 'wpNewTitle' ); - $this->reason = $wgRequest->getText( 'wpReason' ); - if ( $wgRequest->wasPosted() ) { - $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', false ); - } else { - $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', true ); - } - $this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' ); - $this->watch = $wgRequest->getCheck( 'wpWatch' ); - } - - function showForm( $err, $hookErr = '' ) { - global $wgOut, $wgUser, $wgContLang; - - $start = $wgContLang->isRTL() ? 'right' : 'left'; - $end = $wgContLang->isRTL() ? 'left' : 'right'; - - $wgOut->setPagetitle( wfMsg( 'movepage' ) ); - - $ot = Title::newFromURL( $this->oldTitle ); - if( is_null( $ot ) ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; - } - $sk = $wgUser->getSkin(); - $oldTitleLink = $sk->makeLinkObj( $ot ); - $oldTitle = $ot->getPrefixedText(); - - $encOldTitle = htmlspecialchars( $oldTitle ); - if( $this->newTitle == '' ) { - # Show the current title as a default - # when the form is first opened. - $newTitle = $oldTitle; - $encNewTitle = $encOldTitle; - } else { - if( $err == '' ) { - $nt = Title::newFromURL( $this->newTitle ); - if( $nt ) { - # If a title was supplied, probably from the move log revert - # link, check for validity. We can then show some diagnostic - # information and save a click. - $newerr = $ot->isValidMoveOperation( $nt ); - if( is_string( $newerr ) ) { - $err = $newerr; - } - } - } - $newTitle = $this->newTitle; - $encNewTitle = htmlspecialchars( $newTitle ); - } - $encReason = htmlspecialchars( $this->reason ); - - if ( $err == 'articleexists' && $wgUser->isAllowed( 'delete' ) ) { - $wgOut->addWikiMsg( 'delete_and_move_text', $newTitle ); - $movepagebtn = wfMsgHtml( 'delete_and_move' ); - $submitVar = 'wpDeleteAndMove'; - $confirm = " - <tr> - <td></td><td>" . Xml::checkLabel( wfMsg( 'delete_and_move_confirm' ), 'wpConfirm', 'wpConfirm' ) . "</td> - </tr>"; - $err = ''; - } else { - $wgOut->addWikiMsg( 'movepagetext' ); - $movepagebtn = wfMsgHtml( 'movepagebtn' ); - $submitVar = 'wpMove'; - $confirm = false; - } - - $oldTalk = $ot->getTalkPage(); - $considerTalk = ( !$ot->isTalkPage() && $oldTalk->exists() ); - - if ( $considerTalk ) { - $wgOut->addWikiMsg( 'movepagetalktext' ); - } - - $movearticle = wfMsgHtml( 'movearticle' ); - $newtitle = wfMsgHtml( 'newtitle' ); - $movereason = wfMsgHtml( 'movereason' ); - - $titleObj = SpecialPage::getTitleFor( 'Movepage' ); - $action = $titleObj->escapeLocalURL( 'action=submit' ); - $token = htmlspecialchars( $wgUser->editToken() ); - - if ( $err != '' ) { - $wgOut->setSubtitle( wfMsg( 'formerror' ) ); - $errMsg = ""; - if( $err == 'hookaborted' ) { - $errMsg = "<p><strong class=\"error\">$hookErr</strong></p>\n"; - } else { - $errMsg = '<p><strong class="error">' . wfMsgWikiHtml( $err ) . "</strong></p>\n"; - } - $wgOut->addHTML( $errMsg ); - } - - $moveTalkChecked = $this->moveTalk ? ' checked="checked"' : ''; - - $wgOut->addHTML( " -<form id=\"movepage\" method=\"post\" action=\"{$action}\"> - <table border='0'> - <tr> - <td align='$end'>{$movearticle}</td> - <td align='$start'><strong>{$oldTitleLink}</strong></td> - </tr> - <tr> - <td align='$end'><label for='wpNewTitle'>{$newtitle}</label></td> - <td align='$start'> - <input type='text' size='40' name='wpNewTitle' id='wpNewTitle' value=\"{$encNewTitle}\" /> - <input type='hidden' name=\"wpOldTitle\" value=\"{$encOldTitle}\" /> - </td> - </tr> - <tr> - <td align='$end' valign='top'><br /><label for='wpReason'>{$movereason}</label></td> - <td align='$start' valign='top'><br /> - <textarea cols='60' rows='2' name='wpReason' id='wpReason'>{$encReason}</textarea> - </td> - </tr>" ); - - if ( $considerTalk ) { - $wgOut->addHTML( " - <tr> - <td></td><td>" . Xml::checkLabel( wfMsg( 'movetalk' ), 'wpMovetalk', 'wpMovetalk', $moveTalkChecked ) . "</td> - </tr>" ); - } - - $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $ot->userIsWatching(); - $watch = '<tr>'; - $watch .= '<td></td><td>' . Xml::checkLabel( wfMsg( 'move-watch' ), 'wpWatch', 'watch', $watchChecked ) . '</td>'; - $watch .= '</tr>'; - $wgOut->addHtml( $watch ); - - $wgOut->addHTML( " - {$confirm} - <tr> - <td> </td> - <td align='$start'> - <input type='submit' name=\"{$submitVar}\" value=\"{$movepagebtn}\" /> - </td> - </tr> - </table> - <input type='hidden' name='wpEditToken' value=\"{$token}\" /> -</form>\n" ); - - $this->showLogFragment( $ot, $wgOut ); - - } - - function doSubmit() { - global $wgOut, $wgUser, $wgRequest; - - if ( $wgUser->pingLimiter( 'move' ) ) { - $wgOut->rateLimited(); - return; - } - - # Variables beginning with 'o' for old article 'n' for new article - - $ot = Title::newFromText( $this->oldTitle ); - $nt = Title::newFromText( $this->newTitle ); - - # Delete to make way if requested - if ( $wgUser->isAllowed( 'delete' ) && $this->deleteAndMove ) { - $article = new Article( $nt ); - // This may output an error message and exit - $article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) ); - } - - # don't allow moving to pages with # in - if ( !$nt || $nt->getFragment() != '' ) { - $this->showForm( 'badtitletext' ); - return; - } - - $hookErr = null; - if( !wfRunHooks( 'AbortMove', array( $ot, $nt, $wgUser, &$hookErr ) ) ) { - $this->showForm( 'hookaborted', $hookErr ); - return; - } - - $error = $ot->moveTo( $nt, true, $this->reason ); - if ( $error !== true ) { - $this->showForm( $error ); - return; - } - - wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ot , &$nt ) ) ; - - # Move the talk page if relevant, if it exists, and if we've been told to - $ott = $ot->getTalkPage(); - if( $ott->exists() ) { - if( $this->moveTalk && !$ot->isTalkPage() && !$nt->isTalkPage() ) { - $ntt = $nt->getTalkPage(); - - # Attempt the move - $error = $ott->moveTo( $ntt, true, $this->reason ); - if ( $error === true ) { - $talkmoved = 1; - wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ott , &$ntt ) ) ; - } else { - $talkmoved = $error; - } - } else { - # Stay silent on the subject of talk. - $talkmoved = ''; - } - } else { - $talkmoved = 'notalkpage'; - } - - # Deal with watches - if( $this->watch ) { - $wgUser->addWatch( $ot ); - $wgUser->addWatch( $nt ); - } else { - $wgUser->removeWatch( $ot ); - $wgUser->removeWatch( $nt ); - } - - # Give back result to user. - $titleObj = SpecialPage::getTitleFor( 'Movepage' ); - $success = $titleObj->getFullURL( - 'action=success&oldtitle=' . wfUrlencode( $ot->getPrefixedText() ) . - '&newtitle=' . wfUrlencode( $nt->getPrefixedText() ) . - '&talkmoved='.$talkmoved ); - - $wgOut->redirect( $success ); - } - - function showSuccess() { - global $wgOut, $wgRequest, $wgUser; - - $old = Title::newFromText( $wgRequest->getVal( 'oldtitle' ) ); - $new = Title::newFromText( $wgRequest->getVal( 'newtitle' ) ); - - if( is_null( $old ) || is_null( $new ) ) { - throw new ErrorPageError( 'badtitle', 'badtitletext' ); - } - - $wgOut->setPagetitle( wfMsg( 'movepage' ) ); - $wgOut->setSubtitle( wfMsg( 'pagemovedsub' ) ); - - $talkmoved = $wgRequest->getVal( 'talkmoved' ); - $oldUrl = $old->getFullUrl( 'redirect=no' ); - $newUrl = $new->getFullUrl(); - $oldText = $old->getPrefixedText(); - $newText = $new->getPrefixedText(); - $oldLink = "<span class='plainlinks'>[$oldUrl $oldText]</span>"; - $newLink = "<span class='plainlinks'>[$newUrl $newText]</span>"; - - $s = wfMsgNoTrans( 'movepage-moved', $oldLink, $newLink, $oldText, $newText ); - - if ( $talkmoved == 1 ) { - $s .= "\n\n" . wfMsgNoTrans( 'talkpagemoved' ); - } elseif( 'articleexists' == $talkmoved ) { - $s .= "\n\n" . wfMsgNoTrans( 'talkexists' ); - } else { - if( !$old->isTalkPage() && $talkmoved != 'notalkpage' ) { - $s .= "\n\n" . wfMsgNoTrans( 'talkpagenotmoved', wfMsgNoTrans( $talkmoved ) ); - } - } - $wgOut->addWikiText( $s ); - } - - function showLogFragment( $title, &$out ) { - $out->addHtml( wfElement( 'h2', NULL, LogPage::logName( 'move' ) ) ); - $request = new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'move' ) ); - $viewer = new LogViewer( new LogReader( $request ) ); - $viewer->showList( $out ); - } - -} - diff --git a/includes/SpecialNewimages.php b/includes/SpecialNewimages.php deleted file mode 100644 index 013b0986..00000000 --- a/includes/SpecialNewimages.php +++ /dev/null @@ -1,207 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -function wfSpecialNewimages( $par, $specialPage ) { - global $wgUser, $wgOut, $wgLang, $wgRequest, $wgGroupPermissions, $wgMiserMode; - - $wpIlMatch = $wgRequest->getText( 'wpIlMatch' ); - $dbr = wfGetDB( DB_SLAVE ); - $sk = $wgUser->getSkin(); - $shownav = !$specialPage->including(); - $hidebots = $wgRequest->getBool('hidebots',1); - - $hidebotsql = ''; - if ($hidebots) { - - /** Make a list of group names which have the 'bot' flag - set. - */ - $botconds=array(); - foreach ($wgGroupPermissions as $groupname=>$perms) { - if(array_key_exists('bot',$perms) && $perms['bot']) { - $botconds[]="ug_group='$groupname'"; - } - } - - /* If not bot groups, do not set $hidebotsql */ - if ($botconds) { - $isbotmember=$dbr->makeList($botconds, LIST_OR); - - /** This join, in conjunction with WHERE ug_group - IS NULL, returns only those rows from IMAGE - where the uploading user is not a member of - a group which has the 'bot' permission set. - */ - $ug = $dbr->tableName('user_groups'); - $hidebotsql = " LEFT OUTER JOIN $ug ON img_user=ug_user AND ($isbotmember)"; - } - } - - $image = $dbr->tableName('image'); - - $sql="SELECT img_timestamp from $image"; - if ($hidebotsql) { - $sql .= "$hidebotsql WHERE ug_group IS NULL"; - } - $sql.=' ORDER BY img_timestamp DESC LIMIT 1'; - $res = $dbr->query($sql, 'wfSpecialNewImages'); - $row = $dbr->fetchRow($res); - if($row!==false) { - $ts=$row[0]; - } else { - $ts=false; - } - $dbr->freeResult($res); - $sql=''; - - /** If we were clever, we'd use this to cache. */ - $latestTimestamp = wfTimestamp( TS_MW, $ts); - - /** Hardcode this for now. */ - $limit = 48; - - if ( $parval = intval( $par ) ) { - if ( $parval <= $limit && $parval > 0 ) { - $limit = $parval; - } - } - - $where = array(); - $searchpar = ''; - if ( $wpIlMatch != '' && !$wgMiserMode) { - $nt = Title::newFromUrl( $wpIlMatch ); - if($nt ) { - $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); - $m = str_replace( '%', "\\%", $m ); - $m = str_replace( '_', "\\_", $m ); - $where[] = "LOWER(img_name) LIKE '%{$m}%'"; - $searchpar = '&wpIlMatch=' . urlencode( $wpIlMatch ); - } - } - - $invertSort = false; - if( $until = $wgRequest->getVal( 'until' ) ) { - $where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'"; - } - if( $from = $wgRequest->getVal( 'from' ) ) { - $where[] = "img_timestamp >= '" . $dbr->timestamp( $from ) . "'"; - $invertSort = true; - } - $sql='SELECT img_size, img_name, img_user, img_user_text,'. - "img_description,img_timestamp FROM $image"; - - if($hidebotsql) { - $sql .= $hidebotsql; - $where[]='ug_group IS NULL'; - } - if(count($where)) { - $sql.=' WHERE '.$dbr->makeList($where, LIST_AND); - } - $sql.=' ORDER BY img_timestamp '. ( $invertSort ? '' : ' DESC' ); - $sql.=' LIMIT '.($limit+1); - $res = $dbr->query($sql, 'wfSpecialNewImages'); - - /** - * We have to flip things around to get the last N after a certain date - */ - $images = array(); - while ( $s = $dbr->fetchObject( $res ) ) { - if( $invertSort ) { - array_unshift( $images, $s ); - } else { - array_push( $images, $s ); - } - } - $dbr->freeResult( $res ); - - $gallery = new ImageGallery(); - $firstTimestamp = null; - $lastTimestamp = null; - $shownImages = 0; - foreach( $images as $s ) { - if( ++$shownImages > $limit ) { - # One extra just to test for whether to show a page link; - # don't actually show it. - break; - } - - $name = $s->img_name; - $ut = $s->img_user_text; - - $nt = Title::newFromText( $name, NS_IMAGE ); - $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut ); - - $gallery->add( $nt, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" ); - - $timestamp = wfTimestamp( TS_MW, $s->img_timestamp ); - if( empty( $firstTimestamp ) ) { - $firstTimestamp = $timestamp; - } - $lastTimestamp = $timestamp; - } - - $bydate = wfMsg( 'bydate' ); - $lt = $wgLang->formatNum( min( $shownImages, $limit ) ); - if ($shownav) { - $text = wfMsgExt( 'imagelisttext', array('parse'), $lt, $bydate ); - $wgOut->addHTML( $text . "\n" ); - } - - $sub = wfMsg( 'ilsubmit' ); - $titleObj = SpecialPage::getTitleFor( 'Newimages' ); - $action = $titleObj->escapeLocalURL( $hidebots ? '' : 'hidebots=0' ); - if ($shownav && !$wgMiserMode) { - $wgOut->addHTML( "<form id=\"imagesearch\" method=\"post\" action=\"" . - "{$action}\">" . - Xml::input( 'wpIlMatch', 20, $wpIlMatch ) . ' ' . - Xml::submitButton( $sub, array( 'name' => 'wpIlSubmit' ) ) . - "</form>" ); - } - - /** - * Paging controls... - */ - - # If we change bot visibility, this needs to be carried along. - if(!$hidebots) { - $botpar='&hidebots=0'; - } else { - $botpar=''; - } - $now = wfTimestampNow(); - $date = $wgLang->timeanddate( $now, true ); - $dateLink = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml( 'sp-newimages-showfrom', $date ), 'from='.$now.$botpar.$searchpar ); - - $botLink = $sk->makeKnownLinkObj($titleObj, wfMsgHtml( 'showhidebots', ($hidebots ? wfMsgHtml('show') : wfMsgHtml('hide'))),'hidebots='.($hidebots ? '0' : '1').$searchpar); - - $prevLink = wfMsgHtml( 'prevn', $wgLang->formatNum( $limit ) ); - if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) { - $prevLink = $sk->makeKnownLinkObj( $titleObj, $prevLink, 'from=' . $firstTimestamp . $botpar . $searchpar ); - } - - $nextLink = wfMsgHtml( 'nextn', $wgLang->formatNum( $limit ) ); - if( $shownImages > $limit && $lastTimestamp ) { - $nextLink = $sk->makeKnownLinkObj( $titleObj, $nextLink, 'until=' . $lastTimestamp.$botpar.$searchpar ); - } - - $prevnext = '<p>' . $botLink . ' '. wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'</p>'; - - if ($shownav) - $wgOut->addHTML( $prevnext ); - - if( count( $images ) ) { - $wgOut->addHTML( $gallery->toHTML() ); - if ($shownav) - $wgOut->addHTML( $prevnext ); - } else { - $wgOut->addWikiMsg( 'noimages' ); - } -} - - diff --git a/includes/SpecialNewpages.php b/includes/SpecialNewpages.php deleted file mode 100644 index 1c3bee84..00000000 --- a/includes/SpecialNewpages.php +++ /dev/null @@ -1,317 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - - -/** - * Start point - */ -function wfSpecialNewPages( $par, $specialPage ) { - $page = new NewPagesPage( $specialPage ); - $page->execute( $par ); -} - -/** - * implements Special:Newpages - * @addtogroup SpecialPage - */ -class NewPagesPage extends QueryPage { - - protected $options = array(); - protected $nondefaults = array(); - protected $specialPage; - - public function __construct( $specialPage=null ) { - $this->specialPage = $specialPage; - } - - public function execute( $par ) { - global $wgRequest, $wgLang; - - $shownavigation = is_object( $this->specialPage ) && !$this->specialPage->including(); - - $defaults = array( - /* bool */ 'hideliu' => false, - /* bool */ 'hidepatrolled' => false, - /* bool */ 'hidebots' => false, - /* text */ 'namespace' => "0", - /* text */ 'username' => '', - /* int */ 'offset' => 0, - /* int */ 'limit' => 50, - ); - - $options = $defaults; - - if ( $par ) { - $bits = preg_split( '/\s*,\s*/', trim( $par ) ); - foreach ( $bits as $bit ) { - if ( 'shownav' == $bit ) - $shownavigation = true; - if ( 'hideliu' === $bit ) - $options['hideliu'] = true; - if ( 'hidepatrolled' == $bit ) - $options['hidepatrolled'] = true; - if ( 'hidebots' == $bit ) - $options['hidebots'] = true; - if ( is_numeric( $bit ) ) - $options['limit'] = intval( $bit ); - - $m = array(); - if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) - $options['limit'] = intval($m[1]); - if ( preg_match( '/^offset=(\d+)$/', $bit, $m ) ) - $options['offset'] = intval($m[1]); - if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { - $ns = $wgLang->getNsIndex( $m[1] ); - if( $ns !== false ) { - $options['namespace'] = $ns; - } - } - } - } - - // Override all values from requests, if specified - foreach ( $defaults as $v => $t ) { - if ( is_bool($t) ) { - $options[$v] = $wgRequest->getBool( $v, $options[$v] ); - } elseif( is_int($t) ) { - $options[$v] = $wgRequest->getInt( $v, $options[$v] ); - } elseif( is_string($t) ) { - $options[$v] = $wgRequest->getText( $v, $options[$v] ); - } - } - - // Validate limit and offset params - if ( $options['limit'] <= 0 ) { - $options['limit'] = $defaults['limit']; - } - - if ( $options['offset'] < 0 ) { - $options['offset'] = $defaults['offset']; - } - - $nondefaults = array(); - foreach ( $options as $v => $t ) { - if ( $v === 'offset' ) continue; # Reset offset if parameters change - wfAppendToArrayIfNotDefault( $v, $t, $defaults, $nondefaults ); - } - - # bind to class - $this->options = $options; - $this->nondefaults = $nondefaults; - - if ( !$this->doFeed( $wgRequest->getVal( 'feed' ), $options['limit'] ) ) { - $this->doQuery( $options['offset'], $options['limit'], $shownavigation ); - } - } - - function linkParameters() { - $nondefaults = $this->nondefaults; - // QueryPage seems to handle limit and offset itself - if ( isset( $nondefaults['limit'] ) ) { - unset($nondefaults['limit']); - } - return $nondefaults; - } - - function getName() { - return 'Newpages'; - } - - function isExpensive() { - # Indexed on RC, and will *not* work with querycache yet. - return false; - } - - function makeUserWhere( $db ) { - global $wgGroupPermissions; - $conds = array(); - if ($this->options['hidepatrolled']) { - $conds['rc_patrolled'] = 0; - } - if ($this->options['hidebots']) { - $conds['rc_bot'] = 0; - } - if ($wgGroupPermissions['*']['createpage'] == true && $this->options['hideliu']) { - $conds['rc_user'] = 0; - } else { - $title = Title::makeTitleSafe( NS_USER, $this->options['username'] ); - if( $title ) { - $conds['rc_user_text'] = $title->getText(); - } - } - return $conds; - } - - function getSQL() { - global $wgUser, $wgUseNPPatrol, $wgUseRCPatrol; - $usepatrol = ( $wgUseNPPatrol || $wgUseRCPatrol ) ? 1 : 0; - $dbr = wfGetDB( DB_SLAVE ); - list( $recentchanges, $page ) = $dbr->tableNamesN( 'recentchanges', 'page' ); - - $conds = array(); - $conds['rc_new'] = 1; - if ( $this->options['namespace'] !== 'all' ) { - $conds['rc_namespace'] = intval( $this->options['namespace'] ); - } - $conds['page_is_redirect'] = 0; - $conds += $this->makeUserWhere( $dbr ); - $condstext = $dbr->makeList( $conds, LIST_AND ); - - # FIXME: text will break with compression - return - "SELECT 'Newpages' as type, - rc_namespace AS namespace, - rc_title AS title, - rc_cur_id AS cur_id, - rc_user AS \"user\", - rc_user_text AS user_text, - rc_comment as \"comment\", - rc_timestamp AS timestamp, - rc_timestamp AS value, - '{$usepatrol}' as usepatrol, - rc_patrolled AS patrolled, - rc_id AS rcid, - page_len as length, - page_latest as rev_id - FROM $recentchanges,$page - WHERE rc_cur_id=page_id AND $condstext"; - } - - function preprocessResults( $db, $res ) { - # Do a batch existence check on the user and talk pages - $linkBatch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) { - $linkBatch->add( NS_USER, $row->user_text ); - $linkBatch->add( NS_USER_TALK, $row->user_text ); - } - $linkBatch->execute(); - # Seek to start - if( $db->numRows( $res ) > 0 ) - $db->dataSeek( $res, 0 ); - } - - /** - * Format a row, providing the timestamp, links to the page/history, size, user links, and a comment - * - * @param $skin Skin to use - * @param $result Result row - * @return string - */ - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - $dm = $wgContLang->getDirMark(); - - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - $time = $wgLang->timeAndDate( $result->timestamp, true ); - $plink = $skin->makeKnownLinkObj( $title, '', $this->patrollable( $result ) ? 'rcid=' . $result->rcid : '' ); - $hist = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' ); - $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->length ) ) ); - $ulink = $skin->userLink( $result->user, $result->user_text ) . ' ' . $skin->userToolLinks( $result->user, $result->user_text ); - $comment = $skin->commentBlock( $result->comment ); - - return "{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment}"; - } - - /** - * Should a specific result row provide "patrollable" links? - * - * @param $result Result row - * @return bool - */ - function patrollable( $result ) { - global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol; - return ( $wgUseRCPatrol || $wgUseNPPatrol ) - && $wgUser->isAllowed( 'patrol' ) - && !$result->patrolled; - } - - function feedItemDesc( $row ) { - if( isset( $row->rev_id ) ) { - $revision = Revision::newFromId( $row->rev_id ); - if( $revision ) { - return '<p>' . htmlspecialchars( wfMsg( 'summary' ) ) . ': ' . - htmlspecialchars( $revision->getComment() ) . "</p>\n<hr />\n<div>" . - nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; - } - } - return parent::feedItemDesc( $row ); - } - - /** - * Show a form for filtering namespace and username - * - * @return string - */ - function getPageHeader() { - global $wgScript, $wgContLang, $wgGroupPermissions, $wgUser, $wgUseRCPatrol, $wgUseNPPatrol; - $sk = $wgUser->getSkin(); - $align = $wgContLang->isRTL() ? 'left' : 'right'; - $self = SpecialPage::getTitleFor( $this->getName() ); - - // show/hide links - $showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' )); - - $hidelinks = array(); - - if ( $wgGroupPermissions['*']['createpage'] === true ) { - $hidelinks['hideliu'] = 'rcshowhideliu'; - } - if ( $wgUseNPPatrol || $wgUseRCPatrol ) { - $hidelinks['hidepatrolled'] = 'rcshowhidepatr'; - } - $hidelinks['hidebots'] = 'rcshowhidebots'; - - $links = array(); - foreach ( $hidelinks as $key => $msg ) { - $reversed = 1-$this->options[$key]; - $link = $sk->makeKnownLinkObj( $self, $showhide[$reversed], - wfArrayToCGI( array( $key => $reversed ), $this->nondefaults ) - ); - $links[$key] = wfMsgHtml( $msg, $link ); - } - - $hl = implode( ' | ', $links ); - - // Store query values in hidden fields so that form submission doesn't lose them - $hidden = array(); - foreach ( $this->nondefaults as $key => $value ) { - if ( $key === 'namespace' ) continue; - if ( $key === 'username' ) continue; - $hidden[] = Xml::hidden( $key, $value ); - } - $hidden = implode( "\n", $hidden ); - - $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - Xml::hidden( 'title', $self->getPrefixedDBkey() ) . - Xml::openElement( 'table' ) . - "<tr> - <td align=\"$align\">" . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . - "</td> - <td>" . - Xml::namespaceSelector( $this->options['namespace'], 'all' ) . - "</td> - </tr> - <tr> - <td align=\"$align\">" . - Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) . - "</td> - <td>" . - Xml::input( 'username', 30, $this->options['username'], array( 'id' => 'mw-np-username' ) ) . - "</td> - </tr> - <tr> <td></td> - <td>" . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - "</td> - </tr>" . - "<tr><td></td><td>" . $hl . "</td></tr>" . - Xml::closeElement( 'table' ) . - $hidden . - Xml::closeElement( 'form' ); - return $form; - } -} diff --git a/includes/SpecialPopularpages.php b/includes/SpecialPopularpages.php deleted file mode 100644 index af0ed269..00000000 --- a/includes/SpecialPopularpages.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * implements Special:Popularpages - * @addtogroup SpecialPage - */ -class PopularPagesPage extends QueryPage { - - function getName() { - return "Popularpages"; - } - - function isExpensive() { - # page_counter is not indexed - return true; - } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - - $query = - "SELECT 'Popularpages' as type, - page_namespace as namespace, - page_title as title, - page_counter as value - FROM $page "; - $where = - "WHERE page_is_redirect=0 AND page_namespace"; - - global $wgContentNamespaces; - if( empty( $wgContentNamespaces ) ) { - $where .= '='.NS_MAIN; - } else if( count( $wgContentNamespaces ) > 1 ) { - $where .= ' in (' . implode( ', ', $wgContentNamespaces ) . ')'; - } else { - $where .= '='.$wgContentNamespaces[0]; - } - - return $query . $where; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - $title = Title::makeTitle( $result->namespace, $result->title ); - $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); - $nv = wfMsgExt( 'nviews', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - return wfSpecialList($link, $nv); - } -} - -/** - * Constructor - */ -function wfSpecialPopularpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $ppp = new PopularPagesPage(); - - return $ppp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialPreferences.php b/includes/SpecialPreferences.php deleted file mode 100644 index ca163a6a..00000000 --- a/includes/SpecialPreferences.php +++ /dev/null @@ -1,1047 +0,0 @@ -<?php -/** - * Hold things related to displaying and saving user preferences. - * @addtogroup SpecialPage - */ - -/** - * Entry point that create the "Preferences" object - */ -function wfSpecialPreferences() { - global $wgRequest; - - $form = new PreferencesForm( $wgRequest ); - $form->execute(); -} - -/** - * Preferences form handling - * This object will show the preferences form and can save it as well. - * @addtogroup SpecialPage - */ -class PreferencesForm { - var $mQuickbar, $mOldpass, $mNewpass, $mRetypePass, $mStubs; - var $mRows, $mCols, $mSkin, $mMath, $mDate, $mUserEmail, $mEmailFlag, $mNick; - var $mUserLanguage, $mUserVariant; - var $mSearch, $mRecent, $mRecentDays, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; - var $mReset, $mPosted, $mToggles, $mUseAjaxSearch, $mSearchNs, $mRealName, $mImageSize; - var $mUnderline, $mWatchlistEdits; - - /** - * Constructor - * Load some values - */ - function PreferencesForm( &$request ) { - global $wgContLang, $wgUser, $wgAllowRealName; - - $this->mQuickbar = $request->getVal( 'wpQuickbar' ); - $this->mOldpass = $request->getVal( 'wpOldpass' ); - $this->mNewpass = $request->getVal( 'wpNewpass' ); - $this->mRetypePass =$request->getVal( 'wpRetypePass' ); - $this->mStubs = $request->getVal( 'wpStubs' ); - $this->mRows = $request->getVal( 'wpRows' ); - $this->mCols = $request->getVal( 'wpCols' ); - $this->mSkin = $request->getVal( 'wpSkin' ); - $this->mMath = $request->getVal( 'wpMath' ); - $this->mDate = $request->getVal( 'wpDate' ); - $this->mUserEmail = $request->getVal( 'wpUserEmail' ); - $this->mRealName = $wgAllowRealName ? $request->getVal( 'wpRealName' ) : ''; - $this->mEmailFlag = $request->getCheck( 'wpEmailFlag' ) ? 0 : 1; - $this->mNick = $request->getVal( 'wpNick' ); - $this->mUserLanguage = $request->getVal( 'wpUserLanguage' ); - $this->mUserVariant = $request->getVal( 'wpUserVariant' ); - $this->mSearch = $request->getVal( 'wpSearch' ); - $this->mRecent = $request->getVal( 'wpRecent' ); - $this->mRecentDays = $request->getVal( 'wpRecentDays' ); - $this->mHourDiff = $request->getVal( 'wpHourDiff' ); - $this->mSearchLines = $request->getVal( 'wpSearchLines' ); - $this->mSearchChars = $request->getVal( 'wpSearchChars' ); - $this->mImageSize = $request->getVal( 'wpImageSize' ); - $this->mThumbSize = $request->getInt( 'wpThumbSize' ); - $this->mUnderline = $request->getInt( 'wpOpunderline' ); - $this->mAction = $request->getVal( 'action' ); - $this->mReset = $request->getCheck( 'wpReset' ); - $this->mPosted = $request->wasPosted(); - $this->mSuccess = $request->getCheck( 'success' ); - $this->mWatchlistDays = $request->getVal( 'wpWatchlistDays' ); - $this->mWatchlistEdits = $request->getVal( 'wpWatchlistEdits' ); - $this->mUseAjaxSearch = $request->getCheck( 'wpUseAjaxSearch' ); - - $this->mSaveprefs = $request->getCheck( 'wpSaveprefs' ) && - $this->mPosted && - $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - - # User toggles (the big ugly unsorted list of checkboxes) - $this->mToggles = array(); - if ( $this->mPosted ) { - $togs = User::getToggles(); - foreach ( $togs as $tname ) { - $this->mToggles[$tname] = $request->getCheck( "wpOp$tname" ) ? 1 : 0; - } - } - - $this->mUsedToggles = array(); - - # Search namespace options - # Note: namespaces don't necessarily have consecutive keys - $this->mSearchNs = array(); - if ( $this->mPosted ) { - $namespaces = $wgContLang->getNamespaces(); - foreach ( $namespaces as $i => $namespace ) { - if ( $i >= 0 ) { - $this->mSearchNs[$i] = $request->getCheck( "wpNs$i" ) ? 1 : 0; - } - } - } - - # Validate language - if ( !preg_match( '/^[a-z\-]*$/', $this->mUserLanguage ) ) { - $this->mUserLanguage = 'nolanguage'; - } - - wfRunHooks( 'InitPreferencesForm', array( $this, $request ) ); - } - - function execute() { - global $wgUser, $wgOut; - - if ( $wgUser->isAnon() ) { - $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext' ); - return; - } - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - if ( $this->mReset ) { - $this->resetPrefs(); - $this->mainPrefsForm( 'reset', wfMsg( 'prefsreset' ) ); - } else if ( $this->mSaveprefs ) { - $this->savePreferences(); - } else { - $this->resetPrefs(); - $this->mainPrefsForm( '' ); - } - } - /** - * @access private - */ - function validateInt( &$val, $min=0, $max=0x7fffffff ) { - $val = intval($val); - $val = min($val, $max); - $val = max($val, $min); - return $val; - } - - /** - * @access private - */ - function validateFloat( &$val, $min, $max=0x7fffffff ) { - $val = floatval( $val ); - $val = min( $val, $max ); - $val = max( $val, $min ); - return( $val ); - } - - /** - * @access private - */ - function validateIntOrNull( &$val, $min=0, $max=0x7fffffff ) { - $val = trim($val); - if($val === '') { - return $val; - } else { - return $this->validateInt( $val, $min, $max ); - } - } - - /** - * @access private - */ - function validateDate( $val ) { - global $wgLang, $wgContLang; - if ( $val !== false && ( - in_array( $val, (array)$wgLang->getDatePreferences() ) || - in_array( $val, (array)$wgContLang->getDatePreferences() ) ) ) - { - return $val; - } else { - return $wgLang->getDefaultDateFormat(); - } - } - - /** - * Used to validate the user inputed timezone before saving it as - * 'timecorrection', will return '00:00' if fed bogus data. - * Note: It's not a 100% correct implementation timezone-wise, it will - * accept stuff like '14:30', - * @access private - * @param string $s the user input - * @return string - */ - function validateTimeZone( $s ) { - if ( $s !== '' ) { - if ( strpos( $s, ':' ) ) { - # HH:MM - $array = explode( ':' , $s ); - $hour = intval( $array[0] ); - $minute = intval( $array[1] ); - } else { - $minute = intval( $s * 60 ); - $hour = intval( $minute / 60 ); - $minute = abs( $minute ) % 60; - } - # Max is +14:00 and min is -12:00, see: - # http://en.wikipedia.org/wiki/Timezone - $hour = min( $hour, 14 ); - $hour = max( $hour, -12 ); - $minute = min( $minute, 59 ); - $minute = max( $minute, 0 ); - $s = sprintf( "%02d:%02d", $hour, $minute ); - } - return $s; - } - - /** - * @access private - */ - function savePreferences() { - global $wgUser, $wgOut, $wgParser; - global $wgEnableUserEmail, $wgEnableEmail; - global $wgEmailAuthentication, $wgRCMaxAge; - global $wgAuth, $wgEmailConfirmToEdit; - - - if ( '' != $this->mNewpass && $wgAuth->allowPasswordChange() ) { - if ( $this->mNewpass != $this->mRetypePass ) { - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'badretype' ) ); - $this->mainPrefsForm( 'error', wfMsg( 'badretype' ) ); - return; - } - - if (!$wgUser->checkPassword( $this->mOldpass )) { - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'wrongpassword' ) ); - $this->mainPrefsForm( 'error', wfMsg( 'wrongpassword' ) ); - return; - } - - try { - $wgUser->setPassword( $this->mNewpass ); - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'success' ) ); - $this->mNewpass = $this->mOldpass = $this->mRetypePass = ''; - } catch( PasswordError $e ) { - wfRunHooks( 'PrefsPasswordAudit', array( $wgUser, $this->mNewpass, 'error' ) ); - $this->mainPrefsForm( 'error', $e->getMessage() ); - return; - } - } - $wgUser->setRealName( $this->mRealName ); - - if( $wgUser->getOption( 'language' ) !== $this->mUserLanguage ) { - $needRedirect = true; - } else { - $needRedirect = false; - } - - # Validate the signature and clean it up as needed - global $wgMaxSigChars; - if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) { - global $wgLang; - $this->mainPrefsForm( 'error', - wfMsg( 'badsiglength', $wgLang->formatNum( $wgMaxSigChars ) ) ); - return; - } elseif( $this->mToggles['fancysig'] ) { - if( $wgParser->validateSig( $this->mNick ) !== false ) { - $this->mNick = $wgParser->cleanSig( $this->mNick ); - } else { - $this->mainPrefsForm( 'error', wfMsg( 'badsig' ) ); - return; - } - } else { - // When no fancy sig used, make sure ~{3,5} get removed. - $this->mNick = $wgParser->cleanSigInSig( $this->mNick ); - } - - $wgUser->setOption( 'language', $this->mUserLanguage ); - $wgUser->setOption( 'variant', $this->mUserVariant ); - $wgUser->setOption( 'nickname', $this->mNick ); - $wgUser->setOption( 'quickbar', $this->mQuickbar ); - $wgUser->setOption( 'skin', $this->mSkin ); - global $wgUseTeX; - if( $wgUseTeX ) { - $wgUser->setOption( 'math', $this->mMath ); - } - $wgUser->setOption( 'date', $this->validateDate( $this->mDate ) ); - $wgUser->setOption( 'searchlimit', $this->validateIntOrNull( $this->mSearch ) ); - $wgUser->setOption( 'contextlines', $this->validateIntOrNull( $this->mSearchLines ) ); - $wgUser->setOption( 'contextchars', $this->validateIntOrNull( $this->mSearchChars ) ); - $wgUser->setOption( 'rclimit', $this->validateIntOrNull( $this->mRecent ) ); - $wgUser->setOption( 'rcdays', $this->validateInt($this->mRecentDays, 1, ceil($wgRCMaxAge / (3600*24)))); - $wgUser->setOption( 'wllimit', $this->validateIntOrNull( $this->mWatchlistEdits, 0, 1000 ) ); - $wgUser->setOption( 'rows', $this->validateInt( $this->mRows, 4, 1000 ) ); - $wgUser->setOption( 'cols', $this->validateInt( $this->mCols, 4, 1000 ) ); - $wgUser->setOption( 'stubthreshold', $this->validateIntOrNull( $this->mStubs ) ); - $wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mHourDiff, -12, 14 ) ); - $wgUser->setOption( 'imagesize', $this->mImageSize ); - $wgUser->setOption( 'thumbsize', $this->mThumbSize ); - $wgUser->setOption( 'underline', $this->validateInt($this->mUnderline, 0, 2) ); - $wgUser->setOption( 'watchlistdays', $this->validateFloat( $this->mWatchlistDays, 0, 7 ) ); - $wgUser->setOption( 'ajaxsearch', $this->mUseAjaxSearch ); - - # Set search namespace options - foreach( $this->mSearchNs as $i => $value ) { - $wgUser->setOption( "searchNs{$i}", $value ); - } - - if( $wgEnableEmail && $wgEnableUserEmail ) { - $wgUser->setOption( 'disablemail', $this->mEmailFlag ); - } - - # Set user toggles - foreach ( $this->mToggles as $tname => $tvalue ) { - $wgUser->setOption( $tname, $tvalue ); - } - - $error = false; - if( $wgEnableEmail ) { - $newadr = $this->mUserEmail; - $oldadr = $wgUser->getEmail(); - if( ($newadr != '') && ($newadr != $oldadr) ) { - # the user has supplied a new email address on the login page - if( $wgUser->isValidEmailAddr( $newadr ) ) { - $wgUser->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record - $wgUser->mEmailAuthenticated = null; # but flag as "dirty" = unauthenticated - if ($wgEmailAuthentication) { - # Mail a temporary password to the dirty address. - # User can come back through the confirmation URL to re-enable email. - $result = $wgUser->sendConfirmationMail(); - if( WikiError::isError( $result ) ) { - $error = wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) ); - } else { - $error = wfMsg( 'eauthentsent', $wgUser->getName() ); - } - } - } else { - $error = wfMsg( 'invalidemailaddress' ); - } - } else { - if( $wgEmailConfirmToEdit && empty( $newadr ) ) { - $this->mainPrefsForm( 'error', wfMsg( 'noemailtitle' ) ); - return; - } - $wgUser->setEmail( $this->mUserEmail ); - } - if( $oldadr != $newadr ) { - wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) ); - } - } - - if (!$wgAuth->updateExternalDB($wgUser)) { - $this->mainPrefsForm( 'error', wfMsg( 'externaldberror' ) ); - return; - } - - $msg = ''; - if ( !wfRunHooks( 'SavePreferences', array( $this, $wgUser, &$msg ) ) ) { - print "(($msg))"; - $this->mainPrefsForm( 'error', $msg ); - return; - } - - $wgUser->setCookies(); - $wgUser->saveSettings(); - - if( $needRedirect && $error === false ) { - $title = SpecialPage::getTitleFor( 'Preferences' ); - $wgOut->redirect($title->getFullURL('success')); - return; - } - - $wgOut->setParserOptions( ParserOptions::newFromUser( $wgUser ) ); - $this->mainPrefsForm( $error === false ? 'success' : 'error', $error); - } - - /** - * @access private - */ - function resetPrefs() { - global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName; - - $this->mOldpass = $this->mNewpass = $this->mRetypePass = ''; - $this->mUserEmail = $wgUser->getEmail(); - $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp(); - $this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : ''; - - # language value might be blank, default to content language - $this->mUserLanguage = $wgUser->getOption( 'language', $wgContLanguageCode ); - - $this->mUserVariant = $wgUser->getOption( 'variant'); - $this->mEmailFlag = $wgUser->getOption( 'disablemail' ) == 1 ? 1 : 0; - $this->mNick = $wgUser->getOption( 'nickname' ); - - $this->mQuickbar = $wgUser->getOption( 'quickbar' ); - $this->mSkin = Skin::normalizeKey( $wgUser->getOption( 'skin' ) ); - $this->mMath = $wgUser->getOption( 'math' ); - $this->mDate = $wgUser->getDatePreference(); - $this->mRows = $wgUser->getOption( 'rows' ); - $this->mCols = $wgUser->getOption( 'cols' ); - $this->mStubs = $wgUser->getOption( 'stubthreshold' ); - $this->mHourDiff = $wgUser->getOption( 'timecorrection' ); - $this->mSearch = $wgUser->getOption( 'searchlimit' ); - $this->mSearchLines = $wgUser->getOption( 'contextlines' ); - $this->mSearchChars = $wgUser->getOption( 'contextchars' ); - $this->mImageSize = $wgUser->getOption( 'imagesize' ); - $this->mThumbSize = $wgUser->getOption( 'thumbsize' ); - $this->mRecent = $wgUser->getOption( 'rclimit' ); - $this->mRecentDays = $wgUser->getOption( 'rcdays' ); - $this->mWatchlistEdits = $wgUser->getOption( 'wllimit' ); - $this->mUnderline = $wgUser->getOption( 'underline' ); - $this->mWatchlistDays = $wgUser->getOption( 'watchlistdays' ); - $this->mUseAjaxSearch = $wgUser->getBoolOption( 'ajaxsearch' ); - - $togs = User::getToggles(); - foreach ( $togs as $tname ) { - $this->mToggles[$tname] = $wgUser->getOption( $tname ); - } - - $namespaces = $wgContLang->getNamespaces(); - foreach ( $namespaces as $i => $namespace ) { - if ( $i >= NS_MAIN ) { - $this->mSearchNs[$i] = $wgUser->getOption( 'searchNs'.$i ); - } - } - - wfRunHooks( 'ResetPreferences', array( $this, $wgUser ) ); - } - - /** - * @access private - */ - function namespacesCheckboxes() { - global $wgContLang; - - # Determine namespace checkboxes - $namespaces = $wgContLang->getNamespaces(); - $r1 = null; - - foreach ( $namespaces as $i => $name ) { - if ($i < 0) - continue; - $checked = $this->mSearchNs[$i] ? "checked='checked'" : ''; - $name = str_replace( '_', ' ', $namespaces[$i] ); - - if ( empty($name) ) - $name = wfMsg( 'blanknamespace' ); - - $r1 .= "<input type='checkbox' value='1' name='wpNs$i' id='wpNs$i' {$checked}/> <label for='wpNs$i'>{$name}</label><br />\n"; - } - return $r1; - } - - - function getToggle( $tname, $trailer = false, $disabled = false ) { - global $wgUser, $wgLang; - - $this->mUsedToggles[$tname] = true; - $ttext = $wgLang->getUserToggle( $tname ); - - $checked = $wgUser->getOption( $tname ) == 1 ? ' checked="checked"' : ''; - $disabled = $disabled ? ' disabled="disabled"' : ''; - $trailer = $trailer ? $trailer : ''; - return "<div class='toggle'><input type='checkbox' value='1' id=\"$tname\" name=\"wpOp$tname\"$checked$disabled />" . - " <span class='toggletext'><label for=\"$tname\">$ttext</label>$trailer</span></div>\n"; - } - - function getToggles( $items ) { - $out = ""; - foreach( $items as $item ) { - if( $item === false ) - continue; - if( is_array( $item ) ) { - list( $key, $trailer ) = $item; - } else { - $key = $item; - $trailer = false; - } - $out .= $this->getToggle( $key, $trailer ); - } - return $out; - } - - function addRow($td1, $td2) { - return "<tr><td align='right'>$td1</td><td align='left'>$td2</td></tr>"; - } - - /** - * Helper function for user information panel - * @param $td1 label for an item - * @param $td2 item or null - * @param $td3 optional help or null - * @return xhtml block - */ - function tableRow( $td1, $td2 = null, $td3 = null ) { - global $wgContLang; - - $align['align'] = $wgContLang->isRtl() ? 'right' : 'left'; - - if ( is_null( $td3 ) ) { - $td3 = ''; - } else { - $td3 = Xml::tags( 'tr', null, - Xml::tags( 'td', array( 'colspan' => '2' ), $td3 ) - ); - } - - if ( is_null( $td2 ) ) { - $td1 = Xml::tags( 'td', $align + array( 'colspan' => '2' ), $td1 ); - $td2 = ''; - } else { - $td1 = Xml::tags( 'td', $align, $td1 ); - $td2 = Xml::tags( 'td', $align, $td2 ); - } - - return Xml::tags( 'tr', null, $td1 . $td2 ). $td3 . "\n"; - - } - - /** - * @access private - */ - function mainPrefsForm( $status , $message = '' ) { - global $wgUser, $wgOut, $wgLang, $wgContLang; - global $wgAllowRealName, $wgImageLimits, $wgThumbLimits; - global $wgDisableLangConversion; - global $wgEnotifWatchlist, $wgEnotifUserTalk,$wgEnotifMinorEdits; - global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress; - global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication; - global $wgContLanguageCode, $wgDefaultSkin, $wgSkipSkins, $wgAuth; - global $wgEmailConfirmToEdit, $wgAjaxSearch; - - $wgOut->setPageTitle( wfMsg( 'preferences' ) ); - $wgOut->setArticleRelated( false ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - - $wgOut->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc. - - if ( $this->mSuccess || 'success' == $status ) { - $wgOut->wrapWikiMsg( '<div class="successbox"><strong>$1</strong></div>', 'savedprefs' ); - } else if ( 'error' == $status ) { - $wgOut->addWikiText( '<div class="errorbox"><strong>' . $message . '</strong></div>' ); - } else if ( '' != $status ) { - $wgOut->addWikiText( $message . "\n----" ); - } - - $qbs = $wgLang->getQuickbarSettings(); - $skinNames = $wgLang->getSkinNames(); - $mathopts = $wgLang->getMathNames(); - $dateopts = $wgLang->getDatePreferences(); - $togs = User::getToggles(); - - $titleObj = SpecialPage::getTitleFor( 'Preferences' ); - $action = $titleObj->escapeLocalURL(); - - # Pre-expire some toggles so they won't show if disabled - $this->mUsedToggles[ 'shownumberswatching' ] = true; - $this->mUsedToggles[ 'showupdated' ] = true; - $this->mUsedToggles[ 'enotifwatchlistpages' ] = true; - $this->mUsedToggles[ 'enotifusertalkpages' ] = true; - $this->mUsedToggles[ 'enotifminoredits' ] = true; - $this->mUsedToggles[ 'enotifrevealaddr' ] = true; - $this->mUsedToggles[ 'ccmeonemails' ] = true; - $this->mUsedToggles[ 'uselivepreview' ] = true; - - - if ( !$this->mEmailFlag ) { $emfc = 'checked="checked"'; } - else { $emfc = ''; } - - - if ($wgEmailAuthentication && ($this->mUserEmail != '') ) { - if( $wgUser->getEmailAuthenticationTimestamp() ) { - $emailauthenticated = wfMsg('emailauthenticated',$wgLang->timeanddate($wgUser->getEmailAuthenticationTimestamp(), true ) ).'<br />'; - $disableEmailPrefs = false; - } else { - $disableEmailPrefs = true; - $skin = $wgUser->getSkin(); - $emailauthenticated = wfMsg('emailnotauthenticated').'<br />' . - $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Confirmemail' ), - wfMsg( 'emailconfirmlink' ) ) . '<br />'; - } - } else { - $emailauthenticated = ''; - $disableEmailPrefs = false; - } - - if ($this->mUserEmail == '') { - $emailauthenticated = wfMsg( 'noemailprefs' ) . '<br />'; - } - - $ps = $this->namespacesCheckboxes(); - - $enotifwatchlistpages = ($wgEnotifWatchlist) ? $this->getToggle( 'enotifwatchlistpages', false, $disableEmailPrefs ) : ''; - $enotifusertalkpages = ($wgEnotifUserTalk) ? $this->getToggle( 'enotifusertalkpages', false, $disableEmailPrefs ) : ''; - $enotifminoredits = ($wgEnotifWatchlist && $wgEnotifMinorEdits) ? $this->getToggle( 'enotifminoredits', false, $disableEmailPrefs ) : ''; - $enotifrevealaddr = (($wgEnotifWatchlist || $wgEnotifUserTalk) && $wgEnotifRevealEditorAddress) ? $this->getToggle( 'enotifrevealaddr', false, $disableEmailPrefs ) : ''; - - # </FIXME> - - $wgOut->addHTML( "<form action=\"$action\" method='post'>" ); - $wgOut->addHTML( "<div id='preferences'>" ); - - # User data - - $wgOut->addHTML( - Xml::openElement( 'fieldset ' ) . - Xml::element( 'legend', null, wfMsg('prefs-personal') ) . - Xml::openElement( 'table' ) . - $this->tableRow( Xml::element( 'h2', null, wfMsg( 'prefs-personal' ) ) ) - ); - - $userInformationHtml = - $this->tableRow( wfMsgHtml( 'username' ), htmlspecialchars( $wgUser->getName() ) ) . - $this->tableRow( wfMsgHtml( 'uid' ), htmlspecialchars( $wgUser->getID() ) ) . - $this->tableRow( - wfMsgHtml( 'prefs-edits' ), - $wgLang->formatNum( User::edits( $wgUser->getId() ) ) - ); - - if( wfRunHooks( 'PreferencesUserInformationPanel', array( $this, &$userInformationHtml ) ) ) { - $wgOut->addHtml( $userInformationHtml ); - } - - if ( $wgAllowRealName ) { - $wgOut->addHTML( - $this->tableRow( - Xml::label( wfMsg('yourrealname'), 'wpRealName' ), - Xml::input( 'wpRealName', 25, $this->mRealName, array( 'id' => 'wpRealName' ) ), - Xml::tags('div', array( 'class' => 'prefsectiontip' ), - wfMsgExt( 'prefs-help-realname', 'parseinline' ) - ) - ) - ); - } - if ( $wgEnableEmail ) { - $wgOut->addHTML( - $this->tableRow( - Xml::label( wfMsg('youremail'), 'wpUserEmail' ), - Xml::input( 'wpUserEmail', 25, $this->mUserEmail, array( 'id' => 'wpUserEmail' ) ), - Xml::tags('div', array( 'class' => 'prefsectiontip' ), - wfMsgExt( $wgEmailConfirmToEdit ? 'prefs-help-email-required' : 'prefs-help-email', 'parseinline' ) - ) - ) - ); - } - - global $wgParser, $wgMaxSigChars; - if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) { - $invalidSig = $this->tableRow( - ' ', - Xml::element( 'span', array( 'class' => 'error' ), - wfMsg( 'badsiglength', $wgLang->formatNum( $wgMaxSigChars ) ) ) - ); - } elseif( !empty( $this->mToggles['fancysig'] ) && - false === $wgParser->validateSig( $this->mNick ) ) { - $invalidSig = $this->tableRow( - ' ', - Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) ) - ); - } else { - $invalidSig = ''; - } - - $wgOut->addHTML( - $this->tableRow( - Xml::label( wfMsg( 'yournick' ), 'wpNick' ), - Xml::input( 'wpNick', 25, $this->mNick, - array( - 'id' => 'wpNick', - // Note: $wgMaxSigChars is enforced in Unicode characters, - // both on the backend and now in the browser. - // Badly-behaved requests may still try to submit - // an overlong string, however. - 'maxlength' => $wgMaxSigChars ) ) - ) . - $invalidSig . - $this->tableRow( ' ', $this->getToggle( 'fancysig' ) ) - ); - - list( $lsLabel, $lsSelect) = Xml::languageSelector( $this->mUserLanguage ); - $wgOut->addHTML( - $this->tableRow( $lsLabel, $lsSelect ) - ); - - /* see if there are multiple language variants to choose from*/ - if(!$wgDisableLangConversion) { - $variants = $wgContLang->getVariants(); - $variantArray = array(); - - $languages = Language::getLanguageNames( true ); - foreach($variants as $v) { - $v = str_replace( '_', '-', strtolower($v)); - if( array_key_exists( $v, $languages ) ) { - // If it doesn't have a name, we'll pretend it doesn't exist - $variantArray[$v] = $languages[$v]; - } - } - - $options = "\n"; - foreach( $variantArray as $code => $name ) { - $selected = ($code == $this->mUserVariant); - $options .= Xml::option( "$code - $name", $code, $selected ) . "\n"; - } - - if(count($variantArray) > 1) { - $wgOut->addHtml( - $this->tableRow( - Xml::label( wfMsg( 'yourvariant' ), 'wpUserVariant' ), - Xml::tags( 'select', - array( 'name' => 'wpUserVariant', 'id' => 'wpUserVariant' ), - $options - ) - ) - ); - } - } - - # Password - if( $wgAuth->allowPasswordChange() ) { - $wgOut->addHTML( - $this->tableRow( Xml::element( 'h2', null, wfMsg( 'changepassword' ) ) ) . - $this->tableRow( - Xml::label( wfMsg( 'oldpassword' ), 'wpOldpass' ), - Xml::password( 'wpOldpass', 25, $this->mOldpass, array( 'id' => 'wpOldpass' ) ) - ) . - $this->tableRow( - Xml::label( wfMsg( 'newpassword' ), 'wpNewpass' ), - Xml::password( 'wpNewpass', 25, $this->mNewpass, array( 'id' => 'wpNewpass' ) ) - ) . - $this->tableRow( - Xml::label( wfMsg( 'retypenew' ), 'wpRetypePass' ), - Xml::password( 'wpRetypePass', 25, $this->mRetypePass, array( 'id' => 'wpRetypePass' ) ) - ) . - Xml::tags( 'tr', null, - Xml::tags( 'td', array( 'colspan' => '2' ), - $this->getToggle( "rememberpassword" ) - ) - ) - ); - } - - # <FIXME> - # Enotif - if ( $wgEnableEmail ) { - - $moreEmail = ''; - if ($wgEnableUserEmail) { - $emf = wfMsg( 'allowemail' ); - $disabled = $disableEmailPrefs ? ' disabled="disabled"' : ''; - $moreEmail = - "<input type='checkbox' $emfc $disabled value='1' name='wpEmailFlag' id='wpEmailFlag' /> <label for='wpEmailFlag'>$emf</label>"; - } - - - $wgOut->addHTML( - $this->tableRow( Xml::element( 'h2', null, wfMsg( 'email' ) ) ) . - $this->tableRow( - $emailauthenticated. - $enotifrevealaddr. - $enotifwatchlistpages. - $enotifusertalkpages. - $enotifminoredits. - $moreEmail. - $this->getToggle( 'ccmeonemails' ) - ) - ); - } - # </FIXME> - - $wgOut->addHTML( - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ) - ); - - - # Quickbar - # - if ($this->mSkin == 'cologneblue' || $this->mSkin == 'standard') { - $wgOut->addHtml( "<fieldset>\n<legend>" . wfMsg( 'qbsettings' ) . "</legend>\n" ); - for ( $i = 0; $i < count( $qbs ); ++$i ) { - if ( $i == $this->mQuickbar ) { $checked = ' checked="checked"'; } - else { $checked = ""; } - $wgOut->addHTML( "<div><label><input type='radio' name='wpQuickbar' value=\"$i\"$checked />{$qbs[$i]}</label></div>\n" ); - } - $wgOut->addHtml( "</fieldset>\n\n" ); - } else { - # Need to output a hidden option even if the relevant skin is not in use, - # otherwise the preference will get reset to 0 on submit - $wgOut->addHtml( wfHidden( 'wpQuickbar', $this->mQuickbar ) ); - } - - # Skin - # - $wgOut->addHTML( "<fieldset>\n<legend>\n" . wfMsg('skin') . "</legend>\n" ); - $mptitle = Title::newMainPage(); - $previewtext = wfMsg('skinpreview'); - # Only show members of Skin::getSkinNames() rather than - # $skinNames (skins is all skin names from Language.php) - $validSkinNames = Skin::getSkinNames(); - # Sort by UI skin name. First though need to update validSkinNames as sometimes - # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI). - foreach ($validSkinNames as $skinkey => & $skinname ) { - if ( isset( $skinNames[$skinkey] ) ) { - $skinname = $skinNames[$skinkey]; - } - } - asort($validSkinNames); - foreach ($validSkinNames as $skinkey => $sn ) { - if ( in_array( $skinkey, $wgSkipSkins ) ) { - continue; - } - $checked = $skinkey == $this->mSkin ? ' checked="checked"' : ''; - - $mplink = htmlspecialchars($mptitle->getLocalURL("useskin=$skinkey")); - $previewlink = "<a target='_blank' href=\"$mplink\">$previewtext</a>"; - if( $skinkey == $wgDefaultSkin ) - $sn .= ' (' . wfMsg( 'default' ) . ')'; - $wgOut->addHTML( "<input type='radio' name='wpSkin' id=\"wpSkin$skinkey\" value=\"$skinkey\"$checked /> <label for=\"wpSkin$skinkey\">{$sn}</label> $previewlink<br />\n" ); - } - $wgOut->addHTML( "</fieldset>\n\n" ); - - # Math - # - global $wgUseTeX; - if( $wgUseTeX ) { - $wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg('math') . '</legend>' ); - foreach ( $mathopts as $k => $v ) { - $checked = ($k == $this->mMath); - $wgOut->addHTML( - Xml::openElement( 'div' ) . - Xml::radioLabel( wfMsg( $v ), 'wpMath', $k, "mw-sp-math-$k", $checked ) . - Xml::closeElement( 'div' ) . "\n" - ); - } - $wgOut->addHTML( "</fieldset>\n\n" ); - } - - # Files - # - $wgOut->addHTML( - "<fieldset>\n" . Xml::element( 'legend', null, wfMsg( 'files' ) ) . "\n" - ); - - $imageLimitOptions = null; - foreach ( $wgImageLimits as $index => $limits ) { - $selected = ($index == $this->mImageSize); - $imageLimitOptions .= Xml::option( "{$limits[0]}×{$limits[1]}" . - wfMsg('unit-pixel'), $index, $selected ); - } - - $imageSizeId = 'wpImageSize'; - $wgOut->addHTML( - "<div>" . Xml::label( wfMsg('imagemaxsize'), $imageSizeId ) . " " . - Xml::openElement( 'select', array( 'name' => $imageSizeId, 'id' => $imageSizeId ) ) . - $imageLimitOptions . - Xml::closeElement( 'select' ) . "</div>\n" - ); - - $imageThumbOptions = null; - foreach ( $wgThumbLimits as $index => $size ) { - $selected = ($index == $this->mThumbSize); - $imageThumbOptions .= Xml::option($size . wfMsg('unit-pixel'), $index, - $selected); - } - - $thumbSizeId = 'wpThumbSize'; - $wgOut->addHTML( - "<div>" . Xml::label( wfMsg('thumbsize'), $thumbSizeId ) . " " . - Xml::openElement( 'select', array( 'name' => $thumbSizeId, 'id' => $thumbSizeId ) ) . - $imageThumbOptions . - Xml::closeElement( 'select' ) . "</div>\n" - ); - - $wgOut->addHTML( "</fieldset>\n\n" ); - - # Date format - # - # Date/Time - # - - $wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg( 'datetime' ) . "</legend>\n" ); - - if ($dateopts) { - $wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg( 'dateformat' ) . "</legend>\n" ); - $idCnt = 0; - $epoch = '20010115161234'; # Wikipedia day - foreach( $dateopts as $key ) { - if( $key == 'default' ) { - $formatted = wfMsgHtml( 'datedefault' ); - } else { - $formatted = htmlspecialchars( $wgLang->timeanddate( $epoch, false, $key ) ); - } - ($key == $this->mDate) ? $checked = ' checked="checked"' : $checked = ''; - $wgOut->addHTML( "<div><input type='radio' name=\"wpDate\" id=\"wpDate$idCnt\" ". - "value=\"$key\"$checked /> <label for=\"wpDate$idCnt\">$formatted</label></div>\n" ); - $idCnt++; - } - $wgOut->addHTML( "</fieldset>\n" ); - } - - $nowlocal = $wgLang->time( $now = wfTimestampNow(), true ); - $nowserver = $wgLang->time( $now, false ); - - $wgOut->addHTML( '<fieldset><legend>' . wfMsg( 'timezonelegend' ). '</legend><table>' . - $this->addRow( wfMsg( 'servertime' ), $nowserver ) . - $this->addRow( wfMsg( 'localtime' ), $nowlocal ) . - $this->addRow( - '<label for="wpHourDiff">' . wfMsg( 'timezoneoffset' ) . '</label>', - "<input type='text' name='wpHourDiff' id='wpHourDiff' value=\"" . htmlspecialchars( $this->mHourDiff ) . "\" size='6' />" - ) . "<tr><td colspan='2'> - <input type='button' value=\"" . wfMsg( 'guesstimezone' ) ."\" - onclick='javascript:guessTimezone()' id='guesstimezonebutton' style='display:none;' /> - </td></tr></table><div class='prefsectiontip'>¹" . wfMsg( 'timezonetext' ) . "</div></fieldset> - </fieldset>\n\n" ); - - # Editing - # - global $wgLivePreview; - $wgOut->addHTML( '<fieldset><legend>' . wfMsg( 'textboxsize' ) . '</legend> - <div>' . - wfInputLabel( wfMsg( 'rows' ), 'wpRows', 'wpRows', 3, $this->mRows ) . - ' ' . - wfInputLabel( wfMsg( 'columns' ), 'wpCols', 'wpCols', 3, $this->mCols ) . - "</div>" . - $this->getToggles( array( - 'editsection', - 'editsectiononrightclick', - 'editondblclick', - 'editwidth', - 'showtoolbar', - 'previewonfirst', - 'previewontop', - 'minordefault', - 'externaleditor', - 'externaldiff', - $wgLivePreview ? 'uselivepreview' : false, - 'forceeditsummary', - ) ) . '</fieldset>' - ); - - # Recent changes - $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'prefs-rc' ) . '</legend>' ); - - $rc = '<table><tr>'; - $rc .= '<td>' . Xml::label( wfMsg( 'recentchangesdays' ), 'wpRecentDays' ) . '</td>'; - $rc .= '<td>' . Xml::input( 'wpRecentDays', 3, $this->mRecentDays, array( 'id' => 'wpRecentDays' ) ) . '</td>'; - $rc .= '</tr><tr>'; - $rc .= '<td>' . Xml::label( wfMsg( 'recentchangescount' ), 'wpRecent' ) . '</td>'; - $rc .= '<td>' . Xml::input( 'wpRecent', 3, $this->mRecent, array( 'id' => 'wpRecent' ) ) . '</td>'; - $rc .= '</tr></table>'; - $wgOut->addHtml( $rc ); - - $wgOut->addHtml( '<br />' ); - - $toggles[] = 'hideminor'; - if( $wgRCShowWatchingUsers ) - $toggles[] = 'shownumberswatching'; - $toggles[] = 'usenewrc'; - $wgOut->addHtml( $this->getToggles( $toggles ) ); - - $wgOut->addHtml( '</fieldset>' ); - - # Watchlist - $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'prefs-watchlist' ) . '</legend>' ); - - $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) ); - $wgOut->addHtml( '<br /><br />' ); - - $wgOut->addHtml( $this->getToggle( 'extendwatchlist' ) ); - $wgOut->addHtml( wfInputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) ); - $wgOut->addHtml( '<br /><br />' ); - - $wgOut->addHtml( $this->getToggles( array( 'watchlisthideown', 'watchlisthidebots', 'watchlisthideminor' ) ) ); - - if( $wgUser->isAllowed( 'createpage' ) || $wgUser->isAllowed( 'createtalk' ) ) - $wgOut->addHtml( $this->getToggle( 'watchcreations' ) ); - foreach( array( 'edit' => 'watchdefault', 'move' => 'watchmoves', 'delete' => 'watchdeletion' ) as $action => $toggle ) { - if( $wgUser->isAllowed( $action ) ) - $wgOut->addHtml( $this->getToggle( $toggle ) ); - } - $this->mUsedToggles['watchcreations'] = true; - $this->mUsedToggles['watchdefault'] = true; - $this->mUsedToggles['watchmoves'] = true; - $this->mUsedToggles['watchdeletion'] = true; - - $wgOut->addHtml( '</fieldset>' ); - - # Search - $ajaxsearch = $wgAjaxSearch ? - $this->addRow( - wfLabel( wfMsg( 'useajaxsearch' ), 'wpUseAjaxSearch' ), - wfCheck( 'wpUseAjaxSearch', $this->mUseAjaxSearch, array( 'id' => 'wpUseAjaxSearch' ) ) - ) : ''; - $wgOut->addHTML( '<fieldset><legend>' . wfMsg( 'searchresultshead' ) . '</legend><table>' . - $ajaxsearch . - $this->addRow( - wfLabel( wfMsg( 'resultsperpage' ), 'wpSearch' ), - wfInput( 'wpSearch', 4, $this->mSearch, array( 'id' => 'wpSearch' ) ) - ) . - $this->addRow( - wfLabel( wfMsg( 'contextlines' ), 'wpSearchLines' ), - wfInput( 'wpSearchLines', 4, $this->mSearchLines, array( 'id' => 'wpSearchLines' ) ) - ) . - $this->addRow( - wfLabel( wfMsg( 'contextchars' ), 'wpSearchChars' ), - wfInput( 'wpSearchChars', 4, $this->mSearchChars, array( 'id' => 'wpSearchChars' ) ) - ) . - "</table><fieldset><legend>" . wfMsg( 'defaultns' ) . "</legend>$ps</fieldset></fieldset>" ); - - # Misc - # - $wgOut->addHTML('<fieldset><legend>' . wfMsg('prefs-misc') . '</legend>'); - $wgOut->addHtml( '<label for="wpStubs">' . wfMsg( 'stub-threshold' ) . '</label> ' ); - $wgOut->addHtml( Xml::input( 'wpStubs', 6, $this->mStubs, array( 'id' => 'wpStubs' ) ) ); - $msgUnderline = htmlspecialchars( wfMsg ( 'tog-underline' ) ); - $msgUnderlinenever = htmlspecialchars( wfMsg ( 'underline-never' ) ); - $msgUnderlinealways = htmlspecialchars( wfMsg ( 'underline-always' ) ); - $msgUnderlinedefault = htmlspecialchars( wfMsg ( 'underline-default' ) ); - $uopt = $wgUser->getOption("underline"); - $s0 = $uopt == 0 ? ' selected="selected"' : ''; - $s1 = $uopt == 1 ? ' selected="selected"' : ''; - $s2 = $uopt == 2 ? ' selected="selected"' : ''; - $wgOut->addHTML(" -<div class='toggle'><p><label for='wpOpunderline'>$msgUnderline</label> -<select name='wpOpunderline' id='wpOpunderline'> -<option value=\"0\"$s0>$msgUnderlinenever</option> -<option value=\"1\"$s1>$msgUnderlinealways</option> -<option value=\"2\"$s2>$msgUnderlinedefault</option> -</select></p></div>"); - - foreach ( $togs as $tname ) { - if( !array_key_exists( $tname, $this->mUsedToggles ) ) { - $wgOut->addHTML( $this->getToggle( $tname ) ); - } - } - $wgOut->addHTML( '</fieldset>' ); - - wfRunHooks( 'RenderPreferencesForm', array( $this, $wgOut ) ); - - $token = htmlspecialchars( $wgUser->editToken() ); - $skin = $wgUser->getSkin(); - $wgOut->addHTML( " - <div id='prefsubmit'> - <div> - <input type='submit' name='wpSaveprefs' class='btnSavePrefs' value=\"" . wfMsgHtml( 'saveprefs' ) . '"'.$skin->tooltipAndAccesskey('save')." /> - <input type='submit' name='wpReset' value=\"" . wfMsgHtml( 'resetprefs' ) . "\" /> - </div> - - </div> - - <input type='hidden' name='wpEditToken' value=\"{$token}\" /> - </div></form>\n" ); - - $wgOut->addHtml( Xml::tags( 'div', array( 'class' => "prefcache" ), - wfMsgExt( 'clearyourcache', 'parseinline' ) ) - ); - - } -} - diff --git a/includes/SpecialPrefixindex.php b/includes/SpecialPrefixindex.php deleted file mode 100644 index bfab21b6..00000000 --- a/includes/SpecialPrefixindex.php +++ /dev/null @@ -1,149 +0,0 @@ -<?php -/** - * @addtogroup SpecialPage - */ - -/** - * Entry point : initialise variables and call subfunctions. - * @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default NULL) - * @param $specialPage SpecialPage object. - */ -function wfSpecialPrefixIndex( $par=NULL, $specialPage ) { - global $wgRequest, $wgOut, $wgContLang; - - # GET values - $from = $wgRequest->getVal( 'from' ); - $prefix = $wgRequest->getVal( 'prefix' ); - $namespace = $wgRequest->getInt( 'namespace' ); - $namespaces = $wgContLang->getNamespaces(); - - $indexPage = new SpecialPrefixIndex(); - - $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) - ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) - : wfMsg( 'allarticles' ) - ); - - if ( isset($par) ) { - $indexPage->showChunk( $namespace, $par, $specialPage->including(), $from ); - } elseif ( isset($prefix) ) { - $indexPage->showChunk( $namespace, $prefix, $specialPage->including(), $from ); - } elseif ( isset($from) ) { - $indexPage->showChunk( $namespace, $from, $specialPage->including(), $from ); - } else { - $wgOut->addHtml($indexPage->namespaceForm ( $namespace, null )); - } -} - -/** - * implements Special:Prefixindex - * @addtogroup SpecialPage - */ -class SpecialPrefixindex extends SpecialAllpages { - // Inherit $maxPerPage - - // Define other properties - protected $name = 'Prefixindex'; - protected $nsfromMsg = 'allpagesprefix'; - -/** - * @param integer $namespace (Default NS_MAIN) - * @param string $from list all pages from this name (default FALSE) - */ -function showChunk( $namespace = NS_MAIN, $prefix, $including = false, $from = null ) { - global $wgOut, $wgUser, $wgContLang; - - $fname = 'indexShowChunk'; - - $sk = $wgUser->getSkin(); - - if (!isset($from)) $from = $prefix; - - $fromList = $this->getNamespaceKeyAndText($namespace, $from); - $prefixList = $this->getNamespaceKeyAndText($namespace, $prefix); - $namespaces = $wgContLang->getNamespaces(); - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - if ( !$prefixList || !$fromList ) { - $out = wfMsgWikiHtml( 'allpagesbadtitle' ); - } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) { - // Show errormessage and reset to NS_MAIN - $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace ); - $namespace = NS_MAIN; - } else { - list( $namespace, $prefixKey, $prefix ) = $prefixList; - list( /* $fromNs */, $fromKey, $from ) = $fromList; - - ### FIXME: should complain if $fromNs != $namespace - - $dbr = wfGetDB( DB_SLAVE ); - - $res = $dbr->select( 'page', - array( 'page_namespace', 'page_title', 'page_is_redirect' ), - array( - 'page_namespace' => $namespace, - 'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'', - 'page_title >= ' . $dbr->addQuotes( $fromKey ), - ), - $fname, - array( - 'ORDER BY' => 'page_title', - 'LIMIT' => $this->maxPerPage + 1, - 'USE INDEX' => 'name_title', - ) - ); - - ### FIXME: side link to previous - - $n = 0; - $out = '<table style="background: inherit;" border="0" width="100%">'; - - while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $t = Title::makeTitle( $s->page_namespace, $s->page_title ); - if( $t ) { - $link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . - $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) . - ($s->page_is_redirect ? '</div>' : '' ); - } else { - $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; - } - if( $n % 3 == 0 ) { - $out .= '<tr>'; - } - $out .= "<td>$link</td>"; - $n++; - if( $n % 3 == 0 ) { - $out .= '</tr>'; - } - } - if( ($n % 3) != 0 ) { - $out .= '</tr>'; - } - $out .= '</table>'; - } - - if ( $including ) { - $out2 = ''; - } else { - $nsForm = $this->namespaceForm ( $namespace, $prefix ); - $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; - $out2 .= '<tr valign="top"><td>' . $nsForm; - $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . - $sk->makeKnownLink( $wgContLang->specialPage( $this->name ), - wfMsg ( 'allpages' ) ); - if ( isset($dbr) && $dbr && ($n == $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) { - $namespaceparam = $namespace ? "&namespace=$namespace" : ""; - $out2 .= " | " . $sk->makeKnownLink( - $wgContLang->specialPage( $this->name ), - wfMsg ( 'nextpage', $s->page_title ), - "from=" . wfUrlEncode ( $s->page_title ) . - "&prefix=" . wfUrlEncode ( $prefix ) . $namespaceparam ); - } - $out2 .= "</td></tr></table><hr />"; - } - - $wgOut->addHtml( $out2 . $out ); -} -} - - diff --git a/includes/SpecialProtectedpages.php b/includes/SpecialProtectedpages.php deleted file mode 100644 index 60a8d602..00000000 --- a/includes/SpecialProtectedpages.php +++ /dev/null @@ -1,287 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * @todo document - * @addtogroup SpecialPage - */ -class ProtectedPagesForm { - - protected $IdLevel = 'level'; - protected $IdType = 'type'; - - function showList( $msg = '' ) { - global $wgOut, $wgRequest; - - $wgOut->setPagetitle( wfMsg( "protectedpages" ) ); - if ( "" != $msg ) { - $wgOut->setSubtitle( $msg ); - } - - // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { - Title::purgeExpiredRestrictions(); - } - - $type = $wgRequest->getVal( $this->IdType ); - $level = $wgRequest->getVal( $this->IdLevel ); - $sizetype = $wgRequest->getVal( 'sizetype' ); - $size = $wgRequest->getIntOrNull( 'size' ); - $NS = $wgRequest->getIntOrNull( 'namespace' ); - - $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size ); - - $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size ) ); - - if ( $pager->getNumRows() ) { - $s = $pager->getNavigationBar(); - $s .= "<ul>" . - $pager->getBody() . - "</ul>"; - $s .= $pager->getNavigationBar(); - } else { - $s = '<p>' . wfMsgHtml( 'protectedpagesempty' ) . '</p>'; - } - $wgOut->addHTML( $s ); - } - - /** - * Callback function to output a restriction - */ - function formatRow( $row ) { - global $wgUser, $wgLang, $wgContLang; - - wfProfileIn( __METHOD__ ); - - static $skin=null; - - if( is_null( $skin ) ) - $skin = $wgUser->getSkin(); - - $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - $link = $skin->makeLinkObj( $title ); - - $description_items = array (); - - $protType = wfMsgHtml( 'restriction-level-' . $row->pr_level ); - - $description_items[] = $protType; - - if ( $row->pr_cascade ) { - $description_items[] = wfMsg( 'protect-summary-cascade' ); - } - - $expiry_description = ''; $stxt = ''; - - if ( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { - $expiry = Block::decodeExpiry( $row->pr_expiry ); - - $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); - - $description_items[] = $expiry_description; - } - - if (!is_null($size = $row->page_len)) { - if ($size == 0) - $stxt = ' <small>' . wfMsgHtml('historyempty') . '</small>'; - else - $stxt = ' <small>' . wfMsgHtml('historysize', $wgLang->formatNum( $size ) ) . '</small>'; - $stxt = $wgContLang->getDirMark() . $stxt; - } - wfProfileOut( __METHOD__ ); - - return '<li>' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . "</li>\n"; - } - - /** - * @param $namespace int - * @param $type string - * @param $level string - * @param $minsize int - * @private - */ - function showOptions( $namespace, $type='edit', $level, $sizetype, $size ) { - global $wgScript; - $action = htmlspecialchars( $wgScript ); - $title = SpecialPage::getTitleFor( 'ProtectedPages' ); - $special = htmlspecialchars( $title->getPrefixedDBkey() ); - return "<form action=\"$action\" method=\"get\">\n" . - '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'protectedpages' ) ) . - Xml::hidden( 'title', $special ) . " \n" . - $this->getNamespaceMenu( $namespace ) . " \n" . - $this->getTypeMenu( $type ) . " \n" . - $this->getLevelMenu( $level ) . "<br/>\n" . - $this->getSizeLimit( $sizetype, $size ) . "\n" . - " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . - "</fieldset></form>"; - } - - /** - * Prepare the namespace filter drop-down; standard namespace - * selector, sans the MediaWiki namespace - * - * @param mixed $namespace Pre-select namespace - * @return string - */ - function getNamespaceMenu( $namespace = null ) { - return Xml::label( wfMsg( 'namespace' ), 'namespace' ) - . ' ' - . Xml::namespaceSelector( $namespace, '' ); - } - - /** - * @return string Formatted HTML - * @private - */ - function getSizeLimit( $sizetype, $size ) { - $out = Xml::radio( 'sizetype', 'min', ($sizetype=='min'), array('id' => 'wpmin') ); - $out .= Xml::label( wfMsg("minimum-size"), 'wpmin' ); - $out .= " ".Xml::radio( 'sizetype', 'max', ($sizetype=='max'), array('id' => 'wpmax') ); - $out .= Xml::label( wfMsg("maximum-size"), 'wpmax' ); - $out .= " ".Xml::input('size', 9, $size, array( 'id' => 'wpsize' ) ); - $out .= ' '.wfMsgHtml('pagesize'); - return $out; - } - - /** - * @return string Formatted HTML - * @private - */ - function getTypeMenu( $pr_type ) { - global $wgRestrictionTypes; - - $m = array(); // Temporary array - $options = array(); - - // First pass to load the log names - foreach( $wgRestrictionTypes as $type ) { - $text = wfMsg("restriction-$type"); - $m[$text] = $type; - } - - // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_type ); - $options[] = Xml::option( $text, $type, $selected ) . "\n"; - } - - return - Xml::label( wfMsg('restriction-type') , $this->IdType ) . ' ' . - Xml::tags( 'select', - array( 'id' => $this->IdType, 'name' => $this->IdType ), - implode( "\n", $options ) ); - } - - /** - * @return string Formatted HTML - * @private - */ - function getLevelMenu( $pr_level ) { - global $wgRestrictionLevels; - - $m = array( wfMsg('restriction-level-all') => 0 ); // Temporary array - $options = array(); - - // First pass to load the log names - foreach( $wgRestrictionLevels as $type ) { - if ( $type !='' && $type !='*') { - $text = wfMsg("restriction-level-$type"); - $m[$text] = $type; - } - } - - // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_level ); - $options[] = Xml::option( $text, $type, $selected ); - } - - return - Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . - Xml::tags( 'select', - array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), - implode( "\n", $options ) ); - } -} - -/** - * @todo document - * @addtogroup Pager - */ -class ProtectedPagesPager extends AlphabeticPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) { - $this->mForm = $form; - $this->mConds = $conds; - $this->type = ( $type ) ? $type : 'edit'; - $this->level = $level; - $this->namespace = $namespace; - $this->sizetype = $sizetype; - $this->size = intval($size); - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $lb = new LinkBatch; - - while ( $row = $this->mResult->fetchObject() ) { - $lb->add( $row->page_namespace, $row->page_title ); - } - - $lb->execute(); - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - return $this->mForm->formatRow( $row ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds[] = 'pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - $conds[] = 'page_id=pr_page'; - $conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type ); - - if( $this->sizetype=='min' ) { - $conds[] = 'page_len>=' . $this->size; - } else if( $this->sizetype=='max' ) { - $conds[] = 'page_len<=' . $this->size; - } - - if( $this->level ) - $conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level ); - if( !is_null($this->namespace) ) - $conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace ); - return array( - 'tables' => array( 'page_restrictions', 'page' ), - 'fields' => 'pr_id,page_namespace,page_title,page_len,pr_type,pr_level,pr_expiry,pr_cascade', - 'conds' => $conds - ); - } - - function getIndexField() { - return 'pr_id'; - } -} - -/** - * Constructor - */ -function wfSpecialProtectedpages() { - - $ppForm = new ProtectedPagesForm(); - - $ppForm->showList(); -} - - - diff --git a/includes/SpecialProtectedtitles.php b/includes/SpecialProtectedtitles.php deleted file mode 100755 index 4bc303bb..00000000 --- a/includes/SpecialProtectedtitles.php +++ /dev/null @@ -1,219 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * @todo document - * @addtogroup SpecialPage - */ -class ProtectedTitlesForm { - - protected $IdLevel = 'level'; - protected $IdType = 'type'; - - function showList( $msg = '' ) { - global $wgOut, $wgRequest; - - $wgOut->setPagetitle( wfMsg( "protectedtitles" ) ); - if ( "" != $msg ) { - $wgOut->setSubtitle( $msg ); - } - - // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { - Title::purgeExpiredRestrictions(); - } - - $type = $wgRequest->getVal( $this->IdType ); - $level = $wgRequest->getVal( $this->IdLevel ); - $sizetype = $wgRequest->getVal( 'sizetype' ); - $size = $wgRequest->getIntOrNull( 'size' ); - $NS = $wgRequest->getIntOrNull( 'namespace' ); - - $pager = new ProtectedTitlesPager( $this, array(), $type, $level, $NS, $sizetype, $size ); - - $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size ) ); - - if ( $pager->getNumRows() ) { - $s = $pager->getNavigationBar(); - $s .= "<ul>" . - $pager->getBody() . - "</ul>"; - $s .= $pager->getNavigationBar(); - } else { - $s = '<p>' . wfMsgHtml( 'protectedtitlesempty' ) . '</p>'; - } - $wgOut->addHTML( $s ); - } - - /** - * Callback function to output a restriction - */ - function formatRow( $row ) { - global $wgUser, $wgLang, $wgContLang; - - wfProfileIn( __METHOD__ ); - - static $skin=null; - - if( is_null( $skin ) ) - $skin = $wgUser->getSkin(); - - $title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title ); - $link = $skin->makeLinkObj( $title ); - - $description_items = array (); - - $protType = wfMsgHtml( 'restriction-level-' . $row->pt_create_perm ); - - $description_items[] = $protType; - - $expiry_description = ''; $stxt = ''; - - if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) { - $expiry = Block::decodeExpiry( $row->pt_expiry ); - - $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); - - $description_items[] = $expiry_description; - } - - wfProfileOut( __METHOD__ ); - - return '<li>' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . "</li>\n"; - } - - /** - * @param $namespace int - * @param $type string - * @param $level string - * @param $minsize int - * @private - */ - function showOptions( $namespace, $type='edit', $level, $sizetype, $size ) { - global $wgScript; - $action = htmlspecialchars( $wgScript ); - $title = SpecialPage::getTitleFor( 'ProtectedTitles' ); - $special = htmlspecialchars( $title->getPrefixedDBkey() ); - return "<form action=\"$action\" method=\"get\">\n" . - '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'protectedtitles' ) ) . - Xml::hidden( 'title', $special ) . " \n" . - $this->getNamespaceMenu( $namespace ) . " \n" . - // $this->getLevelMenu( $level ) . "<br/>\n" . - " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . - "</fieldset></form>"; - } - - /** - * Prepare the namespace filter drop-down; standard namespace - * selector, sans the MediaWiki namespace - * - * @param mixed $namespace Pre-select namespace - * @return string - */ - function getNamespaceMenu( $namespace = null ) { - return Xml::label( wfMsg( 'namespace' ), 'namespace' ) - . ' ' - . Xml::namespaceSelector( $namespace, '' ); - } - - /** - * @return string Formatted HTML - * @private - */ - function getLevelMenu( $pr_level ) { - global $wgRestrictionLevels; - - $m = array( wfMsg('restriction-level-all') => 0 ); // Temporary array - $options = array(); - - // First pass to load the log names - foreach( $wgRestrictionLevels as $type ) { - if ( $type !='' && $type !='*') { - $text = wfMsg("restriction-level-$type"); - $m[$text] = $type; - } - } - - // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_level ); - $options[] = Xml::option( $text, $type, $selected ); - } - - return - Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . - Xml::tags( 'select', - array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), - implode( "\n", $options ) ); - } -} - -/** - * @todo document - * @addtogroup Pager - */ -class ProtectedtitlesPager extends AlphabeticPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) { - $this->mForm = $form; - $this->mConds = $conds; - $this->level = $level; - $this->namespace = $namespace; - $this->size = intval($size); - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $lb = new LinkBatch; - - while ( $row = $this->mResult->fetchObject() ) { - $lb->add( $row->pt_namespace, $row->pt_title ); - } - - $lb->execute(); - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - return $this->mForm->formatRow( $row ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - - if( !is_null($this->namespace) ) - $conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace ); - return array( - 'tables' => 'protected_titles', - 'fields' => 'pt_namespace,pt_title,pt_create_perm,pt_expiry,pt_timestamp', - 'conds' => $conds - ); - } - - function getIndexField() { - return 'pt_timestamp'; - } -} - -/** - * Constructor - */ -function wfSpecialProtectedtitles() { - - $ppForm = new ProtectedTitlesForm(); - - $ppForm->showList(); -} - - - diff --git a/includes/SpecialRandompage.php b/includes/SpecialRandompage.php deleted file mode 100644 index 9f324bd0..00000000 --- a/includes/SpecialRandompage.php +++ /dev/null @@ -1,108 +0,0 @@ -<?php - -/** - * Special page to direct the user to a random page - * - * @addtogroup SpecialPage - * @author Rob Church <robchur@gmail.com>, Ilmari Karonen - * @license GNU General Public Licence 2.0 or later - */ - -/** - * Special page to direct the user to a random page - * - * @addtogroup SpecialPage - */ -class RandomPage extends SpecialPage { - private $namespace = NS_MAIN; // namespace to select pages from - - function __construct( $name = 'Randompage' ){ - parent::__construct( $name ); - } - - public function getNamespace() { - return $this->namespace; - } - - public function setNamespace ( $ns ) { - if( $ns < NS_MAIN ) $ns = NS_MAIN; - $this->namespace = $ns; - } - - // select redirects instead of normal pages? - // Overriden by SpecialRandomredirect - public function isRedirect(){ - return false; - } - - public function execute( $par ) { - global $wgOut, $wgContLang; - - if ($par) - $this->setNamespace( $wgContLang->getNsIndex( $par ) ); - - $title = $this->getRandomTitle(); - - if( is_null( $title ) ) { - $this->setHeaders(); - $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages' ); - return; - } - - $query = $this->isRedirect() ? 'redirect=no' : ''; - $wgOut->redirect( $title->getFullUrl( $query ) ); - } - - - /** - * Choose a random title. - * @return Title object (or null if nothing to choose from) - */ - public function getRandomTitle() { - $randstr = wfRandom(); - $row = $this->selectRandomPageFromDB( $randstr ); - - /* If we picked a value that was higher than any in - * the DB, wrap around and select the page with the - * lowest value instead! One might think this would - * skew the distribution, but in fact it won't cause - * any more bias than what the page_random scheme - * causes anyway. Trust me, I'm a mathematician. :) - */ - if( !$row ) - $row = $this->selectRandomPageFromDB( "0" ); - - if( $row ) - return Title::makeTitleSafe( $this->namespace, $row->page_title ); - else - return null; - } - - private function selectRandomPageFromDB( $randstr ) { - global $wgExtraRandompageSQL; - $fname = 'RandomPage::selectRandomPageFromDB'; - - $dbr = wfGetDB( DB_SLAVE ); - - $use_index = $dbr->useIndexClause( 'page_random' ); - $page = $dbr->tableName( 'page' ); - - $ns = (int) $this->namespace; - $redirect = $this->isRedirect() ? 1 : 0; - - $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : ""; - $sql = "SELECT page_title - FROM $page $use_index - WHERE page_namespace = $ns - AND page_is_redirect = $redirect - AND page_random >= $randstr - $extra - ORDER BY page_random"; - - $sql = $dbr->limitResult( $sql, 1, 0 ); - $res = $dbr->query( $sql, $fname ); - return $dbr->fetchObject( $res ); - } -} - - diff --git a/includes/SpecialRandomredirect.php b/includes/SpecialRandomredirect.php deleted file mode 100644 index ccf5cbcd..00000000 --- a/includes/SpecialRandomredirect.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -/** - * Special page to direct the user to a random redirect page (minus the second redirect) - * - * @addtogroup SpecialPage - * @author Rob Church <robchur@gmail.com>, Ilmari Karonen - * @license GNU General Public Licence 2.0 or later - */ -class SpecialRandomredirect extends RandomPage { - function __construct(){ - parent::__construct( 'Randomredirect' ); - } - - // Override parent::isRedirect() - public function isRedirect(){ - return true; - } -} - diff --git a/includes/SpecialRecentchanges.php b/includes/SpecialRecentchanges.php deleted file mode 100644 index 60a04e00..00000000 --- a/includes/SpecialRecentchanges.php +++ /dev/null @@ -1,730 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -require_once( dirname(__FILE__) . '/ChangesList.php' ); - -/** - * Constructor - */ -function wfSpecialRecentchanges( $par, $specialPage ) { - global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol; - global $wgRCShowWatchingUsers, $wgShowUpdatedMarker; - global $wgAllowCategorizedRecentChanges ; - $fname = 'wfSpecialRecentchanges'; - - # Get query parameters - $feedFormat = $wgRequest->getVal( 'feed' ); - - /* Checkbox values can't be true by default, because - * we cannot differentiate between unset and not set at all - */ - $defaults = array( - /* int */ 'days' => $wgUser->getDefaultOption('rcdays'), - /* int */ 'limit' => $wgUser->getDefaultOption('rclimit'), - /* bool */ 'hideminor' => false, - /* bool */ 'hidebots' => true, - /* bool */ 'hideanons' => false, - /* bool */ 'hideliu' => false, - /* bool */ 'hidepatrolled' => false, - /* bool */ 'hidemyself' => false, - /* text */ 'from' => '', - /* text */ 'namespace' => null, - /* bool */ 'invert' => false, - /* bool */ 'categories_any' => false, - ); - - extract($defaults); - - - $days = $wgUser->getOption( 'rcdays', $defaults['days']); - $days = $wgRequest->getInt( 'days', $days ); - - $limit = $wgUser->getOption( 'rclimit', $defaults['limit'] ); - - # list( $limit, $offset ) = wfCheckLimits( 100, 'rclimit' ); - $limit = $wgRequest->getInt( 'limit', $limit ); - - /* order of selection: url > preferences > default */ - $hideminor = $wgRequest->getBool( 'hideminor', $wgUser->getOption( 'hideminor') ? true : $defaults['hideminor'] ); - - # As a feed, use limited settings only - if( $feedFormat ) { - global $wgFeedLimit; - if( $limit > $wgFeedLimit ) { - $limit = $wgFeedLimit; - } - - } else { - - $namespace = $wgRequest->getIntOrNull( 'namespace' ); - $invert = $wgRequest->getBool( 'invert', $defaults['invert'] ); - $hidebots = $wgRequest->getBool( 'hidebots', $defaults['hidebots'] ); - $hideanons = $wgRequest->getBool( 'hideanons', $defaults['hideanons'] ); - $hideliu = $wgRequest->getBool( 'hideliu', $defaults['hideliu'] ); - $hidepatrolled = $wgRequest->getBool( 'hidepatrolled', $defaults['hidepatrolled'] ); - $hidemyself = $wgRequest->getBool ( 'hidemyself', $defaults['hidemyself'] ); - $from = $wgRequest->getVal( 'from', $defaults['from'] ); - - # Get query parameters from path - if( $par ) { - $bits = preg_split( '/\s*,\s*/', trim( $par ) ); - foreach ( $bits as $bit ) { - if ( 'hidebots' == $bit ) $hidebots = 1; - if ( 'bots' == $bit ) $hidebots = 0; - if ( 'hideminor' == $bit ) $hideminor = 1; - if ( 'minor' == $bit ) $hideminor = 0; - if ( 'hideliu' == $bit ) $hideliu = 1; - if ( 'hidepatrolled' == $bit ) $hidepatrolled = 1; - if ( 'hideanons' == $bit ) $hideanons = 1; - if ( 'hidemyself' == $bit ) $hidemyself = 1; - - if ( is_numeric( $bit ) ) { - $limit = $bit; - } - - $m = array(); - if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { - $limit = $m[1]; - } - - if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { - $days = $m[1]; - } - } - } - } - - if ( $limit < 0 || $limit > 5000 ) $limit = $defaults['limit']; - - - # Database connection and caching - $dbr = wfGetDB( DB_SLAVE ); - list( $recentchanges, $watchlist ) = $dbr->tableNamesN( '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; - } - } - - # It makes no sense to hide both anons and logged-in users - # Where this occurs, force anons to be shown - if( $hideanons && $hideliu ) - $hideanons = false; - - # Form WHERE fragments for all the options - $hidem = $hideminor ? 'AND rc_minor = 0' : ''; - $hidem .= $hidebots ? ' AND rc_bot = 0' : ''; - $hidem .= $hideliu ? ' AND rc_user = 0' : ''; - $hidem .= ( $wgUseRCPatrol && $hidepatrolled ) ? ' AND rc_patrolled = 0' : ''; - $hidem .= $hideanons ? ' AND rc_user != 0' : ''; - - if( $hidemyself ) { - if( $wgUser->getID() ) { - $hidem .= ' AND rc_user != ' . $wgUser->getID(); - } else { - $hidem .= ' AND rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() ); - } - } - - # Namespace filtering - $hidem .= is_null( $namespace ) ? '' : ' AND rc_namespace' . ($invert ? '!=' : '=') . $namespace; - - // This is the big thing! - - $uid = $wgUser->getID(); - - // Perform query - $forceclause = $dbr->useIndexClause("rc_timestamp"); - $sql2 = "SELECT * FROM $recentchanges $forceclause". - ($uid ? "LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . - "WHERE rc_timestamp >= '{$cutoff}' {$hidem} " . - "ORDER BY rc_timestamp DESC"; - $sql2 = $dbr->limitResult($sql2, $limit, 0); - $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; - if ( !$feedFormat ) { - // User page and talk links - $batch->add( NS_USER, $row->rc_user_text ); - $batch->add( NS_USER_TALK, $row->rc_user_text ); - } - - } - $dbr->freeResult( $res ); - - if( $feedFormat ) { - rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ); - } else { - - # Web output... - - // Run existence checks - $batch->execute(); - $any = $wgRequest->getBool( 'categories_any', $defaults['categories_any']); - - // Output header - if ( !$specialPage->including() ) { - $wgOut->addWikiText( wfMsgForContentNoTrans( "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( 'hideanons', $hideanons, $defaults, $nondefaults ); - wfAppendToArrayIfNotDefault( 'hideliu', $hideliu, $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'hidepatrolled', $hidepatrolled, $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'hidemyself', $hidemyself, $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'from', $from, $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'namespace', $namespace, $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'invert', $invert, $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'categories_any', $any, $defaults, $nondefaults); - - // Add end of the texts - $wgOut->addHTML( '<div class="rcoptions">' . rcOptionsPanel( $defaults, $nondefaults ) . "\n" ); - $wgOut->addHTML( rcNamespaceForm( $namespace, $invert, $nondefaults, $any ) . '</div>'."\n"); - } - - // And now for the content - $wgOut->setSyndicated( true ); - - $list = ChangesList::newFromUser( $wgUser ); - - if ( $wgAllowCategorizedRecentChanges ) { - $categories = trim ( $wgRequest->getVal ( 'categories' , "" ) ) ; - $categories = str_replace ( "|" , "\n" , $categories ) ; - $categories = explode ( "\n" , $categories ) ; - rcFilterByCategories ( $rows , $categories , $any ) ; - } - - $s = $list->beginRecentChangesList(); - $counter = 1; - - $showWatcherCount = $wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' ); - $watcherCache = array(); - - 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; - } - - $rc->numberofWatchingusers = 0; // Default - if ($showWatcherCount && $obj->rc_namespace >= 0) { - if (!isset($watcherCache[$obj->rc_namespace][$obj->rc_title])) { - $watcherCache[$obj->rc_namespace][$obj->rc_title] = - $dbr->selectField( 'watchlist', - 'COUNT(*)', - array( - 'wl_namespace' => $obj->rc_namespace, - 'wl_title' => $obj->rc_title, - ), - __METHOD__ . '-watchers' ); - } - $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; - } - $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ) ); - --$limit; - } - } - $s .= $list->endRecentChangesList(); - $wgOut->addHTML( $s ); - } -} - -function rcFilterByCategories ( &$rows , $categories , $any ) { - if( empty( $categories ) ) { - return; - } - - # Filter categories - $cats = array () ; - foreach ( $categories AS $cat ) { - $cat = trim ( $cat ) ; - if ( $cat == "" ) continue ; - $cats[] = $cat ; - } - - # Filter articles - $articles = array () ; - $a2r = array () ; - foreach ( $rows AS $k => $r ) { - $nt = Title::makeTitle( $r->rc_title , $r->rc_namespace ); - $id = $nt->getArticleID() ; - if ( $id == 0 ) continue ; # Page might have been deleted... - if ( !in_array ( $id , $articles ) ) { - $articles[] = $id ; - } - if ( !isset ( $a2r[$id] ) ) { - $a2r[$id] = array() ; - } - $a2r[$id][] = $k ; - } - - # Shortcut? - if ( count ( $articles ) == 0 OR count ( $cats ) == 0 ) - return ; - - # Look up - $c = new Categoryfinder ; - $c->seed ( $articles , $cats , $any ? "OR" : "AND" ) ; - $match = $c->run () ; - - # Filter - $newrows = array () ; - foreach ( $match AS $id ) { - foreach ( $a2r[$id] AS $rev ) { - $k = $rev ; - $newrows[$k] = $rows[$k] ; - } - } - $rows = $newrows ; -} - -function rcOutputFeed( $rows, $feedFormat, $limit, $hideminor, $lastmod ) { - global $messageMemc, $wgFeedCacheTimeout; - global $wgFeedClasses, $wgTitle, $wgSitename, $wgContLanguageCode; - - if( !isset( $wgFeedClasses[$feedFormat] ) ) { - wfHttpError( 500, "Internal Server Error", "Unsupported feed type." ); - return false; - } - - $timekey = wfMemcKey( 'rcfeed', $feedFormat, 'timestamp' ); - $key = wfMemcKey( 'rcfeed', $feedFormat, 'limit', $limit, 'minor', $hideminor ); - - $feedTitle = $wgSitename . ' - ' . wfMsgForContent( 'recentchanges' ) . - ' [' . $wgContLanguageCode . ']'; - $feed = new $wgFeedClasses[$feedFormat]( - $feedTitle, - htmlspecialchars( wfMsgForContent( 'recentchanges-feed-description' ) ), - $wgTitle->getFullUrl() ); - - //purge cache if requested - global $wgRequest, $wgUser; - $purge = $wgRequest->getVal( 'action' ) == 'purge'; - if ( $purge && $wgUser->isAllowed('purge') ) { - $messageMemc->delete( $timekey ); - $messageMemc->delete( $key ); - } - - /** - * Bumping around loading up diffs can be pretty slow, so where - * possible we want to cache the feed output so the next visitor - * gets it quick too. - */ - $cachedFeed = false; - if( ( $wgFeedCacheTimeout > 0 ) && ( $feedLastmod = $messageMemc->get( $timekey ) ) ) { - /** - * If the cached feed was rendered very recently, we may - * go ahead and use it even if there have been edits made - * since it was rendered. This keeps a swarm of requests - * from being too bad on a super-frequently edited wiki. - */ - if( time() - wfTimestamp( TS_UNIX, $feedLastmod ) - < $wgFeedCacheTimeout - || wfTimestamp( TS_UNIX, $feedLastmod ) - > wfTimestamp( TS_UNIX, $lastmod ) ) { - wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" ); - $cachedFeed = $messageMemc->get( $key ); - } else { - wfDebug( "RC: cached feed timestamp check failed ($feedLastmod; $lastmod)\n" ); - } - } - if( is_string( $cachedFeed ) ) { - wfDebug( "RC: Outputting cached feed\n" ); - $feed->httpHeaders(); - echo $cachedFeed; - } else { - wfDebug( "RC: rendering new feed and caching it\n" ); - ob_start(); - rcDoOutputFeed( $rows, $feed ); - $cachedFeed = ob_get_contents(); - ob_end_flush(); - - $expire = 3600 * 24; # One day - $messageMemc->set( $key, $cachedFeed ); - $messageMemc->set( $timekey, wfTimestamp( TS_MW ), $expire ); - } - return true; -} - -/** - * @todo document - * @param $rows Database resource with recentchanges rows - */ -function rcDoOutputFeed( $rows, &$feed ) { - wfProfileIn( __METHOD__ ); - - $feed->outHeader(); - - # Merge adjacent edits by one user - $sorted = array(); - $n = 0; - foreach( $rows as $obj ) { - if( $n > 0 && - $obj->rc_namespace >= 0 && - $obj->rc_cur_id == $sorted[$n-1]->rc_cur_id && - $obj->rc_user_text == $sorted[$n-1]->rc_user_text ) { - $sorted[$n-1]->rc_last_oldid = $obj->rc_last_oldid; - } else { - $sorted[$n] = $obj; - $n++; - } - } - - foreach( $sorted as $obj ) { - $title = Title::makeTitle( $obj->rc_namespace, $obj->rc_title ); - $talkpage = $title->getTalkPage(); - $item = new FeedItem( - $title->getPrefixedText(), - rcFormatDiff( $obj ), - $title->getFullURL( 'diff=' . $obj->rc_this_oldid . '&oldid=prev' ), - $obj->rc_timestamp, - $obj->rc_user_text, - $talkpage->getFullURL() - ); - $feed->outItem( $item ); - } - $feed->outFooter(); - wfProfileOut( __METHOD__ ); -} - -/** - * - */ -function rcCountLink( $lim, $d, $page='Recentchanges', $more='' ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ), - ($lim ? $wgLang->formatNum( "{$lim}" ) : wfMsg( 'recentchangesall' ) ), "{$more}" . - ($d ? "days={$d}&" : '') . 'limit='.$lim ); - return $s; -} - -/** - * - */ -function rcDaysLink( $lim, $d, $page='Recentchanges', $more='' ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ), - ($d ? $wgLang->formatNum( "{$d}" ) : wfMsg( 'recentchangesall' ) ), $more.'days='.$d . - ($lim ? '&limit='.$lim : '') ); - return $s; -} - -/** - * Used by Recentchangeslinked - */ -function rcDayLimitLinks( $days, $limit, $page='Recentchanges', $more='', $doall = false, $minorLink = '', - $botLink = '', $liuLink = '', $patrLink = '', $myselfLink = '' ) { - if ($more != '') $more .= '&'; - $cl = rcCountLink( 50, $days, $page, $more ) . ' | ' . - rcCountLink( 100, $days, $page, $more ) . ' | ' . - rcCountLink( 250, $days, $page, $more ) . ' | ' . - rcCountLink( 500, $days, $page, $more ) . - ( $doall ? ( ' | ' . rcCountLink( 0, $days, $page, $more ) ) : '' ); - $dl = rcDaysLink( $limit, 1, $page, $more ) . ' | ' . - rcDaysLink( $limit, 3, $page, $more ) . ' | ' . - rcDaysLink( $limit, 7, $page, $more ) . ' | ' . - rcDaysLink( $limit, 14, $page, $more ) . ' | ' . - rcDaysLink( $limit, 30, $page, $more ) . - ( $doall ? ( ' | ' . rcDaysLink( $limit, 0, $page, $more ) ) : '' ); - - $linkParts = array( 'minorLink' => 'minor', 'botLink' => 'bots', 'liuLink' => 'liu', 'patrLink' => 'patr', 'myselfLink' => 'mine' ); - foreach( $linkParts as $linkVar => $linkMsg ) { - if( $$linkVar != '' ) - $links[] = wfMsgHtml( 'rcshowhide' . $linkMsg, $$linkVar ); - } - - $shm = implode( ' | ', $links ); - $note = wfMsg( 'rclinks', $cl, $dl, $shm ); - return $note; -} - - -/** - * Makes change an option link which carries all the other options - * @param $title see Title - * @param $override - * @param $options - */ -function makeOptionsLink( $title, $override, $options ) { - global $wgUser, $wgContLang; - $sk = $wgUser->getSkin(); - return $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchanges' ), - htmlspecialchars( $title ), wfArrayToCGI( $override, $options ) ); -} - -/** - * Creates the options panel. - * @param $defaults - * @param $nondefaults - */ -function rcOptionsPanel( $defaults, $nondefaults ) { - global $wgLang, $wgUseRCPatrol; - - $options = $nondefaults + $defaults; - - if( $options['from'] ) - $note = wfMsgExt( 'rcnotefrom', array( 'parseinline' ), - $wgLang->formatNum( $options['limit'] ), - $wgLang->timeanddate( $options['from'], true ) ); - else - $note = wfMsgExt( 'rcnote', array( 'parseinline' ), - $wgLang->formatNum( $options['limit'] ), - $wgLang->formatNum( $options['days'] ), - $wgLang->timeAndDate( wfTimestampNow(), true ) ); - - // limit links - $options_limit = array(50, 100, 250, 500); - foreach( $options_limit as $value ) { - $cl[] = makeOptionsLink( $wgLang->formatNum( $value ), - array( 'limit' => $value ), $nondefaults) ; - } - $cl = implode( ' | ', $cl); - - // day links, reset 'from' to none - $options_days = array(1, 3, 7, 14, 30); - foreach( $options_days as $value ) { - $dl[] = makeOptionsLink( $wgLang->formatNum( $value ), - array( 'days' => $value, 'from' => '' ), $nondefaults) ; - } - $dl = implode( ' | ', $dl); - - - // show/hide links - $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' )); - $minorLink = makeOptionsLink( $showhide[1-$options['hideminor']], - array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults); - $botLink = makeOptionsLink( $showhide[1-$options['hidebots']], - array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults); - $anonsLink = makeOptionsLink( $showhide[ 1 - $options['hideanons'] ], - array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults ); - $liuLink = makeOptionsLink( $showhide[1-$options['hideliu']], - array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults); - $patrLink = makeOptionsLink( $showhide[1-$options['hidepatrolled']], - array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults); - $myselfLink = makeOptionsLink( $showhide[1-$options['hidemyself']], - array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults); - - $links[] = wfMsgHtml( 'rcshowhideminor', $minorLink ); - $links[] = wfMsgHtml( 'rcshowhidebots', $botLink ); - $links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink ); - $links[] = wfMsgHtml( 'rcshowhideliu', $liuLink ); - if( $wgUseRCPatrol ) - $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink ); - $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink ); - $hl = implode( ' | ', $links ); - - // show from this onward link - $now = $wgLang->timeanddate( wfTimestampNow(), true ); - $tl = makeOptionsLink( $now, array( 'from' => wfTimestampNow()), $nondefaults ); - - $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter'), - $cl, $dl, $hl ); - $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter'), $tl ); - return "$note<br />$rclinks<br />$rclistfrom"; - -} - -/** - * Creates the choose namespace selection - * - * @private - * - * @param $namespace Mixed: the key of the currently selected namespace, empty string - * if there is none - * @param $invert Bool: whether to invert the namespace selection - * @param $nondefaults Array: an array of non default options to be remembered - * @param $categories_any Bool: Default value for the checkbox - * - * @return string - */ -function rcNamespaceForm( $namespace, $invert, $nondefaults, $categories_any ) { - global $wgScript, $wgAllowCategorizedRecentChanges, $wgRequest; - $t = SpecialPage::getTitleFor( 'Recentchanges' ); - - $namespaceselect = HTMLnamespaceselector($namespace, ''); - $submitbutton = '<input type="submit" value="' . wfMsgHtml( 'allpagessubmit' ) . "\" />\n"; - $invertbox = "<input type='checkbox' name='invert' value='1' id='nsinvert'" . ( $invert ? ' checked="checked"' : '' ) . ' />'; - - if ( $wgAllowCategorizedRecentChanges ) { - $categories = trim ( $wgRequest->getVal ( 'categories' , "" ) ) ; - $cb_arr = array( 'type' => 'checkbox', 'name' => 'categories_any', 'value' => "1" ) ; - if ( $categories_any ) $cb_arr['checked'] = "checked" ; - $catbox = "<br />" ; - $catbox .= wfMsgExt('rc_categories', array('parseinline')) . " "; - $catbox .= wfElement('input', array( 'type' => 'text', 'name' => 'categories', 'value' => $categories)); - $catbox .= " " ; - $catbox .= wfElement('input', $cb_arr ); - $catbox .= wfMsgExt('rc_categories_any', array('parseinline')); - } else { - $catbox = "" ; - } - - $out = "<div class='namespacesettings'><form method='get' action='{$wgScript}'>\n"; - - foreach ( $nondefaults as $key => $value ) { - if ($key != 'namespace' && $key != 'invert') - $out .= wfElement('input', array( 'type' => 'hidden', 'name' => $key, 'value' => $value)); - } - - $out .= '<input type="hidden" name="title" value="'.$t->getPrefixedText().'" />'; - $out .= " -<div id='nsselect' class='recentchanges'> - <label for='namespace'>" . wfMsgHtml('namespace') . "</label> - {$namespaceselect}{$submitbutton}{$invertbox} <label for='nsinvert'>" . wfMsgHtml('invert') . "</label>{$catbox}\n</div>"; - $out .= '</form></div>'; - return $out; -} - - -/** - * Format a diff for the newsfeed - */ -function rcFormatDiff( $row ) { - $titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title ); - $timestamp = wfTimestamp( TS_MW, $row->rc_timestamp ); - return rcFormatDiffRow( $titleObj, - $row->rc_last_oldid, $row->rc_this_oldid, - $timestamp, - $row->rc_comment ); -} - -function rcFormatDiffRow( $title, $oldid, $newid, $timestamp, $comment ) { - global $wgFeedDiffCutoff, $wgContLang, $wgUser; - $fname = 'rcFormatDiff'; - wfProfileIn( $fname ); - - $skin = $wgUser->getSkin(); - $completeText = '<p>' . $skin->formatComment( $comment ) . "</p>\n"; - - //NOTE: Check permissions for anonymous users, not current user. - // No "privileged" version should end up in the cache. - // Most feed readers will not log in anway. - $anon = new User(); - $accErrors = $title->getUserPermissionsErrors( 'read', $anon, true ); - - if( $title->getNamespace() >= 0 && !$accErrors ) { - if( $oldid ) { - wfProfileIn( "$fname-dodiff" ); - - $de = new DifferenceEngine( $title, $oldid, $newid ); - #$diffText = $de->getDiff( wfMsg( 'revisionasof', - # $wgContLang->timeanddate( $timestamp ) ), - # wfMsg( 'currentrev' ) ); - $diffText = $de->getDiff( - wfMsg( 'previousrevision' ), // hack - wfMsg( 'revisionasof', - $wgContLang->timeanddate( $timestamp ) ) ); - - - if ( strlen( $diffText ) > $wgFeedDiffCutoff ) { - // Omit large diffs - $diffLink = $title->escapeFullUrl( - 'diff=' . $newid . - '&oldid=' . $oldid ); - $diffText = '<a href="' . - $diffLink . - '">' . - htmlspecialchars( wfMsgForContent( 'difference' ) ) . - '</a>'; - } elseif ( $diffText === false ) { - // Error in diff engine, probably a missing revision - $diffText = "<p>Can't load revision $newid</p>"; - } else { - // Diff output fine, clean up any illegal UTF-8 - $diffText = UtfNormal::cleanUp( $diffText ); - $diffText = rcApplyDiffStyle( $diffText ); - } - wfProfileOut( "$fname-dodiff" ); - } else { - $rev = Revision::newFromId( $newid ); - if( is_null( $rev ) ) { - $newtext = ''; - } else { - $newtext = $rev->getText(); - } - $diffText = '<p><b>' . wfMsg( 'newpage' ) . '</b></p>' . - '<div>' . nl2br( htmlspecialchars( $newtext ) ) . '</div>'; - } - $completeText .= $diffText; - } - - wfProfileOut( $fname ); - return $completeText; -} - -/** - * Hacky application of diff styles for the feeds. - * Might be 'cleaner' to use DOM or XSLT or something, - * but *gack* it's a pain in the ass. - * - * @param $text String: - * @return string - * @private - */ -function rcApplyDiffStyle( $text ) { - $styles = array( - 'diff' => 'background-color: white; color:black;', - 'diff-otitle' => 'background-color: white; color:black;', - 'diff-ntitle' => 'background-color: white; color:black;', - 'diff-addedline' => 'background: #cfc; color:black; font-size: smaller;', - 'diff-deletedline' => 'background: #ffa; color:black; font-size: smaller;', - 'diff-context' => 'background: #eee; color:black; font-size: smaller;', - 'diffchange' => 'color: red; font-weight: bold; text-decoration: none;', - ); - - foreach( $styles as $class => $style ) { - $text = preg_replace( "/(<[^>]+)class=(['\"])$class\\2([^>]*>)/", - "\\1style=\"$style\"\\3", $text ); - } - - return $text; -} - - diff --git a/includes/SpecialRecentchangeslinked.php b/includes/SpecialRecentchangeslinked.php deleted file mode 100644 index bc6bbf4a..00000000 --- a/includes/SpecialRecentchangeslinked.php +++ /dev/null @@ -1,190 +0,0 @@ -<?php -/** - * This is to display changes made to all articles linked in an article. - * @addtogroup SpecialPage - */ - -/** - * - */ -require_once( 'SpecialRecentchanges.php' ); - -/** - * Entrypoint - * @param string $par parent page we will look at - */ -function wfSpecialRecentchangeslinked( $par = NULL ) { - global $wgUser, $wgOut, $wgLang, $wgContLang, $wgRequest, $wgTitle; - $fname = 'wfSpecialRecentchangeslinked'; - - $days = $wgRequest->getInt( 'days' ); - $target = isset($par) ? $par : $wgRequest->getText( 'target' ); - $hideminor = $wgRequest->getBool( 'hideminor' ) ? 1 : 0; - - $wgOut->setPagetitle( wfMsg( 'recentchangeslinked' ) ); - $sk = $wgUser->getSkin(); - - if (is_null($target)) { - $wgOut->errorpage( 'notargettitle', 'notargettext' ); - return; - } - $nt = Title::newFromURL( $target ); - if( !$nt ) { - $wgOut->errorpage( 'notargettitle', 'notargettext' ); - return; - } - $id = $nt->getArticleId(); - - $wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $nt->getPrefixedText() ) ); - $wgOut->setSyndicated(); - $wgOut->setFeedAppendQuery( "target=" . urlencode( $target ) ); - - if ( ! $days ) { - $days = (int)$wgUser->getOption( 'rcdays', 7 ); - } - list( $limit, /* offset */ ) = wfCheckLimits( 100, 'rclimit' ); - - $dbr = wfGetDB( DB_SLAVE,'recentchangeslinked' ); - $cutoff = $dbr->timestamp( time() - ( $days * 86400 ) ); - - $hideminor = ($hideminor ? 1 : 0); - if ( $hideminor ) { - $mlink = $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchangeslinked' ), - wfMsg( 'show' ), 'target=' . htmlspecialchars( $nt->getPrefixedURL() ) . - "&days={$days}&limit={$limit}&hideminor=0" ); - } else { - $mlink = $sk->makeKnownLink( $wgContLang->specialPage( "Recentchangeslinked" ), - wfMsg( "hide" ), "target=" . htmlspecialchars( $nt->getPrefixedURL() ) . - "&days={$days}&limit={$limit}&hideminor=1" ); - } - if ( $hideminor ) { - $cmq = 'AND rc_minor=0'; - } else { $cmq = ''; } - - list($recentchanges, $categorylinks, $pagelinks, $watchlist) = - $dbr->tableNamesN( 'recentchanges', 'categorylinks', 'pagelinks', "watchlist" ); - - $uid = $wgUser->getID(); - - $GROUPBY = " - GROUP BY rc_cur_id,rc_namespace,rc_title, - rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,rc_deleted, - rc_new, rc_id, rc_this_oldid, rc_last_oldid, rc_bot, rc_patrolled, rc_type, rc_old_len, rc_new_len -" . ($uid ? ",wl_user" : "") . " - ORDER BY rc_timestamp DESC - LIMIT {$limit}"; - - // If target is a Category, use categorylinks and invert from and to - if( $nt->getNamespace() == NS_CATEGORY ) { - $catkey = $dbr->addQuotes( $nt->getDBkey() ); - $sql = "SELECT /* wfSpecialRecentchangeslinked */ - rc_id, - rc_cur_id, - rc_namespace, - rc_title, - rc_this_oldid, - rc_last_oldid, - rc_user, - rc_comment, - rc_user_text, - rc_timestamp, - rc_minor, - rc_bot, - rc_new, - rc_patrolled, - rc_type, - rc_old_len, - rc_new_len, - rc_deleted -" . ($uid ? ",wl_user" : "") . " - FROM $categorylinks, $recentchanges -" . ($uid ? "LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . " - WHERE rc_timestamp > '{$cutoff}' - {$cmq} - AND cl_from=rc_cur_id - AND cl_to=$catkey -$GROUPBY - "; - } else { - $sql = -"SELECT /* wfSpecialRecentchangeslinked */ - rc_id, - rc_cur_id, - rc_namespace, - rc_title, - rc_user, - rc_comment, - rc_user_text, - rc_this_oldid, - rc_last_oldid, - rc_timestamp, - rc_minor, - rc_bot, - rc_new, - rc_patrolled, - rc_type, - rc_old_len, - rc_new_len, - rc_deleted -" . ($uid ? ",wl_user" : "") . " - FROM $pagelinks, $recentchanges -" . ($uid ? " LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . " - WHERE rc_timestamp > '{$cutoff}' - {$cmq} - AND pl_namespace=rc_namespace - AND pl_title=rc_title - AND pl_from=$id -$GROUPBY -"; - } - $res = $dbr->query( $sql, $fname ); - - $wgOut->addHTML("< ".$sk->makeLinkObj($nt, "", "redirect=no" )."<br />\n"); - $note = wfMsgExt( "rcnote", array ( 'parseinline' ), $limit, $days, $wgLang->timeAndDate( wfTimestampNow(), true ) ); - $wgOut->addHTML( "<hr />\n{$note}\n<br />" ); - - $note = rcDayLimitlinks( $days, $limit, "Recentchangeslinked", - "target=" . $nt->getPrefixedURL() . "&hideminor={$hideminor}", - false, $mlink ); - - $wgOut->addHTML( $note."\n" ); - - $list = ChangesList::newFromUser( $wgUser ); - $s = $list->beginRecentChangesList(); - $count = $dbr->numRows( $res ); - - $rchanges = array(); - if ( $count ) { - $counter = 1; - while ( $limit ) { - if ( 0 == $count ) { break; } - $obj = $dbr->fetchObject( $res ); - --$count; - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - $s .= $list->recentChangesLine( $rc , !empty( $obj->wl_user) ); - --$limit; - $rchanges[] = $obj; - } - } else { - $wgOut->addWikiMsg('recentchangeslinked-noresult'); - } - $s .= $list->endRecentChangesList(); - - $dbr->freeResult( $res ); - $wgOut->addHTML( $s ); - - global $wgSitename, $wgFeedClasses, $wgContLanguageCode; - $feedFormat = $wgRequest->getVal( 'feed' ); - if( $feedFormat && isset( $wgFeedClasses[$feedFormat] ) ) { - $feedTitle = $wgSitename . ' - ' . wfMsgForContent( 'recentchangeslinked-title', $nt->getPrefixedText() ) . ' [' . $wgContLanguageCode . ']'; - $feed = new $wgFeedClasses[$feedFormat]( $feedTitle, - htmlspecialchars( wfMsgForContent('recentchangeslinked') ), $wgTitle->getFullUrl() ); - - require_once( "SpecialRecentchanges.php" ); - $wgOut->disable(); - rcDoOutputFeed( $rchanges, $feed ); - } -} - - diff --git a/includes/SpecialResetpass.php b/includes/SpecialResetpass.php deleted file mode 100644 index 2ecd15b0..00000000 --- a/includes/SpecialResetpass.php +++ /dev/null @@ -1,165 +0,0 @@ -<?php - -/** Constructor */ -function wfSpecialResetpass( $par ) { - $form = new PasswordResetForm(); - $form->execute( $par ); -} - -/** - * Let users recover their password. - * @addtogroup SpecialPage - */ -class PasswordResetForm extends SpecialPage { - function __construct( $name=null, $reset=null ) { - if( $name !== null ) { - $this->mName = $name; - $this->mTemporaryPassword = $reset; - } else { - global $wgRequest; - $this->mName = $wgRequest->getVal( 'wpName' ); - $this->mTemporaryPassword = $wgRequest->getVal( 'wpPassword' ); - } - } - - /** - * Main execution point - */ - function execute( $par ) { - global $wgUser, $wgAuth, $wgOut, $wgRequest; - - if( !$wgAuth->allowPasswordChange() ) { - $this->error( wfMsg( 'resetpass_forbidden' ) ); - return; - } - - if( $this->mName === null && !$wgRequest->wasPosted() ) { - $this->error( wfMsg( 'resetpass_missing' ) ); - return; - } - - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) { - $newpass = $wgRequest->getVal( 'wpNewPassword' ); - $retype = $wgRequest->getVal( 'wpRetype' ); - try { - $this->attemptReset( $newpass, $retype ); - $wgOut->addWikiMsg( 'resetpass_success' ); - - $data = array( - 'action' => 'submitlogin', - 'wpName' => $this->mName, - 'wpPassword' => $newpass, - 'returnto' => $wgRequest->getVal( 'returnto' ), - ); - if( $wgRequest->getCheck( 'wpRemember' ) ) { - $data['wpRemember'] = 1; - } - $login = new LoginForm( new FauxRequest( $data, true ) ); - $login->execute(); - - return; - } catch( PasswordError $e ) { - $this->error( $e->getMessage() ); - } - } - $this->showForm(); - } - - function error( $msg ) { - global $wgOut; - $wgOut->addHtml( '<div class="errorbox">' . - htmlspecialchars( $msg ) . - '</div>' ); - } - - function showForm() { - global $wgOut, $wgUser, $wgRequest; - - $wgOut->disallowUserJs(); - - $self = SpecialPage::getTitleFor( 'Resetpass' ); - $form = - '<div id="userloginForm">' . - wfOpenElement( 'form', - array( - 'method' => 'post', - 'action' => $self->getLocalUrl() ) ) . - '<h2>' . wfMsgHtml( 'resetpass_header' ) . '</h2>' . - '<div id="userloginprompt">' . - wfMsgExt( 'resetpass_text', array( 'parse' ) ) . - '</div>' . - '<table>' . - wfHidden( 'token', $wgUser->editToken() ) . - wfHidden( 'wpName', $this->mName ) . - wfHidden( 'wpPassword', $this->mTemporaryPassword ) . - wfHidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . - $this->pretty( array( - array( 'wpName', 'username', 'text', $this->mName ), - array( 'wpNewPassword', 'newpassword', 'password', '' ), - array( 'wpRetype', 'yourpasswordagain', 'password', '' ), - ) ) . - '<tr>' . - '<td></td>' . - '<td>' . - Xml::checkLabel( wfMsg( 'remembermypassword' ), - 'wpRemember', 'wpRemember', - $wgRequest->getCheck( 'wpRemember' ) ) . - '</td>' . - '</tr>' . - '<tr>' . - '<td></td>' . - '<td>' . - wfSubmitButton( wfMsgHtml( 'resetpass_submit' ) ) . - '</td>' . - '</tr>' . - '</table>' . - wfCloseElement( 'form' ) . - '</div>'; - $wgOut->addHtml( $form ); - } - - function pretty( $fields ) { - $out = ''; - foreach( $fields as $list ) { - list( $name, $label, $type, $value ) = $list; - if( $type == 'text' ) { - $field = '<tt>' . htmlspecialchars( $value ) . '</tt>'; - } else { - $field = Xml::input( $name, 20, $value, - array( 'id' => $name, 'type' => $type ) ); - } - $out .= '<tr>'; - $out .= '<td align="right">'; - $out .= Xml::label( wfMsg( $label ), $name ); - $out .= '</td>'; - $out .= '<td>'; - $out .= $field; - $out .= '</td>'; - $out .= '</tr>'; - } - return $out; - } - - /** - * @throws PasswordError when cannot set the new password because requirements not met. - */ - function attemptReset( $newpass, $retype ) { - $user = User::newFromName( $this->mName ); - if( $user->isAnon() ) { - throw new PasswordError( 'no such user' ); - } - - if( !$user->checkTemporaryPassword( $this->mTemporaryPassword ) ) { - throw new PasswordError( wfMsg( 'resetpass_bad_temporary' ) ); - } - - if( $newpass !== $retype ) { - throw new PasswordError( wfMsg( 'badretype' ) ); - } - - $user->setPassword( $newpass ); - $user->saveSettings(); - } -} - - diff --git a/includes/SpecialRevisiondelete.php b/includes/SpecialRevisiondelete.php deleted file mode 100644 index b6ca7e14..00000000 --- a/includes/SpecialRevisiondelete.php +++ /dev/null @@ -1,275 +0,0 @@ -<?php - -/** - * Not quite ready for production use yet; need to fix up the restricted mode, - * and provide for preservation across delete/undelete of the page. - * - * To try this out, set up extra permissions something like: - * $wgGroupPermissions['sysop']['deleterevision'] = true; - * $wgGroupPermissions['bureaucrat']['hiderevision'] = true; - */ - -function wfSpecialRevisiondelete( $par = null ) { - global $wgOut, $wgRequest; - - $target = $wgRequest->getVal( 'target' ); - $oldid = $wgRequest->getIntArray( 'oldid' ); - - $page = Title::newFromUrl( $target ); - - if( is_null( $page ) ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; - } - - if( is_null( $oldid ) ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - - $form = new RevisionDeleteForm( $wgRequest ); - if( $wgRequest->wasPosted() ) { - $form->submit( $wgRequest ); - } else { - $form->show( $wgRequest ); - } -} - -/** - * Implements the GUI for Revision Deletion. - * @addtogroup SpecialPage - */ -class RevisionDeleteForm { - /** - * @param Title $page - * @param int $oldid - */ - function __construct( $request ) { - global $wgUser; - - $target = $request->getVal( 'target' ); - $this->page = Title::newFromUrl( $target ); - - $this->revisions = $request->getIntArray( 'oldid', array() ); - - $this->skin = $wgUser->getSkin(); - $this->checks = array( - array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ), - array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ), - array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ), - array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED ) ); - } - - /** - * @param WebRequest $request - */ - function show( $request ) { - global $wgOut, $wgUser; - - $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText() ); - - $wgOut->addHtml( "<ul>" ); - foreach( $this->revisions as $revid ) { - $rev = Revision::newFromTitle( $this->page, $revid ); - if( !isset( $rev ) ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - $wgOut->addHtml( $this->historyLine( $rev ) ); - $bitfields[] = $rev->mDeleted; // FIXME - } - $wgOut->addHtml( "</ul>" ); - - $wgOut->addWikiMsg( 'revdelete-text' ); - - $items = array( - wfInputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), - wfSubmitButton( wfMsg( 'revdelete-submit' ) ) ); - $hidden = array( - wfHidden( 'wpEditToken', $wgUser->editToken() ), - wfHidden( 'target', $this->page->getPrefixedText() ) ); - foreach( $this->revisions as $revid ) { - $hidden[] = wfHidden( 'oldid[]', $revid ); - } - - $special = SpecialPage::getTitleFor( 'Revisiondelete' ); - $wgOut->addHtml( wfElement( 'form', array( - 'method' => 'post', - 'action' => $special->getLocalUrl( 'action=submit' ) ), - null ) ); - - $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' ); - foreach( $this->checks as $item ) { - list( $message, $name, $field ) = $item; - $wgOut->addHtml( '<div>' . - wfCheckLabel( wfMsg( $message), $name, $name, $rev->isDeleted( $field ) ) . - '</div>' ); - } - $wgOut->addHtml( '</fieldset>' ); - foreach( $items as $item ) { - $wgOut->addHtml( '<p>' . $item . '</p>' ); - } - foreach( $hidden as $item ) { - $wgOut->addHtml( $item ); - } - - $wgOut->addHtml( '</form>' ); - } - - /** - * @param Revision $rev - * @returns string - */ - function historyLine( $rev ) { - global $wgContLang; - $date = $wgContLang->timeanddate( $rev->getTimestamp() ); - return - "<li>" . - $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ) . - " " . - $this->skin->revUserLink( $rev ) . - " " . - $this->skin->revComment( $rev ) . - "</li>"; - } - - /** - * @param WebRequest $request - */ - function submit( $request ) { - $bitfield = $this->extractBitfield( $request ); - $comment = $request->getText( 'wpReason' ); - if( $this->save( $bitfield, $comment ) ) { - return $this->success( $request ); - } else { - return $this->show( $request ); - } - } - - function success( $request ) { - global $wgOut; - $wgOut->addWikiText( 'woo' ); - } - - /** - * Put together a rev_deleted bitfield from the submitted checkboxes - * @param WebRequest $request - * @return int - */ - function extractBitfield( $request ) { - $bitfield = 0; - foreach( $this->checks as $item ) { - list( /* message */ , $name, $field ) = $item; - if( $request->getCheck( $name ) ) { - $bitfield |= $field; - } - } - return $bitfield; - } - - function save( $bitfield, $reason ) { - $dbw = wfGetDB( DB_MASTER ); - $deleter = new RevisionDeleter( $dbw ); - $deleter->setVisibility( $this->revisions, $bitfield, $reason ); - } -} - -/** - * Implements the actions for Revision Deletion. - * @addtogroup SpecialPage - */ -class RevisionDeleter { - function __construct( $db ) { - $this->db = $db; - } - - /** - * @param array $items list of revision ID numbers - * @param int $bitfield new rev_deleted value - * @param string $comment Comment for log records - */ - function setVisibility( $items, $bitfield, $comment ) { - $pages = array(); - - // To work! - foreach( $items as $revid ) { - $rev = Revision::newFromId( $revid ); - if( !isset( $rev ) ) { - return false; - } - $this->updateRevision( $rev, $bitfield ); - $this->updateRecentChanges( $rev, $bitfield ); - - // For logging, maintain a count of revisions per page - $pageid = $rev->getPage(); - if( isset( $pages[$pageid] ) ) { - $pages[$pageid]++; - } else { - $pages[$pageid] = 1; - } - } - - // Clear caches... - foreach( $pages as $pageid => $count ) { - $title = Title::newFromId( $pageid ); - $this->updatePage( $title ); - $this->updateLog( $title, $count, $bitfield, $comment ); - } - - return true; - } - - /** - * Update the revision's rev_deleted field - * @param Revision $rev - * @param int $bitfield new rev_deleted bitfield value - */ - function updateRevision( $rev, $bitfield ) { - $this->db->update( 'revision', - array( 'rev_deleted' => $bitfield ), - array( 'rev_id' => $rev->getId() ), - 'RevisionDeleter::updateRevision' ); - } - - /** - * Update the revision's recentchanges record if fields have been hidden - * @param Revision $rev - * @param int $bitfield new rev_deleted bitfield value - */ - function updateRecentChanges( $rev, $bitfield ) { - $this->db->update( 'recentchanges', - array( - 'rc_user' => ($bitfield & Revision::DELETED_USER) ? 0 : $rev->getUser(), - 'rc_user_text' => ($bitfield & Revision::DELETED_USER) ? wfMsg( 'rev-deleted-user' ) : $rev->getUserText(), - 'rc_comment' => ($bitfield & Revision::DELETED_COMMENT) ? wfMsg( 'rev-deleted-comment' ) : $rev->getComment() ), - array( - 'rc_this_oldid' => $rev->getId() ), - 'RevisionDeleter::updateRecentChanges' ); - } - - /** - * Touch the page's cache invalidation timestamp; this forces cached - * history views to refresh, so any newly hidden or shown fields will - * update properly. - * @param Title $title - */ - function updatePage( $title ) { - $title->invalidateCache(); - } - - /** - * Record a log entry on the action - * @param Title $title - * @param int $count the number of revisions altered for this page - * @param int $bitfield the new rev_deleted value - * @param string $comment - */ - function updateLog( $title, $count, $bitfield, $comment ) { - $log = new LogPage( 'delete' ); - $reason = "changed $count revisions to $bitfield"; - $reason .= ": $comment"; - $log->addEntry( 'revision', $title, $reason ); - } -} - - diff --git a/includes/SpecialSearch.php b/includes/SpecialSearch.php deleted file mode 100644 index dcbbb903..00000000 --- a/includes/SpecialSearch.php +++ /dev/null @@ -1,438 +0,0 @@ -<?php -# Copyright (C) 2004 Brion Vibber <brion@pobox.com> -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# http://www.gnu.org/copyleft/gpl.html - -/** - * Run text & title search and display the output - * @addtogroup SpecialPage - */ - -/** - * Entry point - * - * @param $par String: (default '') - */ -function wfSpecialSearch( $par = '' ) { - global $wgRequest, $wgUser; - - $search = $wgRequest->getText( 'search', $par ); - $searchPage = new SpecialSearch( $wgRequest, $wgUser ); - if( $wgRequest->getVal( 'fulltext' ) || - !is_null( $wgRequest->getVal( 'offset' ) ) || - !is_null ($wgRequest->getVal( 'searchx' ) ) ) { - $searchPage->showResults( $search ); - } else { - $searchPage->goResult( $search ); - } -} - -/** - * implements Special:Search - Run text & title search and display the output - * @addtogroup SpecialPage - */ -class SpecialSearch { - - /** - * Set up basic search parameters from the request and user settings. - * Typically you'll pass $wgRequest and $wgUser. - * - * @param WebRequest $request - * @param User $user - * @public - */ - function SpecialSearch( &$request, &$user ) { - list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); - - if( $request->getCheck( 'searchx' ) ) { - $this->namespaces = $this->powerSearch( $request ); - } else { - $this->namespaces = $this->userNamespaces( $user ); - } - - $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; - } - - /** - * If an exact title match can be found, jump straight ahead to it. - * @param string $term - * @public - */ - function goResult( $term ) { - global $wgOut; - global $wgGoToEdit; - - $this->setupPage( $term ); - - # Try to go to page as entered. - $t = Title::newFromText( $term ); - - # If the string cannot be used to create a title - if( is_null( $t ) ){ - return $this->showResults( $term ); - } - - # If there's an exact or very near match, jump right there. - $t = SearchEngine::getNearMatch( $term ); - if( !is_null( $t ) ) { - $wgOut->redirect( $t->getFullURL() ); - return; - } - - # No match, generate an edit URL - $t = Title::newFromText( $term ); - if( ! is_null( $t ) ) { - wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) ); - # If the feature is enabled, go straight to the edit page - if ( $wgGoToEdit ) { - $wgOut->redirect( $t->getFullURL( 'action=edit' ) ); - return; - } - } - if( $t->quickUserCan( 'create' ) && $t->quickUserCan( 'edit' ) ) { - $wgOut->addWikiMsg( 'noexactmatch', wfEscapeWikiText( $term ) ); - } else { - $wgOut->addWikiMsg( 'noexactmatch-nocreate', wfEscapeWikiText( $term ) ); - } - - return $this->showResults( $term ); - } - - /** - * @param string $term - * @public - */ - function showResults( $term ) { - $fname = 'SpecialSearch::showResults'; - wfProfileIn( $fname ); - - $this->setupPage( $term ); - - global $wgOut; - $wgOut->addWikiMsg( 'searchresulttext' ); - - if( '' === trim( $term ) ) { - // Empty query -- straight view of search form - $wgOut->setSubtitle( '' ); - $wgOut->addHTML( $this->powerSearchBox( $term ) ); - $wgOut->addHTML( $this->powerSearchFocus() ); - wfProfileOut( $fname ); - return; - } - - global $wgDisableTextSearch; - if ( $wgDisableTextSearch ) { - global $wgForwardSearchUrl; - if( $wgForwardSearchUrl ) { - $url = str_replace( '$1', urlencode( $term ), $wgForwardSearchUrl ); - $wgOut->redirect( $url ); - return; - } - global $wgInputEncoding; - $wgOut->addHTML( wfMsg( 'searchdisabled' ) ); - $wgOut->addHTML( - wfMsg( 'googlesearch', - htmlspecialchars( $term ), - htmlspecialchars( $wgInputEncoding ), - htmlspecialchars( wfMsg( 'searchbutton' ) ) - ) - ); - wfProfileOut( $fname ); - return; - } - - $search = SearchEngine::create(); - $search->setLimitOffset( $this->limit, $this->offset ); - $search->setNamespaces( $this->namespaces ); - $search->showRedirects = $this->searchRedirects; - $titleMatches = $search->searchTitle( $term ); - - // Sometimes the search engine knows there are too many hits - if ($titleMatches instanceof SearchResultTooMany) { - $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" ); - $wgOut->addHTML( $this->powerSearchBox( $term ) ); - $wgOut->addHTML( $this->powerSearchFocus() ); - wfProfileOut( $fname ); - return; - } - $textMatches = $search->searchText( $term ); - - $num = ( $titleMatches ? $titleMatches->numRows() : 0 ) - + ( $textMatches ? $textMatches->numRows() : 0); - if ( $num > 0 ) { - if ( $num >= $this->limit ) { - $top = wfShowingResults( $this->offset, $this->limit ); - } else { - $top = wfShowingResultsNum( $this->offset, $this->limit, $num ); - } - $wgOut->addHTML( "<p>{$top}</p>\n" ); - } - - if( $num || $this->offset ) { - $prevnext = wfViewPrevNext( $this->offset, $this->limit, - SpecialPage::getTitleFor( 'Search' ), - wfArrayToCGI( - $this->powerSearchOptions(), - array( 'search' => $term ) ), - ($num < $this->limit) ); - $wgOut->addHTML( "<br />{$prevnext}\n" ); - } - - if( $titleMatches ) { - if( $titleMatches->numRows() ) { - $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' ); - $wgOut->addHTML( $this->showMatches( $titleMatches ) ); - } else { - $wgOut->wrapWikiMsg( "==$1==\n", 'notitlematches' ); - } - $titleMatches->free(); - } - - if( $textMatches ) { - if( $textMatches->numRows() ) { - $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' ); - $wgOut->addHTML( $this->showMatches( $textMatches ) ); - } elseif( $num == 0 ) { - # Don't show the 'no text matches' if we received title matches - $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' ); - } - $textMatches->free(); - } - - if ( $num == 0 ) { - $wgOut->addWikiMsg( 'nonefound' ); - } - if( $num || $this->offset ) { - $wgOut->addHTML( "<p>{$prevnext}</p>\n" ); - } - $wgOut->addHTML( $this->powerSearchBox( $term ) ); - wfProfileOut( $fname ); - } - - #------------------------------------------------------------------ - # Private methods below this line - - /** - * - */ - function setupPage( $term ) { - global $wgOut; - $wgOut->setPageTitle( wfMsg( 'searchresults' ) ); - $subtitlemsg = ( Title::newFromText($term) ? 'searchsubtitle' : 'searchsubtitleinvalid' ); - $wgOut->setSubtitle( $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) ) ); - $wgOut->setArticleRelated( false ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - } - - /** - * Extract default namespaces to search from the given user's - * settings, returning a list of index numbers. - * - * @param User $user - * @return array - * @private - */ - function userNamespaces( &$user ) { - $arr = array(); - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - if( $user->getOption( 'searchNs' . $ns ) ) { - $arr[] = $ns; - } - } - return $arr; - } - - /** - * Extract "power search" namespace settings from the request object, - * returning a list of index numbers to search. - * - * @param WebRequest $request - * @return array - * @private - */ - function powerSearch( &$request ) { - $arr = array(); - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - if( $request->getCheck( 'ns' . $ns ) ) { - $arr[] = $ns; - } - } - return $arr; - } - - /** - * Reconstruct the 'power search' options for links - * @return array - * @private - */ - function powerSearchOptions() { - $opt = array(); - foreach( $this->namespaces as $n ) { - $opt['ns' . $n] = 1; - } - $opt['redirs'] = $this->searchRedirects ? 1 : 0; - $opt['searchx'] = 1; - return $opt; - } - - /** - * @param SearchResultSet $matches - * @param string $terms partial regexp for highlighting terms - */ - function showMatches( &$matches ) { - $fname = 'SpecialSearch::showMatches'; - wfProfileIn( $fname ); - - global $wgContLang; - $tm = $wgContLang->convertForSearchResult( $matches->termMatches() ); - $terms = implode( '|', $tm ); - - $off = $this->offset + 1; - $out = "<ol start='{$off}'>\n"; - - while( $result = $matches->next() ) { - $out .= $this->showHit( $result, $terms ); - } - $out .= "</ol>\n"; - - // convert the whole thing to desired language variant - global $wgContLang; - $out = $wgContLang->convert( $out ); - wfProfileOut( $fname ); - return $out; - } - - /** - * Format a single hit result - * @param SearchResult $result - * @param string $terms partial regexp for highlighting terms - */ - function showHit( $result, $terms ) { - $fname = 'SpecialSearch::showHit'; - wfProfileIn( $fname ); - global $wgUser, $wgContLang, $wgLang; - - $t = $result->getTitle(); - if( is_null( $t ) ) { - wfProfileOut( $fname ); - return "<!-- Broken link in search result -->\n"; - } - $sk = $wgUser->getSkin(); - - $contextlines = $wgUser->getOption( 'contextlines', 5 ); - $contextchars = $wgUser->getOption( 'contextchars', 50 ); - - $link = $sk->makeKnownLinkObj( $t ); - - //If page content is not readable, just return the title. - //This is not quite safe, but better than showing excerpts from non-readable pages - //Note that hiding the entry entirely would screw up paging. - if (!$t->userCanRead()) { - return "<li>{$link}</li>\n"; - } - - $revision = Revision::newFromTitle( $t ); - $text = $revision->getText(); - $size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'), - $wgLang->formatNum( strlen( $text ) ) ); - - $lines = explode( "\n", $text ); - - $max = intval( $contextchars ) + 1; - $pat1 = "/(.*)($terms)(.{0,$max})/i"; - - $lineno = 0; - - $extract = ''; - wfProfileIn( "$fname-extract" ); - foreach ( $lines as $line ) { - if ( 0 == $contextlines ) { - break; - } - ++$lineno; - $m = array(); - if ( ! preg_match( $pat1, $line, $m ) ) { - continue; - } - --$contextlines; - $pre = $wgContLang->truncate( $m[1], -$contextchars, '...' ); - - if ( count( $m ) < 3 ) { - $post = ''; - } else { - $post = $wgContLang->truncate( $m[3], $contextchars, '...' ); - } - - $found = $m[2]; - - $line = htmlspecialchars( $pre . $found . $post ); - $pat2 = '/(' . $terms . ")/i"; - $line = preg_replace( $pat2, - "<span class='searchmatch'>\\1</span>", $line ); - - $extract .= "<br /><small>{$lineno}: {$line}</small>\n"; - } - wfProfileOut( "$fname-extract" ); - wfProfileOut( $fname ); - return "<li>{$link} ({$size}){$extract}</li>\n"; - } - - function powerSearchBox( $term ) { - $namespaces = ''; - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - $checked = in_array( $ns, $this->namespaces ) - ? ' checked="checked"' - : ''; - $name = str_replace( '_', ' ', $name ); - if( '' == $name ) { - $name = wfMsg( 'blanknamespace' ); - } - $encName = htmlspecialchars( $name ); - $namespaces .= " <label><input type='checkbox' value=\"1\" name=\"" . - "ns{$ns}\"{$checked} />{$encName}</label>\n"; - } - - $checked = $this->searchRedirects - ? ' checked="checked"' - : ''; - $redirect = "<input type='checkbox' value='1' name=\"redirs\"{$checked} />\n"; - - $searchField = '<input type="text" id="powerSearchText" name="search" value="' . - htmlspecialchars( $term ) ."\" size=\"16\" />\n"; - - $searchButton = '<input type="submit" name="searchx" value="' . - htmlspecialchars( wfMsg('powersearch') ) . "\" />\n"; - - $ret = wfMsg( 'powersearchtext', - $namespaces, $redirect, $searchField, - '', '', '', '', '', # Dummy placeholders - $searchButton ); - - $title = SpecialPage::getTitleFor( 'Search' ); - $action = $title->escapeLocalURL(); - return "<br /><br />\n<form id=\"powersearch\" method=\"get\" " . - "action=\"$action\">\n{$ret}\n</form>\n"; - } - - function powerSearchFocus() { - return "<script type='text/javascript'>" . - "document.getElementById('powerSearchText').focus();" . - "</script>"; - } -} - - diff --git a/includes/SpecialShortpages.php b/includes/SpecialShortpages.php deleted file mode 100644 index 5aa36386..00000000 --- a/includes/SpecialShortpages.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * SpecialShortpages extends QueryPage. It is used to return the shortest - * pages in the database. - * @addtogroup SpecialPage - */ -class ShortPagesPage extends QueryPage { - - function getName() { - return "Shortpages"; - } - - /** - * This query is indexed as of 1.5 - */ - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $name = $dbr->addQuotes( $this->getName() ); - - $forceindex = $dbr->useIndexClause("page_len"); - return - "SELECT $name as type, - page_namespace as namespace, - page_title as title, - page_len AS value - FROM $page $forceindex - WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0"; - } - - function preprocessResults( $db, $res ) { - # There's no point doing a batch check if we aren't caching results; - # the page must exist for it to have been pulled out of the table - if( $this->isCached() ) { - $batch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) - $batch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) ); - $batch->execute(); - if( $db->numRows( $res ) > 0 ) - $db->dataSeek( $res, 0 ); - } - } - - function sortDescending() { - return false; - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - $dm = $wgContLang->getDirMark(); - - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if ( !$title ) { - return '<!-- Invalid title ' . htmlspecialchars( "{$result->namespace}:{$result->title}" ). '-->'; - } - $hlink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' ); - $plink = $this->isCached() - ? $skin->makeLinkObj( $title ) - : $skin->makeKnownLinkObj( $title ); - $size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->value ) ) ); - - return $title->exists() - ? "({$hlink}) {$dm}{$plink} {$dm}[{$size}]" - : "<s>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</s>"; - } -} - -/** - * constructor - */ -function wfSpecialShortpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $spp = new ShortPagesPage(); - - return $spp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialSpecialpages.php b/includes/SpecialSpecialpages.php deleted file mode 100644 index 4ea956b8..00000000 --- a/includes/SpecialSpecialpages.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -function wfSpecialSpecialpages() { - global $wgOut, $wgUser, $wgMessageCache; - - $wgMessageCache->loadAllMessages(); - - $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Is this really needed? - $sk = $wgUser->getSkin(); - - /** Pages available to all */ - wfSpecialSpecialpages_gen( SpecialPage::getRegularPages(), 'spheading', $sk ); - - /** Restricted special pages */ - wfSpecialSpecialpages_gen( SpecialPage::getRestrictedPages(), 'restrictedpheading', $sk ); -} - -/** - * sub function generating the list of pages - * @param $pages the list of pages - * @param $heading header to be used - * @param $sk skin object ??? - */ -function wfSpecialSpecialpages_gen($pages,$heading,$sk) { - global $wgOut, $wgSortSpecialPages; - - if( count( $pages ) == 0 ) { - # Yeah, that was pointless. Thanks for coming. - return; - } - - /** Put them into a sortable array */ - $sortedPages = array(); - foreach ( $pages as $page ) { - if ( $page->isListed() ) { - $sortedPages[$page->getDescription()] = $page->getTitle(); - } - } - - /** Sort */ - if ( $wgSortSpecialPages ) { - ksort( $sortedPages ); - } - - /** Now output the HTML */ - $wgOut->addHTML( '<h2>' . wfMsgHtml( $heading ) . "</h2>\n<ul>" ); - foreach ( $sortedPages as $desc => $title ) { - $link = $sk->makeKnownLinkObj( $title , htmlspecialchars( $desc ) ); - $wgOut->addHTML( "<li>{$link}</li>\n" ); - } - $wgOut->addHTML( "</ul>\n" ); -} - - diff --git a/includes/SpecialStatistics.php b/includes/SpecialStatistics.php deleted file mode 100644 index 983dc896..00000000 --- a/includes/SpecialStatistics.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php - -/** - * Special page lists various statistics, including the contents of - * `site_stats`, plus page view details if enabled - * - * @addtogroup SpecialPage - */ - -/** - * Show the special page - * - * @param mixed $par (not used) - */ -function wfSpecialStatistics( $par = '' ) { - global $wgOut, $wgLang, $wgRequest; - $dbr = wfGetDB( DB_SLAVE ); - - $views = SiteStats::views(); - $edits = SiteStats::edits(); - $good = SiteStats::articles(); - $images = SiteStats::images(); - $total = SiteStats::pages(); - $users = SiteStats::users(); - $admins = SiteStats::admins(); - $numJobs = SiteStats::jobs(); - - if( $wgRequest->getVal( 'action' ) == 'raw' ) { - $wgOut->disable(); - header( 'Pragma: nocache' ); - echo "total=$total;good=$good;views=$views;edits=$edits;users=$users;admins=$admins;images=$images;jobs=$numJobs\n"; - return; - } else { - $text = "__NOTOC__\n"; - $text .= '==' . wfMsgNoTrans( 'sitestats' ) . "==\n"; - $text .= wfMsgExt( 'sitestatstext', array( 'parsemag' ), - $wgLang->formatNum( $total ), - $wgLang->formatNum( $good ), - $wgLang->formatNum( $views ), - $wgLang->formatNum( $edits ), - $wgLang->formatNum( sprintf( '%.2f', $total ? $edits / $total : 0 ) ), - $wgLang->formatNum( sprintf( '%.2f', $edits ? $views / $edits : 0 ) ), - $wgLang->formatNum( $numJobs ), - $wgLang->formatNum( $images ) - )."\n"; - - $text .= "==" . wfMsgNoTrans( 'userstats' ) . "==\n"; - $text .= wfMsgExt( 'userstatstext', array ( 'parsemag' ), - $wgLang->formatNum( $users ), - $wgLang->formatNum( $admins ), - '[[' . wfMsgForContent( 'grouppage-sysop' ) . ']]', # TODO somehow remove, kept for backwards compatibility - $wgLang->formatNum( sprintf( '%.2f', $admins / $users * 100 ) ), - User::makeGroupLinkWiki( 'sysop' ) - )."\n"; - - global $wgDisableCounters, $wgMiserMode, $wgUser, $wgLang, $wgContLang; - if( !$wgDisableCounters && !$wgMiserMode ) { - $res = $dbr->select( - 'page', - array( - 'page_namespace', - 'page_title', - 'page_counter', - ), - array( - 'page_is_redirect' => 0, - 'page_counter > 0', - ), - __METHOD__, - array( - 'ORDER BY' => 'page_counter DESC', - 'LIMIT' => 10, - ) - ); - if( $res->numRows() > 0 ) { - $text .= "==" . wfMsgNoTrans( 'statistics-mostpopular' ) . "==\n"; - while( $row = $res->fetchObject() ) { - $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - if( $title instanceof Title ) - $text .= '* [[:' . $title->getPrefixedText() . ']] (' . $wgLang->formatNum( $row->page_counter ) . ")\n"; - } - $res->free(); - } - } - - $footer = wfMsgNoTrans( 'statistics-footer' ); - if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) - $text .= "\n" . $footer; - - $wgOut->addWikiText( $text ); - } - -} diff --git a/includes/SpecialUncategorizedcategories.php b/includes/SpecialUncategorizedcategories.php deleted file mode 100644 index 67f87aa8..00000000 --- a/includes/SpecialUncategorizedcategories.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -require_once( "SpecialUncategorizedpages.php" ); - -/** - * implements Special:Uncategorizedcategories - * @addtogroup SpecialPage - */ -class UncategorizedCategoriesPage extends UncategorizedPagesPage { - function UncategorizedCategoriesPage() { - $this->requestedNamespace = NS_CATEGORY; - } - - function getName() { - return "Uncategorizedcategories"; - } -} - -/** - * constructor - */ -function wfSpecialUncategorizedcategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new UncategorizedCategoriesPage(); - - return $lpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialUncategorizedimages.php b/includes/SpecialUncategorizedimages.php deleted file mode 100644 index 23deefe8..00000000 --- a/includes/SpecialUncategorizedimages.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -/** - * Special page lists images which haven't been categorised - * - * @addtogroup SpecialPage - * @author Rob Church <robchur@gmail.com> - */ - -class UncategorizedImagesPage extends ImageQueryPage { - - function getName() { - return 'Uncategorizedimages'; - } - - function sortDescending() { - return false; - } - - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $ns = NS_IMAGE; - - return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace, - page_title AS title, page_title AS value - FROM {$page} LEFT JOIN {$categorylinks} ON page_id = cl_from - WHERE cl_from IS NULL AND page_namespace = {$ns} AND page_is_redirect = 0"; - } - -} - -function wfSpecialUncategorizedimages() { - $uip = new UncategorizedImagesPage(); - list( $limit, $offset ) = wfCheckLimits(); - return $uip->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialUncategorizedpages.php b/includes/SpecialUncategorizedpages.php deleted file mode 100644 index b26f6d93..00000000 --- a/includes/SpecialUncategorizedpages.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * A special page looking for page without any category. - * @addtogroup SpecialPage - */ -class UncategorizedPagesPage extends PageQueryPage { - var $requestedNamespace = NS_MAIN; - - function getName() { - return "Uncategorizedpages"; - } - - function sortDescending() { - return false; - } - - function isExpensive() { - return true; - } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $name = $dbr->addQuotes( $this->getName() ); - - return - " - SELECT - $name as type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $categorylinks ON page_id=cl_from - WHERE cl_from IS NULL AND page_namespace={$this->requestedNamespace} AND page_is_redirect=0 - "; - } -} - -/** - * constructor - */ -function wfSpecialUncategorizedpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new UncategorizedPagesPage(); - - return $lpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialUncategorizedtemplates.php b/includes/SpecialUncategorizedtemplates.php deleted file mode 100644 index fb785e00..00000000 --- a/includes/SpecialUncategorizedtemplates.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php - -/** - * Special page lists all uncategorised pages in the - * template namespace - * - * @addtogroup SpecialPage - * @author Rob Church <robchur@gmail.com> - */ -class UncategorizedTemplatesPage extends UncategorizedPagesPage { - - var $requestedNamespace = NS_TEMPLATE; - - public function getName() { - return 'Uncategorizedtemplates'; - } - -} - -/** - * Main execution point - * - * @param mixed $par Parameter passed to the page - */ -function wfSpecialUncategorizedtemplates() { - list( $limit, $offset ) = wfCheckLimits(); - $utp = new UncategorizedTemplatesPage(); - $utp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialUndelete.php b/includes/SpecialUndelete.php deleted file mode 100644 index e6f6298c..00000000 --- a/includes/SpecialUndelete.php +++ /dev/null @@ -1,1075 +0,0 @@ -<?php - -/** - * Special page allowing users with the appropriate permissions to view - * and restore deleted content - * - * @addtogroup SpecialPage - */ - -/** - * Constructor - */ -function wfSpecialUndelete( $par ) { - global $wgRequest; - - $form = new UndeleteForm( $wgRequest, $par ); - $form->execute(); -} - -/** - * Used to show archived pages and eventually restore them. - * @addtogroup SpecialPage - */ -class PageArchive { - protected $title; - var $fileStatus; - - function __construct( $title ) { - if( is_null( $title ) ) { - throw new MWException( 'Archiver() given a null title.'); - } - $this->title = $title; - } - - /** - * List all deleted pages recorded in the archive table. Returns result - * wrapper with (ar_namespace, ar_title, count) fields, ordered by page - * namespace/title. - * - * @return ResultWrapper - */ - public static function listAllPages() { - $dbr = wfGetDB( DB_SLAVE ); - return self::listPages( $dbr, '' ); - } - - /** - * List deleted pages recorded in the archive table matching the - * given title prefix. - * Returns result wrapper with (ar_namespace, ar_title, count) fields. - * - * @return ResultWrapper - */ - public static function listPagesByPrefix( $prefix ) { - $dbr = wfGetDB( DB_SLAVE ); - - $title = Title::newFromText( $prefix ); - if( $title ) { - $ns = $title->getNamespace(); - $encPrefix = $dbr->escapeLike( $title->getDBkey() ); - } else { - // Prolly won't work too good - // @todo handle bare namespace names cleanly? - $ns = 0; - $encPrefix = $dbr->escapeLike( $prefix ); - } - $conds = array( - 'ar_namespace' => $ns, - "ar_title LIKE '$encPrefix%'", - ); - return self::listPages( $dbr, $conds ); - } - - protected static function listPages( $dbr, $condition ) { - return $dbr->resultObject( - $dbr->select( - array( 'archive' ), - array( - 'ar_namespace', - 'ar_title', - 'COUNT(*) AS count', - ), - $condition, - __METHOD__, - array( - 'GROUP BY' => 'ar_namespace,ar_title', - 'ORDER BY' => 'ar_namespace,ar_title', - 'LIMIT' => 100, - ) - ) - ); - } - - /** - * List the revisions of the given page. Returns result wrapper with - * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields. - * - * @return ResultWrapper - */ - function listRevisions() { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'archive', - array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len' ), - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), - 'PageArchive::listRevisions', - array( 'ORDER BY' => 'ar_timestamp DESC' ) ); - $ret = $dbr->resultObject( $res ); - return $ret; - } - - /** - * List the deleted file revisions for this page, if it's a file page. - * Returns a result wrapper with various filearchive fields, or null - * if not a file page. - * - * @return ResultWrapper - * @todo Does this belong in Image for fuller encapsulation? - */ - function listFiles() { - if( $this->title->getNamespace() == NS_IMAGE ) { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'filearchive', - array( - 'fa_id', - 'fa_name', - 'fa_storage_key', - 'fa_size', - 'fa_width', - 'fa_height', - 'fa_description', - 'fa_user', - 'fa_user_text', - 'fa_timestamp' ), - array( 'fa_name' => $this->title->getDBkey() ), - __METHOD__, - array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - $ret = $dbr->resultObject( $res ); - return $ret; - } - return null; - } - - /** - * Fetch (and decompress if necessary) the stored text for the deleted - * revision of the page with the given timestamp. - * - * @return string - * @deprecated Use getRevision() for more flexible information - */ - function getRevisionText( $timestamp ) { - $rev = $this->getRevision( $timestamp ); - return $rev ? $rev->getText() : null; - } - - /** - * Return a Revision object containing data for the deleted revision. - * Note that the result *may* or *may not* have a null page ID. - * @param string $timestamp - * @return Revision - */ - function getRevision( $timestamp ) { - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'archive', - array( - 'ar_rev_id', - 'ar_text', - 'ar_comment', - 'ar_user', - 'ar_user_text', - 'ar_timestamp', - 'ar_minor_edit', - 'ar_flags', - 'ar_text_id', - 'ar_len' ), - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), - __METHOD__ ); - if( $row ) { - return new Revision( array( - 'page' => $this->title->getArticleId(), - 'id' => $row->ar_rev_id, - 'text' => ($row->ar_text_id - ? null - : Revision::getRevisionText( $row, 'ar_' ) ), - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id ) ); - } else { - return null; - } - } - - /** - * Return the most-previous revision, either live or deleted, against - * the deleted revision given by timestamp. - * - * May produce unexpected results in case of history merges or other - * unusual time issues. - * - * @param string $timestamp - * @return Revision or null - */ - function getPreviousRevision( $timestamp ) { - $dbr = wfGetDB( DB_SLAVE ); - - // Check the previous deleted revision... - $row = $dbr->selectRow( 'archive', - 'ar_timestamp', - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp < ' . - $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), - __METHOD__, - array( - 'ORDER BY' => 'ar_timestamp DESC', - 'LIMIT' => 1 ) ); - $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false; - - $row = $dbr->selectRow( array( 'page', 'revision' ), - array( 'rev_id', 'rev_timestamp' ), - array( - 'page_namespace' => $this->title->getNamespace(), - 'page_title' => $this->title->getDBkey(), - 'page_id = rev_page', - 'rev_timestamp < ' . - $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), - __METHOD__, - array( - 'ORDER BY' => 'rev_timestamp DESC', - 'LIMIT' => 1 ) ); - $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false; - $prevLiveId = $row ? intval( $row->rev_id ) : null; - - if( $prevLive && $prevLive > $prevDeleted ) { - // Most prior revision was live - return Revision::newFromId( $prevLiveId ); - } elseif( $prevDeleted ) { - // Most prior revision was deleted - return $this->getRevision( $prevDeleted ); - } else { - // No prior revision on this page. - return null; - } - } - - /** - * Get the text from an archive row containing ar_text, ar_flags and ar_text_id - */ - function getTextFromRow( $row ) { - if( is_null( $row->ar_text_id ) ) { - // An old row from MediaWiki 1.4 or previous. - // Text is embedded in this row in classic compression format. - return Revision::getRevisionText( $row, "ar_" ); - } else { - // New-style: keyed to the text storage backend. - $dbr = wfGetDB( DB_SLAVE ); - $text = $dbr->selectRow( 'text', - array( 'old_text', 'old_flags' ), - array( 'old_id' => $row->ar_text_id ), - __METHOD__ ); - return Revision::getRevisionText( $text ); - } - } - - - /** - * Fetch (and decompress if necessary) the stored text of the most - * recently edited deleted revision of the page. - * - * If there are no archived revisions for the page, returns NULL. - * - * @return string - */ - function getLastRevisionText() { - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'archive', - array( 'ar_text', 'ar_flags', 'ar_text_id' ), - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), - 'PageArchive::getLastRevisionText', - array( 'ORDER BY' => 'ar_timestamp DESC' ) ); - if( $row ) { - return $this->getTextFromRow( $row ); - } else { - return NULL; - } - } - - /** - * Quick check if any archived revisions are present for the page. - * @return bool - */ - function isDeleted() { - $dbr = wfGetDB( DB_SLAVE ); - $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', - array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ) ); - return ($n > 0); - } - - /** - * Restore the given (or all) text and file revisions for the page. - * Once restored, the items will be removed from the archive tables. - * The deletion log will be updated with an undeletion notice. - * - * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param string $comment - * @param array $fileVersions - * - * @return array(number of file revisions restored, number of image revisions restored, log message) - * on success, false on failure - */ - function undelete( $timestamps, $comment = '', $fileVersions = array() ) { - // If both the set of text revisions and file revisions are empty, - // restore everything. Otherwise, just restore the requested items. - $restoreAll = empty( $timestamps ) && empty( $fileVersions ); - - $restoreText = $restoreAll || !empty( $timestamps ); - $restoreFiles = $restoreAll || !empty( $fileVersions ); - - if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) { - $img = wfLocalFile( $this->title ); - $this->fileStatus = $img->restore( $fileVersions ); - $filesRestored = $this->fileStatus->successCount; - } else { - $filesRestored = 0; - } - - if( $restoreText ) { - $textRestored = $this->undeleteRevisions( $timestamps ); - if($textRestored === false) // It must be one of UNDELETE_* - return false; - } else { - $textRestored = 0; - } - - // Touch the log! - global $wgContLang; - $log = new LogPage( 'delete' ); - - if( $textRestored && $filesRestored ) { - $reason = wfMsgExt( 'undeletedrevisions-files', array( 'content', 'parsemag' ), - $wgContLang->formatNum( $textRestored ), - $wgContLang->formatNum( $filesRestored ) ); - } elseif( $textRestored ) { - $reason = wfMsgExt( 'undeletedrevisions', array( 'content', 'parsemag' ), - $wgContLang->formatNum( $textRestored ) ); - } elseif( $filesRestored ) { - $reason = wfMsgExt( 'undeletedfiles', array( 'content', 'parsemag' ), - $wgContLang->formatNum( $filesRestored ) ); - } else { - wfDebug( "Undelete: nothing undeleted...\n" ); - return false; - } - - if( trim( $comment ) != '' ) - $reason .= ": {$comment}"; - $log->addEntry( 'restore', $this->title, $reason ); - - return array($textRestored, $filesRestored, $reason); - } - - /** - * This is the meaty bit -- restores archived revisions of the given page - * to the cur/old tables. If the page currently exists, all revisions will - * be stuffed into old, otherwise the most recent will go into cur. - * - * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param string $comment - * @param array $fileVersions - * - * @return mixed number of revisions restored or false on failure - */ - private function undeleteRevisions( $timestamps ) { - if ( wfReadOnly() ) - return false; - - $restoreAll = empty( $timestamps ); - - $dbw = wfGetDB( DB_MASTER ); - - # Does this page already exist? We'll have to update it... - $article = new Article( $this->title ); - $options = 'FOR UPDATE'; - $page = $dbw->selectRow( 'page', - array( 'page_id', 'page_latest' ), - array( 'page_namespace' => $this->title->getNamespace(), - 'page_title' => $this->title->getDBkey() ), - __METHOD__, - $options ); - if( $page ) { - # Page already exists. Import the history, and if necessary - # we'll update the latest revision field in the record. - $newid = 0; - $pageId = $page->page_id; - $previousRevId = $page->page_latest; - } else { - # Have to create a new article... - $newid = $article->insertOn( $dbw ); - $pageId = $newid; - $previousRevId = 0; - } - - if( $restoreAll ) { - $oldones = '1 = 1'; # All revisions... - } else { - $oldts = implode( ',', - array_map( array( &$dbw, 'addQuotes' ), - array_map( array( &$dbw, 'timestamp' ), - $timestamps ) ) ); - - $oldones = "ar_timestamp IN ( {$oldts} )"; - } - - /** - * Restore each revision... - */ - $result = $dbw->select( 'archive', - /* fields */ array( - 'ar_rev_id', - 'ar_text', - 'ar_comment', - 'ar_user', - 'ar_user_text', - 'ar_timestamp', - 'ar_minor_edit', - 'ar_flags', - 'ar_text_id', - 'ar_page_id', - 'ar_len' ), - /* WHERE */ array( - 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - $oldones ), - __METHOD__, - /* options */ array( - 'ORDER BY' => 'ar_timestamp' ) - ); - if( $dbw->numRows( $result ) < count( $timestamps ) ) { - wfDebug( __METHOD__.": couldn't find all requested rows\n" ); - return false; - } - - $revision = null; - $restored = 0; - - while( $row = $dbw->fetchObject( $result ) ) { - if( $row->ar_text_id ) { - // Revision was deleted in 1.5+; text is in - // the regular text table, use the reference. - // Specify null here so the so the text is - // dereferenced for page length info if needed. - $revText = null; - } else { - // Revision was deleted in 1.4 or earlier. - // Text is squashed into the archive row, and - // a new text table entry will be created for it. - $revText = Revision::getRevisionText( $row, 'ar_' ); - } - $revision = new Revision( array( - 'page' => $pageId, - 'id' => $row->ar_rev_id, - 'text' => $revText, - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id, - 'len' => $row->ar_len - ) ); - $revision->insertOn( $dbw ); - $restored++; - - wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) ); - } - // Was anything restored at all? - if($restored == 0) - return 0; - - if( $revision ) { - // Attach the latest revision to the page... - $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId ); - - if( $newid || $wasnew ) { - // Update site stats, link tables, etc - $article->createUpdates( $revision ); - } - - if( $newid ) { - wfRunHooks( 'ArticleUndelete', array( &$this->title, true ) ); - Article::onArticleCreate( $this->title ); - } else { - wfRunHooks( 'ArticleUndelete', array( &$this->title, false ) ); - Article::onArticleEdit( $this->title ); - } - - if( $this->title->getNamespace() == NS_IMAGE ) { - $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); - $update->doUpdate(); - } - } else { - // Revision couldn't be created. This is very weird - return self::UNDELETE_UNKNOWNERR; - } - - # Now that it's safely stored, take it out of the archive - $dbw->delete( 'archive', - /* WHERE */ array( - 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - $oldones ), - __METHOD__ ); - - return $restored; - } - - function getFileStatus() { return $this->fileStatus; } -} - -/** - * The HTML form for Special:Undelete, which allows users with the appropriate - * permissions to view and restore deleted content. - * @addtogroup SpecialPage - */ -class UndeleteForm { - var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj; - var $mTargetTimestamp, $mAllowed, $mComment; - - function UndeleteForm( $request, $par = "" ) { - global $wgUser; - $this->mAction = $request->getVal( 'action' ); - $this->mTarget = $request->getVal( 'target' ); - $this->mSearchPrefix = $request->getText( 'prefix' ); - $time = $request->getVal( 'timestamp' ); - $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; - $this->mFile = $request->getVal( 'file' ); - - $posted = $request->wasPosted() && - $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - $this->mRestore = $request->getCheck( 'restore' ) && $posted; - $this->mPreview = $request->getCheck( 'preview' ) && $posted; - $this->mDiff = $request->getCheck( 'diff' ); - $this->mComment = $request->getText( 'wpComment' ); - - if( $par != "" ) { - $this->mTarget = $par; - } - if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) { - $this->mAllowed = true; - } else { - $this->mAllowed = false; - $this->mTimestamp = ''; - $this->mRestore = false; - } - if ( $this->mTarget !== "" ) { - $this->mTargetObj = Title::newFromURL( $this->mTarget ); - } else { - $this->mTargetObj = NULL; - } - if( $this->mRestore ) { - $timestamps = array(); - $this->mFileVersions = array(); - foreach( $_REQUEST as $key => $val ) { - $matches = array(); - if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { - array_push( $timestamps, $matches[1] ); - } - - if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { - $this->mFileVersions[] = intval( $matches[1] ); - } - } - rsort( $timestamps ); - $this->mTargetTimestamp = $timestamps; - } - } - - function execute() { - global $wgOut; - if ( $this->mAllowed ) { - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); - } else { - $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); - } - - if( is_null( $this->mTargetObj ) ) { - $this->showSearchForm(); - - # List undeletable articles - if( $this->mSearchPrefix ) { - $result = PageArchive::listPagesByPrefix( - $this->mSearchPrefix ); - $this->showList( $result ); - } - return; - } - if( $this->mTimestamp !== '' ) { - return $this->showRevision( $this->mTimestamp ); - } - if( $this->mFile !== null ) { - return $this->showFile( $this->mFile ); - } - if( $this->mRestore && $this->mAction == "submit" ) { - return $this->undelete(); - } - return $this->showHistory(); - } - - function showSearchForm() { - global $wgOut, $wgScript; - $wgOut->addWikiMsg( 'undelete-header' ); - - $wgOut->addHtml( - Xml::openElement( 'form', array( - 'method' => 'get', - 'action' => $wgScript ) ) . - '<fieldset>' . - Xml::element( 'legend', array(), - wfMsg( 'undelete-search-box' ) ) . - Xml::hidden( 'title', - SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) . - Xml::inputLabel( wfMsg( 'undelete-search-prefix' ), - 'prefix', 'prefix', 20, - $this->mSearchPrefix ) . - Xml::submitButton( wfMsg( 'undelete-search-submit' ) ) . - '</fieldset>' . - '</form>' ); - } - - /* private */ function showList( $result ) { - global $wgLang, $wgContLang, $wgUser, $wgOut; - - if( $result->numRows() == 0 ) { - $wgOut->addWikiMsg( 'undelete-no-results' ); - return; - } - - $wgOut->addWikiMsg( "undeletepagetext" ); - - $sk = $wgUser->getSkin(); - $undelete = SpecialPage::getTitleFor( 'Undelete' ); - $wgOut->addHTML( "<ul>\n" ); - while( $row = $result->fetchObject() ) { - $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); - $link = $sk->makeKnownLinkObj( $undelete, htmlspecialchars( $title->getPrefixedText() ), 'target=' . $title->getPrefixedUrl() ); - #$revs = wfMsgHtml( 'undeleterevisions', $wgLang->formatNum( $row->count ) ); - $revs = wfMsgExt( 'undeleterevisions', - array( 'parseinline' ), - $wgLang->formatNum( $row->count ) ); - $wgOut->addHtml( "<li>{$link} ({$revs})</li>\n" ); - } - $result->free(); - $wgOut->addHTML( "</ul>\n" ); - - return true; - } - - /* private */ function showRevision( $timestamp ) { - global $wgLang, $wgUser, $wgOut; - $self = SpecialPage::getTitleFor( 'Undelete' ); - $skin = $wgUser->getSkin(); - - if(!preg_match("/[0-9]{14}/",$timestamp)) return 0; - - $archive = new PageArchive( $this->mTargetObj ); - $rev = $archive->getRevision( $timestamp ); - - if( !$rev ) { - $wgOut->addWikiMsg( 'undeleterevision-missing' ); - return; - } - - $wgOut->setPageTitle( wfMsg( 'undeletepage' ) ); - - $link = $skin->makeKnownLinkObj( - SpecialPage::getTitleFor( 'Undelete', $this->mTargetObj->getPrefixedDBkey() ), - htmlspecialchars( $this->mTargetObj->getPrefixedText() ) - ); - $time = htmlspecialchars( $wgLang->timeAndDate( $timestamp, true ) ); - $user = $skin->userLink( $rev->getUser(), $rev->getUserText() ) - . $skin->userToolLinks( $rev->getUser(), $rev->getUserText() ); - - if( $this->mDiff ) { - $previousRev = $archive->getPreviousRevision( $timestamp ); - if( $previousRev ) { - $this->showDiff( $previousRev, $rev ); - if( $wgUser->getOption( 'diffonly' ) ) { - return; - } else { - $wgOut->addHtml( '<hr />' ); - } - } else { - $wgOut->addHtml( wfMsgHtml( 'undelete-nodiff' ) ); - } - } - - $wgOut->addHtml( '<p>' . wfMsgHtml( 'undelete-revision', $link, $time, $user ) . '</p>' ); - - wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ); - - if( $this->mPreview ) { - $wgOut->addHtml( "<hr />\n" ); - $wgOut->addWikiTextTitleTidy( $rev->getText(), $this->mTargetObj, false ); - } - - $wgOut->addHtml( - wfElement( 'textarea', array( - 'readonly' => 'readonly', - 'cols' => intval( $wgUser->getOption( 'cols' ) ), - 'rows' => intval( $wgUser->getOption( 'rows' ) ) ), - $rev->getText() . "\n" ) . - wfOpenElement( 'div' ) . - wfOpenElement( 'form', array( - 'method' => 'post', - 'action' => $self->getLocalURL( "action=submit" ) ) ) . - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'target', - 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) . - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'timestamp', - 'value' => $timestamp ) ) . - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'wpEditToken', - 'value' => $wgUser->editToken() ) ) . - wfElement( 'input', array( - 'type' => 'submit', - 'name' => 'preview', - 'value' => wfMsg( 'showpreview' ) ) ) . - wfElement( 'input', array( - 'name' => 'diff', - 'type' => 'submit', - 'value' => wfMsg( 'showdiff' ) ) ) . - wfCloseElement( 'form' ) . - wfCloseElement( 'div' ) ); - } - - /** - * Build a diff display between this and the previous either deleted - * or non-deleted edit. - * @param Revision $previousRev - * @param Revision $currentRev - * @return string HTML - */ - function showDiff( $previousRev, $currentRev ) { - global $wgOut, $wgUser; - - $diffEngine = new DifferenceEngine(); - $diffEngine->showDiffStyle(); - $wgOut->addHtml( - "<div>" . - "<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>" . - "<col class='diff-marker' />" . - "<col class='diff-content' />" . - "<col class='diff-marker' />" . - "<col class='diff-content' />" . - "<tr>" . - "<td colspan='2' width='50%' align='center' class='diff-otitle'>" . - $this->diffHeader( $previousRev ) . - "</td>" . - "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" . - $this->diffHeader( $currentRev ) . - "</td>" . - "</tr>" . - $diffEngine->generateDiffBody( - $previousRev->getText(), $currentRev->getText() ) . - "</table>" . - "</div>\n" ); - - } - - private function diffHeader( $rev ) { - global $wgUser, $wgLang, $wgLang; - $sk = $wgUser->getSkin(); - $isDeleted = !( $rev->getId() && $rev->getTitle() ); - if( $isDeleted ) { - /// @fixme $rev->getTitle() is null for deleted revs...? - $targetPage = SpecialPage::getTitleFor( 'Undelete' ); - $targetQuery = 'target=' . - $this->mTargetObj->getPrefixedUrl() . - '×tamp=' . - wfTimestamp( TS_MW, $rev->getTimestamp() ); - } else { - /// @fixme getId() may return non-zero for deleted revs... - $targetPage = $rev->getTitle(); - $targetQuery = 'oldid=' . $rev->getId(); - } - return - '<div id="mw-diff-otitle1"><strong>' . - $sk->makeLinkObj( $targetPage, - wfMsgHtml( 'revisionasof', - $wgLang->timeanddate( $rev->getTimestamp(), true ) ), - $targetQuery ) . - ( $isDeleted ? ' ' . wfMsgHtml( 'deletedrev' ) : '' ) . - '</strong></div>' . - '<div id="mw-diff-otitle2">' . - $sk->revUserTools( $rev ) . '<br/>' . - '</div>' . - '<div id="mw-diff-otitle3">' . - $sk->revComment( $rev ) . '<br/>' . - '</div>'; - } - - /** - * Show a deleted file version requested by the visitor. - */ - function showFile( $key ) { - global $wgOut, $wgRequest; - $wgOut->disable(); - - # We mustn't allow the output to be Squid cached, otherwise - # if an admin previews a deleted image, and it's cached, then - # a user without appropriate permissions can toddle off and - # nab the image, and Squid will serve it - $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); - $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); - $wgRequest->response()->header( 'Pragma: no-cache' ); - - $store = FileStore::get( 'deleted' ); - $store->stream( $key ); - } - - /* private */ function showHistory() { - global $wgLang, $wgContLang, $wgUser, $wgOut; - - $sk = $wgUser->getSkin(); - if ( $this->mAllowed ) { - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); - } else { - $wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) ); - } - - $archive = new PageArchive( $this->mTargetObj ); - /* - $text = $archive->getLastRevisionText(); - if( is_null( $text ) ) { - $wgOut->addWikiMsg( "nohistory" ); - return; - } - */ - if ( $this->mAllowed ) { - $wgOut->addWikiMsg( "undeletehistory" ); - } else { - $wgOut->addWikiMsg( "undeletehistorynoadmin" ); - } - - # List all stored revisions - $revisions = $archive->listRevisions(); - $files = $archive->listFiles(); - - $haveRevisions = $revisions && $revisions->numRows() > 0; - $haveFiles = $files && $files->numRows() > 0; - - # Batch existence check on user and talk pages - if( $haveRevisions ) { - $batch = new LinkBatch(); - while( $row = $revisions->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) ); - } - $batch->execute(); - $revisions->seek( 0 ); - } - if( $haveFiles ) { - $batch = new LinkBatch(); - while( $row = $files->fetchObject() ) { - $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); - $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) ); - } - $batch->execute(); - $files->seek( 0 ); - } - - if ( $this->mAllowed ) { - $titleObj = SpecialPage::getTitleFor( "Undelete" ); - $action = $titleObj->getLocalURL( "action=submit" ); - # Start the form here - $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) ); - $wgOut->addHtml( $top ); - } - - # Show relevant lines from the deletion log: - $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" ); - $logViewer = new LogViewer( - new LogReader( - new FauxRequest( - array( - 'page' => $this->mTargetObj->getPrefixedText(), - 'type' => 'delete' - ) - ) - ), LogViewer::NO_ACTION_LINK - ); - $logViewer->showList( $wgOut ); - - if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { - # Format the user-visible controls (comment field, submission button) - # in a nice little table - $align = $wgContLang->isRtl() ? 'left' : 'right'; - $table = - Xml::openElement( 'fieldset' ) . - Xml::openElement( 'table' ) . - "<tr> - <td colspan='2'>" . - wfMsgWikiHtml( 'undeleteextrahelp' ) . - "</td> - </tr> - <tr> - <td align='$align'>" . - Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) . - "</td> - <td>" . - Xml::input( 'wpComment', 50, $this->mComment ) . - "</td> - </tr> - <tr> - <td> </td> - <td>" . - Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . - Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . - "</td> - </tr>" . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ); - - $wgOut->addHtml( $table ); - } - - $wgOut->addHTML( "<h2>" . htmlspecialchars( wfMsg( "history" ) ) . "</h2>\n" ); - - if( $haveRevisions ) { - # The page's stored (deleted) history: - $wgOut->addHTML("<ul>"); - $target = urlencode( $this->mTarget ); - $remaining = $revisions->numRows(); - $earliestLiveTime = $this->getEarliestTime( $this->mTargetObj ); - - while( $row = $revisions->fetchObject() ) { - $remaining--; - $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); - if ( $this->mAllowed ) { - $checkBox = Xml::check( "ts$ts" ); - $pageLink = $sk->makeKnownLinkObj( $titleObj, - $wgLang->timeanddate( $ts, true ), - "target=$target×tamp=$ts" ); - if( ($remaining > 0) || - ($earliestLiveTime && $ts > $earliestLiveTime ) ) { - $diffLink = '(' . - $sk->makeKnownLinkObj( $titleObj, - wfMsgHtml( 'diff' ), - "target=$target×tamp=$ts&diff=prev" ) . - ')'; - } else { - // No older revision to diff against - $diffLink = ''; - } - } else { - $checkBox = ''; - $pageLink = $wgLang->timeanddate( $ts, true ); - $diffLink = ''; - } - $userLink = $sk->userLink( $row->ar_user, $row->ar_user_text ) . $sk->userToolLinks( $row->ar_user, $row->ar_user_text ); - $stxt = ''; - if (!is_null($size = $row->ar_len)) { - if ($size == 0) { - $stxt = wfMsgHtml('historyempty'); - } else { - $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); - } - } - $comment = $sk->commentBlock( $row->ar_comment ); - $wgOut->addHTML( "<li>$checkBox $pageLink $diffLink . . $userLink $stxt $comment</li>\n" ); - - } - $revisions->free(); - $wgOut->addHTML("</ul>"); - } else { - $wgOut->addWikiMsg( "nohistory" ); - } - - if( $haveFiles ) { - $wgOut->addHtml( "<h2>" . wfMsgHtml( 'filehist' ) . "</h2>\n" ); - $wgOut->addHtml( "<ul>" ); - while( $row = $files->fetchObject() ) { - $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); - if ( $this->mAllowed && $row->fa_storage_key ) { - $checkBox = Xml::check( "fileid" . $row->fa_id ); - $key = urlencode( $row->fa_storage_key ); - $target = urlencode( $this->mTarget ); - $pageLink = $sk->makeKnownLinkObj( $titleObj, - $wgLang->timeanddate( $ts, true ), - "target=$target&file=$key" ); - } else { - $checkBox = ''; - $pageLink = $wgLang->timeanddate( $ts, true ); - } - $userLink = $sk->userLink( $row->fa_user, $row->fa_user_text ) . $sk->userToolLinks( $row->fa_user, $row->fa_user_text ); - $data = - wfMsgHtml( 'widthheight', - $wgLang->formatNum( $row->fa_width ), - $wgLang->formatNum( $row->fa_height ) ) . - ' (' . - wfMsgHtml( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) . - ')'; - $comment = $sk->commentBlock( $row->fa_description ); - $wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $data $comment</li>\n" ); - } - $files->free(); - $wgOut->addHTML( "</ul>" ); - } - - if ( $this->mAllowed ) { - # Slip in the hidden controls here - $misc = Xml::hidden( 'target', $this->mTarget ); - $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); - $misc .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $misc ); - } - - return true; - } - - private function getEarliestTime( $title ) { - $dbr = wfGetDB( DB_SLAVE ); - if( $title->exists() ) { - $min = $dbr->selectField( 'revision', - 'MIN(rev_timestamp)', - array( 'rev_page' => $title->getArticleId() ), - __METHOD__ ); - return wfTimestampOrNull( TS_MW, $min ); - } - return null; - } - - function undelete() { - global $wgOut, $wgUser; - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - if( !is_null( $this->mTargetObj ) ) { - $archive = new PageArchive( $this->mTargetObj ); - - $ok = $archive->undelete( - $this->mTargetTimestamp, - $this->mComment, - $this->mFileVersions ); - - if( is_array($ok) ) { - $skin = $wgUser->getSkin(); - $link = $skin->makeKnownLinkObj( $this->mTargetObj ); - $wgOut->addHtml( wfMsgWikiHtml( 'undeletedpage', $link ) ); - } else { - $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); - } - - // Show file deletion warnings and errors - $status = $archive->getFileStatus(); - if ( $status && !$status->isGood() ) { - $wgOut->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) ); - } - } else { - $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); - } - return false; - } -} diff --git a/includes/SpecialUnlockdb.php b/includes/SpecialUnlockdb.php deleted file mode 100644 index 74b794dd..00000000 --- a/includes/SpecialUnlockdb.php +++ /dev/null @@ -1,110 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -function wfSpecialUnlockdb() { - global $wgUser, $wgOut, $wgRequest; - - if( !$wgUser->isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); - return; - } - - $action = $wgRequest->getVal( 'action' ); - $f = new DBUnlockForm(); - - if ( "success" == $action ) { - $f->showSuccess(); - } else if ( "submit" == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( "" ); - } -} - -/** - * - * @addtogroup SpecialPage - */ -class DBUnlockForm { - function showForm( $err ) - { - global $wgOut, $wgUser; - - global $wgReadOnlyFile; - if( !file_exists( $wgReadOnlyFile ) ) { - $wgOut->addWikiMsg( 'databasenotlocked' ); - return; - } - - $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); - $wgOut->addWikiMsg( "unlockdbtext" ); - - if ( "" != $err ) { - $wgOut->setSubtitle( wfMsg( "formerror" ) ); - $wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" ); - } - $lc = htmlspecialchars( wfMsg( "unlockconfirm" ) ); - $lb = htmlspecialchars( wfMsg( "unlockbtn" ) ); - $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); - $action = $titleObj->escapeLocalURL( "action=submit" ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( <<<END - -<form id="unlockdb" method="post" action="{$action}"> -<table border="0"> - <tr> - <td align="right"> - <input type="checkbox" name="wpLockConfirm" /> - </td> - <td align="left">{$lc}</td> - </tr> - <tr> - <td> </td> - <td align="left"> - <input type="submit" name="wpLock" value="{$lb}" /> - </td> - </tr> -</table> -<input type="hidden" name="wpEditToken" value="{$token}" /> -</form> -END -); - - } - - function doSubmit() { - global $wgOut, $wgRequest, $wgReadOnlyFile; - - $wpLockConfirm = $wgRequest->getCheck( 'wpLockConfirm' ); - if ( ! $wpLockConfirm ) { - $this->showForm( wfMsg( "locknoconfirm" ) ); - return; - } - if ( @! unlink( $wgReadOnlyFile ) ) { - $wgOut->showFileDeleteError( $wgReadOnlyFile ); - return; - } - $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); - $success = $titleObj->getFullURL( "action=success" ); - $wgOut->redirect( $success ); - } - - function showSuccess() { - global $wgOut; - global $ip; - - $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); - $wgOut->setSubtitle( wfMsg( "unlockdbsuccesssub" ) ); - $wgOut->addWikiMsg( "unlockdbsuccesstext", $ip ); - } -} - - diff --git a/includes/SpecialUnusedcategories.php b/includes/SpecialUnusedcategories.php deleted file mode 100644 index 492c5f84..00000000 --- a/includes/SpecialUnusedcategories.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - * @addtogroup SpecialPage - */ -class UnusedCategoriesPage extends QueryPage { - - function getName() { - return 'Unusedcategories'; - } - - function getPageHeader() { - return wfMsgExt( 'unusedcategoriestext', array( 'parse' ) ); - } - - function getSQL() { - $NScat = NS_CATEGORY; - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); - return "SELECT 'Unusedcategories' as type, - {$NScat} as namespace, page_title as title, page_title as value - FROM $page - LEFT JOIN $categorylinks ON page_title=cl_to - WHERE cl_from IS NULL - AND page_namespace = {$NScat} - AND page_is_redirect = 0"; - } - - function formatResult( $skin, $result ) { - $title = Title::makeTitle( NS_CATEGORY, $result->title ); - return $skin->makeLinkObj( $title, $title->getText() ); - } -} - -/** constructor */ -function wfSpecialUnusedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - $uc = new UnusedCategoriesPage(); - return $uc->doQuery( $offset, $limit ); -} - diff --git a/includes/SpecialUnusedimages.php b/includes/SpecialUnusedimages.php deleted file mode 100644 index 623137c0..00000000 --- a/includes/SpecialUnusedimages.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * implements Special:Unusedimages - * @addtogroup SpecialPage - */ -class UnusedimagesPage extends ImageQueryPage { - - function isExpensive() { return true; } - - function getName() { - return 'Unusedimages'; - } - - function sortDescending() { - return false; - } - function isSyndicated() { return false; } - - function getSQL() { - global $wgCountCategorizedImagesAsUsed; - $dbr = wfGetDB( DB_SLAVE ); - - if ( $wgCountCategorizedImagesAsUsed ) { - list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' ); - - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, - img_user, img_user_text, img_description - FROM ((($page AS I LEFT JOIN $categorylinks AS L ON I.page_id = L.cl_from) - LEFT JOIN $imagelinks AS P ON I.page_title = P.il_to) - INNER JOIN $image AS G ON I.page_title = G.img_name) - WHERE I.page_namespace = ".NS_IMAGE." AND L.cl_from IS NULL AND P.il_to IS NULL"; - } else { - list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' ); - - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, - img_user, img_user_text, img_description - FROM $image LEFT JOIN $imagelinks ON img_name=il_to WHERE il_to IS NULL "; - } - } - - function getPageHeader() { - return wfMsgExt( 'unusedimagestext', array( 'parse') ); - } - -} - -/** - * Entry point - */ -function wfSpecialUnusedimages() { - list( $limit, $offset ) = wfCheckLimits(); - $uip = new UnusedimagesPage(); - - return $uip->doQuery( $offset, $limit ); -} - diff --git a/includes/SpecialUnusedtemplates.php b/includes/SpecialUnusedtemplates.php deleted file mode 100644 index 79e99f3a..00000000 --- a/includes/SpecialUnusedtemplates.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -/** - * implements Special:Unusedtemplates - * @author Rob Church <robchur@gmail.com> - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - * @addtogroup SpecialPage - */ -class UnusedtemplatesPage extends QueryPage { - - function getName() { return( 'Unusedtemplates' ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } - function sortDescending() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' ); - $sql = "SELECT 'Unusedtemplates' AS type, page_title AS title, - page_namespace AS namespace, 0 AS value - FROM $page - LEFT JOIN $templatelinks - ON page_namespace = tl_namespace AND page_title = tl_title - WHERE page_namespace = 10 AND tl_from IS NULL"; - return $sql; - } - - function formatResult( $skin, $result ) { - $title = Title::makeTitle( NS_TEMPLATE, $result->title ); - $pageLink = $skin->makeKnownLinkObj( $title, '', 'redirect=no' ); - $wlhLink = $skin->makeKnownLinkObj( - SpecialPage::getTitleFor( 'Whatlinkshere' ), - wfMsgHtml( 'unusedtemplateswlh' ), - 'target=' . $title->getPrefixedUrl() ); - return wfSpecialList( $pageLink, $wlhLink ); - } - - function getPageHeader() { - return wfMsgExt( 'unusedtemplatestext', array( 'parse' ) ); - } - -} - -function wfSpecialUnusedtemplates() { - list( $limit, $offset ) = wfCheckLimits(); - $utp = new UnusedtemplatesPage(); - $utp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialUnwatchedpages.php b/includes/SpecialUnwatchedpages.php deleted file mode 100644 index b1883e97..00000000 --- a/includes/SpecialUnwatchedpages.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php -/** - * A special page that displays a list of pages that are not on anyones watchlist. - * Implements Special:Unwatchedpages - * - * @addtogroup SpecialPage - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class UnwatchedpagesPage extends QueryPage { - - function getName() { return 'Unwatchedpages'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' ); - $mwns = NS_MEDIAWIKI; - return - " - SELECT - 'Unwatchedpages' as type, - page_namespace as namespace, - page_title as title, - page_namespace as value - FROM $page - LEFT JOIN $watchlist ON wl_namespace = page_namespace AND page_title = wl_title - WHERE wl_title IS NULL AND page_is_redirect = 0 AND page_namespace<>$mwns - "; - } - - function sortDescending() { return false; } - - function formatResult( $skin, $result ) { - global $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->makeKnownLinkObj( $nt, htmlspecialchars( $text ) ); - $wlink = $skin->makeKnownLinkObj( $nt, wfMsgHtml( 'watch' ), 'action=watch' ); - - return wfSpecialList( $plink, $wlink ); - } -} - -/** - * constructor - */ -function wfSpecialUnwatchedpages() { - global $wgUser, $wgOut; - - if ( ! $wgUser->isAllowed( 'unwatchedpages' ) ) - return $wgOut->permissionRequired( 'unwatchedpages' ); - - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new UnwatchedpagesPage(); - - $wpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialUpload.php b/includes/SpecialUpload.php deleted file mode 100644 index 36bae4f7..00000000 --- a/includes/SpecialUpload.php +++ /dev/null @@ -1,1646 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - - -/** - * Entry point - */ -function wfSpecialUpload() { - global $wgRequest; - $form = new UploadForm( $wgRequest ); - $form->execute(); -} - -/** - * implements Special:Upload - * @addtogroup SpecialPage - */ -class UploadForm { - const SUCCESS = 0; - const BEFORE_PROCESSING = 1; - const LARGE_FILE_SERVER = 2; - const EMPTY_FILE = 3; - const MIN_LENGHT_PARTNAME = 4; - const ILLEGAL_FILENAME = 5; - const PROTECTED_PAGE = 6; - const OVERWRITE_EXISTING_FILE = 7; - const FILETYPE_MISSING = 8; - const FILETYPE_BADTYPE = 9; - const VERIFICATION_ERROR = 10; - const UPLOAD_VERIFICATION_ERROR = 11; - const UPLOAD_WARNING = 12; - const INTERNAL_ERROR = 13; - - /**#@+ - * @access private - */ - var $mComment, $mLicense, $mIgnoreWarning, $mCurlError; - var $mDestName, $mTempPath, $mFileSize, $mFileProps; - var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked; - var $mSrcName, $mSessionKey, $mStashed, $mDesiredDestName, $mRemoveTempFile, $mSourceType; - var $mDestWarningAck, $mCurlDestHandle; - var $mLocalFile; - - # Placeholders for text injection by hooks (must be HTML) - # extensions should take care to _append_ to the present value - var $uploadFormTextTop; - var $uploadFormTextAfterSummary; - - const SESSION_VERSION = 1; - /**#@-*/ - - /** - * Constructor : initialise object - * Get data POSTed through the form and assign them to the object - * @param $request Data posted. - */ - function UploadForm( &$request ) { - global $wgAllowCopyUploads; - $this->mDesiredDestName = $request->getText( 'wpDestFile' ); - $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ); - $this->mComment = $request->getText( 'wpUploadDescription' ); - - if( !$request->wasPosted() ) { - # GET requests just give the main form; no data except destination - # filename and description - return; - } - - # Placeholders for text injection by hooks (empty per default) - $this->uploadFormTextTop = ""; - $this->uploadFormTextAfterSummary = ""; - - $this->mReUpload = $request->getCheck( 'wpReUpload' ); - $this->mUploadClicked = $request->getCheck( 'wpUpload' ); - - $this->mLicense = $request->getText( 'wpLicense' ); - $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); - $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); - $this->mWatchthis = $request->getBool( 'wpWatchthis' ); - $this->mSourceType = $request->getText( 'wpSourceType' ); - $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); - - $this->mAction = $request->getVal( 'action' ); - - $this->mSessionKey = $request->getInt( 'wpSessionKey' ); - if( !empty( $this->mSessionKey ) && - isset( $_SESSION['wsUploadData'][$this->mSessionKey]['version'] ) && - $_SESSION['wsUploadData'][$this->mSessionKey]['version'] == self::SESSION_VERSION ) { - /** - * Confirming a temporarily stashed upload. - * We don't want path names to be forged, so we keep - * them in the session on the server and just give - * an opaque key to the user agent. - */ - $data = $_SESSION['wsUploadData'][$this->mSessionKey]; - $this->mTempPath = $data['mTempPath']; - $this->mFileSize = $data['mFileSize']; - $this->mSrcName = $data['mSrcName']; - $this->mFileProps = $data['mFileProps']; - $this->mCurlError = 0/*UPLOAD_ERR_OK*/; - $this->mStashed = true; - $this->mRemoveTempFile = false; - } else { - /** - *Check for a newly uploaded file. - */ - if( $wgAllowCopyUploads && $this->mSourceType == 'web' ) { - $this->initializeFromUrl( $request ); - } else { - $this->initializeFromUpload( $request ); - } - } - } - - /** - * Initialize the uploaded file from PHP data - * @access private - */ - function initializeFromUpload( $request ) { - $this->mTempPath = $request->getFileTempName( 'wpUploadFile' ); - $this->mFileSize = $request->getFileSize( 'wpUploadFile' ); - $this->mSrcName = $request->getFileName( 'wpUploadFile' ); - $this->mCurlError = $request->getUploadError( 'wpUploadFile' ); - $this->mSessionKey = false; - $this->mStashed = false; - $this->mRemoveTempFile = false; // PHP will handle this - } - - /** - * Copy a web file to a temporary file - * @access private - */ - function initializeFromUrl( $request ) { - global $wgTmpDirectory; - $url = $request->getText( 'wpUploadFileURL' ); - $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); - - $this->mTempPath = $local_file; - $this->mFileSize = 0; # Will be set by curlCopy - $this->mCurlError = $this->curlCopy( $url, $local_file ); - $pathParts = explode( '/', $url ); - $this->mSrcName = array_pop( $pathParts ); - $this->mSessionKey = false; - $this->mStashed = false; - - // PHP won't auto-cleanup the file - $this->mRemoveTempFile = file_exists( $local_file ); - } - - /** - * Safe copy from URL - * Returns true if there was an error, false otherwise - */ - private function curlCopy( $url, $dest ) { - global $wgUser, $wgOut; - - if( !$wgUser->isAllowed( 'upload_by_url' ) ) { - $wgOut->permissionRequired( 'upload_by_url' ); - return true; - } - - # Maybe remove some pasting blanks :-) - $url = trim( $url ); - if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) { - # Only HTTP or FTP URLs - $wgOut->errorPage( 'upload-proto-error', 'upload-proto-error-text' ); - return true; - } - - # Open temporary file - $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" ); - if( $this->mCurlDestHandle === false ) { - # Could not open temporary file to write in - $wgOut->errorPage( 'upload-file-error', 'upload-file-error-text'); - return true; - } - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug - curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout - curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed - curl_setopt( $ch, CURLOPT_URL, $url); - curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) ); - curl_exec( $ch ); - $error = curl_errno( $ch ) ? true : false; - $errornum = curl_errno( $ch ); - // if ( $error ) print curl_error ( $ch ) ; # Debugging output - curl_close( $ch ); - - fclose( $this->mCurlDestHandle ); - unset( $this->mCurlDestHandle ); - if( $error ) { - unlink( $dest ); - if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) ) - $wgOut->errorPage( 'upload-misc-error', 'upload-misc-error-text' ); - else - $wgOut->errorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" ); - } - - return $error; - } - - /** - * Callback function for CURL-based web transfer - * Write data to file unless we've passed the length limit; - * if so, abort immediately. - * @access private - */ - function uploadCurlCallback( $ch, $data ) { - global $wgMaxUploadSize; - $length = strlen( $data ); - $this->mFileSize += $length; - if( $this->mFileSize > $wgMaxUploadSize ) { - return 0; - } - fwrite( $this->mCurlDestHandle, $data ); - return $length; - } - - /** - * Start doing stuff - * @access public - */ - function execute() { - global $wgUser, $wgOut; - global $wgEnableUploads; - - # Check uploading enabled - if( !$wgEnableUploads ) { - $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext', array( $this->mDesiredDestName ) ); - return; - } - - # Check permissions - if( !$wgUser->isAllowed( 'upload' ) ) { - if( !$wgUser->isLoggedIn() ) { - $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' ); - } else { - $wgOut->permissionRequired( 'upload' ); - } - return; - } - - # Check blocks - if( $wgUser->isBlocked() ) { - $wgOut->blockedPage(); - return; - } - - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - if( $this->mReUpload ) { - if( !$this->unsaveUploadedFile() ) { - return; - } - $this->mainUploadForm(); - } else if( 'submit' == $this->mAction || $this->mUploadClicked ) { - $this->processUpload(); - } else { - $this->mainUploadForm(); - } - - $this->cleanupTempFile(); - } - - /** - * Do the upload - * Checks are made in SpecialUpload::execute() - * - * @access private - */ - function processUpload(){ - global $wgUser, $wgOut, $wgFileExtensions; - $details = null; - $value = null; - $value = $this->internalProcessUpload( $details ); - - switch($value) { - case self::SUCCESS: - $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() ); - break; - - case self::BEFORE_PROCESSING: - break; - - case self::LARGE_FILE_SERVER: - $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) ); - break; - - case self::EMPTY_FILE: - $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) ); - break; - - case self::MIN_LENGHT_PARTNAME: - $this->mainUploadForm( wfMsgHtml( 'minlength1' ) ); - break; - - case self::ILLEGAL_FILENAME: - $filtered = $details['filtered']; - $this->uploadError( wfMsgWikiHtml( 'illegalfilename', htmlspecialchars( $filtered ) ) ); - break; - - case self::PROTECTED_PAGE: - $this->uploadError( wfMsgWikiHtml( 'protectedpage' ) ); - break; - - case self::OVERWRITE_EXISTING_FILE: - $errorText = $details['overwrite']; - $overwrite = new WikiError( $wgOut->parse( $errorText ) ); - $this->uploadError( $overwrite->toString() ); - break; - - case self::FILETYPE_MISSING: - $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) ); - break; - - case self::FILETYPE_BADTYPE: - $finalExt = $details['finalExt']; - $this->uploadError( - wfMsgExt( 'filetype-banned-type', - array( 'parseinline' ), - htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ) - ) - ); - break; - - case self::VERIFICATION_ERROR: - $veri = $details['veri']; - $this->uploadError( $veri->toString() ); - break; - - case self::UPLOAD_VERIFICATION_ERROR: - $error = $details['error']; - $this->uploadError( $error ); - break; - - case self::UPLOAD_WARNING: - $warning = $details['warning']; - $this->uploadWarning( $warning ); - break; - - case self::INTERNAL_ERROR: - $internal = $details['internal']; - $this->showError( $internal ); - break; - - default: - throw new MWException( __METHOD__ . ": Unknown value `{$value}`" ); - } - } - - /** - * Really do the upload - * Checks are made in SpecialUpload::execute() - * - * @param array $resultDetails contains result-specific dict of additional values - * - * @access private - */ - function internalProcessUpload( &$resultDetails ) { - global $wgUser; - - if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) - { - wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file." ); - return self::BEFORE_PROCESSING; - } - - /* Check for PHP error if any, requires php 4.2 or newer */ - if( $this->mCurlError == 1/*UPLOAD_ERR_INI_SIZE*/ ) { - return self::LARGE_FILE_SERVER; - } - - /** - * If there was no filename or a zero size given, give up quick. - */ - if( trim( $this->mSrcName ) == '' || empty( $this->mFileSize ) ) { - return self::EMPTY_FILE; - } - - # Chop off any directories in the given filename - if( $this->mDesiredDestName ) { - $basename = $this->mDesiredDestName; - } else { - $basename = $this->mSrcName; - } - $filtered = wfBaseName( $basename ); - - /** - * We'll want to blacklist against *any* 'extension', and use - * only the final one for the whitelist. - */ - list( $partname, $ext ) = $this->splitExtensions( $filtered ); - - if( count( $ext ) ) { - $finalExt = $ext[count( $ext ) - 1]; - } else { - $finalExt = ''; - } - - # If there was more than one "extension", reassemble the base - # filename to prevent bogus complaints about length - if( count( $ext ) > 1 ) { - for( $i = 0; $i < count( $ext ) - 1; $i++ ) - $partname .= '.' . $ext[$i]; - } - - if( strlen( $partname ) < 1 ) { - return self::MIN_LENGHT_PARTNAME; - } - - /** - * Filter out illegal characters, and try to make a legible name - * out of it. We'll strip some silently that Title would die on. - */ - $filtered = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $filtered ); - $nt = Title::makeTitleSafe( NS_IMAGE, $filtered ); - if( is_null( $nt ) ) { - $resultDetails = array( 'filtered' => $filtered ); - return self::ILLEGAL_FILENAME; - } - $this->mLocalFile = wfLocalFile( $nt ); - $this->mDestName = $this->mLocalFile->getName(); - - /** - * If the image is protected, non-sysop users won't be able - * to modify it by uploading a new revision. - */ - if( !$nt->userCan( 'edit' ) || !$nt->userCan( 'create' ) ) { - return self::PROTECTED_PAGE; - } - - /** - * In some cases we may forbid overwriting of existing files. - */ - $overwrite = $this->checkOverwrite( $this->mDestName ); - if( $overwrite !== true ) { - $resultDetails = array( 'overwrite' => $overwrite ); - return self::OVERWRITE_EXISTING_FILE; - } - - /* Don't allow users to override the blacklist (check file extension) */ - global $wgStrictFileExtensions; - global $wgFileExtensions, $wgFileBlacklist; - if ($finalExt == '') { - return self::FILETYPE_MISSING; - } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || - ($wgStrictFileExtensions && !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) { - $resultDetails = array( 'finalExt' => $finalExt ); - return self::FILETYPE_BADTYPE; - } - - /** - * Look at the contents of the file; if we can recognize the - * type but it's corrupt or data of the wrong type, we should - * probably not accept it. - */ - if( !$this->mStashed ) { - $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $finalExt ); - $this->checkMacBinary(); - $veri = $this->verify( $this->mTempPath, $finalExt ); - - if( $veri !== true ) { //it's a wiki error... - $resultDetails = array( 'veri' => $veri ); - return self::VERIFICATION_ERROR; - } - - /** - * Provide an opportunity for extensions to add further checks - */ - $error = ''; - if( !wfRunHooks( 'UploadVerification', - array( $this->mDestName, $this->mTempPath, &$error ) ) ) { - $resultDetails = array( 'error' => $error ); - return self::UPLOAD_VERIFICATION_ERROR; - } - } - - - /** - * Check for non-fatal conditions - */ - if ( ! $this->mIgnoreWarning ) { - $warning = ''; - - global $wgCapitalLinks; - if( $wgCapitalLinks ) { - $filtered = ucfirst( $filtered ); - } - if( $basename != $filtered ) { - $warning .= '<li>'.wfMsgHtml( 'badfilename', htmlspecialchars( $this->mDestName ) ).'</li>'; - } - - global $wgCheckFileExtensions; - if ( $wgCheckFileExtensions ) { - if ( !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) { - $warning .= '<li>' . - wfMsgExt( 'filetype-unwanted-type', - array( 'parseinline' ), - htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ) - ) . '</li>'; - } - } - - global $wgUploadSizeWarning; - if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) { - $skin = $wgUser->getSkin(); - $wsize = $skin->formatSize( $wgUploadSizeWarning ); - $asize = $skin->formatSize( $this->mFileSize ); - $warning .= '<li>' . wfMsgHtml( 'large-file', $wsize, $asize ) . '</li>'; - } - if ( $this->mFileSize == 0 ) { - $warning .= '<li>'.wfMsgHtml( 'emptyfile' ).'</li>'; - } - - if ( !$this->mDestWarningAck ) { - $warning .= self::getExistsWarning( $this->mLocalFile ); - } - if( $warning != '' ) { - /** - * Stash the file in a temporary location; the user can choose - * to let it through and we'll complete the upload then. - */ - $resultDetails = array( 'warning' => $warning ); - return self::UPLOAD_WARNING; - } - } - - /** - * Try actually saving the thing... - * It will show an error form on failure. - */ - $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, - $this->mCopyrightStatus, $this->mCopyrightSource ); - - $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText, - File::DELETE_SOURCE, $this->mFileProps ); - if ( !$status->isGood() ) { - $resultDetails = array( 'internal' => $status->getWikiText() ); - return self::INTERNAL_ERROR; - } else { - if ( $this->mWatchthis ) { - global $wgUser; - $wgUser->addWatch( $this->mLocalFile->getTitle() ); - } - // Success, redirect to description page - $img = null; // @todo: added to avoid passing a ref to null - should this be defined somewhere? - wfRunHooks( 'UploadComplete', array( &$this ) ); - return self::SUCCESS; - } - } - - /** - * Do existence checks on a file and produce a warning - * This check is static and can be done pre-upload via AJAX - * Returns an HTML fragment consisting of one or more LI elements if there is a warning - * Returns an empty string if there is no warning - */ - static function getExistsWarning( $file ) { - global $wgUser, $wgContLang; - // Check for uppercase extension. We allow these filenames but check if an image - // with lowercase extension exists already - $warning = ''; - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - if( strpos( $file->getName(), '.' ) == false ) { - $partname = $file->getName(); - $rawExtension = ''; - } else { - list( $partname, $rawExtension ) = explode( '.', $file->getName(), 2 ); - } - $sk = $wgUser->getSkin(); - - if ( $rawExtension != $file->getExtension() ) { - // We're not using the normalized form of the extension. - // Normal form is lowercase, using most common of alternate - // extensions (eg 'jpg' rather than 'JPEG'). - // - // Check for another file using the normalized form... - $nt_lc = Title::newFromText( $partname . '.' . $file->getExtension() ); - $file_lc = wfLocalFile( $nt_lc ); - } else { - $file_lc = false; - } - - if( $file->exists() ) { - $dlink = $sk->makeKnownLinkObj( $file->getTitle() ); - if ( $file->allowInlineDisplay() ) { - $dlink2 = $sk->makeImageLinkObj( $file->getTitle(), wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $file->getName(), $align, array(), false, true ); - } elseif ( !$file->allowInlineDisplay() && $file->isSafeFile() ) { - $icon = $file->iconThumb(); - $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' . - $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>'; - } else { - $dlink2 = ''; - } - - $warning .= '<li>' . wfMsgExt( 'fileexists', array(), $dlink ) . '</li>' . $dlink2; - - } elseif( $file->getTitle()->getArticleID() ) { - $lnk = $sk->makeKnownLinkObj( $file->getTitle(), '', 'redirect=no' ); - $warning .= '<li>' . wfMsgExt( 'filepageexists', array(), $lnk ) . '</li>'; - } elseif ( $file_lc && $file_lc->exists() ) { - # Check if image with lowercase extension exists. - # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension - $dlink = $sk->makeKnownLinkObj( $nt_lc ); - if ( $file_lc->allowInlineDisplay() ) { - $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $nt_lc->getText(), $align, array(), false, true ); - } elseif ( !$file_lc->allowInlineDisplay() && $file_lc->isSafeFile() ) { - $icon = $file_lc->iconThumb(); - $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' . - $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>'; - } else { - $dlink2 = ''; - } - - $warning .= '<li>' . wfMsgExt( 'fileexists-extension', 'parsemag', $file->getName(), $dlink ) . '</li>' . $dlink2; - - } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' ) - && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) ) - { - # Check for filenames like 50px- or 180px-, these are mostly thumbnails - $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension ); - $file_thb = wfLocalFile( $nt_thb ); - if ($file_thb->exists() ) { - # Check if an image without leading '180px-' (or similiar) exists - $dlink = $sk->makeKnownLinkObj( $nt_thb); - if ( $file_thb->allowInlineDisplay() ) { - $dlink2 = $sk->makeImageLinkObj( $nt_thb, - wfMsgExt( 'fileexists-thumb', 'parseinline' ), - $nt_thb->getText(), $align, array(), false, true ); - } elseif ( !$file_thb->allowInlineDisplay() && $file_thb->isSafeFile() ) { - $icon = $file_thb->iconThumb(); - $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' . - $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . - $dlink . '</div>'; - } else { - $dlink2 = ''; - } - - $warning .= '<li>' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) . - '</li>' . $dlink2; - } else { - # Image w/o '180px-' does not exists, but we do not like these filenames - $warning .= '<li>' . wfMsgExt( 'file-thumbnail-no', 'parseinline' , - substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '</li>'; - } - } - - $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist(); - # Do the match - foreach( $filenamePrefixBlacklist as $prefix ) { - if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { - $warning .= '<li>' . wfMsgExt( 'filename-bad-prefix', 'parseinline', $prefix ) . '</li>'; - break; - } - } - - if ( $file->wasDeleted() && !$file->exists() ) { - # If the file existed before and was deleted, warn the user of this - # Don't bother doing so if the file exists now, however - $ltitle = SpecialPage::getTitleFor( 'Log' ); - $llink = $sk->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ), - 'type=delete&page=' . $file->getTitle()->getPrefixedUrl() ); - $warning .= '<li>' . wfMsgWikiHtml( 'filewasdeleted', $llink ) . '</li>'; - } - return $warning; - } - - /** - * Get a list of warnings - * - * @param string local filename, e.g. 'file exists', 'non-descriptive filename' - * @return array list of warning messages - */ - static function ajaxGetExistsWarning( $filename ) { - $file = wfFindFile( $filename ); - if( !$file ) { - // Force local file so we have an object to do further checks against - // if there isn't an exact match... - $file = wfLocalFile( $filename ); - } - $s = ' '; - if ( $file ) { - $warning = self::getExistsWarning( $file ); - if ( $warning !== '' ) { - $s = "<ul>$warning</ul>"; - } - } - return $s; - } - - /** - * Render a preview of a given license for the AJAX preview on upload - * - * @param string $license - * @return string - */ - public static function ajaxGetLicensePreview( $license ) { - global $wgParser, $wgUser; - $text = '{{' . $license . '}}'; - $title = Title::makeTitle( NS_IMAGE, 'Sample.jpg' ); - $options = ParserOptions::newFromUser( $wgUser ); - - // Expand subst: first, then live templates... - $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options ); - $output = $wgParser->parse( $text, $title, $options ); - - return $output->getText(); - } - - /** - * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]] - * - * @return array list of prefixes - */ - public static function getFilenamePrefixBlacklist() { - $blacklist = array(); - $message = wfMsgForContent( 'filename-prefix-blacklist' ); - if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) { - $lines = explode( "\n", $message ); - foreach( $lines as $line ) { - // Remove comment lines - $comment = substr( trim( $line ), 0, 1 ); - if ( $comment == '#' || $comment == '' ) { - continue; - } - // Remove additional comments after a prefix - $comment = strpos( $line, '#' ); - if ( $comment > 0 ) { - $line = substr( $line, 0, $comment-1 ); - } - $blacklist[] = trim( $line ); - } - } - return $blacklist; - } - - /** - * Stash a file in a temporary directory for later processing - * after the user has confirmed it. - * - * If the user doesn't explicitly cancel or accept, these files - * can accumulate in the temp directory. - * - * @param string $saveName - the destination filename - * @param string $tempName - the source temporary file to save - * @return string - full path the stashed file, or false on failure - * @access private - */ - function saveTempUploadedFile( $saveName, $tempName ) { - global $wgOut; - $repo = RepoGroup::singleton()->getLocalRepo(); - $status = $repo->storeTemp( $saveName, $tempName ); - if ( !$status->isGood() ) { - $this->showError( $status->getWikiText() ); - return false; - } else { - return $status->value; - } - } - - /** - * Stash a file in a temporary directory for later processing, - * and save the necessary descriptive info into the session. - * Returns a key value which will be passed through a form - * to pick up the path info on a later invocation. - * - * @return int - * @access private - */ - function stashSession() { - $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); - - if( !$stash ) { - # Couldn't save the file. - return false; - } - - $key = mt_rand( 0, 0x7fffffff ); - $_SESSION['wsUploadData'][$key] = array( - 'mTempPath' => $stash, - 'mFileSize' => $this->mFileSize, - 'mSrcName' => $this->mSrcName, - 'mFileProps' => $this->mFileProps, - 'version' => self::SESSION_VERSION, - ); - return $key; - } - - /** - * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @access private - * @return success - */ - function unsaveUploadedFile() { - global $wgOut; - $repo = RepoGroup::singleton()->getLocalRepo(); - $success = $repo->freeTemp( $this->mTempPath ); - if ( ! $success ) { - $wgOut->showFileDeleteError( $this->mTempPath ); - return false; - } else { - return true; - } - } - - /* -------------------------------------------------------------- */ - - /** - * @param string $error as HTML - * @access private - */ - function uploadError( $error ) { - global $wgOut; - $wgOut->addHTML( "<h2>" . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" ); - $wgOut->addHTML( "<span class='error'>{$error}</span>\n" ); - } - - /** - * There's something wrong with this file, not enough to reject it - * totally but we require manual intervention to save it for real. - * Stash it away, then present a form asking to confirm or cancel. - * - * @param string $warning as HTML - * @access private - */ - function uploadWarning( $warning ) { - global $wgOut, $wgContLang; - global $wgUseCopyrightUpload; - - $this->mSessionKey = $this->stashSession(); - if( !$this->mSessionKey ) { - # Couldn't save file; an error has been displayed so let's go. - return; - } - - $wgOut->addHTML( "<h2>" . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" ); - $wgOut->addHTML( "<ul class='warning'>{$warning}</ul><br />\n" ); - - $save = wfMsgHtml( 'savefile' ); - $reupload = wfMsgHtml( 'reupload' ); - $iw = wfMsgWikiHtml( 'ignorewarning' ); - $reup = wfMsgWikiHtml( 'reuploaddesc' ); - $titleObj = SpecialPage::getTitleFor( 'Upload' ); - $action = $titleObj->escapeLocalURL( 'action=submit' ); - $align1 = $wgContLang->isRTL() ? 'left' : 'right'; - $align2 = $wgContLang->isRTL() ? 'right' : 'left'; - - if ( $wgUseCopyrightUpload ) - { - $copyright = " - <input type='hidden' name='wpUploadCopyStatus' value=\"" . htmlspecialchars( $this->mCopyrightStatus ) . "\" /> - <input type='hidden' name='wpUploadSource' value=\"" . htmlspecialchars( $this->mCopyrightSource ) . "\" /> - "; - } else { - $copyright = ""; - } - - $wgOut->addHTML( " - <form id='uploadwarning' method='post' enctype='multipart/form-data' action='$action'> - <input type='hidden' name='wpIgnoreWarning' value='1' /> - <input type='hidden' name='wpSessionKey' value=\"" . htmlspecialchars( $this->mSessionKey ) . "\" /> - <input type='hidden' name='wpUploadDescription' value=\"" . htmlspecialchars( $this->mComment ) . "\" /> - <input type='hidden' name='wpLicense' value=\"" . htmlspecialchars( $this->mLicense ) . "\" /> - <input type='hidden' name='wpDestFile' value=\"" . htmlspecialchars( $this->mDesiredDestName ) . "\" /> - <input type='hidden' name='wpWatchthis' value=\"" . htmlspecialchars( intval( $this->mWatchthis ) ) . "\" /> - {$copyright} - <table border='0'> - <tr> - <tr> - <td align='$align1'> - <input tabindex='2' type='submit' name='wpUpload' value=\"$save\" /> - </td> - <td align='$align2'>$iw</td> - </tr> - <tr> - <td align='$align1'> - <input tabindex='2' type='submit' name='wpReUpload' value=\"{$reupload}\" /> - </td> - <td align='$align2'>$reup</td> - </tr> - </tr> - </table></form>\n" ); - } - - /** - * Displays the main upload form, optionally with a highlighted - * error message up at the top. - * - * @param string $msg as HTML - * @access private - */ - function mainUploadForm( $msg='' ) { - global $wgOut, $wgUser, $wgContLang; - global $wgUseCopyrightUpload, $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview; - global $wgRequest, $wgAllowCopyUploads; - global $wgStylePath, $wgStyleVersion; - - $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck; - $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview; - - $adc = wfBoolToStr( $useAjaxDestCheck ); - $alp = wfBoolToStr( $useAjaxLicensePreview ); - - $wgOut->addScript( "<script type=\"text/javascript\"> -wgAjaxUploadDestCheck = {$adc}; -wgAjaxLicensePreview = {$alp}; -</script> -<script type=\"text/javascript\" src=\"{$wgStylePath}/common/upload.js?{$wgStyleVersion}\"></script> - " ); - - if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) - { - wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); - return false; - } - - if( $this->mDesiredDestName ) { - $title = Title::makeTitleSafe( NS_IMAGE, $this->mDesiredDestName ); - // Show a subtitle link to deleted revisions (to sysops et al only) - if( $title instanceof Title && ( $count = $title->isDeleted() ) > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) { - $link = wfMsgExt( - $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted', - array( 'parse', 'replaceafter' ), - $wgUser->getSkin()->makeKnownLinkObj( - SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), - wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count ) - ) - ); - $wgOut->addHtml( "<div id=\"contentSub2\">{$link}</div>" ); - } - - // Show the relevant lines from deletion log (for still deleted files only) - if( $title instanceof Title && $title->isDeleted() > 0 && !$title->exists() ) { - $this->showDeletionLog( $wgOut, $title->getPrefixedText() ); - } - } - - $cols = intval($wgUser->getOption( 'cols' )); - - if( $wgUser->getOption( 'editwidth' ) ) { - $width = " style=\"width:100%\""; - } else { - $width = ''; - } - - if ( '' != $msg ) { - $sub = wfMsgHtml( 'uploaderror' ); - $wgOut->addHTML( "<h2>{$sub}</h2>\n" . - "<span class='error'>{$msg}</span>\n" ); - } - $wgOut->addHTML( '<div id="uploadtext">' ); - $wgOut->addWikiMsg( 'uploadtext', $this->mDesiredDestName ); - $wgOut->addHTML( "</div>\n" ); - - # Print a list of allowed file extensions, if so configured. We ignore - # MIME type here, it's incomprehensible to most people and too long. - global $wgCheckFileExtensions, $wgStrictFileExtensions, - $wgFileExtensions, $wgFileBlacklist; - if( $wgCheckFileExtensions ) { - $delim = wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ); - if( $wgStrictFileExtensions ) { - # Everything not permitted is banned - $wgOut->addHTML( - '<div id="mw-upload-permitted">' . - wfMsgWikiHtml( 'upload-permitted', implode( $wgFileExtensions, $delim ) ) . - "</div>\n" - ); - } else { - # We have to list both preferred and prohibited - $wgOut->addHTML( - '<div id="mw-upload-preferred">' . - wfMsgWikiHtml( 'upload-preferred', implode( $wgFileExtensions, $delim ) ) . - "</div>\n" . - '<div id="mw-upload-prohibited">' . - wfMsgWikiHtml( 'upload-prohibited', implode( $wgFileBlacklist, $delim ) ) . - "</div>\n" - ); - } - } - - $sourcefilename = wfMsgHtml( 'sourcefilename' ); - $destfilename = wfMsgHtml( 'destfilename' ); - $summary = wfMsgExt( 'fileuploadsummary', 'parseinline' ); - - $licenses = new Licenses(); - $license = wfMsgExt( 'license', array( 'parseinline' ) ); - $nolicense = wfMsgHtml( 'nolicense' ); - $licenseshtml = $licenses->getHtml(); - - $ulb = wfMsgHtml( 'uploadbtn' ); - - - $titleObj = SpecialPage::getTitleFor( 'Upload' ); - $action = $titleObj->escapeLocalURL(); - - $encDestName = htmlspecialchars( $this->mDesiredDestName ); - - $watchChecked = - ( $wgUser->getOption( 'watchdefault' ) || - ( $wgUser->getOption( 'watchcreations' ) && $this->mDesiredDestName == '' ) ) - ? 'checked="checked"' - : ''; - $warningChecked = $this->mIgnoreWarning ? 'checked' : ''; - - // Prepare form for upload or upload/copy - if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) { - $filename_form = - "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' " . - "onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked />" . - "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " . - "onfocus='" . - "toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");" . - "toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")'" . - ($this->mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . "size='40' />" . - wfMsgHTML( 'upload_source_file' ) . "<br/>" . - "<input type='radio' id='wpSourceTypeURL' name='wpSourceType' value='web' " . - "onchange='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\")' />" . - "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' " . - "onfocus='" . - "toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");" . - "toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")'" . - ($this->mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFileURL\")' ") . "size='40' DISABLED />" . - wfMsgHtml( 'upload_source_url' ) ; - } else { - $filename_form = - "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " . - ($this->mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . - "size='40' />" . - "<input type='hidden' name='wpSourceType' value='file' />" ; - } - if ( $useAjaxDestCheck ) { - $warningRow = "<tr><td colspan='2' id='wpDestFile-warning'> </td></tr>"; - $destOnkeyup = 'onkeyup="wgUploadWarningObj.keypress();"'; - } else { - $warningRow = ''; - $destOnkeyup = ''; - } - - $encComment = htmlspecialchars( $this->mComment ); - $align1 = $wgContLang->isRTL() ? 'left' : 'right'; - $align2 = $wgContLang->isRTL() ? 'right' : 'left'; - - $wgOut->addHTML( <<<EOT - <form id='upload' method='post' enctype='multipart/form-data' action="$action"> - <table border='0'> - <tr> - {$this->uploadFormTextTop} - <td align='$align1' valign='top'><label for='wpUploadFile'>{$sourcefilename}:</label></td> - <td align='$align2'> - {$filename_form} - </td> - </tr> - <tr> - <td align='$align1'><label for='wpDestFile'>{$destfilename}:</label></td> - <td align='$align2'> - <input tabindex='2' type='text' name='wpDestFile' id='wpDestFile' size='40' - value="$encDestName" $destOnkeyup /> - </td> - </tr> - <tr> - <td align='$align1'><label for='wpUploadDescription'>{$summary}</label></td> - <td align='$align2'> - <textarea tabindex='3' name='wpUploadDescription' id='wpUploadDescription' rows='6' - cols='{$cols}'{$width}>$encComment</textarea> - {$this->uploadFormTextAfterSummary} - </td> - </tr> - <tr> -EOT - ); - - if ( $licenseshtml != '' ) { - global $wgStylePath; - $wgOut->addHTML( " - <td align='$align1'><label for='wpLicense'>$license:</label></td> - <td align='$align2'> - <select name='wpLicense' id='wpLicense' tabindex='4' - onchange='licenseSelectorCheck()'> - <option value=''>$nolicense</option> - $licenseshtml - </select> - </td> - </tr> - <tr>" ); - if( $useAjaxLicensePreview ) { - $wgOut->addHtml( " - <td></td> - <td id=\"mw-license-preview\"></td> - </tr> - <tr>" ); - } - } - - if ( $wgUseCopyrightUpload ) { - $filestatus = wfMsgHtml ( 'filestatus' ); - $copystatus = htmlspecialchars( $this->mCopyrightStatus ); - $filesource = wfMsgHtml ( 'filesource' ); - $uploadsource = htmlspecialchars( $this->mCopyrightSource ); - - $wgOut->addHTML( " - <td align='$align1' nowrap='nowrap'><label for='wpUploadCopyStatus'>$filestatus:</label></td> - <td><input tabindex='5' type='text' name='wpUploadCopyStatus' id='wpUploadCopyStatus' - value=\"$copystatus\" size='40' /></td> - </tr> - <tr> - <td align='$align1'><label for='wpUploadCopyStatus'>$filesource:</label></td> - <td><input tabindex='6' type='text' name='wpUploadSource' id='wpUploadCopyStatus' - value=\"$uploadsource\" size='40' /></td> - </tr> - <tr> - "); - } - - $wgOut->addHtml( " - <td></td> - <td> - <input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' /> - <label for='wpWatchthis'>" . wfMsgHtml( 'watchthisupload' ) . "</label> - <input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' $warningChecked/> - <label for='wpIgnoreWarning'>" . wfMsgHtml( 'ignorewarnings' ) . "</label> - </td> - </tr> - $warningRow - <tr> - <td></td> - <td align='$align2'><input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\"" . $wgUser->getSkin()->tooltipAndAccesskey( 'upload' ) . " /></td> - </tr> - <tr> - <td></td> - <td align='$align2'> - " ); - $wgOut->addWikiText( wfMsgForContent( 'edittools' ) ); - $wgOut->addHTML( " - </td> - </tr> - - </table> - <input type='hidden' name='wpDestFileWarningAck' id='wpDestFileWarningAck' value=''/> - </form>" ); - } - - /* -------------------------------------------------------------- */ - - /** - * Split a file into a base name and all dot-delimited 'extensions' - * on the end. Some web server configurations will fall back to - * earlier pseudo-'extensions' to determine type and execute - * scripts, so the blacklist needs to check them all. - * - * @return array - */ - function splitExtensions( $filename ) { - $bits = explode( '.', $filename ); - $basename = array_shift( $bits ); - return array( $basename, $bits ); - } - - /** - * Perform case-insensitive match against a list of file extensions. - * Returns true if the extension is in the list. - * - * @param string $ext - * @param array $list - * @return bool - */ - function checkFileExtension( $ext, $list ) { - return in_array( strtolower( $ext ), $list ); - } - - /** - * Perform case-insensitive match against a list of file extensions. - * Returns true if any of the extensions are in the list. - * - * @param array $ext - * @param array $list - * @return bool - */ - function checkFileExtensionList( $ext, $list ) { - foreach( $ext as $e ) { - if( in_array( strtolower( $e ), $list ) ) { - return true; - } - } - return false; - } - - /** - * Verifies that it's ok to include the uploaded file - * - * @param string $tmpfile the full path of the temporary file to verify - * @param string $extension The filename extension that the file is to be served with - * @return mixed true of the file is verified, a WikiError object otherwise. - */ - function verify( $tmpfile, $extension ) { - #magically determine mime type - $magic=& MimeMagic::singleton(); - $mime= $magic->guessMimeType($tmpfile,false); - - #check mime type, if desired - global $wgVerifyMimeType; - if ($wgVerifyMimeType) { - - wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n"); - #check mime type against file extension - if( !$this->verifyExtension( $mime, $extension ) ) { - return new WikiErrorMsg( 'uploadcorrupt' ); - } - - #check mime type blacklist - global $wgMimeTypeBlacklist; - if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) - && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { - return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) ); - } - } - - #check for htmlish code and javascript - if( $this->detectScript ( $tmpfile, $mime, $extension ) ) { - return new WikiErrorMsg( 'uploadscripted' ); - } - - /** - * Scan the uploaded file for viruses - */ - $virus= $this->detectVirus($tmpfile); - if ( $virus ) { - return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) ); - } - - wfDebug( __METHOD__.": all clear; passing.\n" ); - return true; - } - - /** - * Checks if the mime type of the uploaded file matches the file extension. - * - * @param string $mime the mime type of the uploaded file - * @param string $extension The filename extension that the file is to be served with - * @return bool - */ - function verifyExtension( $mime, $extension ) { - $magic =& MimeMagic::singleton(); - - if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) - if ( ! $magic->isRecognizableExtension( $extension ) ) { - wfDebug( __METHOD__.": passing file with unknown detected mime type; " . - "unrecognized extension '$extension', can't verify\n" ); - return true; - } else { - wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ". - "recognized extension '$extension', so probably invalid file\n" ); - return false; - } - - $match= $magic->isMatchingExtension($extension,$mime); - - if ($match===NULL) { - wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" ); - return true; - } elseif ($match===true) { - wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" ); - - #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it! - return true; - - } else { - wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" ); - return false; - } - } - - /** - * Heuristic for detecting files that *could* contain JavaScript instructions or - * things that may look like HTML to a browser and are thus - * potentially harmful. The present implementation will produce false positives in some situations. - * - * @param string $file Pathname to the temporary upload file - * @param string $mime The mime type of the file - * @param string $extension The extension of the file - * @return bool true if the file contains something looking like embedded scripts - */ - function detectScript($file, $mime, $extension) { - global $wgAllowTitlesInSVG; - - #ugly hack: for text files, always look at the entire file. - #For binarie field, just check the first K. - - if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file ); - else { - $fp = fopen( $file, 'rb' ); - $chunk = fread( $fp, 1024 ); - fclose( $fp ); - } - - $chunk= strtolower( $chunk ); - - if (!$chunk) return false; - - #decode from UTF-16 if needed (could be used for obfuscation). - if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE"; - elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE"; - else $enc= NULL; - - if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk); - - $chunk= trim($chunk); - - #FIXME: convert from UTF-16 if necessarry! - - wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n"); - - #check for HTML doctype - if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true; - - /** - * Internet Explorer for Windows performs some really stupid file type - * autodetection which can cause it to interpret valid image files as HTML - * and potentially execute JavaScript, creating a cross-site scripting - * attack vectors. - * - * Apple's Safari browser also performs some unsafe file type autodetection - * which can cause legitimate files to be interpreted as HTML if the - * web server is not correctly configured to send the right content-type - * (or if you're really uploading plain text and octet streams!) - * - * Returns true if IE is likely to mistake the given file for HTML. - * Also returns true if Safari would mistake the given file for HTML - * when served with a generic content-type. - */ - - $tags = array( - '<body', - '<head', - '<html', #also in safari - '<img', - '<pre', - '<script', #also in safari - '<table' - ); - if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) { - $tags[] = '<title'; - } - - foreach( $tags as $tag ) { - if( false !== strpos( $chunk, $tag ) ) { - return true; - } - } - - /* - * look for javascript - */ - - #resolve entity-refs to look at attributes. may be harsh on big files... cache result? - $chunk = Sanitizer::decodeCharReferences( $chunk ); - - #look for script-types - if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true; - - #look for html-style script-urls - if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; - - #look for css-style script-urls - if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; - - wfDebug("SpecialUpload::detectScript: no scripts found\n"); - return false; - } - - /** - * Generic wrapper function for a virus scanner program. - * This relies on the $wgAntivirus and $wgAntivirusSetup variables. - * $wgAntivirusRequired may be used to deny upload if the scan fails. - * - * @param string $file Pathname to the temporary upload file - * @return mixed false if not virus is found, NULL if the scan fails or is disabled, - * or a string containing feedback from the virus scanner if a virus was found. - * If textual feedback is missing but a virus was found, this function returns true. - */ - function detectVirus($file) { - global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut; - - if ( !$wgAntivirus ) { - wfDebug( __METHOD__.": virus scanner disabled\n"); - return NULL; - } - - if ( !$wgAntivirusSetup[$wgAntivirus] ) { - wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" ); - # @TODO: localise - $wgOut->addHTML( "<div class='error'>Bad configuration: unknown virus scanner: <i>$wgAntivirus</i></div>\n" ); - return "unknown antivirus: $wgAntivirus"; - } - - # look up scanner configuration - $command = $wgAntivirusSetup[$wgAntivirus]["command"]; - $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"]; - $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ? - $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null; - - if ( strpos( $command,"%f" ) === false ) { - # simple pattern: append file to scan - $command .= " " . wfEscapeShellArg( $file ); - } else { - # complex pattern: replace "%f" with file to scan - $command = str_replace( "%f", wfEscapeShellArg( $file ), $command ); - } - - wfDebug( __METHOD__.": running virus scan: $command \n" ); - - # execute virus scanner - $exitCode = false; - - #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too. - # that does not seem to be worth the pain. - # Ask me (Duesentrieb) about it if it's ever needed. - $output = array(); - if ( wfIsWindows() ) { - exec( "$command", $output, $exitCode ); - } else { - exec( "$command 2>&1", $output, $exitCode ); - } - - # map exit code to AV_xxx constants. - $mappedCode = $exitCode; - if ( $exitCodeMap ) { - if ( isset( $exitCodeMap[$exitCode] ) ) { - $mappedCode = $exitCodeMap[$exitCode]; - } elseif ( isset( $exitCodeMap["*"] ) ) { - $mappedCode = $exitCodeMap["*"]; - } - } - - if ( $mappedCode === AV_SCAN_FAILED ) { - # scan failed (code was mapped to false by $exitCodeMap) - wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" ); - - if ( $wgAntivirusRequired ) { - return "scan failed (code $exitCode)"; - } else { - return NULL; - } - } else if ( $mappedCode === AV_SCAN_ABORTED ) { - # scan failed because filetype is unknown (probably imune) - wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" ); - return NULL; - } else if ( $mappedCode === AV_NO_VIRUS ) { - # no virus found - wfDebug( __METHOD__.": file passed virus scan.\n" ); - return false; - } else { - $output = join( "\n", $output ); - $output = trim( $output ); - - if ( !$output ) { - $output = true; #if there's no output, return true - } elseif ( $msgPattern ) { - $groups = array(); - if ( preg_match( $msgPattern, $output, $groups ) ) { - if ( $groups[1] ) { - $output = $groups[1]; - } - } - } - - wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output" ); - return $output; - } - } - - /** - * Check if the temporary file is MacBinary-encoded, as some uploads - * from Internet Explorer on Mac OS Classic and Mac OS X will be. - * If so, the data fork will be extracted to a second temporary file, - * which will then be checked for validity and either kept or discarded. - * - * @access private - */ - function checkMacBinary() { - $macbin = new MacBinary( $this->mTempPath ); - if( $macbin->isValid() ) { - $dataFile = tempnam( wfTempDir(), "WikiMacBinary" ); - $dataHandle = fopen( $dataFile, 'wb' ); - - wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" ); - $macbin->extractData( $dataHandle ); - - $this->mTempPath = $dataFile; - $this->mFileSize = $macbin->dataForkLength(); - - // We'll have to manually remove the new file if it's not kept. - $this->mRemoveTempFile = true; - } - $macbin->close(); - } - - /** - * If we've modified the upload file we need to manually remove it - * on exit to clean up. - * @access private - */ - function cleanupTempFile() { - if ( $this->mRemoveTempFile && file_exists( $this->mTempPath ) ) { - wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file {$this->mTempPath}\n" ); - unlink( $this->mTempPath ); - } - } - - /** - * Check if there's an overwrite conflict and, if so, if restrictions - * forbid this user from performing the upload. - * - * @return mixed true on success, WikiError on failure - * @access private - */ - function checkOverwrite( $name ) { - $img = wfFindFile( $name ); - - $error = ''; - if( $img ) { - global $wgUser, $wgOut; - if( $img->isLocal() ) { - if( !self::userCanReUpload( $wgUser, $img->name ) ) { - $error = 'fileexists-forbidden'; - } - } else { - if( !$wgUser->isAllowed( 'reupload' ) || - !$wgUser->isAllowed( 'reupload-shared' ) ) { - $error = "fileexists-shared-forbidden"; - } - } - } - - if( $error ) { - $errorText = wfMsg( $error, wfEscapeWikiText( $img->getName() ) ); - return $errorText; - } - - // Rockin', go ahead and upload - return true; - } - - /** - * Check if a user is the last uploader - * - * @param User $user - * @param string $img, image name - * @return bool - */ - public static function userCanReUpload( User $user, $img ) { - if( $user->isAllowed( 'reupload' ) ) - return true; // non-conditional - if( !$user->isAllowed( 'reupload-own' ) ) - return false; - - $dbr = wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow('image', - /* SELECT */ 'img_user', - /* WHERE */ array( 'img_name' => $img ) - ); - if ( !$row ) - return false; - - return $user->getID() == $row->img_user; - } - - /** - * Display an error with a wikitext description - */ - function showError( $description ) { - global $wgOut; - $wgOut->setPageTitle( wfMsg( "internalerror" ) ); - $wgOut->setRobotpolicy( "noindex,nofollow" ); - $wgOut->setArticleRelated( false ); - $wgOut->enableClientCache( false ); - $wgOut->addWikiText( $description ); - } - - /** - * Get the initial image page text based on a comment and optional file status information - */ - static function getInitialPageText( $comment, $license, $copyStatus, $source ) { - global $wgUseCopyrightUpload; - if ( $wgUseCopyrightUpload ) { - if ( $license != '' ) { - $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; - } - $pageText = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n" . - '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" . - "$licensetxt" . - '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ; - } else { - if ( $license != '' ) { - $filedesc = $comment == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n"; - $pageText = $filedesc . - '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n"; - } else { - $pageText = $comment; - } - } - return $pageText; - } - - /** - * If there are rows in the deletion log for this file, show them, - * along with a nice little note for the user - * - * @param OutputPage $out - * @param string filename - */ - private function showDeletionLog( $out, $filename ) { - $reader = new LogReader( - new FauxRequest( - array( - 'page' => $filename, - 'type' => 'delete', - ) - ) - ); - if( $reader->hasRows() ) { - $out->addHtml( '<div id="mw-upload-deleted-warn">' ); - $out->addWikiMsg( 'upload-wasdeleted' ); - $viewer = new LogViewer( $reader ); - $viewer->showList( $out ); - $out->addHtml( '</div>' ); - } - } -} diff --git a/includes/SpecialUploadMogile.php b/includes/SpecialUploadMogile.php deleted file mode 100644 index 438e1df4..00000000 --- a/includes/SpecialUploadMogile.php +++ /dev/null @@ -1,136 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * You will need the extension MogileClient to use this special page. - */ -require_once( 'MogileFS.php' ); - -/** - * Entry point - */ -function wfSpecialUploadMogile() { - global $wgRequest; - $form = new UploadFormMogile( $wgRequest ); - $form->execute(); -} - -/** - * Extends Special:Upload with MogileFS. - * @addtogroup SpecialPage - */ -class UploadFormMogile extends UploadForm { - /** - * Move the uploaded file from its temporary location to the final - * destination. If a previous version of the file exists, move - * it into the archive subdirectory. - * - * @todo If the later save fails, we may have disappeared the original file. - * - * @param string $saveName - * @param string $tempName full path to the temporary file - * @param bool $useRename Not used in this implementation - */ - function saveUploadedFile( $saveName, $tempName, $useRename = false ) { - global $wgOut; - $mfs = MogileFS::NewMogileFS(); - - $this->mSavedFile = "image!{$saveName}"; - - if( $mfs->getPaths( $this->mSavedFile )) { - $this->mUploadOldVersion = gmdate( 'YmdHis' ) . "!{$saveName}"; - if( !$mfs->rename( $this->mSavedFile, "archive!{$this->mUploadOldVersion}" ) ) { - $wgOut->showFileRenameError( $this->mSavedFile, - "archive!{$this->mUploadOldVersion}" ); - return false; - } - } else { - $this->mUploadOldVersion = ''; - } - - if ( $this->mStashed ) { - if (!$mfs->rename($tempName,$this->mSavedFile)) { - $wgOut->showFileRenameError($tempName, $this->mSavedFile ); - return false; - } - } else { - if ( !$mfs->saveFile($this->mSavedFile,'normal',$tempName )) { - $wgOut->showFileCopyError( $tempName, $this->mSavedFile ); - return false; - } - unlink($tempName); - } - return true; - } - - /** - * Stash a file in a temporary directory for later processing - * after the user has confirmed it. - * - * If the user doesn't explicitly cancel or accept, these files - * can accumulate in the temp directory. - * - * @param string $saveName - the destination filename - * @param string $tempName - the source temporary file to save - * @return string - full path the stashed file, or false on failure - * @access private - */ - function saveTempUploadedFile( $saveName, $tempName ) { - global $wgOut; - - $stash = 'stash!' . gmdate( "YmdHis" ) . '!' . $saveName; - $mfs = MogileFS::NewMogileFS(); - if ( !$mfs->saveFile( $stash, 'normal', $tempName ) ) { - $wgOut->showFileCopyError( $tempName, $stash ); - return false; - } - unlink($tempName); - return $stash; - } - - /** - * Stash a file in a temporary directory for later processing, - * and save the necessary descriptive info into the session. - * Returns a key value which will be passed through a form - * to pick up the path info on a later invocation. - * - * @return int - * @access private - */ - function stashSession() { - $stash = $this->saveTempUploadedFile( - $this->mUploadSaveName, $this->mUploadTempName ); - - if( !$stash ) { - # Couldn't save the file. - return false; - } - - $key = mt_rand( 0, 0x7fffffff ); - $_SESSION['wsUploadData'][$key] = array( - 'mUploadTempName' => $stash, - 'mUploadSize' => $this->mUploadSize, - 'mOname' => $this->mOname ); - return $key; - } - - /** - * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @access private - * @return success - */ - function unsaveUploadedFile() { - global $wgOut; - $mfs = MogileFS::NewMogileFS(); - if ( ! $mfs->delete( $this->mUploadTempName ) ) { - $wgOut->showFileDeleteError( $this->mUploadTempName ); - return false; - } else { - return true; - } - } -} - diff --git a/includes/SpecialUserlogin.php b/includes/SpecialUserlogin.php deleted file mode 100644 index 3651fdc8..00000000 --- a/includes/SpecialUserlogin.php +++ /dev/null @@ -1,863 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * constructor - */ -function wfSpecialUserlogin( $par = '' ) { - global $wgRequest; - if( session_id() == '' ) { - wfSetupSession(); - } - - $form = new LoginForm( $wgRequest, $par ); - $form->execute(); -} - -/** - * implements Special:Login - * @addtogroup SpecialPage - */ -class LoginForm { - - const SUCCESS = 0; - const NO_NAME = 1; - const ILLEGAL = 2; - const WRONG_PLUGIN_PASS = 3; - const NOT_EXISTS = 4; - const WRONG_PASS = 5; - const EMPTY_PASS = 6; - const RESET_PASS = 7; - const ABORTED = 8; - - var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; - var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; - var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; - - /** - * Constructor - * @param WebRequest $request A WebRequest object passed by reference - */ - function LoginForm( &$request, $par = '' ) { - global $wgLang, $wgAllowRealName, $wgEnableEmail; - global $wgAuth; - - $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]] - $this->mName = $request->getText( 'wpName' ); - $this->mPassword = $request->getText( 'wpPassword' ); - $this->mRetype = $request->getText( 'wpRetype' ); - $this->mDomain = $request->getText( 'wpDomain' ); - $this->mReturnTo = $request->getVal( 'returnto' ); - $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); - $this->mPosted = $request->wasPosted(); - $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); - $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) - && $wgEnableEmail; - $this->mMailmypassword = $request->getCheck( 'wpMailmypassword' ) - && $wgEnableEmail; - $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); - $this->mAction = $request->getVal( 'action' ); - $this->mRemember = $request->getCheck( 'wpRemember' ); - $this->mLanguage = $request->getText( 'uselang' ); - - if( $wgEnableEmail ) { - $this->mEmail = $request->getText( 'wpEmail' ); - } else { - $this->mEmail = ''; - } - if( $wgAllowRealName ) { - $this->mRealName = $request->getText( 'wpRealName' ); - } else { - $this->mRealName = ''; - } - - if( !$wgAuth->validDomain( $this->mDomain ) ) { - $this->mDomain = 'invaliddomain'; - } - $wgAuth->setDomain( $this->mDomain ); - - # When switching accounts, it sucks to get automatically logged out - if( $this->mReturnTo == $wgLang->specialPage( 'Userlogout' ) ) { - $this->mReturnTo = ''; - } - } - - function execute() { - if ( !is_null( $this->mCookieCheck ) ) { - $this->onCookieRedirectCheck( $this->mCookieCheck ); - return; - } else if( $this->mPosted ) { - if( $this->mCreateaccount ) { - return $this->addNewAccount(); - } else if ( $this->mCreateaccountMail ) { - return $this->addNewAccountMailPassword(); - } else if ( $this->mMailmypassword ) { - return $this->mailPassword(); - } else if ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { - return $this->processLogin(); - } - } - $this->mainLoginForm( '' ); - } - - /** - * @private - */ - function addNewAccountMailPassword() { - global $wgOut; - - if ('' == $this->mEmail) { - $this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) ); - return; - } - - $u = $this->addNewaccountInternal(); - - if ($u == NULL) { - return; - } - - // Wipe the initial password and mail a temporary one - $u->setPassword( null ); - $u->saveSettings(); - $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); - - wfRunHooks( 'AddNewAccount', array( $u, true ) ); - - $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - if( WikiError::isError( $result ) ) { - $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); - } else { - $wgOut->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); - $wgOut->returnToMain( false ); - } - $u = 0; - } - - - /** - * @private - */ - function addNewAccount() { - global $wgUser, $wgEmailAuthentication; - - # Create the account and abort if there's a problem doing so - $u = $this->addNewAccountInternal(); - if( $u == NULL ) - return; - - # If we showed up language selection links, and one was in use, be - # smart (and sensible) and save that language as the user's preference - global $wgLoginLanguageSelector; - if( $wgLoginLanguageSelector && $this->mLanguage ) - $u->setOption( 'language', $this->mLanguage ); - - # Save user settings and send out an email authentication message if needed - $u->saveSettings(); - if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) { - global $wgOut; - $error = $u->sendConfirmationMail(); - if( WikiError::isError( $error ) ) { - $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() ); - } else { - $wgOut->addWikiMsg( 'confirmemail_oncreate' ); - } - } - - # If not logged in, assume the new account as the current one and set session cookies - # then show a "welcome" message or a "need cookies" message as needed - if( $wgUser->isAnon() ) { - $wgUser = $u; - $wgUser->setCookies(); - wfRunHooks( 'AddNewAccount', array( $wgUser ) ); - if( $this->hasSessionCookie() ) { - return $this->successfulLogin( wfMsg( 'welcomecreation', $wgUser->getName() ), false ); - } else { - return $this->cookieRedirectCheck( 'new' ); - } - } else { - # Confirm that the account was created - global $wgOut; - $self = SpecialPage::getTitleFor( 'Userlogin' ); - $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) ); - $wgOut->setArticleRelated( false ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->addHtml( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); - $wgOut->returnToMain( false, $self ); - wfRunHooks( 'AddNewAccount', array( $u ) ); - return true; - } - } - - /** - * @private - */ - function addNewAccountInternal() { - global $wgUser, $wgOut; - global $wgEnableSorbs, $wgProxyWhitelist; - global $wgMemc, $wgAccountCreationThrottle; - global $wgAuth, $wgMinimalPasswordLength; - global $wgEmailConfirmToEdit; - - // If the user passes an invalid domain, something is fishy - if( !$wgAuth->validDomain( $this->mDomain ) ) { - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - return false; - } - - // If we are not allowing users to login locally, we should - // be checking to see if the user is actually able to - // authenticate to the authentication server before they - // create an account (otherwise, they can create a local account - // and login as any domain user). We only need to check this for - // domains that aren't local. - if( 'local' != $this->mDomain && '' != $this->mDomain ) { - if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) { - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - return false; - } - } - - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return false; - } - - # Check permissions - if ( !$wgUser->isAllowed( 'createaccount' ) ) { - $this->userNotPrivilegedMessage(); - return false; - } elseif ( $wgUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage(); - return false; - } - - $ip = wfGetIP(); - if ( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) && - $wgUser->inSorbsBlacklist( $ip ) ) - { - $this->mainLoginForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' ); - return; - } - - # Now create a dummy user ($u) and check if it is valid - $name = trim( $this->mName ); - $u = User::newFromName( $name, 'creatable' ); - if ( is_null( $u ) ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return false; - } - - if ( 0 != $u->idForName() ) { - $this->mainLoginForm( wfMsg( 'userexists' ) ); - return false; - } - - if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) { - $this->mainLoginForm( wfMsg( 'badretype' ) ); - return false; - } - - # check for minimal password length - if ( !$u->isValidPassword( $this->mPassword ) ) { - if ( !$this->mCreateaccountMail ) { - $this->mainLoginForm( wfMsg( 'passwordtooshort', $wgMinimalPasswordLength ) ); - return false; - } else { - # do not force a password for account creation by email - # set pseudo password, it will be replaced later by a random generated password - $this->mPassword = '-'; - } - } - - # if you need a confirmed email address to edit, then obviously you need an email address. - if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) { - $this->mainLoginForm( wfMsg( 'noemailtitle' ) ); - return false; - } - - if( !empty( $this->mEmail ) && !User::isValidEmailAddr( $this->mEmail ) ) { - $this->mainLoginForm( wfMsg( 'invalidemailaddress' ) ); - return false; - } - - # Set some additional data so the AbortNewAccount hook can be - # used for more than just username validation - $u->setEmail( $this->mEmail ); - $u->setRealName( $this->mRealName ); - - $abortError = ''; - if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { - // Hook point to add extra creation throttles and blocks - wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); - $this->mainLoginForm( $abortError ); - return false; - } - - if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) { - $key = wfMemcKey( 'acctcreate', 'ip', $ip ); - $value = $wgMemc->incr( $key ); - if ( !$value ) { - $wgMemc->set( $key, 1, 86400 ); - } - if ( $value > $wgAccountCreationThrottle ) { - $this->throttleHit( $wgAccountCreationThrottle ); - return false; - } - } - - if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { - $this->mainLoginForm( wfMsg( 'externaldberror' ) ); - return false; - } - - return $this->initUser( $u, false ); - } - - /** - * Actually add a user to the database. - * Give it a User object that has been initialised with a name. - * - * @param $u User object. - * @param $autocreate boolean -- true if this is an autocreation via auth plugin - * @return User object. - * @private - */ - function initUser( $u, $autocreate ) { - global $wgAuth; - - $u->addToDatabase(); - - if ( $wgAuth->allowPasswordChange() ) { - $u->setPassword( $this->mPassword ); - } - - $u->setEmail( $this->mEmail ); - $u->setRealName( $this->mRealName ); - $u->setToken(); - - $wgAuth->initUser( $u, $autocreate ); - - $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); - $u->saveSettings(); - - # Update user count - $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); - $ssUpdate->doUpdate(); - - return $u; - } - - /** - * Internally authenticate the login request. - * - * This may create a local account as a side effect if the - * authentication plugin allows transparent local account - * creation. - * - * @public - */ - function authenticateUserData() { - global $wgUser, $wgAuth; - if ( '' == $this->mName ) { - return self::NO_NAME; - } - $u = User::newFromName( $this->mName ); - if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) { - return self::ILLEGAL; - } - if ( 0 == $u->getID() ) { - global $wgAuth; - /** - * If the external authentication plugin allows it, - * automatically create a new account for users that - * are externally defined but have not yet logged in. - */ - if ( $wgAuth->autoCreate() && $wgAuth->userExists( $u->getName() ) ) { - if ( $wgAuth->authenticate( $u->getName(), $this->mPassword ) ) { - $u = $this->initUser( $u, true ); - } else { - return self::WRONG_PLUGIN_PASS; - } - } else { - return self::NOT_EXISTS; - } - } else { - $u->load(); - } - - // Give general extensions, such as a captcha, a chance to abort logins - $abort = self::ABORTED; - if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort ) ) ) { - return $abort; - } - - if (!$u->checkPassword( $this->mPassword )) { - if( $u->checkTemporaryPassword( $this->mPassword ) ) { - // The e-mailed temporary password should not be used - // for actual logins; that's a very sloppy habit, - // and insecure if an attacker has a few seconds to - // click "search" on someone's open mail reader. - // - // Allow it to be used only to reset the password - // a single time to a new value, which won't be in - // the user's e-mail archives. - // - // For backwards compatibility, we'll still recognize - // it at the login form to minimize surprises for - // people who have been logging in with a temporary - // password for some time. - // - // As a side-effect, we can authenticate the user's - // e-mail address if it's not already done, since - // the temporary password was sent via e-mail. - // - if( !$u->isEmailConfirmed() ) { - $u->confirmEmail(); - } - - // At this point we just return an appropriate code - // indicating that the UI should show a password - // reset form; bot interfaces etc will probably just - // fail cleanly here. - // - $retval = self::RESET_PASS; - } else { - $retval = '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS; - } - } else { - $wgAuth->updateUser( $u ); - $wgUser = $u; - - $retval = self::SUCCESS; - } - wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) ); - return $retval; - } - - function processLogin() { - global $wgUser, $wgAuth; - - switch ($this->authenticateUserData()) - { - case self::SUCCESS: - # We've verified now, update the real record - if( (bool)$this->mRemember != (bool)$wgUser->getOption( 'rememberpassword' ) ) { - $wgUser->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); - $wgUser->saveSettings(); - } else { - $wgUser->invalidateCache(); - } - $wgUser->setCookies(); - - if( $this->hasSessionCookie() ) { - return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) ); - } else { - return $this->cookieRedirectCheck( 'login' ); - } - break; - - case self::NO_NAME: - case self::ILLEGAL: - $this->mainLoginForm( wfMsg( 'noname' ) ); - break; - case self::WRONG_PLUGIN_PASS: - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - break; - case self::NOT_EXISTS: - if( $wgUser->isAllowed( 'createaccount' ) ){ - $this->mainLoginForm( wfMsg( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); - } else { - $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) ); - } - break; - case self::WRONG_PASS: - $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); - break; - case self::EMPTY_PASS: - $this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) ); - break; - case self::RESET_PASS: - $this->resetLoginForm( wfMsg( 'resetpass_announce' ) ); - break; - default: - wfDebugDieBacktrace( "Unhandled case value" ); - } - } - - function resetLoginForm( $error ) { - global $wgOut; - $wgOut->addWikiText( "<div class=\"errorbox\">$error</div>" ); - $reset = new PasswordResetForm( $this->mName, $this->mPassword ); - $reset->execute( null ); - } - - /** - * @private - */ - function mailPassword() { - global $wgUser, $wgOut, $wgAuth; - - if( !$wgAuth->allowPasswordChange() ) { - $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); - return; - } - - # Check against blocked IPs - # fixme -- should we not? - if( $wgUser->isBlocked() ) { - $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); - return; - } - - # Check against the rate limiter - if( $wgUser->pingLimiter( 'mailpassword' ) ) { - $wgOut->rateLimited(); - return; - } - - if ( '' == $this->mName ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return; - } - $u = User::newFromName( $this->mName ); - if( is_null( $u ) ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return; - } - if ( 0 == $u->getID() ) { - $this->mainLoginForm( wfMsg( 'nosuchuser', $u->getName() ) ); - return; - } - - # Check against password throttle - if ( $u->isPasswordReminderThrottled() ) { - global $wgPasswordReminderResendTime; - # Round the time in hours to 3 d.p., in case someone is specifying minutes or seconds. - $this->mainLoginForm( wfMsg( 'throttled-mailpassword', - round( $wgPasswordReminderResendTime, 3 ) ) ); - return; - } - - $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' ); - if( WikiError::isError( $result ) ) { - $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); - } else { - $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' ); - } - } - - - /** - * @param object user - * @param bool throttle - * @param string message name of email title - * @param string message name of email text - * @return mixed true on success, WikiError on failure - * @private - */ - function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { - global $wgCookiePath, $wgCookieDomain, $wgCookiePrefix, $wgCookieSecure; - global $wgServer, $wgScript; - - if ( '' == $u->getEmail() ) { - return new WikiError( wfMsg( 'noemail', $u->getName() ) ); - } - - $np = $u->randomPassword(); - $u->setNewpassword( $np, $throttle ); - - setcookie( "{$wgCookiePrefix}Token", '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure ); - - $u->saveSettings(); - - $ip = wfGetIP(); - if ( '' == $ip ) { $ip = '(Unknown)'; } - - $m = wfMsg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript ); - $result = $u->sendMail( wfMsg( $emailTitle ), $m ); - - return $result; - } - - - /** - * @param string $msg Message that will be shown on success - * @param bool $auto Toggle auto-redirect to main page; default true - * @private - */ - function successfulLogin( $msg, $auto = true ) { - global $wgUser; - global $wgOut; - - # Run any hooks; ignore results - - wfRunHooks('UserLoginComplete', array(&$wgUser)); - - $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - $wgOut->addWikiText( $msg ); - if ( !empty( $this->mReturnTo ) ) { - $wgOut->returnToMain( $auto, $this->mReturnTo ); - } else { - $wgOut->returnToMain( $auto ); - } - } - - /** */ - function userNotPrivilegedMessage() { - global $wgOut; - - $wgOut->setPageTitle( wfMsg( 'whitelistacctitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - $wgOut->addWikiMsg( 'whitelistacctext' ); - - $wgOut->returnToMain( false ); - } - - /** */ - function userBlockedMessage() { - global $wgOut, $wgUser; - - # Let's be nice about this, it's likely that this feature will be used - # for blocking large numbers of innocent people, e.g. range blocks on - # schools. Don't blame it on the user. There's a small chance that it - # really is the user's fault, i.e. the username is blocked and they - # haven't bothered to log out before trying to create an account to - # evade it, but we'll leave that to their guilty conscience to figure - # out. - - $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - $ip = wfGetIP(); - $blocker = User::whoIs( $wgUser->mBlock->mBy ); - $block_reason = $wgUser->mBlock->mReason; - - $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker ); - $wgOut->returnToMain( false ); - } - - /** - * @private - */ - function mainLoginForm( $msg, $msgtype = 'error' ) { - global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail; - global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector; - global $wgAuth, $wgEmailConfirmToEdit; - - if ( $this->mType == 'signup' ) { - if ( !$wgUser->isAllowed( 'createaccount' ) ) { - $this->userNotPrivilegedMessage(); - return; - } elseif ( $wgUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage(); - return; - } - } - - if ( '' == $this->mName ) { - if ( $wgUser->isLoggedIn() ) { - $this->mName = $wgUser->getName(); - } else { - $this->mName = isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ? $_COOKIE[$wgCookiePrefix.'UserName'] : null; - } - } - - $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - - if ( $this->mType == 'signup' ) { - $template = new UsercreateTemplate(); - $q = 'action=submitlogin&type=signup'; - $linkq = 'type=login'; - $linkmsg = 'gotaccount'; - } else { - $template = new UserloginTemplate(); - $q = 'action=submitlogin&type=login'; - $linkq = 'type=signup'; - $linkmsg = 'nologin'; - } - - if ( !empty( $this->mReturnTo ) ) { - $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); - $q .= $returnto; - $linkq .= $returnto; - } - - # Pass any language selection on to the mode switch link - if( $wgLoginLanguageSelector && $this->mLanguage ) - $linkq .= '&uselang=' . $this->mLanguage; - - $link = '<a href="' . htmlspecialchars ( $titleObj->getLocalUrl( $linkq ) ) . '">'; - $link .= wfMsgHtml( $linkmsg . 'link' ); # Calling either 'gotaccountlink' or 'nologinlink' - $link .= '</a>'; - - # Don't show a "create account" link if the user can't - if( $this->showCreateOrLoginLink( $wgUser ) ) - $template->set( 'link', wfMsgHtml( $linkmsg, $link ) ); - else - $template->set( 'link', '' ); - - $template->set( 'header', '' ); - $template->set( 'name', $this->mName ); - $template->set( 'password', $this->mPassword ); - $template->set( 'retype', $this->mRetype ); - $template->set( 'email', $this->mEmail ); - $template->set( 'realname', $this->mRealName ); - $template->set( 'domain', $this->mDomain ); - - $template->set( 'action', $titleObj->getLocalUrl( $q ) ); - $template->set( 'message', $msg ); - $template->set( 'messagetype', $msgtype ); - $template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() ); - $template->set( 'userealname', $wgAllowRealName ); - $template->set( 'useemail', $wgEnableEmail ); - $template->set( 'emailrequired', $wgEmailConfirmToEdit ); - $template->set( 'canreset', $wgAuth->allowPasswordChange() ); - $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); - - # Prepare language selection links as needed - if( $wgLoginLanguageSelector ) { - $template->set( 'languages', $this->makeLanguageSelector() ); - if( $this->mLanguage ) - $template->set( 'uselang', $this->mLanguage ); - } - - // Give authentication and captcha plugins a chance to modify the form - $wgAuth->modifyUITemplate( $template ); - if ( $this->mType == 'signup' ) { - wfRunHooks( 'UserCreateForm', array( &$template ) ); - } else { - wfRunHooks( 'UserLoginForm', array( &$template ) ); - } - - $wgOut->setPageTitle( wfMsg( 'userlogin' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - $wgOut->disallowUserJs(); // just in case... - $wgOut->addTemplate( $template ); - } - - /** - * @private - */ - function showCreateOrLoginLink( &$user ) { - if( $this->mType == 'signup' ) { - return( true ); - } elseif( $user->isAllowed( 'createaccount' ) ) { - return( true ); - } else { - return( false ); - } - } - - /** - * Check if a session cookie is present. - * - * This will not pick up a cookie set during _this_ request, but is - * meant to ensure that the client is returning the cookie which was - * set on a previous pass through the system. - * - * @private - */ - function hasSessionCookie() { - global $wgDisableCookieCheck, $wgRequest; - return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie(); - } - - /** - * @private - */ - function cookieRedirectCheck( $type ) { - global $wgOut; - - $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - $check = $titleObj->getFullURL( 'wpCookieCheck='.$type ); - - return $wgOut->redirect( $check ); - } - - /** - * @private - */ - function onCookieRedirectCheck( $type ) { - global $wgUser; - - if ( !$this->hasSessionCookie() ) { - if ( $type == 'new' ) { - return $this->mainLoginForm( wfMsg( 'nocookiesnew' ) ); - } else if ( $type == 'login' ) { - return $this->mainLoginForm( wfMsg( 'nocookieslogin' ) ); - } else { - # shouldn't happen - return $this->mainLoginForm( wfMsg( 'error' ) ); - } - } else { - return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) ); - } - } - - /** - * @private - */ - function throttleHit( $limit ) { - global $wgOut; - - $wgOut->addWikiMsg( 'acct_creation_throttle_hit', $limit ); - } - - /** - * Produce a bar of links which allow the user to select another language - * during login/registration but retain "returnto" - * - * @return string - */ - function makeLanguageSelector() { - $msg = wfMsgForContent( 'loginlanguagelinks' ); - if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) { - $langs = explode( "\n", $msg ); - $links = array(); - foreach( $langs as $lang ) { - $lang = trim( $lang, '* ' ); - $parts = explode( '|', $lang ); - if (count($parts) >= 2) { - $links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] ); - } - } - return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', implode( ' | ', $links ) ) : ''; - } else { - return ''; - } - } - - /** - * Create a language selector link for a particular language - * Links back to this page preserving type and returnto - * - * @param $text Link text - * @param $lang Language code - */ - function makeLanguageSelectorLink( $text, $lang ) { - global $wgUser; - $self = SpecialPage::getTitleFor( 'Userlogin' ); - $attr[] = 'uselang=' . $lang; - if( $this->mType == 'signup' ) - $attr[] = 'type=signup'; - if( $this->mReturnTo ) - $attr[] = 'returnto=' . $this->mReturnTo; - $skin = $wgUser->getSkin(); - return $skin->makeKnownLinkObj( $self, htmlspecialchars( $text ), implode( '&', $attr ) ); - } -} - - diff --git a/includes/SpecialUserlogout.php b/includes/SpecialUserlogout.php deleted file mode 100644 index d9952ea5..00000000 --- a/includes/SpecialUserlogout.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * constructor - */ -function wfSpecialUserlogout() { - global $wgUser, $wgOut; - - $wgUser->logout(); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->addHTML( wfMsgExt( 'logouttext', array( 'parse' ) ) ); - $wgOut->returnToMain(); -} - - diff --git a/includes/SpecialUserrights.php b/includes/SpecialUserrights.php deleted file mode 100644 index 48fb3628..00000000 --- a/includes/SpecialUserrights.php +++ /dev/null @@ -1,575 +0,0 @@ -<?php - -/** - * Special page to allow managing user group membership - * - * @addtogroup SpecialPage - * @todo Use checkboxes or something, this list thing is incomprehensible to - * normal human beings. - */ - -/** - * A class to manage user levels rights. - * @addtogroup SpecialPage - */ -class UserrightsPage extends SpecialPage { - # The target of the local right-adjuster's interest. Can be gotten from - # either a GET parameter or a subpage-style parameter, so have a member - # variable for it. - protected $mTarget; - protected $isself = false; - - public function __construct() { - parent::__construct( 'Userrights' ); - } - - public function isRestricted() { - return true; - } - - public function userCanExecute( $user ) { - $available = $this->changeableGroups(); - return !empty( $available['add'] ) - or !empty( $available['remove'] ) - or ($this->isself and - (!empty( $available['add-self'] ) - or !empty( $available['remove-self'] ))); - } - - /** - * Manage forms to be shown according to posted data. - * Depending on the submit button used, call a form or a save function. - * - * @param mixed $par String if any subpage provided, else null - */ - function execute( $par ) { - // If the visitor doesn't have permissions to assign or remove - // any groups, it's a bit silly to give them the user search prompt. - global $wgUser, $wgRequest; - - if( $par ) { - $this->mTarget = $par; - } else { - $this->mTarget = $wgRequest->getVal( 'user' ); - } - - if (!$this->mTarget) { - /* - * If the user specified no target, and they can only - * edit their own groups, automatically set them as the - * target. - */ - $available = $this->changeableGroups(); - if (empty($available['add']) && empty($available['remove'])) - $this->mTarget = $wgUser->getName(); - } - - if ($this->mTarget == $wgUser->getName()) - $this->isself = true; - - if( !$this->userCanExecute( $wgUser ) ) { - // fixme... there may be intermediate groups we can mention. - global $wgOut; - $wgOut->showPermissionsErrorPage( array( - $wgUser->isAnon() - ? 'userrights-nologin' - : 'userrights-notallowed' ) ); - return; - } - - if ( wfReadOnly() ) { - global $wgOut; - $wgOut->readOnlyPage(); - return; - } - - $this->outputHeader(); - - $this->setHeaders(); - - // show the general form - $this->switchForm(); - - if( $wgRequest->wasPosted() ) { - // save settings - if( $wgRequest->getCheck( 'saveusergroups' ) ) { - $reason = $wgRequest->getVal( 'user-reason' ); - if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mTarget ) ) { - $this->saveUserGroups( - $this->mTarget, - $wgRequest->getArray( 'removable' ), - $wgRequest->getArray( 'available' ), - $reason - ); - } - } - } - - // show some more forms - if( $this->mTarget ) { - $this->editUserGroupsForm( $this->mTarget ); - } - } - - /** - * Save user groups changes in the database. - * Data comes from the editUserGroupsForm() form function - * - * @param string $username Username to apply changes to. - * @param array $removegroup id of groups to be removed. - * @param array $addgroup id of groups to be added. - * @param string $reason Reason for group change - * @return null - */ - function saveUserGroups( $username, $removegroup, $addgroup, $reason = '') { - global $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - - $user = $this->fetchUser( $username ); - if( !$user ) { - return; - } - - // Validate input set... - $changeable = $this->changeableGroups(); - if ($wgUser->getId() != 0 && $wgUser->getId() == $user->getId()) { - $addable = array_merge($changeable['add'], $wgGroupsAddToSelf); - $removable = array_merge($changeable['remove'], $wgGroupsRemoveFromSelf); - } else { - $addable = $changeable['add']; - $removable = $changeable['remove']; - } - - $removegroup = array_unique( - array_intersect( (array)$removegroup, $removable ) ); - $addgroup = array_unique( - array_intersect( (array)$addgroup, $addable ) ); - - $oldGroups = $user->getGroups(); - $newGroups = $oldGroups; - // remove then add groups - if( $removegroup ) { - $newGroups = array_diff($newGroups, $removegroup); - foreach( $removegroup as $group ) { - $user->removeGroup( $group ); - } - } - if( $addgroup ) { - $newGroups = array_merge($newGroups, $addgroup); - foreach( $addgroup as $group ) { - $user->addGroup( $group ); - } - } - $newGroups = array_unique( $newGroups ); - - // Ensure that caches are cleared - $user->invalidateCache(); - - wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) ); - wfDebug( 'newGroups: ' . print_r( $newGroups, true ) ); - if( $user instanceof User ) { - // hmmm - wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) ); - } - - if( $newGroups != $oldGroups ) { - $log = new LogPage( 'rights' ); - - global $wgRequest; - $log->addEntry( 'rights', - $user->getUserPage(), - $wgRequest->getText( 'user-reason' ), - array( - $this->makeGroupNameList( $oldGroups ), - $this->makeGroupNameList( $newGroups ) - ) - ); - } - } - - /** - * Edit user groups membership - * @param string $username Name of the user. - */ - function editUserGroupsForm( $username ) { - global $wgOut; - - $user = $this->fetchUser( $username ); - if( !$user ) { - return; - } - - $groups = $user->getGroups(); - - $this->showEditUserGroupsForm( $user, $groups ); - - // This isn't really ideal logging behavior, but let's not hide the - // interwiki logs if we're using them as is. - $this->showLogFragment( $user, $wgOut ); - } - - /** - * Normalize the input username, which may be local or remote, and - * return a user (or proxy) object for manipulating it. - * - * Side effects: error output for invalid access - * @return mixed User, UserRightsProxy, or null - */ - function fetchUser( $username ) { - global $wgOut, $wgUser; - - $parts = explode( '@', $username ); - if( count( $parts ) < 2 ) { - $name = trim( $username ); - $database = ''; - } else { - list( $name, $database ) = array_map( 'trim', $parts ); - - if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) { - $wgOut->addWikiMsg( 'userrights-no-interwiki' ); - return null; - } - if( !UserRightsProxy::validDatabase( $database ) ) { - $wgOut->addWikiMsg( 'userrights-nodatabase', $database ); - return null; - } - } - - if( $name == '' ) { - $wgOut->addWikiMsg( 'nouserspecified' ); - return false; - } - - if( $name{0} == '#' ) { - // Numeric ID can be specified... - // We'll do a lookup for the name internally. - $id = intval( substr( $name, 1 ) ); - - if( $database == '' ) { - $name = User::whoIs( $id ); - } else { - $name = UserRightsProxy::whoIs( $database, $id ); - } - - if( !$name ) { - $wgOut->addWikiMsg( 'noname' ); - return null; - } - } - - if( $database == '' ) { - $user = User::newFromName( $name ); - } else { - $user = UserRightsProxy::newFromName( $database, $name ); - } - - if( !$user || $user->isAnon() ) { - $wgOut->addWikiMsg( 'nosuchusershort', $username ); - return null; - } - - return $user; - } - - function makeGroupNameList( $ids ) { - return implode( ', ', $ids ); - } - - /** - * Output a form to allow searching for a user - */ - function switchForm() { - global $wgOut, $wgScript; - $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser' ) ); - $form .= Xml::hidden( 'title', 'Special:Userrights' ); - $form .= '<fieldset><legend>' . wfMsgHtml( 'userrights-lookup-user' ) . '</legend>'; - $form .= '<p>' . Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, $this->mTarget ) . '</p>'; - $form .= '<p>' . Xml::submitButton( wfMsg( 'editusergroup' ) ) . '</p>'; - $form .= '</fieldset>'; - $form .= '</form>'; - $wgOut->addHTML( $form ); - } - - /** - * Go through used and available groups and return the ones that this - * form will be able to manipulate based on the current user's system - * permissions. - * - * @param $groups Array: list of groups the given user is in - * @return Array: Tuple of addable, then removable groups - */ - protected function splitGroups( $groups ) { - global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - list($addable, $removable) = array_values( $this->changeableGroups() ); - - $removable = array_intersect( - array_merge($this->isself ? $wgGroupsRemoveFromSelf : array(), $removable), - $groups ); // Can't remove groups the user doesn't have - $addable = array_diff( - array_merge($this->isself ? $wgGroupsAddToSelf : array(), $addable), - $groups ); // Can't add groups the user does have - - return array( $addable, $removable ); - } - - /** - * Show the form to edit group memberships. - * - * @todo make all CSS-y and semantic - * @param $user User or UserRightsProxy you're editing - * @param $groups Array: Array of groups the user is in - */ - protected function showEditUserGroupsForm( $user, $groups ) { - global $wgOut, $wgUser; - - list( $addable, $removable ) = $this->splitGroups( $groups ); - - $list = array(); - foreach( $user->getGroups() as $group ) - $list[] = self::buildGroupLink( $group ); - - $grouplist = ''; - if( count( $list ) > 0 ) { - $grouplist = '<p>' . wfMsgHtml( 'userrights-groupsmember' ) . ' ' . implode( ', ', $list ) . '</p>'; - } - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->escapeLocalURL(), 'name' => 'editGroup' ) ) . - Xml::hidden( 'user', $user->getName() ) . - Xml::hidden( 'wpEditToken', $wgUser->editToken( $user->getName() ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) . - wfMsgExt( 'editinguser', array( 'parse' ), - wfEscapeWikiText( $user->getName() ) ) . - $grouplist . - $this->explainRights() . - "<table border='0'> - <tr> - <td></td> - <td> - <table width='400'> - <tr> - <td width='50%'>" . $this->removeSelect( $removable ) . "</td> - <td width='50%'>" . $this->addSelect( $addable ) . "</td> - </tr> - </table> - </tr> - <tr> - <td colspan='2'>" . - $wgOut->parse( wfMsg('userrights-groupshelp') ) . - "</td> - </tr> - <tr> - <td>" . - Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) . - "</td> - <td>" . - Xml::input( 'user-reason', 60, false, array( 'id' => 'wpReason', 'maxlength' => 255 ) ) . - "</td> - </tr> - <tr> - <td></td> - <td>" . - Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups' ) ) . - "</td> - </tr> - </table>\n" . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" - ); - } - - /** - * Format a link to a group description page - * - * @param string $group - * @return string - */ - private static function buildGroupLink( $group ) { - static $cache = array(); - if( !isset( $cache[$group] ) ) - $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) ); - return $cache[$group]; - } - - /** - * Prepare a list of groups the user is able to add and remove - * - * @return string - */ - private function explainRights() { - global $wgUser, $wgLang; - - $out = array(); - list( $add, $remove, $addself, $rmself ) = array_values( $this->changeableGroups() ); - - if( count( $add ) > 0 ) - $out[] = wfMsgExt( 'userrights-available-add', 'parseinline', - $wgLang->listToText( $add ), count( $add ) ); - if( count( $remove ) > 0 ) - $out[] = wfMsgExt( 'userrights-available-remove', 'parseinline', - $wgLang->listToText( $remove ), count( $add ) ); - if( count( $addself ) > 0 ) - $out[] = wfMsgExt( 'userrights-available-add-self', 'parseinline', - $wgLang->listToText( $addself ), count( $addself ) ); - if( count( $rmself ) > 0 ) - $out[] = wfMsgExt( 'userrights-available-remove-self', 'parseinline', - $wgLang->listToText( $rmself ), count( $rmself ) ); - - return count( $out ) > 0 - ? implode( '<br />', $out ) - : wfMsgExt( 'userrights-available-none', 'parseinline' ); - } - - /** - * Adds the <select> thingie where you can select what groups to remove - * - * @param array $groups The groups that can be removed - * @return string XHTML <select> element - */ - private function removeSelect( $groups ) { - return $this->doSelect( $groups, 'removable' ); - } - - /** - * Adds the <select> thingie where you can select what groups to add - * - * @param array $groups The groups that can be added - * @return string XHTML <select> element - */ - private function addSelect( $groups ) { - return $this->doSelect( $groups, 'available' ); - } - - /** - * Adds the <select> thingie where you can select what groups to add/remove - * - * @param array $groups The groups that can be added/removed - * @param string $name 'removable' or 'available' - * @return string XHTML <select> element - */ - private function doSelect( $groups, $name ) { - $ret = wfMsgHtml( "{$this->mName}-groups$name" ) . - Xml::openElement( 'select', array( - 'name' => "{$name}[]", - 'multiple' => 'multiple', - 'size' => '6', - 'style' => 'width: 100%;' - ) - ); - foreach ($groups as $group) { - $ret .= Xml::element( 'option', array( 'value' => $group ), User::getGroupName( $group ) ); - } - $ret .= Xml::closeElement( 'select' ); - return $ret; - } - - /** - * @param string $group The name of the group to check - * @return bool Can we remove the group? - */ - private function canRemove( $group ) { - // $this->changeableGroups()['remove'] doesn't work, of course. Thanks, - // PHP. - $groups = $this->changeableGroups(); - return in_array( $group, $groups['remove'] ); - } - - /** - * @param string $group The name of the group to check - * @return bool Can we add the group? - */ - private function canAdd( $group ) { - $groups = $this->changeableGroups(); - return in_array( $group, $groups['add'] ); - } - - /** - * Returns an array of the groups that the user can add/remove. - * - * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) ) - */ - function changeableGroups() { - global $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - - if( $wgUser->isAllowed( 'userrights' ) ) { - // This group gives the right to modify everything (reverse- - // compatibility with old "userrights lets you change - // everything") - // Using array_merge to make the groups reindexed - $all = array_merge( User::getAllGroups() ); - return array( - 'add' => $all, - 'remove' => $all, - 'add-self' => array(), - 'remove-self' => array() - ); - } - - // Okay, it's not so simple, we will have to go through the arrays - $groups = array( - 'add' => array(), - 'remove' => array(), - 'add-self' => $wgGroupsAddToSelf, - 'remove-self' => $wgGroupsRemoveFromSelf); - $addergroups = $wgUser->getEffectiveGroups(); - - foreach ($addergroups as $addergroup) { - $groups = array_merge_recursive( - $groups, $this->changeableByGroup($addergroup) - ); - $groups['add'] = array_unique( $groups['add'] ); - $groups['remove'] = array_unique( $groups['remove'] ); - } - return $groups; - } - - /** - * Returns an array of the groups that a particular group can add/remove. - * - * @param String $group The group to check for whether it can add/remove - * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) ) - */ - private function changeableByGroup( $group ) { - global $wgAddGroups, $wgRemoveGroups; - - $groups = array( 'add' => array(), 'remove' => array() ); - if( empty($wgAddGroups[$group]) ) { - // Don't add anything to $groups - } elseif( $wgAddGroups[$group] === true ) { - // You get everything - $groups['add'] = User::getAllGroups(); - } elseif( is_array($wgAddGroups[$group]) ) { - $groups['add'] = $wgAddGroups[$group]; - } - - // Same thing for remove - if( empty($wgRemoveGroups[$group]) ) { - } elseif($wgRemoveGroups[$group] === true ) { - $groups['remove'] = User::getAllGroups(); - } elseif( is_array($wgRemoveGroups[$group]) ) { - $groups['remove'] = $wgRemoveGroups[$group]; - } - return $groups; - } - - /** - * Show a rights log fragment for the specified user - * - * @param User $user User to show log for - * @param OutputPage $output OutputPage to use - */ - protected function showLogFragment( $user, $output ) { - $viewer = new LogViewer( - new LogReader( - new FauxRequest( - array( - 'type' => 'rights', - 'page' => $user->getUserPage()->getPrefixedText(), - ) - ) - ) - ); - $output->addHtml( "<h2>" . htmlspecialchars( LogPage::logName( 'rights' ) ) . "</h2>\n" ); - $viewer->showList( $output ); - } - -} diff --git a/includes/SpecialVersion.php b/includes/SpecialVersion.php deleted file mode 100644 index 70203832..00000000 --- a/includes/SpecialVersion.php +++ /dev/null @@ -1,355 +0,0 @@ -<?php -/**#@+ - * Give information about the version of MediaWiki, PHP, the DB and extensions - * - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ - -/** - * constructor - */ -function wfSpecialVersion() { - $version = new SpecialVersion; - $version->execute(); -} - -class SpecialVersion { - private $firstExtOpened = true; - - /** - * main() - */ - function execute() { - global $wgOut, $wgMessageCache; - $wgMessageCache->loadAllMessages(); - - $wgOut->addHTML( '<div dir="ltr">' ); - $wgOut->addWikiText( - $this->MediaWikiCredits() . - $this->softwareInformation() . - $this->extensionCredits() . - $this->wgHooks() - ); - $wgOut->addHTML( $this->IPInfo() ); - $wgOut->addHTML( '</div>' ); - } - - /**#@+ - * @private - */ - - /** - * @return wiki text showing the license information - */ - static function MediaWikiCredits() { - $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) ) . - "__NOTOC__ - This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''', - copyright (C) 2001-2008 Magnus Manske, Brion Vibber, Lee Daniel Crocker, - Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason, - Niklas Laxström, Domas Mituzas, Rob Church and others. - - MediaWiki is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MediaWiki is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU General Public License] - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - or [http://www.gnu.org/copyleft/gpl.html read it online]. - "; - - return str_replace( "\t\t", '', $ret ) . "\n"; - } - - /** - * @return wiki text showing the third party software versions (apache, php, mysql). - */ - static function softwareInformation() { - $dbr = wfGetDB( DB_SLAVE ); - - return Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) . - Xml::openElement( 'table', array( 'id' => 'sv-software' ) ) . - "<tr> - <th>" . wfMsg( 'version-software-product' ) . "</th> - <th>" . wfMsg( 'version-software-version' ) . "</th> - </tr>\n - <tr> - <td>[http://www.mediawiki.org/ MediaWiki]</td> - <td>" . self::getVersion() . "</td> - </tr>\n - <tr> - <td>[http://www.php.net/ PHP]</td> - <td>" . phpversion() . " (" . php_sapi_name() . ")</td> - </tr>\n - <tr> - <td>" . $dbr->getSoftwareLink() . "</td> - <td>" . $dbr->getServerVersion() . "</td> - </tr>\n" . - Xml::closeElement( 'table' ); - } - - /** Return a string of the MediaWiki version with SVN revision if available */ - public static function getVersion() { - global $wgVersion, $IP; - wfProfileIn( __METHOD__ ); - $svn = self::getSvnRevision( $IP ); - $version = $svn ? "$wgVersion (r$svn)" : $wgVersion; - wfProfileOut( __METHOD__ ); - return $version; - } - - /** Generate wikitext showing extensions name, URL, author and description */ - function extensionCredits() { - global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions; - - if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) ) - return ''; - - $extensionTypes = array( - 'specialpage' => wfMsg( 'version-specialpages' ), - 'parserhook' => wfMsg( 'version-parserhooks' ), - 'variable' => wfMsg( 'version-variables' ), - 'media' => wfMsg( 'version-mediahandlers' ), - 'other' => wfMsg( 'version-other' ), - ); - wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) ); - - $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) . - Xml::openElement( 'table', array( 'id' => 'sv-ext' ) ); - - foreach ( $extensionTypes as $type => $text ) { - if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) { - $out .= $this->openExtType( $text ); - - usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); - - foreach ( $wgExtensionCredits[$type] as $extension ) { - $out .= $this->formatCredits( - isset ( $extension['name'] ) ? $extension['name'] : '', - isset ( $extension['version'] ) ? $extension['version'] : null, - isset ( $extension['author'] ) ? $extension['author'] : '', - isset ( $extension['url'] ) ? $extension['url'] : null, - isset ( $extension['description'] ) ? $extension['description'] : '', - isset ( $extension['descriptionmsg'] ) ? $extension['descriptionmsg'] : '' - ); - } - } - } - - if ( count( $wgExtensionFunctions ) ) { - $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) ); - $out .= '<tr><td colspan="3">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n"; - } - - if ( $cnt = count( $tags = $wgParser->getTags() ) ) { - for ( $i = 0; $i < $cnt; ++$i ) - $tags[$i] = "<{$tags[$i]}>"; - $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) ); - $out .= '<tr><td colspan="3">' . $this->listToText( $tags ). "</td></tr>\n"; - } - - if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) { - $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) ); - $out .= '<tr><td colspan="3">' . $this->listToText( $fhooks ) . "</td></tr>\n"; - } - - if ( count( $wgSkinExtensionFunctions ) ) { - $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ) ); - $out .= '<tr><td colspan="3">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n"; - } - $out .= Xml::closeElement( 'table' ); - return $out; - } - - /** Callback to sort extensions by type */ - function compare( $a, $b ) { - global $wgLang; - if( $a['name'] === $b['name'] ) { - return 0; - } else { - return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] ) - ? 1 - : -1; - } - } - - function formatCredits( $name, $version = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) { - $extension = isset( $url ) ? "[$url $name]" : $name; - $version = isset( $version ) ? "(" . wfMsg( 'version-version' ) . " $version)" : ''; - - # Look for a localized description - if( isset( $descriptionMsg ) ) { - $msg = wfMsg( $descriptionMsg ); - if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) { - $description = $msg; - } - } - - return "<tr> - <td><em>$extension $version</em></td> - <td>$description</td> - <td>" . $this->listToText( (array)$author ) . "</td> - </tr>\n"; - } - - /** - * @return string - */ - function wgHooks() { - global $wgHooks; - - if ( count( $wgHooks ) ) { - $myWgHooks = $wgHooks; - ksort( $myWgHooks ); - - $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) . - Xml::openElement( 'table', array( 'id' => 'sv-hooks' ) ) . - "<tr> - <th>" . wfMsg( 'version-hook-name' ) . "</th> - <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th> - </tr>\n"; - - foreach ( $myWgHooks as $hook => $hooks ) - $ret .= "<tr> - <td>$hook</td> - <td>" . $this->listToText( $hooks ) . "</td> - </tr>\n"; - - $ret .= Xml::closeElement( 'table' ); - return $ret; - } else - return ''; - } - - private function openExtType($text, $name = null) { - $opt = array( 'colspan' => 3 ); - $out = ''; - - if(!$this->firstExtOpened) { - // Insert a spacing line - $out .= '<tr class="sv-space">' . Xml::element( 'td', $opt ) . "</tr>\n"; - } - $this->firstExtOpened = false; - - if($name) { $opt['id'] = "sv-$name"; } - - $out .= "<tr>" . Xml::element( 'th', $opt, $text) . "</tr>\n"; - return $out; - } - - /** - * @static - * - * @return string - */ - function IPInfo() { - $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) ); - return "<!-- visited from $ip -->\n" . - "<span style='display:none'>visited from $ip</span>"; - } - - /** - * @param array $list - * @return string - */ - function listToText( $list ) { - $cnt = count( $list ); - - if ( $cnt == 1 ) { - // Enforce always returning a string - return (string)$this->arrayToString( $list[0] ); - } elseif ( $cnt == 0 ) { - return ''; - } else { - sort( $list ); - $t = array_slice( $list, 0, $cnt - 1 ); - $one = array_map( array( &$this, 'arrayToString' ), $t ); - $two = $this->arrayToString( $list[$cnt - 1] ); - $and = wfMsg( 'and' ); - - return implode( ', ', $one ) . " $and $two"; - } - } - - /** - * @static - * - * @param mixed $list Will convert an array to string if given and return - * the paramater unaltered otherwise - * @return mixed - */ - function arrayToString( $list ) { - if( is_object( $list ) ) { - $class = get_class( $list ); - return "($class)"; - } elseif ( ! is_array( $list ) ) { - return $list; - } else { - $class = get_class( $list[0] ); - return "($class, {$list[1]})"; - } - } - - /** - * Retrieve the revision number of a Subversion working directory. - * - * @param string $dir - * @return mixed revision number as int, or false if not a SVN checkout - */ - public static function getSvnRevision( $dir ) { - // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html - $entries = $dir . '/.svn/entries'; - - if( !file_exists( $entries ) ) { - return false; - } - - $content = file( $entries ); - - // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4) - if( preg_match( '/^<\?xml/', $content[0] ) ) { - // subversion is release <= 1.3 - if( !function_exists( 'simplexml_load_file' ) ) { - // We could fall back to expat... YUCK - return false; - } - - // SimpleXml whines about the xmlns... - wfSuppressWarnings(); - $xml = simplexml_load_file( $entries ); - wfRestoreWarnings(); - - if( $xml ) { - foreach( $xml->entry as $entry ) { - if( $xml->entry[0]['name'] == '' ) { - // The directory entry should always have a revision marker. - if( $entry['revision'] ) { - return intval( $entry['revision'] ); - } - } - } - } - return false; - } else { - // subversion is release 1.4 - return intval( $content[3] ); - } - } - - /**#@-*/ -} - -/**#@-*/ - - diff --git a/includes/SpecialWantedcategories.php b/includes/SpecialWantedcategories.php deleted file mode 100644 index 580cc6de..00000000 --- a/includes/SpecialWantedcategories.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php -/** - * A querypage to list the most wanted categories - implements Special:Wantedcategories - * - * @addtogroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later - */ -class WantedCategoriesPage extends QueryPage { - - function getName() { return 'Wantedcategories'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT - $name as type, - " . NS_CATEGORY . " as namespace, - cl_to as title, - COUNT(*) as value - FROM $categorylinks - LEFT JOIN $page ON cl_to = page_title AND page_namespace = ". NS_CATEGORY ." - WHERE page_title IS NULL - GROUP BY 1,2,3 - "; - } - - function sortDescending() { return true; } - - /** - * Fetch user page links and cache their existence - */ - function preprocessResults( $db, $res ) { - $batch = new LinkBatch; - while ( $row = $db->fetchObject( $res ) ) - $batch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) ); - $batch->execute(); - - // Back to start for display - if ( $db->numRows( $res ) > 0 ) - // If there are no rows we get an error seeking. - $db->dataSeek( $res, 0 ); - } - - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getText() ); - - $plink = $this->isCached() ? - $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) : - $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) ); - - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - return wfSpecialList($plink, $nlinks); - } -} - -/** - * constructor - */ -function wfSpecialWantedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new WantedCategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} - - diff --git a/includes/SpecialWantedpages.php b/includes/SpecialWantedpages.php deleted file mode 100644 index 1fb8cdbb..00000000 --- a/includes/SpecialWantedpages.php +++ /dev/null @@ -1,133 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * implements Special:Wantedpages - * @addtogroup SpecialPage - */ -class WantedPagesPage extends QueryPage { - var $nlinks; - - function WantedPagesPage( $inc = false, $nlinks = true ) { - $this->setListoutput( $inc ); - $this->nlinks = $nlinks; - } - - function getName() { - return 'Wantedpages'; - } - - function isExpensive() { - return true; - } - function isSyndicated() { return false; } - - function getSQL() { - global $wgWantedPagesThreshold; - $count = $wgWantedPagesThreshold - 1; - $dbr = wfGetDB( DB_SLAVE ); - $pagelinks = $dbr->tableName( 'pagelinks' ); - $page = $dbr->tableName( 'page' ); - return - "SELECT 'Wantedpages' AS type, - pl_namespace AS namespace, - pl_title AS title, - COUNT(*) AS value - FROM $pagelinks - LEFT JOIN $page AS pg1 - ON pl_namespace = pg1.page_namespace AND pl_title = pg1.page_title - LEFT JOIN $page AS pg2 - ON pl_from = pg2.page_id - WHERE pg1.page_namespace IS NULL - AND pl_namespace NOT IN ( 2, 3 ) - AND pg2.page_namespace != 8 - GROUP BY 1,2,3 - HAVING COUNT(*) > $count"; - } - - /** - * Cache page existence for performance - */ - function preprocessResults( $db, $res ) { - $batch = new LinkBatch; - while ( $row = $db->fetchObject( $res ) ) - $batch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) ); - $batch->execute(); - - // Back to start for display - if ( $db->numRows( $res ) > 0 ) - // If there are no rows we get an error seeking. - $db->dataSeek( $res, 0 ); - } - - /** - * Format an individual result - * - * @param Skin $skin Skin to use for UI elements - * @param object $result Result row - * @return string - */ - public function formatResult( $skin, $result ) { - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if( $title instanceof Title ) { - if( $this->isCached() ) { - $pageLink = $title->exists() - ? '<s>' . $skin->makeLinkObj( $title ) . '</s>' - : $skin->makeBrokenLinkObj( $title ); - } else { - $pageLink = $skin->makeBrokenLinkObj( $title ); - } - return wfSpecialList( $pageLink, $this->makeWlhLink( $title, $skin, $result ) ); - } else { - $tsafe = htmlspecialchars( $result->title ); - return "Invalid title in result set; {$tsafe}"; - } - } - - /** - * Make a "what links here" link for a specified result if required - * - * @param Title $title Title to make the link for - * @param Skin $skin Skin to use - * @param object $result Result row - * @return string - */ - private function makeWlhLink( $title, $skin, $result ) { - global $wgLang; - if( $this->nlinks ) { - $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); - $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->value ) ); - return $skin->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() ); - } else { - return null; - } - } - -} - -/** - * constructor - */ -function wfSpecialWantedpages( $par = null, $specialPage ) { - $inc = $specialPage->including(); - - if ( $inc ) { - @list( $limit, $nlinks ) = explode( '/', $par, 2 ); - $limit = (int)$limit; - $nlinks = $nlinks === 'nlinks'; - $offset = 0; - } else { - list( $limit, $offset ) = wfCheckLimits(); - $nlinks = true; - } - - $wpp = new WantedPagesPage( $inc, $nlinks ); - - $wpp->doQuery( $offset, $limit, !$inc ); -} - - diff --git a/includes/SpecialWatchlist.php b/includes/SpecialWatchlist.php deleted file mode 100644 index 2dfa8ae5..00000000 --- a/includes/SpecialWatchlist.php +++ /dev/null @@ -1,375 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * - */ -require_once( dirname(__FILE__) . '/SpecialRecentchanges.php' ); - -/** - * Constructor - * - * @param $par Parameter passed to the page - */ -function wfSpecialWatchlist( $par ) { - global $wgUser, $wgOut, $wgLang, $wgRequest; - global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; - global $wgEnotifWatchlist; - $fname = 'wfSpecialWatchlist'; - - $skin = $wgUser->getSkin(); - $specialTitle = SpecialPage::getTitleFor( 'Watchlist' ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - - # Anons don't get a watchlist - if( $wgUser->isAnon() ) { - $wgOut->setPageTitle( wfMsg( 'watchnologin' ) ); - $llink = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Userlogin' ), wfMsgHtml( 'loginreqlink' ), 'returnto=' . $specialTitle->getPrefixedUrl() ); - $wgOut->addHtml( wfMsgWikiHtml( 'watchlistanontext', $llink ) ); - return; - } - - $wgOut->setPageTitle( wfMsg( 'watchlist' ) ); - - $sub = wfMsgExt( 'watchlistfor', 'parseinline', $wgUser->getName() ); - $sub .= '<br />' . WatchlistEditor::buildTools( $wgUser->getSkin() ); - $wgOut->setSubtitle( $sub ); - - if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) { - $editor = new WatchlistEditor(); - $editor->execute( $wgUser, $wgOut, $wgRequest, $mode ); - return; - } - - $uid = $wgUser->getId(); - if( $wgEnotifWatchlist && $wgRequest->getVal( 'reset' ) && $wgRequest->wasPosted() ) { - $wgUser->clearAllNotifications( $uid ); - $wgOut->redirect( $specialTitle->getFullUrl() ); - return; - } - - $defaults = array( - /* float */ 'days' => floatval( $wgUser->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */ - /* bool */ 'hideOwn' => (int)$wgUser->getBoolOption( 'watchlisthideown' ), - /* bool */ 'hideBots' => (int)$wgUser->getBoolOption( 'watchlisthidebots' ), - /* bool */ 'hideMinor' => (int)$wgUser->getBoolOption( 'watchlisthideminor' ), - /* ? */ 'namespace' => 'all', - ); - - extract($defaults); - - # Extract variables from the request, falling back to user preferences or - # other default values if these don't exist - $prefs['days' ] = floatval( $wgUser->getOption( 'watchlistdays' ) ); - $prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' ); - $prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' ); - $prefs['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' ); - - # Get query variables - $days = $wgRequest->getVal( 'days', $prefs['days'] ); - $hideOwn = $wgRequest->getBool( 'hideOwn', $prefs['hideown'] ); - $hideBots = $wgRequest->getBool( 'hideBots', $prefs['hidebots'] ); - $hideMinor = $wgRequest->getBool( 'hideMinor', $prefs['hideminor'] ); - - # Get namespace value, if supplied, and prepare a WHERE fragment - $nameSpace = $wgRequest->getIntOrNull( 'namespace' ); - if( !is_null( $nameSpace ) ) { - $nameSpace = intval( $nameSpace ); - $nameSpaceClause = " AND rc_namespace = $nameSpace"; - } else { - $nameSpace = ''; - $nameSpaceClause = ''; - } - - $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - list( $page, $watchlist, $recentchanges ) = $dbr->tableNamesN( 'page', 'watchlist', 'recentchanges' ); - - $watchlistCount = $dbr->selectField( 'watchlist', 'COUNT(*)', - array( 'wl_user' => $uid ), __METHOD__ ); - // Adjust for page X, talk:page X, which are both stored separately, - // but treated together - $nitems = floor($watchlistCount / 2); - - if( is_null($days) || !is_numeric($days) ) { - $big = 1000; /* The magical big */ - if($nitems > $big) { - # Set default cutoff shorter - $days = $defaults['days'] = (12.0 / 24.0); # 12 hours... - } else { - $days = $defaults['days']; # default cutoff for shortlisters - } - } else { - $days = floatval($days); - } - - // Dump everything here - $nondefaults = array(); - - wfAppendToArrayIfNotDefault('days' , $days , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault('hideOwn' , (int)$hideOwn , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault('hideBots' , (int)$hideBots, $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'hideMinor', (int)$hideMinor, $defaults, $nondefaults ); - wfAppendToArrayIfNotDefault('namespace', $nameSpace , $defaults, $nondefaults); - - $hookSql = ""; - if( ! wfRunHooks('BeforeWatchlist', array($nondefaults, $wgUser, &$hookSql)) ) { - return; - } - - if($nitems == 0) { - $wgOut->addWikiMsg( 'nowatchlist' ); - return; - } - - if ( $days <= 0 ) { - $andcutoff = ''; - } else { - $andcutoff = "AND rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'"; - /* - $sql = "SELECT COUNT(*) AS n FROM $page, $revision WHERE rev_timestamp>'$cutoff' AND page_id=rev_page"; - $res = $dbr->query( $sql, $fname ); - $s = $dbr->fetchObject( $res ); - $npages = $s->n; - */ - } - - # If the watchlist is relatively short, it's simplest to zip - # down its entirety and then sort the results. - - # If it's relatively long, it may be worth our while to zip - # through the time-sorted page list checking for watched items. - - # Up estimate of watched items by 15% to compensate for talk pages... - - # Toggles - $andHideOwn = $hideOwn ? "AND (rc_user <> $uid)" : ''; - $andHideBots = $hideBots ? "AND (rc_bot = 0)" : ''; - $andHideMinor = $hideMinor ? 'AND rc_minor = 0' : ''; - - # Show watchlist header - $header = ''; - if( $wgUser->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) { - $header .= wfMsg( 'wlheader-enotif' ) . "\n"; - } - if ( $wgEnotifWatchlist && $wgShowUpdatedMarker ) { - $header .= wfMsg( 'wlheader-showupdated' ) . "\n"; - } - - # Toggle watchlist content (all recent edits or just the latest) - if( $wgUser->getOption( 'extendwatchlist' )) { - $andLatest=''; - $limitWatchlist = 'LIMIT ' . intval( $wgUser->getOption( 'wllimit' ) ); - } else { - $andLatest= 'AND rc_this_oldid=page_latest'; - $limitWatchlist = ''; - } - - $header .= wfMsgExt( 'watchlist-details', array( 'parsemag' ), $wgLang->formatNum( $nitems ) ); - $wgOut->addWikiText( $header ); - - # Show a message about slave lag, if applicable - if( ( $lag = $dbr->getLag() ) > 0 ) - $wgOut->showLagWarning( $lag ); - - if ( $wgEnotifWatchlist && $wgShowUpdatedMarker ) { - $wgOut->addHTML( '<form action="' . - $specialTitle->escapeLocalUrl() . - '" method="post"><input type="submit" name="dummy" value="' . - htmlspecialchars( wfMsg( 'enotif_reset' ) ) . - '" /><input type="hidden" name="reset" value="all" /></form>' . - "\n\n" ); - } - if ( $wgShowUpdatedMarker ) { - $wltsfield=", ${watchlist}.wl_notificationtimestamp "; - } - $sql = "SELECT ${recentchanges}.* ${wltsfield} - FROM $watchlist,$recentchanges,$page - WHERE wl_user=$uid - AND wl_namespace=rc_namespace - AND wl_title=rc_title - AND rc_cur_id=page_id - $andcutoff - $andLatest - $andHideOwn - $andHideBots - $andHideMinor - $nameSpaceClause - $hookSql - ORDER BY rc_timestamp DESC - $limitWatchlist"; - - $res = $dbr->query( $sql, $fname ); - $numRows = $dbr->numRows( $res ); - - /* Start bottom header */ - $wgOut->addHTML( "<hr />\n" ); - - if($days >= 1) { - $wgOut->addWikiText( wfMsgExt( 'rcnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), - $wgLang->formatNum( $days ), $wgLang->timeAndDate( wfTimestampNow(), true ) ) . '<br />' , false ); - } elseif($days > 0) { - $wgOut->addWikiText( wfMsgExt( 'wlnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), - $wgLang->formatNum( round($days*24) ) ) . '<br />' , false ); - } - - $wgOut->addHTML( "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n" ); - - # Spit out some control panel links - $thisTitle = SpecialPage::getTitleFor( 'Watchlist' ); - $skin = $wgUser->getSkin(); - - # Hide/show bot edits - $label = $hideBots ? wfMsgHtml( 'watchlist-show-bots' ) : wfMsgHtml( 'watchlist-hide-bots' ); - $linkBits = wfArrayToCGI( array( 'hideBots' => 1 - (int)$hideBots ), $nondefaults ); - $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); - - # Hide/show own edits - $label = $hideOwn ? wfMsgHtml( 'watchlist-show-own' ) : wfMsgHtml( 'watchlist-hide-own' ); - $linkBits = wfArrayToCGI( array( 'hideOwn' => 1 - (int)$hideOwn ), $nondefaults ); - $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); - - # Hide/show minor edits - $label = $hideMinor ? wfMsgHtml( 'watchlist-show-minor' ) : wfMsgHtml( 'watchlist-hide-minor' ); - $linkBits = wfArrayToCGI( array( 'hideMinor' => 1 - (int)$hideMinor ), $nondefaults ); - $links[] = $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ); - - $wgOut->addHTML( implode( ' | ', $links ) ); - - # Form for namespace filtering - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) ); - $form .= '<p>'; - $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; - $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; - $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>'; - $form .= Xml::hidden( 'days', $days ); - if( $hideOwn ) - $form .= Xml::hidden( 'hideOwn', 1 ); - if( $hideBots ) - $form .= Xml::hidden( 'hideBots', 1 ); - if( $hideMinor ) - $form .= Xml::hidden( 'hideMinor', 1 ); - $form .= Xml::closeElement( 'form' ); - $wgOut->addHtml( $form ); - - # If there's nothing to show, stop here - if( $numRows == 0 ) { - $wgOut->addWikiMsg( 'watchnochange' ); - return; - } - - /* End bottom header */ - - /* Do link batch query */ - $linkBatch = new LinkBatch; - while ( $row = $dbr->fetchObject( $res ) ) { - $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); - if ( $row->rc_user != 0 ) { - $linkBatch->add( NS_USER, $userNameUnderscored ); - } - $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); - } - $linkBatch->execute(); - $dbr->dataSeek( $res, 0 ); - - $list = ChangesList::newFromUser( $wgUser ); - - $s = $list->beginRecentChangesList(); - $counter = 1; - while ( $obj = $dbr->fetchObject( $res ) ) { - # Make RC entry - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - - if ( $wgShowUpdatedMarker ) { - $updated = $obj->wl_notificationtimestamp; - } else { - // Same visual appearance as MW 1.4 - $updated = true; - } - - if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) { - $rc->numberofWatchingusers = $dbr->selectField( 'watchlist', - 'COUNT(*)', - array( - 'wl_namespace' => $obj->rc_namespace, - 'wl_title' => $obj->rc_title, - ), - __METHOD__ ); - } else { - $rc->numberofWatchingusers = 0; - } - - $s .= $list->recentChangesLine( $rc, $updated ); - } - $s .= $list->endRecentChangesList(); - - $dbr->freeResult( $res ); - $wgOut->addHTML( $s ); - -} - -function wlHoursLink( $h, $page, $options = array() ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( - $wgContLang->specialPage( $page ), - $wgLang->formatNum( $h ), - wfArrayToCGI( array('days' => ($h / 24.0)), $options ) ); - return $s; -} - -function wlDaysLink( $d, $page, $options = array() ) { - global $wgUser, $wgLang, $wgContLang; - $sk = $wgUser->getSkin(); - $s = $sk->makeKnownLink( - $wgContLang->specialPage( $page ), - ($d ? $wgLang->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) ), - wfArrayToCGI( array('days' => $d), $options ) ); - return $s; -} - -/** - * Returns html - */ -function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { - $hours = array( 1, 2, 6, 12 ); - $days = array( 1, 3, 7 ); - $i = 0; - foreach( $hours as $h ) { - $hours[$i++] = wlHoursLink( $h, $page, $options ); - } - $i = 0; - foreach( $days as $d ) { - $days[$i++] = wlDaysLink( $d, $page, $options ); - } - return wfMsgExt('wlshowlast', - array('parseinline', 'replaceafter'), - implode(' | ', $hours), - implode(' | ', $days), - wlDaysLink( 0, $page, $options ) ); -} - -/** - * Count the number of items on a user's watchlist - * - * @param $talk Include talk pages - * @return integer - */ -function wlCountItems( &$user, $talk = true ) { - $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - - # Fetch the raw count - $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->mId ), 'wlCountItems' ); - $row = $dbr->fetchObject( $res ); - $count = $row->count; - $dbr->freeResult( $res ); - - # Halve to remove talk pages if needed - if( !$talk ) - $count = floor( $count / 2 ); - - return( $count ); -} diff --git a/includes/SpecialWhatlinkshere.php b/includes/SpecialWhatlinkshere.php deleted file mode 100644 index 16a44ee6..00000000 --- a/includes/SpecialWhatlinkshere.php +++ /dev/null @@ -1,322 +0,0 @@ -<?php -/** - * - * @addtogroup SpecialPage - */ - -/** - * Entry point - * @param string $par An article name ?? - */ -function wfSpecialWhatlinkshere($par = NULL) { - global $wgRequest; - $page = new WhatLinksHerePage( $wgRequest, $par ); - $page->execute(); -} - -/** - * implements Special:Whatlinkshere - * @addtogroup SpecialPage - */ -class WhatLinksHerePage { - var $request, $par; - var $limit, $from, $back, $target; - var $selfTitle, $skin; - - private $namespace; - - function WhatLinksHerePage( &$request, $par = null ) { - global $wgUser; - $this->request =& $request; - $this->skin = $wgUser->getSkin(); - $this->par = $par; - } - - function execute() { - global $wgOut; - - $this->limit = min( $this->request->getInt( 'limit', 50 ), 5000 ); - if ( $this->limit <= 0 ) { - $this->limit = 50; - } - $this->from = $this->request->getInt( 'from' ); - $this->back = $this->request->getInt( 'back' ); - - $targetString = isset($this->par) ? $this->par : $this->request->getVal( 'target' ); - - if ( is_null( $targetString ) ) { - $wgOut->addHTML( $this->whatlinkshereForm() ); - return; - } - - $this->target = Title::newFromURL( $targetString ); - if( !$this->target ) { - $wgOut->addHTML( $this->whatlinkshereForm() ); - return; - } - $this->selfTitle = Title::makeTitleSafe( NS_SPECIAL, - 'Whatlinkshere/' . $this->target->getPrefixedDBkey() ); - - $wgOut->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) ); - $wgOut->setSubtitle( wfMsg( 'linklistsub' ) ); - - $wgOut->addHTML( wfMsgExt( 'whatlinkshere-barrow', array( 'escapenoentities') ) . ' ' .$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."<br />\n"); - - $this->showIndirectLinks( 0, $this->target, $this->limit, $this->from, $this->back ); - } - - /** - * @param int $level Recursion level - * @param Title $target Target title - * @param int $limit Number of entries to display - * @param Title $from Display from this article ID - * @param Title $back Display from this article ID at backwards scrolling - * @private - */ - function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) { - global $wgOut; - $fname = 'WhatLinksHerePage::showIndirectLinks'; - $dbr = wfGetDB( DB_READ ); - $options = array(); - - $ns = $this->request->getIntOrNull( 'namespace' ); - if ( isset( $ns ) ) { - $options['namespace'] = $ns; - $this->setNamespace( $options['namespace'] ); - } else { - $options['namespace'] = ''; - } - - // Make the query - $plConds = array( - 'page_id=pl_from', - 'pl_namespace' => $target->getNamespace(), - 'pl_title' => $target->getDBkey(), - ); - - $tlConds = array( - 'page_id=tl_from', - 'tl_namespace' => $target->getNamespace(), - 'tl_title' => $target->getDBkey(), - ); - - if ( $this->namespace !== null ){ - $plConds['page_namespace'] = (int)$this->namespace; - $tlConds['page_namespace'] = (int)$this->namespace; - } - - if ( $from ) { - $from = (int)$from; // just in case - $tlConds[] = "tl_from >= $from"; - $plConds[] = "pl_from >= $from"; - } - - // Read an extra row as an at-end check - $queryLimit = $limit + 1; - - // enforce join order, sometimes namespace selector may - // trigger filesorts which are far less efficient than scanning many entries - $options[] = 'STRAIGHT_JOIN'; - - $options['LIMIT'] = $queryLimit; - $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ); - - $options['ORDER BY'] = 'pl_from'; - $plRes = $dbr->select( array( 'pagelinks', 'page' ), $fields, - $plConds, $fname, $options ); - - $options['ORDER BY'] = 'tl_from'; - $tlRes = $dbr->select( array( 'templatelinks', 'page' ), $fields, - $tlConds, $fname, $options ); - - if ( !$dbr->numRows( $plRes ) && !$dbr->numRows( $tlRes ) ) { - if ( 0 == $level ) { - $options = array(); // reinitialize for a further namespace search - // really no links to here - $options['namespace'] = $this->namespace; - $options['target'] = $this->target->getPrefixedText(); - list( $options['limit'], $options['offset']) = wfCheckLimits(); - $wgOut->addHTML( $this->whatlinkshereForm( $options ) ); - $errMsg = isset( $this->namespace ) ? 'nolinkshere-ns' : 'nolinkshere'; - $wgOut->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); - } - return; - } - - $options = array(); - list( $options['limit'], $options['offset']) = wfCheckLimits(); - if ( ( $ns = $this->request->getVal( 'namespace', null ) ) !== null && $ns !== '' && ctype_digit($ns) ) { - $options['namespace'] = intval( $ns ); - $this->setNamespace( $options['namespace'] ); - } else { - $options['namespace'] = ''; - $this->setNamespace( null ); - } - $options['offset'] = $this->request->getVal( 'offset' ); - /* Offset must be an integral. */ - if ( !strlen( $options['offset'] ) || !preg_match( '/^[0-9]+$/', $options['offset'] ) ) - $options['offset'] = ''; - $options['target'] = $this->target->getPrefixedText(); - - // Read the rows into an array and remove duplicates - // templatelinks comes second so that the templatelinks row overwrites the - // pagelinks row, so we get (inclusion) rather than nothing - while ( $row = $dbr->fetchObject( $plRes ) ) { - $row->is_template = 0; - $rows[$row->page_id] = $row; - } - $dbr->freeResult( $plRes ); - while ( $row = $dbr->fetchObject( $tlRes ) ) { - $row->is_template = 1; - $rows[$row->page_id] = $row; - } - $dbr->freeResult( $tlRes ); - - // Sort by key and then change the keys to 0-based indices - ksort( $rows ); - $rows = array_values( $rows ); - - $numRows = count( $rows ); - - // Work out the start and end IDs, for prev/next links - if ( $numRows > $limit ) { - // More rows available after these ones - // Get the ID from the last row in the result set - $nextId = $rows[$limit]->page_id; - // Remove undisplayed rows - $rows = array_slice( $rows, 0, $limit ); - } else { - // No more rows after - $nextId = false; - } - $prevId = $from; - - if ( $level == 0 ) { - $wgOut->addHTML( $this->whatlinkshereForm( $options ) ); - $wgOut->addWikiMsg( 'linkshere', $this->target->getPrefixedText() ); - - $prevnext = $this->getPrevNext( $limit, $prevId, $nextId, $options['namespace'] ); - $wgOut->addHTML( $prevnext ); - } - - $wgOut->addHTML( '<ul>' ); - foreach ( $rows as $row ) { - $nt = Title::makeTitle( $row->page_namespace, $row->page_title ); - - if ( $row->page_is_redirect ) { - $extra = 'redirect=no'; - } else { - $extra = ''; - } - - $link = $this->skin->makeKnownLinkObj( $nt, '', $extra ); - $wgOut->addHTML( '<li>'.$link ); - - // Display properties (redirect or template) - $props = array(); - if ( $row->page_is_redirect ) { - $props[] = wfMsgHtml( 'isredirect' ); - } - if ( $row->is_template ) { - $props[] = wfMsgHtml( 'istemplate' ); - } - if ( count( $props ) ) { - $list = implode( wfMsgHtml( 'semicolon-separator' ), $props ); - $wgOut->addHTML( " ($list) " ); - } - - # Space for utilities links, with a what-links-here link provided - $wlh = $this->skin->makeKnownLinkObj( - SpecialPage::getTitleFor( 'Whatlinkshere' ), - wfMsgHtml( 'whatlinkshere-links' ), - 'target=' . $nt->getPrefixedUrl() - ); - $wgOut->addHtml( ' <span class="mw-whatlinkshere-tools">(' . $wlh . ')</span>' ); - - if ( $row->page_is_redirect ) { - if ( $level < 2 ) { - $this->showIndirectLinks( $level + 1, $nt, 500 ); - } - } - $wgOut->addHTML( "</li>\n" ); - } - $wgOut->addHTML( "</ul>\n" ); - - if( $level == 0 ) { - $wgOut->addHTML( $prevnext ); - } - } - - function makeSelfLink( $text, $query ) { - return $this->skin->makeKnownLinkObj( $this->selfTitle, $text, $query ); - } - - function getPrevNext( $limit, $prevId, $nextId ) { - global $wgLang; - $fmtLimit = $wgLang->formatNum( $limit ); - $prev = wfMsgExt( 'whatlinkshere-prev', array( 'parsemag', 'escape' ), $fmtLimit ); - $next = wfMsgExt( 'whatlinkshere-next', array( 'parsemag', 'escape' ), $fmtLimit ); - - $nsText = ''; - if( is_int($this->namespace) ) { - $nsText = "&namespace={$this->namespace}"; - } - - if ( 0 != $prevId ) { - $prevLink = $this->makeSelfLink( $prev, "limit={$limit}&from={$this->back}{$nsText}" ); - } else { - $prevLink = $prev; - } - if ( 0 != $nextId ) { - $nextLink = $this->makeSelfLink( $next, "limit={$limit}&from={$nextId}&back={$prevId}{$nsText}" ); - } else { - $nextLink = $next; - } - $nums = $this->numLink( 20, $prevId ) . ' | ' . - $this->numLink( 50, $prevId ) . ' | ' . - $this->numLink( 100, $prevId ) . ' | ' . - $this->numLink( 250, $prevId ) . ' | ' . - $this->numLink( 500, $prevId ); - - return wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $nums ); - } - - function numLink( $limit, $from, $ns = null ) { - global $wgLang; - $query = "limit={$limit}&from={$from}"; - if( is_int($this->namespace) ) { $query .= "&namespace={$this->namespace}";} - $fmtLimit = $wgLang->formatNum( $limit ); - return $this->makeSelfLink( $fmtLimit, $query ); - } - - function whatlinkshereForm( $options = array( 'target' => '', 'namespace' => '' ) ) { - global $wgScript, $wgTitle; - - $options['title'] = $wgTitle->getPrefixedText(); - - $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'whatlinkshere' ) ) . - Xml::inputLabel( wfMsg( 'whatlinkshere-page' ), 'target', 'mw-whatlinkshere-target', 40, $options['target'] ) . ' '; - - foreach ( $options as $name => $value ) { - if( $name === 'namespace' || $name === 'target' ) - continue; - $f .= "\t" . Xml::hidden( $name, $value ). "\n"; - } - - $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . - Xml::namespaceSelector( $options['namespace'], '' ) . - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n"; - - return $f; - } - - /** Set the namespace we are filtering on */ - private function setNamespace( $ns ) { - $this->namespace = $ns; - } - -} diff --git a/includes/SpecialWithoutinterwiki.php b/includes/SpecialWithoutinterwiki.php deleted file mode 100644 index 37d9a282..00000000 --- a/includes/SpecialWithoutinterwiki.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php - -/** - * Special page lists pages without language links - * - * @package MediaWiki - * @addtogroup SpecialPage - * @author Rob Church <robchur@gmail.com> - */ -class WithoutInterwikiPage extends PageQueryPage { - private $prefix = ''; - - function getName() { - return 'Withoutinterwiki'; - } - - function getPageHeader() { - global $wgScript, $wgContLang; - $prefix = $this->prefix; - $t = SpecialPage::getTitleFor( $this->getName() ); - $align = $wgContLang->isRtl() ? 'left' : 'right'; - - $s = '<p>' . wfMsgExt( 'withoutinterwiki-header', array( 'parseinline' ) ) . '</p>'; - $s .= Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); - $s .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $s .= Xml::hidden( 'title', $t->getPrefixedText() ); - $s .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'withoutinterwiki' ) ); - $s .= "<tr> - <td align='$align'>" . - Xml::label( wfMsg( 'allpagesprefix' ), 'wiprefix' ) . - "</td> - <td>" . - Xml::input( 'prefix', 20, htmlspecialchars ( $prefix ), array( 'id' => 'wiprefix' ) ) . - "</td> - </tr> - <tr> - <td align='$align'></td> - <td>" . - Xml::submitButton( wfMsgHtml( 'withoutinterwiki-submit' ) ) . - "</td> - </tr>"; - $s .= Xml::closeElement( 'table' ); - $s .= Xml::closeElement( 'form' ); - $s .= Xml::closeElement( 'div' ); - return $s; - } - - function sortDescending() { - return false; - } - - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' ); - $prefix = $this->prefix ? "AND page_title LIKE '" . $dbr->escapeLike( $this->prefix ) . "%'" : ''; - return - "SELECT 'Withoutinterwiki' AS type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $langlinks - ON ll_from = page_id - WHERE ll_title IS NULL - AND page_namespace=" . NS_MAIN . " - AND page_is_redirect = 0 - {$prefix}"; - } - - function setPrefix( $prefix = '' ) { - $this->prefix = $prefix; - } - -} - -function wfSpecialWithoutinterwiki() { - global $wgRequest; - list( $limit, $offset ) = wfCheckLimits(); - $prefix = $wgRequest->getVal( 'prefix' ); - $wip = new WithoutInterwikiPage(); - $wip->setPrefix( $prefix ); - $wip->doQuery( $offset, $limit ); -} - - diff --git a/includes/StreamFile.php b/includes/StreamFile.php index b4bf531c..4abd7364 100644 --- a/includes/StreamFile.php +++ b/includes/StreamFile.php @@ -31,6 +31,12 @@ function wfStreamFile( $fname, $headers = array() ) { header('Content-type: application/x-wiki'); } + // Don't stream it out as text/html if there was a PHP error + if ( headers_sent() ) { + echo "Headers already sent, terminating.\n"; + return; + } + global $wgContLanguageCode; header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( basename( $fname ) ) ); @@ -53,25 +59,51 @@ function wfStreamFile( $fname, $headers = array() ) { } /** */ -function wfGetType( $filename ) { +function wfGetType( $filename, $safe = true ) { global $wgTrivialMimeDetection; + $ext = strrchr($filename, '.'); + $ext = $ext === false ? '' : strtolower( substr( $ext, 1 ) ); + # trivial detection by file extension, # used for thumbnails (thumb.php) if ($wgTrivialMimeDetection) { - $ext= strtolower(strrchr($filename, '.')); - switch ($ext) { - case '.gif': return 'image/gif'; - case '.png': return 'image/png'; - case '.jpg': return 'image/jpeg'; - case '.jpeg': return 'image/jpeg'; + case 'gif': return 'image/gif'; + case 'png': return 'image/png'; + case 'jpg': return 'image/jpeg'; + case 'jpeg': return 'image/jpeg'; } return 'unknown/unknown'; } - else { - $magic = MimeMagic::singleton(); - return $magic->guessMimeType($filename); //full fancy mime detection + + $magic = MimeMagic::singleton(); + // Use the extension only, rather than magic numbers, to avoid opening + // up vulnerabilities due to uploads of files with allowed extensions + // but disallowed types. + $type = $magic->guessTypesForExtension( $ext ); + + /** + * Double-check some security settings that were done on upload but might + * have changed since. + */ + if ( $safe ) { + global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions, + $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist, $wgRequest; + $form = new UploadForm( $wgRequest ); + list( $partName, $extList ) = $form->splitExtensions( $filename ); + if ( $form->checkFileExtensionList( $extList, $wgFileBlacklist ) ) { + return 'unknown/unknown'; + } + if ( $wgCheckFileExtensions && $wgStrictFileExtensions + && !$form->checkFileExtensionList( $extList, $wgFileExtensions ) ) + { + return 'unknown/unknown'; + } + if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) { + return 'unknown/unknown'; + } } + return $type; } diff --git a/includes/Title.php b/includes/Title.php index ad425c5e..6326240c 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -320,9 +320,13 @@ class Title { $m[1] = urldecode( ltrim( $m[1], ':' ) ); } $title = Title::newFromText( $m[1] ); - // Redirects to Special:Userlogout are not permitted - if( $title instanceof Title && !$title->isSpecial( 'Userlogout' ) ) + // Redirects to some special pages are not permitted + if( $title instanceof Title + && !$title->isSpecial( 'Userlogout' ) + && !$title->isSpecial( 'Filepath' ) ) + { return $title; + } } } return null; diff --git a/includes/Utf8Case.php b/includes/Utf8Case.php deleted file mode 100644 index 1d3af41c..00000000 --- a/includes/Utf8Case.php +++ /dev/null @@ -1,1505 +0,0 @@ -<?php -/** - * Simple 1:1 upper/lowercase switching arrays for utf-8 text - * Won't get context-sensitive things yet - * - * Hack for bugs in ucfirst() and company - * - * These are pulled from memcached if possible, as this is faster than filling - * up a big array manually. - * @addtogroup Language - */ - -/* - * Translation array to get upper case character - */ -$wikiUpperChars = array( - "a" => "A", - "b" => "B", - "c" => "C", - "d" => "D", - "e" => "E", - "f" => "F", - "g" => "G", - "h" => "H", - "i" => "I", - "j" => "J", - "k" => "K", - "l" => "L", - "m" => "M", - "n" => "N", - "o" => "O", - "p" => "P", - "q" => "Q", - "r" => "R", - "s" => "S", - "t" => "T", - "u" => "U", - "v" => "V", - "w" => "W", - "x" => "X", - "y" => "Y", - "z" => "Z", - "\xc2\xb5" => "\xce\x9c", - "\xc3\xa0" => "\xc3\x80", - "\xc3\xa1" => "\xc3\x81", - "\xc3\xa2" => "\xc3\x82", - "\xc3\xa3" => "\xc3\x83", - "\xc3\xa4" => "\xc3\x84", - "\xc3\xa5" => "\xc3\x85", - "\xc3\xa6" => "\xc3\x86", - "\xc3\xa7" => "\xc3\x87", - "\xc3\xa8" => "\xc3\x88", - "\xc3\xa9" => "\xc3\x89", - "\xc3\xaa" => "\xc3\x8a", - "\xc3\xab" => "\xc3\x8b", - "\xc3\xac" => "\xc3\x8c", - "\xc3\xad" => "\xc3\x8d", - "\xc3\xae" => "\xc3\x8e", - "\xc3\xaf" => "\xc3\x8f", - "\xc3\xb0" => "\xc3\x90", - "\xc3\xb1" => "\xc3\x91", - "\xc3\xb2" => "\xc3\x92", - "\xc3\xb3" => "\xc3\x93", - "\xc3\xb4" => "\xc3\x94", - "\xc3\xb5" => "\xc3\x95", - "\xc3\xb6" => "\xc3\x96", - "\xc3\xb8" => "\xc3\x98", - "\xc3\xb9" => "\xc3\x99", - "\xc3\xba" => "\xc3\x9a", - "\xc3\xbb" => "\xc3\x9b", - "\xc3\xbc" => "\xc3\x9c", - "\xc3\xbd" => "\xc3\x9d", - "\xc3\xbe" => "\xc3\x9e", - "\xc3\xbf" => "\xc5\xb8", - "\xc4\x81" => "\xc4\x80", - "\xc4\x83" => "\xc4\x82", - "\xc4\x85" => "\xc4\x84", - "\xc4\x87" => "\xc4\x86", - "\xc4\x89" => "\xc4\x88", - "\xc4\x8b" => "\xc4\x8a", - "\xc4\x8d" => "\xc4\x8c", - "\xc4\x8f" => "\xc4\x8e", - "\xc4\x91" => "\xc4\x90", - "\xc4\x93" => "\xc4\x92", - "\xc4\x95" => "\xc4\x94", - "\xc4\x97" => "\xc4\x96", - "\xc4\x99" => "\xc4\x98", - "\xc4\x9b" => "\xc4\x9a", - "\xc4\x9d" => "\xc4\x9c", - "\xc4\x9f" => "\xc4\x9e", - "\xc4\xa1" => "\xc4\xa0", - "\xc4\xa3" => "\xc4\xa2", - "\xc4\xa5" => "\xc4\xa4", - "\xc4\xa7" => "\xc4\xa6", - "\xc4\xa9" => "\xc4\xa8", - "\xc4\xab" => "\xc4\xaa", - "\xc4\xad" => "\xc4\xac", - "\xc4\xaf" => "\xc4\xae", - "\xc4\xb1" => "I", - "\xc4\xb3" => "\xc4\xb2", - "\xc4\xb5" => "\xc4\xb4", - "\xc4\xb7" => "\xc4\xb6", - "\xc4\xba" => "\xc4\xb9", - "\xc4\xbc" => "\xc4\xbb", - "\xc4\xbe" => "\xc4\xbd", - "\xc5\x80" => "\xc4\xbf", - "\xc5\x82" => "\xc5\x81", - "\xc5\x84" => "\xc5\x83", - "\xc5\x86" => "\xc5\x85", - "\xc5\x88" => "\xc5\x87", - "\xc5\x8b" => "\xc5\x8a", - "\xc5\x8d" => "\xc5\x8c", - "\xc5\x8f" => "\xc5\x8e", - "\xc5\x91" => "\xc5\x90", - "\xc5\x93" => "\xc5\x92", - "\xc5\x95" => "\xc5\x94", - "\xc5\x97" => "\xc5\x96", - "\xc5\x99" => "\xc5\x98", - "\xc5\x9b" => "\xc5\x9a", - "\xc5\x9d" => "\xc5\x9c", - "\xc5\x9f" => "\xc5\x9e", - "\xc5\xa1" => "\xc5\xa0", - "\xc5\xa3" => "\xc5\xa2", - "\xc5\xa5" => "\xc5\xa4", - "\xc5\xa7" => "\xc5\xa6", - "\xc5\xa9" => "\xc5\xa8", - "\xc5\xab" => "\xc5\xaa", - "\xc5\xad" => "\xc5\xac", - "\xc5\xaf" => "\xc5\xae", - "\xc5\xb1" => "\xc5\xb0", - "\xc5\xb3" => "\xc5\xb2", - "\xc5\xb5" => "\xc5\xb4", - "\xc5\xb7" => "\xc5\xb6", - "\xc5\xba" => "\xc5\xb9", - "\xc5\xbc" => "\xc5\xbb", - "\xc5\xbe" => "\xc5\xbd", - "\xc5\xbf" => "S", - "\xc6\x83" => "\xc6\x82", - "\xc6\x85" => "\xc6\x84", - "\xc6\x88" => "\xc6\x87", - "\xc6\x8c" => "\xc6\x8b", - "\xc6\x92" => "\xc6\x91", - "\xc6\x95" => "\xc7\xb6", - "\xc6\x99" => "\xc6\x98", - "\xc6\xa1" => "\xc6\xa0", - "\xc6\xa3" => "\xc6\xa2", - "\xc6\xa5" => "\xc6\xa4", - "\xc6\xa8" => "\xc6\xa7", - "\xc6\xad" => "\xc6\xac", - "\xc6\xb0" => "\xc6\xaf", - "\xc6\xb4" => "\xc6\xb3", - "\xc6\xb6" => "\xc6\xb5", - "\xc6\xb9" => "\xc6\xb8", - "\xc6\xbd" => "\xc6\xbc", - "\xc6\xbf" => "\xc7\xb7", - "\xc7\x85" => "\xc7\x84", - "\xc7\x86" => "\xc7\x84", - "\xc7\x88" => "\xc7\x87", - "\xc7\x89" => "\xc7\x87", - "\xc7\x8b" => "\xc7\x8a", - "\xc7\x8c" => "\xc7\x8a", - "\xc7\x8e" => "\xc7\x8d", - "\xc7\x90" => "\xc7\x8f", - "\xc7\x92" => "\xc7\x91", - "\xc7\x94" => "\xc7\x93", - "\xc7\x96" => "\xc7\x95", - "\xc7\x98" => "\xc7\x97", - "\xc7\x9a" => "\xc7\x99", - "\xc7\x9c" => "\xc7\x9b", - "\xc7\x9d" => "\xc6\x8e", - "\xc7\x9f" => "\xc7\x9e", - "\xc7\xa1" => "\xc7\xa0", - "\xc7\xa3" => "\xc7\xa2", - "\xc7\xa5" => "\xc7\xa4", - "\xc7\xa7" => "\xc7\xa6", - "\xc7\xa9" => "\xc7\xa8", - "\xc7\xab" => "\xc7\xaa", - "\xc7\xad" => "\xc7\xac", - "\xc7\xaf" => "\xc7\xae", - "\xc7\xb2" => "\xc7\xb1", - "\xc7\xb3" => "\xc7\xb1", - "\xc7\xb5" => "\xc7\xb4", - "\xc7\xb9" => "\xc7\xb8", - "\xc7\xbb" => "\xc7\xba", - "\xc7\xbd" => "\xc7\xbc", - "\xc7\xbf" => "\xc7\xbe", - "\xc8\x81" => "\xc8\x80", - "\xc8\x83" => "\xc8\x82", - "\xc8\x85" => "\xc8\x84", - "\xc8\x87" => "\xc8\x86", - "\xc8\x89" => "\xc8\x88", - "\xc8\x8b" => "\xc8\x8a", - "\xc8\x8d" => "\xc8\x8c", - "\xc8\x8f" => "\xc8\x8e", - "\xc8\x91" => "\xc8\x90", - "\xc8\x93" => "\xc8\x92", - "\xc8\x95" => "\xc8\x94", - "\xc8\x97" => "\xc8\x96", - "\xc8\x99" => "\xc8\x98", - "\xc8\x9b" => "\xc8\x9a", - "\xc8\x9d" => "\xc8\x9c", - "\xc8\x9f" => "\xc8\x9e", - "\xc8\xa3" => "\xc8\xa2", - "\xc8\xa5" => "\xc8\xa4", - "\xc8\xa7" => "\xc8\xa6", - "\xc8\xa9" => "\xc8\xa8", - "\xc8\xab" => "\xc8\xaa", - "\xc8\xad" => "\xc8\xac", - "\xc8\xaf" => "\xc8\xae", - "\xc8\xb1" => "\xc8\xb0", - "\xc8\xb3" => "\xc8\xb2", - "\xc9\x93" => "\xc6\x81", - "\xc9\x94" => "\xc6\x86", - "\xc9\x96" => "\xc6\x89", - "\xc9\x97" => "\xc6\x8a", - "\xc9\x99" => "\xc6\x8f", - "\xc9\x9b" => "\xc6\x90", - "\xc9\xa0" => "\xc6\x93", - "\xc9\xa3" => "\xc6\x94", - "\xc9\xa8" => "\xc6\x97", - "\xc9\xa9" => "\xc6\x96", - "\xc9\xaf" => "\xc6\x9c", - "\xc9\xb2" => "\xc6\x9d", - "\xc9\xb5" => "\xc6\x9f", - "\xca\x80" => "\xc6\xa6", - "\xca\x83" => "\xc6\xa9", - "\xca\x88" => "\xc6\xae", - "\xca\x8a" => "\xc6\xb1", - "\xca\x8b" => "\xc6\xb2", - "\xca\x92" => "\xc6\xb7", - "\xcd\x85" => "\xce\x99", - "\xce\xac" => "\xce\x86", - "\xce\xad" => "\xce\x88", - "\xce\xae" => "\xce\x89", - "\xce\xaf" => "\xce\x8a", - "\xce\xb1" => "\xce\x91", - "\xce\xb2" => "\xce\x92", - "\xce\xb3" => "\xce\x93", - "\xce\xb4" => "\xce\x94", - "\xce\xb5" => "\xce\x95", - "\xce\xb6" => "\xce\x96", - "\xce\xb7" => "\xce\x97", - "\xce\xb8" => "\xce\x98", - "\xce\xb9" => "\xce\x99", - "\xce\xba" => "\xce\x9a", - "\xce\xbb" => "\xce\x9b", - "\xce\xbc" => "\xce\x9c", - "\xce\xbd" => "\xce\x9d", - "\xce\xbe" => "\xce\x9e", - "\xce\xbf" => "\xce\x9f", - "\xcf\x80" => "\xce\xa0", - "\xcf\x81" => "\xce\xa1", - "\xcf\x82" => "\xce\xa3", - "\xcf\x83" => "\xce\xa3", - "\xcf\x84" => "\xce\xa4", - "\xcf\x85" => "\xce\xa5", - "\xcf\x86" => "\xce\xa6", - "\xcf\x87" => "\xce\xa7", - "\xcf\x88" => "\xce\xa8", - "\xcf\x89" => "\xce\xa9", - "\xcf\x8a" => "\xce\xaa", - "\xcf\x8b" => "\xce\xab", - "\xcf\x8c" => "\xce\x8c", - "\xcf\x8d" => "\xce\x8e", - "\xcf\x8e" => "\xce\x8f", - "\xcf\x90" => "\xce\x92", - "\xcf\x91" => "\xce\x98", - "\xcf\x95" => "\xce\xa6", - "\xcf\x96" => "\xce\xa0", - "\xcf\x9b" => "\xcf\x9a", - "\xcf\x9d" => "\xcf\x9c", - "\xcf\x9f" => "\xcf\x9e", - "\xcf\xa1" => "\xcf\xa0", - "\xcf\xa3" => "\xcf\xa2", - "\xcf\xa5" => "\xcf\xa4", - "\xcf\xa7" => "\xcf\xa6", - "\xcf\xa9" => "\xcf\xa8", - "\xcf\xab" => "\xcf\xaa", - "\xcf\xad" => "\xcf\xac", - "\xcf\xaf" => "\xcf\xae", - "\xcf\xb0" => "\xce\x9a", - "\xcf\xb1" => "\xce\xa1", - "\xcf\xb2" => "\xce\xa3", - "\xcf\xb5" => "\xce\x95", - "\xd0\xb0" => "\xd0\x90", - "\xd0\xb1" => "\xd0\x91", - "\xd0\xb2" => "\xd0\x92", - "\xd0\xb3" => "\xd0\x93", - "\xd0\xb4" => "\xd0\x94", - "\xd0\xb5" => "\xd0\x95", - "\xd0\xb6" => "\xd0\x96", - "\xd0\xb7" => "\xd0\x97", - "\xd0\xb8" => "\xd0\x98", - "\xd0\xb9" => "\xd0\x99", - "\xd0\xba" => "\xd0\x9a", - "\xd0\xbb" => "\xd0\x9b", - "\xd0\xbc" => "\xd0\x9c", - "\xd0\xbd" => "\xd0\x9d", - "\xd0\xbe" => "\xd0\x9e", - "\xd0\xbf" => "\xd0\x9f", - "\xd1\x80" => "\xd0\xa0", - "\xd1\x81" => "\xd0\xa1", - "\xd1\x82" => "\xd0\xa2", - "\xd1\x83" => "\xd0\xa3", - "\xd1\x84" => "\xd0\xa4", - "\xd1\x85" => "\xd0\xa5", - "\xd1\x86" => "\xd0\xa6", - "\xd1\x87" => "\xd0\xa7", - "\xd1\x88" => "\xd0\xa8", - "\xd1\x89" => "\xd0\xa9", - "\xd1\x8a" => "\xd0\xaa", - "\xd1\x8b" => "\xd0\xab", - "\xd1\x8c" => "\xd0\xac", - "\xd1\x8d" => "\xd0\xad", - "\xd1\x8e" => "\xd0\xae", - "\xd1\x8f" => "\xd0\xaf", - "\xd1\x90" => "\xd0\x80", - "\xd1\x91" => "\xd0\x81", - "\xd1\x92" => "\xd0\x82", - "\xd1\x93" => "\xd0\x83", - "\xd1\x94" => "\xd0\x84", - "\xd1\x95" => "\xd0\x85", - "\xd1\x96" => "\xd0\x86", - "\xd1\x97" => "\xd0\x87", - "\xd1\x98" => "\xd0\x88", - "\xd1\x99" => "\xd0\x89", - "\xd1\x9a" => "\xd0\x8a", - "\xd1\x9b" => "\xd0\x8b", - "\xd1\x9c" => "\xd0\x8c", - "\xd1\x9d" => "\xd0\x8d", - "\xd1\x9e" => "\xd0\x8e", - "\xd1\x9f" => "\xd0\x8f", - "\xd1\xa1" => "\xd1\xa0", - "\xd1\xa3" => "\xd1\xa2", - "\xd1\xa5" => "\xd1\xa4", - "\xd1\xa7" => "\xd1\xa6", - "\xd1\xa9" => "\xd1\xa8", - "\xd1\xab" => "\xd1\xaa", - "\xd1\xad" => "\xd1\xac", - "\xd1\xaf" => "\xd1\xae", - "\xd1\xb1" => "\xd1\xb0", - "\xd1\xb3" => "\xd1\xb2", - "\xd1\xb5" => "\xd1\xb4", - "\xd1\xb7" => "\xd1\xb6", - "\xd1\xb9" => "\xd1\xb8", - "\xd1\xbb" => "\xd1\xba", - "\xd1\xbd" => "\xd1\xbc", - "\xd1\xbf" => "\xd1\xbe", - "\xd2\x81" => "\xd2\x80", - "\xd2\x8d" => "\xd2\x8c", - "\xd2\x8f" => "\xd2\x8e", - "\xd2\x91" => "\xd2\x90", - "\xd2\x93" => "\xd2\x92", - "\xd2\x95" => "\xd2\x94", - "\xd2\x97" => "\xd2\x96", - "\xd2\x99" => "\xd2\x98", - "\xd2\x9b" => "\xd2\x9a", - "\xd2\x9d" => "\xd2\x9c", - "\xd2\x9f" => "\xd2\x9e", - "\xd2\xa1" => "\xd2\xa0", - "\xd2\xa3" => "\xd2\xa2", - "\xd2\xa5" => "\xd2\xa4", - "\xd2\xa7" => "\xd2\xa6", - "\xd2\xa9" => "\xd2\xa8", - "\xd2\xab" => "\xd2\xaa", - "\xd2\xad" => "\xd2\xac", - "\xd2\xaf" => "\xd2\xae", - "\xd2\xb1" => "\xd2\xb0", - "\xd2\xb3" => "\xd2\xb2", - "\xd2\xb5" => "\xd2\xb4", - "\xd2\xb7" => "\xd2\xb6", - "\xd2\xb9" => "\xd2\xb8", - "\xd2\xbb" => "\xd2\xba", - "\xd2\xbd" => "\xd2\xbc", - "\xd2\xbf" => "\xd2\xbe", - "\xd3\x82" => "\xd3\x81", - "\xd3\x84" => "\xd3\x83", - "\xd3\x88" => "\xd3\x87", - "\xd3\x8c" => "\xd3\x8b", - "\xd3\x91" => "\xd3\x90", - "\xd3\x93" => "\xd3\x92", - "\xd3\x95" => "\xd3\x94", - "\xd3\x97" => "\xd3\x96", - "\xd3\x99" => "\xd3\x98", - "\xd3\x9b" => "\xd3\x9a", - "\xd3\x9d" => "\xd3\x9c", - "\xd3\x9f" => "\xd3\x9e", - "\xd3\xa1" => "\xd3\xa0", - "\xd3\xa3" => "\xd3\xa2", - "\xd3\xa5" => "\xd3\xa4", - "\xd3\xa7" => "\xd3\xa6", - "\xd3\xa9" => "\xd3\xa8", - "\xd3\xab" => "\xd3\xaa", - "\xd3\xad" => "\xd3\xac", - "\xd3\xaf" => "\xd3\xae", - "\xd3\xb1" => "\xd3\xb0", - "\xd3\xb3" => "\xd3\xb2", - "\xd3\xb5" => "\xd3\xb4", - "\xd3\xb9" => "\xd3\xb8", - "\xd5\xa1" => "\xd4\xb1", - "\xd5\xa2" => "\xd4\xb2", - "\xd5\xa3" => "\xd4\xb3", - "\xd5\xa4" => "\xd4\xb4", - "\xd5\xa5" => "\xd4\xb5", - "\xd5\xa6" => "\xd4\xb6", - "\xd5\xa7" => "\xd4\xb7", - "\xd5\xa8" => "\xd4\xb8", - "\xd5\xa9" => "\xd4\xb9", - "\xd5\xaa" => "\xd4\xba", - "\xd5\xab" => "\xd4\xbb", - "\xd5\xac" => "\xd4\xbc", - "\xd5\xad" => "\xd4\xbd", - "\xd5\xae" => "\xd4\xbe", - "\xd5\xaf" => "\xd4\xbf", - "\xd5\xb0" => "\xd5\x80", - "\xd5\xb1" => "\xd5\x81", - "\xd5\xb2" => "\xd5\x82", - "\xd5\xb3" => "\xd5\x83", - "\xd5\xb4" => "\xd5\x84", - "\xd5\xb5" => "\xd5\x85", - "\xd5\xb6" => "\xd5\x86", - "\xd5\xb7" => "\xd5\x87", - "\xd5\xb8" => "\xd5\x88", - "\xd5\xb9" => "\xd5\x89", - "\xd5\xba" => "\xd5\x8a", - "\xd5\xbb" => "\xd5\x8b", - "\xd5\xbc" => "\xd5\x8c", - "\xd5\xbd" => "\xd5\x8d", - "\xd5\xbe" => "\xd5\x8e", - "\xd5\xbf" => "\xd5\x8f", - "\xd6\x80" => "\xd5\x90", - "\xd6\x81" => "\xd5\x91", - "\xd6\x82" => "\xd5\x92", - "\xd6\x83" => "\xd5\x93", - "\xd6\x84" => "\xd5\x94", - "\xd6\x85" => "\xd5\x95", - "\xd6\x86" => "\xd5\x96", - "\xe1\xb8\x81" => "\xe1\xb8\x80", - "\xe1\xb8\x83" => "\xe1\xb8\x82", - "\xe1\xb8\x85" => "\xe1\xb8\x84", - "\xe1\xb8\x87" => "\xe1\xb8\x86", - "\xe1\xb8\x89" => "\xe1\xb8\x88", - "\xe1\xb8\x8b" => "\xe1\xb8\x8a", - "\xe1\xb8\x8d" => "\xe1\xb8\x8c", - "\xe1\xb8\x8f" => "\xe1\xb8\x8e", - "\xe1\xb8\x91" => "\xe1\xb8\x90", - "\xe1\xb8\x93" => "\xe1\xb8\x92", - "\xe1\xb8\x95" => "\xe1\xb8\x94", - "\xe1\xb8\x97" => "\xe1\xb8\x96", - "\xe1\xb8\x99" => "\xe1\xb8\x98", - "\xe1\xb8\x9b" => "\xe1\xb8\x9a", - "\xe1\xb8\x9d" => "\xe1\xb8\x9c", - "\xe1\xb8\x9f" => "\xe1\xb8\x9e", - "\xe1\xb8\xa1" => "\xe1\xb8\xa0", - "\xe1\xb8\xa3" => "\xe1\xb8\xa2", - "\xe1\xb8\xa5" => "\xe1\xb8\xa4", - "\xe1\xb8\xa7" => "\xe1\xb8\xa6", - "\xe1\xb8\xa9" => "\xe1\xb8\xa8", - "\xe1\xb8\xab" => "\xe1\xb8\xaa", - "\xe1\xb8\xad" => "\xe1\xb8\xac", - "\xe1\xb8\xaf" => "\xe1\xb8\xae", - "\xe1\xb8\xb1" => "\xe1\xb8\xb0", - "\xe1\xb8\xb3" => "\xe1\xb8\xb2", - "\xe1\xb8\xb5" => "\xe1\xb8\xb4", - "\xe1\xb8\xb7" => "\xe1\xb8\xb6", - "\xe1\xb8\xb9" => "\xe1\xb8\xb8", - "\xe1\xb8\xbb" => "\xe1\xb8\xba", - "\xe1\xb8\xbd" => "\xe1\xb8\xbc", - "\xe1\xb8\xbf" => "\xe1\xb8\xbe", - "\xe1\xb9\x81" => "\xe1\xb9\x80", - "\xe1\xb9\x83" => "\xe1\xb9\x82", - "\xe1\xb9\x85" => "\xe1\xb9\x84", - "\xe1\xb9\x87" => "\xe1\xb9\x86", - "\xe1\xb9\x89" => "\xe1\xb9\x88", - "\xe1\xb9\x8b" => "\xe1\xb9\x8a", - "\xe1\xb9\x8d" => "\xe1\xb9\x8c", - "\xe1\xb9\x8f" => "\xe1\xb9\x8e", - "\xe1\xb9\x91" => "\xe1\xb9\x90", - "\xe1\xb9\x93" => "\xe1\xb9\x92", - "\xe1\xb9\x95" => "\xe1\xb9\x94", - "\xe1\xb9\x97" => "\xe1\xb9\x96", - "\xe1\xb9\x99" => "\xe1\xb9\x98", - "\xe1\xb9\x9b" => "\xe1\xb9\x9a", - "\xe1\xb9\x9d" => "\xe1\xb9\x9c", - "\xe1\xb9\x9f" => "\xe1\xb9\x9e", - "\xe1\xb9\xa1" => "\xe1\xb9\xa0", - "\xe1\xb9\xa3" => "\xe1\xb9\xa2", - "\xe1\xb9\xa5" => "\xe1\xb9\xa4", - "\xe1\xb9\xa7" => "\xe1\xb9\xa6", - "\xe1\xb9\xa9" => "\xe1\xb9\xa8", - "\xe1\xb9\xab" => "\xe1\xb9\xaa", - "\xe1\xb9\xad" => "\xe1\xb9\xac", - "\xe1\xb9\xaf" => "\xe1\xb9\xae", - "\xe1\xb9\xb1" => "\xe1\xb9\xb0", - "\xe1\xb9\xb3" => "\xe1\xb9\xb2", - "\xe1\xb9\xb5" => "\xe1\xb9\xb4", - "\xe1\xb9\xb7" => "\xe1\xb9\xb6", - "\xe1\xb9\xb9" => "\xe1\xb9\xb8", - "\xe1\xb9\xbb" => "\xe1\xb9\xba", - "\xe1\xb9\xbd" => "\xe1\xb9\xbc", - "\xe1\xb9\xbf" => "\xe1\xb9\xbe", - "\xe1\xba\x81" => "\xe1\xba\x80", - "\xe1\xba\x83" => "\xe1\xba\x82", - "\xe1\xba\x85" => "\xe1\xba\x84", - "\xe1\xba\x87" => "\xe1\xba\x86", - "\xe1\xba\x89" => "\xe1\xba\x88", - "\xe1\xba\x8b" => "\xe1\xba\x8a", - "\xe1\xba\x8d" => "\xe1\xba\x8c", - "\xe1\xba\x8f" => "\xe1\xba\x8e", - "\xe1\xba\x91" => "\xe1\xba\x90", - "\xe1\xba\x93" => "\xe1\xba\x92", - "\xe1\xba\x95" => "\xe1\xba\x94", - "\xe1\xba\x9b" => "\xe1\xb9\xa0", - "\xe1\xba\xa1" => "\xe1\xba\xa0", - "\xe1\xba\xa3" => "\xe1\xba\xa2", - "\xe1\xba\xa5" => "\xe1\xba\xa4", - "\xe1\xba\xa7" => "\xe1\xba\xa6", - "\xe1\xba\xa9" => "\xe1\xba\xa8", - "\xe1\xba\xab" => "\xe1\xba\xaa", - "\xe1\xba\xad" => "\xe1\xba\xac", - "\xe1\xba\xaf" => "\xe1\xba\xae", - "\xe1\xba\xb1" => "\xe1\xba\xb0", - "\xe1\xba\xb3" => "\xe1\xba\xb2", - "\xe1\xba\xb5" => "\xe1\xba\xb4", - "\xe1\xba\xb7" => "\xe1\xba\xb6", - "\xe1\xba\xb9" => "\xe1\xba\xb8", - "\xe1\xba\xbb" => "\xe1\xba\xba", - "\xe1\xba\xbd" => "\xe1\xba\xbc", - "\xe1\xba\xbf" => "\xe1\xba\xbe", - "\xe1\xbb\x81" => "\xe1\xbb\x80", - "\xe1\xbb\x83" => "\xe1\xbb\x82", - "\xe1\xbb\x85" => "\xe1\xbb\x84", - "\xe1\xbb\x87" => "\xe1\xbb\x86", - "\xe1\xbb\x89" => "\xe1\xbb\x88", - "\xe1\xbb\x8b" => "\xe1\xbb\x8a", - "\xe1\xbb\x8d" => "\xe1\xbb\x8c", - "\xe1\xbb\x8f" => "\xe1\xbb\x8e", - "\xe1\xbb\x91" => "\xe1\xbb\x90", - "\xe1\xbb\x93" => "\xe1\xbb\x92", - "\xe1\xbb\x95" => "\xe1\xbb\x94", - "\xe1\xbb\x97" => "\xe1\xbb\x96", - "\xe1\xbb\x99" => "\xe1\xbb\x98", - "\xe1\xbb\x9b" => "\xe1\xbb\x9a", - "\xe1\xbb\x9d" => "\xe1\xbb\x9c", - "\xe1\xbb\x9f" => "\xe1\xbb\x9e", - "\xe1\xbb\xa1" => "\xe1\xbb\xa0", - "\xe1\xbb\xa3" => "\xe1\xbb\xa2", - "\xe1\xbb\xa5" => "\xe1\xbb\xa4", - "\xe1\xbb\xa7" => "\xe1\xbb\xa6", - "\xe1\xbb\xa9" => "\xe1\xbb\xa8", - "\xe1\xbb\xab" => "\xe1\xbb\xaa", - "\xe1\xbb\xad" => "\xe1\xbb\xac", - "\xe1\xbb\xaf" => "\xe1\xbb\xae", - "\xe1\xbb\xb1" => "\xe1\xbb\xb0", - "\xe1\xbb\xb3" => "\xe1\xbb\xb2", - "\xe1\xbb\xb5" => "\xe1\xbb\xb4", - "\xe1\xbb\xb7" => "\xe1\xbb\xb6", - "\xe1\xbb\xb9" => "\xe1\xbb\xb8", - "\xe1\xbc\x80" => "\xe1\xbc\x88", - "\xe1\xbc\x81" => "\xe1\xbc\x89", - "\xe1\xbc\x82" => "\xe1\xbc\x8a", - "\xe1\xbc\x83" => "\xe1\xbc\x8b", - "\xe1\xbc\x84" => "\xe1\xbc\x8c", - "\xe1\xbc\x85" => "\xe1\xbc\x8d", - "\xe1\xbc\x86" => "\xe1\xbc\x8e", - "\xe1\xbc\x87" => "\xe1\xbc\x8f", - "\xe1\xbc\x90" => "\xe1\xbc\x98", - "\xe1\xbc\x91" => "\xe1\xbc\x99", - "\xe1\xbc\x92" => "\xe1\xbc\x9a", - "\xe1\xbc\x93" => "\xe1\xbc\x9b", - "\xe1\xbc\x94" => "\xe1\xbc\x9c", - "\xe1\xbc\x95" => "\xe1\xbc\x9d", - "\xe1\xbc\xa0" => "\xe1\xbc\xa8", - "\xe1\xbc\xa1" => "\xe1\xbc\xa9", - "\xe1\xbc\xa2" => "\xe1\xbc\xaa", - "\xe1\xbc\xa3" => "\xe1\xbc\xab", - "\xe1\xbc\xa4" => "\xe1\xbc\xac", - "\xe1\xbc\xa5" => "\xe1\xbc\xad", - "\xe1\xbc\xa6" => "\xe1\xbc\xae", - "\xe1\xbc\xa7" => "\xe1\xbc\xaf", - "\xe1\xbc\xb0" => "\xe1\xbc\xb8", - "\xe1\xbc\xb1" => "\xe1\xbc\xb9", - "\xe1\xbc\xb2" => "\xe1\xbc\xba", - "\xe1\xbc\xb3" => "\xe1\xbc\xbb", - "\xe1\xbc\xb4" => "\xe1\xbc\xbc", - "\xe1\xbc\xb5" => "\xe1\xbc\xbd", - "\xe1\xbc\xb6" => "\xe1\xbc\xbe", - "\xe1\xbc\xb7" => "\xe1\xbc\xbf", - "\xe1\xbd\x80" => "\xe1\xbd\x88", - "\xe1\xbd\x81" => "\xe1\xbd\x89", - "\xe1\xbd\x82" => "\xe1\xbd\x8a", - "\xe1\xbd\x83" => "\xe1\xbd\x8b", - "\xe1\xbd\x84" => "\xe1\xbd\x8c", - "\xe1\xbd\x85" => "\xe1\xbd\x8d", - "\xe1\xbd\x91" => "\xe1\xbd\x99", - "\xe1\xbd\x93" => "\xe1\xbd\x9b", - "\xe1\xbd\x95" => "\xe1\xbd\x9d", - "\xe1\xbd\x97" => "\xe1\xbd\x9f", - "\xe1\xbd\xa0" => "\xe1\xbd\xa8", - "\xe1\xbd\xa1" => "\xe1\xbd\xa9", - "\xe1\xbd\xa2" => "\xe1\xbd\xaa", - "\xe1\xbd\xa3" => "\xe1\xbd\xab", - "\xe1\xbd\xa4" => "\xe1\xbd\xac", - "\xe1\xbd\xa5" => "\xe1\xbd\xad", - "\xe1\xbd\xa6" => "\xe1\xbd\xae", - "\xe1\xbd\xa7" => "\xe1\xbd\xaf", - "\xe1\xbd\xb0" => "\xe1\xbe\xba", - "\xe1\xbd\xb1" => "\xe1\xbe\xbb", - "\xe1\xbd\xb2" => "\xe1\xbf\x88", - "\xe1\xbd\xb3" => "\xe1\xbf\x89", - "\xe1\xbd\xb4" => "\xe1\xbf\x8a", - "\xe1\xbd\xb5" => "\xe1\xbf\x8b", - "\xe1\xbd\xb6" => "\xe1\xbf\x9a", - "\xe1\xbd\xb7" => "\xe1\xbf\x9b", - "\xe1\xbd\xb8" => "\xe1\xbf\xb8", - "\xe1\xbd\xb9" => "\xe1\xbf\xb9", - "\xe1\xbd\xba" => "\xe1\xbf\xaa", - "\xe1\xbd\xbb" => "\xe1\xbf\xab", - "\xe1\xbd\xbc" => "\xe1\xbf\xba", - "\xe1\xbd\xbd" => "\xe1\xbf\xbb", - "\xe1\xbe\x80" => "\xe1\xbe\x88", - "\xe1\xbe\x81" => "\xe1\xbe\x89", - "\xe1\xbe\x82" => "\xe1\xbe\x8a", - "\xe1\xbe\x83" => "\xe1\xbe\x8b", - "\xe1\xbe\x84" => "\xe1\xbe\x8c", - "\xe1\xbe\x85" => "\xe1\xbe\x8d", - "\xe1\xbe\x86" => "\xe1\xbe\x8e", - "\xe1\xbe\x87" => "\xe1\xbe\x8f", - "\xe1\xbe\x90" => "\xe1\xbe\x98", - "\xe1\xbe\x91" => "\xe1\xbe\x99", - "\xe1\xbe\x92" => "\xe1\xbe\x9a", - "\xe1\xbe\x93" => "\xe1\xbe\x9b", - "\xe1\xbe\x94" => "\xe1\xbe\x9c", - "\xe1\xbe\x95" => "\xe1\xbe\x9d", - "\xe1\xbe\x96" => "\xe1\xbe\x9e", - "\xe1\xbe\x97" => "\xe1\xbe\x9f", - "\xe1\xbe\xa0" => "\xe1\xbe\xa8", - "\xe1\xbe\xa1" => "\xe1\xbe\xa9", - "\xe1\xbe\xa2" => "\xe1\xbe\xaa", - "\xe1\xbe\xa3" => "\xe1\xbe\xab", - "\xe1\xbe\xa4" => "\xe1\xbe\xac", - "\xe1\xbe\xa5" => "\xe1\xbe\xad", - "\xe1\xbe\xa6" => "\xe1\xbe\xae", - "\xe1\xbe\xa7" => "\xe1\xbe\xaf", - "\xe1\xbe\xb0" => "\xe1\xbe\xb8", - "\xe1\xbe\xb1" => "\xe1\xbe\xb9", - "\xe1\xbe\xb3" => "\xe1\xbe\xbc", - "\xe1\xbe\xbe" => "\xce\x99", - "\xe1\xbf\x83" => "\xe1\xbf\x8c", - "\xe1\xbf\x90" => "\xe1\xbf\x98", - "\xe1\xbf\x91" => "\xe1\xbf\x99", - "\xe1\xbf\xa0" => "\xe1\xbf\xa8", - "\xe1\xbf\xa1" => "\xe1\xbf\xa9", - "\xe1\xbf\xa5" => "\xe1\xbf\xac", - "\xe1\xbf\xb3" => "\xe1\xbf\xbc", - "\xe2\x85\xb0" => "\xe2\x85\xa0", - "\xe2\x85\xb1" => "\xe2\x85\xa1", - "\xe2\x85\xb2" => "\xe2\x85\xa2", - "\xe2\x85\xb3" => "\xe2\x85\xa3", - "\xe2\x85\xb4" => "\xe2\x85\xa4", - "\xe2\x85\xb5" => "\xe2\x85\xa5", - "\xe2\x85\xb6" => "\xe2\x85\xa6", - "\xe2\x85\xb7" => "\xe2\x85\xa7", - "\xe2\x85\xb8" => "\xe2\x85\xa8", - "\xe2\x85\xb9" => "\xe2\x85\xa9", - "\xe2\x85\xba" => "\xe2\x85\xaa", - "\xe2\x85\xbb" => "\xe2\x85\xab", - "\xe2\x85\xbc" => "\xe2\x85\xac", - "\xe2\x85\xbd" => "\xe2\x85\xad", - "\xe2\x85\xbe" => "\xe2\x85\xae", - "\xe2\x85\xbf" => "\xe2\x85\xaf", - "\xe2\x93\x90" => "\xe2\x92\xb6", - "\xe2\x93\x91" => "\xe2\x92\xb7", - "\xe2\x93\x92" => "\xe2\x92\xb8", - "\xe2\x93\x93" => "\xe2\x92\xb9", - "\xe2\x93\x94" => "\xe2\x92\xba", - "\xe2\x93\x95" => "\xe2\x92\xbb", - "\xe2\x93\x96" => "\xe2\x92\xbc", - "\xe2\x93\x97" => "\xe2\x92\xbd", - "\xe2\x93\x98" => "\xe2\x92\xbe", - "\xe2\x93\x99" => "\xe2\x92\xbf", - "\xe2\x93\x9a" => "\xe2\x93\x80", - "\xe2\x93\x9b" => "\xe2\x93\x81", - "\xe2\x93\x9c" => "\xe2\x93\x82", - "\xe2\x93\x9d" => "\xe2\x93\x83", - "\xe2\x93\x9e" => "\xe2\x93\x84", - "\xe2\x93\x9f" => "\xe2\x93\x85", - "\xe2\x93\xa0" => "\xe2\x93\x86", - "\xe2\x93\xa1" => "\xe2\x93\x87", - "\xe2\x93\xa2" => "\xe2\x93\x88", - "\xe2\x93\xa3" => "\xe2\x93\x89", - "\xe2\x93\xa4" => "\xe2\x93\x8a", - "\xe2\x93\xa5" => "\xe2\x93\x8b", - "\xe2\x93\xa6" => "\xe2\x93\x8c", - "\xe2\x93\xa7" => "\xe2\x93\x8d", - "\xe2\x93\xa8" => "\xe2\x93\x8e", - "\xe2\x93\xa9" => "\xe2\x93\x8f", - "\xef\xbd\x81" => "\xef\xbc\xa1", - "\xef\xbd\x82" => "\xef\xbc\xa2", - "\xef\xbd\x83" => "\xef\xbc\xa3", - "\xef\xbd\x84" => "\xef\xbc\xa4", - "\xef\xbd\x85" => "\xef\xbc\xa5", - "\xef\xbd\x86" => "\xef\xbc\xa6", - "\xef\xbd\x87" => "\xef\xbc\xa7", - "\xef\xbd\x88" => "\xef\xbc\xa8", - "\xef\xbd\x89" => "\xef\xbc\xa9", - "\xef\xbd\x8a" => "\xef\xbc\xaa", - "\xef\xbd\x8b" => "\xef\xbc\xab", - "\xef\xbd\x8c" => "\xef\xbc\xac", - "\xef\xbd\x8d" => "\xef\xbc\xad", - "\xef\xbd\x8e" => "\xef\xbc\xae", - "\xef\xbd\x8f" => "\xef\xbc\xaf", - "\xef\xbd\x90" => "\xef\xbc\xb0", - "\xef\xbd\x91" => "\xef\xbc\xb1", - "\xef\xbd\x92" => "\xef\xbc\xb2", - "\xef\xbd\x93" => "\xef\xbc\xb3", - "\xef\xbd\x94" => "\xef\xbc\xb4", - "\xef\xbd\x95" => "\xef\xbc\xb5", - "\xef\xbd\x96" => "\xef\xbc\xb6", - "\xef\xbd\x97" => "\xef\xbc\xb7", - "\xef\xbd\x98" => "\xef\xbc\xb8", - "\xef\xbd\x99" => "\xef\xbc\xb9", - "\xef\xbd\x9a" => "\xef\xbc\xba", - "\xf0\x90\x90\xa8" => "\xf0\x90\x90\x80", - "\xf0\x90\x90\xa9" => "\xf0\x90\x90\x81", - "\xf0\x90\x90\xaa" => "\xf0\x90\x90\x82", - "\xf0\x90\x90\xab" => "\xf0\x90\x90\x83", - "\xf0\x90\x90\xac" => "\xf0\x90\x90\x84", - "\xf0\x90\x90\xad" => "\xf0\x90\x90\x85", - "\xf0\x90\x90\xae" => "\xf0\x90\x90\x86", - "\xf0\x90\x90\xaf" => "\xf0\x90\x90\x87", - "\xf0\x90\x90\xb0" => "\xf0\x90\x90\x88", - "\xf0\x90\x90\xb1" => "\xf0\x90\x90\x89", - "\xf0\x90\x90\xb2" => "\xf0\x90\x90\x8a", - "\xf0\x90\x90\xb3" => "\xf0\x90\x90\x8b", - "\xf0\x90\x90\xb4" => "\xf0\x90\x90\x8c", - "\xf0\x90\x90\xb5" => "\xf0\x90\x90\x8d", - "\xf0\x90\x90\xb6" => "\xf0\x90\x90\x8e", - "\xf0\x90\x90\xb7" => "\xf0\x90\x90\x8f", - "\xf0\x90\x90\xb8" => "\xf0\x90\x90\x90", - "\xf0\x90\x90\xb9" => "\xf0\x90\x90\x91", - "\xf0\x90\x90\xba" => "\xf0\x90\x90\x92", - "\xf0\x90\x90\xbb" => "\xf0\x90\x90\x93", - "\xf0\x90\x90\xbc" => "\xf0\x90\x90\x94", - "\xf0\x90\x90\xbd" => "\xf0\x90\x90\x95", - "\xf0\x90\x90\xbe" => "\xf0\x90\x90\x96", - "\xf0\x90\x90\xbf" => "\xf0\x90\x90\x97", - "\xf0\x90\x91\x80" => "\xf0\x90\x90\x98", - "\xf0\x90\x91\x81" => "\xf0\x90\x90\x99", - "\xf0\x90\x91\x82" => "\xf0\x90\x90\x9a", - "\xf0\x90\x91\x83" => "\xf0\x90\x90\x9b", - "\xf0\x90\x91\x84" => "\xf0\x90\x90\x9c", - "\xf0\x90\x91\x85" => "\xf0\x90\x90\x9d", - "\xf0\x90\x91\x86" => "\xf0\x90\x90\x9e", - "\xf0\x90\x91\x87" => "\xf0\x90\x90\x9f", - "\xf0\x90\x91\x88" => "\xf0\x90\x90\xa0", - "\xf0\x90\x91\x89" => "\xf0\x90\x90\xa1", - "\xf0\x90\x91\x8a" => "\xf0\x90\x90\xa2", - "\xf0\x90\x91\x8b" => "\xf0\x90\x90\xa3", - "\xf0\x90\x91\x8c" => "\xf0\x90\x90\xa4", - "\xf0\x90\x91\x8d" => "\xf0\x90\x90\xa5" -); - -/* - * Translation array to get lower case character - */ -$wikiLowerChars = array ( - "A" => "a", - "B" => "b", - "C" => "c", - "D" => "d", - "E" => "e", - "F" => "f", - "G" => "g", - "H" => "h", - "I" => "i", - "J" => "j", - "K" => "k", - "L" => "l", - "M" => "m", - "N" => "n", - "O" => "o", - "P" => "p", - "Q" => "q", - "R" => "r", - "S" => "s", - "T" => "t", - "U" => "u", - "V" => "v", - "W" => "w", - "X" => "x", - "Y" => "y", - "Z" => "z", - "\xc3\x80" => "\xc3\xa0", - "\xc3\x81" => "\xc3\xa1", - "\xc3\x82" => "\xc3\xa2", - "\xc3\x83" => "\xc3\xa3", - "\xc3\x84" => "\xc3\xa4", - "\xc3\x85" => "\xc3\xa5", - "\xc3\x86" => "\xc3\xa6", - "\xc3\x87" => "\xc3\xa7", - "\xc3\x88" => "\xc3\xa8", - "\xc3\x89" => "\xc3\xa9", - "\xc3\x8a" => "\xc3\xaa", - "\xc3\x8b" => "\xc3\xab", - "\xc3\x8c" => "\xc3\xac", - "\xc3\x8d" => "\xc3\xad", - "\xc3\x8e" => "\xc3\xae", - "\xc3\x8f" => "\xc3\xaf", - "\xc3\x90" => "\xc3\xb0", - "\xc3\x91" => "\xc3\xb1", - "\xc3\x92" => "\xc3\xb2", - "\xc3\x93" => "\xc3\xb3", - "\xc3\x94" => "\xc3\xb4", - "\xc3\x95" => "\xc3\xb5", - "\xc3\x96" => "\xc3\xb6", - "\xc3\x98" => "\xc3\xb8", - "\xc3\x99" => "\xc3\xb9", - "\xc3\x9a" => "\xc3\xba", - "\xc3\x9b" => "\xc3\xbb", - "\xc3\x9c" => "\xc3\xbc", - "\xc3\x9d" => "\xc3\xbd", - "\xc3\x9e" => "\xc3\xbe", - "\xc4\x80" => "\xc4\x81", - "\xc4\x82" => "\xc4\x83", - "\xc4\x84" => "\xc4\x85", - "\xc4\x86" => "\xc4\x87", - "\xc4\x88" => "\xc4\x89", - "\xc4\x8a" => "\xc4\x8b", - "\xc4\x8c" => "\xc4\x8d", - "\xc4\x8e" => "\xc4\x8f", - "\xc4\x90" => "\xc4\x91", - "\xc4\x92" => "\xc4\x93", - "\xc4\x94" => "\xc4\x95", - "\xc4\x96" => "\xc4\x97", - "\xc4\x98" => "\xc4\x99", - "\xc4\x9a" => "\xc4\x9b", - "\xc4\x9c" => "\xc4\x9d", - "\xc4\x9e" => "\xc4\x9f", - "\xc4\xa0" => "\xc4\xa1", - "\xc4\xa2" => "\xc4\xa3", - "\xc4\xa4" => "\xc4\xa5", - "\xc4\xa6" => "\xc4\xa7", - "\xc4\xa8" => "\xc4\xa9", - "\xc4\xaa" => "\xc4\xab", - "\xc4\xac" => "\xc4\xad", - "\xc4\xae" => "\xc4\xaf", - "\xc4\xb0" => "i", - "\xc4\xb2" => "\xc4\xb3", - "\xc4\xb4" => "\xc4\xb5", - "\xc4\xb6" => "\xc4\xb7", - "\xc4\xb9" => "\xc4\xba", - "\xc4\xbb" => "\xc4\xbc", - "\xc4\xbd" => "\xc4\xbe", - "\xc4\xbf" => "\xc5\x80", - "\xc5\x81" => "\xc5\x82", - "\xc5\x83" => "\xc5\x84", - "\xc5\x85" => "\xc5\x86", - "\xc5\x87" => "\xc5\x88", - "\xc5\x8a" => "\xc5\x8b", - "\xc5\x8c" => "\xc5\x8d", - "\xc5\x8e" => "\xc5\x8f", - "\xc5\x90" => "\xc5\x91", - "\xc5\x92" => "\xc5\x93", - "\xc5\x94" => "\xc5\x95", - "\xc5\x96" => "\xc5\x97", - "\xc5\x98" => "\xc5\x99", - "\xc5\x9a" => "\xc5\x9b", - "\xc5\x9c" => "\xc5\x9d", - "\xc5\x9e" => "\xc5\x9f", - "\xc5\xa0" => "\xc5\xa1", - "\xc5\xa2" => "\xc5\xa3", - "\xc5\xa4" => "\xc5\xa5", - "\xc5\xa6" => "\xc5\xa7", - "\xc5\xa8" => "\xc5\xa9", - "\xc5\xaa" => "\xc5\xab", - "\xc5\xac" => "\xc5\xad", - "\xc5\xae" => "\xc5\xaf", - "\xc5\xb0" => "\xc5\xb1", - "\xc5\xb2" => "\xc5\xb3", - "\xc5\xb4" => "\xc5\xb5", - "\xc5\xb6" => "\xc5\xb7", - "\xc5\xb8" => "\xc3\xbf", - "\xc5\xb9" => "\xc5\xba", - "\xc5\xbb" => "\xc5\xbc", - "\xc5\xbd" => "\xc5\xbe", - "\xc6\x81" => "\xc9\x93", - "\xc6\x82" => "\xc6\x83", - "\xc6\x84" => "\xc6\x85", - "\xc6\x86" => "\xc9\x94", - "\xc6\x87" => "\xc6\x88", - "\xc6\x89" => "\xc9\x96", - "\xc6\x8a" => "\xc9\x97", - "\xc6\x8b" => "\xc6\x8c", - "\xc6\x8e" => "\xc7\x9d", - "\xc6\x8f" => "\xc9\x99", - "\xc6\x90" => "\xc9\x9b", - "\xc6\x91" => "\xc6\x92", - "\xc6\x93" => "\xc9\xa0", - "\xc6\x94" => "\xc9\xa3", - "\xc6\x96" => "\xc9\xa9", - "\xc6\x97" => "\xc9\xa8", - "\xc6\x98" => "\xc6\x99", - "\xc6\x9c" => "\xc9\xaf", - "\xc6\x9d" => "\xc9\xb2", - "\xc6\x9f" => "\xc9\xb5", - "\xc6\xa0" => "\xc6\xa1", - "\xc6\xa2" => "\xc6\xa3", - "\xc6\xa4" => "\xc6\xa5", - "\xc6\xa6" => "\xca\x80", - "\xc6\xa7" => "\xc6\xa8", - "\xc6\xa9" => "\xca\x83", - "\xc6\xac" => "\xc6\xad", - "\xc6\xae" => "\xca\x88", - "\xc6\xaf" => "\xc6\xb0", - "\xc6\xb1" => "\xca\x8a", - "\xc6\xb2" => "\xca\x8b", - "\xc6\xb3" => "\xc6\xb4", - "\xc6\xb5" => "\xc6\xb6", - "\xc6\xb7" => "\xca\x92", - "\xc6\xb8" => "\xc6\xb9", - "\xc6\xbc" => "\xc6\xbd", - "\xc7\x84" => "\xc7\x86", - "\xc7\x85" => "\xc7\x86", - "\xc7\x87" => "\xc7\x89", - "\xc7\x88" => "\xc7\x89", - "\xc7\x8a" => "\xc7\x8c", - "\xc7\x8b" => "\xc7\x8c", - "\xc7\x8d" => "\xc7\x8e", - "\xc7\x8f" => "\xc7\x90", - "\xc7\x91" => "\xc7\x92", - "\xc7\x93" => "\xc7\x94", - "\xc7\x95" => "\xc7\x96", - "\xc7\x97" => "\xc7\x98", - "\xc7\x99" => "\xc7\x9a", - "\xc7\x9b" => "\xc7\x9c", - "\xc7\x9e" => "\xc7\x9f", - "\xc7\xa0" => "\xc7\xa1", - "\xc7\xa2" => "\xc7\xa3", - "\xc7\xa4" => "\xc7\xa5", - "\xc7\xa6" => "\xc7\xa7", - "\xc7\xa8" => "\xc7\xa9", - "\xc7\xaa" => "\xc7\xab", - "\xc7\xac" => "\xc7\xad", - "\xc7\xae" => "\xc7\xaf", - "\xc7\xb1" => "\xc7\xb3", - "\xc7\xb2" => "\xc7\xb3", - "\xc7\xb4" => "\xc7\xb5", - "\xc7\xb6" => "\xc6\x95", - "\xc7\xb7" => "\xc6\xbf", - "\xc7\xb8" => "\xc7\xb9", - "\xc7\xba" => "\xc7\xbb", - "\xc7\xbc" => "\xc7\xbd", - "\xc7\xbe" => "\xc7\xbf", - "\xc8\x80" => "\xc8\x81", - "\xc8\x82" => "\xc8\x83", - "\xc8\x84" => "\xc8\x85", - "\xc8\x86" => "\xc8\x87", - "\xc8\x88" => "\xc8\x89", - "\xc8\x8a" => "\xc8\x8b", - "\xc8\x8c" => "\xc8\x8d", - "\xc8\x8e" => "\xc8\x8f", - "\xc8\x90" => "\xc8\x91", - "\xc8\x92" => "\xc8\x93", - "\xc8\x94" => "\xc8\x95", - "\xc8\x96" => "\xc8\x97", - "\xc8\x98" => "\xc8\x99", - "\xc8\x9a" => "\xc8\x9b", - "\xc8\x9c" => "\xc8\x9d", - "\xc8\x9e" => "\xc8\x9f", - "\xc8\xa2" => "\xc8\xa3", - "\xc8\xa4" => "\xc8\xa5", - "\xc8\xa6" => "\xc8\xa7", - "\xc8\xa8" => "\xc8\xa9", - "\xc8\xaa" => "\xc8\xab", - "\xc8\xac" => "\xc8\xad", - "\xc8\xae" => "\xc8\xaf", - "\xc8\xb0" => "\xc8\xb1", - "\xc8\xb2" => "\xc8\xb3", - "\xce\x86" => "\xce\xac", - "\xce\x88" => "\xce\xad", - "\xce\x89" => "\xce\xae", - "\xce\x8a" => "\xce\xaf", - "\xce\x8c" => "\xcf\x8c", - "\xce\x8e" => "\xcf\x8d", - "\xce\x8f" => "\xcf\x8e", - "\xce\x91" => "\xce\xb1", - "\xce\x92" => "\xce\xb2", - "\xce\x93" => "\xce\xb3", - "\xce\x94" => "\xce\xb4", - "\xce\x95" => "\xce\xb5", - "\xce\x96" => "\xce\xb6", - "\xce\x97" => "\xce\xb7", - "\xce\x98" => "\xce\xb8", - "\xce\x99" => "\xce\xb9", - "\xce\x9a" => "\xce\xba", - "\xce\x9b" => "\xce\xbb", - "\xce\x9c" => "\xce\xbc", - "\xce\x9d" => "\xce\xbd", - "\xce\x9e" => "\xce\xbe", - "\xce\x9f" => "\xce\xbf", - "\xce\xa0" => "\xcf\x80", - "\xce\xa1" => "\xcf\x81", - "\xce\xa3" => "\xcf\x83", - "\xce\xa4" => "\xcf\x84", - "\xce\xa5" => "\xcf\x85", - "\xce\xa6" => "\xcf\x86", - "\xce\xa7" => "\xcf\x87", - "\xce\xa8" => "\xcf\x88", - "\xce\xa9" => "\xcf\x89", - "\xce\xaa" => "\xcf\x8a", - "\xce\xab" => "\xcf\x8b", - "\xcf\x9a" => "\xcf\x9b", - "\xcf\x9c" => "\xcf\x9d", - "\xcf\x9e" => "\xcf\x9f", - "\xcf\xa0" => "\xcf\xa1", - "\xcf\xa2" => "\xcf\xa3", - "\xcf\xa4" => "\xcf\xa5", - "\xcf\xa6" => "\xcf\xa7", - "\xcf\xa8" => "\xcf\xa9", - "\xcf\xaa" => "\xcf\xab", - "\xcf\xac" => "\xcf\xad", - "\xcf\xae" => "\xcf\xaf", - "\xcf\xb4" => "\xce\xb8", - "\xd0\x80" => "\xd1\x90", - "\xd0\x81" => "\xd1\x91", - "\xd0\x82" => "\xd1\x92", - "\xd0\x83" => "\xd1\x93", - "\xd0\x84" => "\xd1\x94", - "\xd0\x85" => "\xd1\x95", - "\xd0\x86" => "\xd1\x96", - "\xd0\x87" => "\xd1\x97", - "\xd0\x88" => "\xd1\x98", - "\xd0\x89" => "\xd1\x99", - "\xd0\x8a" => "\xd1\x9a", - "\xd0\x8b" => "\xd1\x9b", - "\xd0\x8c" => "\xd1\x9c", - "\xd0\x8d" => "\xd1\x9d", - "\xd0\x8e" => "\xd1\x9e", - "\xd0\x8f" => "\xd1\x9f", - "\xd0\x90" => "\xd0\xb0", - "\xd0\x91" => "\xd0\xb1", - "\xd0\x92" => "\xd0\xb2", - "\xd0\x93" => "\xd0\xb3", - "\xd0\x94" => "\xd0\xb4", - "\xd0\x95" => "\xd0\xb5", - "\xd0\x96" => "\xd0\xb6", - "\xd0\x97" => "\xd0\xb7", - "\xd0\x98" => "\xd0\xb8", - "\xd0\x99" => "\xd0\xb9", - "\xd0\x9a" => "\xd0\xba", - "\xd0\x9b" => "\xd0\xbb", - "\xd0\x9c" => "\xd0\xbc", - "\xd0\x9d" => "\xd0\xbd", - "\xd0\x9e" => "\xd0\xbe", - "\xd0\x9f" => "\xd0\xbf", - "\xd0\xa0" => "\xd1\x80", - "\xd0\xa1" => "\xd1\x81", - "\xd0\xa2" => "\xd1\x82", - "\xd0\xa3" => "\xd1\x83", - "\xd0\xa4" => "\xd1\x84", - "\xd0\xa5" => "\xd1\x85", - "\xd0\xa6" => "\xd1\x86", - "\xd0\xa7" => "\xd1\x87", - "\xd0\xa8" => "\xd1\x88", - "\xd0\xa9" => "\xd1\x89", - "\xd0\xaa" => "\xd1\x8a", - "\xd0\xab" => "\xd1\x8b", - "\xd0\xac" => "\xd1\x8c", - "\xd0\xad" => "\xd1\x8d", - "\xd0\xae" => "\xd1\x8e", - "\xd0\xaf" => "\xd1\x8f", - "\xd1\xa0" => "\xd1\xa1", - "\xd1\xa2" => "\xd1\xa3", - "\xd1\xa4" => "\xd1\xa5", - "\xd1\xa6" => "\xd1\xa7", - "\xd1\xa8" => "\xd1\xa9", - "\xd1\xaa" => "\xd1\xab", - "\xd1\xac" => "\xd1\xad", - "\xd1\xae" => "\xd1\xaf", - "\xd1\xb0" => "\xd1\xb1", - "\xd1\xb2" => "\xd1\xb3", - "\xd1\xb4" => "\xd1\xb5", - "\xd1\xb6" => "\xd1\xb7", - "\xd1\xb8" => "\xd1\xb9", - "\xd1\xba" => "\xd1\xbb", - "\xd1\xbc" => "\xd1\xbd", - "\xd1\xbe" => "\xd1\xbf", - "\xd2\x80" => "\xd2\x81", - "\xd2\x8c" => "\xd2\x8d", - "\xd2\x8e" => "\xd2\x8f", - "\xd2\x90" => "\xd2\x91", - "\xd2\x92" => "\xd2\x93", - "\xd2\x94" => "\xd2\x95", - "\xd2\x96" => "\xd2\x97", - "\xd2\x98" => "\xd2\x99", - "\xd2\x9a" => "\xd2\x9b", - "\xd2\x9c" => "\xd2\x9d", - "\xd2\x9e" => "\xd2\x9f", - "\xd2\xa0" => "\xd2\xa1", - "\xd2\xa2" => "\xd2\xa3", - "\xd2\xa4" => "\xd2\xa5", - "\xd2\xa6" => "\xd2\xa7", - "\xd2\xa8" => "\xd2\xa9", - "\xd2\xaa" => "\xd2\xab", - "\xd2\xac" => "\xd2\xad", - "\xd2\xae" => "\xd2\xaf", - "\xd2\xb0" => "\xd2\xb1", - "\xd2\xb2" => "\xd2\xb3", - "\xd2\xb4" => "\xd2\xb5", - "\xd2\xb6" => "\xd2\xb7", - "\xd2\xb8" => "\xd2\xb9", - "\xd2\xba" => "\xd2\xbb", - "\xd2\xbc" => "\xd2\xbd", - "\xd2\xbe" => "\xd2\xbf", - "\xd3\x81" => "\xd3\x82", - "\xd3\x83" => "\xd3\x84", - "\xd3\x87" => "\xd3\x88", - "\xd3\x8b" => "\xd3\x8c", - "\xd3\x90" => "\xd3\x91", - "\xd3\x92" => "\xd3\x93", - "\xd3\x94" => "\xd3\x95", - "\xd3\x96" => "\xd3\x97", - "\xd3\x98" => "\xd3\x99", - "\xd3\x9a" => "\xd3\x9b", - "\xd3\x9c" => "\xd3\x9d", - "\xd3\x9e" => "\xd3\x9f", - "\xd3\xa0" => "\xd3\xa1", - "\xd3\xa2" => "\xd3\xa3", - "\xd3\xa4" => "\xd3\xa5", - "\xd3\xa6" => "\xd3\xa7", - "\xd3\xa8" => "\xd3\xa9", - "\xd3\xaa" => "\xd3\xab", - "\xd3\xac" => "\xd3\xad", - "\xd3\xae" => "\xd3\xaf", - "\xd3\xb0" => "\xd3\xb1", - "\xd3\xb2" => "\xd3\xb3", - "\xd3\xb4" => "\xd3\xb5", - "\xd3\xb8" => "\xd3\xb9", - "\xd4\xb1" => "\xd5\xa1", - "\xd4\xb2" => "\xd5\xa2", - "\xd4\xb3" => "\xd5\xa3", - "\xd4\xb4" => "\xd5\xa4", - "\xd4\xb5" => "\xd5\xa5", - "\xd4\xb6" => "\xd5\xa6", - "\xd4\xb7" => "\xd5\xa7", - "\xd4\xb8" => "\xd5\xa8", - "\xd4\xb9" => "\xd5\xa9", - "\xd4\xba" => "\xd5\xaa", - "\xd4\xbb" => "\xd5\xab", - "\xd4\xbc" => "\xd5\xac", - "\xd4\xbd" => "\xd5\xad", - "\xd4\xbe" => "\xd5\xae", - "\xd4\xbf" => "\xd5\xaf", - "\xd5\x80" => "\xd5\xb0", - "\xd5\x81" => "\xd5\xb1", - "\xd5\x82" => "\xd5\xb2", - "\xd5\x83" => "\xd5\xb3", - "\xd5\x84" => "\xd5\xb4", - "\xd5\x85" => "\xd5\xb5", - "\xd5\x86" => "\xd5\xb6", - "\xd5\x87" => "\xd5\xb7", - "\xd5\x88" => "\xd5\xb8", - "\xd5\x89" => "\xd5\xb9", - "\xd5\x8a" => "\xd5\xba", - "\xd5\x8b" => "\xd5\xbb", - "\xd5\x8c" => "\xd5\xbc", - "\xd5\x8d" => "\xd5\xbd", - "\xd5\x8e" => "\xd5\xbe", - "\xd5\x8f" => "\xd5\xbf", - "\xd5\x90" => "\xd6\x80", - "\xd5\x91" => "\xd6\x81", - "\xd5\x92" => "\xd6\x82", - "\xd5\x93" => "\xd6\x83", - "\xd5\x94" => "\xd6\x84", - "\xd5\x95" => "\xd6\x85", - "\xd5\x96" => "\xd6\x86", - "\xe1\xb8\x80" => "\xe1\xb8\x81", - "\xe1\xb8\x82" => "\xe1\xb8\x83", - "\xe1\xb8\x84" => "\xe1\xb8\x85", - "\xe1\xb8\x86" => "\xe1\xb8\x87", - "\xe1\xb8\x88" => "\xe1\xb8\x89", - "\xe1\xb8\x8a" => "\xe1\xb8\x8b", - "\xe1\xb8\x8c" => "\xe1\xb8\x8d", - "\xe1\xb8\x8e" => "\xe1\xb8\x8f", - "\xe1\xb8\x90" => "\xe1\xb8\x91", - "\xe1\xb8\x92" => "\xe1\xb8\x93", - "\xe1\xb8\x94" => "\xe1\xb8\x95", - "\xe1\xb8\x96" => "\xe1\xb8\x97", - "\xe1\xb8\x98" => "\xe1\xb8\x99", - "\xe1\xb8\x9a" => "\xe1\xb8\x9b", - "\xe1\xb8\x9c" => "\xe1\xb8\x9d", - "\xe1\xb8\x9e" => "\xe1\xb8\x9f", - "\xe1\xb8\xa0" => "\xe1\xb8\xa1", - "\xe1\xb8\xa2" => "\xe1\xb8\xa3", - "\xe1\xb8\xa4" => "\xe1\xb8\xa5", - "\xe1\xb8\xa6" => "\xe1\xb8\xa7", - "\xe1\xb8\xa8" => "\xe1\xb8\xa9", - "\xe1\xb8\xaa" => "\xe1\xb8\xab", - "\xe1\xb8\xac" => "\xe1\xb8\xad", - "\xe1\xb8\xae" => "\xe1\xb8\xaf", - "\xe1\xb8\xb0" => "\xe1\xb8\xb1", - "\xe1\xb8\xb2" => "\xe1\xb8\xb3", - "\xe1\xb8\xb4" => "\xe1\xb8\xb5", - "\xe1\xb8\xb6" => "\xe1\xb8\xb7", - "\xe1\xb8\xb8" => "\xe1\xb8\xb9", - "\xe1\xb8\xba" => "\xe1\xb8\xbb", - "\xe1\xb8\xbc" => "\xe1\xb8\xbd", - "\xe1\xb8\xbe" => "\xe1\xb8\xbf", - "\xe1\xb9\x80" => "\xe1\xb9\x81", - "\xe1\xb9\x82" => "\xe1\xb9\x83", - "\xe1\xb9\x84" => "\xe1\xb9\x85", - "\xe1\xb9\x86" => "\xe1\xb9\x87", - "\xe1\xb9\x88" => "\xe1\xb9\x89", - "\xe1\xb9\x8a" => "\xe1\xb9\x8b", - "\xe1\xb9\x8c" => "\xe1\xb9\x8d", - "\xe1\xb9\x8e" => "\xe1\xb9\x8f", - "\xe1\xb9\x90" => "\xe1\xb9\x91", - "\xe1\xb9\x92" => "\xe1\xb9\x93", - "\xe1\xb9\x94" => "\xe1\xb9\x95", - "\xe1\xb9\x96" => "\xe1\xb9\x97", - "\xe1\xb9\x98" => "\xe1\xb9\x99", - "\xe1\xb9\x9a" => "\xe1\xb9\x9b", - "\xe1\xb9\x9c" => "\xe1\xb9\x9d", - "\xe1\xb9\x9e" => "\xe1\xb9\x9f", - "\xe1\xb9\xa0" => "\xe1\xb9\xa1", - "\xe1\xb9\xa2" => "\xe1\xb9\xa3", - "\xe1\xb9\xa4" => "\xe1\xb9\xa5", - "\xe1\xb9\xa6" => "\xe1\xb9\xa7", - "\xe1\xb9\xa8" => "\xe1\xb9\xa9", - "\xe1\xb9\xaa" => "\xe1\xb9\xab", - "\xe1\xb9\xac" => "\xe1\xb9\xad", - "\xe1\xb9\xae" => "\xe1\xb9\xaf", - "\xe1\xb9\xb0" => "\xe1\xb9\xb1", - "\xe1\xb9\xb2" => "\xe1\xb9\xb3", - "\xe1\xb9\xb4" => "\xe1\xb9\xb5", - "\xe1\xb9\xb6" => "\xe1\xb9\xb7", - "\xe1\xb9\xb8" => "\xe1\xb9\xb9", - "\xe1\xb9\xba" => "\xe1\xb9\xbb", - "\xe1\xb9\xbc" => "\xe1\xb9\xbd", - "\xe1\xb9\xbe" => "\xe1\xb9\xbf", - "\xe1\xba\x80" => "\xe1\xba\x81", - "\xe1\xba\x82" => "\xe1\xba\x83", - "\xe1\xba\x84" => "\xe1\xba\x85", - "\xe1\xba\x86" => "\xe1\xba\x87", - "\xe1\xba\x88" => "\xe1\xba\x89", - "\xe1\xba\x8a" => "\xe1\xba\x8b", - "\xe1\xba\x8c" => "\xe1\xba\x8d", - "\xe1\xba\x8e" => "\xe1\xba\x8f", - "\xe1\xba\x90" => "\xe1\xba\x91", - "\xe1\xba\x92" => "\xe1\xba\x93", - "\xe1\xba\x94" => "\xe1\xba\x95", - "\xe1\xba\xa0" => "\xe1\xba\xa1", - "\xe1\xba\xa2" => "\xe1\xba\xa3", - "\xe1\xba\xa4" => "\xe1\xba\xa5", - "\xe1\xba\xa6" => "\xe1\xba\xa7", - "\xe1\xba\xa8" => "\xe1\xba\xa9", - "\xe1\xba\xaa" => "\xe1\xba\xab", - "\xe1\xba\xac" => "\xe1\xba\xad", - "\xe1\xba\xae" => "\xe1\xba\xaf", - "\xe1\xba\xb0" => "\xe1\xba\xb1", - "\xe1\xba\xb2" => "\xe1\xba\xb3", - "\xe1\xba\xb4" => "\xe1\xba\xb5", - "\xe1\xba\xb6" => "\xe1\xba\xb7", - "\xe1\xba\xb8" => "\xe1\xba\xb9", - "\xe1\xba\xba" => "\xe1\xba\xbb", - "\xe1\xba\xbc" => "\xe1\xba\xbd", - "\xe1\xba\xbe" => "\xe1\xba\xbf", - "\xe1\xbb\x80" => "\xe1\xbb\x81", - "\xe1\xbb\x82" => "\xe1\xbb\x83", - "\xe1\xbb\x84" => "\xe1\xbb\x85", - "\xe1\xbb\x86" => "\xe1\xbb\x87", - "\xe1\xbb\x88" => "\xe1\xbb\x89", - "\xe1\xbb\x8a" => "\xe1\xbb\x8b", - "\xe1\xbb\x8c" => "\xe1\xbb\x8d", - "\xe1\xbb\x8e" => "\xe1\xbb\x8f", - "\xe1\xbb\x90" => "\xe1\xbb\x91", - "\xe1\xbb\x92" => "\xe1\xbb\x93", - "\xe1\xbb\x94" => "\xe1\xbb\x95", - "\xe1\xbb\x96" => "\xe1\xbb\x97", - "\xe1\xbb\x98" => "\xe1\xbb\x99", - "\xe1\xbb\x9a" => "\xe1\xbb\x9b", - "\xe1\xbb\x9c" => "\xe1\xbb\x9d", - "\xe1\xbb\x9e" => "\xe1\xbb\x9f", - "\xe1\xbb\xa0" => "\xe1\xbb\xa1", - "\xe1\xbb\xa2" => "\xe1\xbb\xa3", - "\xe1\xbb\xa4" => "\xe1\xbb\xa5", - "\xe1\xbb\xa6" => "\xe1\xbb\xa7", - "\xe1\xbb\xa8" => "\xe1\xbb\xa9", - "\xe1\xbb\xaa" => "\xe1\xbb\xab", - "\xe1\xbb\xac" => "\xe1\xbb\xad", - "\xe1\xbb\xae" => "\xe1\xbb\xaf", - "\xe1\xbb\xb0" => "\xe1\xbb\xb1", - "\xe1\xbb\xb2" => "\xe1\xbb\xb3", - "\xe1\xbb\xb4" => "\xe1\xbb\xb5", - "\xe1\xbb\xb6" => "\xe1\xbb\xb7", - "\xe1\xbb\xb8" => "\xe1\xbb\xb9", - "\xe1\xbc\x88" => "\xe1\xbc\x80", - "\xe1\xbc\x89" => "\xe1\xbc\x81", - "\xe1\xbc\x8a" => "\xe1\xbc\x82", - "\xe1\xbc\x8b" => "\xe1\xbc\x83", - "\xe1\xbc\x8c" => "\xe1\xbc\x84", - "\xe1\xbc\x8d" => "\xe1\xbc\x85", - "\xe1\xbc\x8e" => "\xe1\xbc\x86", - "\xe1\xbc\x8f" => "\xe1\xbc\x87", - "\xe1\xbc\x98" => "\xe1\xbc\x90", - "\xe1\xbc\x99" => "\xe1\xbc\x91", - "\xe1\xbc\x9a" => "\xe1\xbc\x92", - "\xe1\xbc\x9b" => "\xe1\xbc\x93", - "\xe1\xbc\x9c" => "\xe1\xbc\x94", - "\xe1\xbc\x9d" => "\xe1\xbc\x95", - "\xe1\xbc\xa8" => "\xe1\xbc\xa0", - "\xe1\xbc\xa9" => "\xe1\xbc\xa1", - "\xe1\xbc\xaa" => "\xe1\xbc\xa2", - "\xe1\xbc\xab" => "\xe1\xbc\xa3", - "\xe1\xbc\xac" => "\xe1\xbc\xa4", - "\xe1\xbc\xad" => "\xe1\xbc\xa5", - "\xe1\xbc\xae" => "\xe1\xbc\xa6", - "\xe1\xbc\xaf" => "\xe1\xbc\xa7", - "\xe1\xbc\xb8" => "\xe1\xbc\xb0", - "\xe1\xbc\xb9" => "\xe1\xbc\xb1", - "\xe1\xbc\xba" => "\xe1\xbc\xb2", - "\xe1\xbc\xbb" => "\xe1\xbc\xb3", - "\xe1\xbc\xbc" => "\xe1\xbc\xb4", - "\xe1\xbc\xbd" => "\xe1\xbc\xb5", - "\xe1\xbc\xbe" => "\xe1\xbc\xb6", - "\xe1\xbc\xbf" => "\xe1\xbc\xb7", - "\xe1\xbd\x88" => "\xe1\xbd\x80", - "\xe1\xbd\x89" => "\xe1\xbd\x81", - "\xe1\xbd\x8a" => "\xe1\xbd\x82", - "\xe1\xbd\x8b" => "\xe1\xbd\x83", - "\xe1\xbd\x8c" => "\xe1\xbd\x84", - "\xe1\xbd\x8d" => "\xe1\xbd\x85", - "\xe1\xbd\x99" => "\xe1\xbd\x91", - "\xe1\xbd\x9b" => "\xe1\xbd\x93", - "\xe1\xbd\x9d" => "\xe1\xbd\x95", - "\xe1\xbd\x9f" => "\xe1\xbd\x97", - "\xe1\xbd\xa8" => "\xe1\xbd\xa0", - "\xe1\xbd\xa9" => "\xe1\xbd\xa1", - "\xe1\xbd\xaa" => "\xe1\xbd\xa2", - "\xe1\xbd\xab" => "\xe1\xbd\xa3", - "\xe1\xbd\xac" => "\xe1\xbd\xa4", - "\xe1\xbd\xad" => "\xe1\xbd\xa5", - "\xe1\xbd\xae" => "\xe1\xbd\xa6", - "\xe1\xbd\xaf" => "\xe1\xbd\xa7", - "\xe1\xbe\x88" => "\xe1\xbe\x80", - "\xe1\xbe\x89" => "\xe1\xbe\x81", - "\xe1\xbe\x8a" => "\xe1\xbe\x82", - "\xe1\xbe\x8b" => "\xe1\xbe\x83", - "\xe1\xbe\x8c" => "\xe1\xbe\x84", - "\xe1\xbe\x8d" => "\xe1\xbe\x85", - "\xe1\xbe\x8e" => "\xe1\xbe\x86", - "\xe1\xbe\x8f" => "\xe1\xbe\x87", - "\xe1\xbe\x98" => "\xe1\xbe\x90", - "\xe1\xbe\x99" => "\xe1\xbe\x91", - "\xe1\xbe\x9a" => "\xe1\xbe\x92", - "\xe1\xbe\x9b" => "\xe1\xbe\x93", - "\xe1\xbe\x9c" => "\xe1\xbe\x94", - "\xe1\xbe\x9d" => "\xe1\xbe\x95", - "\xe1\xbe\x9e" => "\xe1\xbe\x96", - "\xe1\xbe\x9f" => "\xe1\xbe\x97", - "\xe1\xbe\xa8" => "\xe1\xbe\xa0", - "\xe1\xbe\xa9" => "\xe1\xbe\xa1", - "\xe1\xbe\xaa" => "\xe1\xbe\xa2", - "\xe1\xbe\xab" => "\xe1\xbe\xa3", - "\xe1\xbe\xac" => "\xe1\xbe\xa4", - "\xe1\xbe\xad" => "\xe1\xbe\xa5", - "\xe1\xbe\xae" => "\xe1\xbe\xa6", - "\xe1\xbe\xaf" => "\xe1\xbe\xa7", - "\xe1\xbe\xb8" => "\xe1\xbe\xb0", - "\xe1\xbe\xb9" => "\xe1\xbe\xb1", - "\xe1\xbe\xba" => "\xe1\xbd\xb0", - "\xe1\xbe\xbb" => "\xe1\xbd\xb1", - "\xe1\xbe\xbc" => "\xe1\xbe\xb3", - "\xe1\xbf\x88" => "\xe1\xbd\xb2", - "\xe1\xbf\x89" => "\xe1\xbd\xb3", - "\xe1\xbf\x8a" => "\xe1\xbd\xb4", - "\xe1\xbf\x8b" => "\xe1\xbd\xb5", - "\xe1\xbf\x8c" => "\xe1\xbf\x83", - "\xe1\xbf\x98" => "\xe1\xbf\x90", - "\xe1\xbf\x99" => "\xe1\xbf\x91", - "\xe1\xbf\x9a" => "\xe1\xbd\xb6", - "\xe1\xbf\x9b" => "\xe1\xbd\xb7", - "\xe1\xbf\xa8" => "\xe1\xbf\xa0", - "\xe1\xbf\xa9" => "\xe1\xbf\xa1", - "\xe1\xbf\xaa" => "\xe1\xbd\xba", - "\xe1\xbf\xab" => "\xe1\xbd\xbb", - "\xe1\xbf\xac" => "\xe1\xbf\xa5", - "\xe1\xbf\xb8" => "\xe1\xbd\xb8", - "\xe1\xbf\xb9" => "\xe1\xbd\xb9", - "\xe1\xbf\xba" => "\xe1\xbd\xbc", - "\xe1\xbf\xbb" => "\xe1\xbd\xbd", - "\xe1\xbf\xbc" => "\xe1\xbf\xb3", - "\xe2\x84\xa6" => "\xcf\x89", - "\xe2\x84\xaa" => "k", - "\xe2\x84\xab" => "\xc3\xa5", - "\xe2\x85\xa0" => "\xe2\x85\xb0", - "\xe2\x85\xa1" => "\xe2\x85\xb1", - "\xe2\x85\xa2" => "\xe2\x85\xb2", - "\xe2\x85\xa3" => "\xe2\x85\xb3", - "\xe2\x85\xa4" => "\xe2\x85\xb4", - "\xe2\x85\xa5" => "\xe2\x85\xb5", - "\xe2\x85\xa6" => "\xe2\x85\xb6", - "\xe2\x85\xa7" => "\xe2\x85\xb7", - "\xe2\x85\xa8" => "\xe2\x85\xb8", - "\xe2\x85\xa9" => "\xe2\x85\xb9", - "\xe2\x85\xaa" => "\xe2\x85\xba", - "\xe2\x85\xab" => "\xe2\x85\xbb", - "\xe2\x85\xac" => "\xe2\x85\xbc", - "\xe2\x85\xad" => "\xe2\x85\xbd", - "\xe2\x85\xae" => "\xe2\x85\xbe", - "\xe2\x85\xaf" => "\xe2\x85\xbf", - "\xe2\x92\xb6" => "\xe2\x93\x90", - "\xe2\x92\xb7" => "\xe2\x93\x91", - "\xe2\x92\xb8" => "\xe2\x93\x92", - "\xe2\x92\xb9" => "\xe2\x93\x93", - "\xe2\x92\xba" => "\xe2\x93\x94", - "\xe2\x92\xbb" => "\xe2\x93\x95", - "\xe2\x92\xbc" => "\xe2\x93\x96", - "\xe2\x92\xbd" => "\xe2\x93\x97", - "\xe2\x92\xbe" => "\xe2\x93\x98", - "\xe2\x92\xbf" => "\xe2\x93\x99", - "\xe2\x93\x80" => "\xe2\x93\x9a", - "\xe2\x93\x81" => "\xe2\x93\x9b", - "\xe2\x93\x82" => "\xe2\x93\x9c", - "\xe2\x93\x83" => "\xe2\x93\x9d", - "\xe2\x93\x84" => "\xe2\x93\x9e", - "\xe2\x93\x85" => "\xe2\x93\x9f", - "\xe2\x93\x86" => "\xe2\x93\xa0", - "\xe2\x93\x87" => "\xe2\x93\xa1", - "\xe2\x93\x88" => "\xe2\x93\xa2", - "\xe2\x93\x89" => "\xe2\x93\xa3", - "\xe2\x93\x8a" => "\xe2\x93\xa4", - "\xe2\x93\x8b" => "\xe2\x93\xa5", - "\xe2\x93\x8c" => "\xe2\x93\xa6", - "\xe2\x93\x8d" => "\xe2\x93\xa7", - "\xe2\x93\x8e" => "\xe2\x93\xa8", - "\xe2\x93\x8f" => "\xe2\x93\xa9", - "\xef\xbc\xa1" => "\xef\xbd\x81", - "\xef\xbc\xa2" => "\xef\xbd\x82", - "\xef\xbc\xa3" => "\xef\xbd\x83", - "\xef\xbc\xa4" => "\xef\xbd\x84", - "\xef\xbc\xa5" => "\xef\xbd\x85", - "\xef\xbc\xa6" => "\xef\xbd\x86", - "\xef\xbc\xa7" => "\xef\xbd\x87", - "\xef\xbc\xa8" => "\xef\xbd\x88", - "\xef\xbc\xa9" => "\xef\xbd\x89", - "\xef\xbc\xaa" => "\xef\xbd\x8a", - "\xef\xbc\xab" => "\xef\xbd\x8b", - "\xef\xbc\xac" => "\xef\xbd\x8c", - "\xef\xbc\xad" => "\xef\xbd\x8d", - "\xef\xbc\xae" => "\xef\xbd\x8e", - "\xef\xbc\xaf" => "\xef\xbd\x8f", - "\xef\xbc\xb0" => "\xef\xbd\x90", - "\xef\xbc\xb1" => "\xef\xbd\x91", - "\xef\xbc\xb2" => "\xef\xbd\x92", - "\xef\xbc\xb3" => "\xef\xbd\x93", - "\xef\xbc\xb4" => "\xef\xbd\x94", - "\xef\xbc\xb5" => "\xef\xbd\x95", - "\xef\xbc\xb6" => "\xef\xbd\x96", - "\xef\xbc\xb7" => "\xef\xbd\x97", - "\xef\xbc\xb8" => "\xef\xbd\x98", - "\xef\xbc\xb9" => "\xef\xbd\x99", - "\xef\xbc\xba" => "\xef\xbd\x9a", - "\xf0\x90\x90\x80" => "\xf0\x90\x90\xa8", - "\xf0\x90\x90\x81" => "\xf0\x90\x90\xa9", - "\xf0\x90\x90\x82" => "\xf0\x90\x90\xaa", - "\xf0\x90\x90\x83" => "\xf0\x90\x90\xab", - "\xf0\x90\x90\x84" => "\xf0\x90\x90\xac", - "\xf0\x90\x90\x85" => "\xf0\x90\x90\xad", - "\xf0\x90\x90\x86" => "\xf0\x90\x90\xae", - "\xf0\x90\x90\x87" => "\xf0\x90\x90\xaf", - "\xf0\x90\x90\x88" => "\xf0\x90\x90\xb0", - "\xf0\x90\x90\x89" => "\xf0\x90\x90\xb1", - "\xf0\x90\x90\x8a" => "\xf0\x90\x90\xb2", - "\xf0\x90\x90\x8b" => "\xf0\x90\x90\xb3", - "\xf0\x90\x90\x8c" => "\xf0\x90\x90\xb4", - "\xf0\x90\x90\x8d" => "\xf0\x90\x90\xb5", - "\xf0\x90\x90\x8e" => "\xf0\x90\x90\xb6", - "\xf0\x90\x90\x8f" => "\xf0\x90\x90\xb7", - "\xf0\x90\x90\x90" => "\xf0\x90\x90\xb8", - "\xf0\x90\x90\x91" => "\xf0\x90\x90\xb9", - "\xf0\x90\x90\x92" => "\xf0\x90\x90\xba", - "\xf0\x90\x90\x93" => "\xf0\x90\x90\xbb", - "\xf0\x90\x90\x94" => "\xf0\x90\x90\xbc", - "\xf0\x90\x90\x95" => "\xf0\x90\x90\xbd", - "\xf0\x90\x90\x96" => "\xf0\x90\x90\xbe", - "\xf0\x90\x90\x97" => "\xf0\x90\x90\xbf", - "\xf0\x90\x90\x98" => "\xf0\x90\x91\x80", - "\xf0\x90\x90\x99" => "\xf0\x90\x91\x81", - "\xf0\x90\x90\x9a" => "\xf0\x90\x91\x82", - "\xf0\x90\x90\x9b" => "\xf0\x90\x91\x83", - "\xf0\x90\x90\x9c" => "\xf0\x90\x91\x84", - "\xf0\x90\x90\x9d" => "\xf0\x90\x91\x85", - "\xf0\x90\x90\x9e" => "\xf0\x90\x91\x86", - "\xf0\x90\x90\x9f" => "\xf0\x90\x91\x87", - "\xf0\x90\x90\xa0" => "\xf0\x90\x91\x88", - "\xf0\x90\x90\xa1" => "\xf0\x90\x91\x89", - "\xf0\x90\x90\xa2" => "\xf0\x90\x91\x8a", - "\xf0\x90\x90\xa3" => "\xf0\x90\x91\x8b", - "\xf0\x90\x90\xa4" => "\xf0\x90\x91\x8c", - "\xf0\x90\x90\xa5" => "\xf0\x90\x91\x8d" -); - - diff --git a/includes/XmlTypeCheck.php b/includes/XmlTypeCheck.php index 09b8c20a..8ee211e1 100644 --- a/includes/XmlTypeCheck.php +++ b/includes/XmlTypeCheck.php @@ -6,6 +6,12 @@ class XmlTypeCheck { * well-formed XML. Note that this doesn't check schema validity. */ public $wellFormed = false; + + /** + * Will be set to true if the optional element filter returned + * a match at some point. + */ + public $filterMatch = false; /** * Name of the document's root element, including any namespace @@ -13,33 +19,26 @@ class XmlTypeCheck { */ public $rootElement = ''; - private $softNamespaces; - private $namespaces = array(); - /** * @param $file string filename - * @param $softNamespaces bool - * If set to true, use of undeclared XML namespaces will be ignored. - * This matches the behavior of rsvg, but more compliant consumers - * such as Firefox will reject such files. - * Leave off for the default, stricter checks. + * @param $filterCallback callable (optional) + * Function to call to do additional custom validity checks from the + * SAX element handler event. This gives you access to the element + * namespace, name, and attributes, but not to text contents. + * Filter should return 'true' to toggle on $this->filterMatch */ - function __construct( $file, $softNamespaces=false ) { - $this->softNamespaces = $softNamespaces; + function __construct( $file, $filterCallback=null ) { + $this->filterCallback = $filterCallback; $this->run( $file ); } private function run( $fname ) { - if( $this->softNamespaces ) { - $parser = xml_parser_create( 'UTF-8' ); - } else { - $parser = xml_parser_create_ns( 'UTF-8' ); - } + $parser = xml_parser_create_ns( 'UTF-8' ); // case folding violates XML standard, turn it off xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); - xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false ); + xml_set_element_handler( $parser, array( $this, 'rootElementOpen' ), false ); $file = fopen( $fname, "rb" ); do { @@ -59,35 +58,22 @@ class XmlTypeCheck { xml_parser_free( $parser ); } + private function rootElementOpen( $parser, $name, $attribs ) { + $this->rootElement = $name; + + if( is_callable( $this->filterCallback ) ) { + xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false ); + $this->elementOpen( $parser, $name, $attribs ); + } else { + // We only need the first open element + xml_set_element_handler( $parser, false, false ); + } + } + private function elementOpen( $parser, $name, $attribs ) { - if( $this->softNamespaces ) { - // Check namespaces manually, so expat doesn't throw - // errors on use of undeclared namespaces. - foreach( $attribs as $attrib => $val ) { - if( $attrib == 'xmlns' ) { - $this->namespaces[''] = $val; - } elseif( substr( $attrib, 0, strlen( 'xmlns:' ) ) == 'xmlns:' ) { - $this->namespaces[substr( $attrib, strlen( 'xmlns:' ) )] = $val; - } - } - - if( strpos( $name, ':' ) === false ) { - $ns = ''; - $subname = $name; - } else { - list( $ns, $subname ) = explode( ':', $name, 2 ); - } - - if( isset( $this->namespaces[$ns] ) ) { - $name = $this->namespaces[$ns] . ':' . $subname; - } else { - // Technically this is invalid for XML with Namespaces. - // But..... we'll just let it slide in soft mode. - } + if( call_user_func( $this->filterCallback, $name, $attribs ) ) { + // Filter hit! + $this->filterMatch = true; } - - // We only need the first open element - $this->rootElement = $name; - xml_set_element_handler( $parser, false, false ); } } diff --git a/includes/api/ApiChangeRights.php b/includes/api/ApiChangeRights.php deleted file mode 100644 index 647a5194..00000000 --- a/includes/api/ApiChangeRights.php +++ /dev/null @@ -1,155 +0,0 @@ -<?php - -/* - * Created on Sep 11, 2007 - * API for MediaWiki 1.8+ - * - * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - */ - -if (!defined('MEDIAWIKI')) { - // Eclipse helper - will be ignored in production - require_once ("ApiBase.php"); -} - -/** - * API module that facilitates the changing of user rights. The API eqivalent of - * Special:Userrights. Requires API write mode to be enabled. - * - * @addtogroup API - */ -class ApiChangeRights extends ApiBase { - - public function __construct($main, $action) { - parent :: __construct($main, $action); - } - - public function execute() { - global $wgUser, $wgRequest; - $this->getMain()->requestWriteMode(); - - if(wfReadOnly()) - $this->dieUsage('The wiki is in read-only mode', 'readonly'); - $params = $this->extractRequestParams(); - - $ur = new UserrightsPage($wgRequest); - $allowed = $ur->changeableGroups(); - $res = array(); - - $u = $ur->fetchUser_real($params['user']); - if(is_array($u)) - switch($u[0]) - { - case UserrightsPage::FETCHUSER_NO_INTERWIKI: - $this->dieUsage("You don't have permission to change users' rights on other wikis", 'nointerwiki'); - case UserrightsPage::FETCHUSER_NO_DATABASE: - $this->dieUsage("Database ``{$u[1]}'' does not exist or is not local", 'nosuchdatabase'); - case UserrightsPage::FETCHUSER_NO_USER: - $this->dieUsage("You specified an empty username, or none at all", 'emptyuser'); - case UserrightsPage::FETCHUSER_NOSUCH_USERID: - $this->dieUsage("There is no user with ID ``{$u[1]}''", 'nosuchuserid'); - case UserrightsPage::FETCHUSER_NOSUCH_USERNAME: - $this->dieUsage("There is no user with username ``{$u[1]}''", 'nosuchusername'); - default: - $this->dieDebug(__METHOD__, "UserrightsPage::fetchUser_real() returned an unknown error ({$u[0]})"); - } - - $curgroups = $u->getGroups(); - if($params['listgroups']) - { - $res['user'] = $u->getName(); - $res['allowedgroups'] = $allowed; - $res['ingroups'] = $curgroups; - $this->getResult()->setIndexedTagName($res['ingroups'], 'group'); - $this->getResult()->setIndexedTagName($res['allowedgroups']['add'], 'group'); - $this->getResult()->setIndexedTagName($res['allowedgroups']['remove'], 'group'); - } -; - if($params['gettoken']) - { - $res['changerightstoken'] = $wgUser->editToken($u->getName()); - $this->getResult()->addValue(null, $this->getModuleName(), $res); - return; - } - - if(empty($params['addto']) && empty($params['rmfrom'])) - $this->dieUsage('At least one of the addto and rmfrom parameters must be set', 'noaddrm'); - if(is_null($params['token'])) - $this->dieUsage('The token parameter must be set', 'notoken'); - if(!$wgUser->matchEditToken($params['token'], $u->getName())) - $this->dieUsage('Invalid token', 'badtoken'); - - $dbw = wfGetDb(DB_MASTER); - $dbw->begin(); - $ur->saveUserGroups($u, $params['rmfrom'], $params['addto'], $params['reason']); - $dbw->commit(); - $res['user'] = $u->getName(); - $res['addedto'] = (array)$params['addto']; - $res['removedfrom'] = (array)$params['rmfrom']; - $res['reason'] = $params['reason']; - - $this->getResult()->setIndexedTagName($res['addedto'], 'group'); - $this->getResult()->setIndexedTagName($res['removedfrom'], 'group'); - $this->getResult()->addValue(null, $this->getModuleName(), $res); - } - - public function getAllowedParams() { - return array ( - 'user' => null, - 'token' => null, - 'gettoken' => false, - 'listgroups' => false, - 'addto' => array( - ApiBase :: PARAM_ISMULTI => true, - ), - 'rmfrom' => array( - ApiBase :: PARAM_ISMULTI => true, - ), - 'reason' => '' - ); - } - - public function getParamDescription() { - return array ( - 'user' => 'The user you want to add to or remove from groups.', - 'token' => 'A changerights token previously obtained through the gettoken parameter.', - 'gettoken' => 'Output a token. Note that the user parameter still has to be set.', - 'listgroups' => 'List the groups the user is in, and the ones you can add them to and remove them from.', - 'addto' => 'Pipe-separated list of groups to add this user to', - 'rmfrom' => 'Pipe-separated list of groups to remove this user from', - 'reason' => 'Reason for change (optional)' - ); - } - - public function getDescription() { - return array( - 'Add or remove a user from certain groups.' - ); - } - - protected function getExamples() { - return array ( - 'api.php?action=changerights&user=Bob&gettoken&listgroups', - 'api.php?action=changerights&user=Bob&token=123ABC&addto=sysop&reason=Promoting%20per%20RFA' - ); - } - - public function getVersion() { - return __CLASS__ . ': $Id: ApiChangeRights.php 28216 2007-12-06 18:33:18Z vasilievvv $'; - } -} diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index db58fe52..8f08f4db 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -70,6 +70,13 @@ abstract class ApiFormatBase extends ApiBase { } /** + * Get the internal format name + */ + public function getFormat() { + return $this->mFormat; + } + + /** * Specify whether or not ampersands should be escaped to '&' when rendering. This * should only be set to true for the help message when rendered in the default (xmlfm) * format. This is a temporary special-case fix that should be removed once the help @@ -232,7 +239,7 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 36153 2008-06-10 15:20:22Z tstarling $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 44569 2008-12-14 08:31:04Z tstarling $'; } } @@ -293,6 +300,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 36153 2008-06-10 15:20:22Z tstarling $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 44569 2008-12-14 08:31:04Z tstarling $'; } } diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index cce4c3e7..2d0e278c 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -403,7 +403,7 @@ class ApiMain extends ApiBase { * tell the printer not to escape ampersands so that our links do * not break. */ $printer->setUnescapeAmps ( ( $this->mAction == 'help' || $isError ) - && $this->getParameter('format') == ApiMain::API_DEFAULT_FORMAT ); + && $printer->getFormat() == 'XML' && $printer->getIsHtml() ); $printer->initPrinter($isError); @@ -603,7 +603,7 @@ class ApiMain extends ApiBase { public function getVersion() { $vers = array (); $vers[] = 'MediaWiki ' . SpecialVersion::getVersion(); - $vers[] = __CLASS__ . ': $Id: ApiMain.php 37349 2008-07-08 20:53:41Z catrope $'; + $vers[] = __CLASS__ . ': $Id: ApiMain.php 44569 2008-12-14 08:31:04Z tstarling $'; $vers[] = ApiBase :: getBaseVersion(); $vers[] = ApiFormatBase :: getBaseVersion(); $vers[] = ApiQueryBase :: getBaseVersion(); diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php index 08ec1514..eb8df0f5 100644 --- a/includes/filerepo/FSRepo.php +++ b/includes/filerepo/FSRepo.php @@ -149,10 +149,8 @@ class FSRepo extends FileRepo { if ( !wfMkdirParents( $dstDir ) ) { return $this->newFatal( 'directorycreateerror', $dstDir ); } - // In the deleted zone, seed new directories with a blank - // index.html, to prevent crawling if ( $dstZone == 'deleted' ) { - file_put_contents( "$dstDir/index.html", '' ); + $this->initDeletedDir( $dstDir ); } } @@ -215,6 +213,20 @@ class FSRepo extends FileRepo { } /** + * Take all available measures to prevent web accessibility of new deleted + * directories, in case the user has not configured offline storage + */ + protected function initDeletedDir( $dir ) { + // Add a .htaccess file to the root of the deleted zone + $root = $this->getZonePath( 'deleted' ); + if ( !file_exists( "$root/.htaccess" ) ) { + file_put_contents( "$root/.htaccess", "Deny from all\n" ); + } + // Seed new directories with a blank index.html, to prevent crawling + file_put_contents( "$dir/index.html", '' ); + } + + /** * Pick a random name in the temp zone and store a file to it. * @param string $originalName The base name of the file as specified * by the user. The file extension will be maintained. @@ -393,8 +405,7 @@ class FSRepo extends FileRepo { $status->fatal( 'directorycreateerror', $archiveDir ); continue; } - // Seed new directories with a blank index.html, to prevent crawling - file_put_contents( "$archiveDir/index.html", '' ); + $this->initDeletedDir( $archiveDir ); } // Check if the archive directory is writable // This doesn't appear to work on NTFS diff --git a/includes/filerepo/ICRepo.php b/includes/filerepo/ICRepo.php deleted file mode 100644 index ab686f9b..00000000 --- a/includes/filerepo/ICRepo.php +++ /dev/null @@ -1,309 +0,0 @@ -<?php - -/** - * A repository for files accessible via InstantCommons. - */ - -class ICRepo extends LocalRepo { - var $directory, $url, $hashLevels, $cache; - var $fileFactory = array( 'ICFile', 'newFromTitle' ); - var $oldFileFactory = false; - - function __construct( $info ) { - parent::__construct( $info ); - // Required settings - $this->directory = $info['directory']; - $this->url = $info['url']; - $this->hashLevels = $info['hashLevels']; - if(isset($info['cache'])){ - $this->cache = getcwd().'/images/'.$info['cache']; - } - } -} - -/** - * A file loaded from InstantCommons - */ -class ICFile extends LocalFile{ - static function newFromTitle($title,$repo){ - return new self($title, $repo); - } - - /** - * Returns true if the file comes from the local file repository. - * - * @return bool - */ - function isLocal() { - return true; - } - - function load(){ - if (!$this->dataLoaded ) { - if ( !$this->loadFromCache() ) { - if(!$this->loadFromDB()){ - $this->loadFromIC(); - } - $this->saveToCache(); - } - $this->dataLoaded = true; - } - } - - /** - * Load file metadata from the DB - */ - function loadFromDB() { - wfProfileIn( __METHOD__ ); - - # Unconditionally set loaded=true, we don't want the accessors constantly rechecking - $this->dataLoaded = true; - - $dbr = $this->repo->getSlaveDB(); - - $row = $dbr->selectRow( 'ic_image', $this->getCacheFields( 'img_' ), - array( 'img_name' => $this->getName() ), __METHOD__ ); - if ( $row ) { - if (trim($row->img_media_type)==NULL) { - $this->upgradeRow(); - $this->upgraded = true; - } - $this->loadFromRow( $row ); - //This means that these files are local so the repository locations are local - $this->setUrlPathLocal(); - $this->fileExists = true; - //var_dump($this); exit; - } else { - $this->fileExists = false; - } - - wfProfileOut( __METHOD__ ); - - return $this->fileExists; - } - - /** - * Fix assorted version-related problems with the image row by reloading it from the file - */ - function upgradeRow() { - wfProfileIn( __METHOD__ ); - - $this->loadFromIC(); - - $dbw = $this->repo->getMasterDB(); - list( $major, $minor ) = self::splitMime( $this->mime ); - - wfDebug(__METHOD__.': upgrading '.$this->getName()." to the current schema\n"); - - $dbw->update( 'ic_image', - array( - 'img_width' => $this->width, - 'img_height' => $this->height, - 'img_bits' => $this->bits, - 'img_media_type' => $this->type, - 'img_major_mime' => $major, - 'img_minor_mime' => $minor, - 'img_metadata' => $this->metadata, - ), array( 'img_name' => $this->getName() ), - __METHOD__ - ); - $this->saveToCache(); - wfProfileOut( __METHOD__ ); - } - - function exists(){ - $this->load(); - return $this->fileExists; - } - - /** - * Fetch the file from the repository. Check local ic_images table first. If not available, check remote server - */ - function loadFromIC(){ - # Unconditionally set loaded=true, we don't want the accessors constantly rechecking - $this->dataLoaded = true; - $icUrl = $this->repo->directory.'&media='.$this->title->mDbkeyform; - if($h = @fopen($icUrl, 'rb')){ - $contents = fread($h, 3000); - $image = $this->api_xml_to_array($contents); - if($image['fileExists']){ - foreach($image as $property=>$value){ - if($property=="url"){$value=$this->repo->url.$value; } - $this->$property = $value; - } - if($this->curl_file_get_contents($this->repo->url.$image['url'], $this->repo->cache.'/'.$image['name'])){ - //Record the image - $this->recordDownload("Downloaded with InstantCommons"); - - //Then cache it - }else{//set fileExists back to false - $this->fileExists = false; - } - } - } - } - - function setUrlPathLocal(){ - global $wgScriptPath; - $path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath)); - $this->repo->url = $path;//.'/'.rawurlencode($this->title->mDbkeyform); - $this->repo->directory = $this->repo->cache;//.'/'.rawurlencode($this->title->mDbkeyform); - - } - - function getThumbPath( $suffix=false ){ - $path = $this->repo->cache; - if ( $suffix !== false ) { - $path .= '/thumb/' . rawurlencode( $suffix ); - } - return $path; - } - function getThumbUrl( $suffix=false ){ - global $wgScriptPath; - $path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath)); - if ( $suffix !== false ) { - $path .= '/thumb/' . rawurlencode( $suffix ); - } - return $path; - } - - /** - * Convert the InstantCommons Server API XML Response to an associative array - */ - function api_xml_to_array($xml){ - preg_match("/<instantcommons><image(.*?)<\/instantcommons>/",$xml,$match); - preg_match_all("/(.*?=\".*?\")/",$match[1], $matches); - foreach($matches[1] as $match){ - list($key,$value) = split("=",$match); - $image[trim($key,'<" ')]=trim($value,' "'); - } - return $image; - } - - /** - * Use cURL to read the content of a URL into a string - * ref: http://groups-beta.google.com/group/comp.lang.php/browse_thread/thread/8efbbaced3c45e3c/d63c7891cf8e380b?lnk=raot - * @param string $url - the URL to fetch - * @param resource $fp - filename to write file contents to - * @param boolean $bg - call cURL in the background (don't hang page until complete) - * @param int $timeout - cURL connect timeout - */ - function curl_file_get_contents($url, $fp, $bg=TRUE, $timeout = 1) { - # Call curl in the background to download the file - $cmd = 'curl '.wfEscapeShellArg($url).' -o '.$fp.' &'; - wfDebug('Curl download initiated='.$cmd ); - $success = false; - $file_contents = array(); - $file_contents['err'] = wfShellExec($cmd, $file_contents['return']); - if($file_contents['err']==0){//Success - $success = true; - } - return $success; - } - - function getMasterDB() { - if ( !isset( $this->dbConn ) ) { - $class = 'Database' . ucfirst( $this->dbType ); - $this->dbConn = new $class( $this->dbServer, $this->dbUser, - $this->dbPassword, $this->dbName, false, $this->dbFlags, - $this->tablePrefix ); - } - return $this->dbConn; - } - - /** - * Record a file upload in the upload log and the image table - */ - private function recordDownload($comment='', $timestamp = false ){ - global $wgUser; - - $dbw = $this->repo->getMasterDB(); - - if ( $timestamp === false ) { - $timestamp = $dbw->timestamp(); - } - list( $major, $minor ) = self::splitMime( $this->mime ); - - # Test to see if the row exists using INSERT IGNORE - # This avoids race conditions by locking the row until the commit, and also - # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. - $dbw->insert( 'ic_image', - array( - 'img_name' => $this->getName(), - 'img_size'=> $this->size, - 'img_width' => intval( $this->width ), - 'img_height' => intval( $this->height ), - 'img_bits' => $this->bits, - 'img_media_type' => $this->type, - 'img_major_mime' => $major, - 'img_minor_mime' => $minor, - 'img_timestamp' => $timestamp, - 'img_description' => $comment, - 'img_user' => $wgUser->getID(), - 'img_user_text' => $wgUser->getName(), - 'img_metadata' => $this->metadata, - ), - __METHOD__, - 'IGNORE' - ); - - if( $dbw->affectedRows() == 0 ) { - # Collision, this is an update of a file - # Update the current image row - $dbw->update( 'ic_image', - array( /* SET */ - 'img_size' => $this->size, - 'img_width' => intval( $this->width ), - 'img_height' => intval( $this->height ), - 'img_bits' => $this->bits, - 'img_media_type' => $this->media_type, - 'img_major_mime' => $this->major_mime, - 'img_minor_mime' => $this->minor_mime, - 'img_timestamp' => $timestamp, - 'img_description' => $comment, - 'img_user' => $wgUser->getID(), - 'img_user_text' => $wgUser->getName(), - 'img_metadata' => $this->metadata, - ), array( /* WHERE */ - 'img_name' => $this->getName() - ), __METHOD__ - ); - } else { - # This is a new file - # Update the image count - $site_stats = $dbw->tableName( 'site_stats' ); - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ ); - } - - $descTitle = $this->getTitle(); - $article = new Article( $descTitle ); - - # Add the log entry - $log = new LogPage( 'icdownload' ); - $log->addEntry( 'InstantCommons download', $descTitle, $comment ); - - if( $descTitle->exists() ) { - # Create a null revision - $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(), $log->getRcComment(), false ); - $nullRevision->insertOn( $dbw ); - $article->updateRevisionOn( $dbw, $nullRevision ); - - # Invalidate the cache for the description page - $descTitle->invalidateCache(); - $descTitle->purgeSquid(); - } - - - # Commit the transaction now, in case something goes wrong later - # The most important thing is that files don't get lost, especially archives - $dbw->immediateCommit(); - - # Invalidate cache for all pages using this file - $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ); - $update->doUpdate(); - - return true; - } - -} - diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 4c37f1f9..1623245d 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -43,26 +43,30 @@ function wfSpecialImport( $page = '' ) { if( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit') { $isUpload = false; $namespace = $wgRequest->getIntOrNull( 'namespace' ); + $sourceName = $wgRequest->getVal( "source" ); - switch( $wgRequest->getVal( "source" ) ) { - case "upload": + if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'editToken' ) ) ) { + $source = new WikiErrorMsg( 'import-token-mismatch' ); + } elseif ( $sourceName == 'upload' ) { $isUpload = true; if( $wgUser->isAllowed( 'importupload' ) ) { $source = ImportStreamSource::newFromUpload( "xmlimport" ); } else { return $wgOut->permissionRequired( 'importupload' ); } - break; - case "interwiki": + } elseif ( $sourceName == "interwiki" ) { $interwiki = $wgRequest->getVal( 'interwiki' ); - $history = $wgRequest->getCheck( 'interwikiHistory' ); - $frompage = $wgRequest->getText( "frompage" ); - $source = ImportStreamSource::newFromInterwiki( - $interwiki, - $frompage, - $history ); - break; - default: + if ( !in_array( $interwiki, $wgImportSources ) ) { + $source = new WikiErrorMsg( "import-invalid-interwiki" ); + } else { + $history = $wgRequest->getCheck( 'interwikiHistory' ); + $frompage = $wgRequest->getText( "frompage" ); + $source = ImportStreamSource::newFromInterwiki( + $interwiki, + $frompage, + $history ); + } + } else { $source = new WikiErrorMsg( "importunknownsource" ); } @@ -106,6 +110,7 @@ function wfSpecialImport( $page = '' ) { Xml::hidden( 'action', 'submit' ) . Xml::hidden( 'source', 'upload' ) . Xml::input( 'xmlimport', 50, '', array( 'type' => 'file' ) ) . ' ' . + Xml::hidden( 'editToken', $wgUser->editToken() ) . Xml::submitButton( wfMsg( 'uploadbtn' ) ) . Xml::closeElement( 'form' ) . Xml::closeElement( 'fieldset' ) @@ -124,6 +129,7 @@ function wfSpecialImport( $page = '' ) { wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) . Xml::hidden( 'action', 'submit' ) . Xml::hidden( 'source', 'interwiki' ) . + Xml::hidden( 'editToken', $wgUser->editToken() ) . Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . "<tr> <td>" . diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index fbbf89d6..d862ebb3 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -571,7 +571,7 @@ class PageArchive { */ class UndeleteForm { var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj; - var $mTargetTimestamp, $mAllowed, $mComment; + var $mTargetTimestamp, $mAllowed, $mComment, $mToken; function UndeleteForm( $request, $par = "" ) { global $wgUser; @@ -589,6 +589,7 @@ class UndeleteForm { $this->mDiff = $request->getCheck( 'diff' ); $this->mComment = $request->getText( 'wpComment' ); $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); + $this->mToken = $request->getVal( 'token' ); if( $par != "" ) { $this->mTarget = $par; @@ -655,6 +656,9 @@ class UndeleteForm { if( !$file->userCan( File::DELETED_FILE ) ) { $wgOut->permissionRequired( 'suppressrevision' ); return false; + } elseif ( !$wgUser->matchEditToken( $this->mToken, $this->mFile ) ) { + $this->showFileConfirmationForm( $this->mFile ); + return false; } else { return $this->showFile( $this->mFile ); } @@ -880,6 +884,29 @@ class UndeleteForm { } /** + * Show a form confirming whether a tokenless user really wants to see a file + */ + private function showFileConfirmationForm( $key ) { + global $wgOut, $wgUser, $wgLang; + $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile ); + $wgOut->addWikiMsg( 'undelete-show-file-confirm', + $this->mTargetObj->getText(), + $wgLang->timeanddate( $file->getTimestamp() ) ); + $wgOut->addHTML( + Xml::openElement( 'form', array( + 'method' => 'POST', + 'action' => SpecialPage::getTitleFor( 'Undelete' )->getLocalUrl( + 'target=' . urlencode( $this->mTarget ) . + '&file=' . urlencode( $key ) . + '&token=' . urlencode( $wgUser->editToken( $key ) ) ) + ) + ) . + Xml::submitButton( wfMsg( 'undelete-show-file-submit' ) ) . + '</form>' + ); + } + + /** * Show a deleted file version requested by the visitor. */ private function showFile( $key ) { @@ -1191,13 +1218,15 @@ class UndeleteForm { * @return string */ function getFileLink( $file, $titleObj, $ts, $key, $sk ) { - global $wgLang; + global $wgLang, $wgUser; if( !$file->userCan(File::DELETED_FILE) ) { return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>'; } else { $link = $sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), - "target=".$this->mTargetObj->getPrefixedUrl()."&file=$key" ); + "target=".$this->mTargetObj->getPrefixedUrl(). + "&file=$key" . + "&token=" . urlencode( $wgUser->editToken( $key ) ) ); if( $file->isDeleted(File::DELETED_FILE) ) $link = '<span class="history-deleted">' . $link . '</span>'; return $link; diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 8fe2f52f..3a79e052 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -1326,11 +1326,11 @@ wgUploadAutoFill = {$autofill}; $magic = MimeMagic::singleton(); $mime = $magic->guessMimeType($tmpfile,false); + #check mime type, if desired global $wgVerifyMimeType; if ($wgVerifyMimeType) { - - wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n"); + wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n"); #check mime type against file extension if( !self::verifyExtension( $mime, $extension ) ) { return new WikiErrorMsg( 'uploadcorrupt' ); @@ -1338,9 +1338,22 @@ wgUploadAutoFill = {$autofill}; #check mime type blacklist global $wgMimeTypeBlacklist; - if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) - && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { - return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) ); + if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) ) { + if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { + return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) ); + } + + # Check IE type + $fp = fopen( $tmpfile, 'rb' ); + $chunk = fread( $fp, 256 ); + fclose( $fp ); + $extMime = $magic->guessTypesForExtension( $extension ); + $ieTypes = $magic->getIEMimeTypes( $tmpfile, $chunk, $extMime ); + foreach ( $ieTypes as $ieType ) { + if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) { + return new WikiErrorMsg( 'filetype-bad-ie-mime', $ieType ); + } + } } } @@ -1348,6 +1361,11 @@ wgUploadAutoFill = {$autofill}; if( $this->detectScript ( $tmpfile, $mime, $extension ) ) { return new WikiErrorMsg( 'uploadscripted' ); } + if( $extension == 'svg' || $mime == 'image/svg+xml' ) { + if( $this->detectScriptInSvg( $tmpfile ) ) { + return new WikiErrorMsg( 'uploadscripted' ); + } + } /** * Scan the uploaded file for viruses @@ -1399,6 +1417,7 @@ wgUploadAutoFill = {$autofill}; } } + /** * Heuristic for detecting files that *could* contain JavaScript instructions or * things that may look like HTML to a browser and are thus @@ -1459,6 +1478,7 @@ wgUploadAutoFill = {$autofill}; */ $tags = array( + '<a href', '<body', '<head', '<html', #also in safari @@ -1497,6 +1517,41 @@ wgUploadAutoFill = {$autofill}; return false; } + function detectScriptInSvg( $filename ) { + $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) ); + return $check->filterMatch; + } + + /** + * @todo Replace this with a whitelist filter! + */ + function checkSvgScriptCallback( $element, $attribs ) { + $stripped = $this->stripXmlNamespace( $element ); + + if( $stripped == 'script' ) { + wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" ); + return true; + } + + foreach( $attribs as $attrib => $value ) { + $stripped = $this->stripXmlNamespace( $attrib ); + if( substr( $stripped, 0, 2 ) == 'on' ) { + wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" ); + return true; + } + if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) { + wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" ); + return true; + } + } + } + + private function stripXmlNamespace( $name ) { + // 'http://www.w3.org/2000/svg:script' -> 'script' + $parts = explode( ':', strtolower( $name ) ); + return array_pop( $parts ); + } + /** * Generic wrapper function for a virus scanner program. * This relies on the $wgAntivirus and $wgAntivirusSetup variables. |