summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2008-12-15 18:02:47 +0100
committerPierre Schmitz <pierre@archlinux.de>2008-12-15 18:02:47 +0100
commit396b28f3d881f5debd888ba9bb9b47c2d478a76f (patch)
tree10d6e1a721ee4ef69def34a57f02d7eb3fc9e31e /includes
parent0be4d3ccf6c4fe98a72704f9463ecdea2ee5e615 (diff)
update to Mediawiki 1.13.3; some cleanups
Diffstat (limited to 'includes')
-rw-r--r--includes/AutoLoader.php1
-rw-r--r--includes/CacheManager.php159
-rw-r--r--includes/CoreParserFunctions.php268
-rw-r--r--includes/Database.php2443
-rw-r--r--includes/DatabaseMysql.php6
-rw-r--r--includes/DatabaseOracle.php697
-rw-r--r--includes/DatabasePostgres.php1313
-rw-r--r--includes/DateFormatter.php285
-rw-r--r--includes/DefaultSettings.php6
-rw-r--r--includes/Exception.php11
-rw-r--r--includes/HTMLForm.php107
-rw-r--r--includes/IEContentAnalyzer.php823
-rw-r--r--includes/Image.php2142
-rw-r--r--includes/LoadBalancer.php653
-rw-r--r--includes/MimeMagic.php27
-rw-r--r--includes/Parser.php4913
-rw-r--r--includes/ParserCache.php119
-rw-r--r--includes/ParserOptions.php145
-rw-r--r--includes/ParserOutput.php189
-rw-r--r--includes/ParserXML.php643
-rw-r--r--includes/Parser_DiffTest.php85
-rw-r--r--includes/Parser_OldPP.php4942
-rw-r--r--includes/Preprocessor.php154
-rw-r--r--includes/Preprocessor_DOM.php1356
-rw-r--r--includes/Preprocessor_Hash.php1471
-rw-r--r--includes/Profiling.php353
-rw-r--r--includes/SearchTsearch2.php122
-rw-r--r--includes/SiteStatsUpdate.php92
-rw-r--r--includes/SpecialAllmessages.php219
-rw-r--r--includes/SpecialAllpages.php395
-rw-r--r--includes/SpecialAncientpages.php63
-rw-r--r--includes/SpecialBlockip.php476
-rw-r--r--includes/SpecialBlockme.php38
-rw-r--r--includes/SpecialBooksources.php113
-rw-r--r--includes/SpecialBrokenRedirects.php95
-rw-r--r--includes/SpecialCategories.php63
-rw-r--r--includes/SpecialConfirmemail.php104
-rw-r--r--includes/SpecialContributions.php434
-rw-r--r--includes/SpecialDeadendpages.php65
-rw-r--r--includes/SpecialDisambiguations.php106
-rw-r--r--includes/SpecialDoubleRedirects.php104
-rw-r--r--includes/SpecialEmailuser.php222
-rw-r--r--includes/SpecialExport.php284
-rw-r--r--includes/SpecialFewestrevisions.php65
-rw-r--r--includes/SpecialFilepath.php69
-rw-r--r--includes/SpecialImagelist.php166
-rw-r--r--includes/SpecialImport.php921
-rw-r--r--includes/SpecialIpblocklist.php430
-rw-r--r--includes/SpecialListredirects.php60
-rw-r--r--includes/SpecialListusers.php217
-rw-r--r--includes/SpecialLockdb.php134
-rw-r--r--includes/SpecialLog.php527
-rw-r--r--includes/SpecialLonelypages.php60
-rw-r--r--includes/SpecialLongpages.php33
-rw-r--r--includes/SpecialMIMEsearch.php141
-rw-r--r--includes/SpecialMergeHistory.php423
-rw-r--r--includes/SpecialMostcategories.php59
-rw-r--r--includes/SpecialMostimages.php55
-rw-r--r--includes/SpecialMostlinked.php93
-rw-r--r--includes/SpecialMostlinkedcategories.php75
-rw-r--r--includes/SpecialMostlinkedtemplates.php129
-rw-r--r--includes/SpecialMostrevisions.php66
-rw-r--r--includes/SpecialMovepage.php327
-rw-r--r--includes/SpecialNewimages.php207
-rw-r--r--includes/SpecialNewpages.php317
-rw-r--r--includes/SpecialPopularpages.php69
-rw-r--r--includes/SpecialPreferences.php1047
-rw-r--r--includes/SpecialPrefixindex.php149
-rw-r--r--includes/SpecialProtectedpages.php287
-rwxr-xr-xincludes/SpecialProtectedtitles.php219
-rw-r--r--includes/SpecialRandompage.php108
-rw-r--r--includes/SpecialRandomredirect.php20
-rw-r--r--includes/SpecialRecentchanges.php730
-rw-r--r--includes/SpecialRecentchangeslinked.php190
-rw-r--r--includes/SpecialResetpass.php165
-rw-r--r--includes/SpecialRevisiondelete.php275
-rw-r--r--includes/SpecialSearch.php438
-rw-r--r--includes/SpecialShortpages.php92
-rw-r--r--includes/SpecialSpecialpages.php61
-rw-r--r--includes/SpecialStatistics.php93
-rw-r--r--includes/SpecialUncategorizedcategories.php37
-rw-r--r--includes/SpecialUncategorizedimages.php47
-rw-r--r--includes/SpecialUncategorizedpages.php57
-rw-r--r--includes/SpecialUncategorizedtemplates.php31
-rw-r--r--includes/SpecialUndelete.php1075
-rw-r--r--includes/SpecialUnlockdb.php110
-rw-r--r--includes/SpecialUnusedcategories.php46
-rw-r--r--includes/SpecialUnusedimages.php61
-rw-r--r--includes/SpecialUnusedtemplates.php51
-rw-r--r--includes/SpecialUnwatchedpages.php65
-rw-r--r--includes/SpecialUpload.php1646
-rw-r--r--includes/SpecialUploadMogile.php136
-rw-r--r--includes/SpecialUserlogin.php863
-rw-r--r--includes/SpecialUserlogout.php19
-rw-r--r--includes/SpecialUserrights.php575
-rw-r--r--includes/SpecialVersion.php355
-rw-r--r--includes/SpecialWantedcategories.php79
-rw-r--r--includes/SpecialWantedpages.php133
-rw-r--r--includes/SpecialWatchlist.php375
-rw-r--r--includes/SpecialWhatlinkshere.php322
-rw-r--r--includes/SpecialWithoutinterwiki.php93
-rw-r--r--includes/StreamFile.php52
-rw-r--r--includes/Title.php8
-rw-r--r--includes/Utf8Case.php1505
-rw-r--r--includes/XmlTypeCheck.php74
-rw-r--r--includes/api/ApiChangeRights.php155
-rw-r--r--includes/api/ApiFormatBase.php11
-rw-r--r--includes/api/ApiMain.php4
-rw-r--r--includes/filerepo/FSRepo.php21
-rw-r--r--includes/filerepo/ICRepo.php309
-rw-r--r--includes/specials/SpecialImport.php30
-rw-r--r--includes/specials/SpecialUndelete.php35
-rw-r--r--includes/specials/SpecialUpload.php65
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&nbsp;\\2',
- # french spaces, Guillemet-right
- '/(\\302\\253) /' => '\\1&nbsp;',
- );
- $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&lt;div\\5&gt;\\6&lt;/div&gt;\\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( '-->', '--&gt;', $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 &ouml; 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('&lt;', '&gt;'), 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 .= '&lt;'.$ext.'&gt;';
- $ret .= $this->sub_makeXHTML($parser);
- $ret .= '&lt;/'.$ext.'&gt; ';
- 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 .= '&lt;'.$n.'&gt;';
- $ret .= $this->sub_makeXHTML($parser);
- $ret .= '&lt;/'.$n.'&gt; ';
- } // 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&nbsp;\\2',
- # french spaces, Guillemet-right
- '/(\\302\\253) /' => '\\1&nbsp;',
- );
- $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&lt;div\\5&gt;\\6&lt;/div&gt;\\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 &ouml; 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('&lt;', '&gt;'), 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 .= '&lt;';
- ++$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 .= '&lt;';
- ++$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>&nbsp;</td>
- <td>
- " . wfCheckLabel( wfMsgHtml( 'ipbanononly' ),
- 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly,
- array( 'tabindex' => '6' ) ) . "
- </td>
- </tr>
- <tr id='wpCreateAccountRow'>
- <td>&nbsp;</td>
- <td>
- " . wfCheckLabel( wfMsgHtml( 'ipbcreateaccount' ),
- 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount,
- array( 'tabindex' => '7' ) ) . "
- </td>
- </tr>
- <tr id='wpEnableAutoblockRow'>
- <td>&nbsp;</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>&nbsp;</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>&nbsp;</td>
- <td>
- " . wfCheckLabel( wfMsgHtml( 'ipbhidename' ),
- 'wpHideName', 'wpHideName', $this->BlockHideName,
- array( 'tabindex' => '9' ) ) . "
- </td>
- </tr>
- ");
- }
-
- $wgOut->addHTML("
- <tr>
- <td style='padding-top: 1em'>&nbsp;</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 .= '&nbsp;' . 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 ) . '&nbsp;';
- $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>&nbsp;</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 ) .
- '&nbsp;' .
- 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>&nbsp;</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>&nbsp;</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&timestamp=$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>&nbsp;</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(
- '&nbsp;',
- Xml::element( 'span', array( 'class' => 'error' ),
- wfMsg( 'badsiglength', $wgLang->formatNum( $wgMaxSigChars ) ) )
- );
- } elseif( !empty( $this->mToggles['fancysig'] ) &&
- false === $wgParser->validateSig( $this->mNick ) ) {
- $invalidSig = $this->tableRow(
- '&nbsp;',
- 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( '&nbsp;', $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>&nbsp;' );
- $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 ) . "&nbsp;\n" .
- $this->getNamespaceMenu( $namespace ) . "&nbsp;\n" .
- $this->getTypeMenu( $type ) . "&nbsp;\n" .
- $this->getLevelMenu( $level ) . "<br/>\n" .
- $this->getSizeLimit( $sizetype, $size ) . "\n" .
- "&nbsp;" . 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' )
- . '&nbsp;'
- . 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 .= "&nbsp;".Xml::radio( 'sizetype', 'max', ($sizetype=='max'), array('id' => 'wpmax') );
- $out .= Xml::label( wfMsg("maximum-size"), 'wpmax' );
- $out .= "&nbsp;".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 ) . '&nbsp;' .
- 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 ) . '&nbsp;' .
- 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 ) . "&nbsp;\n" .
- $this->getNamespaceMenu( $namespace ) . "&nbsp;\n" .
- // $this->getLevelMenu( $level ) . "<br/>\n" .
- "&nbsp;" . 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' )
- . '&nbsp;'
- . 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 ) . '&nbsp;' .
- 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 .= " &nbsp;" ;
- $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("&lt; ".$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() .
- '&timestamp=' .
- 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>&nbsp;</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&timestamp=$ts" );
- if( ($remaining > 0) ||
- ($earliestLiveTime && $ts > $earliestLiveTime ) ) {
- $diffLink = '(' .
- $sk->makeKnownLinkObj( $titleObj,
- wfMsgHtml( 'diff' ),
- "target=$target&timestamp=$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>&nbsp;</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 = '&nbsp;';
- 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'>&nbsp;</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] = "&lt;{$tags[$i]}&gt;";
- $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' ) . '&nbsp;';
- $form .= Xml::namespaceSelector( $nameSpace, '' ) . '&nbsp;';
- $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 '&amp;' 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.