PageRenderTime 134ms CodeModel.GetById 51ms app.highlight 69ms RepoModel.GetById 2ms app.codeStats 0ms

/mediawiki-integration/source/php/mediawiki/includes/GlobalFunctions.php

https://code.google.com/
PHP | 2138 lines | 1330 code | 187 blank | 621 comment | 235 complexity | 9e9da7beed22b14b341fa62a233d4e05 MD5 | raw file

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

   1<?php
   2
   3/**
   4 * Global functions used everywhere
   5 * @package MediaWiki
   6 */
   7
   8/**
   9 * Some globals and requires needed
  10 */
  11
  12/** Total number of articles */
  13$wgNumberOfArticles = -1; # Unset
  14
  15/** Total number of views */
  16$wgTotalViews = -1;
  17
  18/** Total number of edits */
  19$wgTotalEdits = -1;
  20
  21
  22require_once( 'LogPage.php' );
  23require_once( 'normal/UtfNormalUtil.php' );
  24require_once( 'XmlFunctions.php' );
  25
  26/**
  27 * Compatibility functions
  28 *
  29 * We more or less support PHP 5.0.x and up.
  30 * Re-implementations of newer functions or functions in non-standard
  31 * PHP extensions may be included here.
  32 */
  33if( !function_exists('iconv') ) {
  34	# iconv support is not in the default configuration and so may not be present.
  35	# Assume will only ever use utf-8 and iso-8859-1.
  36	# This will *not* work in all circumstances.
  37	function iconv( $from, $to, $string ) {
  38		if(strcasecmp( $from, $to ) == 0) return $string;
  39		if(strcasecmp( $from, 'utf-8' ) == 0) return utf8_decode( $string );
  40		if(strcasecmp( $to, 'utf-8' ) == 0) return utf8_encode( $string );
  41		return $string;
  42	}
  43}
  44
  45# UTF-8 substr function based on a PHP manual comment
  46if ( !function_exists( 'mb_substr' ) ) {
  47	function mb_substr( $str, $start ) {
  48		$ar = array();
  49		preg_match_all( '/./us', $str, $ar );
  50
  51		if( func_num_args() >= 3 ) {
  52			$end = func_get_arg( 2 );
  53			return join( '', array_slice( $ar[0], $start, $end ) );
  54		} else {
  55			return join( '', array_slice( $ar[0], $start ) );
  56		}
  57	}
  58}
  59
  60if ( !function_exists( 'array_diff_key' ) ) {
  61	/**
  62	 * Exists in PHP 5.1.0+
  63	 * Not quite compatible, two-argument version only
  64	 * Null values will cause problems due to this use of isset()
  65	 */
  66	function array_diff_key( $left, $right ) {
  67		$result = $left;
  68		foreach ( $left as $key => $unused ) {
  69			if ( isset( $right[$key] ) ) {
  70				unset( $result[$key] );
  71			}
  72		}
  73		return $result;
  74	}
  75}
  76
  77
  78/**
  79 * Wrapper for clone(), for compatibility with PHP4-friendly extensions.
  80 * PHP 5 won't let you declare a 'clone' function, even conditionally,
  81 * so it has to be a wrapper with a different name.
  82 */
  83function wfClone( $object ) {
  84	return clone( $object );
  85}
  86
  87/**
  88 * Where as we got a random seed
  89 */
  90$wgRandomSeeded = false;
  91
  92/**
  93 * Seed Mersenne Twister
  94 * No-op for compatibility; only necessary in PHP < 4.2.0
  95 */
  96function wfSeedRandom() {
  97	/* No-op */
  98}
  99
 100/**
 101 * Get a random decimal value between 0 and 1, in a way
 102 * not likely to give duplicate values for any realistic
 103 * number of articles.
 104 *
 105 * @return string
 106 */
 107function wfRandom() {
 108	# The maximum random value is "only" 2^31-1, so get two random
 109	# values to reduce the chance of dupes
 110	$max = mt_getrandmax() + 1;
 111	$rand = number_format( (mt_rand() * $max + mt_rand())
 112		/ $max / $max, 12, '.', '' );
 113	return $rand;
 114}
 115
 116/**
 117 * We want / and : to be included as literal characters in our title URLs.
 118 * %2F in the page titles seems to fatally break for some reason.
 119 *
 120 * @param $s String:
 121 * @return string
 122*/
 123function wfUrlencode ( $s ) {
 124	$s = urlencode( $s );
 125	$s = preg_replace( '/%3[Aa]/', ':', $s );
 126	$s = preg_replace( '/%2[Ff]/', '/', $s );
 127
 128	return $s;
 129}
 130
 131/**
 132 * Sends a line to the debug log if enabled or, optionally, to a comment in output.
 133 * In normal operation this is a NOP.
 134 *
 135 * Controlling globals:
 136 * $wgDebugLogFile - points to the log file
 137 * $wgProfileOnly - if set, normal debug messages will not be recorded.
 138 * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
 139 * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
 140 *
 141 * @param $text String
 142 * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
 143 */
 144function wfDebug( $text, $logonly = false ) {
 145	global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
 146	static $recursion = 0;
 147
 148	# Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
 149	if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
 150		return;
 151	}
 152
 153	if ( $wgDebugComments && !$logonly ) {
 154		if ( !isset( $wgOut ) ) {
 155			return;
 156		}
 157		if ( !StubObject::isRealObject( $wgOut ) ) {
 158			if ( $recursion ) {
 159				return;
 160			}
 161			$recursion++;
 162			$wgOut->_unstub();
 163			$recursion--;
 164		}
 165		$wgOut->debug( $text );
 166	}
 167	if ( '' != $wgDebugLogFile && !$wgProfileOnly ) {
 168		# Strip unprintables; they can switch terminal modes when binary data
 169		# gets dumped, which is pretty annoying.
 170		$text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
 171		@error_log( $text, 3, $wgDebugLogFile );
 172	}
 173}
 174
 175/**
 176 * Send a line to a supplementary debug log file, if configured, or main debug log if not.
 177 * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
 178 *
 179 * @param $logGroup String
 180 * @param $text String
 181 * @param $public Bool: whether to log the event in the public log if no private
 182 *                     log file is specified, (default true)
 183 */
 184function wfDebugLog( $logGroup, $text, $public = true ) {
 185	global $wgDebugLogGroups;
 186	if( $text{strlen( $text ) - 1} != "\n" ) $text .= "\n";
 187	if( isset( $wgDebugLogGroups[$logGroup] ) ) {
 188		$time = wfTimestamp( TS_DB );
 189		$wiki = wfWikiID();
 190		@error_log( "$time $wiki: $text", 3, $wgDebugLogGroups[$logGroup] );
 191	} else if ( $public === true ) {
 192		wfDebug( $text, true );
 193	}
 194}
 195
 196/**
 197 * Log for database errors
 198 * @param $text String: database error message.
 199 */
 200function wfLogDBError( $text ) {
 201	global $wgDBerrorLog;
 202	if ( $wgDBerrorLog ) {
 203		$host = trim(`hostname`);
 204		$text = date('D M j G:i:s T Y') . "\t$host\t".$text;
 205		error_log( $text, 3, $wgDBerrorLog );
 206	}
 207}
 208
 209/**
 210 * @todo document
 211 */
 212function wfLogProfilingData() {
 213	global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
 214	global $wgProfiling, $wgUser;
 215	if ( $wgProfiling ) {
 216		$now = wfTime();
 217		$elapsed = $now - $wgRequestTime;
 218		$prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
 219		$forward = '';
 220		if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
 221			$forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
 222		if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) )
 223			$forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
 224		if( !empty( $_SERVER['HTTP_FROM'] ) )
 225			$forward .= ' from ' . $_SERVER['HTTP_FROM'];
 226		if( $forward )
 227			$forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
 228		// Don't unstub $wgUser at this late stage just for statistics purposes
 229		if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() )
 230			$forward .= ' anon';
 231		$log = sprintf( "%s\t%04.3f\t%s\n",
 232		  gmdate( 'YmdHis' ), $elapsed,
 233		  urldecode( $wgRequest->getRequestURL() . $forward ) );
 234		if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) {
 235			error_log( $log . $prof, 3, $wgDebugLogFile );
 236		}
 237	}
 238}
 239
 240/**
 241 * Check if the wiki read-only lock file is present. This can be used to lock
 242 * off editing functions, but doesn't guarantee that the database will not be
 243 * modified.
 244 * @return bool
 245 */
 246function wfReadOnly() {
 247	global $wgReadOnlyFile, $wgReadOnly;
 248
 249	if ( !is_null( $wgReadOnly ) ) {
 250		return (bool)$wgReadOnly;
 251	}
 252	if ( '' == $wgReadOnlyFile ) {
 253		return false;
 254	}
 255	// Set $wgReadOnly for faster access next time
 256	if ( is_file( $wgReadOnlyFile ) ) {
 257		$wgReadOnly = file_get_contents( $wgReadOnlyFile );
 258	} else {
 259		$wgReadOnly = false;
 260	}
 261	return (bool)$wgReadOnly;
 262}
 263
 264
 265/**
 266 * Get a message from anywhere, for the current user language.
 267 *
 268 * Use wfMsgForContent() instead if the message should NOT
 269 * change depending on the user preferences.
 270 *
 271 * Note that the message may contain HTML, and is therefore
 272 * not safe for insertion anywhere. Some functions such as
 273 * addWikiText will do the escaping for you. Use wfMsgHtml()
 274 * if you need an escaped message.
 275 *
 276 * @param $key String: lookup key for the message, usually
 277 *    defined in languages/Language.php
 278 * 
 279 * This function also takes extra optional parameters (not 
 280 * shown in the function definition), which can by used to 
 281 * insert variable text into the predefined message.
 282 */
 283function wfMsg( $key ) {
 284	$args = func_get_args();
 285	array_shift( $args );
 286	return wfMsgReal( $key, $args, true );
 287}
 288
 289/**
 290 * Same as above except doesn't transform the message
 291 */
 292function wfMsgNoTrans( $key ) {
 293	$args = func_get_args();
 294	array_shift( $args );
 295	return wfMsgReal( $key, $args, true, false, false );
 296}
 297
 298/**
 299 * Get a message from anywhere, for the current global language
 300 * set with $wgLanguageCode.
 301 *
 302 * Use this if the message should NOT change  dependent on the
 303 * language set in the user's preferences. This is the case for
 304 * most text written into logs, as well as link targets (such as
 305 * the name of the copyright policy page). Link titles, on the
 306 * other hand, should be shown in the UI language.
 307 *
 308 * Note that MediaWiki allows users to change the user interface
 309 * language in their preferences, but a single installation
 310 * typically only contains content in one language.
 311 *
 312 * Be wary of this distinction: If you use wfMsg() where you should
 313 * use wfMsgForContent(), a user of the software may have to
 314 * customize over 70 messages in order to, e.g., fix a link in every
 315 * possible language.
 316 *
 317 * @param $key String: lookup key for the message, usually
 318 *    defined in languages/Language.php
 319 */
 320function wfMsgForContent( $key ) {
 321	global $wgForceUIMsgAsContentMsg;
 322	$args = func_get_args();
 323	array_shift( $args );
 324	$forcontent = true;
 325	if( is_array( $wgForceUIMsgAsContentMsg ) &&
 326		in_array( $key, $wgForceUIMsgAsContentMsg ) )
 327		$forcontent = false;
 328	return wfMsgReal( $key, $args, true, $forcontent );
 329}
 330
 331/**
 332 * Same as above except doesn't transform the message
 333 */
 334function wfMsgForContentNoTrans( $key ) {
 335	global $wgForceUIMsgAsContentMsg;
 336	$args = func_get_args();
 337	array_shift( $args );
 338	$forcontent = true;
 339	if( is_array( $wgForceUIMsgAsContentMsg ) &&
 340		in_array( $key, $wgForceUIMsgAsContentMsg ) )
 341		$forcontent = false;
 342	return wfMsgReal( $key, $args, true, $forcontent, false );
 343}
 344
 345/**
 346 * Get a message from the language file, for the UI elements
 347 */
 348function wfMsgNoDB( $key ) {
 349	$args = func_get_args();
 350	array_shift( $args );
 351	return wfMsgReal( $key, $args, false );
 352}
 353
 354/**
 355 * Get a message from the language file, for the content
 356 */
 357function wfMsgNoDBForContent( $key ) {
 358	global $wgForceUIMsgAsContentMsg;
 359	$args = func_get_args();
 360	array_shift( $args );
 361	$forcontent = true;
 362	if( is_array( $wgForceUIMsgAsContentMsg ) &&
 363		in_array( $key,	$wgForceUIMsgAsContentMsg ) )
 364		$forcontent = false;
 365	return wfMsgReal( $key, $args, false, $forcontent );
 366}
 367
 368
 369/**
 370 * Really get a message
 371 * @param $key String: key to get.
 372 * @param $args
 373 * @param $useDB Boolean
 374 * @param $transform Boolean: Whether or not to transform the message.
 375 * @param $forContent Boolean
 376 * @return String: the requested message.
 377 */
 378function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) {
 379	$message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
 380	$message = wfMsgReplaceArgs( $message, $args );
 381	return $message;
 382}
 383
 384/**
 385 * This function provides the message source for messages to be edited which are *not* stored in the database.
 386 * @param $key String:
 387 */
 388function wfMsgWeirdKey ( $key ) {
 389	$subsource = str_replace ( ' ' , '_' , $key ) ;
 390	$source = wfMsgForContentNoTrans( $subsource ) ;
 391	if ( wfEmptyMsg( $subsource, $source) ) {
 392		# Try again with first char lower case
 393		$subsource = strtolower ( substr ( $subsource , 0 , 1 ) ) . substr ( $subsource , 1 ) ;
 394		$source = wfMsgForContentNoTrans( $subsource ) ;
 395	}
 396	if ( wfEmptyMsg( $subsource, $source ) ) {
 397		# Didn't work either, return blank text
 398		$source = "" ;
 399	}
 400	return $source ;
 401}
 402
 403/**
 404 * Fetch a message string value, but don't replace any keys yet.
 405 * @param string $key
 406 * @param bool $useDB
 407 * @param bool $forContent
 408 * @return string
 409 * @private
 410 */
 411function wfMsgGetKey( $key, $useDB, $forContent = false, $transform = true ) {
 412	global $wgParser, $wgContLang, $wgMessageCache, $wgLang;
 413
 414	if ( is_object( $wgMessageCache ) )
 415		$transstat = $wgMessageCache->getTransform();
 416
 417	if( is_object( $wgMessageCache ) ) {
 418		if ( ! $transform )
 419			$wgMessageCache->disableTransform();
 420		$message = $wgMessageCache->get( $key, $useDB, $forContent );
 421	} else {
 422		if( $forContent ) {
 423			$lang = &$wgContLang;
 424		} else {
 425			$lang = &$wgLang;
 426		}
 427
 428		wfSuppressWarnings();
 429
 430		if( is_object( $lang ) ) {
 431			$message = $lang->getMessage( $key );
 432		} else {
 433			$message = false;
 434		}
 435		wfRestoreWarnings();
 436		if($message === false)
 437			$message = Language::getMessage($key);
 438		if ( $transform && strstr( $message, '{{' ) !== false ) {
 439			$message = $wgParser->transformMsg($message, $wgMessageCache->getParserOptions() );
 440		}
 441	}
 442
 443	if ( is_object( $wgMessageCache ) && ! $transform )
 444		$wgMessageCache->setTransform( $transstat );
 445
 446	return $message;
 447}
 448
 449/**
 450 * Replace message parameter keys on the given formatted output.
 451 *
 452 * @param string $message
 453 * @param array $args
 454 * @return string
 455 * @private
 456 */
 457function wfMsgReplaceArgs( $message, $args ) {
 458	# Fix windows line-endings
 459	# Some messages are split with explode("\n", $msg)
 460	$message = str_replace( "\r", '', $message );
 461
 462	// Replace arguments
 463	if ( count( $args ) ) {
 464		if ( is_array( $args[0] ) ) {
 465			foreach ( $args[0] as $key => $val ) {
 466				$message = str_replace( '$' . $key, $val, $message );
 467			}
 468		} else {
 469			foreach( $args as $n => $param ) {
 470				$replacementKeys['$' . ($n + 1)] = $param;
 471			}
 472			$message = strtr( $message, $replacementKeys );
 473		}
 474	}
 475
 476	return $message;
 477}
 478
 479/**
 480 * Return an HTML-escaped version of a message.
 481 * Parameter replacements, if any, are done *after* the HTML-escaping,
 482 * so parameters may contain HTML (eg links or form controls). Be sure
 483 * to pre-escape them if you really do want plaintext, or just wrap
 484 * the whole thing in htmlspecialchars().
 485 *
 486 * @param string $key
 487 * @param string ... parameters
 488 * @return string
 489 */
 490function wfMsgHtml( $key ) {
 491	$args = func_get_args();
 492	array_shift( $args );
 493	return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
 494}
 495
 496/**
 497 * Return an HTML version of message
 498 * Parameter replacements, if any, are done *after* parsing the wiki-text message,
 499 * so parameters may contain HTML (eg links or form controls). Be sure
 500 * to pre-escape them if you really do want plaintext, or just wrap
 501 * the whole thing in htmlspecialchars().
 502 *
 503 * @param string $key
 504 * @param string ... parameters
 505 * @return string
 506 */
 507function wfMsgWikiHtml( $key ) {
 508	global $wgOut;
 509	$args = func_get_args();
 510	array_shift( $args );
 511	return wfMsgReplaceArgs( $wgOut->parse( wfMsgGetKey( $key, true ), /* can't be set to false */ true ), $args );
 512}
 513
 514/**
 515 * Returns message in the requested format
 516 * @param string $key Key of the message
 517 * @param array $options Processing rules:
 518 *  <i>parse<i>: parses wikitext to html
 519 *  <i>parseinline<i>: parses wikitext to html and removes the surrounding p's added by parser or tidy
 520 *  <i>escape<i>: filters message trough htmlspecialchars
 521 *  <i>replaceafter<i>: parameters are substituted after parsing or escaping
 522 *  <i>parsemag<i>: ??
 523 */
 524function wfMsgExt( $key, $options ) {
 525	global $wgOut, $wgParser;
 526
 527	$args = func_get_args();
 528	array_shift( $args );
 529	array_shift( $args );
 530
 531	if( !is_array($options) ) {
 532		$options = array($options);
 533	}
 534
 535	$string = wfMsgGetKey( $key, true, false, false );
 536
 537	if( !in_array('replaceafter', $options) ) {
 538		$string = wfMsgReplaceArgs( $string, $args );
 539	}
 540
 541	if( in_array('parse', $options) ) {
 542		$string = $wgOut->parse( $string, true, true );
 543	} elseif ( in_array('parseinline', $options) ) {
 544		$string = $wgOut->parse( $string, true, true );
 545		$m = array();
 546		if( preg_match( "~^<p>(.*)\n?</p>$~", $string, $m ) ) {
 547			$string = $m[1];
 548		}
 549	} elseif ( in_array('parsemag', $options) ) {
 550		global $wgMessageCache;
 551		if ( isset( $wgMessageCache ) ) {
 552			$string = $wgMessageCache->transform( $string );
 553		}
 554	}
 555
 556	if ( in_array('escape', $options) ) {
 557		$string = htmlspecialchars ( $string );
 558	}
 559
 560	if( in_array('replaceafter', $options) ) {
 561		$string = wfMsgReplaceArgs( $string, $args );
 562	}
 563
 564	return $string;
 565}
 566
 567
 568/**
 569 * Just like exit() but makes a note of it.
 570 * Commits open transactions except if the error parameter is set
 571 *
 572 * @obsolete Please return control to the caller or throw an exception
 573 */
 574function wfAbruptExit( $error = false ){
 575	global $wgLoadBalancer;
 576	static $called = false;
 577	if ( $called ){
 578		exit( -1 );
 579	}
 580	$called = true;
 581
 582	$bt = wfDebugBacktrace();
 583	if( $bt ) {
 584		for($i = 0; $i < count($bt) ; $i++){
 585			$file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown";
 586			$line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown";
 587			wfDebug("WARNING: Abrupt exit in $file at line $line\n");
 588		}
 589	} else {
 590		wfDebug('WARNING: Abrupt exit\n');
 591	}
 592
 593	wfLogProfilingData();
 594
 595	if ( !$error ) {
 596		$wgLoadBalancer->closeAll();
 597	}
 598	exit( -1 );
 599}
 600
 601/**
 602 * @obsolete Please return control the caller or throw an exception
 603 */
 604function wfErrorExit() {
 605	wfAbruptExit( true );
 606}
 607
 608/**
 609 * Print a simple message and die, returning nonzero to the shell if any.
 610 * Plain die() fails to return nonzero to the shell if you pass a string.
 611 * @param string $msg
 612 */
 613function wfDie( $msg='' ) {
 614	echo $msg;
 615	die( 1 );
 616}
 617
 618/**
 619 * Throw a debugging exception. This function previously once exited the process, 
 620 * but now throws an exception instead, with similar results.
 621 *
 622 * @param string $msg Message shown when dieing.
 623 */
 624function wfDebugDieBacktrace( $msg = '' ) {
 625	throw new MWException( $msg );
 626}
 627
 628/**
 629 * Fetch server name for use in error reporting etc.
 630 * Use real server name if available, so we know which machine
 631 * in a server farm generated the current page.
 632 * @return string
 633 */
 634function wfHostname() {
 635	if ( function_exists( 'posix_uname' ) ) {
 636		// This function not present on Windows
 637		$uname = @posix_uname();
 638	} else {
 639		$uname = false;
 640	}
 641	if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
 642		return $uname['nodename'];
 643	} else {
 644		# This may be a virtual server.
 645		return $_SERVER['SERVER_NAME'];
 646	}
 647}
 648
 649	/**
 650	 * Returns a HTML comment with the elapsed time since request.
 651	 * This method has no side effects.
 652	 * @return string
 653	 */
 654	function wfReportTime() {
 655		global $wgRequestTime;
 656
 657		$now = wfTime();
 658		$elapsed = $now - $wgRequestTime;
 659
 660		$com = sprintf( "<!-- Served by %s in %01.3f secs. -->",
 661		  wfHostname(), $elapsed );
 662		return $com;
 663	}
 664
 665/**
 666 * Safety wrapper for debug_backtrace().
 667 *
 668 * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
 669 * murky circumstances, which may be triggered in part by stub objects
 670 * or other fancy talkin'.
 671 *
 672 * Will return an empty array if Zend Optimizer is detected, otherwise
 673 * the output from debug_backtrace() (trimmed).
 674 *
 675 * @return array of backtrace information
 676 */
 677function wfDebugBacktrace() {
 678	if( extension_loaded( 'Zend Optimizer' ) ) {
 679		wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
 680		return array();
 681	} else {
 682		return array_slice( debug_backtrace(), 1 );
 683	}
 684}
 685
 686function wfBacktrace() {
 687	global $wgCommandLineMode;
 688
 689	if ( $wgCommandLineMode ) {
 690		$msg = '';
 691	} else {
 692		$msg = "<ul>\n";
 693	}
 694	$backtrace = wfDebugBacktrace();
 695	foreach( $backtrace as $call ) {
 696		if( isset( $call['file'] ) ) {
 697			$f = explode( DIRECTORY_SEPARATOR, $call['file'] );
 698			$file = $f[count($f)-1];
 699		} else {
 700			$file = '-';
 701		}
 702		if( isset( $call['line'] ) ) {
 703			$line = $call['line'];
 704		} else {
 705			$line = '-';
 706		}
 707		if ( $wgCommandLineMode ) {
 708			$msg .= "$file line $line calls ";
 709		} else {
 710			$msg .= '<li>' . $file . ' line ' . $line . ' calls ';
 711		}
 712		if( !empty( $call['class'] ) ) $msg .= $call['class'] . '::';
 713		$msg .= $call['function'] . '()';
 714
 715		if ( $wgCommandLineMode ) {
 716			$msg .= "\n";
 717		} else {
 718			$msg .= "</li>\n";
 719		}
 720	}
 721	if ( $wgCommandLineMode ) {
 722		$msg .= "\n";
 723	} else {
 724		$msg .= "</ul>\n";
 725	}
 726
 727	return $msg;
 728}
 729
 730
 731/* Some generic result counters, pulled out of SearchEngine */
 732
 733
 734/**
 735 * @todo document
 736 */
 737function wfShowingResults( $offset, $limit ) {
 738	global $wgLang;
 739	return wfMsg( 'showingresults', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) );
 740}
 741
 742/**
 743 * @todo document
 744 */
 745function wfShowingResultsNum( $offset, $limit, $num ) {
 746	global $wgLang;
 747	return wfMsg( 'showingresultsnum', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
 748}
 749
 750/**
 751 * @todo document
 752 */
 753function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
 754	global $wgLang;
 755	$fmtLimit = $wgLang->formatNum( $limit );
 756	$prev = wfMsg( 'prevn', $fmtLimit );
 757	$next = wfMsg( 'nextn', $fmtLimit );
 758
 759	if( is_object( $link ) ) {
 760		$title =& $link;
 761	} else {
 762		$title = Title::newFromText( $link );
 763		if( is_null( $title ) ) {
 764			return false;
 765		}
 766	}
 767
 768	if ( 0 != $offset ) {
 769		$po = $offset - $limit;
 770		if ( $po < 0 ) { $po = 0; }
 771		$q = "limit={$limit}&offset={$po}";
 772		if ( '' != $query ) { $q .= '&'.$query; }
 773		$plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\">{$prev}</a>";
 774	} else { $plink = $prev; }
 775
 776	$no = $offset + $limit;
 777	$q = 'limit='.$limit.'&offset='.$no;
 778	if ( '' != $query ) { $q .= '&'.$query; }
 779
 780	if ( $atend ) {
 781		$nlink = $next;
 782	} else {
 783		$nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\">{$next}</a>";
 784	}
 785	$nums = wfNumLink( $offset, 20, $title, $query ) . ' | ' .
 786	  wfNumLink( $offset, 50, $title, $query ) . ' | ' .
 787	  wfNumLink( $offset, 100, $title, $query ) . ' | ' .
 788	  wfNumLink( $offset, 250, $title, $query ) . ' | ' .
 789	  wfNumLink( $offset, 500, $title, $query );
 790
 791	return wfMsg( 'viewprevnext', $plink, $nlink, $nums );
 792}
 793
 794/**
 795 * @todo document
 796 */
 797function wfNumLink( $offset, $limit, &$title, $query = '' ) {
 798	global $wgLang;
 799	if ( '' == $query ) { $q = ''; }
 800	else { $q = $query.'&'; }
 801	$q .= 'limit='.$limit.'&offset='.$offset;
 802
 803	$fmtLimit = $wgLang->formatNum( $limit );
 804	$s = '<a href="' . $title->escapeLocalUrl( $q ) . "\">{$fmtLimit}</a>";
 805	return $s;
 806}
 807
 808/**
 809 * @todo document
 810 * @todo FIXME: we may want to blacklist some broken browsers
 811 *
 812 * @return bool Whereas client accept gzip compression
 813 */
 814function wfClientAcceptsGzip() {
 815	global $wgUseGzip;
 816	if( $wgUseGzip ) {
 817		# FIXME: we may want to blacklist some broken browsers
 818		$m = array();
 819		if( preg_match(
 820			'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
 821			$_SERVER['HTTP_ACCEPT_ENCODING'],
 822			$m ) ) {
 823			if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) return false;
 824			wfDebug( " accepts gzip\n" );
 825			return true;
 826		}
 827	}
 828	return false;
 829}
 830
 831/**
 832 * Obtain the offset and limit values from the request string;
 833 * used in special pages
 834 *
 835 * @param $deflimit Default limit if none supplied
 836 * @param $optionname Name of a user preference to check against
 837 * @return array
 838 * 
 839 */
 840function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
 841	global $wgRequest;
 842	return $wgRequest->getLimitOffset( $deflimit, $optionname );
 843}
 844
 845/**
 846 * Escapes the given text so that it may be output using addWikiText()
 847 * without any linking, formatting, etc. making its way through. This
 848 * is achieved by substituting certain characters with HTML entities.
 849 * As required by the callers, <nowiki> is not used. It currently does
 850 * not filter out characters which have special meaning only at the
 851 * start of a line, such as "*".
 852 *
 853 * @param string $text Text to be escaped
 854 */
 855function wfEscapeWikiText( $text ) {
 856	$text = str_replace(
 857		array( '[',     '|',      '\'',    'ISBN ',     'RFC ',     '://',     "\n=",     '{{' ),
 858		array( '&#91;', '&#124;', '&#39;', 'ISBN&#32;', 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;' ),
 859		htmlspecialchars($text) );
 860	return $text;
 861}
 862
 863/**
 864 * @todo document
 865 */
 866function wfQuotedPrintable( $string, $charset = '' ) {
 867	# Probably incomplete; see RFC 2045
 868	if( empty( $charset ) ) {
 869		global $wgInputEncoding;
 870		$charset = $wgInputEncoding;
 871	}
 872	$charset = strtoupper( $charset );
 873	$charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
 874
 875	$illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
 876	$replace = $illegal . '\t ?_';
 877	if( !preg_match( "/[$illegal]/", $string ) ) return $string;
 878	$out = "=?$charset?Q?";
 879	$out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
 880	$out .= '?=';
 881	return $out;
 882}
 883
 884
 885/**
 886 * @todo document
 887 * @return float
 888 */
 889function wfTime() {
 890	return microtime(true);
 891}
 892
 893/**
 894 * Sets dest to source and returns the original value of dest
 895 * If source is NULL, it just returns the value, it doesn't set the variable
 896 */
 897function wfSetVar( &$dest, $source ) {
 898	$temp = $dest;
 899	if ( !is_null( $source ) ) {
 900		$dest = $source;
 901	}
 902	return $temp;
 903}
 904
 905/**
 906 * As for wfSetVar except setting a bit
 907 */
 908function wfSetBit( &$dest, $bit, $state = true ) {
 909	$temp = (bool)($dest & $bit );
 910	if ( !is_null( $state ) ) {
 911		if ( $state ) {
 912			$dest |= $bit;
 913		} else {
 914			$dest &= ~$bit;
 915		}
 916	}
 917	return $temp;
 918}
 919
 920/**
 921 * This function takes two arrays as input, and returns a CGI-style string, e.g.
 922 * "days=7&limit=100". Options in the first array override options in the second.
 923 * Options set to "" will not be output.
 924 */
 925function wfArrayToCGI( $array1, $array2 = NULL )
 926{
 927	if ( !is_null( $array2 ) ) {
 928		$array1 = $array1 + $array2;
 929	}
 930
 931	$cgi = '';
 932	foreach ( $array1 as $key => $value ) {
 933		if ( '' !== $value ) {
 934			if ( '' != $cgi ) {
 935				$cgi .= '&';
 936			}
 937			$cgi .= urlencode( $key ) . '=' . urlencode( $value );
 938		}
 939	}
 940	return $cgi;
 941}
 942
 943/**
 944 * This is obsolete, use SquidUpdate::purge()
 945 * @deprecated
 946 */
 947function wfPurgeSquidServers ($urlArr) {
 948	SquidUpdate::purge( $urlArr );
 949}
 950
 951/**
 952 * Windows-compatible version of escapeshellarg()
 953 * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
 954 * function puts single quotes in regardless of OS
 955 */
 956function wfEscapeShellArg( ) {
 957	$args = func_get_args();
 958	$first = true;
 959	$retVal = '';
 960	foreach ( $args as $arg ) {
 961		if ( !$first ) {
 962			$retVal .= ' ';
 963		} else {
 964			$first = false;
 965		}
 966
 967		if ( wfIsWindows() ) {
 968			// Escaping for an MSVC-style command line parser
 969			// Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
 970			// Double the backslashes before any double quotes. Escape the double quotes.
 971			$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
 972			$arg = '';
 973			$delim = false;
 974			foreach ( $tokens as $token ) {
 975				if ( $delim ) {
 976					$arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
 977				} else {
 978					$arg .= $token;
 979				}
 980				$delim = !$delim;
 981			}
 982			// Double the backslashes before the end of the string, because
 983			// we will soon add a quote
 984			$m = array();
 985			if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
 986				$arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
 987			}
 988
 989			// Add surrounding quotes
 990			$retVal .= '"' . $arg . '"';
 991		} else {
 992			$retVal .= escapeshellarg( $arg );
 993		}
 994	}
 995	return $retVal;
 996}
 997
 998/**
 999 * wfMerge attempts to merge differences between three texts.
1000 * Returns true for a clean merge and false for failure or a conflict.
1001 */
1002function wfMerge( $old, $mine, $yours, &$result ){
1003	global $wgDiff3;
1004
1005	# This check may also protect against code injection in
1006	# case of broken installations.
1007	if(! file_exists( $wgDiff3 ) ){
1008		wfDebug( "diff3 not found\n" );
1009		return false;
1010	}
1011
1012	# Make temporary files
1013	$td = wfTempDir();
1014	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1015	$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
1016	$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
1017
1018	fwrite( $oldtextFile, $old ); fclose( $oldtextFile );
1019	fwrite( $mytextFile, $mine ); fclose( $mytextFile );
1020	fwrite( $yourtextFile, $yours ); fclose( $yourtextFile );
1021
1022	# Check for a conflict
1023	$cmd = $wgDiff3 . ' -a --overlap-only ' .
1024	  wfEscapeShellArg( $mytextName ) . ' ' .
1025	  wfEscapeShellArg( $oldtextName ) . ' ' .
1026	  wfEscapeShellArg( $yourtextName );
1027	$handle = popen( $cmd, 'r' );
1028
1029	if( fgets( $handle, 1024 ) ){
1030		$conflict = true;
1031	} else {
1032		$conflict = false;
1033	}
1034	pclose( $handle );
1035
1036	# Merge differences
1037	$cmd = $wgDiff3 . ' -a -e --merge ' .
1038	  wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
1039	$handle = popen( $cmd, 'r' );
1040	$result = '';
1041	do {
1042		$data = fread( $handle, 8192 );
1043		if ( strlen( $data ) == 0 ) {
1044			break;
1045		}
1046		$result .= $data;
1047	} while ( true );
1048	pclose( $handle );
1049	unlink( $mytextName ); unlink( $oldtextName ); unlink( $yourtextName );
1050
1051	if ( $result === '' && $old !== '' && $conflict == false ) {
1052		wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
1053		$conflict = true;
1054	}
1055	return ! $conflict;
1056}
1057
1058/**
1059 * @todo document
1060 */
1061function wfVarDump( $var ) {
1062	global $wgOut;
1063	$s = str_replace("\n","<br />\n", var_export( $var, true ) . "\n");
1064	if ( headers_sent() || !@is_object( $wgOut ) ) {
1065		print $s;
1066	} else {
1067		$wgOut->addHTML( $s );
1068	}
1069}
1070
1071/**
1072 * Provide a simple HTTP error.
1073 */
1074function wfHttpError( $code, $label, $desc ) {
1075	global $wgOut;
1076	$wgOut->disable();
1077	header( "HTTP/1.0 $code $label" );
1078	header( "Status: $code $label" );
1079	$wgOut->sendCacheControl();
1080
1081	header( 'Content-type: text/html; charset=utf-8' );
1082	print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
1083		"<html><head><title>" .
1084		htmlspecialchars( $label ) .
1085		"</title></head><body><h1>" .
1086		htmlspecialchars( $label ) .
1087		"</h1><p>" .
1088		nl2br( htmlspecialchars( $desc ) ) .
1089		"</p></body></html>\n";
1090}
1091
1092/**
1093 * Clear away any user-level output buffers, discarding contents.
1094 *
1095 * Suitable for 'starting afresh', for instance when streaming
1096 * relatively large amounts of data without buffering, or wanting to
1097 * output image files without ob_gzhandler's compression.
1098 *
1099 * The optional $resetGzipEncoding parameter controls suppression of
1100 * the Content-Encoding header sent by ob_gzhandler; by default it
1101 * is left. See comments for wfClearOutputBuffers() for why it would
1102 * be used.
1103 *
1104 * Note that some PHP configuration options may add output buffer
1105 * layers which cannot be removed; these are left in place.
1106 *
1107 * @parameter bool $resetGzipEncoding
1108 */
1109function wfResetOutputBuffers( $resetGzipEncoding=true ) {
1110	while( $status = ob_get_status() ) {
1111		if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
1112			// Probably from zlib.output_compression or other
1113			// PHP-internal setting which can't be removed.
1114			//
1115			// Give up, and hope the result doesn't break
1116			// output behavior.
1117			break;
1118		}
1119		if( !ob_end_clean() ) {
1120			// Could not remove output buffer handler; abort now
1121			// to avoid getting in some kind of infinite loop.
1122			break;
1123		}
1124		if( $resetGzipEncoding ) {
1125			if( $status['name'] == 'ob_gzhandler' ) {
1126				// Reset the 'Content-Encoding' field set by this handler
1127				// so we can start fresh.
1128				header( 'Content-Encoding:' );
1129			}
1130		}
1131	}
1132}
1133
1134/**
1135 * More legible than passing a 'false' parameter to wfResetOutputBuffers():
1136 *
1137 * Clear away output buffers, but keep the Content-Encoding header
1138 * produced by ob_gzhandler, if any.
1139 *
1140 * This should be used for HTTP 304 responses, where you need to
1141 * preserve the Content-Encoding header of the real result, but
1142 * also need to suppress the output of ob_gzhandler to keep to spec
1143 * and avoid breaking Firefox in rare cases where the headers and
1144 * body are broken over two packets.
1145 */
1146function wfClearOutputBuffers() {
1147	wfResetOutputBuffers( false );
1148}
1149
1150/**
1151 * Converts an Accept-* header into an array mapping string values to quality
1152 * factors
1153 */
1154function wfAcceptToPrefs( $accept, $def = '*/*' ) {
1155	# No arg means accept anything (per HTTP spec)
1156	if( !$accept ) {
1157		return array( $def => 1 );
1158	}
1159
1160	$prefs = array();
1161
1162	$parts = explode( ',', $accept );
1163
1164	foreach( $parts as $part ) {
1165		# FIXME: doesn't deal with params like 'text/html; level=1'
1166		@list( $value, $qpart ) = explode( ';', $part );
1167		$match = array();
1168		if( !isset( $qpart ) ) {
1169			$prefs[$value] = 1;
1170		} elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
1171			$prefs[$value] = $match[1];
1172		}
1173	}
1174
1175	return $prefs;
1176}
1177
1178/**
1179 * Checks if a given MIME type matches any of the keys in the given
1180 * array. Basic wildcards are accepted in the array keys.
1181 *
1182 * Returns the matching MIME type (or wildcard) if a match, otherwise
1183 * NULL if no match.
1184 *
1185 * @param string $type
1186 * @param array $avail
1187 * @return string
1188 * @private
1189 */
1190function mimeTypeMatch( $type, $avail ) {
1191	if( array_key_exists($type, $avail) ) {
1192		return $type;
1193	} else {
1194		$parts = explode( '/', $type );
1195		if( array_key_exists( $parts[0] . '/*', $avail ) ) {
1196			return $parts[0] . '/*';
1197		} elseif( array_key_exists( '*/*', $avail ) ) {
1198			return '*/*';
1199		} else {
1200			return NULL;
1201		}
1202	}
1203}
1204
1205/**
1206 * Returns the 'best' match between a client's requested internet media types
1207 * and the server's list of available types. Each list should be an associative
1208 * array of type to preference (preference is a float between 0.0 and 1.0).
1209 * Wildcards in the types are acceptable.
1210 *
1211 * @param array $cprefs Client's acceptable type list
1212 * @param array $sprefs Server's offered types
1213 * @return string
1214 *
1215 * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
1216 * XXX: generalize to negotiate other stuff
1217 */
1218function wfNegotiateType( $cprefs, $sprefs ) {
1219	$combine = array();
1220
1221	foreach( array_keys($sprefs) as $type ) {
1222		$parts = explode( '/', $type );
1223		if( $parts[1] != '*' ) {
1224			$ckey = mimeTypeMatch( $type, $cprefs );
1225			if( $ckey ) {
1226				$combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1227			}
1228		}
1229	}
1230
1231	foreach( array_keys( $cprefs ) as $type ) {
1232		$parts = explode( '/', $type );
1233		if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
1234			$skey = mimeTypeMatch( $type, $sprefs );
1235			if( $skey ) {
1236				$combine[$type] = $sprefs[$skey] * $cprefs[$type];
1237			}
1238		}
1239	}
1240
1241	$bestq = 0;
1242	$besttype = NULL;
1243
1244	foreach( array_keys( $combine ) as $type ) {
1245		if( $combine[$type] > $bestq ) {
1246			$besttype = $type;
1247			$bestq = $combine[$type];
1248		}
1249	}
1250
1251	return $besttype;
1252}
1253
1254/**
1255 * Array lookup
1256 * Returns an array where the values in the first array are replaced by the
1257 * values in the second array with the corresponding keys
1258 *
1259 * @return array
1260 */
1261function wfArrayLookup( $a, $b ) {
1262	return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
1263}
1264
1265/**
1266 * Convenience function; returns MediaWiki timestamp for the present time.
1267 * @return string
1268 */
1269function wfTimestampNow() {
1270	# return NOW
1271	return wfTimestamp( TS_MW, time() );
1272}
1273
1274/**
1275 * Reference-counted warning suppression
1276 */
1277function wfSuppressWarnings( $end = false ) {
1278	static $suppressCount = 0;
1279	static $originalLevel = false;
1280
1281	if ( $end ) {
1282		if ( $suppressCount ) {
1283			--$suppressCount;
1284			if ( !$suppressCount ) {
1285				error_reporting( $originalLevel );
1286			}
1287		}
1288	} else {
1289		if ( !$suppressCount ) {
1290			$originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE ) );
1291		}
1292		++$suppressCount;
1293	}
1294}
1295
1296/**
1297 * Restore error level to previous value
1298 */
1299function wfRestoreWarnings() {
1300	wfSuppressWarnings( true );
1301}
1302
1303# Autodetect, convert and provide timestamps of various types
1304
1305/**
1306 * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
1307 */
1308define('TS_UNIX', 0);
1309
1310/**
1311 * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
1312 */
1313define('TS_MW', 1);
1314
1315/**
1316 * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
1317 */
1318define('TS_DB', 2);
1319
1320/**
1321 * RFC 2822 format, for E-mail and HTTP headers
1322 */
1323define('TS_RFC2822', 3);
1324
1325/**
1326 * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
1327 *
1328 * This is used by Special:Export
1329 */
1330define('TS_ISO_8601', 4);
1331
1332/**
1333 * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
1334 *
1335 * @url http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
1336 *       DateTime tag and page 36 for the DateTimeOriginal and
1337 *       DateTimeDigitized tags.
1338 */
1339define('TS_EXIF', 5);
1340
1341/**
1342 * Oracle format time.
1343 */
1344define('TS_ORACLE', 6);
1345
1346/**
1347 * Postgres format time.
1348 */
1349define('TS_POSTGRES', 7);
1350
1351/**
1352 * @param mixed $outputtype A timestamp in one of the supported formats, the
1353 *                          function will autodetect which format is supplied
1354 *                          and act accordingly.
1355 * @return string Time in the format specified in $outputtype
1356 */
1357function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
1358	$uts = 0;
1359	$da = array();
1360	if ($ts==0) {
1361		$uts=time();
1362	} elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
1363		# TS_DB
1364		$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1365			    (int)$da[2],(int)$da[3],(int)$da[1]);
1366	} elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
1367		# TS_EXIF
1368		$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1369			(int)$da[2],(int)$da[3],(int)$da[1]);
1370	} elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) {
1371		# TS_MW
1372		$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1373			    (int)$da[2],(int)$da[3],(int)$da[1]);
1374	} elseif (preg_match('/^(\d{1,13})$/D',$ts,$da)) {
1375		# TS_UNIX
1376		$uts = $ts;
1377	} elseif (preg_match('/^(\d{1,2})-(...)-(\d\d(\d\d)?) (\d\d)\.(\d\d)\.(\d\d)/', $ts, $da)) {
1378		# TS_ORACLE
1379		$uts = strtotime(preg_replace('/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
1380				str_replace("+00:00", "UTC", $ts)));
1381	} elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/', $ts, $da)) {
1382		# TS_ISO_8601
1383		$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1384			(int)$da[2],(int)$da[3],(int)$da[1]);
1385	} elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)[\+\- ](\d\d)$/',$ts,$da)) {
1386		# TS_POSTGRES
1387		$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1388		(int)$da[2],(int)$da[3],(int)$da[1]);
1389	} elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/',$ts,$da)) {
1390		# TS_POSTGRES
1391		$uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1392		(int)$da[2],(int)$da[3],(int)$da[1]);
1393	} else {
1394		# Bogus value; fall back to the epoch...
1395		wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
1396		$uts = 0;
1397	}
1398
1399
1400 	switch($outputtype) {
1401		case TS_UNIX:
1402			return $uts;
1403		case TS_MW:
1404			return gmdate( 'YmdHis', $uts );
1405		case TS_DB:
1406			return gmdate( 'Y-m-d H:i:s', $uts );
1407		case TS_ISO_8601:
1408			return gmdate( 'Y-m-d\TH:i:s\Z', $uts );
1409		// This shouldn't ever be used, but is included for completeness
1410		case TS_EXIF:
1411			return gmdate(  'Y:m:d H:i:s', $uts );
1412		case TS_RFC2822:
1413			return gmdate( 'D, d M Y H:i:s', $uts ) . ' GMT';
1414		case TS_ORACLE:
1415			return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
1416		case TS_POSTGRES:
1417			return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
1418		default:
1419			throw new MWException( 'wfTimestamp() called with illegal output type.');
1420	}
1421}
1422
1423/**
1424 * Return a formatted timestamp, or null if input is null.
1425 * For dealing with nullable timestamp columns in the database.
1426 * @param int $outputtype
1427 * @param string $ts
1428 * @return string
1429 */
1430function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
1431	if( is_null( $ts ) ) {
1432		return null;
1433	} else {
1434		return wfTimestamp( $outputtype, $ts );
1435	}
1436}
1437
1438/**
1439 * Check if the operating system is Windows
1440 *
1441 * @return bool True if it's Windows, False otherwise.
1442 */
1443function wfIsWindows() {
1444	if (substr(php_uname(), 0, 7) == 'Windows') {
1445		return true;
1446	} else {
1447		return false;
1448	}
1449}
1450
1451/**
1452 * Swap two variables
1453 */
1454function swap( &$x, &$y ) {
1455	$z = $x;
1456	$x = $y;
1457	$y = $z;
1458}
1459
1460function wfGetCachedNotice( $name ) {
1461	global $wgOut, $parserMemc;
1462	$fname = 'wfGetCachedNotice';
1463	wfProfileIn( $fname );
1464	
1465	$needParse = false;
1466	
1467	if( $name === 'default' ) {
1468		// special case
1469		global $wgSiteNotice;
1470		$notice = $wgSiteNotice;
1471		if( empty( $notice ) ) {
1472			wfProfileOut( $fname );
1473			return false;
1474		}
1475	} else {
1476		$notice = wfMsgForContentNoTrans( $name );
1477		if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
1478			wfProfileOut( $fname );
1479			return( false );
1480		}
1481	}
1482	
1483	$cachedNotice = $parserMemc->get( wfMemcKey( $name ) );
1484	if( is_array( $cachedNotice ) ) {
1485		if( md5( $notice ) == $cachedNotice['hash'] ) {
1486			$notice = $cachedNotice['html'];
1487		} else {
1488			$needParse = true;
1489		}
1490	} else {
1491		$needParse = true;
1492	}
1493	
1494	if( $needParse ) {
1495		if( is_object( $wgOut ) ) {
1496			$parsed = $wgOut->parse( $notice );
1497			$parserMemc->set( wfMemcKey( $name ), array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
1498			$notice = $parsed;
1499		} else {
1500			wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' );
1501			$notice = '';
1502		}
1503	}
1504	
1505	wfProfileOut( $fname );
1506	return $notice;
1507}
1508
1509function wfGetNamespaceNotice() {
1510	global $wgTitle;
1511	
1512	# Paranoia
1513	if ( !isset( $wgTitle ) || !is_object( $wgTitle ) )
1514		return "";
1515
1516	$fname = 'wfGetNamespaceNotice';
1517	wfProfileIn( $fname );
1518	
1519	$key = "namespacenotice-" . $wgTitle->getNsText();
1520	$namespaceNotice = wfGetCachedNotice( $key );
1521	if ( $namespaceNotice && substr ( $namespaceNotice , 0 ,7 ) != "<p>&lt;" ) {
1522		 $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . "</div>";
1523	} else {
1524		$namespaceNotice = "";
1525	}
1526
1527	wfProfileOut( $fname );
1528	return $namespaceNotice;
1529}
1530
1531function wfGetSiteNotice() {
1532	global $wgUser, $wgSiteNotice;
1533	$fname = 'wfGetSiteNotice';
1534	wfProfileIn( $fname );
1535	$siteNotice = '';	
1536	
1537	if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
1538		if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
1539			$siteNotice = wfGetCachedNotice( 'sitenotice' );
1540		} else {
1541			$anonNotice = wfGetCachedNotice( 'anonnotice' );
1542			if( !$anonNotice ) {
1543				$siteNotice = wfGetCachedNotice( 'sitenotice' );
1544			} else {
1545				$siteNotice = $anonNotice;
1546			}
1547		}
1548		if( !$siteNotice ) {
1549			$siteNotice = wfGetCachedNotice( 'default' );
1550		}
1551	}
1552
1553	wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
1554	wfProfileOut( $fname );
1555	return $siteNotice;
1556}
1557
1558/** 
1559 * BC wrapper for MimeMagic::singleton()
1560 * @deprecated
1561 */
1562function &wfGetMimeMagic() {
1563	return MimeMagic::singleton();
1564}
1565
1566/**
1567 * Tries to get the system directory for temporary files.
1568 * The TMPDIR, TMP, and TEMP environment variables are checked in sequence,
1569 * and if none are set /tmp is returned as the generic Unix default.
1570 *
1571 * NOTE: When possible, use the tempfile() function to create temporary
1572 * files to avoid race conditions on file creation, etc.
1573 *
1574 * @return string
1575 */
1576function wfTempDir() {
1577	foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
1578		$tmp = getenv( $var );
1579		if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
1580			return $tmp;
1581		}
1582	}
1583	# Hope this is Unix of some kind!
1584	return '/tmp';
1585}
1586
1587/**
1588 * Make directory, and make all parent directories if they don't exist
1589 */
1590function wfMkdirParents( $fullDir, $mode = 0777 ) {
1591	if ( strval( $fullDir ) === '' ) {
1592		return true;
1593	}
1594	
1595	# Go back through the paths to find the first directory that exists
1596	$currentDir = $fullDir;
1597	$createList = array();
1598	while ( strval( $currentDir ) !== '' && !file_exists( $currentDir ) ) {	
1599		# Strip trailing slashes
1600		$currentDir = rtrim( $currentDir, '/\\' );
1601
1602		# Add to create list
1603		$createList[] = $currentDir;
1604
1605		# Find next delimiter searching from the end
1606		$p = max( strrpos( $currentDir, '/' ), strrpos( $currentDir, '\\' ) );
1607		if ( $p === false ) {
1608			$currentDir = false;
1609		} else {
1610			$currentDir = substr( $currentDir, 0, $p );
1611		}
1612	}
1613	
1614	if ( count( $createList ) == 0 ) {
1615		# Directory specified already exists
1616		return true;
1617	} elseif ( $currentDir === false ) {
1618		# Went all the way back to root and it apparently doesn't exist
1619		return false;
1620	}
1621	
1622	# Now go forward creating directories
1623	$createList = array_reverse( $createList );
1624	foreach ( $createList as $dir ) {
1625		# use chmod to override the umask, as suggested by the PHP manual
1626		if ( !mkdir( $dir, $mode ) || !chmod( $dir, $mode ) ) {
1627			return false;
1628		} 
1629	}
1630	return true;
1631}
1632
1633/**
1634 * Increment a statistics counter
1635 */
1636 function wfIncrStats( $key ) {
1637	 global $wgMemc;
1638	 $key = wfMemcKey( 'stats', $key );
1639	 if ( is_null( $wgMemc->incr( $key ) ) ) {
1640		 $wgMemc->add( $key, 1 );
1641	 }
1642 }
1643
1644/**
1645 * @param mixed $nr The number to format
1646 * @param int $acc The number of digits after the decimal point, default 2
1647 * @param bool $round Whether or not to round the value, default true
1648 * @return float
1649 */
1650function wfPercent( $nr, $acc = 2, $round = true ) {
1651	$ret = sprintf( "%.${acc}f", $nr );
1652	return $round ? round( $ret, $acc ) . '%' : "$ret%";
1653}
1654
1655/**
1656 * Encrypt a username/password.
1657 *
1658 * @param string $userid ID of the user
1659 * @param string $password Password of the user
1660 * @return string Hashed password
1661 */
1662function wfEncryptPassword( $userid, $password ) {
1663	global $wgPasswordSalt;
1664	$p = md5( $password);
1665
1666	if($wgPasswordSalt)
1667		return md5( "{$userid}-{$p}" );
1668	else
1669		return $p;
1670}
1671
1672/**
1673 * Appends to second array if $value differs from that in $default
1674 */
1675function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
1676	if ( is_null( $changed ) ) {
1677		throw new MWException('GlobalFunctions::wfAppendToArrayIfNotDefault got null');
1678	}
1679	if ( $default[$key] !== $value ) {
1680		$changed[$key] = $value;
1681	}
1682}
1683
1684/**
1685 * Since wfMsg() and co suck, they don't return false if the message key they
1686 * looked up didn't exist but a XHTML string, this function checks for the
1687 * nonexistance of messages by looking at wfMsg() output
1688 *
1689 * @param $msg      The message key looked up
1690 * @param $wfMsgOut The output of wfMsg*()
1691 * @return bool
1692 */
1693function wfEmptyMsg( $msg, $wfMsgOut ) {
1694	return $wfMsgOut === "&lt;$msg&gt;";
1695}
1696
1697/**
1698 * Find out whether or not a mixed variable exists in a string
1699 *
1700 * @param mixed  needle
1701 * @param string haystack
1702 * @return bool
1703 */
1704function in_string( $needle, $str ) {
1705	return strpos( $str, $needle ) !== false;
1706}
1707
1708function wfSpecialList( $page, $details ) {
1709	global $wgContLang;
1710	$details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : "";
1711	return $page . $details;
1712}
1713
1714/**
1715 * Returns a regular expression of url protocols
1716 *
1717 * @return string
1718 */
1719function wfUrlProtocols() {
1720	global $wgUrlProtocols;
1721
1722	// Support old-style $wgUrlProtocols strings, for backwards compatibility
1723	// with LocalSettings files from 1.5
1724	if ( is_array( $wgUrlProtocols ) ) {
1725		$protocols = array();
1726		foreach ($wgUrlProtocols as $protocol)
1727			$protocols[] = preg_quote( $protocol, '/' );
1728
1729		return implode( '|', $protocols );
1730	} else {
1731		return $wgUrlProtocols;
1732	}
1733}
1734
1735/**
1736 * Execute a shell command, with time and memory limits mirrored from the PHP
1737 * configuration if supported.
1738 * @param $cmd Command line, properly escaped for shell.
1739 * @param &$retval optional, will receive the program's exit code.
1740 *                 (non-zero is usually failure)
1741 * @return collected stdout as a string (trailing newlines stripped)
1742 */
1743function wfShellExec( $cmd, &$retval=null ) {
1744	global $IP, $wgMaxShellMemory, $wgMaxShellFileSize;
1745	
1746	if( ini_get( 'safe_mode' ) ) {
1747		wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
1748		$retval = 1;
1749		return "Unable to run external programs in safe mode.";
1750	}
1751
1752	if ( php_uname( 's' ) == 'Linux' ) {
1753		$time = ini_get( 'max_execution_time' );
1754		$mem = intval( $wgMaxShellMemory );
1755		$filesize = intval( $wgMaxShellFileSize );
1756
1757		if ( $time > 0 && $mem > 0 ) {
1758			$script = "$IP/bin/ulimit-tvf.sh";
1759			if ( is_executable( $script ) ) {
1760				$cmd = escapeshellarg( $script ) . " $time $mem $filesize $cmd";
1761			}
1762		}
1763	} elseif ( php_uname( 's' ) == 'Windows NT' ) {
1764		# This is a hack to work around PHP's flawed invocation of cmd.exe
1765		# http://news.php.net/php.internals/21796
1766		$cmd = '"' . $cmd . '"';
1767	}
1768	wfDebug( "wfShellExec: $cmd\n" );
1769	
1770	$output = array();
1771	$retval = 1; // error by default?
1772	exec( $cmd, $output, $retval ); // returns the last line of output.
1773	return implode( "\n", $output );
1774	
1775}
1776
1777/**
1778 * This function works like "use VERSION" in Perl, the program will die with a
1779 * backtrace if the current version of PHP is less than the version provided
1780 *
1781 * This is useful for extensions which due to their nature are not kept in sync
1782 * with releases, and might depend on other versions of PHP than the main code
1783 *
1784 * Note: PHP might die due to parsing errors in some cases before it ever
1785 *       manages to call this function, such is life
1786 *
1787 * @see perldoc -f use
1788 *
1789 * @param mixed $version The version to check, can be a string, an integer, or
1790 *                       a float
1791 */
1792function wfUsePHP( $req_ver ) {
1793	$php_ver = PHP_VERSION;
1794
1795	if ( version_compare( $php_ver, (string)$req_ver, '<' ) )
1796		 throw new MWException( "PHP $req_ver required--this is only $php_ver" );
1797}
1798
1799/**
1800 * This function works like "use VERSION" in Perl except it checks the version
1801 * of MediaWiki, the program will die with a backtrace if the current version
1802 * of MediaWiki is less than the version provided.
1803 *
1804 * This is useful for extensions which due to their nature are not kept in sync
1805 * with releases
1806 *
1807 * @see perldoc -f use
1808 *
1809 * @param mixed $version The version to check, can be a string, an integer, or
1810 *                       a float
1811 */
1812function wfUseMW( $req_ver ) {
1813	global $wgVersion;
1814
1815	if ( version_compare( $wgVersion, (string)$req_ver, '<' ) )
1816		throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
1817}
1818
1819/**
1820 * @deprecated use StringUtils::escapeRegexReplacement
1821 */
1822function wfRegexReplacement( $string ) {
1823	return StringUtils::escapeRegexReplacement( $string );
1824}
1825
1826/**
1827 * Return the final portion of a pathname.
1828 * Reimplemented because PHP5's basename() is buggy with multibyte text.
1829 * http://bugs.php.net/bug.php?id=33898
1830 *
1831 * PHP's basename() only considers '\' a pathchar on Windows and Netware.
1832 * We'll consider it so always, as we don't want \s in our Unix paths either.
1833 * 
1834 * @param string $path
1835 * @return string
1836 */
1837function wfBaseName( $path ) {
1838	$matches = array();
1839	if( preg_match( '#([^/\\\\]*)[/\\\\]*$#', $path, $matches ) ) {
1840		return $matches[1];
1841	} else {
1842		return '';
1843	}
1844}
1845
1846/**
1847 * Make a URL index, appropriate for the el_index field of externallinks.
1848 */
1849function wfMakeUrlIndex( $url ) {
1850	wfSuppressWarnings();
1851	$bits = parse_url( $url );
1852	wfRestoreWarnings();
1853	if ( !$bits || $bits['scheme'] !== 'http' ) {
1854		return false;
1855	}
1856	// Reverse the labels in the hostname, convert to lower case
1857	$reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
1858	// Add an extra dot to the end
1859	if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
1860		$reversedHost .= '.';
1861	}
1862	// Reconstruct the pseudo-URL
1863	$index = "http://$rever…

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