PageRenderTime 109ms CodeModel.GetById 19ms app.highlight 74ms RepoModel.GetById 1ms app.codeStats 1ms

/includes/GlobalFunctions.php

https://bitbucket.org/kgrashad/thawrapedia
PHP | 3619 lines | 2297 code | 285 blank | 1037 comment | 395 complexity | 5380fae10701aef4cdee98dc40ab1add MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * Global functions used everywhere
   4 * @file
   5 */
   6
   7if ( !defined( 'MEDIAWIKI' ) ) {
   8	die( "This file is part of MediaWiki, it is not a valid entry point" );
   9}
  10
  11require_once dirname( __FILE__ ) . '/normal/UtfNormalUtil.php';
  12
  13// Hide compatibility functions from Doxygen
  14/// @cond
  15
  16/**
  17 * Compatibility functions
  18 *
  19 * We support PHP 5.1.x and up.
  20 * Re-implementations of newer functions or functions in non-standard
  21 * PHP extensions may be included here.
  22 */
  23if( !function_exists( 'iconv' ) ) {
  24	# iconv support is not in the default configuration and so may not be present.
  25	# Assume will only ever use utf-8 and iso-8859-1.
  26	# This will *not* work in all circumstances.
  27	function iconv( $from, $to, $string ) {
  28		if ( substr( $to, -8 ) == '//IGNORE' ) {
  29			$to = substr( $to, 0, strlen( $to ) - 8 );
  30		}
  31		if( strcasecmp( $from, $to ) == 0 ) {
  32			return $string;
  33		}
  34		if( strcasecmp( $from, 'utf-8' ) == 0 ) {
  35			return utf8_decode( $string );
  36		}
  37		if( strcasecmp( $to, 'utf-8' ) == 0 ) {
  38			return utf8_encode( $string );
  39		}
  40		return $string;
  41	}
  42}
  43
  44if ( !function_exists( 'mb_substr' ) ) {
  45	/**
  46	 * Fallback implementation for mb_substr, hardcoded to UTF-8.
  47	 * Attempts to be at least _moderately_ efficient; best optimized
  48	 * for relatively small offset and count values -- about 5x slower
  49	 * than native mb_string in my testing.
  50	 *
  51	 * Larger offsets are still fairly efficient for Latin text, but
  52	 * can be up to 100x slower than native if the text is heavily
  53	 * multibyte and we have to slog through a few hundred kb.
  54	 */
  55	function mb_substr( $str, $start, $count='end' ) {
  56		if( $start != 0 ) {
  57			$split = mb_substr_split_unicode( $str, intval( $start ) );
  58			$str = substr( $str, $split );
  59		}
  60
  61		if( $count !== 'end' ) {
  62			$split = mb_substr_split_unicode( $str, intval( $count ) );
  63			$str = substr( $str, 0, $split );
  64		}
  65
  66		return $str;
  67	}
  68
  69	function mb_substr_split_unicode( $str, $splitPos ) {
  70		if( $splitPos == 0 ) {
  71			return 0;
  72		}
  73
  74		$byteLen = strlen( $str );
  75
  76		if( $splitPos > 0 ) {
  77			if( $splitPos > 256 ) {
  78				// Optimize large string offsets by skipping ahead N bytes.
  79				// This will cut out most of our slow time on Latin-based text,
  80				// and 1/2 to 1/3 on East European and Asian scripts.
  81				$bytePos = $splitPos;
  82				while ( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
  83					++$bytePos;
  84				}
  85				$charPos = mb_strlen( substr( $str, 0, $bytePos ) );
  86			} else {
  87				$charPos = 0;
  88				$bytePos = 0;
  89			}
  90
  91			while( $charPos++ < $splitPos ) {
  92				++$bytePos;
  93				// Move past any tail bytes
  94				while ( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
  95					++$bytePos;
  96				}
  97			}
  98		} else {
  99			$splitPosX = $splitPos + 1;
 100			$charPos = 0; // relative to end of string; we don't care about the actual char position here
 101			$bytePos = $byteLen;
 102			while( $bytePos > 0 && $charPos-- >= $splitPosX ) {
 103				--$bytePos;
 104				// Move past any tail bytes
 105				while ( $bytePos > 0 && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
 106					--$bytePos;
 107				}
 108			}
 109		}
 110
 111		return $bytePos;
 112	}
 113}
 114
 115if ( !function_exists( 'mb_strlen' ) ) {
 116	/**
 117	 * Fallback implementation of mb_strlen, hardcoded to UTF-8.
 118	 * @param string $str
 119	 * @param string $enc optional encoding; ignored
 120	 * @return int
 121	 */
 122	function mb_strlen( $str, $enc = '' ) {
 123		$counts = count_chars( $str );
 124		$total = 0;
 125
 126		// Count ASCII bytes
 127		for( $i = 0; $i < 0x80; $i++ ) {
 128			$total += $counts[$i];
 129		}
 130
 131		// Count multibyte sequence heads
 132		for( $i = 0xc0; $i < 0xff; $i++ ) {
 133			$total += $counts[$i];
 134		}
 135		return $total;
 136	}
 137}
 138
 139
 140if( !function_exists( 'mb_strpos' ) ) {
 141	/**
 142	 * Fallback implementation of mb_strpos, hardcoded to UTF-8.
 143	 * @param $haystack String
 144	 * @param $needle String
 145	 * @param $offset String: optional start position
 146	 * @param $encoding String: optional encoding; ignored
 147	 * @return int
 148	 */
 149	function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
 150		$needle = preg_quote( $needle, '/' );
 151
 152		$ar = array();
 153		preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
 154
 155		if( isset( $ar[0][1] ) ) {
 156			return $ar[0][1];
 157		} else {
 158			return false;
 159		}
 160	}
 161}
 162
 163if( !function_exists( 'mb_strrpos' ) ) {
 164	/**
 165	 * Fallback implementation of mb_strrpos, hardcoded to UTF-8.
 166	 * @param $haystack String
 167	 * @param $needle String
 168	 * @param $offset String: optional start position
 169	 * @param $encoding String: optional encoding; ignored
 170	 * @return int
 171	 */
 172	function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
 173		$needle = preg_quote( $needle, '/' );
 174
 175		$ar = array();
 176		preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
 177
 178		if( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
 179			isset( $ar[0][count( $ar[0] ) - 1][1] ) ) {
 180			return $ar[0][count( $ar[0] ) - 1][1];
 181		} else {
 182			return false;
 183		}
 184	}
 185}
 186
 187// Support for Wietse Venema's taint feature
 188if ( !function_exists( 'istainted' ) ) {
 189	function istainted( $var ) {
 190		return 0;
 191	}
 192	function taint( $var, $level = 0 ) {}
 193	function untaint( $var, $level = 0 ) {}
 194	define( 'TC_HTML', 1 );
 195	define( 'TC_SHELL', 1 );
 196	define( 'TC_MYSQL', 1 );
 197	define( 'TC_PCRE', 1 );
 198	define( 'TC_SELF', 1 );
 199}
 200
 201// array_fill_keys() was only added in 5.2, but people use it anyway
 202// add a back-compat layer for 5.1. See bug 27781
 203if( !function_exists( 'array_fill_keys' ) ) {
 204	function array_fill_keys( $keys, $value ) {
 205		return array_combine( $keys, array_fill( 0, count( $keys ), $value ) );
 206	}
 207}
 208
 209
 210/// @endcond
 211
 212
 213/**
 214 * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
 215 */
 216function wfArrayDiff2( $a, $b ) {
 217	return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
 218}
 219function wfArrayDiff2_cmp( $a, $b ) {
 220	if ( !is_array( $a ) ) {
 221		return strcmp( $a, $b );
 222	} elseif ( count( $a ) !== count( $b ) ) {
 223		return count( $a ) < count( $b ) ? -1 : 1;
 224	} else {
 225		reset( $a );
 226		reset( $b );
 227		while( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
 228			$cmp = strcmp( $valueA, $valueB );
 229			if ( $cmp !== 0 ) {
 230				return $cmp;
 231			}
 232		}
 233		return 0;
 234	}
 235}
 236
 237/**
 238 * Seed Mersenne Twister
 239 * No-op for compatibility; only necessary in PHP < 4.2.0
 240 * @deprecated. Remove in 1.18
 241 */
 242function wfSeedRandom() {
 243	wfDeprecated(__FUNCTION__);
 244}
 245
 246/**
 247 * Get a random decimal value between 0 and 1, in a way
 248 * not likely to give duplicate values for any realistic
 249 * number of articles.
 250 *
 251 * @return string
 252 */
 253function wfRandom() {
 254	# The maximum random value is "only" 2^31-1, so get two random
 255	# values to reduce the chance of dupes
 256	$max = mt_getrandmax() + 1;
 257	$rand = number_format( ( mt_rand() * $max + mt_rand() )
 258		/ $max / $max, 12, '.', '' );
 259	return $rand;
 260}
 261
 262/**
 263 * We want some things to be included as literal characters in our title URLs
 264 * for prettiness, which urlencode encodes by default.  According to RFC 1738,
 265 * all of the following should be safe:
 266 *
 267 * ;:@&=$-_.+!*'(),
 268 *
 269 * But + is not safe because it's used to indicate a space; &= are only safe in
 270 * paths and not in queries (and we don't distinguish here); ' seems kind of
 271 * scary; and urlencode() doesn't touch -_. to begin with.  Plus, although /
 272 * is reserved, we don't care.  So the list we unescape is:
 273 *
 274 * ;:@$!*(),/
 275 *
 276 * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
 277 * so no fancy : for IIS7.
 278 *
 279 * %2F in the page titles seems to fatally break for some reason.
 280 *
 281 * @param $s String:
 282 * @return string
 283*/
 284function wfUrlencode( $s ) {
 285	static $needle;
 286	if ( is_null( $needle ) ) {
 287		$needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
 288		if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) || ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false ) ) {
 289			$needle[] = '%3A';
 290		}
 291	}
 292
 293	$s = urlencode( $s );
 294	$s = str_ireplace(
 295		$needle,
 296		array( ';', '@', '$', '!', '*', '(', ')', ',', '/', ':' ),
 297		$s
 298	);
 299
 300	return $s;
 301}
 302
 303/**
 304 * Sends a line to the debug log if enabled or, optionally, to a comment in output.
 305 * In normal operation this is a NOP.
 306 *
 307 * Controlling globals:
 308 * $wgDebugLogFile - points to the log file
 309 * $wgProfileOnly - if set, normal debug messages will not be recorded.
 310 * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
 311 * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
 312 *
 313 * @param $text String
 314 * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
 315 */
 316function wfDebug( $text, $logonly = false ) {
 317	global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
 318	global $wgDebugLogPrefix, $wgShowDebug;
 319	static $recursion = 0;
 320
 321	static $cache = array(); // Cache of unoutputted messages
 322	$text = wfDebugTimer() . $text;
 323
 324	# Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
 325	if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
 326		return;
 327	}
 328
 329	if ( ( $wgDebugComments || $wgShowDebug ) && !$logonly ) {
 330		$cache[] = $text;
 331
 332		if ( !isset( $wgOut ) ) {
 333			return;
 334		}
 335		if ( !StubObject::isRealObject( $wgOut ) ) {
 336			if ( $recursion ) {
 337				return;
 338			}
 339			$recursion++;
 340			$wgOut->_unstub();
 341			$recursion--;
 342		}
 343
 344		// add the message and possible cached ones to the output
 345		array_map( array( $wgOut, 'debug' ), $cache );
 346		$cache = array();
 347	}
 348	if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
 349		# Strip unprintables; they can switch terminal modes when binary data
 350		# gets dumped, which is pretty annoying.
 351		$text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
 352		$text = $wgDebugLogPrefix . $text;
 353		wfErrorLog( $text, $wgDebugLogFile );
 354	}
 355}
 356
 357function wfDebugTimer() {
 358	global $wgDebugTimestamps;
 359	if ( !$wgDebugTimestamps ) {
 360		return '';
 361	}
 362	static $start = null;
 363
 364	if ( $start === null ) {
 365		$start = microtime( true );
 366		$prefix = "\n$start";
 367	} else {
 368		$prefix = sprintf( "%6.4f", microtime( true ) - $start );
 369	}
 370
 371	return $prefix . '  ';
 372}
 373
 374/**
 375 * Send a line giving PHP memory usage.
 376 * @param $exact Bool: print exact values instead of kilobytes (default: false)
 377 */
 378function wfDebugMem( $exact = false ) {
 379	$mem = memory_get_usage();
 380	if( !$exact ) {
 381		$mem = floor( $mem / 1024 ) . ' kilobytes';
 382	} else {
 383		$mem .= ' bytes';
 384	}
 385	wfDebug( "Memory usage: $mem\n" );
 386}
 387
 388/**
 389 * Send a line to a supplementary debug log file, if configured, or main debug log if not.
 390 * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
 391 *
 392 * @param $logGroup String
 393 * @param $text String
 394 * @param $public Bool: whether to log the event in the public log if no private
 395 *                     log file is specified, (default true)
 396 */
 397function wfDebugLog( $logGroup, $text, $public = true ) {
 398	global $wgDebugLogGroups, $wgShowHostnames;
 399	$text = trim( $text ) . "\n";
 400	if( isset( $wgDebugLogGroups[$logGroup] ) ) {
 401		$time = wfTimestamp( TS_DB );
 402		$wiki = wfWikiID();
 403		if ( $wgShowHostnames ) {
 404			$host = wfHostname();
 405		} else {
 406			$host = '';
 407		}
 408		wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
 409	} elseif ( $public === true ) {
 410		wfDebug( $text, true );
 411	}
 412}
 413
 414/**
 415 * Log for database errors
 416 * @param $text String: database error message.
 417 */
 418function wfLogDBError( $text ) {
 419	global $wgDBerrorLog, $wgDBname;
 420	if ( $wgDBerrorLog ) {
 421		$host = trim(`hostname`);
 422		$text = date( 'D M j G:i:s T Y' ) . "\t$host\t$wgDBname\t$text";
 423		wfErrorLog( $text, $wgDBerrorLog );
 424	}
 425}
 426
 427/**
 428 * Log to a file without getting "file size exceeded" signals.
 429 *
 430 * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
 431 * send lines to the specified port, prefixed by the specified prefix and a space.
 432 */
 433function wfErrorLog( $text, $file ) {
 434	if ( substr( $file, 0, 4 ) == 'udp:' ) {
 435		# Needs the sockets extension
 436		if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
 437			// IPv6 bracketed host
 438			$host = $m[2];
 439			$port = intval( $m[3] );
 440			$prefix = isset( $m[4] ) ? $m[4] : false;
 441			$domain = AF_INET6;
 442		} elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
 443			$host = $m[2];
 444			if ( !IP::isIPv4( $host ) ) {
 445				$host = gethostbyname( $host );
 446			}
 447			$port = intval( $m[3] );
 448			$prefix = isset( $m[4] ) ? $m[4] : false;
 449			$domain = AF_INET;
 450		} else {
 451			throw new MWException( __METHOD__ . ': Invalid UDP specification' );
 452		}
 453		// Clean it up for the multiplexer
 454		if ( strval( $prefix ) !== '' ) {
 455			$text = preg_replace( '/^/m', $prefix . ' ', $text );
 456			if ( substr( $text, -1 ) != "\n" ) {
 457				$text .= "\n";
 458			}
 459		}
 460
 461		$sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
 462		if ( !$sock ) {
 463			return;
 464		}
 465		socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
 466		socket_close( $sock );
 467	} else {
 468		wfSuppressWarnings();
 469		$exists = file_exists( $file );
 470		$size = $exists ? filesize( $file ) : false;
 471		if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
 472			error_log( $text, 3, $file );
 473		}
 474		wfRestoreWarnings();
 475	}
 476}
 477
 478/**
 479 * @todo document
 480 */
 481function wfLogProfilingData() {
 482	global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
 483	global $wgProfiler, $wgProfileLimit, $wgUser;
 484	# Profiling must actually be enabled...
 485	if( is_null( $wgProfiler ) ) {
 486		return;
 487	}
 488	# Get total page request time
 489	$now = wfTime();
 490	$elapsed = $now - $wgRequestTime;
 491	# Only show pages that longer than $wgProfileLimit time (default is 0)
 492	if( $elapsed <= $wgProfileLimit ) {
 493		return;
 494	}
 495	$prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
 496	$forward = '';
 497	if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
 498		$forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
 499	}
 500	if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
 501		$forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
 502	}
 503	if( !empty( $_SERVER['HTTP_FROM'] ) ) {
 504		$forward .= ' from ' . $_SERVER['HTTP_FROM'];
 505	}
 506	if( $forward ) {
 507		$forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
 508	}
 509	// Don't unstub $wgUser at this late stage just for statistics purposes
 510	// FIXME: We can detect some anons even if it is not loaded. See User::getId()
 511	if( $wgUser->mDataLoaded && $wgUser->isAnon() ) {
 512		$forward .= ' anon';
 513	}
 514	$log = sprintf( "%s\t%04.3f\t%s\n",
 515		gmdate( 'YmdHis' ), $elapsed,
 516		urldecode( $wgRequest->getRequestURL() . $forward ) );
 517	if ( $wgDebugLogFile != '' && ( $wgRequest->getVal( 'action' ) != 'raw' || $wgDebugRawPage ) ) {
 518		wfErrorLog( $log . $prof, $wgDebugLogFile );
 519	}
 520}
 521
 522/**
 523 * Check if the wiki read-only lock file is present. This can be used to lock
 524 * off editing functions, but doesn't guarantee that the database will not be
 525 * modified.
 526 * @return bool
 527 */
 528function wfReadOnly() {
 529	global $wgReadOnlyFile, $wgReadOnly;
 530
 531	if ( !is_null( $wgReadOnly ) ) {
 532		return (bool)$wgReadOnly;
 533	}
 534	if ( $wgReadOnlyFile == '' ) {
 535		return false;
 536	}
 537	// Set $wgReadOnly for faster access next time
 538	if ( is_file( $wgReadOnlyFile ) ) {
 539		$wgReadOnly = file_get_contents( $wgReadOnlyFile );
 540	} else {
 541		$wgReadOnly = false;
 542	}
 543	return (bool)$wgReadOnly;
 544}
 545
 546function wfReadOnlyReason() {
 547	global $wgReadOnly;
 548	wfReadOnly();
 549	return $wgReadOnly;
 550}
 551
 552/**
 553 * Return a Language object from $langcode
 554 * @param $langcode Mixed: either:
 555 *                  - a Language object
 556 *                  - code of the language to get the message for, if it is
 557 *                    a valid code create a language for that language, if
 558 *                    it is a string but not a valid code then make a basic
 559 *                    language object
 560 *                  - a boolean: if it's false then use the current users
 561 *                    language (as a fallback for the old parameter
 562 *                    functionality), or if it is true then use the wikis
 563 * @return Language object
 564 */
 565function wfGetLangObj( $langcode = false ) {
 566	# Identify which language to get or create a language object for.
 567	# Using is_object here due to Stub objects.
 568	if( is_object( $langcode ) ) {
 569		# Great, we already have the object (hopefully)!
 570		return $langcode;
 571	}
 572
 573	global $wgContLang, $wgLanguageCode;
 574	if( $langcode === true || $langcode === $wgLanguageCode ) {
 575		# $langcode is the language code of the wikis content language object.
 576		# or it is a boolean and value is true
 577		return $wgContLang;
 578	}
 579
 580	global $wgLang;
 581	if( $langcode === false || $langcode === $wgLang->getCode() ) {
 582		# $langcode is the language code of user language object.
 583		# or it was a boolean and value is false
 584		return $wgLang;
 585	}
 586
 587	$validCodes = array_keys( Language::getLanguageNames() );
 588	if( in_array( $langcode, $validCodes ) ) {
 589		# $langcode corresponds to a valid language.
 590		return Language::factory( $langcode );
 591	}
 592
 593	# $langcode is a string, but not a valid language code; use content language.
 594	wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
 595	return $wgContLang;
 596}
 597
 598/**
 599 * Use this instead of $wgContLang, when working with user interface.
 600 * User interface is currently hard coded according to wiki content language
 601 * in many ways, especially regarding to text direction. There is lots stuff
 602 * to fix, hence this function to keep the old behaviour unless the global
 603 * $wgBetterDirectionality is enabled (or removed when everything works).
 604 */
 605function wfUILang() {
 606	global $wgBetterDirectionality;
 607	return wfGetLangObj( !$wgBetterDirectionality );
 608}
 609
 610/**
 611 * This is the new function for getting translated interface messages.
 612 * See the Message class for documentation how to use them.
 613 * The intention is that this function replaces all old wfMsg* functions.
 614 * @param $key \string Message key.
 615 * Varargs: normal message parameters.
 616 * @return \type{Message}
 617 * @since 1.17
 618 */
 619function wfMessage( $key /*...*/) {
 620	$params = func_get_args();
 621	array_shift( $params );
 622	if ( isset( $params[0] ) && is_array( $params[0] ) ) {
 623		$params = $params[0];
 624	}
 625	return new Message( $key, $params );
 626}
 627
 628/**
 629 * Get a message from anywhere, for the current user language.
 630 *
 631 * Use wfMsgForContent() instead if the message should NOT
 632 * change depending on the user preferences.
 633 *
 634 * @param $key String: lookup key for the message, usually
 635 *    defined in languages/Language.php
 636 *
 637 * This function also takes extra optional parameters (not
 638 * shown in the function definition), which can be used to
 639 * insert variable text into the predefined message.
 640 */
 641function wfMsg( $key ) {
 642	$args = func_get_args();
 643	array_shift( $args );
 644	return wfMsgReal( $key, $args, true );
 645}
 646
 647/**
 648 * Same as above except doesn't transform the message
 649 */
 650function wfMsgNoTrans( $key ) {
 651	$args = func_get_args();
 652	array_shift( $args );
 653	return wfMsgReal( $key, $args, true, false, false );
 654}
 655
 656/**
 657 * Get a message from anywhere, for the current global language
 658 * set with $wgLanguageCode.
 659 *
 660 * Use this if the message should NOT change dependent on the
 661 * language set in the user's preferences. This is the case for
 662 * most text written into logs, as well as link targets (such as
 663 * the name of the copyright policy page). Link titles, on the
 664 * other hand, should be shown in the UI language.
 665 *
 666 * Note that MediaWiki allows users to change the user interface
 667 * language in their preferences, but a single installation
 668 * typically only contains content in one language.
 669 *
 670 * Be wary of this distinction: If you use wfMsg() where you should
 671 * use wfMsgForContent(), a user of the software may have to
 672 * customize potentially hundreds of messages in
 673 * order to, e.g., fix a link in every possible language.
 674 *
 675 * @param $key String: lookup key for the message, usually
 676 *    defined in languages/Language.php
 677 */
 678function wfMsgForContent( $key ) {
 679	global $wgForceUIMsgAsContentMsg;
 680	$args = func_get_args();
 681	array_shift( $args );
 682	$forcontent = true;
 683	if( is_array( $wgForceUIMsgAsContentMsg ) &&
 684		in_array( $key, $wgForceUIMsgAsContentMsg ) )
 685	{
 686		$forcontent = false;
 687	}
 688	return wfMsgReal( $key, $args, true, $forcontent );
 689}
 690
 691/**
 692 * Same as above except doesn't transform the message
 693 */
 694function wfMsgForContentNoTrans( $key ) {
 695	global $wgForceUIMsgAsContentMsg;
 696	$args = func_get_args();
 697	array_shift( $args );
 698	$forcontent = true;
 699	if( is_array( $wgForceUIMsgAsContentMsg ) &&
 700		in_array( $key, $wgForceUIMsgAsContentMsg ) )
 701	{
 702		$forcontent = false;
 703	}
 704	return wfMsgReal( $key, $args, true, $forcontent, false );
 705}
 706
 707/**
 708 * Get a message from the language file, for the UI elements
 709 */
 710function wfMsgNoDB( $key ) {
 711	$args = func_get_args();
 712	array_shift( $args );
 713	return wfMsgReal( $key, $args, false );
 714}
 715
 716/**
 717 * Get a message from the language file, for the content
 718 */
 719function wfMsgNoDBForContent( $key ) {
 720	global $wgForceUIMsgAsContentMsg;
 721	$args = func_get_args();
 722	array_shift( $args );
 723	$forcontent = true;
 724	if( is_array( $wgForceUIMsgAsContentMsg ) &&
 725		in_array( $key,	$wgForceUIMsgAsContentMsg ) )
 726	{
 727		$forcontent = false;
 728	}
 729	return wfMsgReal( $key, $args, false, $forcontent );
 730}
 731
 732
 733/**
 734 * Really get a message
 735 * @param $key String: key to get.
 736 * @param $args
 737 * @param $useDB Boolean
 738 * @param $forContent Mixed: Language code, or false for user lang, true for content lang.
 739 * @param $transform Boolean: Whether or not to transform the message.
 740 * @return String: the requested message.
 741 */
 742function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
 743	wfProfileIn( __METHOD__ );
 744	$message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
 745	$message = wfMsgReplaceArgs( $message, $args );
 746	wfProfileOut( __METHOD__ );
 747	return $message;
 748}
 749
 750/**
 751 * This function provides the message source for messages to be edited which are *not* stored in the database.
 752 * @param $key String:
 753 */
 754function wfMsgWeirdKey( $key ) {
 755	$source = wfMsgGetKey( $key, false, true, false );
 756	if ( wfEmptyMsg( $key, $source ) ) {
 757		return '';
 758	} else {
 759		return $source;
 760	}
 761}
 762
 763/**
 764 * Fetch a message string value, but don't replace any keys yet.
 765 * @param $key String
 766 * @param $useDB Bool
 767 * @param $langCode String: Code of the language to get the message for, or
 768 *                  behaves as a content language switch if it is a boolean.
 769 * @param $transform Boolean: whether to parse magic words, etc.
 770 * @return string
 771 */
 772function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
 773	global $wgMessageCache;
 774
 775	wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
 776
 777	if ( !is_object( $wgMessageCache ) ) {
 778		throw new MWException( 'Trying to get message before message cache is initialised' );
 779	}
 780
 781	$message = $wgMessageCache->get( $key, $useDB, $langCode );
 782	if( $message === false ) {
 783		$message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
 784	} elseif ( $transform ) {
 785		$message = $wgMessageCache->transform( $message );
 786	}
 787	return $message;
 788}
 789
 790/**
 791 * Replace message parameter keys on the given formatted output.
 792 *
 793 * @param $message String
 794 * @param $args Array
 795 * @return string
 796 * @private
 797 */
 798function wfMsgReplaceArgs( $message, $args ) {
 799	# Fix windows line-endings
 800	# Some messages are split with explode("\n", $msg)
 801	$message = str_replace( "\r", '', $message );
 802
 803	// Replace arguments
 804	if ( count( $args ) ) {
 805		if ( is_array( $args[0] ) ) {
 806			$args = array_values( $args[0] );
 807		}
 808		$replacementKeys = array();
 809		foreach( $args as $n => $param ) {
 810			$replacementKeys['$' . ( $n + 1 )] = $param;
 811		}
 812		$message = strtr( $message, $replacementKeys );
 813	}
 814
 815	return $message;
 816}
 817
 818/**
 819 * Return an HTML-escaped version of a message.
 820 * Parameter replacements, if any, are done *after* the HTML-escaping,
 821 * so parameters may contain HTML (eg links or form controls). Be sure
 822 * to pre-escape them if you really do want plaintext, or just wrap
 823 * the whole thing in htmlspecialchars().
 824 *
 825 * @param $key String
 826 * @param string ... parameters
 827 * @return string
 828 */
 829function wfMsgHtml( $key ) {
 830	$args = func_get_args();
 831	array_shift( $args );
 832	return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
 833}
 834
 835/**
 836 * Return an HTML version of message
 837 * Parameter replacements, if any, are done *after* parsing the wiki-text message,
 838 * so parameters may contain HTML (eg links or form controls). Be sure
 839 * to pre-escape them if you really do want plaintext, or just wrap
 840 * the whole thing in htmlspecialchars().
 841 *
 842 * @param $key String
 843 * @param string ... parameters
 844 * @return string
 845 */
 846function wfMsgWikiHtml( $key ) {
 847	global $wgOut;
 848	$args = func_get_args();
 849	array_shift( $args );
 850	return wfMsgReplaceArgs( $wgOut->parse( wfMsgGetKey( $key, true ), /* can't be set to false */ true ), $args );
 851}
 852
 853/**
 854 * Returns message in the requested format
 855 * @param $key String: key of the message
 856 * @param $options Array: processing rules. Can take the following options:
 857 *   <i>parse</i>: parses wikitext to HTML
 858 *   <i>parseinline</i>: parses wikitext to HTML and removes the surrounding
 859 *       p's added by parser or tidy
 860 *   <i>escape</i>: filters message through htmlspecialchars
 861 *   <i>escapenoentities</i>: same, but allows entity references like &#160; through
 862 *   <i>replaceafter</i>: parameters are substituted after parsing or escaping
 863 *   <i>parsemag</i>: transform the message using magic phrases
 864 *   <i>content</i>: fetch message for content language instead of interface
 865 * Also can accept a single associative argument, of the form 'language' => 'xx':
 866 *   <i>language</i>: Language object or language code to fetch message for
 867 *       (overriden by <i>content</i>), its behaviour with parse, parseinline
 868 *       and parsemag is undefined.
 869 * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
 870 */
 871function wfMsgExt( $key, $options ) {
 872	global $wgOut;
 873
 874	$args = func_get_args();
 875	array_shift( $args );
 876	array_shift( $args );
 877	$options = (array)$options;
 878
 879	foreach( $options as $arrayKey => $option ) {
 880		if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
 881			# An unknown index, neither numeric nor "language"
 882			wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING );
 883		} elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
 884		array( 'parse', 'parseinline', 'escape', 'escapenoentities',
 885		'replaceafter', 'parsemag', 'content' ) ) ) {
 886			# A numeric index with unknown value
 887			wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING );
 888		}
 889	}
 890
 891	if( in_array( 'content', $options, true ) ) {
 892		$forContent = true;
 893		$langCode = true;
 894	} elseif( array_key_exists( 'language', $options ) ) {
 895		$forContent = false;
 896		$langCode = wfGetLangObj( $options['language'] );
 897	} else {
 898		$forContent = false;
 899		$langCode = false;
 900	}
 901
 902	$string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
 903
 904	if( !in_array( 'replaceafter', $options, true ) ) {
 905		$string = wfMsgReplaceArgs( $string, $args );
 906	}
 907
 908	if( in_array( 'parse', $options, true ) ) {
 909		$string = $wgOut->parse( $string, true, !$forContent );
 910	} elseif ( in_array( 'parseinline', $options, true ) ) {
 911		$string = $wgOut->parse( $string, true, !$forContent );
 912		$m = array();
 913		if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
 914			$string = $m[1];
 915		}
 916	} elseif ( in_array( 'parsemag', $options, true ) ) {
 917		global $wgMessageCache;
 918		if ( isset( $wgMessageCache ) ) {
 919			$string = $wgMessageCache->transform( $string,
 920				!$forContent,
 921				is_object( $langCode ) ? $langCode : null );
 922		}
 923	}
 924
 925	if ( in_array( 'escape', $options, true ) ) {
 926		$string = htmlspecialchars ( $string );
 927	} elseif ( in_array( 'escapenoentities', $options, true ) ) {
 928		$string = Sanitizer::escapeHtmlAllowEntities( $string );
 929	}
 930
 931	if( in_array( 'replaceafter', $options, true ) ) {
 932		$string = wfMsgReplaceArgs( $string, $args );
 933	}
 934
 935	return $string;
 936}
 937
 938
 939/**
 940 * Just like exit() but makes a note of it.
 941 * Commits open transactions except if the error parameter is set
 942 *
 943 * @deprecated Please return control to the caller or throw an exception. Will
 944 *             be removed in 1.19.
 945 */
 946function wfAbruptExit( $error = false ) {
 947	static $called = false;
 948	if ( $called ) {
 949		exit( -1 );
 950	}
 951	$called = true;
 952
 953	wfDeprecated( __FUNCTION__ );
 954	$bt = wfDebugBacktrace();
 955	if( $bt ) {
 956		for( $i = 0; $i < count( $bt ); $i++ ) {
 957			$file = isset( $bt[$i]['file'] ) ? $bt[$i]['file'] : 'unknown';
 958			$line = isset( $bt[$i]['line'] ) ? $bt[$i]['line'] : 'unknown';
 959			wfDebug( "WARNING: Abrupt exit in $file at line $line\n");
 960		}
 961	} else {
 962		wfDebug( "WARNING: Abrupt exit\n" );
 963	}
 964
 965	wfLogProfilingData();
 966
 967	if ( !$error ) {
 968		wfGetLB()->closeAll();
 969	}
 970	exit( -1 );
 971}
 972
 973/**
 974 * @deprecated Please return control the caller or throw an exception. Will
 975 *             be removed in 1.19.
 976 */
 977function wfErrorExit() {
 978	wfDeprecated( __FUNCTION__ );
 979	wfAbruptExit( true );
 980}
 981
 982/**
 983 * Print a simple message and die, returning nonzero to the shell if any.
 984 * Plain die() fails to return nonzero to the shell if you pass a string.
 985 * @param $msg String
 986 */
 987function wfDie( $msg = '' ) {
 988	echo $msg;
 989	die( 1 );
 990}
 991
 992/**
 993 * Throw a debugging exception. This function previously once exited the process,
 994 * but now throws an exception instead, with similar results.
 995 *
 996 * @param $msg String: message shown when dieing.
 997 */
 998function wfDebugDieBacktrace( $msg = '' ) {
 999	throw new MWException( $msg );
1000}
1001
1002/**
1003 * Fetch server name for use in error reporting etc.
1004 * Use real server name if available, so we know which machine
1005 * in a server farm generated the current page.
1006 * @return string
1007 */
1008function wfHostname() {
1009	static $host;
1010	if ( is_null( $host ) ) {
1011		if ( function_exists( 'posix_uname' ) ) {
1012			// This function not present on Windows
1013			$uname = @posix_uname();
1014		} else {
1015			$uname = false;
1016		}
1017		if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
1018			$host = $uname['nodename'];
1019		} elseif ( getenv( 'COMPUTERNAME' ) ) {
1020			# Windows computer name
1021			$host = getenv( 'COMPUTERNAME' );
1022		} else {
1023			# This may be a virtual server.
1024			$host = $_SERVER['SERVER_NAME'];
1025		}
1026	}
1027	return $host;
1028}
1029
1030/**
1031 * Returns a HTML comment with the elapsed time since request.
1032 * This method has no side effects.
1033 * @return string
1034 */
1035function wfReportTime() {
1036	global $wgRequestTime, $wgShowHostnames;
1037
1038	$now = wfTime();
1039	$elapsed = $now - $wgRequestTime;
1040
1041	return $wgShowHostnames
1042		? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
1043		: sprintf( '<!-- Served in %01.3f secs. -->', $elapsed );
1044}
1045
1046/**
1047 * Safety wrapper for debug_backtrace().
1048 *
1049 * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
1050 * murky circumstances, which may be triggered in part by stub objects
1051 * or other fancy talkin'.
1052 *
1053 * Will return an empty array if Zend Optimizer is detected or if
1054 * debug_backtrace is disabled, otherwise the output from
1055 * debug_backtrace() (trimmed).
1056 *
1057 * @return array of backtrace information
1058 */
1059function wfDebugBacktrace() {
1060	static $disabled = null;
1061
1062	if( extension_loaded( 'Zend Optimizer' ) ) {
1063		wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
1064		return array();
1065	}
1066
1067	if ( is_null( $disabled ) ) {
1068		$disabled = false;
1069		$functions = explode( ',', ini_get( 'disable_functions' ) );
1070		$functions = array_map( 'trim', $functions );
1071		$functions = array_map( 'strtolower', $functions );
1072		if ( in_array( 'debug_backtrace', $functions ) ) {
1073			wfDebug( "debug_backtrace is in disabled_functions\n" );
1074			$disabled = true;
1075		}
1076	}
1077	if ( $disabled ) {
1078		return array();
1079	}
1080
1081	return array_slice( debug_backtrace(), 1 );
1082}
1083
1084function wfBacktrace() {
1085	global $wgCommandLineMode;
1086
1087	if ( $wgCommandLineMode ) {
1088		$msg = '';
1089	} else {
1090		$msg = "<ul>\n";
1091	}
1092	$backtrace = wfDebugBacktrace();
1093	foreach( $backtrace as $call ) {
1094		if( isset( $call['file'] ) ) {
1095			$f = explode( DIRECTORY_SEPARATOR, $call['file'] );
1096			$file = $f[count( $f ) - 1];
1097		} else {
1098			$file = '-';
1099		}
1100		if( isset( $call['line'] ) ) {
1101			$line = $call['line'];
1102		} else {
1103			$line = '-';
1104		}
1105		if ( $wgCommandLineMode ) {
1106			$msg .= "$file line $line calls ";
1107		} else {
1108			$msg .= '<li>' . $file . ' line ' . $line . ' calls ';
1109		}
1110		if( !empty( $call['class'] ) ) {
1111			$msg .= $call['class'] . '::';
1112		}
1113		$msg .= $call['function'] . '()';
1114
1115		if ( $wgCommandLineMode ) {
1116			$msg .= "\n";
1117		} else {
1118			$msg .= "</li>\n";
1119		}
1120	}
1121	if ( $wgCommandLineMode ) {
1122		$msg .= "\n";
1123	} else {
1124		$msg .= "</ul>\n";
1125	}
1126
1127	return $msg;
1128}
1129
1130
1131/* Some generic result counters, pulled out of SearchEngine */
1132
1133
1134/**
1135 * @todo document
1136 */
1137function wfShowingResults( $offset, $limit ) {
1138	global $wgLang;
1139	return wfMsgExt(
1140		'showingresults',
1141		array( 'parseinline' ),
1142		$wgLang->formatNum( $limit ),
1143		$wgLang->formatNum( $offset + 1 )
1144	);
1145}
1146
1147/**
1148 * @todo document
1149 */
1150function wfShowingResultsNum( $offset, $limit, $num ) {
1151	global $wgLang;
1152	return wfMsgExt(
1153		'showingresultsnum',
1154		array( 'parseinline' ),
1155		$wgLang->formatNum( $limit ),
1156		$wgLang->formatNum( $offset + 1 ),
1157		$wgLang->formatNum( $num )
1158	);
1159}
1160
1161/**
1162 * Generate (prev x| next x) (20|50|100...) type links for paging
1163 * @param $offset String
1164 * @param $limit Integer
1165 * @param $link String
1166 * @param $query String: optional URL query parameter string
1167 * @param $atend Bool: optional param for specified if this is the last page
1168 */
1169function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
1170	global $wgLang;
1171	$fmtLimit = $wgLang->formatNum( $limit );
1172	// FIXME: Why on earth this needs one message for the text and another one for tooltip??
1173	# Get prev/next link display text
1174	$prev = wfMsgExt( 'prevn', array( 'parsemag', 'escape' ), $fmtLimit );
1175	$next = wfMsgExt( 'nextn', array( 'parsemag', 'escape' ), $fmtLimit );
1176	# Get prev/next link title text
1177	$pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit );
1178	$nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit );
1179	# Fetch the title object
1180	if( is_object( $link ) ) {
1181		$title =& $link;
1182	} else {
1183		$title = Title::newFromText( $link );
1184		if( is_null( $title ) ) {
1185			return false;
1186		}
1187	}
1188	# Make 'previous' link
1189	if( 0 != $offset ) {
1190		$po = $offset - $limit;
1191		$po = max( $po, 0 );
1192		$q = "limit={$limit}&offset={$po}";
1193		if( $query != '' ) {
1194			$q .= '&' . $query;
1195		}
1196		$plink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$pTitle}\" class=\"mw-prevlink\">{$prev}</a>";
1197	} else {
1198		$plink = $prev;
1199	}
1200	# Make 'next' link
1201	$no = $offset + $limit;
1202	$q = "limit={$limit}&offset={$no}";
1203	if( $query != '' ) {
1204		$q .= '&' . $query;
1205	}
1206	if( $atend ) {
1207		$nlink = $next;
1208	} else {
1209		$nlink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$nTitle}\" class=\"mw-nextlink\">{$next}</a>";
1210	}
1211	# Make links to set number of items per page
1212	$nums = $wgLang->pipeList( array(
1213		wfNumLink( $offset, 20, $title, $query ),
1214		wfNumLink( $offset, 50, $title, $query ),
1215		wfNumLink( $offset, 100, $title, $query ),
1216		wfNumLink( $offset, 250, $title, $query ),
1217		wfNumLink( $offset, 500, $title, $query )
1218	) );
1219	return wfMsgHtml( 'viewprevnext', $plink, $nlink, $nums );
1220}
1221
1222/**
1223 * Generate links for (20|50|100...) items-per-page links
1224 * @param $offset String
1225 * @param $limit Integer
1226 * @param $title Title
1227 * @param $query String: optional URL query parameter string
1228 */
1229function wfNumLink( $offset, $limit, $title, $query = '' ) {
1230	global $wgLang;
1231	if( $query == '' ) {
1232		$q = '';
1233	} else {
1234		$q = $query.'&';
1235	}
1236	$q .= "limit={$limit}&offset={$offset}";
1237	$fmtLimit = $wgLang->formatNum( $limit );
1238	$lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $limit );
1239	$s = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>";
1240	return $s;
1241}
1242
1243/**
1244 * @todo document
1245 * @todo FIXME: we may want to blacklist some broken browsers
1246 *
1247 * @return bool Whereas client accept gzip compression
1248 */
1249function wfClientAcceptsGzip() {
1250	static $result = null;
1251	if ( $result === null ) {
1252		$result = false;
1253		if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
1254			# FIXME: we may want to blacklist some broken browsers
1255			$m = array();
1256			if( preg_match(
1257				'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
1258				$_SERVER['HTTP_ACCEPT_ENCODING'],
1259				$m )
1260			)
1261			{
1262				if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
1263					$result = false;
1264					return $result;
1265				}
1266				wfDebug( " accepts gzip\n" );
1267				$result = true;
1268			}
1269		}
1270	}
1271	return $result;
1272}
1273
1274/**
1275 * Obtain the offset and limit values from the request string;
1276 * used in special pages
1277 *
1278 * @param $deflimit Default limit if none supplied
1279 * @param $optionname Name of a user preference to check against
1280 * @return array
1281 *
1282 */
1283function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
1284	global $wgRequest;
1285	return $wgRequest->getLimitOffset( $deflimit, $optionname );
1286}
1287
1288/**
1289 * Escapes the given text so that it may be output using addWikiText()
1290 * without any linking, formatting, etc. making its way through. This
1291 * is achieved by substituting certain characters with HTML entities.
1292 * As required by the callers, <nowiki> is not used. It currently does
1293 * not filter out characters which have special meaning only at the
1294 * start of a line, such as "*".
1295 *
1296 * @param $text String: text to be escaped
1297 */
1298function wfEscapeWikiText( $text ) {
1299	$text = str_replace(
1300		array( '[',     '|',      ']',     '\'',    'ISBN ',
1301			'RFC ',     '://',     "\n=",     '{{',           '}}' ),
1302		array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;',
1303			'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;', '&#125;&#125;' ),
1304		htmlspecialchars( $text )
1305	);
1306	return $text;
1307}
1308
1309/**
1310 * @todo document
1311 */
1312function wfQuotedPrintable( $string, $charset = '' ) {
1313	# Probably incomplete; see RFC 2045
1314	if( empty( $charset ) ) {
1315		global $wgInputEncoding;
1316		$charset = $wgInputEncoding;
1317	}
1318	$charset = strtoupper( $charset );
1319	$charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
1320
1321	$illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
1322	$replace = $illegal . '\t ?_';
1323	if( !preg_match( "/[$illegal]/", $string ) ) {
1324		return $string;
1325	}
1326	$out = "=?$charset?Q?";
1327	$out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
1328	$out .= '?=';
1329	return $out;
1330}
1331
1332
1333/**
1334 * @todo document
1335 * @return float
1336 */
1337function wfTime() {
1338	return microtime( true );
1339}
1340
1341/**
1342 * Sets dest to source and returns the original value of dest
1343 * If source is NULL, it just returns the value, it doesn't set the variable
1344 */
1345function wfSetVar( &$dest, $source ) {
1346	$temp = $dest;
1347	if ( !is_null( $source ) ) {
1348		$dest = $source;
1349	}
1350	return $temp;
1351}
1352
1353/**
1354 * As for wfSetVar except setting a bit
1355 */
1356function wfSetBit( &$dest, $bit, $state = true ) {
1357	$temp = (bool)( $dest & $bit );
1358	if ( !is_null( $state ) ) {
1359		if ( $state ) {
1360			$dest |= $bit;
1361		} else {
1362			$dest &= ~$bit;
1363		}
1364	}
1365	return $temp;
1366}
1367
1368/**
1369 * This function takes two arrays as input, and returns a CGI-style string, e.g.
1370 * "days=7&limit=100". Options in the first array override options in the second.
1371 * Options set to "" will not be output.
1372 */
1373function wfArrayToCGI( $array1, $array2 = null ) {
1374	if ( !is_null( $array2 ) ) {
1375		$array1 = $array1 + $array2;
1376	}
1377
1378	$cgi = '';
1379	foreach ( $array1 as $key => $value ) {
1380		if ( $value !== '' ) {
1381			if ( $cgi != '' ) {
1382				$cgi .= '&';
1383			}
1384			if ( is_array( $value ) ) {
1385				$firstTime = true;
1386				foreach ( $value as $v ) {
1387					$cgi .= ( $firstTime ? '' : '&') .
1388						urlencode( $key . '[]' ) . '=' .
1389						urlencode( $v );
1390					$firstTime = false;
1391				}
1392			} else {
1393				if ( is_object( $value ) ) {
1394					$value = $value->__toString();
1395				}
1396				$cgi .= urlencode( $key ) . '=' .
1397					urlencode( $value );
1398			}
1399		}
1400	}
1401	return $cgi;
1402}
1403
1404/**
1405 * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
1406 * its argument and returns the same string in array form.  This allows compa-
1407 * tibility with legacy functions that accept raw query strings instead of nice
1408 * arrays.  Of course, keys and values are urldecode()d.  Don't try passing in-
1409 * valid query strings, or it will explode.
1410 *
1411 * @param $query String: query string
1412 * @return array Array version of input
1413 */
1414function wfCgiToArray( $query ) {
1415	if( isset( $query[0] ) && $query[0] == '?' ) {
1416		$query = substr( $query, 1 );
1417	}
1418	$bits = explode( '&', $query );
1419	$ret = array();
1420	foreach( $bits as $bit ) {
1421		if( $bit === '' ) {
1422			continue;
1423		}
1424		list( $key, $value ) = explode( '=', $bit );
1425		$key = urldecode( $key );
1426		$value = urldecode( $value );
1427		$ret[$key] = $value;
1428	}
1429	return $ret;
1430}
1431
1432/**
1433 * Append a query string to an existing URL, which may or may not already
1434 * have query string parameters already. If so, they will be combined.
1435 *
1436 * @param $url String
1437 * @param $query Mixed: string or associative array
1438 * @return string
1439 */
1440function wfAppendQuery( $url, $query ) {
1441	if ( is_array( $query ) ) {
1442		$query = wfArrayToCGI( $query );
1443	}
1444	if( $query != '' ) {
1445		if( false === strpos( $url, '?' ) ) {
1446			$url .= '?';
1447		} else {
1448			$url .= '&';
1449		}
1450		$url .= $query;
1451	}
1452	return $url;
1453}
1454
1455/**
1456 * Expand a potentially local URL to a fully-qualified URL.  Assumes $wgServer
1457 * and $wgProto are correct.
1458 *
1459 * @todo this won't work with current-path-relative URLs
1460 * like "subdir/foo.html", etc.
1461 *
1462 * @param $url String: either fully-qualified or a local path + query
1463 * @return string Fully-qualified URL
1464 */
1465function wfExpandUrl( $url ) {
1466	if( substr( $url, 0, 2 ) == '//' ) {
1467		global $wgProto;
1468		return $wgProto . ':' . $url;
1469	} elseif( substr( $url, 0, 1 ) == '/' ) {
1470		global $wgServer;
1471		return $wgServer . $url;
1472	} else {
1473		return $url;
1474	}
1475}
1476
1477/**
1478 * Windows-compatible version of escapeshellarg()
1479 * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
1480 * function puts single quotes in regardless of OS.
1481 *
1482 * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
1483 * earlier distro releases of PHP)
1484 */
1485function wfEscapeShellArg( ) {
1486	wfInitShellLocale();
1487
1488	$args = func_get_args();
1489	$first = true;
1490	$retVal = '';
1491	foreach ( $args as $arg ) {
1492		if ( !$first ) {
1493			$retVal .= ' ';
1494		} else {
1495			$first = false;
1496		}
1497
1498		if ( wfIsWindows() ) {
1499			// Escaping for an MSVC-style command line parser
1500			// Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
1501			// Double the backslashes before any double quotes. Escape the double quotes.
1502			$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
1503			$arg = '';
1504			$iteration = 0;
1505			foreach ( $tokens as $token ) {
1506				if ( $iteration % 2 == 1 ) {
1507					// Delimiter, a double quote preceded by zero or more slashes
1508					$arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
1509				} elseif ( $iteration % 4 == 2 ) {
1510					// ^ in $token will be outside quotes, need to be escaped
1511					$arg .= str_replace( '^', '^^', $token );
1512				} else { // $iteration % 4 == 0
1513					// ^ in $token will appear inside double quotes, so leave as is
1514					$arg .= $token;
1515				}
1516				$iteration++;
1517			}
1518			// Double the backslashes before the end of the string, because
1519			// we will soon add a quote
1520			$m = array();
1521			if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
1522				$arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
1523			}
1524
1525			// Add surrounding quotes
1526			$retVal .= '"' . $arg . '"';
1527		} else {
1528			$retVal .= escapeshellarg( $arg );
1529		}
1530	}
1531	return $retVal;
1532}
1533
1534/**
1535 * wfMerge attempts to merge differences between three texts.
1536 * Returns true for a clean merge and false for failure or a conflict.
1537 */
1538function wfMerge( $old, $mine, $yours, &$result ) {
1539	global $wgDiff3;
1540
1541	# This check may also protect against code injection in
1542	# case of broken installations.
1543	wfSuppressWarnings();
1544	$haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
1545	wfRestoreWarnings();
1546
1547	if( !$haveDiff3 ) {
1548		wfDebug( "diff3 not found\n" );
1549		return false;
1550	}
1551
1552	# Make temporary files
1553	$td = wfTempDir();
1554	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1555	$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
1556	$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
1557
1558	fwrite( $oldtextFile, $old );
1559	fclose( $oldtextFile );
1560	fwrite( $mytextFile, $mine );
1561	fclose( $mytextFile );
1562	fwrite( $yourtextFile, $yours );
1563	fclose( $yourtextFile );
1564
1565	# Check for a conflict
1566	$cmd = $wgDiff3 . ' -a --overlap-only ' .
1567		wfEscapeShellArg( $mytextName ) . ' ' .
1568		wfEscapeShellArg( $oldtextName ) . ' ' .
1569		wfEscapeShellArg( $yourtextName );
1570	$handle = popen( $cmd, 'r' );
1571
1572	if( fgets( $handle, 1024 ) ) {
1573		$conflict = true;
1574	} else {
1575		$conflict = false;
1576	}
1577	pclose( $handle );
1578
1579	# Merge differences
1580	$cmd = $wgDiff3 . ' -a -e --merge ' .
1581		wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
1582	$handle = popen( $cmd, 'r' );
1583	$result = '';
1584	do {
1585		$data = fread( $handle, 8192 );
1586		if ( strlen( $data ) == 0 ) {
1587			break;
1588		}
1589		$result .= $data;
1590	} while ( true );
1591	pclose( $handle );
1592	unlink( $mytextName );
1593	unlink( $oldtextName );
1594	unlink( $yourtextName );
1595
1596	if ( $result === '' && $old !== '' && !$conflict ) {
1597		wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
1598		$conflict = true;
1599	}
1600	return !$conflict;
1601}
1602
1603/**
1604 * Returns unified plain-text diff of two texts.
1605 * Useful for machine processing of diffs.
1606 * @param $before String: the text before the changes.
1607 * @param $after String: the text after the changes.
1608 * @param $params String: command-line options for the diff command.
1609 * @return String: unified diff of $before and $after
1610 */
1611function wfDiff( $before, $after, $params = '-u' ) {
1612	if ( $before == $after ) {
1613		return '';
1614	}
1615
1616	global $wgDiff;
1617	wfSuppressWarnings();
1618	$haveDiff = $wgDiff && file_exists( $wgDiff );
1619	wfRestoreWarnings();
1620
1621	# This check may also protect against code injection in
1622	# case of broken installations.
1623	if( !$haveDiff ) {
1624		wfDebug( "diff executable not found\n" );
1625		$diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
1626		$format = new UnifiedDiffFormatter();
1627		return $format->format( $diffs );
1628	}
1629
1630	# Make temporary files
1631	$td = wfTempDir();
1632	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1633	$newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
1634
1635	fwrite( $oldtextFile, $before );
1636	fclose( $oldtextFile );
1637	fwrite( $newtextFile, $after );
1638	fclose( $newtextFile );
1639
1640	// Get the diff of the two files
1641	$cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
1642
1643	$h = popen( $cmd, 'r' );
1644
1645	$diff = '';
1646
1647	do {
1648		$data = fread( $h, 8192 );
1649		if ( strlen( $data ) == 0 ) {
1650			break;
1651		}
1652		$diff .= $data;
1653	} while ( true );
1654
1655	// Clean up
1656	pclose( $h );
1657	unlink( $oldtextName );
1658	unlink( $newtextName );
1659
1660	// Kill the --- and +++ lines. They're not useful.
1661	$diff_lines = explode( "\n", $diff );
1662	if ( strpos( $diff_lines[0], '---' ) === 0 ) {
1663		unset( $diff_lines[0] );
1664	}
1665	if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
1666		unset( $diff_lines[1] );
1667	}
1668
1669	$diff = implode( "\n", $diff_lines );
1670
1671	return $diff;
1672}
1673
1674/**
1675 * A wrapper around the PHP function var_export().
1676 * Either print it or add it to the regular output ($wgOut).
1677 *
1678 * @param $var A PHP variable to dump.
1679 */
1680function wfVarDump( $var ) {
1681	global $wgOut;
1682	$s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
1683	if ( headers_sent() || !@is_object( $wgOut ) ) {
1684		print $s;
1685	} else {
1686		$wgOut->addHTML( $s );
1687	}
1688}
1689
1690/**
1691 * Provide a simple HTTP error.
1692 */
1693function wfHttpError( $code, $label, $desc ) {
1694	global $wgOut;
1695	$wgOut->disable();
1696	header( "HTTP/1.0 $code $label" );
1697	header( "Status: $code $label" );
1698	$wgOut->sendCacheControl();
1699
1700	header( 'Content-type: text/html; charset=utf-8' );
1701	print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
1702		'<html><head><title>' .
1703		htmlspecialchars( $label ) .
1704		'</title></head><body><h1>' .
1705		htmlspecialchars( $label ) .
1706		'</h1><p>' .
1707		nl2br( htmlspecialchars( $desc ) ) .
1708		"</p></body></html>\n";
1709}
1710
1711/**
1712 * Clear away any user-level output buffers, discarding contents.
1713 *
1714 * Suitable for 'starting afresh', for instance when streaming
1715 * relatively large amounts of data without buffering, or wanting to
1716 * output image files without ob_gzhandler's compression.
1717 *
1718 * The optional $resetGzipEncoding parameter controls suppression of
1719 * the Content-Encoding header sent by ob_gzhandler; by default it
1720 * is left. See comments for wfClearOutputBuffers() for why it would
1721 * be used.
1722 *
1723 * Note that some PHP configuratiā€¦

Large files files are truncated, but you can click here to view the full file