r53127 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r53126‎ | r53127 | r53128 >
Date:23:00, 11 July 2009
Author:jdpond
Status:deferred
Tags:
Comment:
Release 0.0 of NSFileRepo Extension
Modified paths:
  • /trunk/extensions/NSFileRepo/NSFileRepo.i18n.php (added) (history)
  • /trunk/extensions/NSFileRepo/NSFileRepo.php (added) (history)
  • /trunk/extensions/NSFileRepo/README (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_13_0 (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_13_0/phase3 (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_13_0/phase3/img_auth.i18n.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_13_0/phase3/img_auth.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_13_0/phase3/includes (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_13_0/phase3/includes/GlobalFunctions.patch (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_13_0/phase3/includes/GlobalFunctions.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_14_0 (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_14_0/phase3 (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_14_0/phase3/img_auth.i18n.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_14_0/phase3/img_auth.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_14_0/phase3/includes (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_14_0/phase3/includes/GlobalFunctions.patch (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_14_0/phase3/includes/GlobalFunctions.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_15_0 (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_15_0/phase3 (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_15_0/phase3/img_auth.i18n.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_15_0/phase3/img_auth.php (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_15_0/phase3/includes (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_15_0/phase3/includes/GlobalFunctions.patch (added) (history)
  • /trunk/extensions/NSFileRepo/REL1_15_0/phase3/includes/GlobalFunctions.php (added) (history)

Diff [purge]

Index: trunk/extensions/NSFileRepo/REL1_13_0/phase3/img_auth.i18n.php
@@ -0,0 +1,32 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for img_auth script (see see http://www.mediawiki.org/wiki/Manual:Image_Authorization).
 5+*/
 6+
 7+$messages = array();
 8+
 9+/** English
 10+ * @author Jack D. Pond
 11+ */
 12+$messages['en'] = array(
 13+ 'image_auth-desc' => 'Image authorisation script',
 14+ 'image_auth-nopathinfo' => "Missing PATH_INFO. Your server is not set up to pass this information -
 15+may be CGI-based and can't support img_auth. See `Image Authorization` on MediaWiki.",
 16+ 'image_auth-notindir' => "Requested path not in upload directory.",
 17+ 'image_auth-badtitle' => "Unable to construct a valid Title from `$1`.",
 18+ 'image_auth-nologinnWL' => "Not logged in and `$1` not in whitelist.",
 19+ 'image_auth-nofile' => "`$1` does not exist.",
 20+ 'image_auth-isdir' => "`$1` is a directory.",
 21+ 'image_auth-streaming' => "Streaming `$1`.",
 22+ 'image_auth-public' => "The function of img_auth.php is to output files from a private wiki. This wiki
 23+is configured as a public wiki. For optimal security, img_auth.php is disabled for this case.",
 24+ 'image_auth-noread' => "User does not have access to read `$1`."
 25+);
 26+
 27+/** Message documentation (Message documentation)
 28+ * @author Jack D. Pond
 29+ */
 30+$messages['qqq'] = array(
 31+ 'image_auth-desc' => 'Image authorisation script'
 32+);
 33+
Property changes on: trunk/extensions/NSFileRepo/REL1_13_0/phase3/img_auth.i18n.php
___________________________________________________________________
Added: svn:eol-style
134 + native
Index: trunk/extensions/NSFileRepo/REL1_13_0/phase3/img_auth.php
@@ -0,0 +1,136 @@
 2+<?php
 3+
 4+/**
 5+ * Image authorisation script
 6+ *
 7+ * To use this, see http://www.mediawiki.org/wiki/Manual:Image_Authorization
 8+ *
 9+ * - Set $wgUploadDirectory to a non-public directory (not web accessible)
 10+ * - Set $wgUploadPath to point to this file
 11+ *
 12+ * Your server needs to support PATH_INFO; CGI-based configurations usually don't.
 13+ *
 14+ * @file
 15+ */
 16+
 17+
 18+/**
 19+ For security reasons, you usually don't want your user to know access was denied, just that it was.
 20+ If you want to change this, you can set $wgImgAuthDetails to 'true' in localsettings.php and it will give the user the reason
 21+ why access was denied.
 22+**/
 23+
 24+global $wgImgAuthDetails;
 25+$wgImgAuthDetails = false;
 26+
 27+define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
 28+require_once( dirname( __FILE__ ) . '/includes/WebStart.php' );
 29+wfProfileIn( 'img_auth.php' );
 30+require_once( dirname( __FILE__ ) . '/includes/StreamFile.php' );
 31+
 32+global $wgMessageCache, $messages;
 33+require_once( dirname( __FILE__ ) . '/img_auth.i18n.php' );
 34+foreach( $messages as $lang => $LangMsg )
 35+ $wgMessageCache->addMessages( $LangMsg, $lang );
 36+
 37+$perms = User::getGroupPermissions( array( '*' ) );
 38+
 39+// See if this is a public Wiki (no protections)
 40+if ( in_array( 'read', $perms, true ) )
 41+ wfPublicError(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-public'));
 42+
 43+// Extract path and image information
 44+if( !isset( $_SERVER['PATH_INFO'] ) )
 45+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nopathinfo'));
 46+
 47+$path = $_SERVER['PATH_INFO'];
 48+$filename = realpath( $wgUploadDirectory . $_SERVER['PATH_INFO'] );
 49+$realUpload = realpath( $wgUploadDirectory );
 50+
 51+// Basic directory traversal check
 52+if( substr( $filename, 0, strlen( $realUpload ) ) != $realUpload )
 53+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-notindir'));
 54+
 55+// Extract the file name and chop off the size specifier
 56+// (e.g. 120px-Foo.png => Foo.png)
 57+$name = wfBaseName( $path );
 58+if( preg_match( '!\d+px-(.*)!i', $name, $m ) )
 59+ $name = $m[1];
 60+
 61+// Check to see if the file exists
 62+if( !file_exists( $filename ) )
 63+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nofile',$filename));
 64+
 65+// Check to see if tried to access a directory
 66+if( is_dir( $filename ) )
 67+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-isdir',$filename));
 68+
 69+
 70+$title = Title::makeTitleSafe( NS_FILE, $name );
 71+
 72+// See if could create the title object
 73+if( !$title instanceof Title )
 74+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-badtitle',$name));
 75+
 76+// Run hook
 77+if (!wfRunHooks( 'ImgAuthBeforeStream', array( &$title, &$path, &$name, &$result ) ) )
 78+ wfForbidden($result[0],$result[1]);
 79+
 80+// Check the whitelist if needed, deprecated since usercan added
 81+// $pTitle = $title->getPrefixedText();
 82+// if( !$wgUser->getId() && ( !is_array( $wgWhitelistRead ) || !in_array( $pTitle, $wgWhitelistRead ) ) )
 83+// wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nologinnWL',$pTitle));
 84+
 85+
 86+// Check user authorization for this title
 87+if( !$title->userCanRead() )
 88+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-noread',$name));
 89+
 90+
 91+// Stream the requested file
 92+wfDebugLog( 'img_auth', "Streaming `{$filename}`" );
 93+wfStreamFile( $filename, array( 'Cache-Control: private', 'Vary: Cookie' ) );
 94+wfLogProfilingData();
 95+
 96+/**
 97+ * Issue a standard HTTP 403 Forbidden header ($msg1) and an
 98+ * error message ($msg2), then end the script
 99+ */
 100+function wfForbidden($msg1,$msg2) {
 101+ global $wgImgAuthDetails;
 102+ $detailMsg = $wgImgAuthDetails ? $msg2 : wfMsgHTML('badaccess-group0');
 103+ wfDebugLog( 'img_auth', "wfForbidden Msg: ".$msg2 );
 104+ header( 'HTTP/1.0 403 Forbidden' );
 105+ header( 'Vary: Cookie' );
 106+ header( 'Content-Type: text/html; charset=utf-8' );
 107+ echo <<<ENDS
 108+<html>
 109+<body>
 110+<h1>$msg1</h1>
 111+<p>$detailMsg</p>
 112+</body>
 113+</html>
 114+ENDS;
 115+ wfLogProfilingData();
 116+ exit();
 117+}
 118+
 119+/**
 120+ * Show a 403 error for use when the wiki is public
 121+ */
 122+function wfPublicError($msg1,$msg2) {
 123+ header( 'HTTP/1.0 403 Forbidden' );
 124+ header( 'Content-Type: text/html; charset=utf-8' );
 125+ wfDebugLog( 'img_auth', "wfPublicError Msg: ".$msg2 );
 126+ echo <<<ENDS
 127+<html>
 128+<body>
 129+<h1>$msg1</h1>
 130+<p>$msg2</p>
 131+</body>
 132+</html>
 133+ENDS;
 134+ wfLogProfilingData();
 135+ exit;
 136+}
 137+
Property changes on: trunk/extensions/NSFileRepo/REL1_13_0/phase3/img_auth.php
___________________________________________________________________
Added: svn:eol-style
1138 + native
Index: trunk/extensions/NSFileRepo/REL1_13_0/phase3/includes/GlobalFunctions.php
@@ -0,0 +1,2769 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ die( "This file is part of MediaWiki, it is not a valid entry point" );
 6+}
 7+
 8+/**
 9+ * Global functions used everywhere
 10+ */
 11+
 12+require_once dirname(__FILE__) . '/LogPage.php';
 13+require_once dirname(__FILE__) . '/normal/UtfNormalUtil.php';
 14+require_once dirname(__FILE__) . '/XmlFunctions.php';
 15+
 16+/**
 17+ * Compatibility functions
 18+ *
 19+ * We more or less support PHP 5.0.x and up.
 20+ * Re-implementations of newer functions or functions in non-standard
 21+ * PHP extensions may be included here.
 22+ */
 23+if( !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(strcasecmp( $from, $to ) == 0) return $string;
 29+ if(strcasecmp( $from, 'utf-8' ) == 0) return utf8_decode( $string );
 30+ if(strcasecmp( $to, 'utf-8' ) == 0) return utf8_encode( $string );
 31+ return $string;
 32+ }
 33+}
 34+
 35+# UTF-8 substr function based on a PHP manual comment
 36+if ( !function_exists( 'mb_substr' ) ) {
 37+ function mb_substr( $str, $start ) {
 38+ $ar = array();
 39+ preg_match_all( '/./us', $str, $ar );
 40+
 41+ if( func_num_args() >= 3 ) {
 42+ $end = func_get_arg( 2 );
 43+ return join( '', array_slice( $ar[0], $start, $end ) );
 44+ } else {
 45+ return join( '', array_slice( $ar[0], $start ) );
 46+ }
 47+ }
 48+}
 49+
 50+if ( !function_exists( 'mb_strlen' ) ) {
 51+ /**
 52+ * Fallback implementation of mb_strlen, hardcoded to UTF-8.
 53+ * @param string $str
 54+ * @param string $enc optional encoding; ignored
 55+ * @return int
 56+ */
 57+ function mb_strlen( $str, $enc="" ) {
 58+ $counts = count_chars( $str );
 59+ $total = 0;
 60+
 61+ // Count ASCII bytes
 62+ for( $i = 0; $i < 0x80; $i++ ) {
 63+ $total += $counts[$i];
 64+ }
 65+
 66+ // Count multibyte sequence heads
 67+ for( $i = 0xc0; $i < 0xff; $i++ ) {
 68+ $total += $counts[$i];
 69+ }
 70+ return $total;
 71+ }
 72+}
 73+
 74+if ( !function_exists( 'array_diff_key' ) ) {
 75+ /**
 76+ * Exists in PHP 5.1.0+
 77+ * Not quite compatible, two-argument version only
 78+ * Null values will cause problems due to this use of isset()
 79+ */
 80+ function array_diff_key( $left, $right ) {
 81+ $result = $left;
 82+ foreach ( $left as $key => $unused ) {
 83+ if ( isset( $right[$key] ) ) {
 84+ unset( $result[$key] );
 85+ }
 86+ }
 87+ return $result;
 88+ }
 89+}
 90+
 91+/**
 92+ * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
 93+ */
 94+function wfArrayDiff2( $a, $b ) {
 95+ return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
 96+}
 97+function wfArrayDiff2_cmp( $a, $b ) {
 98+ if ( !is_array( $a ) ) {
 99+ return strcmp( $a, $b );
 100+ } elseif ( count( $a ) !== count( $b ) ) {
 101+ return count( $a ) < count( $b ) ? -1 : 1;
 102+ } else {
 103+ reset( $a );
 104+ reset( $b );
 105+ while( ( list( $keyA, $valueA ) = each( $a ) ) && ( list( $keyB, $valueB ) = each( $b ) ) ) {
 106+ $cmp = strcmp( $valueA, $valueB );
 107+ if ( $cmp !== 0 ) {
 108+ return $cmp;
 109+ }
 110+ }
 111+ return 0;
 112+ }
 113+}
 114+
 115+/**
 116+ * Wrapper for clone(), for compatibility with PHP4-friendly extensions.
 117+ * PHP 5 won't let you declare a 'clone' function, even conditionally,
 118+ * so it has to be a wrapper with a different name.
 119+ */
 120+function wfClone( $object ) {
 121+ return clone( $object );
 122+}
 123+
 124+/**
 125+ * Seed Mersenne Twister
 126+ * No-op for compatibility; only necessary in PHP < 4.2.0
 127+ */
 128+function wfSeedRandom() {
 129+ /* No-op */
 130+}
 131+
 132+/**
 133+ * Get a random decimal value between 0 and 1, in a way
 134+ * not likely to give duplicate values for any realistic
 135+ * number of articles.
 136+ *
 137+ * @return string
 138+ */
 139+function wfRandom() {
 140+ # The maximum random value is "only" 2^31-1, so get two random
 141+ # values to reduce the chance of dupes
 142+ $max = mt_getrandmax() + 1;
 143+ $rand = number_format( (mt_rand() * $max + mt_rand())
 144+ / $max / $max, 12, '.', '' );
 145+ return $rand;
 146+}
 147+
 148+/**
 149+ * We want / and : to be included as literal characters in our title URLs.
 150+ * %2F in the page titles seems to fatally break for some reason.
 151+ *
 152+ * @param $s String:
 153+ * @return string
 154+*/
 155+function wfUrlencode ( $s ) {
 156+ $s = urlencode( $s );
 157+ $s = preg_replace( '/%3[Aa]/', ':', $s );
 158+ $s = preg_replace( '/%2[Ff]/', '/', $s );
 159+
 160+ return $s;
 161+}
 162+
 163+/**
 164+ * Sends a line to the debug log if enabled or, optionally, to a comment in output.
 165+ * In normal operation this is a NOP.
 166+ *
 167+ * Controlling globals:
 168+ * $wgDebugLogFile - points to the log file
 169+ * $wgProfileOnly - if set, normal debug messages will not be recorded.
 170+ * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
 171+ * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
 172+ *
 173+ * @param $text String
 174+ * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
 175+ */
 176+function wfDebug( $text, $logonly = false ) {
 177+ global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
 178+ static $recursion = 0;
 179+
 180+ static $cache = array(); // Cache of unoutputted messages
 181+
 182+ # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
 183+ if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
 184+ return;
 185+ }
 186+
 187+ if ( $wgDebugComments && !$logonly ) {
 188+ $cache[] = $text;
 189+
 190+ if ( !isset( $wgOut ) ) {
 191+ return;
 192+ }
 193+ if ( !StubObject::isRealObject( $wgOut ) ) {
 194+ if ( $recursion ) {
 195+ return;
 196+ }
 197+ $recursion++;
 198+ $wgOut->_unstub();
 199+ $recursion--;
 200+ }
 201+
 202+ // add the message and possible cached ones to the output
 203+ array_map( array( $wgOut, 'debug' ), $cache );
 204+ $cache = array();
 205+ }
 206+ if ( '' != $wgDebugLogFile && !$wgProfileOnly ) {
 207+ # Strip unprintables; they can switch terminal modes when binary data
 208+ # gets dumped, which is pretty annoying.
 209+ $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
 210+ wfErrorLog( $text, $wgDebugLogFile );
 211+ }
 212+}
 213+
 214+/**
 215+ * Send a line to a supplementary debug log file, if configured, or main debug log if not.
 216+ * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
 217+ *
 218+ * @param $logGroup String
 219+ * @param $text String
 220+ * @param $public Bool: whether to log the event in the public log if no private
 221+ * log file is specified, (default true)
 222+ */
 223+function wfDebugLog( $logGroup, $text, $public = true ) {
 224+ global $wgDebugLogGroups;
 225+ if( $text{strlen( $text ) - 1} != "\n" ) $text .= "\n";
 226+ if( isset( $wgDebugLogGroups[$logGroup] ) ) {
 227+ $time = wfTimestamp( TS_DB );
 228+ $wiki = wfWikiID();
 229+ wfErrorLog( "$time $wiki: $text", $wgDebugLogGroups[$logGroup] );
 230+ } else if ( $public === true ) {
 231+ wfDebug( $text, true );
 232+ }
 233+}
 234+
 235+/**
 236+ * Log for database errors
 237+ * @param $text String: database error message.
 238+ */
 239+function wfLogDBError( $text ) {
 240+ global $wgDBerrorLog, $wgDBname;
 241+ if ( $wgDBerrorLog ) {
 242+ $host = trim(`hostname`);
 243+ $text = date('D M j G:i:s T Y') . "\t$host\t$wgDBname\t$text";
 244+ wfErrorLog( $text, $wgDBerrorLog );
 245+ }
 246+}
 247+
 248+/**
 249+ * Log to a file without getting "file size exceeded" signals
 250+ */
 251+function wfErrorLog( $text, $file ) {
 252+ wfSuppressWarnings();
 253+ $exists = file_exists( $file );
 254+ $size = $exists ? filesize( $file ) : false;
 255+ if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
 256+ error_log( $text, 3, $file );
 257+ }
 258+ wfRestoreWarnings();
 259+}
 260+
 261+/**
 262+ * @todo document
 263+ */
 264+function wfLogProfilingData() {
 265+ global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
 266+ global $wgProfiler, $wgUser;
 267+ if ( !isset( $wgProfiler ) )
 268+ return;
 269+
 270+ $now = wfTime();
 271+ $elapsed = $now - $wgRequestTime;
 272+ $prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
 273+ $forward = '';
 274+ if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
 275+ $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
 276+ if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) )
 277+ $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
 278+ if( !empty( $_SERVER['HTTP_FROM'] ) )
 279+ $forward .= ' from ' . $_SERVER['HTTP_FROM'];
 280+ if( $forward )
 281+ $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
 282+ // Don't unstub $wgUser at this late stage just for statistics purposes
 283+ if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() )
 284+ $forward .= ' anon';
 285+ $log = sprintf( "%s\t%04.3f\t%s\n",
 286+ gmdate( 'YmdHis' ), $elapsed,
 287+ urldecode( $wgRequest->getRequestURL() . $forward ) );
 288+ if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) {
 289+ wfErrorLog( $log . $prof, $wgDebugLogFile );
 290+ }
 291+}
 292+
 293+/**
 294+ * Check if the wiki read-only lock file is present. This can be used to lock
 295+ * off editing functions, but doesn't guarantee that the database will not be
 296+ * modified.
 297+ * @return bool
 298+ */
 299+function wfReadOnly() {
 300+ global $wgReadOnlyFile, $wgReadOnly;
 301+
 302+ if ( !is_null( $wgReadOnly ) ) {
 303+ return (bool)$wgReadOnly;
 304+ }
 305+ if ( '' == $wgReadOnlyFile ) {
 306+ return false;
 307+ }
 308+ // Set $wgReadOnly for faster access next time
 309+ if ( is_file( $wgReadOnlyFile ) ) {
 310+ $wgReadOnly = file_get_contents( $wgReadOnlyFile );
 311+ } else {
 312+ $wgReadOnly = false;
 313+ }
 314+ return (bool)$wgReadOnly;
 315+}
 316+
 317+function wfReadOnlyReason() {
 318+ global $wgReadOnly;
 319+ wfReadOnly();
 320+ return $wgReadOnly;
 321+}
 322+
 323+/**
 324+ * Get a message from anywhere, for the current user language.
 325+ *
 326+ * Use wfMsgForContent() instead if the message should NOT
 327+ * change depending on the user preferences.
 328+ *
 329+ * @param $key String: lookup key for the message, usually
 330+ * defined in languages/Language.php
 331+ *
 332+ * This function also takes extra optional parameters (not
 333+ * shown in the function definition), which can by used to
 334+ * insert variable text into the predefined message.
 335+ */
 336+function wfMsg( $key ) {
 337+ $args = func_get_args();
 338+ array_shift( $args );
 339+ return wfMsgReal( $key, $args, true );
 340+}
 341+
 342+/**
 343+ * Same as above except doesn't transform the message
 344+ */
 345+function wfMsgNoTrans( $key ) {
 346+ $args = func_get_args();
 347+ array_shift( $args );
 348+ return wfMsgReal( $key, $args, true, false, false );
 349+}
 350+
 351+/**
 352+ * Get a message from anywhere, for the current global language
 353+ * set with $wgLanguageCode.
 354+ *
 355+ * Use this if the message should NOT change dependent on the
 356+ * language set in the user's preferences. This is the case for
 357+ * most text written into logs, as well as link targets (such as
 358+ * the name of the copyright policy page). Link titles, on the
 359+ * other hand, should be shown in the UI language.
 360+ *
 361+ * Note that MediaWiki allows users to change the user interface
 362+ * language in their preferences, but a single installation
 363+ * typically only contains content in one language.
 364+ *
 365+ * Be wary of this distinction: If you use wfMsg() where you should
 366+ * use wfMsgForContent(), a user of the software may have to
 367+ * customize over 70 messages in order to, e.g., fix a link in every
 368+ * possible language.
 369+ *
 370+ * @param $key String: lookup key for the message, usually
 371+ * defined in languages/Language.php
 372+ */
 373+function wfMsgForContent( $key ) {
 374+ global $wgForceUIMsgAsContentMsg;
 375+ $args = func_get_args();
 376+ array_shift( $args );
 377+ $forcontent = true;
 378+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 379+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 380+ $forcontent = false;
 381+ return wfMsgReal( $key, $args, true, $forcontent );
 382+}
 383+
 384+/**
 385+ * Same as above except doesn't transform the message
 386+ */
 387+function wfMsgForContentNoTrans( $key ) {
 388+ global $wgForceUIMsgAsContentMsg;
 389+ $args = func_get_args();
 390+ array_shift( $args );
 391+ $forcontent = true;
 392+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 393+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 394+ $forcontent = false;
 395+ return wfMsgReal( $key, $args, true, $forcontent, false );
 396+}
 397+
 398+/**
 399+ * Get a message from the language file, for the UI elements
 400+ */
 401+function wfMsgNoDB( $key ) {
 402+ $args = func_get_args();
 403+ array_shift( $args );
 404+ return wfMsgReal( $key, $args, false );
 405+}
 406+
 407+/**
 408+ * Get a message from the language file, for the content
 409+ */
 410+function wfMsgNoDBForContent( $key ) {
 411+ global $wgForceUIMsgAsContentMsg;
 412+ $args = func_get_args();
 413+ array_shift( $args );
 414+ $forcontent = true;
 415+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 416+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 417+ $forcontent = false;
 418+ return wfMsgReal( $key, $args, false, $forcontent );
 419+}
 420+
 421+
 422+/**
 423+ * Really get a message
 424+ * @param $key String: key to get.
 425+ * @param $args
 426+ * @param $useDB Boolean
 427+ * @param $transform Boolean: Whether or not to transform the message.
 428+ * @param $forContent Boolean
 429+ * @return String: the requested message.
 430+ */
 431+function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) {
 432+ wfProfileIn( __METHOD__ );
 433+ $message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
 434+ $message = wfMsgReplaceArgs( $message, $args );
 435+ wfProfileOut( __METHOD__ );
 436+ return $message;
 437+}
 438+
 439+/**
 440+ * This function provides the message source for messages to be edited which are *not* stored in the database.
 441+ * @param $key String:
 442+ */
 443+function wfMsgWeirdKey ( $key ) {
 444+ $source = wfMsgGetKey( $key, false, true, false );
 445+ if ( wfEmptyMsg( $key, $source ) )
 446+ return "";
 447+ else
 448+ return $source;
 449+}
 450+
 451+/**
 452+ * Fetch a message string value, but don't replace any keys yet.
 453+ * @param string $key
 454+ * @param bool $useDB
 455+ * @param string $langcode Code of the language to get the message for, or
 456+ * behaves as a content language switch if it is a
 457+ * boolean.
 458+ * @return string
 459+ * @private
 460+ */
 461+function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
 462+ global $wgParser, $wgContLang, $wgMessageCache, $wgLang;
 463+
 464+ wfRunHooks('NormalizeMessageKey', array(&$key, &$useDB, &$langCode, &$transform));
 465+
 466+ # If $wgMessageCache isn't initialised yet, try to return something sensible.
 467+ if( is_object( $wgMessageCache ) ) {
 468+ $message = $wgMessageCache->get( $key, $useDB, $langCode );
 469+ if ( $transform ) {
 470+ $message = $wgMessageCache->transform( $message );
 471+ }
 472+ } else {
 473+ if( $langCode === true ) {
 474+ $lang = &$wgContLang;
 475+ } elseif( $langCode === false ) {
 476+ $lang = &$wgLang;
 477+ } else {
 478+ $validCodes = array_keys( Language::getLanguageNames() );
 479+ if( in_array( $langCode, $validCodes ) ) {
 480+ # $langcode corresponds to a valid language.
 481+ $lang = Language::factory( $langCode );
 482+ } else {
 483+ # $langcode is a string, but not a valid language code; use content language.
 484+ $lang =& $wgContLang;
 485+ wfDebug( 'Invalid language code passed to wfMsgGetKey, falling back to content language.' );
 486+ }
 487+ }
 488+
 489+ # MessageCache::get() does this already, Language::getMessage() doesn't
 490+ # ISSUE: Should we try to handle "message/lang" here too?
 491+ $key = str_replace( ' ' , '_' , $wgContLang->lcfirst( $key ) );
 492+
 493+ if( is_object( $lang ) ) {
 494+ $message = $lang->getMessage( $key );
 495+ } else {
 496+ $message = false;
 497+ }
 498+ }
 499+
 500+ return $message;
 501+}
 502+
 503+/**
 504+ * Replace message parameter keys on the given formatted output.
 505+ *
 506+ * @param string $message
 507+ * @param array $args
 508+ * @return string
 509+ * @private
 510+ */
 511+function wfMsgReplaceArgs( $message, $args ) {
 512+ # Fix windows line-endings
 513+ # Some messages are split with explode("\n", $msg)
 514+ $message = str_replace( "\r", '', $message );
 515+
 516+ // Replace arguments
 517+ if ( count( $args ) ) {
 518+ if ( is_array( $args[0] ) ) {
 519+ $args = array_values( $args[0] );
 520+ }
 521+ $replacementKeys = array();
 522+ foreach( $args as $n => $param ) {
 523+ $replacementKeys['$' . ($n + 1)] = $param;
 524+ }
 525+ $message = strtr( $message, $replacementKeys );
 526+ }
 527+
 528+ return $message;
 529+}
 530+
 531+/**
 532+ * Return an HTML-escaped version of a message.
 533+ * Parameter replacements, if any, are done *after* the HTML-escaping,
 534+ * so parameters may contain HTML (eg links or form controls). Be sure
 535+ * to pre-escape them if you really do want plaintext, or just wrap
 536+ * the whole thing in htmlspecialchars().
 537+ *
 538+ * @param string $key
 539+ * @param string ... parameters
 540+ * @return string
 541+ */
 542+function wfMsgHtml( $key ) {
 543+ $args = func_get_args();
 544+ array_shift( $args );
 545+ return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
 546+}
 547+
 548+/**
 549+ * Return an HTML version of message
 550+ * Parameter replacements, if any, are done *after* parsing the wiki-text message,
 551+ * so parameters may contain HTML (eg links or form controls). Be sure
 552+ * to pre-escape them if you really do want plaintext, or just wrap
 553+ * the whole thing in htmlspecialchars().
 554+ *
 555+ * @param string $key
 556+ * @param string ... parameters
 557+ * @return string
 558+ */
 559+function wfMsgWikiHtml( $key ) {
 560+ global $wgOut;
 561+ $args = func_get_args();
 562+ array_shift( $args );
 563+ return wfMsgReplaceArgs( $wgOut->parse( wfMsgGetKey( $key, true ), /* can't be set to false */ true ), $args );
 564+}
 565+
 566+/**
 567+ * Returns message in the requested format
 568+ * @param string $key Key of the message
 569+ * @param array $options Processing rules:
 570+ * <i>parse</i>: parses wikitext to html
 571+ * <i>parseinline</i>: parses wikitext to html and removes the surrounding p's added by parser or tidy
 572+ * <i>escape</i>: filters message through htmlspecialchars
 573+ * <i>escapenoentities</i>: same, but allows entity references like &nbsp; through
 574+ * <i>replaceafter</i>: parameters are substituted after parsing or escaping
 575+ * <i>parsemag</i>: transform the message using magic phrases
 576+ * <i>content</i>: fetch message for content language instead of interface
 577+ * <i>language</i>: language code to fetch message for (overriden by <i>content</i>), its behaviour
 578+ * with parser, parseinline and parsemag is undefined.
 579+ * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
 580+ */
 581+function wfMsgExt( $key, $options ) {
 582+ global $wgOut, $wgParser;
 583+
 584+ $args = func_get_args();
 585+ array_shift( $args );
 586+ array_shift( $args );
 587+
 588+ if( !is_array($options) ) {
 589+ $options = array($options);
 590+ }
 591+
 592+ if( in_array('content', $options) ) {
 593+ $forContent = true;
 594+ $langCode = true;
 595+ } elseif( array_key_exists('language', $options) ) {
 596+ $forContent = false;
 597+ $langCode = $options['language'];
 598+ $validCodes = array_keys( Language::getLanguageNames() );
 599+ if( !in_array($options['language'], $validCodes) ) {
 600+ # Fallback to en, instead of whatever interface language we might have
 601+ $langCode = 'en';
 602+ }
 603+ } else {
 604+ $forContent = false;
 605+ $langCode = false;
 606+ }
 607+
 608+ $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
 609+
 610+ if( !in_array('replaceafter', $options) ) {
 611+ $string = wfMsgReplaceArgs( $string, $args );
 612+ }
 613+
 614+ if( in_array('parse', $options) ) {
 615+ $string = $wgOut->parse( $string, true, !$forContent );
 616+ } elseif ( in_array('parseinline', $options) ) {
 617+ $string = $wgOut->parse( $string, true, !$forContent );
 618+ $m = array();
 619+ if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
 620+ $string = $m[1];
 621+ }
 622+ } elseif ( in_array('parsemag', $options) ) {
 623+ global $wgMessageCache;
 624+ if ( isset( $wgMessageCache ) ) {
 625+ $string = $wgMessageCache->transform( $string, !$forContent );
 626+ }
 627+ }
 628+
 629+ if ( in_array('escape', $options) ) {
 630+ $string = htmlspecialchars ( $string );
 631+ } elseif ( in_array( 'escapenoentities', $options ) ) {
 632+ $string = htmlspecialchars( $string );
 633+ $string = str_replace( '&amp;', '&', $string );
 634+ $string = Sanitizer::normalizeCharReferences( $string );
 635+ }
 636+
 637+ if( in_array('replaceafter', $options) ) {
 638+ $string = wfMsgReplaceArgs( $string, $args );
 639+ }
 640+
 641+ return $string;
 642+}
 643+
 644+
 645+/**
 646+ * Just like exit() but makes a note of it.
 647+ * Commits open transactions except if the error parameter is set
 648+ *
 649+ * @deprecated Please return control to the caller or throw an exception
 650+ */
 651+function wfAbruptExit( $error = false ){
 652+ static $called = false;
 653+ if ( $called ){
 654+ exit( -1 );
 655+ }
 656+ $called = true;
 657+
 658+ $bt = wfDebugBacktrace();
 659+ if( $bt ) {
 660+ for($i = 0; $i < count($bt) ; $i++){
 661+ $file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown";
 662+ $line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown";
 663+ wfDebug("WARNING: Abrupt exit in $file at line $line\n");
 664+ }
 665+ } else {
 666+ wfDebug('WARNING: Abrupt exit\n');
 667+ }
 668+
 669+ wfLogProfilingData();
 670+
 671+ if ( !$error ) {
 672+ wfGetLB()->closeAll();
 673+ }
 674+ exit( -1 );
 675+}
 676+
 677+/**
 678+ * @deprecated Please return control the caller or throw an exception
 679+ */
 680+function wfErrorExit() {
 681+ wfAbruptExit( true );
 682+}
 683+
 684+/**
 685+ * Print a simple message and die, returning nonzero to the shell if any.
 686+ * Plain die() fails to return nonzero to the shell if you pass a string.
 687+ * @param string $msg
 688+ */
 689+function wfDie( $msg='' ) {
 690+ echo $msg;
 691+ die( 1 );
 692+}
 693+
 694+/**
 695+ * Throw a debugging exception. This function previously once exited the process,
 696+ * but now throws an exception instead, with similar results.
 697+ *
 698+ * @param string $msg Message shown when dieing.
 699+ */
 700+function wfDebugDieBacktrace( $msg = '' ) {
 701+ throw new MWException( $msg );
 702+}
 703+
 704+/**
 705+ * Fetch server name for use in error reporting etc.
 706+ * Use real server name if available, so we know which machine
 707+ * in a server farm generated the current page.
 708+ * @return string
 709+ */
 710+function wfHostname() {
 711+ if ( function_exists( 'posix_uname' ) ) {
 712+ // This function not present on Windows
 713+ $uname = @posix_uname();
 714+ } else {
 715+ $uname = false;
 716+ }
 717+ if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
 718+ return $uname['nodename'];
 719+ } else {
 720+ # This may be a virtual server.
 721+ return $_SERVER['SERVER_NAME'];
 722+ }
 723+}
 724+
 725+ /**
 726+ * Returns a HTML comment with the elapsed time since request.
 727+ * This method has no side effects.
 728+ * @return string
 729+ */
 730+ function wfReportTime() {
 731+ global $wgRequestTime, $wgShowHostnames;
 732+
 733+ $now = wfTime();
 734+ $elapsed = $now - $wgRequestTime;
 735+
 736+ return $wgShowHostnames
 737+ ? sprintf( "<!-- Served by %s in %01.3f secs. -->", wfHostname(), $elapsed )
 738+ : sprintf( "<!-- Served in %01.3f secs. -->", $elapsed );
 739+ }
 740+
 741+/**
 742+ * Safety wrapper for debug_backtrace().
 743+ *
 744+ * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
 745+ * murky circumstances, which may be triggered in part by stub objects
 746+ * or other fancy talkin'.
 747+ *
 748+ * Will return an empty array if Zend Optimizer is detected, otherwise
 749+ * the output from debug_backtrace() (trimmed).
 750+ *
 751+ * @return array of backtrace information
 752+ */
 753+function wfDebugBacktrace() {
 754+ if( extension_loaded( 'Zend Optimizer' ) ) {
 755+ wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
 756+ return array();
 757+ } else {
 758+ return array_slice( debug_backtrace(), 1 );
 759+ }
 760+}
 761+
 762+function wfBacktrace() {
 763+ global $wgCommandLineMode;
 764+
 765+ if ( $wgCommandLineMode ) {
 766+ $msg = '';
 767+ } else {
 768+ $msg = "<ul>\n";
 769+ }
 770+ $backtrace = wfDebugBacktrace();
 771+ foreach( $backtrace as $call ) {
 772+ if( isset( $call['file'] ) ) {
 773+ $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
 774+ $file = $f[count($f)-1];
 775+ } else {
 776+ $file = '-';
 777+ }
 778+ if( isset( $call['line'] ) ) {
 779+ $line = $call['line'];
 780+ } else {
 781+ $line = '-';
 782+ }
 783+ if ( $wgCommandLineMode ) {
 784+ $msg .= "$file line $line calls ";
 785+ } else {
 786+ $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
 787+ }
 788+ if( !empty( $call['class'] ) ) $msg .= $call['class'] . '::';
 789+ $msg .= $call['function'] . '()';
 790+
 791+ if ( $wgCommandLineMode ) {
 792+ $msg .= "\n";
 793+ } else {
 794+ $msg .= "</li>\n";
 795+ }
 796+ }
 797+ if ( $wgCommandLineMode ) {
 798+ $msg .= "\n";
 799+ } else {
 800+ $msg .= "</ul>\n";
 801+ }
 802+
 803+ return $msg;
 804+}
 805+
 806+
 807+/* Some generic result counters, pulled out of SearchEngine */
 808+
 809+
 810+/**
 811+ * @todo document
 812+ */
 813+function wfShowingResults( $offset, $limit ) {
 814+ global $wgLang;
 815+ return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) );
 816+}
 817+
 818+/**
 819+ * @todo document
 820+ */
 821+function wfShowingResultsNum( $offset, $limit, $num ) {
 822+ global $wgLang;
 823+ return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
 824+}
 825+
 826+/**
 827+ * @todo document
 828+ */
 829+function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
 830+ global $wgLang;
 831+ $fmtLimit = $wgLang->formatNum( $limit );
 832+ $prev = wfMsg( 'prevn', $fmtLimit );
 833+ $next = wfMsg( 'nextn', $fmtLimit );
 834+
 835+ if( is_object( $link ) ) {
 836+ $title =& $link;
 837+ } else {
 838+ $title = Title::newFromText( $link );
 839+ if( is_null( $title ) ) {
 840+ return false;
 841+ }
 842+ }
 843+
 844+ if ( 0 != $offset ) {
 845+ $po = $offset - $limit;
 846+ if ( $po < 0 ) { $po = 0; }
 847+ $q = "limit={$limit}&offset={$po}";
 848+ if ( '' != $query ) { $q .= '&'.$query; }
 849+ $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-prevlink\">{$prev}</a>";
 850+ } else { $plink = $prev; }
 851+
 852+ $no = $offset + $limit;
 853+ $q = 'limit='.$limit.'&offset='.$no;
 854+ if ( '' != $query ) { $q .= '&'.$query; }
 855+
 856+ if ( $atend ) {
 857+ $nlink = $next;
 858+ } else {
 859+ $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-nextlink\">{$next}</a>";
 860+ }
 861+ $nums = wfNumLink( $offset, 20, $title, $query ) . ' | ' .
 862+ wfNumLink( $offset, 50, $title, $query ) . ' | ' .
 863+ wfNumLink( $offset, 100, $title, $query ) . ' | ' .
 864+ wfNumLink( $offset, 250, $title, $query ) . ' | ' .
 865+ wfNumLink( $offset, 500, $title, $query );
 866+
 867+ return wfMsg( 'viewprevnext', $plink, $nlink, $nums );
 868+}
 869+
 870+/**
 871+ * @todo document
 872+ */
 873+function wfNumLink( $offset, $limit, &$title, $query = '' ) {
 874+ global $wgLang;
 875+ if ( '' == $query ) { $q = ''; }
 876+ else { $q = $query.'&'; }
 877+ $q .= 'limit='.$limit.'&offset='.$offset;
 878+
 879+ $fmtLimit = $wgLang->formatNum( $limit );
 880+ $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-numlink\">{$fmtLimit}</a>";
 881+ return $s;
 882+}
 883+
 884+/**
 885+ * @todo document
 886+ * @todo FIXME: we may want to blacklist some broken browsers
 887+ *
 888+ * @return bool Whereas client accept gzip compression
 889+ */
 890+function wfClientAcceptsGzip() {
 891+ global $wgUseGzip;
 892+ if( $wgUseGzip ) {
 893+ # FIXME: we may want to blacklist some broken browsers
 894+ $m = array();
 895+ if( preg_match(
 896+ '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
 897+ $_SERVER['HTTP_ACCEPT_ENCODING'],
 898+ $m ) ) {
 899+ if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) return false;
 900+ wfDebug( " accepts gzip\n" );
 901+ return true;
 902+ }
 903+ }
 904+ return false;
 905+}
 906+
 907+/**
 908+ * Obtain the offset and limit values from the request string;
 909+ * used in special pages
 910+ *
 911+ * @param $deflimit Default limit if none supplied
 912+ * @param $optionname Name of a user preference to check against
 913+ * @return array
 914+ *
 915+ */
 916+function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
 917+ global $wgRequest;
 918+ return $wgRequest->getLimitOffset( $deflimit, $optionname );
 919+}
 920+
 921+/**
 922+ * Escapes the given text so that it may be output using addWikiText()
 923+ * without any linking, formatting, etc. making its way through. This
 924+ * is achieved by substituting certain characters with HTML entities.
 925+ * As required by the callers, <nowiki> is not used. It currently does
 926+ * not filter out characters which have special meaning only at the
 927+ * start of a line, such as "*".
 928+ *
 929+ * @param string $text Text to be escaped
 930+ */
 931+function wfEscapeWikiText( $text ) {
 932+ $text = str_replace(
 933+ array( '[', '|', ']', '\'', 'ISBN ', 'RFC ', '://', "\n=", '{{' ),
 934+ array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;', 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;' ),
 935+ htmlspecialchars($text) );
 936+ return $text;
 937+}
 938+
 939+/**
 940+ * @todo document
 941+ */
 942+function wfQuotedPrintable( $string, $charset = '' ) {
 943+ # Probably incomplete; see RFC 2045
 944+ if( empty( $charset ) ) {
 945+ global $wgInputEncoding;
 946+ $charset = $wgInputEncoding;
 947+ }
 948+ $charset = strtoupper( $charset );
 949+ $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
 950+
 951+ $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
 952+ $replace = $illegal . '\t ?_';
 953+ if( !preg_match( "/[$illegal]/", $string ) ) return $string;
 954+ $out = "=?$charset?Q?";
 955+ $out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
 956+ $out .= '?=';
 957+ return $out;
 958+}
 959+
 960+
 961+/**
 962+ * @todo document
 963+ * @return float
 964+ */
 965+function wfTime() {
 966+ return microtime(true);
 967+}
 968+
 969+/**
 970+ * Sets dest to source and returns the original value of dest
 971+ * If source is NULL, it just returns the value, it doesn't set the variable
 972+ */
 973+function wfSetVar( &$dest, $source ) {
 974+ $temp = $dest;
 975+ if ( !is_null( $source ) ) {
 976+ $dest = $source;
 977+ }
 978+ return $temp;
 979+}
 980+
 981+/**
 982+ * As for wfSetVar except setting a bit
 983+ */
 984+function wfSetBit( &$dest, $bit, $state = true ) {
 985+ $temp = (bool)($dest & $bit );
 986+ if ( !is_null( $state ) ) {
 987+ if ( $state ) {
 988+ $dest |= $bit;
 989+ } else {
 990+ $dest &= ~$bit;
 991+ }
 992+ }
 993+ return $temp;
 994+}
 995+
 996+/**
 997+ * This function takes two arrays as input, and returns a CGI-style string, e.g.
 998+ * "days=7&limit=100". Options in the first array override options in the second.
 999+ * Options set to "" will not be output.
 1000+ */
 1001+function wfArrayToCGI( $array1, $array2 = NULL )
 1002+{
 1003+ if ( !is_null( $array2 ) ) {
 1004+ $array1 = $array1 + $array2;
 1005+ }
 1006+
 1007+ $cgi = '';
 1008+ foreach ( $array1 as $key => $value ) {
 1009+ if ( '' !== $value ) {
 1010+ if ( '' != $cgi ) {
 1011+ $cgi .= '&';
 1012+ }
 1013+ if(is_array($value))
 1014+ {
 1015+ $firstTime = true;
 1016+ foreach($value as $v)
 1017+ {
 1018+ $cgi .= ($firstTime ? '' : '&') .
 1019+ urlencode( $key . '[]' ) . '=' .
 1020+ urlencode( $v );
 1021+ $firstTime = false;
 1022+ }
 1023+ }
 1024+ else
 1025+ $cgi .= urlencode( $key ) . '=' .
 1026+ urlencode( $value );
 1027+ }
 1028+ }
 1029+ return $cgi;
 1030+}
 1031+
 1032+/**
 1033+ * Append a query string to an existing URL, which may or may not already
 1034+ * have query string parameters already. If so, they will be combined.
 1035+ *
 1036+ * @param string $url
 1037+ * @param string $query
 1038+ * @return string
 1039+ */
 1040+function wfAppendQuery( $url, $query ) {
 1041+ if( $query != '' ) {
 1042+ if( false === strpos( $url, '?' ) ) {
 1043+ $url .= '?';
 1044+ } else {
 1045+ $url .= '&';
 1046+ }
 1047+ $url .= $query;
 1048+ }
 1049+ return $url;
 1050+}
 1051+
 1052+/**
 1053+ * Expand a potentially local URL to a fully-qualified URL.
 1054+ * Assumes $wgServer is correct. :)
 1055+ * @param string $url, either fully-qualified or a local path + query
 1056+ * @return string Fully-qualified URL
 1057+ */
 1058+function wfExpandUrl( $url ) {
 1059+ if( substr( $url, 0, 1 ) == '/' ) {
 1060+ global $wgServer;
 1061+ return $wgServer . $url;
 1062+ } else {
 1063+ return $url;
 1064+ }
 1065+}
 1066+
 1067+/**
 1068+ * This is obsolete, use SquidUpdate::purge()
 1069+ * @deprecated
 1070+ */
 1071+function wfPurgeSquidServers ($urlArr) {
 1072+ SquidUpdate::purge( $urlArr );
 1073+}
 1074+
 1075+/**
 1076+ * Windows-compatible version of escapeshellarg()
 1077+ * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
 1078+ * function puts single quotes in regardless of OS
 1079+ */
 1080+function wfEscapeShellArg( ) {
 1081+ $args = func_get_args();
 1082+ $first = true;
 1083+ $retVal = '';
 1084+ foreach ( $args as $arg ) {
 1085+ if ( !$first ) {
 1086+ $retVal .= ' ';
 1087+ } else {
 1088+ $first = false;
 1089+ }
 1090+
 1091+ if ( wfIsWindows() ) {
 1092+ // Escaping for an MSVC-style command line parser
 1093+ // Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
 1094+ // Double the backslashes before any double quotes. Escape the double quotes.
 1095+ $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
 1096+ $arg = '';
 1097+ $delim = false;
 1098+ foreach ( $tokens as $token ) {
 1099+ if ( $delim ) {
 1100+ $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
 1101+ } else {
 1102+ $arg .= $token;
 1103+ }
 1104+ $delim = !$delim;
 1105+ }
 1106+ // Double the backslashes before the end of the string, because
 1107+ // we will soon add a quote
 1108+ $m = array();
 1109+ if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
 1110+ $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
 1111+ }
 1112+
 1113+ // Add surrounding quotes
 1114+ $retVal .= '"' . $arg . '"';
 1115+ } else {
 1116+ $retVal .= escapeshellarg( $arg );
 1117+ }
 1118+ }
 1119+ return $retVal;
 1120+}
 1121+
 1122+/**
 1123+ * wfMerge attempts to merge differences between three texts.
 1124+ * Returns true for a clean merge and false for failure or a conflict.
 1125+ */
 1126+function wfMerge( $old, $mine, $yours, &$result ){
 1127+ global $wgDiff3;
 1128+
 1129+ # This check may also protect against code injection in
 1130+ # case of broken installations.
 1131+ if(! file_exists( $wgDiff3 ) ){
 1132+ wfDebug( "diff3 not found\n" );
 1133+ return false;
 1134+ }
 1135+
 1136+ # Make temporary files
 1137+ $td = wfTempDir();
 1138+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
 1139+ $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
 1140+ $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
 1141+
 1142+ fwrite( $oldtextFile, $old ); fclose( $oldtextFile );
 1143+ fwrite( $mytextFile, $mine ); fclose( $mytextFile );
 1144+ fwrite( $yourtextFile, $yours ); fclose( $yourtextFile );
 1145+
 1146+ # Check for a conflict
 1147+ $cmd = $wgDiff3 . ' -a --overlap-only ' .
 1148+ wfEscapeShellArg( $mytextName ) . ' ' .
 1149+ wfEscapeShellArg( $oldtextName ) . ' ' .
 1150+ wfEscapeShellArg( $yourtextName );
 1151+ $handle = popen( $cmd, 'r' );
 1152+
 1153+ if( fgets( $handle, 1024 ) ){
 1154+ $conflict = true;
 1155+ } else {
 1156+ $conflict = false;
 1157+ }
 1158+ pclose( $handle );
 1159+
 1160+ # Merge differences
 1161+ $cmd = $wgDiff3 . ' -a -e --merge ' .
 1162+ wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
 1163+ $handle = popen( $cmd, 'r' );
 1164+ $result = '';
 1165+ do {
 1166+ $data = fread( $handle, 8192 );
 1167+ if ( strlen( $data ) == 0 ) {
 1168+ break;
 1169+ }
 1170+ $result .= $data;
 1171+ } while ( true );
 1172+ pclose( $handle );
 1173+ unlink( $mytextName ); unlink( $oldtextName ); unlink( $yourtextName );
 1174+
 1175+ if ( $result === '' && $old !== '' && $conflict == false ) {
 1176+ wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
 1177+ $conflict = true;
 1178+ }
 1179+ return ! $conflict;
 1180+}
 1181+
 1182+/**
 1183+ * Returns unified plain-text diff of two texts.
 1184+ * Useful for machine processing of diffs.
 1185+ * @param $before string The text before the changes.
 1186+ * @param $after string The text after the changes.
 1187+ * @param $params string Command-line options for the diff command.
 1188+ * @return string Unified diff of $before and $after
 1189+ */
 1190+function wfDiff( $before, $after, $params = '-u' ) {
 1191+ global $wgDiff;
 1192+
 1193+ # This check may also protect against code injection in
 1194+ # case of broken installations.
 1195+ if( !file_exists( $wgDiff ) ){
 1196+ wfDebug( "diff executable not found\n" );
 1197+ $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
 1198+ $format = new UnifiedDiffFormatter();
 1199+ return $format->format( $diffs );
 1200+ }
 1201+
 1202+ # Make temporary files
 1203+ $td = wfTempDir();
 1204+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
 1205+ $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
 1206+
 1207+ fwrite( $oldtextFile, $before ); fclose( $oldtextFile );
 1208+ fwrite( $newtextFile, $after ); fclose( $newtextFile );
 1209+
 1210+ // Get the diff of the two files
 1211+ $cmd = "$wgDiff " . $params . ' ' .wfEscapeShellArg( $oldtextName, $newtextName );
 1212+
 1213+ $h = popen( $cmd, 'r' );
 1214+
 1215+ $diff = '';
 1216+
 1217+ do {
 1218+ $data = fread( $h, 8192 );
 1219+ if ( strlen( $data ) == 0 ) {
 1220+ break;
 1221+ }
 1222+ $diff .= $data;
 1223+ } while ( true );
 1224+
 1225+ // Clean up
 1226+ pclose( $h );
 1227+ unlink( $oldtextName );
 1228+ unlink( $newtextName );
 1229+
 1230+ // Kill the --- and +++ lines. They're not useful.
 1231+ $diff_lines = explode( "\n", $diff );
 1232+ if (strpos( $diff_lines[0], '---' ) === 0) {
 1233+ unset($diff_lines[0]);
 1234+ }
 1235+ if (strpos( $diff_lines[1], '+++' ) === 0) {
 1236+ unset($diff_lines[1]);
 1237+ }
 1238+
 1239+ $diff = implode( "\n", $diff_lines );
 1240+
 1241+ return $diff;
 1242+}
 1243+
 1244+/**
 1245+ * @todo document
 1246+ */
 1247+function wfVarDump( $var ) {
 1248+ global $wgOut;
 1249+ $s = str_replace("\n","<br />\n", var_export( $var, true ) . "\n");
 1250+ if ( headers_sent() || !@is_object( $wgOut ) ) {
 1251+ print $s;
 1252+ } else {
 1253+ $wgOut->addHTML( $s );
 1254+ }
 1255+}
 1256+
 1257+/**
 1258+ * Provide a simple HTTP error.
 1259+ */
 1260+function wfHttpError( $code, $label, $desc ) {
 1261+ global $wgOut;
 1262+ $wgOut->disable();
 1263+ header( "HTTP/1.0 $code $label" );
 1264+ header( "Status: $code $label" );
 1265+ $wgOut->sendCacheControl();
 1266+
 1267+ header( 'Content-type: text/html; charset=utf-8' );
 1268+ print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
 1269+ "<html><head><title>" .
 1270+ htmlspecialchars( $label ) .
 1271+ "</title></head><body><h1>" .
 1272+ htmlspecialchars( $label ) .
 1273+ "</h1><p>" .
 1274+ nl2br( htmlspecialchars( $desc ) ) .
 1275+ "</p></body></html>\n";
 1276+}
 1277+
 1278+/**
 1279+ * Clear away any user-level output buffers, discarding contents.
 1280+ *
 1281+ * Suitable for 'starting afresh', for instance when streaming
 1282+ * relatively large amounts of data without buffering, or wanting to
 1283+ * output image files without ob_gzhandler's compression.
 1284+ *
 1285+ * The optional $resetGzipEncoding parameter controls suppression of
 1286+ * the Content-Encoding header sent by ob_gzhandler; by default it
 1287+ * is left. See comments for wfClearOutputBuffers() for why it would
 1288+ * be used.
 1289+ *
 1290+ * Note that some PHP configuration options may add output buffer
 1291+ * layers which cannot be removed; these are left in place.
 1292+ *
 1293+ * @param bool $resetGzipEncoding
 1294+ */
 1295+function wfResetOutputBuffers( $resetGzipEncoding=true ) {
 1296+ if( $resetGzipEncoding ) {
 1297+ // Suppress Content-Encoding and Content-Length
 1298+ // headers from 1.10+s wfOutputHandler
 1299+ global $wgDisableOutputCompression;
 1300+ $wgDisableOutputCompression = true;
 1301+ }
 1302+ while( $status = ob_get_status() ) {
 1303+ if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
 1304+ // Probably from zlib.output_compression or other
 1305+ // PHP-internal setting which can't be removed.
 1306+ //
 1307+ // Give up, and hope the result doesn't break
 1308+ // output behavior.
 1309+ break;
 1310+ }
 1311+ if( !ob_end_clean() ) {
 1312+ // Could not remove output buffer handler; abort now
 1313+ // to avoid getting in some kind of infinite loop.
 1314+ break;
 1315+ }
 1316+ if( $resetGzipEncoding ) {
 1317+ if( $status['name'] == 'ob_gzhandler' ) {
 1318+ // Reset the 'Content-Encoding' field set by this handler
 1319+ // so we can start fresh.
 1320+ header( 'Content-Encoding:' );
 1321+ }
 1322+ }
 1323+ }
 1324+}
 1325+
 1326+/**
 1327+ * More legible than passing a 'false' parameter to wfResetOutputBuffers():
 1328+ *
 1329+ * Clear away output buffers, but keep the Content-Encoding header
 1330+ * produced by ob_gzhandler, if any.
 1331+ *
 1332+ * This should be used for HTTP 304 responses, where you need to
 1333+ * preserve the Content-Encoding header of the real result, but
 1334+ * also need to suppress the output of ob_gzhandler to keep to spec
 1335+ * and avoid breaking Firefox in rare cases where the headers and
 1336+ * body are broken over two packets.
 1337+ */
 1338+function wfClearOutputBuffers() {
 1339+ wfResetOutputBuffers( false );
 1340+}
 1341+
 1342+/**
 1343+ * Converts an Accept-* header into an array mapping string values to quality
 1344+ * factors
 1345+ */
 1346+function wfAcceptToPrefs( $accept, $def = '*/*' ) {
 1347+ # No arg means accept anything (per HTTP spec)
 1348+ if( !$accept ) {
 1349+ return array( $def => 1.0 );
 1350+ }
 1351+
 1352+ $prefs = array();
 1353+
 1354+ $parts = explode( ',', $accept );
 1355+
 1356+ foreach( $parts as $part ) {
 1357+ # FIXME: doesn't deal with params like 'text/html; level=1'
 1358+ @list( $value, $qpart ) = explode( ';', trim( $part ) );
 1359+ $match = array();
 1360+ if( !isset( $qpart ) ) {
 1361+ $prefs[$value] = 1.0;
 1362+ } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
 1363+ $prefs[$value] = floatval($match[1]);
 1364+ }
 1365+ }
 1366+
 1367+ return $prefs;
 1368+}
 1369+
 1370+/**
 1371+ * Checks if a given MIME type matches any of the keys in the given
 1372+ * array. Basic wildcards are accepted in the array keys.
 1373+ *
 1374+ * Returns the matching MIME type (or wildcard) if a match, otherwise
 1375+ * NULL if no match.
 1376+ *
 1377+ * @param string $type
 1378+ * @param array $avail
 1379+ * @return string
 1380+ * @private
 1381+ */
 1382+function mimeTypeMatch( $type, $avail ) {
 1383+ if( array_key_exists($type, $avail) ) {
 1384+ return $type;
 1385+ } else {
 1386+ $parts = explode( '/', $type );
 1387+ if( array_key_exists( $parts[0] . '/*', $avail ) ) {
 1388+ return $parts[0] . '/*';
 1389+ } elseif( array_key_exists( '*/*', $avail ) ) {
 1390+ return '*/*';
 1391+ } else {
 1392+ return NULL;
 1393+ }
 1394+ }
 1395+}
 1396+
 1397+/**
 1398+ * Returns the 'best' match between a client's requested internet media types
 1399+ * and the server's list of available types. Each list should be an associative
 1400+ * array of type to preference (preference is a float between 0.0 and 1.0).
 1401+ * Wildcards in the types are acceptable.
 1402+ *
 1403+ * @param array $cprefs Client's acceptable type list
 1404+ * @param array $sprefs Server's offered types
 1405+ * @return string
 1406+ *
 1407+ * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
 1408+ * XXX: generalize to negotiate other stuff
 1409+ */
 1410+function wfNegotiateType( $cprefs, $sprefs ) {
 1411+ $combine = array();
 1412+
 1413+ foreach( array_keys($sprefs) as $type ) {
 1414+ $parts = explode( '/', $type );
 1415+ if( $parts[1] != '*' ) {
 1416+ $ckey = mimeTypeMatch( $type, $cprefs );
 1417+ if( $ckey ) {
 1418+ $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
 1419+ }
 1420+ }
 1421+ }
 1422+
 1423+ foreach( array_keys( $cprefs ) as $type ) {
 1424+ $parts = explode( '/', $type );
 1425+ if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
 1426+ $skey = mimeTypeMatch( $type, $sprefs );
 1427+ if( $skey ) {
 1428+ $combine[$type] = $sprefs[$skey] * $cprefs[$type];
 1429+ }
 1430+ }
 1431+ }
 1432+
 1433+ $bestq = 0;
 1434+ $besttype = NULL;
 1435+
 1436+ foreach( array_keys( $combine ) as $type ) {
 1437+ if( $combine[$type] > $bestq ) {
 1438+ $besttype = $type;
 1439+ $bestq = $combine[$type];
 1440+ }
 1441+ }
 1442+
 1443+ return $besttype;
 1444+}
 1445+
 1446+/**
 1447+ * Array lookup
 1448+ * Returns an array where the values in the first array are replaced by the
 1449+ * values in the second array with the corresponding keys
 1450+ *
 1451+ * @return array
 1452+ */
 1453+function wfArrayLookup( $a, $b ) {
 1454+ return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
 1455+}
 1456+
 1457+/**
 1458+ * Convenience function; returns MediaWiki timestamp for the present time.
 1459+ * @return string
 1460+ */
 1461+function wfTimestampNow() {
 1462+ # return NOW
 1463+ return wfTimestamp( TS_MW, time() );
 1464+}
 1465+
 1466+/**
 1467+ * Reference-counted warning suppression
 1468+ */
 1469+function wfSuppressWarnings( $end = false ) {
 1470+ static $suppressCount = 0;
 1471+ static $originalLevel = false;
 1472+
 1473+ if ( $end ) {
 1474+ if ( $suppressCount ) {
 1475+ --$suppressCount;
 1476+ if ( !$suppressCount ) {
 1477+ error_reporting( $originalLevel );
 1478+ }
 1479+ }
 1480+ } else {
 1481+ if ( !$suppressCount ) {
 1482+ $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE ) );
 1483+ }
 1484+ ++$suppressCount;
 1485+ }
 1486+}
 1487+
 1488+/**
 1489+ * Restore error level to previous value
 1490+ */
 1491+function wfRestoreWarnings() {
 1492+ wfSuppressWarnings( true );
 1493+}
 1494+
 1495+# Autodetect, convert and provide timestamps of various types
 1496+
 1497+/**
 1498+ * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
 1499+ */
 1500+define('TS_UNIX', 0);
 1501+
 1502+/**
 1503+ * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
 1504+ */
 1505+define('TS_MW', 1);
 1506+
 1507+/**
 1508+ * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
 1509+ */
 1510+define('TS_DB', 2);
 1511+
 1512+/**
 1513+ * RFC 2822 format, for E-mail and HTTP headers
 1514+ */
 1515+define('TS_RFC2822', 3);
 1516+
 1517+/**
 1518+ * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
 1519+ *
 1520+ * This is used by Special:Export
 1521+ */
 1522+define('TS_ISO_8601', 4);
 1523+
 1524+/**
 1525+ * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
 1526+ *
 1527+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
 1528+ * DateTime tag and page 36 for the DateTimeOriginal and
 1529+ * DateTimeDigitized tags.
 1530+ */
 1531+define('TS_EXIF', 5);
 1532+
 1533+/**
 1534+ * Oracle format time.
 1535+ */
 1536+define('TS_ORACLE', 6);
 1537+
 1538+/**
 1539+ * Postgres format time.
 1540+ */
 1541+define('TS_POSTGRES', 7);
 1542+
 1543+/**
 1544+ * @param mixed $outputtype A timestamp in one of the supported formats, the
 1545+ * function will autodetect which format is supplied
 1546+ * and act accordingly.
 1547+ * @return string Time in the format specified in $outputtype
 1548+ */
 1549+function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
 1550+ $uts = 0;
 1551+ $da = array();
 1552+ if ($ts==0) {
 1553+ $uts=time();
 1554+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
 1555+ # TS_DB
 1556+ } elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
 1557+ # TS_EXIF
 1558+ } elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) {
 1559+ # TS_MW
 1560+ } elseif (preg_match('/^\d{1,13}$/D',$ts)) {
 1561+ # TS_UNIX
 1562+ $uts = $ts;
 1563+ } elseif (preg_match('/^\d{1,2}-...-\d\d(?:\d\d)? \d\d\.\d\d\.\d\d/', $ts)) {
 1564+ # TS_ORACLE
 1565+ $uts = strtotime(preg_replace('/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
 1566+ str_replace("+00:00", "UTC", $ts)));
 1567+ } elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/', $ts, $da)) {
 1568+ # TS_ISO_8601
 1569+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)[\+\- ](\d\d)$/',$ts,$da)) {
 1570+ # TS_POSTGRES
 1571+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/',$ts,$da)) {
 1572+ # TS_POSTGRES
 1573+ } else {
 1574+ # Bogus value; fall back to the epoch...
 1575+ wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
 1576+ $uts = 0;
 1577+ }
 1578+
 1579+ if (count( $da ) ) {
 1580+ // Warning! gmmktime() acts oddly if the month or day is set to 0
 1581+ // We may want to handle that explicitly at some point
 1582+ $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
 1583+ (int)$da[2],(int)$da[3],(int)$da[1]);
 1584+ }
 1585+
 1586+ switch($outputtype) {
 1587+ case TS_UNIX:
 1588+ return $uts;
 1589+ case TS_MW:
 1590+ return gmdate( 'YmdHis', $uts );
 1591+ case TS_DB:
 1592+ return gmdate( 'Y-m-d H:i:s', $uts );
 1593+ case TS_ISO_8601:
 1594+ return gmdate( 'Y-m-d\TH:i:s\Z', $uts );
 1595+ // This shouldn't ever be used, but is included for completeness
 1596+ case TS_EXIF:
 1597+ return gmdate( 'Y:m:d H:i:s', $uts );
 1598+ case TS_RFC2822:
 1599+ return gmdate( 'D, d M Y H:i:s', $uts ) . ' GMT';
 1600+ case TS_ORACLE:
 1601+ return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
 1602+ case TS_POSTGRES:
 1603+ return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
 1604+ default:
 1605+ throw new MWException( 'wfTimestamp() called with illegal output type.');
 1606+ }
 1607+}
 1608+
 1609+/**
 1610+ * Return a formatted timestamp, or null if input is null.
 1611+ * For dealing with nullable timestamp columns in the database.
 1612+ * @param int $outputtype
 1613+ * @param string $ts
 1614+ * @return string
 1615+ */
 1616+function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
 1617+ if( is_null( $ts ) ) {
 1618+ return null;
 1619+ } else {
 1620+ return wfTimestamp( $outputtype, $ts );
 1621+ }
 1622+}
 1623+
 1624+/**
 1625+ * Check if the operating system is Windows
 1626+ *
 1627+ * @return bool True if it's Windows, False otherwise.
 1628+ */
 1629+function wfIsWindows() {
 1630+ if (substr(php_uname(), 0, 7) == 'Windows') {
 1631+ return true;
 1632+ } else {
 1633+ return false;
 1634+ }
 1635+}
 1636+
 1637+/**
 1638+ * Swap two variables
 1639+ */
 1640+function swap( &$x, &$y ) {
 1641+ $z = $x;
 1642+ $x = $y;
 1643+ $y = $z;
 1644+}
 1645+
 1646+function wfGetCachedNotice( $name ) {
 1647+ global $wgOut, $parserMemc;
 1648+ $fname = 'wfGetCachedNotice';
 1649+ wfProfileIn( $fname );
 1650+
 1651+ $needParse = false;
 1652+
 1653+ if( $name === 'default' ) {
 1654+ // special case
 1655+ global $wgSiteNotice;
 1656+ $notice = $wgSiteNotice;
 1657+ if( empty( $notice ) ) {
 1658+ wfProfileOut( $fname );
 1659+ return false;
 1660+ }
 1661+ } else {
 1662+ $notice = wfMsgForContentNoTrans( $name );
 1663+ if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
 1664+ wfProfileOut( $fname );
 1665+ return( false );
 1666+ }
 1667+ }
 1668+
 1669+ $cachedNotice = $parserMemc->get( wfMemcKey( $name ) );
 1670+ if( is_array( $cachedNotice ) ) {
 1671+ if( md5( $notice ) == $cachedNotice['hash'] ) {
 1672+ $notice = $cachedNotice['html'];
 1673+ } else {
 1674+ $needParse = true;
 1675+ }
 1676+ } else {
 1677+ $needParse = true;
 1678+ }
 1679+
 1680+ if( $needParse ) {
 1681+ if( is_object( $wgOut ) ) {
 1682+ $parsed = $wgOut->parse( $notice );
 1683+ $parserMemc->set( wfMemcKey( $name ), array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
 1684+ $notice = $parsed;
 1685+ } else {
 1686+ wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' );
 1687+ $notice = '';
 1688+ }
 1689+ }
 1690+
 1691+ wfProfileOut( $fname );
 1692+ return $notice;
 1693+}
 1694+
 1695+function wfGetNamespaceNotice() {
 1696+ global $wgTitle;
 1697+
 1698+ # Paranoia
 1699+ if ( !isset( $wgTitle ) || !is_object( $wgTitle ) )
 1700+ return "";
 1701+
 1702+ $fname = 'wfGetNamespaceNotice';
 1703+ wfProfileIn( $fname );
 1704+
 1705+ $key = "namespacenotice-" . $wgTitle->getNsText();
 1706+ $namespaceNotice = wfGetCachedNotice( $key );
 1707+ if ( $namespaceNotice && substr ( $namespaceNotice , 0 ,7 ) != "<p>&lt;" ) {
 1708+ $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . "</div>";
 1709+ } else {
 1710+ $namespaceNotice = "";
 1711+ }
 1712+
 1713+ wfProfileOut( $fname );
 1714+ return $namespaceNotice;
 1715+}
 1716+
 1717+function wfGetSiteNotice() {
 1718+ global $wgUser, $wgSiteNotice;
 1719+ $fname = 'wfGetSiteNotice';
 1720+ wfProfileIn( $fname );
 1721+ $siteNotice = '';
 1722+
 1723+ if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
 1724+ if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
 1725+ $siteNotice = wfGetCachedNotice( 'sitenotice' );
 1726+ } else {
 1727+ $anonNotice = wfGetCachedNotice( 'anonnotice' );
 1728+ if( !$anonNotice ) {
 1729+ $siteNotice = wfGetCachedNotice( 'sitenotice' );
 1730+ } else {
 1731+ $siteNotice = $anonNotice;
 1732+ }
 1733+ }
 1734+ if( !$siteNotice ) {
 1735+ $siteNotice = wfGetCachedNotice( 'default' );
 1736+ }
 1737+ }
 1738+
 1739+ wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
 1740+ wfProfileOut( $fname );
 1741+ return $siteNotice;
 1742+}
 1743+
 1744+/**
 1745+ * BC wrapper for MimeMagic::singleton()
 1746+ * @deprecated
 1747+ */
 1748+function &wfGetMimeMagic() {
 1749+ return MimeMagic::singleton();
 1750+}
 1751+
 1752+/**
 1753+ * Tries to get the system directory for temporary files.
 1754+ * The TMPDIR, TMP, and TEMP environment variables are checked in sequence,
 1755+ * and if none are set /tmp is returned as the generic Unix default.
 1756+ *
 1757+ * NOTE: When possible, use the tempfile() function to create temporary
 1758+ * files to avoid race conditions on file creation, etc.
 1759+ *
 1760+ * @return string
 1761+ */
 1762+function wfTempDir() {
 1763+ foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
 1764+ $tmp = getenv( $var );
 1765+ if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
 1766+ return $tmp;
 1767+ }
 1768+ }
 1769+ # Hope this is Unix of some kind!
 1770+ return '/tmp';
 1771+}
 1772+
 1773+/**
 1774+ * Make directory, and make all parent directories if they don't exist
 1775+ *
 1776+ * @param string $fullDir Full path to directory to create
 1777+ * @param int $mode Chmod value to use, default is $wgDirectoryMode
 1778+ * @return bool
 1779+ */
 1780+function wfMkdirParents( $fullDir, $mode = null ) {
 1781+ global $wgDirectoryMode;
 1782+ if( strval( $fullDir ) === '' )
 1783+ return true;
 1784+ if( file_exists( $fullDir ) )
 1785+ return true;
 1786+ // If not defined or isn't an int, set to default
 1787+ if ( is_null( $mode ) ) {
 1788+ $mode = $wgDirectoryMode;
 1789+ }
 1790+
 1791+
 1792+ # Go back through the paths to find the first directory that exists
 1793+ $currentDir = $fullDir;
 1794+ $createList = array();
 1795+ while ( strval( $currentDir ) !== '' && !file_exists( $currentDir ) ) {
 1796+ # Strip trailing slashes
 1797+ $currentDir = rtrim( $currentDir, '/\\' );
 1798+
 1799+ # Add to create list
 1800+ $createList[] = $currentDir;
 1801+
 1802+ # Find next delimiter searching from the end
 1803+ $p = max( strrpos( $currentDir, '/' ), strrpos( $currentDir, '\\' ) );
 1804+ if ( $p === false ) {
 1805+ $currentDir = false;
 1806+ } else {
 1807+ $currentDir = substr( $currentDir, 0, $p );
 1808+ }
 1809+ }
 1810+
 1811+ if ( count( $createList ) == 0 ) {
 1812+ # Directory specified already exists
 1813+ return true;
 1814+ } elseif ( $currentDir === false ) {
 1815+ # Went all the way back to root and it apparently doesn't exist
 1816+ wfDebugLog( 'mkdir', "Root doesn't exist?\n" );
 1817+ return false;
 1818+ }
 1819+ # Now go forward creating directories
 1820+ $createList = array_reverse( $createList );
 1821+
 1822+ # Is the parent directory writable?
 1823+ if ( $currentDir === '' ) {
 1824+ $currentDir = '/';
 1825+ }
 1826+ if ( !is_writable( $currentDir ) ) {
 1827+ wfDebugLog( 'mkdir', "Not writable: $currentDir\n" );
 1828+ return false;
 1829+ }
 1830+
 1831+ foreach ( $createList as $dir ) {
 1832+ # use chmod to override the umask, as suggested by the PHP manual
 1833+ if ( !mkdir( $dir, $mode ) || !chmod( $dir, $mode ) ) {
 1834+ wfDebugLog( 'mkdir', "Unable to create directory $dir\n" );
 1835+ return false;
 1836+ }
 1837+ }
 1838+ return true;
 1839+}
 1840+
 1841+/**
 1842+ * Increment a statistics counter
 1843+ */
 1844+function wfIncrStats( $key ) {
 1845+ global $wgStatsMethod;
 1846+
 1847+ if( $wgStatsMethod == 'udp' ) {
 1848+ global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname;
 1849+ static $socket;
 1850+ if (!$socket) {
 1851+ $socket=socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
 1852+ $statline="stats/{$wgDBname} - 1 1 1 1 1 -total\n";
 1853+ socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
 1854+ }
 1855+ $statline="stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
 1856+ @socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
 1857+ } elseif( $wgStatsMethod == 'cache' ) {
 1858+ global $wgMemc;
 1859+ $key = wfMemcKey( 'stats', $key );
 1860+ if ( is_null( $wgMemc->incr( $key ) ) ) {
 1861+ $wgMemc->add( $key, 1 );
 1862+ }
 1863+ } else {
 1864+ // Disabled
 1865+ }
 1866+}
 1867+
 1868+/**
 1869+ * @param mixed $nr The number to format
 1870+ * @param int $acc The number of digits after the decimal point, default 2
 1871+ * @param bool $round Whether or not to round the value, default true
 1872+ * @return float
 1873+ */
 1874+function wfPercent( $nr, $acc = 2, $round = true ) {
 1875+ $ret = sprintf( "%.${acc}f", $nr );
 1876+ return $round ? round( $ret, $acc ) . '%' : "$ret%";
 1877+}
 1878+
 1879+/**
 1880+ * Encrypt a username/password.
 1881+ *
 1882+ * @param string $userid ID of the user
 1883+ * @param string $password Password of the user
 1884+ * @return string Hashed password
 1885+ * @deprecated Use User::crypt() or User::oldCrypt() instead
 1886+ */
 1887+function wfEncryptPassword( $userid, $password ) {
 1888+ wfDeprecated(__FUNCTION__);
 1889+ # Just wrap around User::oldCrypt()
 1890+ return User::oldCrypt($password, $userid);
 1891+}
 1892+
 1893+/**
 1894+ * Appends to second array if $value differs from that in $default
 1895+ */
 1896+function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
 1897+ if ( is_null( $changed ) ) {
 1898+ throw new MWException('GlobalFunctions::wfAppendToArrayIfNotDefault got null');
 1899+ }
 1900+ if ( $default[$key] !== $value ) {
 1901+ $changed[$key] = $value;
 1902+ }
 1903+}
 1904+
 1905+/**
 1906+ * Since wfMsg() and co suck, they don't return false if the message key they
 1907+ * looked up didn't exist but a XHTML string, this function checks for the
 1908+ * nonexistance of messages by looking at wfMsg() output
 1909+ *
 1910+ * @param $msg The message key looked up
 1911+ * @param $wfMsgOut The output of wfMsg*()
 1912+ * @return bool
 1913+ */
 1914+function wfEmptyMsg( $msg, $wfMsgOut ) {
 1915+ return $wfMsgOut === htmlspecialchars( "<$msg>" );
 1916+}
 1917+
 1918+/**
 1919+ * Find out whether or not a mixed variable exists in a string
 1920+ *
 1921+ * @param mixed needle
 1922+ * @param string haystack
 1923+ * @return bool
 1924+ */
 1925+function in_string( $needle, $str ) {
 1926+ return strpos( $str, $needle ) !== false;
 1927+}
 1928+
 1929+function wfSpecialList( $page, $details ) {
 1930+ global $wgContLang;
 1931+ $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : "";
 1932+ return $page . $details;
 1933+}
 1934+
 1935+/**
 1936+ * Returns a regular expression of url protocols
 1937+ *
 1938+ * @return string
 1939+ */
 1940+function wfUrlProtocols() {
 1941+ global $wgUrlProtocols;
 1942+
 1943+ // Support old-style $wgUrlProtocols strings, for backwards compatibility
 1944+ // with LocalSettings files from 1.5
 1945+ if ( is_array( $wgUrlProtocols ) ) {
 1946+ $protocols = array();
 1947+ foreach ($wgUrlProtocols as $protocol)
 1948+ $protocols[] = preg_quote( $protocol, '/' );
 1949+
 1950+ return implode( '|', $protocols );
 1951+ } else {
 1952+ return $wgUrlProtocols;
 1953+ }
 1954+}
 1955+
 1956+/**
 1957+ * Safety wrapper around ini_get() for boolean settings.
 1958+ * The values returned from ini_get() are pre-normalized for settings
 1959+ * set via php.ini or php_flag/php_admin_flag... but *not*
 1960+ * for those set via php_value/php_admin_value.
 1961+ *
 1962+ * It's fairly common for people to use php_value instead of php_flag,
 1963+ * which can leave you with an 'off' setting giving a false positive
 1964+ * for code that just takes the ini_get() return value as a boolean.
 1965+ *
 1966+ * To make things extra interesting, setting via php_value accepts
 1967+ * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
 1968+ * Unrecognized values go false... again opposite PHP's own coercion
 1969+ * from string to bool.
 1970+ *
 1971+ * Luckily, 'properly' set settings will always come back as '0' or '1',
 1972+ * so we only have to worry about them and the 'improper' settings.
 1973+ *
 1974+ * I frickin' hate PHP... :P
 1975+ *
 1976+ * @param string $setting
 1977+ * @return bool
 1978+ */
 1979+function wfIniGetBool( $setting ) {
 1980+ $val = ini_get( $setting );
 1981+ // 'on' and 'true' can't have whitespace around them, but '1' can.
 1982+ return strtolower( $val ) == 'on'
 1983+ || strtolower( $val ) == 'true'
 1984+ || strtolower( $val ) == 'yes'
 1985+ || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
 1986+}
 1987+
 1988+/**
 1989+ * Execute a shell command, with time and memory limits mirrored from the PHP
 1990+ * configuration if supported.
 1991+ * @param $cmd Command line, properly escaped for shell.
 1992+ * @param &$retval optional, will receive the program's exit code.
 1993+ * (non-zero is usually failure)
 1994+ * @return collected stdout as a string (trailing newlines stripped)
 1995+ */
 1996+function wfShellExec( $cmd, &$retval=null ) {
 1997+ global $IP, $wgMaxShellMemory, $wgMaxShellFileSize;
 1998+
 1999+ if( wfIniGetBool( 'safe_mode' ) ) {
 2000+ wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
 2001+ $retval = 1;
 2002+ return "Unable to run external programs in safe mode.";
 2003+ }
 2004+
 2005+ if ( php_uname( 's' ) == 'Linux' ) {
 2006+ $time = intval( ini_get( 'max_execution_time' ) );
 2007+ $mem = intval( $wgMaxShellMemory );
 2008+ $filesize = intval( $wgMaxShellFileSize );
 2009+
 2010+ if ( $time > 0 && $mem > 0 ) {
 2011+ $script = "$IP/bin/ulimit4.sh";
 2012+ if ( is_executable( $script ) ) {
 2013+ $cmd = escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
 2014+ }
 2015+ }
 2016+ } elseif ( php_uname( 's' ) == 'Windows NT' ) {
 2017+ # This is a hack to work around PHP's flawed invocation of cmd.exe
 2018+ # http://news.php.net/php.internals/21796
 2019+ $cmd = '"' . $cmd . '"';
 2020+ }
 2021+ wfDebug( "wfShellExec: $cmd\n" );
 2022+
 2023+ $retval = 1; // error by default?
 2024+ ob_start();
 2025+ passthru( $cmd, $retval );
 2026+ $output = ob_get_contents();
 2027+ ob_end_clean();
 2028+ return $output;
 2029+
 2030+}
 2031+
 2032+/**
 2033+ * This function works like "use VERSION" in Perl, the program will die with a
 2034+ * backtrace if the current version of PHP is less than the version provided
 2035+ *
 2036+ * This is useful for extensions which due to their nature are not kept in sync
 2037+ * with releases, and might depend on other versions of PHP than the main code
 2038+ *
 2039+ * Note: PHP might die due to parsing errors in some cases before it ever
 2040+ * manages to call this function, such is life
 2041+ *
 2042+ * @see perldoc -f use
 2043+ *
 2044+ * @param mixed $version The version to check, can be a string, an integer, or
 2045+ * a float
 2046+ */
 2047+function wfUsePHP( $req_ver ) {
 2048+ $php_ver = PHP_VERSION;
 2049+
 2050+ if ( version_compare( $php_ver, (string)$req_ver, '<' ) )
 2051+ throw new MWException( "PHP $req_ver required--this is only $php_ver" );
 2052+}
 2053+
 2054+/**
 2055+ * This function works like "use VERSION" in Perl except it checks the version
 2056+ * of MediaWiki, the program will die with a backtrace if the current version
 2057+ * of MediaWiki is less than the version provided.
 2058+ *
 2059+ * This is useful for extensions which due to their nature are not kept in sync
 2060+ * with releases
 2061+ *
 2062+ * @see perldoc -f use
 2063+ *
 2064+ * @param mixed $version The version to check, can be a string, an integer, or
 2065+ * a float
 2066+ */
 2067+function wfUseMW( $req_ver ) {
 2068+ global $wgVersion;
 2069+
 2070+ if ( version_compare( $wgVersion, (string)$req_ver, '<' ) )
 2071+ throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
 2072+}
 2073+
 2074+/**
 2075+ * @deprecated use StringUtils::escapeRegexReplacement
 2076+ */
 2077+function wfRegexReplacement( $string ) {
 2078+ return StringUtils::escapeRegexReplacement( $string );
 2079+}
 2080+
 2081+/**
 2082+ * Return the final portion of a pathname.
 2083+ * Reimplemented because PHP5's basename() is buggy with multibyte text.
 2084+ * http://bugs.php.net/bug.php?id=33898
 2085+ *
 2086+ * PHP's basename() only considers '\' a pathchar on Windows and Netware.
 2087+ * We'll consider it so always, as we don't want \s in our Unix paths either.
 2088+ *
 2089+ * @param string $path
 2090+ * @param string $suffix to remove if present
 2091+ * @return string
 2092+ */
 2093+function wfBaseName( $path, $suffix='' ) {
 2094+ $encSuffix = ($suffix == '')
 2095+ ? ''
 2096+ : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
 2097+ $matches = array();
 2098+ if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
 2099+ return $matches[1];
 2100+ } else {
 2101+ return '';
 2102+ }
 2103+}
 2104+
 2105+/**
 2106+ * Generate a relative path name to the given file.
 2107+ * May explode on non-matching case-insensitive paths,
 2108+ * funky symlinks, etc.
 2109+ *
 2110+ * @param string $path Absolute destination path including target filename
 2111+ * @param string $from Absolute source path, directory only
 2112+ * @return string
 2113+ */
 2114+function wfRelativePath( $path, $from ) {
 2115+ // Normalize mixed input on Windows...
 2116+ $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
 2117+ $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
 2118+
 2119+ // Trim trailing slashes -- fix for drive root
 2120+ $path = rtrim( $path, DIRECTORY_SEPARATOR );
 2121+ $from = rtrim( $from, DIRECTORY_SEPARATOR );
 2122+
 2123+ $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
 2124+ $against = explode( DIRECTORY_SEPARATOR, $from );
 2125+
 2126+ if( $pieces[0] !== $against[0] ) {
 2127+ // Non-matching Windows drive letters?
 2128+ // Return a full path.
 2129+ return $path;
 2130+ }
 2131+
 2132+ // Trim off common prefix
 2133+ while( count( $pieces ) && count( $against )
 2134+ && $pieces[0] == $against[0] ) {
 2135+ array_shift( $pieces );
 2136+ array_shift( $against );
 2137+ }
 2138+
 2139+ // relative dots to bump us to the parent
 2140+ while( count( $against ) ) {
 2141+ array_unshift( $pieces, '..' );
 2142+ array_shift( $against );
 2143+ }
 2144+
 2145+ array_push( $pieces, wfBaseName( $path ) );
 2146+
 2147+ return implode( DIRECTORY_SEPARATOR, $pieces );
 2148+}
 2149+
 2150+/**
 2151+ * array_merge() does awful things with "numeric" indexes, including
 2152+ * string indexes when happen to look like integers. When we want
 2153+ * to merge arrays with arbitrary string indexes, we don't want our
 2154+ * arrays to be randomly corrupted just because some of them consist
 2155+ * of numbers.
 2156+ *
 2157+ * Fuck you, PHP. Fuck you in the ear!
 2158+ *
 2159+ * @param array $array1, [$array2, [...]]
 2160+ * @return array
 2161+ */
 2162+function wfArrayMerge( $array1/* ... */ ) {
 2163+ $out = $array1;
 2164+ for( $i = 1; $i < func_num_args(); $i++ ) {
 2165+ foreach( func_get_arg( $i ) as $key => $value ) {
 2166+ $out[$key] = $value;
 2167+ }
 2168+ }
 2169+ return $out;
 2170+}
 2171+
 2172+/**
 2173+ * Make a URL index, appropriate for the el_index field of externallinks.
 2174+ */
 2175+function wfMakeUrlIndex( $url ) {
 2176+ global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
 2177+ wfSuppressWarnings();
 2178+ $bits = parse_url( $url );
 2179+ wfRestoreWarnings();
 2180+ if ( !$bits ) {
 2181+ return false;
 2182+ }
 2183+ // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
 2184+ $delimiter = '';
 2185+ if ( in_array( $bits['scheme'] . '://' , $wgUrlProtocols ) ) {
 2186+ $delimiter = '://';
 2187+ } elseif ( in_array( $bits['scheme'] .':' , $wgUrlProtocols ) ) {
 2188+ $delimiter = ':';
 2189+ // parse_url detects for news: and mailto: the host part of an url as path
 2190+ // We have to correct this wrong detection
 2191+ if ( isset ( $bits['path'] ) ) {
 2192+ $bits['host'] = $bits['path'];
 2193+ $bits['path'] = '';
 2194+ }
 2195+ } else {
 2196+ return false;
 2197+ }
 2198+
 2199+ // Reverse the labels in the hostname, convert to lower case
 2200+ // For emails reverse domainpart only
 2201+ if ( $bits['scheme'] == 'mailto' ) {
 2202+ $mailparts = explode( '@', $bits['host'], 2 );
 2203+ if ( count($mailparts) === 2 ) {
 2204+ $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
 2205+ } else {
 2206+ // No domain specified, don't mangle it
 2207+ $domainpart = '';
 2208+ }
 2209+ $reversedHost = $domainpart . '@' . $mailparts[0];
 2210+ } else {
 2211+ $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
 2212+ }
 2213+ // Add an extra dot to the end
 2214+ // Why? Is it in wrong place in mailto links?
 2215+ if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
 2216+ $reversedHost .= '.';
 2217+ }
 2218+ // Reconstruct the pseudo-URL
 2219+ $prot = $bits['scheme'];
 2220+ $index = "$prot$delimiter$reversedHost";
 2221+ // Leave out user and password. Add the port, path, query and fragment
 2222+ if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port'];
 2223+ if ( isset( $bits['path'] ) ) {
 2224+ $index .= $bits['path'];
 2225+ } else {
 2226+ $index .= '/';
 2227+ }
 2228+ if ( isset( $bits['query'] ) ) $index .= '?' . $bits['query'];
 2229+ if ( isset( $bits['fragment'] ) ) $index .= '#' . $bits['fragment'];
 2230+ return $index;
 2231+}
 2232+
 2233+/**
 2234+ * Do any deferred updates and clear the list
 2235+ * TODO: This could be in Wiki.php if that class made any sense at all
 2236+ */
 2237+function wfDoUpdates()
 2238+{
 2239+ global $wgPostCommitUpdateList, $wgDeferredUpdateList;
 2240+ foreach ( $wgDeferredUpdateList as $update ) {
 2241+ $update->doUpdate();
 2242+ }
 2243+ foreach ( $wgPostCommitUpdateList as $update ) {
 2244+ $update->doUpdate();
 2245+ }
 2246+ $wgDeferredUpdateList = array();
 2247+ $wgPostCommitUpdateList = array();
 2248+}
 2249+
 2250+/**
 2251+ * @deprecated use StringUtils::explodeMarkup
 2252+ */
 2253+function wfExplodeMarkup( $separator, $text ) {
 2254+ return StringUtils::explodeMarkup( $separator, $text );
 2255+}
 2256+
 2257+/**
 2258+ * Convert an arbitrarily-long digit string from one numeric base
 2259+ * to another, optionally zero-padding to a minimum column width.
 2260+ *
 2261+ * Supports base 2 through 36; digit values 10-36 are represented
 2262+ * as lowercase letters a-z. Input is case-insensitive.
 2263+ *
 2264+ * @param $input string of digits
 2265+ * @param $sourceBase int 2-36
 2266+ * @param $destBase int 2-36
 2267+ * @param $pad int 1 or greater
 2268+ * @param $lowercase bool
 2269+ * @return string or false on invalid input
 2270+ */
 2271+function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true ) {
 2272+ $input = strval( $input );
 2273+ if( $sourceBase < 2 ||
 2274+ $sourceBase > 36 ||
 2275+ $destBase < 2 ||
 2276+ $destBase > 36 ||
 2277+ $pad < 1 ||
 2278+ $sourceBase != intval( $sourceBase ) ||
 2279+ $destBase != intval( $destBase ) ||
 2280+ $pad != intval( $pad ) ||
 2281+ !is_string( $input ) ||
 2282+ $input == '' ) {
 2283+ return false;
 2284+ }
 2285+ $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 2286+ $inDigits = array();
 2287+ $outChars = '';
 2288+
 2289+ // Decode and validate input string
 2290+ $input = strtolower( $input );
 2291+ for( $i = 0; $i < strlen( $input ); $i++ ) {
 2292+ $n = strpos( $digitChars, $input{$i} );
 2293+ if( $n === false || $n > $sourceBase ) {
 2294+ return false;
 2295+ }
 2296+ $inDigits[] = $n;
 2297+ }
 2298+
 2299+ // Iterate over the input, modulo-ing out an output digit
 2300+ // at a time until input is gone.
 2301+ while( count( $inDigits ) ) {
 2302+ $work = 0;
 2303+ $workDigits = array();
 2304+
 2305+ // Long division...
 2306+ foreach( $inDigits as $digit ) {
 2307+ $work *= $sourceBase;
 2308+ $work += $digit;
 2309+
 2310+ if( $work < $destBase ) {
 2311+ // Gonna need to pull another digit.
 2312+ if( count( $workDigits ) ) {
 2313+ // Avoid zero-padding; this lets us find
 2314+ // the end of the input very easily when
 2315+ // length drops to zero.
 2316+ $workDigits[] = 0;
 2317+ }
 2318+ } else {
 2319+ // Finally! Actual division!
 2320+ $workDigits[] = intval( $work / $destBase );
 2321+
 2322+ // Isn't it annoying that most programming languages
 2323+ // don't have a single divide-and-remainder operator,
 2324+ // even though the CPU implements it that way?
 2325+ $work = $work % $destBase;
 2326+ }
 2327+ }
 2328+
 2329+ // All that division leaves us with a remainder,
 2330+ // which is conveniently our next output digit.
 2331+ $outChars .= $digitChars[$work];
 2332+
 2333+ // And we continue!
 2334+ $inDigits = $workDigits;
 2335+ }
 2336+
 2337+ while( strlen( $outChars ) < $pad ) {
 2338+ $outChars .= '0';
 2339+ }
 2340+
 2341+ return strrev( $outChars );
 2342+}
 2343+
 2344+/**
 2345+ * Create an object with a given name and an array of construct parameters
 2346+ * @param string $name
 2347+ * @param array $p parameters
 2348+ */
 2349+function wfCreateObject( $name, $p ){
 2350+ $p = array_values( $p );
 2351+ switch ( count( $p ) ) {
 2352+ case 0:
 2353+ return new $name;
 2354+ case 1:
 2355+ return new $name( $p[0] );
 2356+ case 2:
 2357+ return new $name( $p[0], $p[1] );
 2358+ case 3:
 2359+ return new $name( $p[0], $p[1], $p[2] );
 2360+ case 4:
 2361+ return new $name( $p[0], $p[1], $p[2], $p[3] );
 2362+ case 5:
 2363+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4] );
 2364+ case 6:
 2365+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4], $p[5] );
 2366+ default:
 2367+ throw new MWException( "Too many arguments to construtor in wfCreateObject" );
 2368+ }
 2369+}
 2370+
 2371+/**
 2372+ * Alias for modularized function
 2373+ * @deprecated Use Http::get() instead
 2374+ */
 2375+function wfGetHTTP( $url, $timeout = 'default' ) {
 2376+ wfDeprecated(__FUNCTION__);
 2377+ return Http::get( $url, $timeout );
 2378+}
 2379+
 2380+/**
 2381+ * Alias for modularized function
 2382+ * @deprecated Use Http::isLocalURL() instead
 2383+ */
 2384+function wfIsLocalURL( $url ) {
 2385+ wfDeprecated(__FUNCTION__);
 2386+ return Http::isLocalURL( $url );
 2387+}
 2388+
 2389+function wfHttpOnlySafe() {
 2390+ global $wgHttpOnlyBlacklist;
 2391+ if( !version_compare("5.2", PHP_VERSION, "<") )
 2392+ return false;
 2393+
 2394+ if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
 2395+ foreach( $wgHttpOnlyBlacklist as $regex ) {
 2396+ if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
 2397+ return false;
 2398+ }
 2399+ }
 2400+ }
 2401+
 2402+ return true;
 2403+}
 2404+
 2405+/**
 2406+ * Initialise php session
 2407+ */
 2408+function wfSetupSession() {
 2409+ global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly;
 2410+ if( $wgSessionsInMemcached ) {
 2411+ require_once( 'MemcachedSessions.php' );
 2412+ } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
 2413+ # If it's left on 'user' or another setting from another
 2414+ # application, it will end up failing. Try to recover.
 2415+ ini_set ( 'session.save_handler', 'files' );
 2416+ }
 2417+ $httpOnlySafe = wfHttpOnlySafe();
 2418+ wfDebugLog( 'cookie',
 2419+ 'session_set_cookie_params: "' . implode( '", "',
 2420+ array(
 2421+ 0,
 2422+ $wgCookiePath,
 2423+ $wgCookieDomain,
 2424+ $wgCookieSecure,
 2425+ $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' );
 2426+ if( $httpOnlySafe && $wgCookieHttpOnly ) {
 2427+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly );
 2428+ } else {
 2429+ // PHP 5.1 throws warnings if you pass the HttpOnly parameter for 5.2.
 2430+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
 2431+ }
 2432+ session_cache_limiter( 'private, must-revalidate' );
 2433+ wfSuppressWarnings();
 2434+ session_start();
 2435+ wfRestoreWarnings();
 2436+}
 2437+
 2438+/**
 2439+ * Get an object from the precompiled serialized directory
 2440+ *
 2441+ * @return mixed The variable on success, false on failure
 2442+ */
 2443+function wfGetPrecompiledData( $name ) {
 2444+ global $IP;
 2445+
 2446+ $file = "$IP/serialized/$name";
 2447+ if ( file_exists( $file ) ) {
 2448+ $blob = file_get_contents( $file );
 2449+ if ( $blob ) {
 2450+ return unserialize( $blob );
 2451+ }
 2452+ }
 2453+ return false;
 2454+}
 2455+
 2456+function wfGetCaller( $level = 2 ) {
 2457+ $backtrace = wfDebugBacktrace();
 2458+ if ( isset( $backtrace[$level] ) ) {
 2459+ return wfFormatStackFrame($backtrace[$level]);
 2460+ } else {
 2461+ $caller = 'unknown';
 2462+ }
 2463+ return $caller;
 2464+}
 2465+
 2466+/** Return a string consisting all callers in stack, somewhat useful sometimes for profiling specific points */
 2467+function wfGetAllCallers() {
 2468+ return implode('/', array_map('wfFormatStackFrame',array_reverse(wfDebugBacktrace())));
 2469+}
 2470+
 2471+/** Return a string representation of frame */
 2472+function wfFormatStackFrame($frame) {
 2473+ return isset( $frame["class"] )?
 2474+ $frame["class"]."::".$frame["function"]:
 2475+ $frame["function"];
 2476+}
 2477+
 2478+/**
 2479+ * Get a cache key
 2480+ */
 2481+function wfMemcKey( /*... */ ) {
 2482+ $args = func_get_args();
 2483+ $key = wfWikiID() . ':' . implode( ':', $args );
 2484+ return $key;
 2485+}
 2486+
 2487+/**
 2488+ * Get a cache key for a foreign DB
 2489+ */
 2490+function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
 2491+ $args = array_slice( func_get_args(), 2 );
 2492+ if ( $prefix ) {
 2493+ $key = "$db-$prefix:" . implode( ':', $args );
 2494+ } else {
 2495+ $key = $db . ':' . implode( ':', $args );
 2496+ }
 2497+ return $key;
 2498+}
 2499+
 2500+/**
 2501+ * Get an ASCII string identifying this wiki
 2502+ * This is used as a prefix in memcached keys
 2503+ */
 2504+function wfWikiID( $db = null ) {
 2505+ if( $db instanceof Database ) {
 2506+ return $db->getWikiID();
 2507+ } else {
 2508+ global $wgDBprefix, $wgDBname;
 2509+ if ( $wgDBprefix ) {
 2510+ return "$wgDBname-$wgDBprefix";
 2511+ } else {
 2512+ return $wgDBname;
 2513+ }
 2514+ }
 2515+}
 2516+
 2517+/**
 2518+ * Split a wiki ID into DB name and table prefix
 2519+ */
 2520+function wfSplitWikiID( $wiki ) {
 2521+ $bits = explode( '-', $wiki, 2 );
 2522+ if ( count( $bits ) < 2 ) {
 2523+ $bits[] = '';
 2524+ }
 2525+ return $bits;
 2526+}
 2527+
 2528+/*
 2529+ * Get a Database object.
 2530+ * @param integer $db Index of the connection to get. May be DB_MASTER for the
 2531+ * master (for write queries), DB_SLAVE for potentially lagged
 2532+ * read queries, or an integer >= 0 for a particular server.
 2533+ *
 2534+ * @param mixed $groups Query groups. An array of group names that this query
 2535+ * belongs to. May contain a single string if the query is only
 2536+ * in one group.
 2537+ *
 2538+ * @param string $wiki The wiki ID, or false for the current wiki
 2539+ *
 2540+ * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
 2541+ * will always return the same object, unless the underlying connection or load
 2542+ * balancer is manually destroyed.
 2543+ */
 2544+function &wfGetDB( $db = DB_LAST, $groups = array(), $wiki = false ) {
 2545+ return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
 2546+}
 2547+
 2548+/**
 2549+ * Get a load balancer object.
 2550+ *
 2551+ * @param array $groups List of query groups
 2552+ * @param string $wiki Wiki ID, or false for the current wiki
 2553+ * @return LoadBalancer
 2554+ */
 2555+function wfGetLB( $wiki = false ) {
 2556+ return wfGetLBFactory()->getMainLB( $wiki );
 2557+}
 2558+
 2559+/**
 2560+ * Get the load balancer factory object
 2561+ */
 2562+function &wfGetLBFactory() {
 2563+ return LBFactory::singleton();
 2564+}
 2565+
 2566+/**
 2567+ * Find a file.
 2568+ * Shortcut for RepoGroup::singleton()->findFile()
 2569+ * @param mixed $title Title object or string. May be interwiki.
 2570+ * @param mixed $time Requested time for an archived image, or false for the
 2571+ * current version. An image object will be returned which
 2572+ * was created at the specified time.
 2573+ * @param mixed $flags FileRepo::FIND_ flags
 2574+ * @return File, or false if the file does not exist
 2575+ */
 2576+function wfFindFile( $title, $time = false, $flags = 0 ) {
 2577+ return RepoGroup::singleton()->findFile( $title, $time, $flags );
 2578+}
 2579+
 2580+/**
 2581+ * Get an object referring to a locally registered file.
 2582+ * Returns a valid placeholder object if the file does not exist.
 2583+ */
 2584+function wfLocalFile( $title ) {
 2585+ return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
 2586+}
 2587+
 2588+/**
 2589+ * Should low-performance queries be disabled?
 2590+ *
 2591+ * @return bool
 2592+ */
 2593+function wfQueriesMustScale() {
 2594+ global $wgMiserMode;
 2595+ return $wgMiserMode
 2596+ || ( SiteStats::pages() > 100000
 2597+ && SiteStats::edits() > 1000000
 2598+ && SiteStats::users() > 10000 );
 2599+}
 2600+
 2601+/**
 2602+ * Get the path to a specified script file, respecting file
 2603+ * extensions; this is a wrapper around $wgScriptExtension etc.
 2604+ *
 2605+ * @param string $script Script filename, sans extension
 2606+ * @return string
 2607+ */
 2608+function wfScript( $script = 'index' ) {
 2609+ global $wgScriptPath, $wgScriptExtension;
 2610+ return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
 2611+}
 2612+
 2613+/**
 2614+ * Convenience function converts boolean values into "true"
 2615+ * or "false" (string) values
 2616+ *
 2617+ * @param bool $value
 2618+ * @return string
 2619+ */
 2620+function wfBoolToStr( $value ) {
 2621+ return $value ? 'true' : 'false';
 2622+}
 2623+
 2624+/**
 2625+ * Load an extension messages file
 2626+ *
 2627+ * @param string $extensionName Name of extension to load messages from\for.
 2628+ * @param string $langcode Language to load messages for, or false for default
 2629+ * behvaiour (en, content language and user language).
 2630+ */
 2631+function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
 2632+ global $wgExtensionMessagesFiles, $wgMessageCache, $wgLang, $wgContLang;
 2633+
 2634+ #For recording whether extension message files have been loaded in a given language.
 2635+ static $loaded = array();
 2636+
 2637+ if( !array_key_exists( $extensionName, $loaded ) ) {
 2638+ $loaded[$extensionName] = array();
 2639+ }
 2640+
 2641+ if ( !isset($wgExtensionMessagesFiles[$extensionName]) ) {
 2642+ throw new MWException( "Messages file for extensions $extensionName is not defined" );
 2643+ }
 2644+
 2645+ if( !$langcode && !array_key_exists( '*', $loaded[$extensionName] ) ) {
 2646+ # Just do en, content language and user language.
 2647+ $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], false );
 2648+ # Mark that they have been loaded.
 2649+ $loaded[$extensionName]['en'] = true;
 2650+ $loaded[$extensionName][$wgLang->getCode()] = true;
 2651+ $loaded[$extensionName][$wgContLang->getCode()] = true;
 2652+ # Mark that this part has been done to avoid weird if statements.
 2653+ $loaded[$extensionName]['*'] = true;
 2654+ } elseif( is_string( $langcode ) && !array_key_exists( $langcode, $loaded[$extensionName] ) ) {
 2655+ # Load messages for specified language.
 2656+ $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], $langcode );
 2657+ # Mark that they have been loaded.
 2658+ $loaded[$extensionName][$langcode] = true;
 2659+ }
 2660+}
 2661+
 2662+/**
 2663+ * Get a platform-independent path to the null file, e.g.
 2664+ * /dev/null
 2665+ *
 2666+ * @return string
 2667+ */
 2668+function wfGetNull() {
 2669+ return wfIsWindows()
 2670+ ? 'NUL'
 2671+ : '/dev/null';
 2672+}
 2673+
 2674+/**
 2675+ * Displays a maxlag error
 2676+ *
 2677+ * @param string $host Server that lags the most
 2678+ * @param int $lag Maxlag (actual)
 2679+ * @param int $maxLag Maxlag (requested)
 2680+ */
 2681+function wfMaxlagError( $host, $lag, $maxLag ) {
 2682+ global $wgShowHostnames;
 2683+ header( 'HTTP/1.1 503 Service Unavailable' );
 2684+ header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
 2685+ header( 'X-Database-Lag: ' . intval( $lag ) );
 2686+ header( 'Content-Type: text/plain' );
 2687+ if( $wgShowHostnames ) {
 2688+ echo "Waiting for $host: $lag seconds lagged\n";
 2689+ } else {
 2690+ echo "Waiting for a database server: $lag seconds lagged\n";
 2691+ }
 2692+}
 2693+
 2694+/**
 2695+ * Throws an E_USER_NOTICE saying that $function is deprecated
 2696+ * @param string $function
 2697+ * @return null
 2698+ */
 2699+function wfDeprecated( $function ) {
 2700+ global $wgDebugLogFile;
 2701+ if ( !$wgDebugLogFile ) {
 2702+ return;
 2703+ }
 2704+ $callers = wfDebugBacktrace();
 2705+ if( isset( $callers[2] ) ){
 2706+ $callerfunc = $callers[2];
 2707+ $callerfile = $callers[1];
 2708+ if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ){
 2709+ $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
 2710+ } else {
 2711+ $file = '(internal function)';
 2712+ }
 2713+ $func = '';
 2714+ if( isset( $callerfunc['class'] ) )
 2715+ $func .= $callerfunc['class'] . '::';
 2716+ $func .= @$callerfunc['function'];
 2717+ $msg = "Use of $function is deprecated. Called from $func in $file";
 2718+ } else {
 2719+ $msg = "Use of $function is deprecated.";
 2720+ }
 2721+ wfDebug( "$msg\n" );
 2722+}
 2723+
 2724+/**
 2725+ * Sleep until the worst slave's replication lag is less than or equal to
 2726+ * $maxLag, in seconds. Use this when updating very large numbers of rows, as
 2727+ * in maintenance scripts, to avoid causing too much lag. Of course, this is
 2728+ * a no-op if there are no slaves.
 2729+ *
 2730+ * Every time the function has to wait for a slave, it will print a message to
 2731+ * that effect (and then sleep for a little while), so it's probably not best
 2732+ * to use this outside maintenance scripts in its present form.
 2733+ *
 2734+ * @param int $maxLag
 2735+ * @return null
 2736+ */
 2737+function wfWaitForSlaves( $maxLag ) {
 2738+ if( $maxLag ) {
 2739+ $lb = wfGetLB();
 2740+ list( $host, $lag ) = $lb->getMaxLag();
 2741+ while( $lag > $maxLag ) {
 2742+ $name = @gethostbyaddr( $host );
 2743+ if( $name !== false ) {
 2744+ $host = $name;
 2745+ }
 2746+ print "Waiting for $host (lagged $lag seconds)...\n";
 2747+ sleep($maxLag);
 2748+ list( $host, $lag ) = $lb->getMaxLag();
 2749+ }
 2750+ }
 2751+}
 2752+
 2753+/** Generate a random 32-character hexadecimal token.
 2754+ * @param mixed $salt Some sort of salt, if necessary, to add to random characters before hashing.
 2755+ */
 2756+function wfGenerateToken( $salt = '' ) {
 2757+ $salt = serialize($salt);
 2758+
 2759+ return md5( mt_rand( 0, 0x7fffffff ) . $salt );
 2760+}
 2761+
 2762+/**
 2763+ * Replace all invalid characters with -
 2764+ * @param mixed $title Filename to process
 2765+ */
 2766+function wfStripIllegalFilenameChars( $name ) {
 2767+ $name = wfBaseName( $name );
 2768+ $name = preg_replace ( "/[^".Title::legalChars()."]/", '-', $name );
 2769+ return $name;
 2770+}
Property changes on: trunk/extensions/NSFileRepo/REL1_13_0/phase3/includes/GlobalFunctions.php
___________________________________________________________________
Added: svn:eol-style
12771 + native
Index: trunk/extensions/NSFileRepo/REL1_13_0/phase3/includes/GlobalFunctions.patch
@@ -0,0 +1,12 @@
 2+Index: GlobalFunctions.php
 3+===================================================================
 4+--- GlobalFunctions.php (revision 53111)
 5+@@ -2764,6 +2764,6 @@
 6+ */
 7+ function wfStripIllegalFilenameChars( $name ) {
 8+ $name = wfBaseName( $name );
 9+- $name = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $name );
 10++ $name = preg_replace ( "/[^".Title::legalChars()."]/", '-', $name );
 11+ return $name;
 12+ }
Index: trunk/extensions/NSFileRepo/REL1_14_0/phase3/img_auth.i18n.php
@@ -0,0 +1,32 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for img_auth script (see see http://www.mediawiki.org/wiki/Manual:Image_Authorization).
 5+*/
 6+
 7+$messages = array();
 8+
 9+/** English
 10+ * @author Jack D. Pond
 11+ */
 12+$messages['en'] = array(
 13+ 'image_auth-desc' => 'Image authorisation script',
 14+ 'image_auth-nopathinfo' => "Missing PATH_INFO. Your server is not set up to pass this information -
 15+may be CGI-based and can't support img_auth. See `Image Authorization` on MediaWiki.",
 16+ 'image_auth-notindir' => "Requested path not in upload directory.",
 17+ 'image_auth-badtitle' => "Unable to construct a valid Title from `$1`.",
 18+ 'image_auth-nologinnWL' => "Not logged in and `$1` not in whitelist.",
 19+ 'image_auth-nofile' => "`$1` does not exist.",
 20+ 'image_auth-isdir' => "`$1` is a directory.",
 21+ 'image_auth-streaming' => "Streaming `$1`.",
 22+ 'image_auth-public' => "The function of img_auth.php is to output files from a private wiki. This wiki
 23+is configured as a public wiki. For optimal security, img_auth.php is disabled for this case.",
 24+ 'image_auth-noread' => "User does not have access to read `$1`."
 25+);
 26+
 27+/** Message documentation (Message documentation)
 28+ * @author Jack D. Pond
 29+ */
 30+$messages['qqq'] = array(
 31+ 'image_auth-desc' => 'Image authorisation script'
 32+);
 33+
Property changes on: trunk/extensions/NSFileRepo/REL1_14_0/phase3/img_auth.i18n.php
___________________________________________________________________
Added: svn:eol-style
134 + native
Index: trunk/extensions/NSFileRepo/REL1_14_0/phase3/img_auth.php
@@ -0,0 +1,136 @@
 2+<?php
 3+
 4+/**
 5+ * Image authorisation script
 6+ *
 7+ * To use this, see http://www.mediawiki.org/wiki/Manual:Image_Authorization
 8+ *
 9+ * - Set $wgUploadDirectory to a non-public directory (not web accessible)
 10+ * - Set $wgUploadPath to point to this file
 11+ *
 12+ * Your server needs to support PATH_INFO; CGI-based configurations usually don't.
 13+ *
 14+ * @file
 15+ */
 16+
 17+
 18+/**
 19+ For security reasons, you usually don't want your user to know access was denied, just that it was.
 20+ If you want to change this, you can set $wgImgAuthDetails to 'true' in localsettings.php and it will give the user the reason
 21+ why access was denied.
 22+**/
 23+
 24+global $wgImgAuthDetails;
 25+$wgImgAuthDetails = false;
 26+
 27+define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
 28+require_once( dirname( __FILE__ ) . '/includes/WebStart.php' );
 29+wfProfileIn( 'img_auth.php' );
 30+require_once( dirname( __FILE__ ) . '/includes/StreamFile.php' );
 31+
 32+global $wgMessageCache, $messages;
 33+require_once( dirname( __FILE__ ) . '/img_auth.i18n.php' );
 34+foreach( $messages as $lang => $LangMsg )
 35+ $wgMessageCache->addMessages( $LangMsg, $lang );
 36+
 37+$perms = User::getGroupPermissions( array( '*' ) );
 38+
 39+// See if this is a public Wiki (no protections)
 40+if ( in_array( 'read', $perms, true ) )
 41+ wfPublicError(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-public'));
 42+
 43+// Extract path and image information
 44+if( !isset( $_SERVER['PATH_INFO'] ) )
 45+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nopathinfo'));
 46+
 47+$path = $_SERVER['PATH_INFO'];
 48+$filename = realpath( $wgUploadDirectory . $_SERVER['PATH_INFO'] );
 49+$realUpload = realpath( $wgUploadDirectory );
 50+
 51+// Basic directory traversal check
 52+if( substr( $filename, 0, strlen( $realUpload ) ) != $realUpload )
 53+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-notindir'));
 54+
 55+// Extract the file name and chop off the size specifier
 56+// (e.g. 120px-Foo.png => Foo.png)
 57+$name = wfBaseName( $path );
 58+if( preg_match( '!\d+px-(.*)!i', $name, $m ) )
 59+ $name = $m[1];
 60+
 61+// Check to see if the file exists
 62+if( !file_exists( $filename ) )
 63+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nofile',$filename));
 64+
 65+// Check to see if tried to access a directory
 66+if( is_dir( $filename ) )
 67+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-isdir',$filename));
 68+
 69+
 70+$title = Title::makeTitleSafe( NS_FILE, $name );
 71+
 72+// See if could create the title object
 73+if( !$title instanceof Title )
 74+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-badtitle',$name));
 75+
 76+// Run hook
 77+if (!wfRunHooks( 'ImgAuthBeforeStream', array( &$title, &$path, &$name, &$result ) ) )
 78+ wfForbidden($result[0],$result[1]);
 79+
 80+// Check the whitelist if needed, deprecated since usercan added
 81+// $pTitle = $title->getPrefixedText();
 82+// if( !$wgUser->getId() && ( !is_array( $wgWhitelistRead ) || !in_array( $pTitle, $wgWhitelistRead ) ) )
 83+// wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nologinnWL',$pTitle));
 84+
 85+
 86+// Check user authorization for this title
 87+if( !$title->userCanRead() )
 88+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-noread',$name));
 89+
 90+
 91+// Stream the requested file
 92+wfDebugLog( 'img_auth', "Streaming `{$filename}`" );
 93+wfStreamFile( $filename, array( 'Cache-Control: private', 'Vary: Cookie' ) );
 94+wfLogProfilingData();
 95+
 96+/**
 97+ * Issue a standard HTTP 403 Forbidden header ($msg1) and an
 98+ * error message ($msg2), then end the script
 99+ */
 100+function wfForbidden($msg1,$msg2) {
 101+ global $wgImgAuthDetails;
 102+ $detailMsg = $wgImgAuthDetails ? $msg2 : wfMsgHTML('badaccess-group0');
 103+ wfDebugLog( 'img_auth', "wfForbidden Msg: ".$msg2 );
 104+ header( 'HTTP/1.0 403 Forbidden' );
 105+ header( 'Vary: Cookie' );
 106+ header( 'Content-Type: text/html; charset=utf-8' );
 107+ echo <<<ENDS
 108+<html>
 109+<body>
 110+<h1>$msg1</h1>
 111+<p>$detailMsg</p>
 112+</body>
 113+</html>
 114+ENDS;
 115+ wfLogProfilingData();
 116+ exit();
 117+}
 118+
 119+/**
 120+ * Show a 403 error for use when the wiki is public
 121+ */
 122+function wfPublicError($msg1,$msg2) {
 123+ header( 'HTTP/1.0 403 Forbidden' );
 124+ header( 'Content-Type: text/html; charset=utf-8' );
 125+ wfDebugLog( 'img_auth', "wfPublicError Msg: ".$msg2 );
 126+ echo <<<ENDS
 127+<html>
 128+<body>
 129+<h1>$msg1</h1>
 130+<p>$msg2</p>
 131+</body>
 132+</html>
 133+ENDS;
 134+ wfLogProfilingData();
 135+ exit;
 136+}
 137+
Property changes on: trunk/extensions/NSFileRepo/REL1_14_0/phase3/img_auth.php
___________________________________________________________________
Added: svn:eol-style
1138 + native
Index: trunk/extensions/NSFileRepo/REL1_14_0/phase3/includes/GlobalFunctions.php
@@ -0,0 +1,2939 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ die( "This file is part of MediaWiki, it is not a valid entry point" );
 6+}
 7+
 8+/**
 9+ * Global functions used everywhere
 10+ */
 11+
 12+require_once dirname(__FILE__) . '/normal/UtfNormalUtil.php';
 13+require_once dirname(__FILE__) . '/XmlFunctions.php';
 14+
 15+// Hide compatibility functions from Doxygen
 16+/// @cond
 17+
 18+/**
 19+ * Compatibility functions
 20+ *
 21+ * We more or less support PHP 5.0.x and up.
 22+ * Re-implementations of newer functions or functions in non-standard
 23+ * PHP extensions may be included here.
 24+ */
 25+if( !function_exists('iconv') ) {
 26+ # iconv support is not in the default configuration and so may not be present.
 27+ # Assume will only ever use utf-8 and iso-8859-1.
 28+ # This will *not* work in all circumstances.
 29+ function iconv( $from, $to, $string ) {
 30+ if(strcasecmp( $from, $to ) == 0) return $string;
 31+ if(strcasecmp( $from, 'utf-8' ) == 0) return utf8_decode( $string );
 32+ if(strcasecmp( $to, 'utf-8' ) == 0) return utf8_encode( $string );
 33+ return $string;
 34+ }
 35+}
 36+
 37+# UTF-8 substr function based on a PHP manual comment
 38+if ( !function_exists( 'mb_substr' ) ) {
 39+ function mb_substr( $str, $start ) {
 40+ $ar = array();
 41+ preg_match_all( '/./us', $str, $ar );
 42+
 43+ if( func_num_args() >= 3 ) {
 44+ $end = func_get_arg( 2 );
 45+ return join( '', array_slice( $ar[0], $start, $end ) );
 46+ } else {
 47+ return join( '', array_slice( $ar[0], $start ) );
 48+ }
 49+ }
 50+}
 51+
 52+if ( !function_exists( 'mb_strlen' ) ) {
 53+ /**
 54+ * Fallback implementation of mb_strlen, hardcoded to UTF-8.
 55+ * @param string $str
 56+ * @param string $enc optional encoding; ignored
 57+ * @return int
 58+ */
 59+ function mb_strlen( $str, $enc="" ) {
 60+ $counts = count_chars( $str );
 61+ $total = 0;
 62+
 63+ // Count ASCII bytes
 64+ for( $i = 0; $i < 0x80; $i++ ) {
 65+ $total += $counts[$i];
 66+ }
 67+
 68+ // Count multibyte sequence heads
 69+ for( $i = 0xc0; $i < 0xff; $i++ ) {
 70+ $total += $counts[$i];
 71+ }
 72+ return $total;
 73+ }
 74+}
 75+
 76+if ( !function_exists( 'array_diff_key' ) ) {
 77+ /**
 78+ * Exists in PHP 5.1.0+
 79+ * Not quite compatible, two-argument version only
 80+ * Null values will cause problems due to this use of isset()
 81+ */
 82+ function array_diff_key( $left, $right ) {
 83+ $result = $left;
 84+ foreach ( $left as $key => $unused ) {
 85+ if ( isset( $right[$key] ) ) {
 86+ unset( $result[$key] );
 87+ }
 88+ }
 89+ return $result;
 90+ }
 91+}
 92+
 93+/// @endcond
 94+
 95+
 96+/**
 97+ * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
 98+ */
 99+function wfArrayDiff2( $a, $b ) {
 100+ return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
 101+}
 102+function wfArrayDiff2_cmp( $a, $b ) {
 103+ if ( !is_array( $a ) ) {
 104+ return strcmp( $a, $b );
 105+ } elseif ( count( $a ) !== count( $b ) ) {
 106+ return count( $a ) < count( $b ) ? -1 : 1;
 107+ } else {
 108+ reset( $a );
 109+ reset( $b );
 110+ while( ( list( $keyA, $valueA ) = each( $a ) ) && ( list( $keyB, $valueB ) = each( $b ) ) ) {
 111+ $cmp = strcmp( $valueA, $valueB );
 112+ if ( $cmp !== 0 ) {
 113+ return $cmp;
 114+ }
 115+ }
 116+ return 0;
 117+ }
 118+}
 119+
 120+/**
 121+ * Wrapper for clone(), for compatibility with PHP4-friendly extensions.
 122+ * PHP 5 won't let you declare a 'clone' function, even conditionally,
 123+ * so it has to be a wrapper with a different name.
 124+ */
 125+function wfClone( $object ) {
 126+ return clone( $object );
 127+}
 128+
 129+/**
 130+ * Seed Mersenne Twister
 131+ * No-op for compatibility; only necessary in PHP < 4.2.0
 132+ */
 133+function wfSeedRandom() {
 134+ /* No-op */
 135+}
 136+
 137+/**
 138+ * Get a random decimal value between 0 and 1, in a way
 139+ * not likely to give duplicate values for any realistic
 140+ * number of articles.
 141+ *
 142+ * @return string
 143+ */
 144+function wfRandom() {
 145+ # The maximum random value is "only" 2^31-1, so get two random
 146+ # values to reduce the chance of dupes
 147+ $max = mt_getrandmax() + 1;
 148+ $rand = number_format( (mt_rand() * $max + mt_rand())
 149+ / $max / $max, 12, '.', '' );
 150+ return $rand;
 151+}
 152+
 153+/**
 154+ * We want some things to be included as literal characters in our title URLs
 155+ * for prettiness, which urlencode encodes by default. According to RFC 1738,
 156+ * all of the following should be safe:
 157+ *
 158+ * ;:@&=$-_.+!*'(),
 159+ *
 160+ * But + is not safe because it's used to indicate a space; &= are only safe in
 161+ * paths and not in queries (and we don't distinguish here); ' seems kind of
 162+ * scary; and urlencode() doesn't touch -_. to begin with. Plus, although /
 163+ * is reserved, we don't care. So the list we unescape is:
 164+ *
 165+ * ;:@$!*(),/
 166+ *
 167+ * %2F in the page titles seems to fatally break for some reason.
 168+ *
 169+ * @param $s String:
 170+ * @return string
 171+*/
 172+function wfUrlencode( $s ) {
 173+ $s = urlencode( $s );
 174+ $s = str_ireplace(
 175+ array( '%3B','%3A','%40','%24','%21','%2A','%28','%29','%2C','%2F' ),
 176+ array( ';', ':', '@', '$', '!', '*', '(', ')', ',', '/' ),
 177+ $s
 178+ );
 179+
 180+ return $s;
 181+}
 182+
 183+/**
 184+ * Sends a line to the debug log if enabled or, optionally, to a comment in output.
 185+ * In normal operation this is a NOP.
 186+ *
 187+ * Controlling globals:
 188+ * $wgDebugLogFile - points to the log file
 189+ * $wgProfileOnly - if set, normal debug messages will not be recorded.
 190+ * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
 191+ * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
 192+ *
 193+ * @param $text String
 194+ * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
 195+ */
 196+function wfDebug( $text, $logonly = false ) {
 197+ global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
 198+ global $wgDebugLogPrefix;
 199+ static $recursion = 0;
 200+
 201+ static $cache = array(); // Cache of unoutputted messages
 202+
 203+ # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
 204+ if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
 205+ return;
 206+ }
 207+
 208+ if ( $wgDebugComments && !$logonly ) {
 209+ $cache[] = $text;
 210+
 211+ if ( !isset( $wgOut ) ) {
 212+ return;
 213+ }
 214+ if ( !StubObject::isRealObject( $wgOut ) ) {
 215+ if ( $recursion ) {
 216+ return;
 217+ }
 218+ $recursion++;
 219+ $wgOut->_unstub();
 220+ $recursion--;
 221+ }
 222+
 223+ // add the message and possible cached ones to the output
 224+ array_map( array( $wgOut, 'debug' ), $cache );
 225+ $cache = array();
 226+ }
 227+ if ( '' != $wgDebugLogFile && !$wgProfileOnly ) {
 228+ # Strip unprintables; they can switch terminal modes when binary data
 229+ # gets dumped, which is pretty annoying.
 230+ $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
 231+ $text = $wgDebugLogPrefix . $text;
 232+ wfErrorLog( $text, $wgDebugLogFile );
 233+ }
 234+}
 235+
 236+/**
 237+ * Send a line giving PHP memory usage.
 238+ * @param $exact Bool : print exact values instead of kilobytes (default: false)
 239+ */
 240+function wfDebugMem( $exact = false ) {
 241+ $mem = memory_get_usage();
 242+ if( !$exact ) {
 243+ $mem = floor( $mem / 1024 ) . ' kilobytes';
 244+ } else {
 245+ $mem .= ' bytes';
 246+ }
 247+ wfDebug( "Memory usage: $mem\n" );
 248+}
 249+
 250+/**
 251+ * Send a line to a supplementary debug log file, if configured, or main debug log if not.
 252+ * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
 253+ *
 254+ * @param $logGroup String
 255+ * @param $text String
 256+ * @param $public Bool: whether to log the event in the public log if no private
 257+ * log file is specified, (default true)
 258+ */
 259+function wfDebugLog( $logGroup, $text, $public = true ) {
 260+ global $wgDebugLogGroups, $wgShowHostnames;
 261+ $text = trim($text)."\n";
 262+ if( isset( $wgDebugLogGroups[$logGroup] ) ) {
 263+ $time = wfTimestamp( TS_DB );
 264+ $wiki = wfWikiID();
 265+ if ( $wgShowHostnames ) {
 266+ $host = wfHostname();
 267+ } else {
 268+ $host = '';
 269+ }
 270+ wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
 271+ } else if ( $public === true ) {
 272+ wfDebug( $text, true );
 273+ }
 274+}
 275+
 276+/**
 277+ * Log for database errors
 278+ * @param $text String: database error message.
 279+ */
 280+function wfLogDBError( $text ) {
 281+ global $wgDBerrorLog, $wgDBname;
 282+ if ( $wgDBerrorLog ) {
 283+ $host = trim(`hostname`);
 284+ $text = date('D M j G:i:s T Y') . "\t$host\t$wgDBname\t$text";
 285+ wfErrorLog( $text, $wgDBerrorLog );
 286+ }
 287+}
 288+
 289+/**
 290+ * Log to a file without getting "file size exceeded" signals.
 291+ *
 292+ * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
 293+ * send lines to the specified port, prefixed by the specified prefix and a space.
 294+ */
 295+function wfErrorLog( $text, $file ) {
 296+ if ( substr( $file, 0, 4 ) == 'udp:' ) {
 297+ if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
 298+ // IPv6 bracketed host
 299+ $protocol = $m[1];
 300+ $host = $m[2];
 301+ $port = $m[3];
 302+ $prefix = isset( $m[4] ) ? $m[4] : false;
 303+ } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
 304+ $protocol = $m[1];
 305+ $host = $m[2];
 306+ $port = $m[3];
 307+ $prefix = isset( $m[4] ) ? $m[4] : false;
 308+ } else {
 309+ throw new MWException( __METHOD__.": Invalid UDP specification" );
 310+ }
 311+ // Clean it up for the multiplexer
 312+ if ( strval( $prefix ) !== '' ) {
 313+ $text = preg_replace( '/^/m', $prefix . ' ', $text );
 314+ if ( substr( $text, -1 ) != "\n" ) {
 315+ $text .= "\n";
 316+ }
 317+ }
 318+
 319+ $sock = fsockopen( "$protocol://$host", $port );
 320+ if ( !$sock ) {
 321+ return;
 322+ }
 323+ fwrite( $sock, $text );
 324+ fclose( $sock );
 325+ } else {
 326+ wfSuppressWarnings();
 327+ $exists = file_exists( $file );
 328+ $size = $exists ? filesize( $file ) : false;
 329+ if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
 330+ error_log( $text, 3, $file );
 331+ }
 332+ wfRestoreWarnings();
 333+ }
 334+}
 335+
 336+/**
 337+ * @todo document
 338+ */
 339+function wfLogProfilingData() {
 340+ global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
 341+ global $wgProfiler, $wgUser;
 342+ if ( !isset( $wgProfiler ) )
 343+ return;
 344+
 345+ $now = wfTime();
 346+ $elapsed = $now - $wgRequestTime;
 347+ $prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
 348+ $forward = '';
 349+ if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
 350+ $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
 351+ if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) )
 352+ $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
 353+ if( !empty( $_SERVER['HTTP_FROM'] ) )
 354+ $forward .= ' from ' . $_SERVER['HTTP_FROM'];
 355+ if( $forward )
 356+ $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
 357+ // Don't unstub $wgUser at this late stage just for statistics purposes
 358+ if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() )
 359+ $forward .= ' anon';
 360+ $log = sprintf( "%s\t%04.3f\t%s\n",
 361+ gmdate( 'YmdHis' ), $elapsed,
 362+ urldecode( $wgRequest->getRequestURL() . $forward ) );
 363+ if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) {
 364+ wfErrorLog( $log . $prof, $wgDebugLogFile );
 365+ }
 366+}
 367+
 368+/**
 369+ * Check if the wiki read-only lock file is present. This can be used to lock
 370+ * off editing functions, but doesn't guarantee that the database will not be
 371+ * modified.
 372+ * @return bool
 373+ */
 374+function wfReadOnly() {
 375+ global $wgReadOnlyFile, $wgReadOnly;
 376+
 377+ if ( !is_null( $wgReadOnly ) ) {
 378+ return (bool)$wgReadOnly;
 379+ }
 380+ if ( '' == $wgReadOnlyFile ) {
 381+ return false;
 382+ }
 383+ // Set $wgReadOnly for faster access next time
 384+ if ( is_file( $wgReadOnlyFile ) ) {
 385+ $wgReadOnly = file_get_contents( $wgReadOnlyFile );
 386+ } else {
 387+ $wgReadOnly = false;
 388+ }
 389+ return (bool)$wgReadOnly;
 390+}
 391+
 392+function wfReadOnlyReason() {
 393+ global $wgReadOnly;
 394+ wfReadOnly();
 395+ return $wgReadOnly;
 396+}
 397+
 398+/**
 399+ * Return a Language object from $langcode
 400+ * @param $langcode Mixed: either:
 401+ * - a Language object
 402+ * - code of the language to get the message for, if it is
 403+ * a valid code create a language for that language, if
 404+ * it is a string but not a valid code then make a basic
 405+ * language object
 406+ * - a boolean: if it's false then use the current users
 407+ * language (as a fallback for the old parameter
 408+ * functionality), or if it is true then use the wikis
 409+ * @return Language object
 410+ */
 411+function wfGetLangObj( $langcode = false ){
 412+ # Identify which language to get or create a language object for.
 413+ if( $langcode instanceof Language )
 414+ # Great, we already have the object!
 415+ return $langcode;
 416+
 417+ global $wgContLang;
 418+ if( $langcode === $wgContLang->getCode() || $langcode === true )
 419+ # $langcode is the language code of the wikis content language object.
 420+ # or it is a boolean and value is true
 421+ return $wgContLang;
 422+
 423+ global $wgLang;
 424+ if( $langcode === $wgLang->getCode() || $langcode === false )
 425+ # $langcode is the language code of user language object.
 426+ # or it was a boolean and value is false
 427+ return $wgLang;
 428+
 429+ $validCodes = array_keys( Language::getLanguageNames() );
 430+ if( in_array( $langcode, $validCodes ) )
 431+ # $langcode corresponds to a valid language.
 432+ return Language::factory( $langcode );
 433+
 434+ # $langcode is a string, but not a valid language code; use content language.
 435+ wfDebug( 'Invalid language code passed to wfGetLangObj, falling back to content language.' );
 436+ return $wgContLang;
 437+}
 438+
 439+/**
 440+ * Get a message from anywhere, for the current user language.
 441+ *
 442+ * Use wfMsgForContent() instead if the message should NOT
 443+ * change depending on the user preferences.
 444+ *
 445+ * @param $key String: lookup key for the message, usually
 446+ * defined in languages/Language.php
 447+ *
 448+ * This function also takes extra optional parameters (not
 449+ * shown in the function definition), which can by used to
 450+ * insert variable text into the predefined message.
 451+ */
 452+function wfMsg( $key ) {
 453+ $args = func_get_args();
 454+ array_shift( $args );
 455+ return wfMsgReal( $key, $args, true );
 456+}
 457+
 458+/**
 459+ * Same as above except doesn't transform the message
 460+ */
 461+function wfMsgNoTrans( $key ) {
 462+ $args = func_get_args();
 463+ array_shift( $args );
 464+ return wfMsgReal( $key, $args, true, false, false );
 465+}
 466+
 467+/**
 468+ * Get a message from anywhere, for the current global language
 469+ * set with $wgLanguageCode.
 470+ *
 471+ * Use this if the message should NOT change dependent on the
 472+ * language set in the user's preferences. This is the case for
 473+ * most text written into logs, as well as link targets (such as
 474+ * the name of the copyright policy page). Link titles, on the
 475+ * other hand, should be shown in the UI language.
 476+ *
 477+ * Note that MediaWiki allows users to change the user interface
 478+ * language in their preferences, but a single installation
 479+ * typically only contains content in one language.
 480+ *
 481+ * Be wary of this distinction: If you use wfMsg() where you should
 482+ * use wfMsgForContent(), a user of the software may have to
 483+ * customize over 70 messages in order to, e.g., fix a link in every
 484+ * possible language.
 485+ *
 486+ * @param $key String: lookup key for the message, usually
 487+ * defined in languages/Language.php
 488+ */
 489+function wfMsgForContent( $key ) {
 490+ global $wgForceUIMsgAsContentMsg;
 491+ $args = func_get_args();
 492+ array_shift( $args );
 493+ $forcontent = true;
 494+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 495+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 496+ $forcontent = false;
 497+ return wfMsgReal( $key, $args, true, $forcontent );
 498+}
 499+
 500+/**
 501+ * Same as above except doesn't transform the message
 502+ */
 503+function wfMsgForContentNoTrans( $key ) {
 504+ global $wgForceUIMsgAsContentMsg;
 505+ $args = func_get_args();
 506+ array_shift( $args );
 507+ $forcontent = true;
 508+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 509+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 510+ $forcontent = false;
 511+ return wfMsgReal( $key, $args, true, $forcontent, false );
 512+}
 513+
 514+/**
 515+ * Get a message from the language file, for the UI elements
 516+ */
 517+function wfMsgNoDB( $key ) {
 518+ $args = func_get_args();
 519+ array_shift( $args );
 520+ return wfMsgReal( $key, $args, false );
 521+}
 522+
 523+/**
 524+ * Get a message from the language file, for the content
 525+ */
 526+function wfMsgNoDBForContent( $key ) {
 527+ global $wgForceUIMsgAsContentMsg;
 528+ $args = func_get_args();
 529+ array_shift( $args );
 530+ $forcontent = true;
 531+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 532+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 533+ $forcontent = false;
 534+ return wfMsgReal( $key, $args, false, $forcontent );
 535+}
 536+
 537+
 538+/**
 539+ * Really get a message
 540+ * @param $key String: key to get.
 541+ * @param $args
 542+ * @param $useDB Boolean
 543+ * @param $transform Boolean: Whether or not to transform the message.
 544+ * @param $forContent Boolean
 545+ * @return String: the requested message.
 546+ */
 547+function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) {
 548+ wfProfileIn( __METHOD__ );
 549+ $message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
 550+ $message = wfMsgReplaceArgs( $message, $args );
 551+ wfProfileOut( __METHOD__ );
 552+ return $message;
 553+}
 554+
 555+/**
 556+ * This function provides the message source for messages to be edited which are *not* stored in the database.
 557+ * @param $key String:
 558+ */
 559+function wfMsgWeirdKey ( $key ) {
 560+ $source = wfMsgGetKey( $key, false, true, false );
 561+ if ( wfEmptyMsg( $key, $source ) )
 562+ return "";
 563+ else
 564+ return $source;
 565+}
 566+
 567+/**
 568+ * Fetch a message string value, but don't replace any keys yet.
 569+ * @param string $key
 570+ * @param bool $useDB
 571+ * @param string $langcode Code of the language to get the message for, or
 572+ * behaves as a content language switch if it is a
 573+ * boolean.
 574+ * @return string
 575+ * @private
 576+ */
 577+function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
 578+ global $wgContLang, $wgMessageCache;
 579+
 580+ wfRunHooks('NormalizeMessageKey', array(&$key, &$useDB, &$langCode, &$transform));
 581+
 582+ # If $wgMessageCache isn't initialised yet, try to return something sensible.
 583+ if( is_object( $wgMessageCache ) ) {
 584+ $message = $wgMessageCache->get( $key, $useDB, $langCode );
 585+ if ( $transform ) {
 586+ $message = $wgMessageCache->transform( $message );
 587+ }
 588+ } else {
 589+ $lang = wfGetLangObj( $langCode );
 590+
 591+ # MessageCache::get() does this already, Language::getMessage() doesn't
 592+ # ISSUE: Should we try to handle "message/lang" here too?
 593+ $key = str_replace( ' ' , '_' , $wgContLang->lcfirst( $key ) );
 594+
 595+ if( is_object( $lang ) ) {
 596+ $message = $lang->getMessage( $key );
 597+ } else {
 598+ $message = false;
 599+ }
 600+ }
 601+
 602+ return $message;
 603+}
 604+
 605+/**
 606+ * Replace message parameter keys on the given formatted output.
 607+ *
 608+ * @param string $message
 609+ * @param array $args
 610+ * @return string
 611+ * @private
 612+ */
 613+function wfMsgReplaceArgs( $message, $args ) {
 614+ # Fix windows line-endings
 615+ # Some messages are split with explode("\n", $msg)
 616+ $message = str_replace( "\r", '', $message );
 617+
 618+ // Replace arguments
 619+ if ( count( $args ) ) {
 620+ if ( is_array( $args[0] ) ) {
 621+ $args = array_values( $args[0] );
 622+ }
 623+ $replacementKeys = array();
 624+ foreach( $args as $n => $param ) {
 625+ $replacementKeys['$' . ($n + 1)] = $param;
 626+ }
 627+ $message = strtr( $message, $replacementKeys );
 628+ }
 629+
 630+ return $message;
 631+}
 632+
 633+/**
 634+ * Return an HTML-escaped version of a message.
 635+ * Parameter replacements, if any, are done *after* the HTML-escaping,
 636+ * so parameters may contain HTML (eg links or form controls). Be sure
 637+ * to pre-escape them if you really do want plaintext, or just wrap
 638+ * the whole thing in htmlspecialchars().
 639+ *
 640+ * @param string $key
 641+ * @param string ... parameters
 642+ * @return string
 643+ */
 644+function wfMsgHtml( $key ) {
 645+ $args = func_get_args();
 646+ array_shift( $args );
 647+ return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
 648+}
 649+
 650+/**
 651+ * Return an HTML version of message
 652+ * Parameter replacements, if any, are done *after* parsing the wiki-text message,
 653+ * so parameters may contain HTML (eg links or form controls). Be sure
 654+ * to pre-escape them if you really do want plaintext, or just wrap
 655+ * the whole thing in htmlspecialchars().
 656+ *
 657+ * @param string $key
 658+ * @param string ... parameters
 659+ * @return string
 660+ */
 661+function wfMsgWikiHtml( $key ) {
 662+ global $wgOut;
 663+ $args = func_get_args();
 664+ array_shift( $args );
 665+ return wfMsgReplaceArgs( $wgOut->parse( wfMsgGetKey( $key, true ), /* can't be set to false */ true ), $args );
 666+}
 667+
 668+/**
 669+ * Returns message in the requested format
 670+ * @param string $key Key of the message
 671+ * @param array $options Processing rules. Can take the following options:
 672+ * <i>parse</i>: parses wikitext to html
 673+ * <i>parseinline</i>: parses wikitext to html and removes the surrounding
 674+ * p's added by parser or tidy
 675+ * <i>escape</i>: filters message through htmlspecialchars
 676+ * <i>escapenoentities</i>: same, but allows entity references like &nbsp; through
 677+ * <i>replaceafter</i>: parameters are substituted after parsing or escaping
 678+ * <i>parsemag</i>: transform the message using magic phrases
 679+ * <i>content</i>: fetch message for content language instead of interface
 680+ * Also can accept a single associative argument, of the form 'language' => 'xx':
 681+ * <i>language</i>: Language object or language code to fetch message for
 682+ * (overriden by <i>content</i>), its behaviour with parser, parseinline
 683+ * and parsemag is undefined.
 684+ * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
 685+ */
 686+function wfMsgExt( $key, $options ) {
 687+ global $wgOut;
 688+
 689+ $args = func_get_args();
 690+ array_shift( $args );
 691+ array_shift( $args );
 692+ $options = (array)$options;
 693+
 694+ foreach( $options as $arrayKey => $option ) {
 695+ if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
 696+ # An unknown index, neither numeric nor "language"
 697+ trigger_error( "wfMsgExt called with incorrect parameter key $arrayKey", E_USER_WARNING );
 698+ } elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
 699+ array( 'parse', 'parseinline', 'escape', 'escapenoentities',
 700+ 'replaceafter', 'parsemag', 'content' ) ) ) {
 701+ # A numeric index with unknown value
 702+ trigger_error( "wfMsgExt called with incorrect parameter $option", E_USER_WARNING );
 703+ }
 704+ }
 705+
 706+ if( in_array('content', $options, true ) ) {
 707+ $forContent = true;
 708+ $langCode = true;
 709+ } elseif( array_key_exists('language', $options) ) {
 710+ $forContent = false;
 711+ $langCode = wfGetLangObj( $options['language'] );
 712+ } else {
 713+ $forContent = false;
 714+ $langCode = false;
 715+ }
 716+
 717+ $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
 718+
 719+ if( !in_array('replaceafter', $options, true ) ) {
 720+ $string = wfMsgReplaceArgs( $string, $args );
 721+ }
 722+
 723+ if( in_array('parse', $options, true ) ) {
 724+ $string = $wgOut->parse( $string, true, !$forContent );
 725+ } elseif ( in_array('parseinline', $options, true ) ) {
 726+ $string = $wgOut->parse( $string, true, !$forContent );
 727+ $m = array();
 728+ if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
 729+ $string = $m[1];
 730+ }
 731+ } elseif ( in_array('parsemag', $options, true ) ) {
 732+ global $wgMessageCache;
 733+ if ( isset( $wgMessageCache ) ) {
 734+ $string = $wgMessageCache->transform( $string,
 735+ !$forContent,
 736+ is_object( $langCode ) ? $langCode : null );
 737+ }
 738+ }
 739+
 740+ if ( in_array('escape', $options, true ) ) {
 741+ $string = htmlspecialchars ( $string );
 742+ } elseif ( in_array( 'escapenoentities', $options, true ) ) {
 743+ $string = Sanitizer::escapeHtmlAllowEntities( $string );
 744+ }
 745+
 746+ if( in_array('replaceafter', $options, true ) ) {
 747+ $string = wfMsgReplaceArgs( $string, $args );
 748+ }
 749+
 750+ return $string;
 751+}
 752+
 753+
 754+/**
 755+ * Just like exit() but makes a note of it.
 756+ * Commits open transactions except if the error parameter is set
 757+ *
 758+ * @deprecated Please return control to the caller or throw an exception
 759+ */
 760+function wfAbruptExit( $error = false ){
 761+ static $called = false;
 762+ if ( $called ){
 763+ exit( -1 );
 764+ }
 765+ $called = true;
 766+
 767+ $bt = wfDebugBacktrace();
 768+ if( $bt ) {
 769+ for($i = 0; $i < count($bt) ; $i++){
 770+ $file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown";
 771+ $line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown";
 772+ wfDebug("WARNING: Abrupt exit in $file at line $line\n");
 773+ }
 774+ } else {
 775+ wfDebug('WARNING: Abrupt exit\n');
 776+ }
 777+
 778+ wfLogProfilingData();
 779+
 780+ if ( !$error ) {
 781+ wfGetLB()->closeAll();
 782+ }
 783+ exit( -1 );
 784+}
 785+
 786+/**
 787+ * @deprecated Please return control the caller or throw an exception
 788+ */
 789+function wfErrorExit() {
 790+ wfAbruptExit( true );
 791+}
 792+
 793+/**
 794+ * Print a simple message and die, returning nonzero to the shell if any.
 795+ * Plain die() fails to return nonzero to the shell if you pass a string.
 796+ * @param string $msg
 797+ */
 798+function wfDie( $msg='' ) {
 799+ echo $msg;
 800+ die( 1 );
 801+}
 802+
 803+/**
 804+ * Throw a debugging exception. This function previously once exited the process,
 805+ * but now throws an exception instead, with similar results.
 806+ *
 807+ * @param string $msg Message shown when dieing.
 808+ */
 809+function wfDebugDieBacktrace( $msg = '' ) {
 810+ throw new MWException( $msg );
 811+}
 812+
 813+/**
 814+ * Fetch server name for use in error reporting etc.
 815+ * Use real server name if available, so we know which machine
 816+ * in a server farm generated the current page.
 817+ * @return string
 818+ */
 819+function wfHostname() {
 820+ static $host;
 821+ if ( is_null( $host ) ) {
 822+ if ( function_exists( 'posix_uname' ) ) {
 823+ // This function not present on Windows
 824+ $uname = @posix_uname();
 825+ } else {
 826+ $uname = false;
 827+ }
 828+ if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
 829+ $host = $uname['nodename'];
 830+ } elseif ( getenv( 'COMPUTERNAME' ) ) {
 831+ # Windows computer name
 832+ $host = getenv( 'COMPUTERNAME' );
 833+ } else {
 834+ # This may be a virtual server.
 835+ $host = $_SERVER['SERVER_NAME'];
 836+ }
 837+ }
 838+ return $host;
 839+}
 840+
 841+ /**
 842+ * Returns a HTML comment with the elapsed time since request.
 843+ * This method has no side effects.
 844+ * @return string
 845+ */
 846+ function wfReportTime() {
 847+ global $wgRequestTime, $wgShowHostnames;
 848+
 849+ $now = wfTime();
 850+ $elapsed = $now - $wgRequestTime;
 851+
 852+ return $wgShowHostnames
 853+ ? sprintf( "<!-- Served by %s in %01.3f secs. -->", wfHostname(), $elapsed )
 854+ : sprintf( "<!-- Served in %01.3f secs. -->", $elapsed );
 855+ }
 856+
 857+/**
 858+ * Safety wrapper for debug_backtrace().
 859+ *
 860+ * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
 861+ * murky circumstances, which may be triggered in part by stub objects
 862+ * or other fancy talkin'.
 863+ *
 864+ * Will return an empty array if Zend Optimizer is detected, otherwise
 865+ * the output from debug_backtrace() (trimmed).
 866+ *
 867+ * @return array of backtrace information
 868+ */
 869+function wfDebugBacktrace() {
 870+ if( extension_loaded( 'Zend Optimizer' ) ) {
 871+ wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
 872+ return array();
 873+ } else {
 874+ return array_slice( debug_backtrace(), 1 );
 875+ }
 876+}
 877+
 878+function wfBacktrace() {
 879+ global $wgCommandLineMode;
 880+
 881+ if ( $wgCommandLineMode ) {
 882+ $msg = '';
 883+ } else {
 884+ $msg = "<ul>\n";
 885+ }
 886+ $backtrace = wfDebugBacktrace();
 887+ foreach( $backtrace as $call ) {
 888+ if( isset( $call['file'] ) ) {
 889+ $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
 890+ $file = $f[count($f)-1];
 891+ } else {
 892+ $file = '-';
 893+ }
 894+ if( isset( $call['line'] ) ) {
 895+ $line = $call['line'];
 896+ } else {
 897+ $line = '-';
 898+ }
 899+ if ( $wgCommandLineMode ) {
 900+ $msg .= "$file line $line calls ";
 901+ } else {
 902+ $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
 903+ }
 904+ if( !empty( $call['class'] ) ) $msg .= $call['class'] . '::';
 905+ $msg .= $call['function'] . '()';
 906+
 907+ if ( $wgCommandLineMode ) {
 908+ $msg .= "\n";
 909+ } else {
 910+ $msg .= "</li>\n";
 911+ }
 912+ }
 913+ if ( $wgCommandLineMode ) {
 914+ $msg .= "\n";
 915+ } else {
 916+ $msg .= "</ul>\n";
 917+ }
 918+
 919+ return $msg;
 920+}
 921+
 922+
 923+/* Some generic result counters, pulled out of SearchEngine */
 924+
 925+
 926+/**
 927+ * @todo document
 928+ */
 929+function wfShowingResults( $offset, $limit ) {
 930+ global $wgLang;
 931+ return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) );
 932+}
 933+
 934+/**
 935+ * @todo document
 936+ */
 937+function wfShowingResultsNum( $offset, $limit, $num ) {
 938+ global $wgLang;
 939+ return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
 940+}
 941+
 942+/**
 943+ * @todo document
 944+ */
 945+function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
 946+ global $wgLang;
 947+ $fmtLimit = $wgLang->formatNum( $limit );
 948+ $prev = wfMsg( 'prevn', $fmtLimit );
 949+ $next = wfMsg( 'nextn', $fmtLimit );
 950+
 951+ if( is_object( $link ) ) {
 952+ $title =& $link;
 953+ } else {
 954+ $title = Title::newFromText( $link );
 955+ if( is_null( $title ) ) {
 956+ return false;
 957+ }
 958+ }
 959+
 960+ if ( 0 != $offset ) {
 961+ $po = $offset - $limit;
 962+ if ( $po < 0 ) { $po = 0; }
 963+ $q = "limit={$limit}&offset={$po}";
 964+ if ( '' != $query ) { $q .= '&'.$query; }
 965+ $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-prevlink\">{$prev}</a>";
 966+ } else { $plink = $prev; }
 967+
 968+ $no = $offset + $limit;
 969+ $q = 'limit='.$limit.'&offset='.$no;
 970+ if ( '' != $query ) { $q .= '&'.$query; }
 971+
 972+ if ( $atend ) {
 973+ $nlink = $next;
 974+ } else {
 975+ $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-nextlink\">{$next}</a>";
 976+ }
 977+ $nums = wfNumLink( $offset, 20, $title, $query ) . ' | ' .
 978+ wfNumLink( $offset, 50, $title, $query ) . ' | ' .
 979+ wfNumLink( $offset, 100, $title, $query ) . ' | ' .
 980+ wfNumLink( $offset, 250, $title, $query ) . ' | ' .
 981+ wfNumLink( $offset, 500, $title, $query );
 982+
 983+ return wfMsg( 'viewprevnext', $plink, $nlink, $nums );
 984+}
 985+
 986+/**
 987+ * @todo document
 988+ */
 989+function wfNumLink( $offset, $limit, &$title, $query = '' ) {
 990+ global $wgLang;
 991+ if ( '' == $query ) { $q = ''; }
 992+ else { $q = $query.'&'; }
 993+ $q .= 'limit='.$limit.'&offset='.$offset;
 994+
 995+ $fmtLimit = $wgLang->formatNum( $limit );
 996+ $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-numlink\">{$fmtLimit}</a>";
 997+ return $s;
 998+}
 999+
 1000+/**
 1001+ * @todo document
 1002+ * @todo FIXME: we may want to blacklist some broken browsers
 1003+ *
 1004+ * @return bool Whereas client accept gzip compression
 1005+ */
 1006+function wfClientAcceptsGzip() {
 1007+ global $wgUseGzip;
 1008+ if( $wgUseGzip ) {
 1009+ # FIXME: we may want to blacklist some broken browsers
 1010+ $m = array();
 1011+ if( preg_match(
 1012+ '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
 1013+ $_SERVER['HTTP_ACCEPT_ENCODING'],
 1014+ $m ) ) {
 1015+ if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) return false;
 1016+ wfDebug( " accepts gzip\n" );
 1017+ return true;
 1018+ }
 1019+ }
 1020+ return false;
 1021+}
 1022+
 1023+/**
 1024+ * Obtain the offset and limit values from the request string;
 1025+ * used in special pages
 1026+ *
 1027+ * @param $deflimit Default limit if none supplied
 1028+ * @param $optionname Name of a user preference to check against
 1029+ * @return array
 1030+ *
 1031+ */
 1032+function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
 1033+ global $wgRequest;
 1034+ return $wgRequest->getLimitOffset( $deflimit, $optionname );
 1035+}
 1036+
 1037+/**
 1038+ * Escapes the given text so that it may be output using addWikiText()
 1039+ * without any linking, formatting, etc. making its way through. This
 1040+ * is achieved by substituting certain characters with HTML entities.
 1041+ * As required by the callers, <nowiki> is not used. It currently does
 1042+ * not filter out characters which have special meaning only at the
 1043+ * start of a line, such as "*".
 1044+ *
 1045+ * @param string $text Text to be escaped
 1046+ */
 1047+function wfEscapeWikiText( $text ) {
 1048+ $text = str_replace(
 1049+ array( '[', '|', ']', '\'', 'ISBN ', 'RFC ', '://', "\n=", '{{' ), # }}
 1050+ array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;', 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;' ),
 1051+ htmlspecialchars($text) );
 1052+ return $text;
 1053+}
 1054+
 1055+/**
 1056+ * @todo document
 1057+ */
 1058+function wfQuotedPrintable( $string, $charset = '' ) {
 1059+ # Probably incomplete; see RFC 2045
 1060+ if( empty( $charset ) ) {
 1061+ global $wgInputEncoding;
 1062+ $charset = $wgInputEncoding;
 1063+ }
 1064+ $charset = strtoupper( $charset );
 1065+ $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
 1066+
 1067+ $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
 1068+ $replace = $illegal . '\t ?_';
 1069+ if( !preg_match( "/[$illegal]/", $string ) ) return $string;
 1070+ $out = "=?$charset?Q?";
 1071+ $out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
 1072+ $out .= '?=';
 1073+ return $out;
 1074+}
 1075+
 1076+
 1077+/**
 1078+ * @todo document
 1079+ * @return float
 1080+ */
 1081+function wfTime() {
 1082+ return microtime(true);
 1083+}
 1084+
 1085+/**
 1086+ * Sets dest to source and returns the original value of dest
 1087+ * If source is NULL, it just returns the value, it doesn't set the variable
 1088+ */
 1089+function wfSetVar( &$dest, $source ) {
 1090+ $temp = $dest;
 1091+ if ( !is_null( $source ) ) {
 1092+ $dest = $source;
 1093+ }
 1094+ return $temp;
 1095+}
 1096+
 1097+/**
 1098+ * As for wfSetVar except setting a bit
 1099+ */
 1100+function wfSetBit( &$dest, $bit, $state = true ) {
 1101+ $temp = (bool)($dest & $bit );
 1102+ if ( !is_null( $state ) ) {
 1103+ if ( $state ) {
 1104+ $dest |= $bit;
 1105+ } else {
 1106+ $dest &= ~$bit;
 1107+ }
 1108+ }
 1109+ return $temp;
 1110+}
 1111+
 1112+/**
 1113+ * This function takes two arrays as input, and returns a CGI-style string, e.g.
 1114+ * "days=7&limit=100". Options in the first array override options in the second.
 1115+ * Options set to "" will not be output.
 1116+ */
 1117+function wfArrayToCGI( $array1, $array2 = NULL )
 1118+{
 1119+ if ( !is_null( $array2 ) ) {
 1120+ $array1 = $array1 + $array2;
 1121+ }
 1122+
 1123+ $cgi = '';
 1124+ foreach ( $array1 as $key => $value ) {
 1125+ if ( '' !== $value ) {
 1126+ if ( '' != $cgi ) {
 1127+ $cgi .= '&';
 1128+ }
 1129+ if(is_array($value))
 1130+ {
 1131+ $firstTime = true;
 1132+ foreach($value as $v)
 1133+ {
 1134+ $cgi .= ($firstTime ? '' : '&') .
 1135+ urlencode( $key . '[]' ) . '=' .
 1136+ urlencode( $v );
 1137+ $firstTime = false;
 1138+ }
 1139+ }
 1140+ else
 1141+ $cgi .= urlencode( $key ) . '=' .
 1142+ urlencode( $value );
 1143+ }
 1144+ }
 1145+ return $cgi;
 1146+}
 1147+
 1148+/**
 1149+ * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
 1150+ * its argument and returns the same string in array form. This allows compa-
 1151+ * tibility with legacy functions that accept raw query strings instead of nice
 1152+ * arrays. Of course, keys and values are urldecode()d. Don't try passing in-
 1153+ * valid query strings, or it will explode.
 1154+ *
 1155+ * @param $query string Query string
 1156+ * @return array Array version of input
 1157+ */
 1158+function wfCgiToArray( $query ) {
 1159+ if( isset( $query[0] ) and $query[0] == '?' ) {
 1160+ $query = substr( $query, 1 );
 1161+ }
 1162+ $bits = explode( '&', $query );
 1163+ $ret = array();
 1164+ foreach( $bits as $bit ) {
 1165+ if( $bit === '' ) {
 1166+ continue;
 1167+ }
 1168+ list( $key, $value ) = explode( '=', $bit );
 1169+ $key = urldecode( $key );
 1170+ $value = urldecode( $value );
 1171+ $ret[$key] = $value;
 1172+ }
 1173+ return $ret;
 1174+}
 1175+
 1176+/**
 1177+ * Append a query string to an existing URL, which may or may not already
 1178+ * have query string parameters already. If so, they will be combined.
 1179+ *
 1180+ * @param string $url
 1181+ * @param string $query
 1182+ * @return string
 1183+ */
 1184+function wfAppendQuery( $url, $query ) {
 1185+ if( $query != '' ) {
 1186+ if( false === strpos( $url, '?' ) ) {
 1187+ $url .= '?';
 1188+ } else {
 1189+ $url .= '&';
 1190+ }
 1191+ $url .= $query;
 1192+ }
 1193+ return $url;
 1194+}
 1195+
 1196+/**
 1197+ * Expand a potentially local URL to a fully-qualified URL.
 1198+ * Assumes $wgServer is correct. :)
 1199+ * @param string $url, either fully-qualified or a local path + query
 1200+ * @return string Fully-qualified URL
 1201+ */
 1202+function wfExpandUrl( $url ) {
 1203+ if( substr( $url, 0, 1 ) == '/' ) {
 1204+ global $wgServer;
 1205+ return $wgServer . $url;
 1206+ } else {
 1207+ return $url;
 1208+ }
 1209+}
 1210+
 1211+/**
 1212+ * This is obsolete, use SquidUpdate::purge()
 1213+ * @deprecated
 1214+ */
 1215+function wfPurgeSquidServers ($urlArr) {
 1216+ SquidUpdate::purge( $urlArr );
 1217+}
 1218+
 1219+/**
 1220+ * Windows-compatible version of escapeshellarg()
 1221+ * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
 1222+ * function puts single quotes in regardless of OS.
 1223+ *
 1224+ * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
 1225+ * earlier distro releases of PHP)
 1226+ */
 1227+function wfEscapeShellArg( ) {
 1228+ wfInitShellLocale();
 1229+
 1230+ $args = func_get_args();
 1231+ $first = true;
 1232+ $retVal = '';
 1233+ foreach ( $args as $arg ) {
 1234+ if ( !$first ) {
 1235+ $retVal .= ' ';
 1236+ } else {
 1237+ $first = false;
 1238+ }
 1239+
 1240+ if ( wfIsWindows() ) {
 1241+ // Escaping for an MSVC-style command line parser
 1242+ // Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
 1243+ // Double the backslashes before any double quotes. Escape the double quotes.
 1244+ $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
 1245+ $arg = '';
 1246+ $delim = false;
 1247+ foreach ( $tokens as $token ) {
 1248+ if ( $delim ) {
 1249+ $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
 1250+ } else {
 1251+ $arg .= $token;
 1252+ }
 1253+ $delim = !$delim;
 1254+ }
 1255+ // Double the backslashes before the end of the string, because
 1256+ // we will soon add a quote
 1257+ $m = array();
 1258+ if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
 1259+ $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
 1260+ }
 1261+
 1262+ // Add surrounding quotes
 1263+ $retVal .= '"' . $arg . '"';
 1264+ } else {
 1265+ $retVal .= escapeshellarg( $arg );
 1266+ }
 1267+ }
 1268+ return $retVal;
 1269+}
 1270+
 1271+/**
 1272+ * wfMerge attempts to merge differences between three texts.
 1273+ * Returns true for a clean merge and false for failure or a conflict.
 1274+ */
 1275+function wfMerge( $old, $mine, $yours, &$result ){
 1276+ global $wgDiff3;
 1277+
 1278+ # This check may also protect against code injection in
 1279+ # case of broken installations.
 1280+ if( !$wgDiff3 || !file_exists( $wgDiff3 ) ) {
 1281+ wfDebug( "diff3 not found\n" );
 1282+ return false;
 1283+ }
 1284+
 1285+ # Make temporary files
 1286+ $td = wfTempDir();
 1287+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
 1288+ $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
 1289+ $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
 1290+
 1291+ fwrite( $oldtextFile, $old ); fclose( $oldtextFile );
 1292+ fwrite( $mytextFile, $mine ); fclose( $mytextFile );
 1293+ fwrite( $yourtextFile, $yours ); fclose( $yourtextFile );
 1294+
 1295+ # Check for a conflict
 1296+ $cmd = $wgDiff3 . ' -a --overlap-only ' .
 1297+ wfEscapeShellArg( $mytextName ) . ' ' .
 1298+ wfEscapeShellArg( $oldtextName ) . ' ' .
 1299+ wfEscapeShellArg( $yourtextName );
 1300+ $handle = popen( $cmd, 'r' );
 1301+
 1302+ if( fgets( $handle, 1024 ) ){
 1303+ $conflict = true;
 1304+ } else {
 1305+ $conflict = false;
 1306+ }
 1307+ pclose( $handle );
 1308+
 1309+ # Merge differences
 1310+ $cmd = $wgDiff3 . ' -a -e --merge ' .
 1311+ wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
 1312+ $handle = popen( $cmd, 'r' );
 1313+ $result = '';
 1314+ do {
 1315+ $data = fread( $handle, 8192 );
 1316+ if ( strlen( $data ) == 0 ) {
 1317+ break;
 1318+ }
 1319+ $result .= $data;
 1320+ } while ( true );
 1321+ pclose( $handle );
 1322+ unlink( $mytextName ); unlink( $oldtextName ); unlink( $yourtextName );
 1323+
 1324+ if ( $result === '' && $old !== '' && $conflict == false ) {
 1325+ wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
 1326+ $conflict = true;
 1327+ }
 1328+ return ! $conflict;
 1329+}
 1330+
 1331+/**
 1332+ * Returns unified plain-text diff of two texts.
 1333+ * Useful for machine processing of diffs.
 1334+ * @param $before string The text before the changes.
 1335+ * @param $after string The text after the changes.
 1336+ * @param $params string Command-line options for the diff command.
 1337+ * @return string Unified diff of $before and $after
 1338+ */
 1339+function wfDiff( $before, $after, $params = '-u' ) {
 1340+ global $wgDiff;
 1341+
 1342+ # This check may also protect against code injection in
 1343+ # case of broken installations.
 1344+ if( !file_exists( $wgDiff ) ){
 1345+ wfDebug( "diff executable not found\n" );
 1346+ $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
 1347+ $format = new UnifiedDiffFormatter();
 1348+ return $format->format( $diffs );
 1349+ }
 1350+
 1351+ # Make temporary files
 1352+ $td = wfTempDir();
 1353+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
 1354+ $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
 1355+
 1356+ fwrite( $oldtextFile, $before ); fclose( $oldtextFile );
 1357+ fwrite( $newtextFile, $after ); fclose( $newtextFile );
 1358+
 1359+ // Get the diff of the two files
 1360+ $cmd = "$wgDiff " . $params . ' ' .wfEscapeShellArg( $oldtextName, $newtextName );
 1361+
 1362+ $h = popen( $cmd, 'r' );
 1363+
 1364+ $diff = '';
 1365+
 1366+ do {
 1367+ $data = fread( $h, 8192 );
 1368+ if ( strlen( $data ) == 0 ) {
 1369+ break;
 1370+ }
 1371+ $diff .= $data;
 1372+ } while ( true );
 1373+
 1374+ // Clean up
 1375+ pclose( $h );
 1376+ unlink( $oldtextName );
 1377+ unlink( $newtextName );
 1378+
 1379+ // Kill the --- and +++ lines. They're not useful.
 1380+ $diff_lines = explode( "\n", $diff );
 1381+ if (strpos( $diff_lines[0], '---' ) === 0) {
 1382+ unset($diff_lines[0]);
 1383+ }
 1384+ if (strpos( $diff_lines[1], '+++' ) === 0) {
 1385+ unset($diff_lines[1]);
 1386+ }
 1387+
 1388+ $diff = implode( "\n", $diff_lines );
 1389+
 1390+ return $diff;
 1391+}
 1392+
 1393+/**
 1394+ * A wrapper around the PHP function var_export().
 1395+ * Either print it or add it to the regular output ($wgOut).
 1396+ *
 1397+ * @param $var A PHP variable to dump.
 1398+ */
 1399+function wfVarDump( $var ) {
 1400+ global $wgOut;
 1401+ $s = str_replace("\n","<br />\n", var_export( $var, true ) . "\n");
 1402+ if ( headers_sent() || !@is_object( $wgOut ) ) {
 1403+ print $s;
 1404+ } else {
 1405+ $wgOut->addHTML( $s );
 1406+ }
 1407+}
 1408+
 1409+/**
 1410+ * Provide a simple HTTP error.
 1411+ */
 1412+function wfHttpError( $code, $label, $desc ) {
 1413+ global $wgOut;
 1414+ $wgOut->disable();
 1415+ header( "HTTP/1.0 $code $label" );
 1416+ header( "Status: $code $label" );
 1417+ $wgOut->sendCacheControl();
 1418+
 1419+ header( 'Content-type: text/html; charset=utf-8' );
 1420+ print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
 1421+ "<html><head><title>" .
 1422+ htmlspecialchars( $label ) .
 1423+ "</title></head><body><h1>" .
 1424+ htmlspecialchars( $label ) .
 1425+ "</h1><p>" .
 1426+ nl2br( htmlspecialchars( $desc ) ) .
 1427+ "</p></body></html>\n";
 1428+}
 1429+
 1430+/**
 1431+ * Clear away any user-level output buffers, discarding contents.
 1432+ *
 1433+ * Suitable for 'starting afresh', for instance when streaming
 1434+ * relatively large amounts of data without buffering, or wanting to
 1435+ * output image files without ob_gzhandler's compression.
 1436+ *
 1437+ * The optional $resetGzipEncoding parameter controls suppression of
 1438+ * the Content-Encoding header sent by ob_gzhandler; by default it
 1439+ * is left. See comments for wfClearOutputBuffers() for why it would
 1440+ * be used.
 1441+ *
 1442+ * Note that some PHP configuration options may add output buffer
 1443+ * layers which cannot be removed; these are left in place.
 1444+ *
 1445+ * @param bool $resetGzipEncoding
 1446+ */
 1447+function wfResetOutputBuffers( $resetGzipEncoding=true ) {
 1448+ if( $resetGzipEncoding ) {
 1449+ // Suppress Content-Encoding and Content-Length
 1450+ // headers from 1.10+s wfOutputHandler
 1451+ global $wgDisableOutputCompression;
 1452+ $wgDisableOutputCompression = true;
 1453+ }
 1454+ while( $status = ob_get_status() ) {
 1455+ if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
 1456+ // Probably from zlib.output_compression or other
 1457+ // PHP-internal setting which can't be removed.
 1458+ //
 1459+ // Give up, and hope the result doesn't break
 1460+ // output behavior.
 1461+ break;
 1462+ }
 1463+ if( !ob_end_clean() ) {
 1464+ // Could not remove output buffer handler; abort now
 1465+ // to avoid getting in some kind of infinite loop.
 1466+ break;
 1467+ }
 1468+ if( $resetGzipEncoding ) {
 1469+ if( $status['name'] == 'ob_gzhandler' ) {
 1470+ // Reset the 'Content-Encoding' field set by this handler
 1471+ // so we can start fresh.
 1472+ header( 'Content-Encoding:' );
 1473+ break;
 1474+ }
 1475+ }
 1476+ }
 1477+}
 1478+
 1479+/**
 1480+ * More legible than passing a 'false' parameter to wfResetOutputBuffers():
 1481+ *
 1482+ * Clear away output buffers, but keep the Content-Encoding header
 1483+ * produced by ob_gzhandler, if any.
 1484+ *
 1485+ * This should be used for HTTP 304 responses, where you need to
 1486+ * preserve the Content-Encoding header of the real result, but
 1487+ * also need to suppress the output of ob_gzhandler to keep to spec
 1488+ * and avoid breaking Firefox in rare cases where the headers and
 1489+ * body are broken over two packets.
 1490+ */
 1491+function wfClearOutputBuffers() {
 1492+ wfResetOutputBuffers( false );
 1493+}
 1494+
 1495+/**
 1496+ * Converts an Accept-* header into an array mapping string values to quality
 1497+ * factors
 1498+ */
 1499+function wfAcceptToPrefs( $accept, $def = '*/*' ) {
 1500+ # No arg means accept anything (per HTTP spec)
 1501+ if( !$accept ) {
 1502+ return array( $def => 1.0 );
 1503+ }
 1504+
 1505+ $prefs = array();
 1506+
 1507+ $parts = explode( ',', $accept );
 1508+
 1509+ foreach( $parts as $part ) {
 1510+ # FIXME: doesn't deal with params like 'text/html; level=1'
 1511+ @list( $value, $qpart ) = explode( ';', trim( $part ) );
 1512+ $match = array();
 1513+ if( !isset( $qpart ) ) {
 1514+ $prefs[$value] = 1.0;
 1515+ } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
 1516+ $prefs[$value] = floatval($match[1]);
 1517+ }
 1518+ }
 1519+
 1520+ return $prefs;
 1521+}
 1522+
 1523+/**
 1524+ * Checks if a given MIME type matches any of the keys in the given
 1525+ * array. Basic wildcards are accepted in the array keys.
 1526+ *
 1527+ * Returns the matching MIME type (or wildcard) if a match, otherwise
 1528+ * NULL if no match.
 1529+ *
 1530+ * @param string $type
 1531+ * @param array $avail
 1532+ * @return string
 1533+ * @private
 1534+ */
 1535+function mimeTypeMatch( $type, $avail ) {
 1536+ if( array_key_exists($type, $avail) ) {
 1537+ return $type;
 1538+ } else {
 1539+ $parts = explode( '/', $type );
 1540+ if( array_key_exists( $parts[0] . '/*', $avail ) ) {
 1541+ return $parts[0] . '/*';
 1542+ } elseif( array_key_exists( '*/*', $avail ) ) {
 1543+ return '*/*';
 1544+ } else {
 1545+ return NULL;
 1546+ }
 1547+ }
 1548+}
 1549+
 1550+/**
 1551+ * Returns the 'best' match between a client's requested internet media types
 1552+ * and the server's list of available types. Each list should be an associative
 1553+ * array of type to preference (preference is a float between 0.0 and 1.0).
 1554+ * Wildcards in the types are acceptable.
 1555+ *
 1556+ * @param array $cprefs Client's acceptable type list
 1557+ * @param array $sprefs Server's offered types
 1558+ * @return string
 1559+ *
 1560+ * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
 1561+ * XXX: generalize to negotiate other stuff
 1562+ */
 1563+function wfNegotiateType( $cprefs, $sprefs ) {
 1564+ $combine = array();
 1565+
 1566+ foreach( array_keys($sprefs) as $type ) {
 1567+ $parts = explode( '/', $type );
 1568+ if( $parts[1] != '*' ) {
 1569+ $ckey = mimeTypeMatch( $type, $cprefs );
 1570+ if( $ckey ) {
 1571+ $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
 1572+ }
 1573+ }
 1574+ }
 1575+
 1576+ foreach( array_keys( $cprefs ) as $type ) {
 1577+ $parts = explode( '/', $type );
 1578+ if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
 1579+ $skey = mimeTypeMatch( $type, $sprefs );
 1580+ if( $skey ) {
 1581+ $combine[$type] = $sprefs[$skey] * $cprefs[$type];
 1582+ }
 1583+ }
 1584+ }
 1585+
 1586+ $bestq = 0;
 1587+ $besttype = NULL;
 1588+
 1589+ foreach( array_keys( $combine ) as $type ) {
 1590+ if( $combine[$type] > $bestq ) {
 1591+ $besttype = $type;
 1592+ $bestq = $combine[$type];
 1593+ }
 1594+ }
 1595+
 1596+ return $besttype;
 1597+}
 1598+
 1599+/**
 1600+ * Array lookup
 1601+ * Returns an array where the values in the first array are replaced by the
 1602+ * values in the second array with the corresponding keys
 1603+ *
 1604+ * @return array
 1605+ */
 1606+function wfArrayLookup( $a, $b ) {
 1607+ return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
 1608+}
 1609+
 1610+/**
 1611+ * Convenience function; returns MediaWiki timestamp for the present time.
 1612+ * @return string
 1613+ */
 1614+function wfTimestampNow() {
 1615+ # return NOW
 1616+ return wfTimestamp( TS_MW, time() );
 1617+}
 1618+
 1619+/**
 1620+ * Reference-counted warning suppression
 1621+ */
 1622+function wfSuppressWarnings( $end = false ) {
 1623+ static $suppressCount = 0;
 1624+ static $originalLevel = false;
 1625+
 1626+ if ( $end ) {
 1627+ if ( $suppressCount ) {
 1628+ --$suppressCount;
 1629+ if ( !$suppressCount ) {
 1630+ error_reporting( $originalLevel );
 1631+ }
 1632+ }
 1633+ } else {
 1634+ if ( !$suppressCount ) {
 1635+ $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE ) );
 1636+ }
 1637+ ++$suppressCount;
 1638+ }
 1639+}
 1640+
 1641+/**
 1642+ * Restore error level to previous value
 1643+ */
 1644+function wfRestoreWarnings() {
 1645+ wfSuppressWarnings( true );
 1646+}
 1647+
 1648+# Autodetect, convert and provide timestamps of various types
 1649+
 1650+/**
 1651+ * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
 1652+ */
 1653+define('TS_UNIX', 0);
 1654+
 1655+/**
 1656+ * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
 1657+ */
 1658+define('TS_MW', 1);
 1659+
 1660+/**
 1661+ * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
 1662+ */
 1663+define('TS_DB', 2);
 1664+
 1665+/**
 1666+ * RFC 2822 format, for E-mail and HTTP headers
 1667+ */
 1668+define('TS_RFC2822', 3);
 1669+
 1670+/**
 1671+ * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
 1672+ *
 1673+ * This is used by Special:Export
 1674+ */
 1675+define('TS_ISO_8601', 4);
 1676+
 1677+/**
 1678+ * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
 1679+ *
 1680+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
 1681+ * DateTime tag and page 36 for the DateTimeOriginal and
 1682+ * DateTimeDigitized tags.
 1683+ */
 1684+define('TS_EXIF', 5);
 1685+
 1686+/**
 1687+ * Oracle format time.
 1688+ */
 1689+define('TS_ORACLE', 6);
 1690+
 1691+/**
 1692+ * Postgres format time.
 1693+ */
 1694+define('TS_POSTGRES', 7);
 1695+
 1696+/**
 1697+ * @param mixed $outputtype A timestamp in one of the supported formats, the
 1698+ * function will autodetect which format is supplied
 1699+ * and act accordingly.
 1700+ * @return string Time in the format specified in $outputtype
 1701+ */
 1702+function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
 1703+ $uts = 0;
 1704+ $da = array();
 1705+ if ($ts==0) {
 1706+ $uts=time();
 1707+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
 1708+ # TS_DB
 1709+ } elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
 1710+ # TS_EXIF
 1711+ } elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) {
 1712+ # TS_MW
 1713+ } elseif (preg_match('/^\d{1,13}$/D',$ts)) {
 1714+ # TS_UNIX
 1715+ $uts = $ts;
 1716+ } elseif (preg_match('/^\d{1,2}-...-\d\d(?:\d\d)? \d\d\.\d\d\.\d\d/', $ts)) {
 1717+ # TS_ORACLE
 1718+ $uts = strtotime(preg_replace('/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
 1719+ str_replace("+00:00", "UTC", $ts)));
 1720+ } elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da)) {
 1721+ # TS_ISO_8601
 1722+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',$ts,$da)) {
 1723+ # TS_POSTGRES
 1724+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',$ts,$da)) {
 1725+ # TS_POSTGRES
 1726+ } else {
 1727+ # Bogus value; fall back to the epoch...
 1728+ wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
 1729+ $uts = 0;
 1730+ }
 1731+
 1732+ if (count( $da ) ) {
 1733+ // Warning! gmmktime() acts oddly if the month or day is set to 0
 1734+ // We may want to handle that explicitly at some point
 1735+ $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
 1736+ (int)$da[2],(int)$da[3],(int)$da[1]);
 1737+ }
 1738+
 1739+ switch($outputtype) {
 1740+ case TS_UNIX:
 1741+ return $uts;
 1742+ case TS_MW:
 1743+ return gmdate( 'YmdHis', $uts );
 1744+ case TS_DB:
 1745+ return gmdate( 'Y-m-d H:i:s', $uts );
 1746+ case TS_ISO_8601:
 1747+ return gmdate( 'Y-m-d\TH:i:s\Z', $uts );
 1748+ // This shouldn't ever be used, but is included for completeness
 1749+ case TS_EXIF:
 1750+ return gmdate( 'Y:m:d H:i:s', $uts );
 1751+ case TS_RFC2822:
 1752+ return gmdate( 'D, d M Y H:i:s', $uts ) . ' GMT';
 1753+ case TS_ORACLE:
 1754+ return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
 1755+ case TS_POSTGRES:
 1756+ return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
 1757+ default:
 1758+ throw new MWException( 'wfTimestamp() called with illegal output type.');
 1759+ }
 1760+}
 1761+
 1762+/**
 1763+ * Return a formatted timestamp, or null if input is null.
 1764+ * For dealing with nullable timestamp columns in the database.
 1765+ * @param int $outputtype
 1766+ * @param string $ts
 1767+ * @return string
 1768+ */
 1769+function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
 1770+ if( is_null( $ts ) ) {
 1771+ return null;
 1772+ } else {
 1773+ return wfTimestamp( $outputtype, $ts );
 1774+ }
 1775+}
 1776+
 1777+/**
 1778+ * Check if the operating system is Windows
 1779+ *
 1780+ * @return bool True if it's Windows, False otherwise.
 1781+ */
 1782+function wfIsWindows() {
 1783+ if (substr(php_uname(), 0, 7) == 'Windows') {
 1784+ return true;
 1785+ } else {
 1786+ return false;
 1787+ }
 1788+}
 1789+
 1790+/**
 1791+ * Swap two variables
 1792+ */
 1793+function swap( &$x, &$y ) {
 1794+ $z = $x;
 1795+ $x = $y;
 1796+ $y = $z;
 1797+}
 1798+
 1799+function wfGetCachedNotice( $name ) {
 1800+ global $wgOut, $wgRenderHashAppend, $parserMemc;
 1801+ $fname = 'wfGetCachedNotice';
 1802+ wfProfileIn( $fname );
 1803+
 1804+ $needParse = false;
 1805+
 1806+ if( $name === 'default' ) {
 1807+ // special case
 1808+ global $wgSiteNotice;
 1809+ $notice = $wgSiteNotice;
 1810+ if( empty( $notice ) ) {
 1811+ wfProfileOut( $fname );
 1812+ return false;
 1813+ }
 1814+ } else {
 1815+ $notice = wfMsgForContentNoTrans( $name );
 1816+ if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
 1817+ wfProfileOut( $fname );
 1818+ return( false );
 1819+ }
 1820+ }
 1821+
 1822+ // Use the extra hash appender to let eg SSL variants separately cache.
 1823+ $key = wfMemcKey( $name . $wgRenderHashAppend );
 1824+ $cachedNotice = $parserMemc->get( $key );
 1825+ if( is_array( $cachedNotice ) ) {
 1826+ if( md5( $notice ) == $cachedNotice['hash'] ) {
 1827+ $notice = $cachedNotice['html'];
 1828+ } else {
 1829+ $needParse = true;
 1830+ }
 1831+ } else {
 1832+ $needParse = true;
 1833+ }
 1834+
 1835+ if( $needParse ) {
 1836+ if( is_object( $wgOut ) ) {
 1837+ $parsed = $wgOut->parse( $notice );
 1838+ $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
 1839+ $notice = $parsed;
 1840+ } else {
 1841+ wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' );
 1842+ $notice = '';
 1843+ }
 1844+ }
 1845+
 1846+ wfProfileOut( $fname );
 1847+ return $notice;
 1848+}
 1849+
 1850+function wfGetNamespaceNotice() {
 1851+ global $wgTitle;
 1852+
 1853+ # Paranoia
 1854+ if ( !isset( $wgTitle ) || !is_object( $wgTitle ) )
 1855+ return "";
 1856+
 1857+ $fname = 'wfGetNamespaceNotice';
 1858+ wfProfileIn( $fname );
 1859+
 1860+ $key = "namespacenotice-" . $wgTitle->getNsText();
 1861+ $namespaceNotice = wfGetCachedNotice( $key );
 1862+ if ( $namespaceNotice && substr ( $namespaceNotice , 0 ,7 ) != "<p>&lt;" ) {
 1863+ $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . "</div>";
 1864+ } else {
 1865+ $namespaceNotice = "";
 1866+ }
 1867+
 1868+ wfProfileOut( $fname );
 1869+ return $namespaceNotice;
 1870+}
 1871+
 1872+function wfGetSiteNotice() {
 1873+ global $wgUser, $wgSiteNotice;
 1874+ $fname = 'wfGetSiteNotice';
 1875+ wfProfileIn( $fname );
 1876+ $siteNotice = '';
 1877+
 1878+ if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
 1879+ if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
 1880+ $siteNotice = wfGetCachedNotice( 'sitenotice' );
 1881+ } else {
 1882+ $anonNotice = wfGetCachedNotice( 'anonnotice' );
 1883+ if( !$anonNotice ) {
 1884+ $siteNotice = wfGetCachedNotice( 'sitenotice' );
 1885+ } else {
 1886+ $siteNotice = $anonNotice;
 1887+ }
 1888+ }
 1889+ if( !$siteNotice ) {
 1890+ $siteNotice = wfGetCachedNotice( 'default' );
 1891+ }
 1892+ }
 1893+
 1894+ wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
 1895+ wfProfileOut( $fname );
 1896+ return $siteNotice;
 1897+}
 1898+
 1899+/**
 1900+ * BC wrapper for MimeMagic::singleton()
 1901+ * @deprecated
 1902+ */
 1903+function &wfGetMimeMagic() {
 1904+ return MimeMagic::singleton();
 1905+}
 1906+
 1907+/**
 1908+ * Tries to get the system directory for temporary files.
 1909+ * The TMPDIR, TMP, and TEMP environment variables are checked in sequence,
 1910+ * and if none are set /tmp is returned as the generic Unix default.
 1911+ *
 1912+ * NOTE: When possible, use the tempfile() function to create temporary
 1913+ * files to avoid race conditions on file creation, etc.
 1914+ *
 1915+ * @return string
 1916+ */
 1917+function wfTempDir() {
 1918+ foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
 1919+ $tmp = getenv( $var );
 1920+ if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
 1921+ return $tmp;
 1922+ }
 1923+ }
 1924+ # Hope this is Unix of some kind!
 1925+ return '/tmp';
 1926+}
 1927+
 1928+/**
 1929+ * Make directory, and make all parent directories if they don't exist
 1930+ *
 1931+ * @param string $dir Full path to directory to create
 1932+ * @param int $mode Chmod value to use, default is $wgDirectoryMode
 1933+ * @return bool
 1934+ */
 1935+function wfMkdirParents( $dir, $mode = null ) {
 1936+ global $wgDirectoryMode;
 1937+
 1938+ if( strval( $dir ) === '' || file_exists( $dir ) )
 1939+ return true;
 1940+
 1941+ if ( is_null( $mode ) )
 1942+ $mode = $wgDirectoryMode;
 1943+
 1944+ return mkdir( $dir, $mode, true ); // PHP5 <3
 1945+}
 1946+
 1947+/**
 1948+ * Increment a statistics counter
 1949+ */
 1950+function wfIncrStats( $key ) {
 1951+ global $wgStatsMethod;
 1952+
 1953+ if( $wgStatsMethod == 'udp' ) {
 1954+ global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname;
 1955+ static $socket;
 1956+ if (!$socket) {
 1957+ $socket=socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
 1958+ $statline="stats/{$wgDBname} - 1 1 1 1 1 -total\n";
 1959+ socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
 1960+ }
 1961+ $statline="stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
 1962+ @socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
 1963+ } elseif( $wgStatsMethod == 'cache' ) {
 1964+ global $wgMemc;
 1965+ $key = wfMemcKey( 'stats', $key );
 1966+ if ( is_null( $wgMemc->incr( $key ) ) ) {
 1967+ $wgMemc->add( $key, 1 );
 1968+ }
 1969+ } else {
 1970+ // Disabled
 1971+ }
 1972+}
 1973+
 1974+/**
 1975+ * @param mixed $nr The number to format
 1976+ * @param int $acc The number of digits after the decimal point, default 2
 1977+ * @param bool $round Whether or not to round the value, default true
 1978+ * @return float
 1979+ */
 1980+function wfPercent( $nr, $acc = 2, $round = true ) {
 1981+ $ret = sprintf( "%.${acc}f", $nr );
 1982+ return $round ? round( $ret, $acc ) . '%' : "$ret%";
 1983+}
 1984+
 1985+/**
 1986+ * Encrypt a username/password.
 1987+ *
 1988+ * @param string $userid ID of the user
 1989+ * @param string $password Password of the user
 1990+ * @return string Hashed password
 1991+ * @deprecated Use User::crypt() or User::oldCrypt() instead
 1992+ */
 1993+function wfEncryptPassword( $userid, $password ) {
 1994+ wfDeprecated(__FUNCTION__);
 1995+ # Just wrap around User::oldCrypt()
 1996+ return User::oldCrypt($password, $userid);
 1997+}
 1998+
 1999+/**
 2000+ * Appends to second array if $value differs from that in $default
 2001+ */
 2002+function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
 2003+ if ( is_null( $changed ) ) {
 2004+ throw new MWException('GlobalFunctions::wfAppendToArrayIfNotDefault got null');
 2005+ }
 2006+ if ( $default[$key] !== $value ) {
 2007+ $changed[$key] = $value;
 2008+ }
 2009+}
 2010+
 2011+/**
 2012+ * Since wfMsg() and co suck, they don't return false if the message key they
 2013+ * looked up didn't exist but a XHTML string, this function checks for the
 2014+ * nonexistance of messages by looking at wfMsg() output
 2015+ *
 2016+ * @param $msg The message key looked up
 2017+ * @param $wfMsgOut The output of wfMsg*()
 2018+ * @return bool
 2019+ */
 2020+function wfEmptyMsg( $msg, $wfMsgOut ) {
 2021+ return $wfMsgOut === htmlspecialchars( "<$msg>" );
 2022+}
 2023+
 2024+/**
 2025+ * Find out whether or not a mixed variable exists in a string
 2026+ *
 2027+ * @param mixed needle
 2028+ * @param string haystack
 2029+ * @return bool
 2030+ */
 2031+function in_string( $needle, $str ) {
 2032+ return strpos( $str, $needle ) !== false;
 2033+}
 2034+
 2035+function wfSpecialList( $page, $details ) {
 2036+ global $wgContLang;
 2037+ $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : "";
 2038+ return $page . $details;
 2039+}
 2040+
 2041+/**
 2042+ * Returns a regular expression of url protocols
 2043+ *
 2044+ * @return string
 2045+ */
 2046+function wfUrlProtocols() {
 2047+ global $wgUrlProtocols;
 2048+
 2049+ // Support old-style $wgUrlProtocols strings, for backwards compatibility
 2050+ // with LocalSettings files from 1.5
 2051+ if ( is_array( $wgUrlProtocols ) ) {
 2052+ $protocols = array();
 2053+ foreach ($wgUrlProtocols as $protocol)
 2054+ $protocols[] = preg_quote( $protocol, '/' );
 2055+
 2056+ return implode( '|', $protocols );
 2057+ } else {
 2058+ return $wgUrlProtocols;
 2059+ }
 2060+}
 2061+
 2062+/**
 2063+ * Safety wrapper around ini_get() for boolean settings.
 2064+ * The values returned from ini_get() are pre-normalized for settings
 2065+ * set via php.ini or php_flag/php_admin_flag... but *not*
 2066+ * for those set via php_value/php_admin_value.
 2067+ *
 2068+ * It's fairly common for people to use php_value instead of php_flag,
 2069+ * which can leave you with an 'off' setting giving a false positive
 2070+ * for code that just takes the ini_get() return value as a boolean.
 2071+ *
 2072+ * To make things extra interesting, setting via php_value accepts
 2073+ * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
 2074+ * Unrecognized values go false... again opposite PHP's own coercion
 2075+ * from string to bool.
 2076+ *
 2077+ * Luckily, 'properly' set settings will always come back as '0' or '1',
 2078+ * so we only have to worry about them and the 'improper' settings.
 2079+ *
 2080+ * I frickin' hate PHP... :P
 2081+ *
 2082+ * @param string $setting
 2083+ * @return bool
 2084+ */
 2085+function wfIniGetBool( $setting ) {
 2086+ $val = ini_get( $setting );
 2087+ // 'on' and 'true' can't have whitespace around them, but '1' can.
 2088+ return strtolower( $val ) == 'on'
 2089+ || strtolower( $val ) == 'true'
 2090+ || strtolower( $val ) == 'yes'
 2091+ || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
 2092+}
 2093+
 2094+/**
 2095+ * Execute a shell command, with time and memory limits mirrored from the PHP
 2096+ * configuration if supported.
 2097+ * @param $cmd Command line, properly escaped for shell.
 2098+ * @param &$retval optional, will receive the program's exit code.
 2099+ * (non-zero is usually failure)
 2100+ * @return collected stdout as a string (trailing newlines stripped)
 2101+ */
 2102+function wfShellExec( $cmd, &$retval=null ) {
 2103+ global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
 2104+
 2105+ if( wfIniGetBool( 'safe_mode' ) ) {
 2106+ wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
 2107+ $retval = 1;
 2108+ return "Unable to run external programs in safe mode.";
 2109+ }
 2110+ wfInitShellLocale();
 2111+
 2112+ if ( php_uname( 's' ) == 'Linux' ) {
 2113+ $time = intval( $wgMaxShellTime );
 2114+ $mem = intval( $wgMaxShellMemory );
 2115+ $filesize = intval( $wgMaxShellFileSize );
 2116+
 2117+ if ( $time > 0 && $mem > 0 ) {
 2118+ $script = "$IP/bin/ulimit4.sh";
 2119+ if ( is_executable( $script ) ) {
 2120+ $cmd = escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
 2121+ }
 2122+ }
 2123+ } elseif ( php_uname( 's' ) == 'Windows NT' ) {
 2124+ # This is a hack to work around PHP's flawed invocation of cmd.exe
 2125+ # http://news.php.net/php.internals/21796
 2126+ $cmd = '"' . $cmd . '"';
 2127+ }
 2128+ wfDebug( "wfShellExec: $cmd\n" );
 2129+
 2130+ $retval = 1; // error by default?
 2131+ ob_start();
 2132+ passthru( $cmd, $retval );
 2133+ $output = ob_get_contents();
 2134+ ob_end_clean();
 2135+
 2136+ if ( $retval == 127 ) {
 2137+ wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
 2138+ }
 2139+ return $output;
 2140+}
 2141+
 2142+/**
 2143+ * Workaround for http://bugs.php.net/bug.php?id=45132
 2144+ * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
 2145+ */
 2146+function wfInitShellLocale() {
 2147+ static $done = false;
 2148+ if ( $done ) return;
 2149+ $done = true;
 2150+ global $wgShellLocale;
 2151+ if ( !wfIniGetBool( 'safe_mode' ) ) {
 2152+ putenv( "LC_CTYPE=$wgShellLocale" );
 2153+ setlocale( LC_CTYPE, $wgShellLocale );
 2154+ }
 2155+}
 2156+
 2157+/**
 2158+ * This function works like "use VERSION" in Perl, the program will die with a
 2159+ * backtrace if the current version of PHP is less than the version provided
 2160+ *
 2161+ * This is useful for extensions which due to their nature are not kept in sync
 2162+ * with releases, and might depend on other versions of PHP than the main code
 2163+ *
 2164+ * Note: PHP might die due to parsing errors in some cases before it ever
 2165+ * manages to call this function, such is life
 2166+ *
 2167+ * @see perldoc -f use
 2168+ *
 2169+ * @param mixed $version The version to check, can be a string, an integer, or
 2170+ * a float
 2171+ */
 2172+function wfUsePHP( $req_ver ) {
 2173+ $php_ver = PHP_VERSION;
 2174+
 2175+ if ( version_compare( $php_ver, (string)$req_ver, '<' ) )
 2176+ throw new MWException( "PHP $req_ver required--this is only $php_ver" );
 2177+}
 2178+
 2179+/**
 2180+ * This function works like "use VERSION" in Perl except it checks the version
 2181+ * of MediaWiki, the program will die with a backtrace if the current version
 2182+ * of MediaWiki is less than the version provided.
 2183+ *
 2184+ * This is useful for extensions which due to their nature are not kept in sync
 2185+ * with releases
 2186+ *
 2187+ * @see perldoc -f use
 2188+ *
 2189+ * @param mixed $version The version to check, can be a string, an integer, or
 2190+ * a float
 2191+ */
 2192+function wfUseMW( $req_ver ) {
 2193+ global $wgVersion;
 2194+
 2195+ if ( version_compare( $wgVersion, (string)$req_ver, '<' ) )
 2196+ throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
 2197+}
 2198+
 2199+/**
 2200+ * @deprecated use StringUtils::escapeRegexReplacement
 2201+ */
 2202+function wfRegexReplacement( $string ) {
 2203+ return StringUtils::escapeRegexReplacement( $string );
 2204+}
 2205+
 2206+/**
 2207+ * Return the final portion of a pathname.
 2208+ * Reimplemented because PHP5's basename() is buggy with multibyte text.
 2209+ * http://bugs.php.net/bug.php?id=33898
 2210+ *
 2211+ * PHP's basename() only considers '\' a pathchar on Windows and Netware.
 2212+ * We'll consider it so always, as we don't want \s in our Unix paths either.
 2213+ *
 2214+ * @param string $path
 2215+ * @param string $suffix to remove if present
 2216+ * @return string
 2217+ */
 2218+function wfBaseName( $path, $suffix='' ) {
 2219+ $encSuffix = ($suffix == '')
 2220+ ? ''
 2221+ : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
 2222+ $matches = array();
 2223+ if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
 2224+ return $matches[1];
 2225+ } else {
 2226+ return '';
 2227+ }
 2228+}
 2229+
 2230+/**
 2231+ * Generate a relative path name to the given file.
 2232+ * May explode on non-matching case-insensitive paths,
 2233+ * funky symlinks, etc.
 2234+ *
 2235+ * @param string $path Absolute destination path including target filename
 2236+ * @param string $from Absolute source path, directory only
 2237+ * @return string
 2238+ */
 2239+function wfRelativePath( $path, $from ) {
 2240+ // Normalize mixed input on Windows...
 2241+ $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
 2242+ $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
 2243+
 2244+ // Trim trailing slashes -- fix for drive root
 2245+ $path = rtrim( $path, DIRECTORY_SEPARATOR );
 2246+ $from = rtrim( $from, DIRECTORY_SEPARATOR );
 2247+
 2248+ $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
 2249+ $against = explode( DIRECTORY_SEPARATOR, $from );
 2250+
 2251+ if( $pieces[0] !== $against[0] ) {
 2252+ // Non-matching Windows drive letters?
 2253+ // Return a full path.
 2254+ return $path;
 2255+ }
 2256+
 2257+ // Trim off common prefix
 2258+ while( count( $pieces ) && count( $against )
 2259+ && $pieces[0] == $against[0] ) {
 2260+ array_shift( $pieces );
 2261+ array_shift( $against );
 2262+ }
 2263+
 2264+ // relative dots to bump us to the parent
 2265+ while( count( $against ) ) {
 2266+ array_unshift( $pieces, '..' );
 2267+ array_shift( $against );
 2268+ }
 2269+
 2270+ array_push( $pieces, wfBaseName( $path ) );
 2271+
 2272+ return implode( DIRECTORY_SEPARATOR, $pieces );
 2273+}
 2274+
 2275+/**
 2276+ * Backwards array plus for people who haven't bothered to read the PHP manual
 2277+ * XXX: will not darn your socks for you.
 2278+ *
 2279+ * @param array $array1, [$array2, [...]]
 2280+ * @return array
 2281+ */
 2282+function wfArrayMerge( $array1/* ... */ ) {
 2283+ $args = func_get_args();
 2284+ $args = array_reverse( $args, true );
 2285+ $out = array();
 2286+ foreach ( $args as $arg ) {
 2287+ $out += $arg;
 2288+ }
 2289+ return $out;
 2290+}
 2291+
 2292+/**
 2293+ * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
 2294+ * e.g.
 2295+ * wfMergeErrorArrays(
 2296+ * array( array( 'x' ) ),
 2297+ * array( array( 'x', '2' ) ),
 2298+ * array( array( 'x' ) ),
 2299+ * array( array( 'y') )
 2300+ * );
 2301+ * returns:
 2302+ * array(
 2303+ * array( 'x', '2' ),
 2304+ * array( 'x' ),
 2305+ * array( 'y' )
 2306+ * )
 2307+ */
 2308+function wfMergeErrorArrays(/*...*/) {
 2309+ $args = func_get_args();
 2310+ $out = array();
 2311+ foreach ( $args as $errors ) {
 2312+ foreach ( $errors as $params ) {
 2313+ $spec = implode( "\t", $params );
 2314+ $out[$spec] = $params;
 2315+ }
 2316+ }
 2317+ return array_values( $out );
 2318+}
 2319+
 2320+/**
 2321+ * Make a URL index, appropriate for the el_index field of externallinks.
 2322+ */
 2323+function wfMakeUrlIndex( $url ) {
 2324+ global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
 2325+ wfSuppressWarnings();
 2326+ $bits = parse_url( $url );
 2327+ wfRestoreWarnings();
 2328+ if ( !$bits ) {
 2329+ return false;
 2330+ }
 2331+ // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
 2332+ $delimiter = '';
 2333+ if ( in_array( $bits['scheme'] . '://' , $wgUrlProtocols ) ) {
 2334+ $delimiter = '://';
 2335+ } elseif ( in_array( $bits['scheme'] .':' , $wgUrlProtocols ) ) {
 2336+ $delimiter = ':';
 2337+ // parse_url detects for news: and mailto: the host part of an url as path
 2338+ // We have to correct this wrong detection
 2339+ if ( isset ( $bits['path'] ) ) {
 2340+ $bits['host'] = $bits['path'];
 2341+ $bits['path'] = '';
 2342+ }
 2343+ } else {
 2344+ return false;
 2345+ }
 2346+
 2347+ // Reverse the labels in the hostname, convert to lower case
 2348+ // For emails reverse domainpart only
 2349+ if ( $bits['scheme'] == 'mailto' ) {
 2350+ $mailparts = explode( '@', $bits['host'], 2 );
 2351+ if ( count($mailparts) === 2 ) {
 2352+ $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
 2353+ } else {
 2354+ // No domain specified, don't mangle it
 2355+ $domainpart = '';
 2356+ }
 2357+ $reversedHost = $domainpart . '@' . $mailparts[0];
 2358+ } else {
 2359+ $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
 2360+ }
 2361+ // Add an extra dot to the end
 2362+ // Why? Is it in wrong place in mailto links?
 2363+ if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
 2364+ $reversedHost .= '.';
 2365+ }
 2366+ // Reconstruct the pseudo-URL
 2367+ $prot = $bits['scheme'];
 2368+ $index = "$prot$delimiter$reversedHost";
 2369+ // Leave out user and password. Add the port, path, query and fragment
 2370+ if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port'];
 2371+ if ( isset( $bits['path'] ) ) {
 2372+ $index .= $bits['path'];
 2373+ } else {
 2374+ $index .= '/';
 2375+ }
 2376+ if ( isset( $bits['query'] ) ) $index .= '?' . $bits['query'];
 2377+ if ( isset( $bits['fragment'] ) ) $index .= '#' . $bits['fragment'];
 2378+ return $index;
 2379+}
 2380+
 2381+/**
 2382+ * Do any deferred updates and clear the list
 2383+ * TODO: This could be in Wiki.php if that class made any sense at all
 2384+ */
 2385+function wfDoUpdates()
 2386+{
 2387+ global $wgPostCommitUpdateList, $wgDeferredUpdateList;
 2388+ foreach ( $wgDeferredUpdateList as $update ) {
 2389+ $update->doUpdate();
 2390+ }
 2391+ foreach ( $wgPostCommitUpdateList as $update ) {
 2392+ $update->doUpdate();
 2393+ }
 2394+ $wgDeferredUpdateList = array();
 2395+ $wgPostCommitUpdateList = array();
 2396+}
 2397+
 2398+/**
 2399+ * @deprecated use StringUtils::explodeMarkup
 2400+ */
 2401+function wfExplodeMarkup( $separator, $text ) {
 2402+ return StringUtils::explodeMarkup( $separator, $text );
 2403+}
 2404+
 2405+/**
 2406+ * Convert an arbitrarily-long digit string from one numeric base
 2407+ * to another, optionally zero-padding to a minimum column width.
 2408+ *
 2409+ * Supports base 2 through 36; digit values 10-36 are represented
 2410+ * as lowercase letters a-z. Input is case-insensitive.
 2411+ *
 2412+ * @param $input string of digits
 2413+ * @param $sourceBase int 2-36
 2414+ * @param $destBase int 2-36
 2415+ * @param $pad int 1 or greater
 2416+ * @param $lowercase bool
 2417+ * @return string or false on invalid input
 2418+ */
 2419+function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true ) {
 2420+ $input = strval( $input );
 2421+ if( $sourceBase < 2 ||
 2422+ $sourceBase > 36 ||
 2423+ $destBase < 2 ||
 2424+ $destBase > 36 ||
 2425+ $pad < 1 ||
 2426+ $sourceBase != intval( $sourceBase ) ||
 2427+ $destBase != intval( $destBase ) ||
 2428+ $pad != intval( $pad ) ||
 2429+ !is_string( $input ) ||
 2430+ $input == '' ) {
 2431+ return false;
 2432+ }
 2433+ $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 2434+ $inDigits = array();
 2435+ $outChars = '';
 2436+
 2437+ // Decode and validate input string
 2438+ $input = strtolower( $input );
 2439+ for( $i = 0; $i < strlen( $input ); $i++ ) {
 2440+ $n = strpos( $digitChars, $input{$i} );
 2441+ if( $n === false || $n > $sourceBase ) {
 2442+ return false;
 2443+ }
 2444+ $inDigits[] = $n;
 2445+ }
 2446+
 2447+ // Iterate over the input, modulo-ing out an output digit
 2448+ // at a time until input is gone.
 2449+ while( count( $inDigits ) ) {
 2450+ $work = 0;
 2451+ $workDigits = array();
 2452+
 2453+ // Long division...
 2454+ foreach( $inDigits as $digit ) {
 2455+ $work *= $sourceBase;
 2456+ $work += $digit;
 2457+
 2458+ if( $work < $destBase ) {
 2459+ // Gonna need to pull another digit.
 2460+ if( count( $workDigits ) ) {
 2461+ // Avoid zero-padding; this lets us find
 2462+ // the end of the input very easily when
 2463+ // length drops to zero.
 2464+ $workDigits[] = 0;
 2465+ }
 2466+ } else {
 2467+ // Finally! Actual division!
 2468+ $workDigits[] = intval( $work / $destBase );
 2469+
 2470+ // Isn't it annoying that most programming languages
 2471+ // don't have a single divide-and-remainder operator,
 2472+ // even though the CPU implements it that way?
 2473+ $work = $work % $destBase;
 2474+ }
 2475+ }
 2476+
 2477+ // All that division leaves us with a remainder,
 2478+ // which is conveniently our next output digit.
 2479+ $outChars .= $digitChars[$work];
 2480+
 2481+ // And we continue!
 2482+ $inDigits = $workDigits;
 2483+ }
 2484+
 2485+ while( strlen( $outChars ) < $pad ) {
 2486+ $outChars .= '0';
 2487+ }
 2488+
 2489+ return strrev( $outChars );
 2490+}
 2491+
 2492+/**
 2493+ * Create an object with a given name and an array of construct parameters
 2494+ * @param string $name
 2495+ * @param array $p parameters
 2496+ */
 2497+function wfCreateObject( $name, $p ){
 2498+ $p = array_values( $p );
 2499+ switch ( count( $p ) ) {
 2500+ case 0:
 2501+ return new $name;
 2502+ case 1:
 2503+ return new $name( $p[0] );
 2504+ case 2:
 2505+ return new $name( $p[0], $p[1] );
 2506+ case 3:
 2507+ return new $name( $p[0], $p[1], $p[2] );
 2508+ case 4:
 2509+ return new $name( $p[0], $p[1], $p[2], $p[3] );
 2510+ case 5:
 2511+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4] );
 2512+ case 6:
 2513+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4], $p[5] );
 2514+ default:
 2515+ throw new MWException( "Too many arguments to construtor in wfCreateObject" );
 2516+ }
 2517+}
 2518+
 2519+/**
 2520+ * Alias for modularized function
 2521+ * @deprecated Use Http::get() instead
 2522+ */
 2523+function wfGetHTTP( $url, $timeout = 'default' ) {
 2524+ wfDeprecated(__FUNCTION__);
 2525+ return Http::get( $url, $timeout );
 2526+}
 2527+
 2528+/**
 2529+ * Alias for modularized function
 2530+ * @deprecated Use Http::isLocalURL() instead
 2531+ */
 2532+function wfIsLocalURL( $url ) {
 2533+ wfDeprecated(__FUNCTION__);
 2534+ return Http::isLocalURL( $url );
 2535+}
 2536+
 2537+function wfHttpOnlySafe() {
 2538+ global $wgHttpOnlyBlacklist;
 2539+ if( !version_compare("5.2", PHP_VERSION, "<") )
 2540+ return false;
 2541+
 2542+ if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
 2543+ foreach( $wgHttpOnlyBlacklist as $regex ) {
 2544+ if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
 2545+ return false;
 2546+ }
 2547+ }
 2548+ }
 2549+
 2550+ return true;
 2551+}
 2552+
 2553+/**
 2554+ * Initialise php session
 2555+ */
 2556+function wfSetupSession() {
 2557+ global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly;
 2558+ if( $wgSessionsInMemcached ) {
 2559+ require_once( 'MemcachedSessions.php' );
 2560+ } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
 2561+ # If it's left on 'user' or another setting from another
 2562+ # application, it will end up failing. Try to recover.
 2563+ ini_set ( 'session.save_handler', 'files' );
 2564+ }
 2565+ $httpOnlySafe = wfHttpOnlySafe();
 2566+ wfDebugLog( 'cookie',
 2567+ 'session_set_cookie_params: "' . implode( '", "',
 2568+ array(
 2569+ 0,
 2570+ $wgCookiePath,
 2571+ $wgCookieDomain,
 2572+ $wgCookieSecure,
 2573+ $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' );
 2574+ if( $httpOnlySafe && $wgCookieHttpOnly ) {
 2575+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly );
 2576+ } else {
 2577+ // PHP 5.1 throws warnings if you pass the HttpOnly parameter for 5.2.
 2578+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
 2579+ }
 2580+ session_cache_limiter( 'private, must-revalidate' );
 2581+ wfSuppressWarnings();
 2582+ session_start();
 2583+ wfRestoreWarnings();
 2584+}
 2585+
 2586+/**
 2587+ * Get an object from the precompiled serialized directory
 2588+ *
 2589+ * @return mixed The variable on success, false on failure
 2590+ */
 2591+function wfGetPrecompiledData( $name ) {
 2592+ global $IP;
 2593+
 2594+ $file = "$IP/serialized/$name";
 2595+ if ( file_exists( $file ) ) {
 2596+ $blob = file_get_contents( $file );
 2597+ if ( $blob ) {
 2598+ return unserialize( $blob );
 2599+ }
 2600+ }
 2601+ return false;
 2602+}
 2603+
 2604+function wfGetCaller( $level = 2 ) {
 2605+ $backtrace = wfDebugBacktrace();
 2606+ if ( isset( $backtrace[$level] ) ) {
 2607+ return wfFormatStackFrame($backtrace[$level]);
 2608+ } else {
 2609+ $caller = 'unknown';
 2610+ }
 2611+ return $caller;
 2612+}
 2613+
 2614+/** Return a string consisting all callers in stack, somewhat useful sometimes for profiling specific points */
 2615+function wfGetAllCallers() {
 2616+ return implode('/', array_map('wfFormatStackFrame',array_reverse(wfDebugBacktrace())));
 2617+}
 2618+
 2619+/** Return a string representation of frame */
 2620+function wfFormatStackFrame($frame) {
 2621+ return isset( $frame["class"] )?
 2622+ $frame["class"]."::".$frame["function"]:
 2623+ $frame["function"];
 2624+}
 2625+
 2626+/**
 2627+ * Get a cache key
 2628+ */
 2629+function wfMemcKey( /*... */ ) {
 2630+ $args = func_get_args();
 2631+ $key = wfWikiID() . ':' . implode( ':', $args );
 2632+ return $key;
 2633+}
 2634+
 2635+/**
 2636+ * Get a cache key for a foreign DB
 2637+ */
 2638+function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
 2639+ $args = array_slice( func_get_args(), 2 );
 2640+ if ( $prefix ) {
 2641+ $key = "$db-$prefix:" . implode( ':', $args );
 2642+ } else {
 2643+ $key = $db . ':' . implode( ':', $args );
 2644+ }
 2645+ return $key;
 2646+}
 2647+
 2648+/**
 2649+ * Get an ASCII string identifying this wiki
 2650+ * This is used as a prefix in memcached keys
 2651+ */
 2652+function wfWikiID( $db = null ) {
 2653+ if( $db instanceof Database ) {
 2654+ return $db->getWikiID();
 2655+ } else {
 2656+ global $wgDBprefix, $wgDBname;
 2657+ if ( $wgDBprefix ) {
 2658+ return "$wgDBname-$wgDBprefix";
 2659+ } else {
 2660+ return $wgDBname;
 2661+ }
 2662+ }
 2663+}
 2664+
 2665+/**
 2666+ * Split a wiki ID into DB name and table prefix
 2667+ */
 2668+function wfSplitWikiID( $wiki ) {
 2669+ $bits = explode( '-', $wiki, 2 );
 2670+ if ( count( $bits ) < 2 ) {
 2671+ $bits[] = '';
 2672+ }
 2673+ return $bits;
 2674+}
 2675+
 2676+/*
 2677+ * Get a Database object.
 2678+ * @param integer $db Index of the connection to get. May be DB_MASTER for the
 2679+ * master (for write queries), DB_SLAVE for potentially lagged
 2680+ * read queries, or an integer >= 0 for a particular server.
 2681+ *
 2682+ * @param mixed $groups Query groups. An array of group names that this query
 2683+ * belongs to. May contain a single string if the query is only
 2684+ * in one group.
 2685+ *
 2686+ * @param string $wiki The wiki ID, or false for the current wiki
 2687+ *
 2688+ * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
 2689+ * will always return the same object, unless the underlying connection or load
 2690+ * balancer is manually destroyed.
 2691+ */
 2692+function &wfGetDB( $db, $groups = array(), $wiki = false ) {
 2693+ return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
 2694+}
 2695+
 2696+/**
 2697+ * Get a load balancer object.
 2698+ *
 2699+ * @param array $groups List of query groups
 2700+ * @param string $wiki Wiki ID, or false for the current wiki
 2701+ * @return LoadBalancer
 2702+ */
 2703+function wfGetLB( $wiki = false ) {
 2704+ return wfGetLBFactory()->getMainLB( $wiki );
 2705+}
 2706+
 2707+/**
 2708+ * Get the load balancer factory object
 2709+ */
 2710+function &wfGetLBFactory() {
 2711+ return LBFactory::singleton();
 2712+}
 2713+
 2714+/**
 2715+ * Find a file.
 2716+ * Shortcut for RepoGroup::singleton()->findFile()
 2717+ * @param mixed $title Title object or string. May be interwiki.
 2718+ * @param mixed $time Requested time for an archived image, or false for the
 2719+ * current version. An image object will be returned which
 2720+ * was created at the specified time.
 2721+ * @param mixed $flags FileRepo::FIND_ flags
 2722+ * @param boolean $bypass Bypass the file cache even if it could be used
 2723+ * @return File, or false if the file does not exist
 2724+ */
 2725+function wfFindFile( $title, $time = false, $flags = 0, $bypass = false ) {
 2726+ if( !$time && !$flags && !$bypass ) {
 2727+ return FileCache::singleton()->findFile( $title );
 2728+ } else {
 2729+ return RepoGroup::singleton()->findFile( $title, $time, $flags );
 2730+ }
 2731+}
 2732+
 2733+/**
 2734+ * Get an object referring to a locally registered file.
 2735+ * Returns a valid placeholder object if the file does not exist.
 2736+ */
 2737+function wfLocalFile( $title ) {
 2738+ return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
 2739+}
 2740+
 2741+/**
 2742+ * Should low-performance queries be disabled?
 2743+ *
 2744+ * @return bool
 2745+ */
 2746+function wfQueriesMustScale() {
 2747+ global $wgMiserMode;
 2748+ return $wgMiserMode
 2749+ || ( SiteStats::pages() > 100000
 2750+ && SiteStats::edits() > 1000000
 2751+ && SiteStats::users() > 10000 );
 2752+}
 2753+
 2754+/**
 2755+ * Get the path to a specified script file, respecting file
 2756+ * extensions; this is a wrapper around $wgScriptExtension etc.
 2757+ *
 2758+ * @param string $script Script filename, sans extension
 2759+ * @return string
 2760+ */
 2761+function wfScript( $script = 'index' ) {
 2762+ global $wgScriptPath, $wgScriptExtension;
 2763+ return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
 2764+}
 2765+
 2766+/**
 2767+ * Convenience function converts boolean values into "true"
 2768+ * or "false" (string) values
 2769+ *
 2770+ * @param bool $value
 2771+ * @return string
 2772+ */
 2773+function wfBoolToStr( $value ) {
 2774+ return $value ? 'true' : 'false';
 2775+}
 2776+
 2777+/**
 2778+ * Load an extension messages file
 2779+ *
 2780+ * @param string $extensionName Name of extension to load messages from\for.
 2781+ * @param string $langcode Language to load messages for, or false for default
 2782+ * behvaiour (en, content language and user language).
 2783+ * @since r24808 (v1.11) Using this method of loading extension messages will not work
 2784+ * on MediaWiki prior to that
 2785+ */
 2786+function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
 2787+ global $wgExtensionMessagesFiles, $wgMessageCache, $wgLang, $wgContLang;
 2788+
 2789+ #For recording whether extension message files have been loaded in a given language.
 2790+ static $loaded = array();
 2791+
 2792+ if( !array_key_exists( $extensionName, $loaded ) ) {
 2793+ $loaded[$extensionName] = array();
 2794+ }
 2795+
 2796+ if ( !isset($wgExtensionMessagesFiles[$extensionName]) ) {
 2797+ throw new MWException( "Messages file for extensions $extensionName is not defined" );
 2798+ }
 2799+
 2800+ if( !$langcode && !array_key_exists( '*', $loaded[$extensionName] ) ) {
 2801+ # Just do en, content language and user language.
 2802+ $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], false );
 2803+ # Mark that they have been loaded.
 2804+ $loaded[$extensionName]['en'] = true;
 2805+ $loaded[$extensionName][$wgLang->getCode()] = true;
 2806+ $loaded[$extensionName][$wgContLang->getCode()] = true;
 2807+ # Mark that this part has been done to avoid weird if statements.
 2808+ $loaded[$extensionName]['*'] = true;
 2809+ } elseif( is_string( $langcode ) && !array_key_exists( $langcode, $loaded[$extensionName] ) ) {
 2810+ # Load messages for specified language.
 2811+ $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], $langcode );
 2812+ # Mark that they have been loaded.
 2813+ $loaded[$extensionName][$langcode] = true;
 2814+ }
 2815+}
 2816+
 2817+/**
 2818+ * Get a platform-independent path to the null file, e.g.
 2819+ * /dev/null
 2820+ *
 2821+ * @return string
 2822+ */
 2823+function wfGetNull() {
 2824+ return wfIsWindows()
 2825+ ? 'NUL'
 2826+ : '/dev/null';
 2827+}
 2828+
 2829+/**
 2830+ * Displays a maxlag error
 2831+ *
 2832+ * @param string $host Server that lags the most
 2833+ * @param int $lag Maxlag (actual)
 2834+ * @param int $maxLag Maxlag (requested)
 2835+ */
 2836+function wfMaxlagError( $host, $lag, $maxLag ) {
 2837+ global $wgShowHostnames;
 2838+ header( 'HTTP/1.1 503 Service Unavailable' );
 2839+ header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
 2840+ header( 'X-Database-Lag: ' . intval( $lag ) );
 2841+ header( 'Content-Type: text/plain' );
 2842+ if( $wgShowHostnames ) {
 2843+ echo "Waiting for $host: $lag seconds lagged\n";
 2844+ } else {
 2845+ echo "Waiting for a database server: $lag seconds lagged\n";
 2846+ }
 2847+}
 2848+
 2849+/**
 2850+ * Throws an E_USER_NOTICE saying that $function is deprecated
 2851+ * @param string $function
 2852+ * @return null
 2853+ */
 2854+function wfDeprecated( $function ) {
 2855+ global $wgDebugLogFile;
 2856+ if ( !$wgDebugLogFile ) {
 2857+ return;
 2858+ }
 2859+ $callers = wfDebugBacktrace();
 2860+ if( isset( $callers[2] ) ){
 2861+ $callerfunc = $callers[2];
 2862+ $callerfile = $callers[1];
 2863+ if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ){
 2864+ $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
 2865+ } else {
 2866+ $file = '(internal function)';
 2867+ }
 2868+ $func = '';
 2869+ if( isset( $callerfunc['class'] ) )
 2870+ $func .= $callerfunc['class'] . '::';
 2871+ $func .= @$callerfunc['function'];
 2872+ $msg = "Use of $function is deprecated. Called from $func in $file";
 2873+ } else {
 2874+ $msg = "Use of $function is deprecated.";
 2875+ }
 2876+ wfDebug( "$msg\n" );
 2877+}
 2878+
 2879+/**
 2880+ * Sleep until the worst slave's replication lag is less than or equal to
 2881+ * $maxLag, in seconds. Use this when updating very large numbers of rows, as
 2882+ * in maintenance scripts, to avoid causing too much lag. Of course, this is
 2883+ * a no-op if there are no slaves.
 2884+ *
 2885+ * Every time the function has to wait for a slave, it will print a message to
 2886+ * that effect (and then sleep for a little while), so it's probably not best
 2887+ * to use this outside maintenance scripts in its present form.
 2888+ *
 2889+ * @param int $maxLag
 2890+ * @return null
 2891+ */
 2892+function wfWaitForSlaves( $maxLag ) {
 2893+ if( $maxLag ) {
 2894+ $lb = wfGetLB();
 2895+ list( $host, $lag ) = $lb->getMaxLag();
 2896+ while( $lag > $maxLag ) {
 2897+ $name = @gethostbyaddr( $host );
 2898+ if( $name !== false ) {
 2899+ $host = $name;
 2900+ }
 2901+ print "Waiting for $host (lagged $lag seconds)...\n";
 2902+ sleep($maxLag);
 2903+ list( $host, $lag ) = $lb->getMaxLag();
 2904+ }
 2905+ }
 2906+}
 2907+
 2908+/**
 2909+ * Output some plain text in command-line mode or in the installer (updaters.inc).
 2910+ * Do not use it in any other context, its behaviour is subject to change.
 2911+ */
 2912+function wfOut( $s ) {
 2913+ static $lineStarted = false;
 2914+ global $wgCommandLineMode;
 2915+ if ( $wgCommandLineMode && !defined( 'MEDIAWIKI_INSTALL' ) ) {
 2916+ echo $s;
 2917+ } else {
 2918+ echo htmlspecialchars( $s );
 2919+ }
 2920+ flush();
 2921+}
 2922+
 2923+/** Generate a random 32-character hexadecimal token.
 2924+ * @param mixed $salt Some sort of salt, if necessary, to add to random characters before hashing.
 2925+ */
 2926+function wfGenerateToken( $salt = '' ) {
 2927+ $salt = serialize($salt);
 2928+
 2929+ return md5( mt_rand( 0, 0x7fffffff ) . $salt );
 2930+}
 2931+
 2932+/**
 2933+ * Replace all invalid characters with -
 2934+ * @param mixed $title Filename to process
 2935+ */
 2936+function wfStripIllegalFilenameChars( $name ) {
 2937+ $name = wfBaseName( $name );
 2938+ $name = preg_replace ( "/[^".Title::legalChars()."]/", '-', $name );
 2939+ return $name;
 2940+}
Property changes on: trunk/extensions/NSFileRepo/REL1_14_0/phase3/includes/GlobalFunctions.php
___________________________________________________________________
Added: svn:eol-style
12941 + native
Index: trunk/extensions/NSFileRepo/REL1_14_0/phase3/includes/GlobalFunctions.patch
@@ -0,0 +1,12 @@
 2+Index: GlobalFunctions.php
 3+===================================================================
 4+--- GlobalFunctions.php (revision 53111)
 5+@@ -2934,6 +2934,6 @@
 6+ */
 7+ function wfStripIllegalFilenameChars( $name ) {
 8+ $name = wfBaseName( $name );
 9+- $name = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $name );
 10++ $name = preg_replace ( "/[^".Title::legalChars()."]/", '-', $name );
 11+ return $name;
 12+ }
Index: trunk/extensions/NSFileRepo/REL1_15_0/phase3/img_auth.i18n.php
@@ -0,0 +1,32 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for img_auth script (see see http://www.mediawiki.org/wiki/Manual:Image_Authorization).
 5+*/
 6+
 7+$messages = array();
 8+
 9+/** English
 10+ * @author Jack D. Pond
 11+ */
 12+$messages['en'] = array(
 13+ 'image_auth-desc' => 'Image authorisation script',
 14+ 'image_auth-nopathinfo' => "Missing PATH_INFO. Your server is not set up to pass this information -
 15+may be CGI-based and can't support img_auth. See `Image Authorization` on MediaWiki.",
 16+ 'image_auth-notindir' => "Requested path not in upload directory.",
 17+ 'image_auth-badtitle' => "Unable to construct a valid Title from `$1`.",
 18+ 'image_auth-nologinnWL' => "Not logged in and `$1` not in whitelist.",
 19+ 'image_auth-nofile' => "`$1` does not exist.",
 20+ 'image_auth-isdir' => "`$1` is a directory.",
 21+ 'image_auth-streaming' => "Streaming `$1`.",
 22+ 'image_auth-public' => "The function of img_auth.php is to output files from a private wiki. This wiki
 23+is configured as a public wiki. For optimal security, img_auth.php is disabled for this case.",
 24+ 'image_auth-noread' => "User does not have access to read `$1`."
 25+);
 26+
 27+/** Message documentation (Message documentation)
 28+ * @author Jack D. Pond
 29+ */
 30+$messages['qqq'] = array(
 31+ 'image_auth-desc' => 'Image authorisation script'
 32+);
 33+
Property changes on: trunk/extensions/NSFileRepo/REL1_15_0/phase3/img_auth.i18n.php
___________________________________________________________________
Added: svn:eol-style
134 + native
Index: trunk/extensions/NSFileRepo/REL1_15_0/phase3/img_auth.php
@@ -0,0 +1,136 @@
 2+<?php
 3+
 4+/**
 5+ * Image authorisation script
 6+ *
 7+ * To use this, see http://www.mediawiki.org/wiki/Manual:Image_Authorization
 8+ *
 9+ * - Set $wgUploadDirectory to a non-public directory (not web accessible)
 10+ * - Set $wgUploadPath to point to this file
 11+ *
 12+ * Your server needs to support PATH_INFO; CGI-based configurations usually don't.
 13+ *
 14+ * @file
 15+ */
 16+
 17+
 18+/**
 19+ For security reasons, you usually don't want your user to know access was denied, just that it was.
 20+ If you want to change this, you can set $wgImgAuthDetails to 'true' in localsettings.php and it will give the user the reason
 21+ why access was denied.
 22+**/
 23+
 24+global $wgImgAuthDetails;
 25+$wgImgAuthDetails = false;
 26+
 27+define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
 28+require_once( dirname( __FILE__ ) . '/includes/WebStart.php' );
 29+wfProfileIn( 'img_auth.php' );
 30+require_once( dirname( __FILE__ ) . '/includes/StreamFile.php' );
 31+
 32+global $wgMessageCache, $messages;
 33+require_once( dirname( __FILE__ ) . '/img_auth.i18n.php' );
 34+foreach( $messages as $lang => $LangMsg )
 35+ $wgMessageCache->addMessages( $LangMsg, $lang );
 36+
 37+$perms = User::getGroupPermissions( array( '*' ) );
 38+
 39+// See if this is a public Wiki (no protections)
 40+if ( in_array( 'read', $perms, true ) )
 41+ wfPublicError(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-public'));
 42+
 43+// Extract path and image information
 44+if( !isset( $_SERVER['PATH_INFO'] ) )
 45+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nopathinfo'));
 46+
 47+$path = $_SERVER['PATH_INFO'];
 48+$filename = realpath( $wgUploadDirectory . $_SERVER['PATH_INFO'] );
 49+$realUpload = realpath( $wgUploadDirectory );
 50+
 51+// Basic directory traversal check
 52+if( substr( $filename, 0, strlen( $realUpload ) ) != $realUpload )
 53+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-notindir'));
 54+
 55+// Extract the file name and chop off the size specifier
 56+// (e.g. 120px-Foo.png => Foo.png)
 57+$name = wfBaseName( $path );
 58+if( preg_match( '!\d+px-(.*)!i', $name, $m ) )
 59+ $name = $m[1];
 60+
 61+// Check to see if the file exists
 62+if( !file_exists( $filename ) )
 63+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nofile',$filename));
 64+
 65+// Check to see if tried to access a directory
 66+if( is_dir( $filename ) )
 67+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-isdir',$filename));
 68+
 69+
 70+$title = Title::makeTitleSafe( NS_FILE, $name );
 71+
 72+// See if could create the title object
 73+if( !$title instanceof Title )
 74+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-badtitle',$name));
 75+
 76+// Run hook
 77+if (!wfRunHooks( 'ImgAuthBeforeStream', array( &$title, &$path, &$name, &$result ) ) )
 78+ wfForbidden($result[0],$result[1]);
 79+
 80+// Check the whitelist if needed, deprecated since usercan added
 81+// $pTitle = $title->getPrefixedText();
 82+// if( !$wgUser->getId() && ( !is_array( $wgWhitelistRead ) || !in_array( $pTitle, $wgWhitelistRead ) ) )
 83+// wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-nologinnWL',$pTitle));
 84+
 85+
 86+// Check user authorization for this title
 87+if( !$title->userCanRead() )
 88+ wfForbidden(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-noread',$name));
 89+
 90+
 91+// Stream the requested file
 92+wfDebugLog( 'img_auth', "Streaming `{$filename}`" );
 93+wfStreamFile( $filename, array( 'Cache-Control: private', 'Vary: Cookie' ) );
 94+wfLogProfilingData();
 95+
 96+/**
 97+ * Issue a standard HTTP 403 Forbidden header ($msg1) and an
 98+ * error message ($msg2), then end the script
 99+ */
 100+function wfForbidden($msg1,$msg2) {
 101+ global $wgImgAuthDetails;
 102+ $detailMsg = $wgImgAuthDetails ? $msg2 : wfMsgHTML('badaccess-group0');
 103+ wfDebugLog( 'img_auth', "wfForbidden Msg: ".$msg2 );
 104+ header( 'HTTP/1.0 403 Forbidden' );
 105+ header( 'Vary: Cookie' );
 106+ header( 'Content-Type: text/html; charset=utf-8' );
 107+ echo <<<ENDS
 108+<html>
 109+<body>
 110+<h1>$msg1</h1>
 111+<p>$detailMsg</p>
 112+</body>
 113+</html>
 114+ENDS;
 115+ wfLogProfilingData();
 116+ exit();
 117+}
 118+
 119+/**
 120+ * Show a 403 error for use when the wiki is public
 121+ */
 122+function wfPublicError($msg1,$msg2) {
 123+ header( 'HTTP/1.0 403 Forbidden' );
 124+ header( 'Content-Type: text/html; charset=utf-8' );
 125+ wfDebugLog( 'img_auth', "wfPublicError Msg: ".$msg2 );
 126+ echo <<<ENDS
 127+<html>
 128+<body>
 129+<h1>$msg1</h1>
 130+<p>$msg2</p>
 131+</body>
 132+</html>
 133+ENDS;
 134+ wfLogProfilingData();
 135+ exit;
 136+}
 137+
Property changes on: trunk/extensions/NSFileRepo/REL1_15_0/phase3/img_auth.php
___________________________________________________________________
Added: svn:eol-style
1138 + native
Index: trunk/extensions/NSFileRepo/REL1_15_0/phase3/includes/GlobalFunctions.php
@@ -0,0 +1,3039 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ die( "This file is part of MediaWiki, it is not a valid entry point" );
 6+}
 7+
 8+/**
 9+ * Global functions used everywhere
 10+ */
 11+
 12+require_once dirname(__FILE__) . '/normal/UtfNormalUtil.php';
 13+require_once dirname(__FILE__) . '/XmlFunctions.php';
 14+
 15+// Hide compatibility functions from Doxygen
 16+/// @cond
 17+
 18+/**
 19+ * Compatibility functions
 20+ *
 21+ * We more or less support PHP 5.0.x and up.
 22+ * Re-implementations of newer functions or functions in non-standard
 23+ * PHP extensions may be included here.
 24+ */
 25+if( !function_exists('iconv') ) {
 26+ # iconv support is not in the default configuration and so may not be present.
 27+ # Assume will only ever use utf-8 and iso-8859-1.
 28+ # This will *not* work in all circumstances.
 29+ function iconv( $from, $to, $string ) {
 30+ if(strcasecmp( $from, $to ) == 0) return $string;
 31+ if(strcasecmp( $from, 'utf-8' ) == 0) return utf8_decode( $string );
 32+ if(strcasecmp( $to, 'utf-8' ) == 0) return utf8_encode( $string );
 33+ return $string;
 34+ }
 35+}
 36+
 37+# UTF-8 substr function based on a PHP manual comment
 38+if ( !function_exists( 'mb_substr' ) ) {
 39+ function mb_substr( $str, $start ) {
 40+ $ar = array();
 41+ preg_match_all( '/./us', $str, $ar );
 42+
 43+ if( func_num_args() >= 3 ) {
 44+ $end = func_get_arg( 2 );
 45+ return join( '', array_slice( $ar[0], $start, $end ) );
 46+ } else {
 47+ return join( '', array_slice( $ar[0], $start ) );
 48+ }
 49+ }
 50+}
 51+
 52+if ( !function_exists( 'mb_strlen' ) ) {
 53+ /**
 54+ * Fallback implementation of mb_strlen, hardcoded to UTF-8.
 55+ * @param string $str
 56+ * @param string $enc optional encoding; ignored
 57+ * @return int
 58+ */
 59+ function mb_strlen( $str, $enc="" ) {
 60+ $counts = count_chars( $str );
 61+ $total = 0;
 62+
 63+ // Count ASCII bytes
 64+ for( $i = 0; $i < 0x80; $i++ ) {
 65+ $total += $counts[$i];
 66+ }
 67+
 68+ // Count multibyte sequence heads
 69+ for( $i = 0xc0; $i < 0xff; $i++ ) {
 70+ $total += $counts[$i];
 71+ }
 72+ return $total;
 73+ }
 74+}
 75+
 76+if ( !function_exists( 'array_diff_key' ) ) {
 77+ /**
 78+ * Exists in PHP 5.1.0+
 79+ * Not quite compatible, two-argument version only
 80+ * Null values will cause problems due to this use of isset()
 81+ */
 82+ function array_diff_key( $left, $right ) {
 83+ $result = $left;
 84+ foreach ( $left as $key => $unused ) {
 85+ if ( isset( $right[$key] ) ) {
 86+ unset( $result[$key] );
 87+ }
 88+ }
 89+ return $result;
 90+ }
 91+}
 92+
 93+// Support for Wietse Venema's taint feature
 94+if ( !function_exists( 'istainted' ) ) {
 95+ function istainted( $var ) {
 96+ return 0;
 97+ }
 98+ function taint( $var, $level = 0 ) {}
 99+ function untaint( $var, $level = 0 ) {}
 100+ define( 'TC_HTML', 1 );
 101+ define( 'TC_SHELL', 1 );
 102+ define( 'TC_MYSQL', 1 );
 103+ define( 'TC_PCRE', 1 );
 104+ define( 'TC_SELF', 1 );
 105+}
 106+/// @endcond
 107+
 108+
 109+/**
 110+ * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
 111+ */
 112+function wfArrayDiff2( $a, $b ) {
 113+ return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
 114+}
 115+function wfArrayDiff2_cmp( $a, $b ) {
 116+ if ( !is_array( $a ) ) {
 117+ return strcmp( $a, $b );
 118+ } elseif ( count( $a ) !== count( $b ) ) {
 119+ return count( $a ) < count( $b ) ? -1 : 1;
 120+ } else {
 121+ reset( $a );
 122+ reset( $b );
 123+ while( ( list( $keyA, $valueA ) = each( $a ) ) && ( list( $keyB, $valueB ) = each( $b ) ) ) {
 124+ $cmp = strcmp( $valueA, $valueB );
 125+ if ( $cmp !== 0 ) {
 126+ return $cmp;
 127+ }
 128+ }
 129+ return 0;
 130+ }
 131+}
 132+
 133+/**
 134+ * Wrapper for clone(), for compatibility with PHP4-friendly extensions.
 135+ * PHP 5 won't let you declare a 'clone' function, even conditionally,
 136+ * so it has to be a wrapper with a different name.
 137+ */
 138+function wfClone( $object ) {
 139+ return clone( $object );
 140+}
 141+
 142+/**
 143+ * Seed Mersenne Twister
 144+ * No-op for compatibility; only necessary in PHP < 4.2.0
 145+ */
 146+function wfSeedRandom() {
 147+ /* No-op */
 148+}
 149+
 150+/**
 151+ * Get a random decimal value between 0 and 1, in a way
 152+ * not likely to give duplicate values for any realistic
 153+ * number of articles.
 154+ *
 155+ * @return string
 156+ */
 157+function wfRandom() {
 158+ # The maximum random value is "only" 2^31-1, so get two random
 159+ # values to reduce the chance of dupes
 160+ $max = mt_getrandmax() + 1;
 161+ $rand = number_format( (mt_rand() * $max + mt_rand())
 162+ / $max / $max, 12, '.', '' );
 163+ return $rand;
 164+}
 165+
 166+/**
 167+ * We want some things to be included as literal characters in our title URLs
 168+ * for prettiness, which urlencode encodes by default. According to RFC 1738,
 169+ * all of the following should be safe:
 170+ *
 171+ * ;:@&=$-_.+!*'(),
 172+ *
 173+ * But + is not safe because it's used to indicate a space; &= are only safe in
 174+ * paths and not in queries (and we don't distinguish here); ' seems kind of
 175+ * scary; and urlencode() doesn't touch -_. to begin with. Plus, although /
 176+ * is reserved, we don't care. So the list we unescape is:
 177+ *
 178+ * ;:@$!*(),/
 179+ *
 180+ * %2F in the page titles seems to fatally break for some reason.
 181+ *
 182+ * @param $s String:
 183+ * @return string
 184+*/
 185+function wfUrlencode( $s ) {
 186+ $s = urlencode( $s );
 187+ $s = str_ireplace(
 188+ array( '%3B','%3A','%40','%24','%21','%2A','%28','%29','%2C','%2F' ),
 189+ array( ';', ':', '@', '$', '!', '*', '(', ')', ',', '/' ),
 190+ $s
 191+ );
 192+
 193+ return $s;
 194+}
 195+
 196+/**
 197+ * Sends a line to the debug log if enabled or, optionally, to a comment in output.
 198+ * In normal operation this is a NOP.
 199+ *
 200+ * Controlling globals:
 201+ * $wgDebugLogFile - points to the log file
 202+ * $wgProfileOnly - if set, normal debug messages will not be recorded.
 203+ * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
 204+ * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
 205+ *
 206+ * @param $text String
 207+ * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
 208+ */
 209+function wfDebug( $text, $logonly = false ) {
 210+ global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
 211+ global $wgDebugLogPrefix;
 212+ static $recursion = 0;
 213+
 214+ static $cache = array(); // Cache of unoutputted messages
 215+
 216+ # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
 217+ if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
 218+ return;
 219+ }
 220+
 221+ if ( $wgDebugComments && !$logonly ) {
 222+ $cache[] = $text;
 223+
 224+ if ( !isset( $wgOut ) ) {
 225+ return;
 226+ }
 227+ if ( !StubObject::isRealObject( $wgOut ) ) {
 228+ if ( $recursion ) {
 229+ return;
 230+ }
 231+ $recursion++;
 232+ $wgOut->_unstub();
 233+ $recursion--;
 234+ }
 235+
 236+ // add the message and possible cached ones to the output
 237+ array_map( array( $wgOut, 'debug' ), $cache );
 238+ $cache = array();
 239+ }
 240+ if ( '' != $wgDebugLogFile && !$wgProfileOnly ) {
 241+ # Strip unprintables; they can switch terminal modes when binary data
 242+ # gets dumped, which is pretty annoying.
 243+ $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
 244+ $text = $wgDebugLogPrefix . $text;
 245+ wfErrorLog( $text, $wgDebugLogFile );
 246+ }
 247+}
 248+
 249+/**
 250+ * Send a line giving PHP memory usage.
 251+ * @param $exact Bool : print exact values instead of kilobytes (default: false)
 252+ */
 253+function wfDebugMem( $exact = false ) {
 254+ $mem = memory_get_usage();
 255+ if( !$exact ) {
 256+ $mem = floor( $mem / 1024 ) . ' kilobytes';
 257+ } else {
 258+ $mem .= ' bytes';
 259+ }
 260+ wfDebug( "Memory usage: $mem\n" );
 261+}
 262+
 263+/**
 264+ * Send a line to a supplementary debug log file, if configured, or main debug log if not.
 265+ * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
 266+ *
 267+ * @param $logGroup String
 268+ * @param $text String
 269+ * @param $public Bool: whether to log the event in the public log if no private
 270+ * log file is specified, (default true)
 271+ */
 272+function wfDebugLog( $logGroup, $text, $public = true ) {
 273+ global $wgDebugLogGroups, $wgShowHostnames;
 274+ $text = trim($text)."\n";
 275+ if( isset( $wgDebugLogGroups[$logGroup] ) ) {
 276+ $time = wfTimestamp( TS_DB );
 277+ $wiki = wfWikiID();
 278+ if ( $wgShowHostnames ) {
 279+ $host = wfHostname();
 280+ } else {
 281+ $host = '';
 282+ }
 283+ wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
 284+ } else if ( $public === true ) {
 285+ wfDebug( $text, true );
 286+ }
 287+}
 288+
 289+/**
 290+ * Log for database errors
 291+ * @param $text String: database error message.
 292+ */
 293+function wfLogDBError( $text ) {
 294+ global $wgDBerrorLog, $wgDBname;
 295+ if ( $wgDBerrorLog ) {
 296+ $host = trim(`hostname`);
 297+ $text = date('D M j G:i:s T Y') . "\t$host\t$wgDBname\t$text";
 298+ wfErrorLog( $text, $wgDBerrorLog );
 299+ }
 300+}
 301+
 302+/**
 303+ * Log to a file without getting "file size exceeded" signals.
 304+ *
 305+ * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
 306+ * send lines to the specified port, prefixed by the specified prefix and a space.
 307+ */
 308+function wfErrorLog( $text, $file ) {
 309+ if ( substr( $file, 0, 4 ) == 'udp:' ) {
 310+ if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
 311+ // IPv6 bracketed host
 312+ $protocol = $m[1];
 313+ $host = $m[2];
 314+ $port = $m[3];
 315+ $prefix = isset( $m[4] ) ? $m[4] : false;
 316+ } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
 317+ $protocol = $m[1];
 318+ $host = $m[2];
 319+ $port = $m[3];
 320+ $prefix = isset( $m[4] ) ? $m[4] : false;
 321+ } else {
 322+ throw new MWException( __METHOD__.": Invalid UDP specification" );
 323+ }
 324+ // Clean it up for the multiplexer
 325+ if ( strval( $prefix ) !== '' ) {
 326+ $text = preg_replace( '/^/m', $prefix . ' ', $text );
 327+ if ( substr( $text, -1 ) != "\n" ) {
 328+ $text .= "\n";
 329+ }
 330+ }
 331+
 332+ $sock = fsockopen( "$protocol://$host", $port );
 333+ if ( !$sock ) {
 334+ return;
 335+ }
 336+ fwrite( $sock, $text );
 337+ fclose( $sock );
 338+ } else {
 339+ wfSuppressWarnings();
 340+ $exists = file_exists( $file );
 341+ $size = $exists ? filesize( $file ) : false;
 342+ if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
 343+ error_log( $text, 3, $file );
 344+ }
 345+ wfRestoreWarnings();
 346+ }
 347+}
 348+
 349+/**
 350+ * @todo document
 351+ */
 352+function wfLogProfilingData() {
 353+ global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
 354+ global $wgProfiler, $wgProfileLimit, $wgUser;
 355+ # Profiling must actually be enabled...
 356+ if( !isset( $wgProfiler ) ) return;
 357+ # Get total page request time
 358+ $now = wfTime();
 359+ $elapsed = $now - $wgRequestTime;
 360+ # Only show pages that longer than $wgProfileLimit time (default is 0)
 361+ if( $elapsed <= $wgProfileLimit ) return;
 362+ $prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
 363+ $forward = '';
 364+ if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
 365+ $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
 366+ if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) )
 367+ $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
 368+ if( !empty( $_SERVER['HTTP_FROM'] ) )
 369+ $forward .= ' from ' . $_SERVER['HTTP_FROM'];
 370+ if( $forward )
 371+ $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
 372+ // Don't unstub $wgUser at this late stage just for statistics purposes
 373+ if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() )
 374+ $forward .= ' anon';
 375+ $log = sprintf( "%s\t%04.3f\t%s\n",
 376+ gmdate( 'YmdHis' ), $elapsed,
 377+ urldecode( $wgRequest->getRequestURL() . $forward ) );
 378+ if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) {
 379+ wfErrorLog( $log . $prof, $wgDebugLogFile );
 380+ }
 381+}
 382+
 383+/**
 384+ * Check if the wiki read-only lock file is present. This can be used to lock
 385+ * off editing functions, but doesn't guarantee that the database will not be
 386+ * modified.
 387+ * @return bool
 388+ */
 389+function wfReadOnly() {
 390+ global $wgReadOnlyFile, $wgReadOnly;
 391+
 392+ if ( !is_null( $wgReadOnly ) ) {
 393+ return (bool)$wgReadOnly;
 394+ }
 395+ if ( '' == $wgReadOnlyFile ) {
 396+ return false;
 397+ }
 398+ // Set $wgReadOnly for faster access next time
 399+ if ( is_file( $wgReadOnlyFile ) ) {
 400+ $wgReadOnly = file_get_contents( $wgReadOnlyFile );
 401+ } else {
 402+ $wgReadOnly = false;
 403+ }
 404+ return (bool)$wgReadOnly;
 405+}
 406+
 407+function wfReadOnlyReason() {
 408+ global $wgReadOnly;
 409+ wfReadOnly();
 410+ return $wgReadOnly;
 411+}
 412+
 413+/**
 414+ * Return a Language object from $langcode
 415+ * @param $langcode Mixed: either:
 416+ * - a Language object
 417+ * - code of the language to get the message for, if it is
 418+ * a valid code create a language for that language, if
 419+ * it is a string but not a valid code then make a basic
 420+ * language object
 421+ * - a boolean: if it's false then use the current users
 422+ * language (as a fallback for the old parameter
 423+ * functionality), or if it is true then use the wikis
 424+ * @return Language object
 425+ */
 426+function wfGetLangObj( $langcode = false ){
 427+ # Identify which language to get or create a language object for.
 428+ if( $langcode instanceof Language )
 429+ # Great, we already have the object!
 430+ return $langcode;
 431+
 432+ global $wgContLang;
 433+ if( $langcode === $wgContLang->getCode() || $langcode === true )
 434+ # $langcode is the language code of the wikis content language object.
 435+ # or it is a boolean and value is true
 436+ return $wgContLang;
 437+
 438+ global $wgLang;
 439+ if( $langcode === $wgLang->getCode() || $langcode === false )
 440+ # $langcode is the language code of user language object.
 441+ # or it was a boolean and value is false
 442+ return $wgLang;
 443+
 444+ $validCodes = array_keys( Language::getLanguageNames() );
 445+ if( in_array( $langcode, $validCodes ) )
 446+ # $langcode corresponds to a valid language.
 447+ return Language::factory( $langcode );
 448+
 449+ # $langcode is a string, but not a valid language code; use content language.
 450+ wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
 451+ return $wgContLang;
 452+}
 453+
 454+/**
 455+ * Get a message from anywhere, for the current user language.
 456+ *
 457+ * Use wfMsgForContent() instead if the message should NOT
 458+ * change depending on the user preferences.
 459+ *
 460+ * @param $key String: lookup key for the message, usually
 461+ * defined in languages/Language.php
 462+ *
 463+ * This function also takes extra optional parameters (not
 464+ * shown in the function definition), which can by used to
 465+ * insert variable text into the predefined message.
 466+ */
 467+function wfMsg( $key ) {
 468+ $args = func_get_args();
 469+ array_shift( $args );
 470+ return wfMsgReal( $key, $args, true );
 471+}
 472+
 473+/**
 474+ * Same as above except doesn't transform the message
 475+ */
 476+function wfMsgNoTrans( $key ) {
 477+ $args = func_get_args();
 478+ array_shift( $args );
 479+ return wfMsgReal( $key, $args, true, false, false );
 480+}
 481+
 482+/**
 483+ * Get a message from anywhere, for the current global language
 484+ * set with $wgLanguageCode.
 485+ *
 486+ * Use this if the message should NOT change dependent on the
 487+ * language set in the user's preferences. This is the case for
 488+ * most text written into logs, as well as link targets (such as
 489+ * the name of the copyright policy page). Link titles, on the
 490+ * other hand, should be shown in the UI language.
 491+ *
 492+ * Note that MediaWiki allows users to change the user interface
 493+ * language in their preferences, but a single installation
 494+ * typically only contains content in one language.
 495+ *
 496+ * Be wary of this distinction: If you use wfMsg() where you should
 497+ * use wfMsgForContent(), a user of the software may have to
 498+ * customize over 70 messages in order to, e.g., fix a link in every
 499+ * possible language.
 500+ *
 501+ * @param $key String: lookup key for the message, usually
 502+ * defined in languages/Language.php
 503+ */
 504+function wfMsgForContent( $key ) {
 505+ global $wgForceUIMsgAsContentMsg;
 506+ $args = func_get_args();
 507+ array_shift( $args );
 508+ $forcontent = true;
 509+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 510+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 511+ $forcontent = false;
 512+ return wfMsgReal( $key, $args, true, $forcontent );
 513+}
 514+
 515+/**
 516+ * Same as above except doesn't transform the message
 517+ */
 518+function wfMsgForContentNoTrans( $key ) {
 519+ global $wgForceUIMsgAsContentMsg;
 520+ $args = func_get_args();
 521+ array_shift( $args );
 522+ $forcontent = true;
 523+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 524+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 525+ $forcontent = false;
 526+ return wfMsgReal( $key, $args, true, $forcontent, false );
 527+}
 528+
 529+/**
 530+ * Get a message from the language file, for the UI elements
 531+ */
 532+function wfMsgNoDB( $key ) {
 533+ $args = func_get_args();
 534+ array_shift( $args );
 535+ return wfMsgReal( $key, $args, false );
 536+}
 537+
 538+/**
 539+ * Get a message from the language file, for the content
 540+ */
 541+function wfMsgNoDBForContent( $key ) {
 542+ global $wgForceUIMsgAsContentMsg;
 543+ $args = func_get_args();
 544+ array_shift( $args );
 545+ $forcontent = true;
 546+ if( is_array( $wgForceUIMsgAsContentMsg ) &&
 547+ in_array( $key, $wgForceUIMsgAsContentMsg ) )
 548+ $forcontent = false;
 549+ return wfMsgReal( $key, $args, false, $forcontent );
 550+}
 551+
 552+
 553+/**
 554+ * Really get a message
 555+ * @param $key String: key to get.
 556+ * @param $args
 557+ * @param $useDB Boolean
 558+ * @param $transform Boolean: Whether or not to transform the message.
 559+ * @param $forContent Boolean
 560+ * @return String: the requested message.
 561+ */
 562+function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) {
 563+ wfProfileIn( __METHOD__ );
 564+ $message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
 565+ $message = wfMsgReplaceArgs( $message, $args );
 566+ wfProfileOut( __METHOD__ );
 567+ return $message;
 568+}
 569+
 570+/**
 571+ * This function provides the message source for messages to be edited which are *not* stored in the database.
 572+ * @param $key String:
 573+ */
 574+function wfMsgWeirdKey ( $key ) {
 575+ $source = wfMsgGetKey( $key, false, true, false );
 576+ if ( wfEmptyMsg( $key, $source ) )
 577+ return "";
 578+ else
 579+ return $source;
 580+}
 581+
 582+/**
 583+ * Fetch a message string value, but don't replace any keys yet.
 584+ * @param string $key
 585+ * @param bool $useDB
 586+ * @param string $langcode Code of the language to get the message for, or
 587+ * behaves as a content language switch if it is a
 588+ * boolean.
 589+ * @return string
 590+ * @private
 591+ */
 592+function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
 593+ global $wgContLang, $wgMessageCache;
 594+
 595+ wfRunHooks('NormalizeMessageKey', array(&$key, &$useDB, &$langCode, &$transform));
 596+
 597+ # If $wgMessageCache isn't initialised yet, try to return something sensible.
 598+ if( is_object( $wgMessageCache ) ) {
 599+ $message = $wgMessageCache->get( $key, $useDB, $langCode );
 600+ if ( $transform ) {
 601+ $message = $wgMessageCache->transform( $message );
 602+ }
 603+ } else {
 604+ $lang = wfGetLangObj( $langCode );
 605+
 606+ # MessageCache::get() does this already, Language::getMessage() doesn't
 607+ # ISSUE: Should we try to handle "message/lang" here too?
 608+ $key = str_replace( ' ' , '_' , $wgContLang->lcfirst( $key ) );
 609+
 610+ if( is_object( $lang ) ) {
 611+ $message = $lang->getMessage( $key );
 612+ } else {
 613+ $message = false;
 614+ }
 615+ }
 616+
 617+ return $message;
 618+}
 619+
 620+/**
 621+ * Replace message parameter keys on the given formatted output.
 622+ *
 623+ * @param string $message
 624+ * @param array $args
 625+ * @return string
 626+ * @private
 627+ */
 628+function wfMsgReplaceArgs( $message, $args ) {
 629+ # Fix windows line-endings
 630+ # Some messages are split with explode("\n", $msg)
 631+ $message = str_replace( "\r", '', $message );
 632+
 633+ // Replace arguments
 634+ if ( count( $args ) ) {
 635+ if ( is_array( $args[0] ) ) {
 636+ $args = array_values( $args[0] );
 637+ }
 638+ $replacementKeys = array();
 639+ foreach( $args as $n => $param ) {
 640+ $replacementKeys['$' . ($n + 1)] = $param;
 641+ }
 642+ $message = strtr( $message, $replacementKeys );
 643+ }
 644+
 645+ return $message;
 646+}
 647+
 648+/**
 649+ * Return an HTML-escaped version of a message.
 650+ * Parameter replacements, if any, are done *after* the HTML-escaping,
 651+ * so parameters may contain HTML (eg links or form controls). Be sure
 652+ * to pre-escape them if you really do want plaintext, or just wrap
 653+ * the whole thing in htmlspecialchars().
 654+ *
 655+ * @param string $key
 656+ * @param string ... parameters
 657+ * @return string
 658+ */
 659+function wfMsgHtml( $key ) {
 660+ $args = func_get_args();
 661+ array_shift( $args );
 662+ return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
 663+}
 664+
 665+/**
 666+ * Return an HTML version of message
 667+ * Parameter replacements, if any, are done *after* parsing the wiki-text message,
 668+ * so parameters may contain HTML (eg links or form controls). Be sure
 669+ * to pre-escape them if you really do want plaintext, or just wrap
 670+ * the whole thing in htmlspecialchars().
 671+ *
 672+ * @param string $key
 673+ * @param string ... parameters
 674+ * @return string
 675+ */
 676+function wfMsgWikiHtml( $key ) {
 677+ global $wgOut;
 678+ $args = func_get_args();
 679+ array_shift( $args );
 680+ return wfMsgReplaceArgs( $wgOut->parse( wfMsgGetKey( $key, true ), /* can't be set to false */ true ), $args );
 681+}
 682+
 683+/**
 684+ * Returns message in the requested format
 685+ * @param string $key Key of the message
 686+ * @param array $options Processing rules. Can take the following options:
 687+ * <i>parse</i>: parses wikitext to html
 688+ * <i>parseinline</i>: parses wikitext to html and removes the surrounding
 689+ * p's added by parser or tidy
 690+ * <i>escape</i>: filters message through htmlspecialchars
 691+ * <i>escapenoentities</i>: same, but allows entity references like &nbsp; through
 692+ * <i>replaceafter</i>: parameters are substituted after parsing or escaping
 693+ * <i>parsemag</i>: transform the message using magic phrases
 694+ * <i>content</i>: fetch message for content language instead of interface
 695+ * Also can accept a single associative argument, of the form 'language' => 'xx':
 696+ * <i>language</i>: Language object or language code to fetch message for
 697+ * (overriden by <i>content</i>), its behaviour with parser, parseinline
 698+ * and parsemag is undefined.
 699+ * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
 700+ */
 701+function wfMsgExt( $key, $options ) {
 702+ global $wgOut;
 703+
 704+ $args = func_get_args();
 705+ array_shift( $args );
 706+ array_shift( $args );
 707+ $options = (array)$options;
 708+
 709+ foreach( $options as $arrayKey => $option ) {
 710+ if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
 711+ # An unknown index, neither numeric nor "language"
 712+ trigger_error( "wfMsgExt called with incorrect parameter key $arrayKey", E_USER_WARNING );
 713+ } elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
 714+ array( 'parse', 'parseinline', 'escape', 'escapenoentities',
 715+ 'replaceafter', 'parsemag', 'content' ) ) ) {
 716+ # A numeric index with unknown value
 717+ trigger_error( "wfMsgExt called with incorrect parameter $option", E_USER_WARNING );
 718+ }
 719+ }
 720+
 721+ if( in_array('content', $options, true ) ) {
 722+ $forContent = true;
 723+ $langCode = true;
 724+ } elseif( array_key_exists('language', $options) ) {
 725+ $forContent = false;
 726+ $langCode = wfGetLangObj( $options['language'] );
 727+ } else {
 728+ $forContent = false;
 729+ $langCode = false;
 730+ }
 731+
 732+ $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
 733+
 734+ if( !in_array('replaceafter', $options, true ) ) {
 735+ $string = wfMsgReplaceArgs( $string, $args );
 736+ }
 737+
 738+ if( in_array('parse', $options, true ) ) {
 739+ $string = $wgOut->parse( $string, true, !$forContent );
 740+ } elseif ( in_array('parseinline', $options, true ) ) {
 741+ $string = $wgOut->parse( $string, true, !$forContent );
 742+ $m = array();
 743+ if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
 744+ $string = $m[1];
 745+ }
 746+ } elseif ( in_array('parsemag', $options, true ) ) {
 747+ global $wgMessageCache;
 748+ if ( isset( $wgMessageCache ) ) {
 749+ $string = $wgMessageCache->transform( $string,
 750+ !$forContent,
 751+ is_object( $langCode ) ? $langCode : null );
 752+ }
 753+ }
 754+
 755+ if ( in_array('escape', $options, true ) ) {
 756+ $string = htmlspecialchars ( $string );
 757+ } elseif ( in_array( 'escapenoentities', $options, true ) ) {
 758+ $string = Sanitizer::escapeHtmlAllowEntities( $string );
 759+ }
 760+
 761+ if( in_array('replaceafter', $options, true ) ) {
 762+ $string = wfMsgReplaceArgs( $string, $args );
 763+ }
 764+
 765+ return $string;
 766+}
 767+
 768+
 769+/**
 770+ * Just like exit() but makes a note of it.
 771+ * Commits open transactions except if the error parameter is set
 772+ *
 773+ * @deprecated Please return control to the caller or throw an exception
 774+ */
 775+function wfAbruptExit( $error = false ){
 776+ static $called = false;
 777+ if ( $called ){
 778+ exit( -1 );
 779+ }
 780+ $called = true;
 781+
 782+ $bt = wfDebugBacktrace();
 783+ if( $bt ) {
 784+ for($i = 0; $i < count($bt) ; $i++){
 785+ $file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown";
 786+ $line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown";
 787+ wfDebug("WARNING: Abrupt exit in $file at line $line\n");
 788+ }
 789+ } else {
 790+ wfDebug("WARNING: Abrupt exit\n");
 791+ }
 792+
 793+ wfLogProfilingData();
 794+
 795+ if ( !$error ) {
 796+ wfGetLB()->closeAll();
 797+ }
 798+ exit( -1 );
 799+}
 800+
 801+/**
 802+ * @deprecated Please return control the caller or throw an exception
 803+ */
 804+function wfErrorExit() {
 805+ wfAbruptExit( true );
 806+}
 807+
 808+/**
 809+ * Print a simple message and die, returning nonzero to the shell if any.
 810+ * Plain die() fails to return nonzero to the shell if you pass a string.
 811+ * @param string $msg
 812+ */
 813+function wfDie( $msg='' ) {
 814+ echo $msg;
 815+ die( 1 );
 816+}
 817+
 818+/**
 819+ * Throw a debugging exception. This function previously once exited the process,
 820+ * but now throws an exception instead, with similar results.
 821+ *
 822+ * @param string $msg Message shown when dieing.
 823+ */
 824+function wfDebugDieBacktrace( $msg = '' ) {
 825+ throw new MWException( $msg );
 826+}
 827+
 828+/**
 829+ * Fetch server name for use in error reporting etc.
 830+ * Use real server name if available, so we know which machine
 831+ * in a server farm generated the current page.
 832+ * @return string
 833+ */
 834+function wfHostname() {
 835+ static $host;
 836+ if ( is_null( $host ) ) {
 837+ if ( function_exists( 'posix_uname' ) ) {
 838+ // This function not present on Windows
 839+ $uname = @posix_uname();
 840+ } else {
 841+ $uname = false;
 842+ }
 843+ if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
 844+ $host = $uname['nodename'];
 845+ } elseif ( getenv( 'COMPUTERNAME' ) ) {
 846+ # Windows computer name
 847+ $host = getenv( 'COMPUTERNAME' );
 848+ } else {
 849+ # This may be a virtual server.
 850+ $host = $_SERVER['SERVER_NAME'];
 851+ }
 852+ }
 853+ return $host;
 854+}
 855+
 856+ /**
 857+ * Returns a HTML comment with the elapsed time since request.
 858+ * This method has no side effects.
 859+ * @return string
 860+ */
 861+ function wfReportTime() {
 862+ global $wgRequestTime, $wgShowHostnames;
 863+
 864+ $now = wfTime();
 865+ $elapsed = $now - $wgRequestTime;
 866+
 867+ return $wgShowHostnames
 868+ ? sprintf( "<!-- Served by %s in %01.3f secs. -->", wfHostname(), $elapsed )
 869+ : sprintf( "<!-- Served in %01.3f secs. -->", $elapsed );
 870+ }
 871+
 872+/**
 873+ * Safety wrapper for debug_backtrace().
 874+ *
 875+ * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
 876+ * murky circumstances, which may be triggered in part by stub objects
 877+ * or other fancy talkin'.
 878+ *
 879+ * Will return an empty array if Zend Optimizer is detected or if
 880+ * debug_backtrace is disabled, otherwise the output from
 881+ * debug_backtrace() (trimmed).
 882+ *
 883+ * @return array of backtrace information
 884+ */
 885+function wfDebugBacktrace() {
 886+ static $disabled = null;
 887+
 888+ if( extension_loaded( 'Zend Optimizer' ) ) {
 889+ wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
 890+ return array();
 891+ }
 892+
 893+ if ( is_null( $disabled ) ) {
 894+ $disabled = false;
 895+ $functions = explode( ',', ini_get( 'disable_functions' ) );
 896+ $functions = array_map( 'trim', $functions );
 897+ $functions = array_map( 'strtolower', $functions );
 898+ if ( in_array( 'debug_backtrace', $functions ) ) {
 899+ wfDebug( "debug_backtrace is in disabled_functions\n" );
 900+ $disabled = true;
 901+ }
 902+ }
 903+ if ( $disabled ) {
 904+ return array();
 905+ }
 906+
 907+ return array_slice( debug_backtrace(), 1 );
 908+}
 909+
 910+function wfBacktrace() {
 911+ global $wgCommandLineMode;
 912+
 913+ if ( $wgCommandLineMode ) {
 914+ $msg = '';
 915+ } else {
 916+ $msg = "<ul>\n";
 917+ }
 918+ $backtrace = wfDebugBacktrace();
 919+ foreach( $backtrace as $call ) {
 920+ if( isset( $call['file'] ) ) {
 921+ $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
 922+ $file = $f[count($f)-1];
 923+ } else {
 924+ $file = '-';
 925+ }
 926+ if( isset( $call['line'] ) ) {
 927+ $line = $call['line'];
 928+ } else {
 929+ $line = '-';
 930+ }
 931+ if ( $wgCommandLineMode ) {
 932+ $msg .= "$file line $line calls ";
 933+ } else {
 934+ $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
 935+ }
 936+ if( !empty( $call['class'] ) ) $msg .= $call['class'] . '::';
 937+ $msg .= $call['function'] . '()';
 938+
 939+ if ( $wgCommandLineMode ) {
 940+ $msg .= "\n";
 941+ } else {
 942+ $msg .= "</li>\n";
 943+ }
 944+ }
 945+ if ( $wgCommandLineMode ) {
 946+ $msg .= "\n";
 947+ } else {
 948+ $msg .= "</ul>\n";
 949+ }
 950+
 951+ return $msg;
 952+}
 953+
 954+
 955+/* Some generic result counters, pulled out of SearchEngine */
 956+
 957+
 958+/**
 959+ * @todo document
 960+ */
 961+function wfShowingResults( $offset, $limit ) {
 962+ global $wgLang;
 963+ return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ),
 964+ $wgLang->formatNum( $offset+1 ) );
 965+}
 966+
 967+/**
 968+ * @todo document
 969+ */
 970+function wfShowingResultsNum( $offset, $limit, $num ) {
 971+ global $wgLang;
 972+ return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ),
 973+ $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
 974+}
 975+
 976+/**
 977+ * Generate (prev x| next x) (20|50|100...) type links for paging
 978+ * @param $offset string
 979+ * @param $limit int
 980+ * @param $link string
 981+ * @param $query string, optional URL query parameter string
 982+ * @param $atend bool, optional param for specified if this is the last page
 983+ */
 984+function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
 985+ global $wgLang;
 986+ $fmtLimit = $wgLang->formatNum( $limit );
 987+ # Get prev/next link display text
 988+ $prev = wfMsgHtml( 'prevn', $fmtLimit );
 989+ $next = wfMsgHtml( 'nextn', $fmtLimit );
 990+ # Get prev/next link title text
 991+ $pTitle = wfMsgExt( 'prevn-title', array('parsemag','escape'), $fmtLimit );
 992+ $nTitle = wfMsgExt( 'nextn-title', array('parsemag','escape'), $fmtLimit );
 993+ # Fetch the title object
 994+ if( is_object( $link ) ) {
 995+ $title =& $link;
 996+ } else {
 997+ $title = Title::newFromText( $link );
 998+ if( is_null( $title ) ) {
 999+ return false;
 1000+ }
 1001+ }
 1002+ # Make 'previous' link
 1003+ if( 0 != $offset ) {
 1004+ $po = $offset - $limit;
 1005+ $po = max($po,0);
 1006+ $q = "limit={$limit}&offset={$po}";
 1007+ if( $query != '' ) {
 1008+ $q .= '&'.$query;
 1009+ }
 1010+ $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$pTitle}\" class=\"mw-prevlink\">{$prev}</a>";
 1011+ } else {
 1012+ $plink = $prev;
 1013+ }
 1014+ # Make 'next' link
 1015+ $no = $offset + $limit;
 1016+ $q = "limit={$limit}&offset={$no}";
 1017+ if( $query != '' ) {
 1018+ $q .= '&'.$query;
 1019+ }
 1020+ if( $atend ) {
 1021+ $nlink = $next;
 1022+ } else {
 1023+ $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$nTitle}\" class=\"mw-nextlink\">{$next}</a>";
 1024+ }
 1025+ # Make links to set number of items per page
 1026+ $nums = $wgLang->pipeList( array(
 1027+ wfNumLink( $offset, 20, $title, $query ),
 1028+ wfNumLink( $offset, 50, $title, $query ),
 1029+ wfNumLink( $offset, 100, $title, $query ),
 1030+ wfNumLink( $offset, 250, $title, $query ),
 1031+ wfNumLink( $offset, 500, $title, $query )
 1032+ ) );
 1033+ return wfMsg( 'viewprevnext', $plink, $nlink, $nums );
 1034+}
 1035+
 1036+/**
 1037+ * Generate links for (20|50|100...) items-per-page links
 1038+ * @param $offset string
 1039+ * @param $limit int
 1040+ * @param $title Title
 1041+ * @param $query string, optional URL query parameter string
 1042+ */
 1043+function wfNumLink( $offset, $limit, $title, $query = '' ) {
 1044+ global $wgLang;
 1045+ if( $query == '' ) {
 1046+ $q = '';
 1047+ } else {
 1048+ $q = $query.'&';
 1049+ }
 1050+ $q .= "limit={$limit}&offset={$offset}";
 1051+ $fmtLimit = $wgLang->formatNum( $limit );
 1052+ $lTitle = wfMsgExt('shown-title',array('parsemag','escape'),$limit);
 1053+ $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>";
 1054+ return $s;
 1055+}
 1056+
 1057+/**
 1058+ * @todo document
 1059+ * @todo FIXME: we may want to blacklist some broken browsers
 1060+ *
 1061+ * @return bool Whereas client accept gzip compression
 1062+ */
 1063+function wfClientAcceptsGzip() {
 1064+ global $wgUseGzip;
 1065+ if( $wgUseGzip ) {
 1066+ # FIXME: we may want to blacklist some broken browsers
 1067+ $m = array();
 1068+ if( preg_match(
 1069+ '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
 1070+ $_SERVER['HTTP_ACCEPT_ENCODING'],
 1071+ $m ) ) {
 1072+ if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) return false;
 1073+ wfDebug( " accepts gzip\n" );
 1074+ return true;
 1075+ }
 1076+ }
 1077+ return false;
 1078+}
 1079+
 1080+/**
 1081+ * Obtain the offset and limit values from the request string;
 1082+ * used in special pages
 1083+ *
 1084+ * @param $deflimit Default limit if none supplied
 1085+ * @param $optionname Name of a user preference to check against
 1086+ * @return array
 1087+ *
 1088+ */
 1089+function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
 1090+ global $wgRequest;
 1091+ return $wgRequest->getLimitOffset( $deflimit, $optionname );
 1092+}
 1093+
 1094+/**
 1095+ * Escapes the given text so that it may be output using addWikiText()
 1096+ * without any linking, formatting, etc. making its way through. This
 1097+ * is achieved by substituting certain characters with HTML entities.
 1098+ * As required by the callers, <nowiki> is not used. It currently does
 1099+ * not filter out characters which have special meaning only at the
 1100+ * start of a line, such as "*".
 1101+ *
 1102+ * @param string $text Text to be escaped
 1103+ */
 1104+function wfEscapeWikiText( $text ) {
 1105+ $text = str_replace(
 1106+ array( '[', '|', ']', '\'', 'ISBN ', 'RFC ', '://', "\n=", '{{' ), # }}
 1107+ array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;', 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;' ),
 1108+ htmlspecialchars($text) );
 1109+ return $text;
 1110+}
 1111+
 1112+/**
 1113+ * @todo document
 1114+ */
 1115+function wfQuotedPrintable( $string, $charset = '' ) {
 1116+ # Probably incomplete; see RFC 2045
 1117+ if( empty( $charset ) ) {
 1118+ global $wgInputEncoding;
 1119+ $charset = $wgInputEncoding;
 1120+ }
 1121+ $charset = strtoupper( $charset );
 1122+ $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
 1123+
 1124+ $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
 1125+ $replace = $illegal . '\t ?_';
 1126+ if( !preg_match( "/[$illegal]/", $string ) ) return $string;
 1127+ $out = "=?$charset?Q?";
 1128+ $out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
 1129+ $out .= '?=';
 1130+ return $out;
 1131+}
 1132+
 1133+
 1134+/**
 1135+ * @todo document
 1136+ * @return float
 1137+ */
 1138+function wfTime() {
 1139+ return microtime(true);
 1140+}
 1141+
 1142+/**
 1143+ * Sets dest to source and returns the original value of dest
 1144+ * If source is NULL, it just returns the value, it doesn't set the variable
 1145+ */
 1146+function wfSetVar( &$dest, $source ) {
 1147+ $temp = $dest;
 1148+ if ( !is_null( $source ) ) {
 1149+ $dest = $source;
 1150+ }
 1151+ return $temp;
 1152+}
 1153+
 1154+/**
 1155+ * As for wfSetVar except setting a bit
 1156+ */
 1157+function wfSetBit( &$dest, $bit, $state = true ) {
 1158+ $temp = (bool)($dest & $bit );
 1159+ if ( !is_null( $state ) ) {
 1160+ if ( $state ) {
 1161+ $dest |= $bit;
 1162+ } else {
 1163+ $dest &= ~$bit;
 1164+ }
 1165+ }
 1166+ return $temp;
 1167+}
 1168+
 1169+/**
 1170+ * This function takes two arrays as input, and returns a CGI-style string, e.g.
 1171+ * "days=7&limit=100". Options in the first array override options in the second.
 1172+ * Options set to "" will not be output.
 1173+ */
 1174+function wfArrayToCGI( $array1, $array2 = NULL )
 1175+{
 1176+ if ( !is_null( $array2 ) ) {
 1177+ $array1 = $array1 + $array2;
 1178+ }
 1179+
 1180+ $cgi = '';
 1181+ foreach ( $array1 as $key => $value ) {
 1182+ if ( '' !== $value ) {
 1183+ if ( '' != $cgi ) {
 1184+ $cgi .= '&';
 1185+ }
 1186+ if(is_array($value))
 1187+ {
 1188+ $firstTime = true;
 1189+ foreach($value as $v)
 1190+ {
 1191+ $cgi .= ($firstTime ? '' : '&') .
 1192+ urlencode( $key . '[]' ) . '=' .
 1193+ urlencode( $v );
 1194+ $firstTime = false;
 1195+ }
 1196+ }
 1197+ else
 1198+ $cgi .= urlencode( $key ) . '=' .
 1199+ urlencode( $value );
 1200+ }
 1201+ }
 1202+ return $cgi;
 1203+}
 1204+
 1205+/**
 1206+ * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
 1207+ * its argument and returns the same string in array form. This allows compa-
 1208+ * tibility with legacy functions that accept raw query strings instead of nice
 1209+ * arrays. Of course, keys and values are urldecode()d. Don't try passing in-
 1210+ * valid query strings, or it will explode.
 1211+ *
 1212+ * @param $query string Query string
 1213+ * @return array Array version of input
 1214+ */
 1215+function wfCgiToArray( $query ) {
 1216+ if( isset( $query[0] ) and $query[0] == '?' ) {
 1217+ $query = substr( $query, 1 );
 1218+ }
 1219+ $bits = explode( '&', $query );
 1220+ $ret = array();
 1221+ foreach( $bits as $bit ) {
 1222+ if( $bit === '' ) {
 1223+ continue;
 1224+ }
 1225+ list( $key, $value ) = explode( '=', $bit );
 1226+ $key = urldecode( $key );
 1227+ $value = urldecode( $value );
 1228+ $ret[$key] = $value;
 1229+ }
 1230+ return $ret;
 1231+}
 1232+
 1233+/**
 1234+ * Append a query string to an existing URL, which may or may not already
 1235+ * have query string parameters already. If so, they will be combined.
 1236+ *
 1237+ * @param string $url
 1238+ * @param string $query
 1239+ * @return string
 1240+ */
 1241+function wfAppendQuery( $url, $query ) {
 1242+ if( $query != '' ) {
 1243+ if( false === strpos( $url, '?' ) ) {
 1244+ $url .= '?';
 1245+ } else {
 1246+ $url .= '&';
 1247+ }
 1248+ $url .= $query;
 1249+ }
 1250+ return $url;
 1251+}
 1252+
 1253+/**
 1254+ * Expand a potentially local URL to a fully-qualified URL.
 1255+ * Assumes $wgServer is correct. :)
 1256+ * @param string $url, either fully-qualified or a local path + query
 1257+ * @return string Fully-qualified URL
 1258+ */
 1259+function wfExpandUrl( $url ) {
 1260+ if( substr( $url, 0, 1 ) == '/' ) {
 1261+ global $wgServer;
 1262+ return $wgServer . $url;
 1263+ } else {
 1264+ return $url;
 1265+ }
 1266+}
 1267+
 1268+/**
 1269+ * This is obsolete, use SquidUpdate::purge()
 1270+ * @deprecated
 1271+ */
 1272+function wfPurgeSquidServers ($urlArr) {
 1273+ SquidUpdate::purge( $urlArr );
 1274+}
 1275+
 1276+/**
 1277+ * Windows-compatible version of escapeshellarg()
 1278+ * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
 1279+ * function puts single quotes in regardless of OS.
 1280+ *
 1281+ * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
 1282+ * earlier distro releases of PHP)
 1283+ */
 1284+function wfEscapeShellArg( ) {
 1285+ wfInitShellLocale();
 1286+
 1287+ $args = func_get_args();
 1288+ $first = true;
 1289+ $retVal = '';
 1290+ foreach ( $args as $arg ) {
 1291+ if ( !$first ) {
 1292+ $retVal .= ' ';
 1293+ } else {
 1294+ $first = false;
 1295+ }
 1296+
 1297+ if ( wfIsWindows() ) {
 1298+ // Escaping for an MSVC-style command line parser
 1299+ // Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
 1300+ // Double the backslashes before any double quotes. Escape the double quotes.
 1301+ $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
 1302+ $arg = '';
 1303+ $delim = false;
 1304+ foreach ( $tokens as $token ) {
 1305+ if ( $delim ) {
 1306+ $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
 1307+ } else {
 1308+ $arg .= $token;
 1309+ }
 1310+ $delim = !$delim;
 1311+ }
 1312+ // Double the backslashes before the end of the string, because
 1313+ // we will soon add a quote
 1314+ $m = array();
 1315+ if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
 1316+ $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
 1317+ }
 1318+
 1319+ // Add surrounding quotes
 1320+ $retVal .= '"' . $arg . '"';
 1321+ } else {
 1322+ $retVal .= escapeshellarg( $arg );
 1323+ }
 1324+ }
 1325+ return $retVal;
 1326+}
 1327+
 1328+/**
 1329+ * wfMerge attempts to merge differences between three texts.
 1330+ * Returns true for a clean merge and false for failure or a conflict.
 1331+ */
 1332+function wfMerge( $old, $mine, $yours, &$result ){
 1333+ global $wgDiff3;
 1334+
 1335+ # This check may also protect against code injection in
 1336+ # case of broken installations.
 1337+ if( !$wgDiff3 || !file_exists( $wgDiff3 ) ) {
 1338+ wfDebug( "diff3 not found\n" );
 1339+ return false;
 1340+ }
 1341+
 1342+ # Make temporary files
 1343+ $td = wfTempDir();
 1344+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
 1345+ $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
 1346+ $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
 1347+
 1348+ fwrite( $oldtextFile, $old ); fclose( $oldtextFile );
 1349+ fwrite( $mytextFile, $mine ); fclose( $mytextFile );
 1350+ fwrite( $yourtextFile, $yours ); fclose( $yourtextFile );
 1351+
 1352+ # Check for a conflict
 1353+ $cmd = $wgDiff3 . ' -a --overlap-only ' .
 1354+ wfEscapeShellArg( $mytextName ) . ' ' .
 1355+ wfEscapeShellArg( $oldtextName ) . ' ' .
 1356+ wfEscapeShellArg( $yourtextName );
 1357+ $handle = popen( $cmd, 'r' );
 1358+
 1359+ if( fgets( $handle, 1024 ) ){
 1360+ $conflict = true;
 1361+ } else {
 1362+ $conflict = false;
 1363+ }
 1364+ pclose( $handle );
 1365+
 1366+ # Merge differences
 1367+ $cmd = $wgDiff3 . ' -a -e --merge ' .
 1368+ wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
 1369+ $handle = popen( $cmd, 'r' );
 1370+ $result = '';
 1371+ do {
 1372+ $data = fread( $handle, 8192 );
 1373+ if ( strlen( $data ) == 0 ) {
 1374+ break;
 1375+ }
 1376+ $result .= $data;
 1377+ } while ( true );
 1378+ pclose( $handle );
 1379+ unlink( $mytextName ); unlink( $oldtextName ); unlink( $yourtextName );
 1380+
 1381+ if ( $result === '' && $old !== '' && $conflict == false ) {
 1382+ wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
 1383+ $conflict = true;
 1384+ }
 1385+ return ! $conflict;
 1386+}
 1387+
 1388+/**
 1389+ * Returns unified plain-text diff of two texts.
 1390+ * Useful for machine processing of diffs.
 1391+ * @param $before string The text before the changes.
 1392+ * @param $after string The text after the changes.
 1393+ * @param $params string Command-line options for the diff command.
 1394+ * @return string Unified diff of $before and $after
 1395+ */
 1396+function wfDiff( $before, $after, $params = '-u' ) {
 1397+ global $wgDiff;
 1398+
 1399+ # This check may also protect against code injection in
 1400+ # case of broken installations.
 1401+ if( !file_exists( $wgDiff ) ){
 1402+ wfDebug( "diff executable not found\n" );
 1403+ $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
 1404+ $format = new UnifiedDiffFormatter();
 1405+ return $format->format( $diffs );
 1406+ }
 1407+
 1408+ # Make temporary files
 1409+ $td = wfTempDir();
 1410+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
 1411+ $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
 1412+
 1413+ fwrite( $oldtextFile, $before ); fclose( $oldtextFile );
 1414+ fwrite( $newtextFile, $after ); fclose( $newtextFile );
 1415+
 1416+ // Get the diff of the two files
 1417+ $cmd = "$wgDiff " . $params . ' ' .wfEscapeShellArg( $oldtextName, $newtextName );
 1418+
 1419+ $h = popen( $cmd, 'r' );
 1420+
 1421+ $diff = '';
 1422+
 1423+ do {
 1424+ $data = fread( $h, 8192 );
 1425+ if ( strlen( $data ) == 0 ) {
 1426+ break;
 1427+ }
 1428+ $diff .= $data;
 1429+ } while ( true );
 1430+
 1431+ // Clean up
 1432+ pclose( $h );
 1433+ unlink( $oldtextName );
 1434+ unlink( $newtextName );
 1435+
 1436+ // Kill the --- and +++ lines. They're not useful.
 1437+ $diff_lines = explode( "\n", $diff );
 1438+ if (strpos( $diff_lines[0], '---' ) === 0) {
 1439+ unset($diff_lines[0]);
 1440+ }
 1441+ if (strpos( $diff_lines[1], '+++' ) === 0) {
 1442+ unset($diff_lines[1]);
 1443+ }
 1444+
 1445+ $diff = implode( "\n", $diff_lines );
 1446+
 1447+ return $diff;
 1448+}
 1449+
 1450+/**
 1451+ * A wrapper around the PHP function var_export().
 1452+ * Either print it or add it to the regular output ($wgOut).
 1453+ *
 1454+ * @param $var A PHP variable to dump.
 1455+ */
 1456+function wfVarDump( $var ) {
 1457+ global $wgOut;
 1458+ $s = str_replace("\n","<br />\n", var_export( $var, true ) . "\n");
 1459+ if ( headers_sent() || !@is_object( $wgOut ) ) {
 1460+ print $s;
 1461+ } else {
 1462+ $wgOut->addHTML( $s );
 1463+ }
 1464+}
 1465+
 1466+/**
 1467+ * Provide a simple HTTP error.
 1468+ */
 1469+function wfHttpError( $code, $label, $desc ) {
 1470+ global $wgOut;
 1471+ $wgOut->disable();
 1472+ header( "HTTP/1.0 $code $label" );
 1473+ header( "Status: $code $label" );
 1474+ $wgOut->sendCacheControl();
 1475+
 1476+ header( 'Content-type: text/html; charset=utf-8' );
 1477+ print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
 1478+ "<html><head><title>" .
 1479+ htmlspecialchars( $label ) .
 1480+ "</title></head><body><h1>" .
 1481+ htmlspecialchars( $label ) .
 1482+ "</h1><p>" .
 1483+ nl2br( htmlspecialchars( $desc ) ) .
 1484+ "</p></body></html>\n";
 1485+}
 1486+
 1487+/**
 1488+ * Clear away any user-level output buffers, discarding contents.
 1489+ *
 1490+ * Suitable for 'starting afresh', for instance when streaming
 1491+ * relatively large amounts of data without buffering, or wanting to
 1492+ * output image files without ob_gzhandler's compression.
 1493+ *
 1494+ * The optional $resetGzipEncoding parameter controls suppression of
 1495+ * the Content-Encoding header sent by ob_gzhandler; by default it
 1496+ * is left. See comments for wfClearOutputBuffers() for why it would
 1497+ * be used.
 1498+ *
 1499+ * Note that some PHP configuration options may add output buffer
 1500+ * layers which cannot be removed; these are left in place.
 1501+ *
 1502+ * @param bool $resetGzipEncoding
 1503+ */
 1504+function wfResetOutputBuffers( $resetGzipEncoding=true ) {
 1505+ if( $resetGzipEncoding ) {
 1506+ // Suppress Content-Encoding and Content-Length
 1507+ // headers from 1.10+s wfOutputHandler
 1508+ global $wgDisableOutputCompression;
 1509+ $wgDisableOutputCompression = true;
 1510+ }
 1511+ while( $status = ob_get_status() ) {
 1512+ if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
 1513+ // Probably from zlib.output_compression or other
 1514+ // PHP-internal setting which can't be removed.
 1515+ //
 1516+ // Give up, and hope the result doesn't break
 1517+ // output behavior.
 1518+ break;
 1519+ }
 1520+ if( !ob_end_clean() ) {
 1521+ // Could not remove output buffer handler; abort now
 1522+ // to avoid getting in some kind of infinite loop.
 1523+ break;
 1524+ }
 1525+ if( $resetGzipEncoding ) {
 1526+ if( $status['name'] == 'ob_gzhandler' ) {
 1527+ // Reset the 'Content-Encoding' field set by this handler
 1528+ // so we can start fresh.
 1529+ header( 'Content-Encoding:' );
 1530+ break;
 1531+ }
 1532+ }
 1533+ }
 1534+}
 1535+
 1536+/**
 1537+ * More legible than passing a 'false' parameter to wfResetOutputBuffers():
 1538+ *
 1539+ * Clear away output buffers, but keep the Content-Encoding header
 1540+ * produced by ob_gzhandler, if any.
 1541+ *
 1542+ * This should be used for HTTP 304 responses, where you need to
 1543+ * preserve the Content-Encoding header of the real result, but
 1544+ * also need to suppress the output of ob_gzhandler to keep to spec
 1545+ * and avoid breaking Firefox in rare cases where the headers and
 1546+ * body are broken over two packets.
 1547+ */
 1548+function wfClearOutputBuffers() {
 1549+ wfResetOutputBuffers( false );
 1550+}
 1551+
 1552+/**
 1553+ * Converts an Accept-* header into an array mapping string values to quality
 1554+ * factors
 1555+ */
 1556+function wfAcceptToPrefs( $accept, $def = '*/*' ) {
 1557+ # No arg means accept anything (per HTTP spec)
 1558+ if( !$accept ) {
 1559+ return array( $def => 1.0 );
 1560+ }
 1561+
 1562+ $prefs = array();
 1563+
 1564+ $parts = explode( ',', $accept );
 1565+
 1566+ foreach( $parts as $part ) {
 1567+ # FIXME: doesn't deal with params like 'text/html; level=1'
 1568+ @list( $value, $qpart ) = explode( ';', trim( $part ) );
 1569+ $match = array();
 1570+ if( !isset( $qpart ) ) {
 1571+ $prefs[$value] = 1.0;
 1572+ } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
 1573+ $prefs[$value] = floatval($match[1]);
 1574+ }
 1575+ }
 1576+
 1577+ return $prefs;
 1578+}
 1579+
 1580+/**
 1581+ * Checks if a given MIME type matches any of the keys in the given
 1582+ * array. Basic wildcards are accepted in the array keys.
 1583+ *
 1584+ * Returns the matching MIME type (or wildcard) if a match, otherwise
 1585+ * NULL if no match.
 1586+ *
 1587+ * @param string $type
 1588+ * @param array $avail
 1589+ * @return string
 1590+ * @private
 1591+ */
 1592+function mimeTypeMatch( $type, $avail ) {
 1593+ if( array_key_exists($type, $avail) ) {
 1594+ return $type;
 1595+ } else {
 1596+ $parts = explode( '/', $type );
 1597+ if( array_key_exists( $parts[0] . '/*', $avail ) ) {
 1598+ return $parts[0] . '/*';
 1599+ } elseif( array_key_exists( '*/*', $avail ) ) {
 1600+ return '*/*';
 1601+ } else {
 1602+ return NULL;
 1603+ }
 1604+ }
 1605+}
 1606+
 1607+/**
 1608+ * Returns the 'best' match between a client's requested internet media types
 1609+ * and the server's list of available types. Each list should be an associative
 1610+ * array of type to preference (preference is a float between 0.0 and 1.0).
 1611+ * Wildcards in the types are acceptable.
 1612+ *
 1613+ * @param array $cprefs Client's acceptable type list
 1614+ * @param array $sprefs Server's offered types
 1615+ * @return string
 1616+ *
 1617+ * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
 1618+ * XXX: generalize to negotiate other stuff
 1619+ */
 1620+function wfNegotiateType( $cprefs, $sprefs ) {
 1621+ $combine = array();
 1622+
 1623+ foreach( array_keys($sprefs) as $type ) {
 1624+ $parts = explode( '/', $type );
 1625+ if( $parts[1] != '*' ) {
 1626+ $ckey = mimeTypeMatch( $type, $cprefs );
 1627+ if( $ckey ) {
 1628+ $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
 1629+ }
 1630+ }
 1631+ }
 1632+
 1633+ foreach( array_keys( $cprefs ) as $type ) {
 1634+ $parts = explode( '/', $type );
 1635+ if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
 1636+ $skey = mimeTypeMatch( $type, $sprefs );
 1637+ if( $skey ) {
 1638+ $combine[$type] = $sprefs[$skey] * $cprefs[$type];
 1639+ }
 1640+ }
 1641+ }
 1642+
 1643+ $bestq = 0;
 1644+ $besttype = NULL;
 1645+
 1646+ foreach( array_keys( $combine ) as $type ) {
 1647+ if( $combine[$type] > $bestq ) {
 1648+ $besttype = $type;
 1649+ $bestq = $combine[$type];
 1650+ }
 1651+ }
 1652+
 1653+ return $besttype;
 1654+}
 1655+
 1656+/**
 1657+ * Array lookup
 1658+ * Returns an array where the values in the first array are replaced by the
 1659+ * values in the second array with the corresponding keys
 1660+ *
 1661+ * @return array
 1662+ */
 1663+function wfArrayLookup( $a, $b ) {
 1664+ return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
 1665+}
 1666+
 1667+/**
 1668+ * Convenience function; returns MediaWiki timestamp for the present time.
 1669+ * @return string
 1670+ */
 1671+function wfTimestampNow() {
 1672+ # return NOW
 1673+ return wfTimestamp( TS_MW, time() );
 1674+}
 1675+
 1676+/**
 1677+ * Reference-counted warning suppression
 1678+ */
 1679+function wfSuppressWarnings( $end = false ) {
 1680+ static $suppressCount = 0;
 1681+ static $originalLevel = false;
 1682+
 1683+ if ( $end ) {
 1684+ if ( $suppressCount ) {
 1685+ --$suppressCount;
 1686+ if ( !$suppressCount ) {
 1687+ error_reporting( $originalLevel );
 1688+ }
 1689+ }
 1690+ } else {
 1691+ if ( !$suppressCount ) {
 1692+ $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE ) );
 1693+ }
 1694+ ++$suppressCount;
 1695+ }
 1696+}
 1697+
 1698+/**
 1699+ * Restore error level to previous value
 1700+ */
 1701+function wfRestoreWarnings() {
 1702+ wfSuppressWarnings( true );
 1703+}
 1704+
 1705+# Autodetect, convert and provide timestamps of various types
 1706+
 1707+/**
 1708+ * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
 1709+ */
 1710+define('TS_UNIX', 0);
 1711+
 1712+/**
 1713+ * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
 1714+ */
 1715+define('TS_MW', 1);
 1716+
 1717+/**
 1718+ * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
 1719+ */
 1720+define('TS_DB', 2);
 1721+
 1722+/**
 1723+ * RFC 2822 format, for E-mail and HTTP headers
 1724+ */
 1725+define('TS_RFC2822', 3);
 1726+
 1727+/**
 1728+ * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
 1729+ *
 1730+ * This is used by Special:Export
 1731+ */
 1732+define('TS_ISO_8601', 4);
 1733+
 1734+/**
 1735+ * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
 1736+ *
 1737+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
 1738+ * DateTime tag and page 36 for the DateTimeOriginal and
 1739+ * DateTimeDigitized tags.
 1740+ */
 1741+define('TS_EXIF', 5);
 1742+
 1743+/**
 1744+ * Oracle format time.
 1745+ */
 1746+define('TS_ORACLE', 6);
 1747+
 1748+/**
 1749+ * Postgres format time.
 1750+ */
 1751+define('TS_POSTGRES', 7);
 1752+
 1753+/**
 1754+ * DB2 format time
 1755+ */
 1756+define('TS_DB2', 8);
 1757+
 1758+/**
 1759+ * @param mixed $outputtype A timestamp in one of the supported formats, the
 1760+ * function will autodetect which format is supplied
 1761+ * and act accordingly.
 1762+ * @return string Time in the format specified in $outputtype
 1763+ */
 1764+function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
 1765+ $uts = 0;
 1766+ $da = array();
 1767+ if ($ts==0) {
 1768+ $uts=time();
 1769+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
 1770+ # TS_DB
 1771+ } elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) {
 1772+ # TS_EXIF
 1773+ } elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) {
 1774+ # TS_MW
 1775+ } elseif (preg_match('/^\d{1,13}$/D',$ts)) {
 1776+ # TS_UNIX
 1777+ $uts = $ts;
 1778+ } elseif (preg_match('/^\d{1,2}-...-\d\d(?:\d\d)? \d\d\.\d\d\.\d\d/', $ts)) {
 1779+ # TS_ORACLE
 1780+ $uts = strtotime(preg_replace('/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
 1781+ str_replace("+00:00", "UTC", $ts)));
 1782+ } elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da)) {
 1783+ # TS_ISO_8601
 1784+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',$ts,$da)) {
 1785+ # TS_POSTGRES
 1786+ } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',$ts,$da)) {
 1787+ # TS_POSTGRES
 1788+ } else {
 1789+ # Bogus value; fall back to the epoch...
 1790+ wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
 1791+ $uts = 0;
 1792+ }
 1793+
 1794+ if (count( $da ) ) {
 1795+ // Warning! gmmktime() acts oddly if the month or day is set to 0
 1796+ // We may want to handle that explicitly at some point
 1797+ $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
 1798+ (int)$da[2],(int)$da[3],(int)$da[1]);
 1799+ }
 1800+
 1801+ switch($outputtype) {
 1802+ case TS_UNIX:
 1803+ return $uts;
 1804+ case TS_MW:
 1805+ return gmdate( 'YmdHis', $uts );
 1806+ case TS_DB:
 1807+ return gmdate( 'Y-m-d H:i:s', $uts );
 1808+ case TS_ISO_8601:
 1809+ return gmdate( 'Y-m-d\TH:i:s\Z', $uts );
 1810+ // This shouldn't ever be used, but is included for completeness
 1811+ case TS_EXIF:
 1812+ return gmdate( 'Y:m:d H:i:s', $uts );
 1813+ case TS_RFC2822:
 1814+ return gmdate( 'D, d M Y H:i:s', $uts ) . ' GMT';
 1815+ case TS_ORACLE:
 1816+ return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
 1817+ case TS_POSTGRES:
 1818+ return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
 1819+ case TS_DB2:
 1820+ return gmdate( 'Y-m-d H:i:s', $uts);
 1821+ default:
 1822+ throw new MWException( 'wfTimestamp() called with illegal output type.');
 1823+ }
 1824+}
 1825+
 1826+/**
 1827+ * Return a formatted timestamp, or null if input is null.
 1828+ * For dealing with nullable timestamp columns in the database.
 1829+ * @param int $outputtype
 1830+ * @param string $ts
 1831+ * @return string
 1832+ */
 1833+function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
 1834+ if( is_null( $ts ) ) {
 1835+ return null;
 1836+ } else {
 1837+ return wfTimestamp( $outputtype, $ts );
 1838+ }
 1839+}
 1840+
 1841+/**
 1842+ * Check if the operating system is Windows
 1843+ *
 1844+ * @return bool True if it's Windows, False otherwise.
 1845+ */
 1846+function wfIsWindows() {
 1847+ if (substr(php_uname(), 0, 7) == 'Windows') {
 1848+ return true;
 1849+ } else {
 1850+ return false;
 1851+ }
 1852+}
 1853+
 1854+/**
 1855+ * Swap two variables
 1856+ */
 1857+function swap( &$x, &$y ) {
 1858+ $z = $x;
 1859+ $x = $y;
 1860+ $y = $z;
 1861+}
 1862+
 1863+function wfGetCachedNotice( $name ) {
 1864+ global $wgOut, $wgRenderHashAppend, $parserMemc;
 1865+ $fname = 'wfGetCachedNotice';
 1866+ wfProfileIn( $fname );
 1867+
 1868+ $needParse = false;
 1869+
 1870+ if( $name === 'default' ) {
 1871+ // special case
 1872+ global $wgSiteNotice;
 1873+ $notice = $wgSiteNotice;
 1874+ if( empty( $notice ) ) {
 1875+ wfProfileOut( $fname );
 1876+ return false;
 1877+ }
 1878+ } else {
 1879+ $notice = wfMsgForContentNoTrans( $name );
 1880+ if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
 1881+ wfProfileOut( $fname );
 1882+ return( false );
 1883+ }
 1884+ }
 1885+
 1886+ // Use the extra hash appender to let eg SSL variants separately cache.
 1887+ $key = wfMemcKey( $name . $wgRenderHashAppend );
 1888+ $cachedNotice = $parserMemc->get( $key );
 1889+ if( is_array( $cachedNotice ) ) {
 1890+ if( md5( $notice ) == $cachedNotice['hash'] ) {
 1891+ $notice = $cachedNotice['html'];
 1892+ } else {
 1893+ $needParse = true;
 1894+ }
 1895+ } else {
 1896+ $needParse = true;
 1897+ }
 1898+
 1899+ if( $needParse ) {
 1900+ if( is_object( $wgOut ) ) {
 1901+ $parsed = $wgOut->parse( $notice );
 1902+ $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
 1903+ $notice = $parsed;
 1904+ } else {
 1905+ wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available'."\n" );
 1906+ $notice = '';
 1907+ }
 1908+ }
 1909+
 1910+ wfProfileOut( $fname );
 1911+ return $notice;
 1912+}
 1913+
 1914+function wfGetNamespaceNotice() {
 1915+ global $wgTitle;
 1916+
 1917+ # Paranoia
 1918+ if ( !isset( $wgTitle ) || !is_object( $wgTitle ) )
 1919+ return "";
 1920+
 1921+ $fname = 'wfGetNamespaceNotice';
 1922+ wfProfileIn( $fname );
 1923+
 1924+ $key = "namespacenotice-" . $wgTitle->getNsText();
 1925+ $namespaceNotice = wfGetCachedNotice( $key );
 1926+ if ( $namespaceNotice && substr ( $namespaceNotice , 0 ,7 ) != "<p>&lt;" ) {
 1927+ $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . "</div>";
 1928+ } else {
 1929+ $namespaceNotice = "";
 1930+ }
 1931+
 1932+ wfProfileOut( $fname );
 1933+ return $namespaceNotice;
 1934+}
 1935+
 1936+function wfGetSiteNotice() {
 1937+ global $wgUser, $wgSiteNotice;
 1938+ $fname = 'wfGetSiteNotice';
 1939+ wfProfileIn( $fname );
 1940+ $siteNotice = '';
 1941+
 1942+ if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
 1943+ if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
 1944+ $siteNotice = wfGetCachedNotice( 'sitenotice' );
 1945+ } else {
 1946+ $anonNotice = wfGetCachedNotice( 'anonnotice' );
 1947+ if( !$anonNotice ) {
 1948+ $siteNotice = wfGetCachedNotice( 'sitenotice' );
 1949+ } else {
 1950+ $siteNotice = $anonNotice;
 1951+ }
 1952+ }
 1953+ if( !$siteNotice ) {
 1954+ $siteNotice = wfGetCachedNotice( 'default' );
 1955+ }
 1956+ }
 1957+
 1958+ wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
 1959+ wfProfileOut( $fname );
 1960+ return $siteNotice;
 1961+}
 1962+
 1963+/**
 1964+ * BC wrapper for MimeMagic::singleton()
 1965+ * @deprecated
 1966+ */
 1967+function &wfGetMimeMagic() {
 1968+ return MimeMagic::singleton();
 1969+}
 1970+
 1971+/**
 1972+ * Tries to get the system directory for temporary files.
 1973+ * The TMPDIR, TMP, and TEMP environment variables are checked in sequence,
 1974+ * and if none are set /tmp is returned as the generic Unix default.
 1975+ *
 1976+ * NOTE: When possible, use the tempfile() function to create temporary
 1977+ * files to avoid race conditions on file creation, etc.
 1978+ *
 1979+ * @return string
 1980+ */
 1981+function wfTempDir() {
 1982+ foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
 1983+ $tmp = getenv( $var );
 1984+ if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
 1985+ return $tmp;
 1986+ }
 1987+ }
 1988+ # Hope this is Unix of some kind!
 1989+ return '/tmp';
 1990+}
 1991+
 1992+/**
 1993+ * Make directory, and make all parent directories if they don't exist
 1994+ *
 1995+ * @param string $dir Full path to directory to create
 1996+ * @param int $mode Chmod value to use, default is $wgDirectoryMode
 1997+ * @param string $caller Optional caller param for debugging.
 1998+ * @return bool
 1999+ */
 2000+function wfMkdirParents( $dir, $mode = null, $caller = null ) {
 2001+ global $wgDirectoryMode;
 2002+
 2003+ if ( !is_null( $caller ) ) {
 2004+ wfDebug( "$caller: called wfMkdirParents($dir)" );
 2005+ }
 2006+
 2007+ if( strval( $dir ) === '' || file_exists( $dir ) )
 2008+ return true;
 2009+
 2010+ if ( is_null( $mode ) )
 2011+ $mode = $wgDirectoryMode;
 2012+
 2013+ return mkdir( $dir, $mode, true ); // PHP5 <3
 2014+}
 2015+
 2016+/**
 2017+ * Increment a statistics counter
 2018+ */
 2019+function wfIncrStats( $key ) {
 2020+ global $wgStatsMethod;
 2021+
 2022+ if( $wgStatsMethod == 'udp' ) {
 2023+ global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname;
 2024+ static $socket;
 2025+ if (!$socket) {
 2026+ $socket=socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
 2027+ $statline="stats/{$wgDBname} - 1 1 1 1 1 -total\n";
 2028+ socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
 2029+ }
 2030+ $statline="stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
 2031+ @socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort);
 2032+ } elseif( $wgStatsMethod == 'cache' ) {
 2033+ global $wgMemc;
 2034+ $key = wfMemcKey( 'stats', $key );
 2035+ if ( is_null( $wgMemc->incr( $key ) ) ) {
 2036+ $wgMemc->add( $key, 1 );
 2037+ }
 2038+ } else {
 2039+ // Disabled
 2040+ }
 2041+}
 2042+
 2043+/**
 2044+ * @param mixed $nr The number to format
 2045+ * @param int $acc The number of digits after the decimal point, default 2
 2046+ * @param bool $round Whether or not to round the value, default true
 2047+ * @return float
 2048+ */
 2049+function wfPercent( $nr, $acc = 2, $round = true ) {
 2050+ $ret = sprintf( "%.${acc}f", $nr );
 2051+ return $round ? round( $ret, $acc ) . '%' : "$ret%";
 2052+}
 2053+
 2054+/**
 2055+ * Encrypt a username/password.
 2056+ *
 2057+ * @param string $userid ID of the user
 2058+ * @param string $password Password of the user
 2059+ * @return string Hashed password
 2060+ * @deprecated Use User::crypt() or User::oldCrypt() instead
 2061+ */
 2062+function wfEncryptPassword( $userid, $password ) {
 2063+ wfDeprecated(__FUNCTION__);
 2064+ # Just wrap around User::oldCrypt()
 2065+ return User::oldCrypt($password, $userid);
 2066+}
 2067+
 2068+/**
 2069+ * Appends to second array if $value differs from that in $default
 2070+ */
 2071+function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
 2072+ if ( is_null( $changed ) ) {
 2073+ throw new MWException('GlobalFunctions::wfAppendToArrayIfNotDefault got null');
 2074+ }
 2075+ if ( $default[$key] !== $value ) {
 2076+ $changed[$key] = $value;
 2077+ }
 2078+}
 2079+
 2080+/**
 2081+ * Since wfMsg() and co suck, they don't return false if the message key they
 2082+ * looked up didn't exist but a XHTML string, this function checks for the
 2083+ * nonexistance of messages by looking at wfMsg() output
 2084+ *
 2085+ * @param $msg The message key looked up
 2086+ * @param $wfMsgOut The output of wfMsg*()
 2087+ * @return bool
 2088+ */
 2089+function wfEmptyMsg( $msg, $wfMsgOut ) {
 2090+ return $wfMsgOut === htmlspecialchars( "<$msg>" );
 2091+}
 2092+
 2093+/**
 2094+ * Find out whether or not a mixed variable exists in a string
 2095+ *
 2096+ * @param mixed needle
 2097+ * @param string haystack
 2098+ * @return bool
 2099+ */
 2100+function in_string( $needle, $str ) {
 2101+ return strpos( $str, $needle ) !== false;
 2102+}
 2103+
 2104+function wfSpecialList( $page, $details ) {
 2105+ global $wgContLang;
 2106+ $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : "";
 2107+ return $page . $details;
 2108+}
 2109+
 2110+/**
 2111+ * Returns a regular expression of url protocols
 2112+ *
 2113+ * @return string
 2114+ */
 2115+function wfUrlProtocols() {
 2116+ global $wgUrlProtocols;
 2117+
 2118+ // Support old-style $wgUrlProtocols strings, for backwards compatibility
 2119+ // with LocalSettings files from 1.5
 2120+ if ( is_array( $wgUrlProtocols ) ) {
 2121+ $protocols = array();
 2122+ foreach ($wgUrlProtocols as $protocol)
 2123+ $protocols[] = preg_quote( $protocol, '/' );
 2124+
 2125+ return implode( '|', $protocols );
 2126+ } else {
 2127+ return $wgUrlProtocols;
 2128+ }
 2129+}
 2130+
 2131+/**
 2132+ * Safety wrapper around ini_get() for boolean settings.
 2133+ * The values returned from ini_get() are pre-normalized for settings
 2134+ * set via php.ini or php_flag/php_admin_flag... but *not*
 2135+ * for those set via php_value/php_admin_value.
 2136+ *
 2137+ * It's fairly common for people to use php_value instead of php_flag,
 2138+ * which can leave you with an 'off' setting giving a false positive
 2139+ * for code that just takes the ini_get() return value as a boolean.
 2140+ *
 2141+ * To make things extra interesting, setting via php_value accepts
 2142+ * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
 2143+ * Unrecognized values go false... again opposite PHP's own coercion
 2144+ * from string to bool.
 2145+ *
 2146+ * Luckily, 'properly' set settings will always come back as '0' or '1',
 2147+ * so we only have to worry about them and the 'improper' settings.
 2148+ *
 2149+ * I frickin' hate PHP... :P
 2150+ *
 2151+ * @param string $setting
 2152+ * @return bool
 2153+ */
 2154+function wfIniGetBool( $setting ) {
 2155+ $val = ini_get( $setting );
 2156+ // 'on' and 'true' can't have whitespace around them, but '1' can.
 2157+ return strtolower( $val ) == 'on'
 2158+ || strtolower( $val ) == 'true'
 2159+ || strtolower( $val ) == 'yes'
 2160+ || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
 2161+}
 2162+
 2163+/**
 2164+ * Execute a shell command, with time and memory limits mirrored from the PHP
 2165+ * configuration if supported.
 2166+ * @param $cmd Command line, properly escaped for shell.
 2167+ * @param &$retval optional, will receive the program's exit code.
 2168+ * (non-zero is usually failure)
 2169+ * @return collected stdout as a string (trailing newlines stripped)
 2170+ */
 2171+function wfShellExec( $cmd, &$retval=null ) {
 2172+ global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
 2173+
 2174+ static $disabled;
 2175+ if ( is_null( $disabled ) ) {
 2176+ $disabled = false;
 2177+ if( wfIniGetBool( 'safe_mode' ) ) {
 2178+ wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
 2179+ $disabled = true;
 2180+ }
 2181+ $functions = explode( ',', ini_get( 'disable_functions' ) );
 2182+ $functions = array_map( 'trim', $functions );
 2183+ $functions = array_map( 'strtolower', $functions );
 2184+ if ( in_array( 'passthru', $functions ) ) {
 2185+ wfDebug( "passthru is in disabled_functions\n" );
 2186+ $disabled = true;
 2187+ }
 2188+ }
 2189+ if ( $disabled ) {
 2190+ $retval = 1;
 2191+ return "Unable to run external programs in safe mode.";
 2192+ }
 2193+
 2194+ wfInitShellLocale();
 2195+
 2196+ if ( php_uname( 's' ) == 'Linux' ) {
 2197+ $time = intval( $wgMaxShellTime );
 2198+ $mem = intval( $wgMaxShellMemory );
 2199+ $filesize = intval( $wgMaxShellFileSize );
 2200+
 2201+ if ( $time > 0 && $mem > 0 ) {
 2202+ $script = "$IP/bin/ulimit4.sh";
 2203+ if ( is_executable( $script ) ) {
 2204+ $cmd = escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
 2205+ }
 2206+ }
 2207+ } elseif ( php_uname( 's' ) == 'Windows NT' ) {
 2208+ # This is a hack to work around PHP's flawed invocation of cmd.exe
 2209+ # http://news.php.net/php.internals/21796
 2210+ $cmd = '"' . $cmd . '"';
 2211+ }
 2212+ wfDebug( "wfShellExec: $cmd\n" );
 2213+
 2214+ $retval = 1; // error by default?
 2215+ ob_start();
 2216+ passthru( $cmd, $retval );
 2217+ $output = ob_get_contents();
 2218+ ob_end_clean();
 2219+
 2220+ if ( $retval == 127 ) {
 2221+ wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
 2222+ }
 2223+ return $output;
 2224+}
 2225+
 2226+/**
 2227+ * Workaround for http://bugs.php.net/bug.php?id=45132
 2228+ * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
 2229+ */
 2230+function wfInitShellLocale() {
 2231+ static $done = false;
 2232+ if ( $done ) return;
 2233+ $done = true;
 2234+ global $wgShellLocale;
 2235+ if ( !wfIniGetBool( 'safe_mode' ) ) {
 2236+ putenv( "LC_CTYPE=$wgShellLocale" );
 2237+ setlocale( LC_CTYPE, $wgShellLocale );
 2238+ }
 2239+}
 2240+
 2241+/**
 2242+ * This function works like "use VERSION" in Perl, the program will die with a
 2243+ * backtrace if the current version of PHP is less than the version provided
 2244+ *
 2245+ * This is useful for extensions which due to their nature are not kept in sync
 2246+ * with releases, and might depend on other versions of PHP than the main code
 2247+ *
 2248+ * Note: PHP might die due to parsing errors in some cases before it ever
 2249+ * manages to call this function, such is life
 2250+ *
 2251+ * @see perldoc -f use
 2252+ *
 2253+ * @param mixed $version The version to check, can be a string, an integer, or
 2254+ * a float
 2255+ */
 2256+function wfUsePHP( $req_ver ) {
 2257+ $php_ver = PHP_VERSION;
 2258+
 2259+ if ( version_compare( $php_ver, (string)$req_ver, '<' ) )
 2260+ throw new MWException( "PHP $req_ver required--this is only $php_ver" );
 2261+}
 2262+
 2263+/**
 2264+ * This function works like "use VERSION" in Perl except it checks the version
 2265+ * of MediaWiki, the program will die with a backtrace if the current version
 2266+ * of MediaWiki is less than the version provided.
 2267+ *
 2268+ * This is useful for extensions which due to their nature are not kept in sync
 2269+ * with releases
 2270+ *
 2271+ * @see perldoc -f use
 2272+ *
 2273+ * @param mixed $version The version to check, can be a string, an integer, or
 2274+ * a float
 2275+ */
 2276+function wfUseMW( $req_ver ) {
 2277+ global $wgVersion;
 2278+
 2279+ if ( version_compare( $wgVersion, (string)$req_ver, '<' ) )
 2280+ throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
 2281+}
 2282+
 2283+/**
 2284+ * @deprecated use StringUtils::escapeRegexReplacement
 2285+ */
 2286+function wfRegexReplacement( $string ) {
 2287+ return StringUtils::escapeRegexReplacement( $string );
 2288+}
 2289+
 2290+/**
 2291+ * Return the final portion of a pathname.
 2292+ * Reimplemented because PHP5's basename() is buggy with multibyte text.
 2293+ * http://bugs.php.net/bug.php?id=33898
 2294+ *
 2295+ * PHP's basename() only considers '\' a pathchar on Windows and Netware.
 2296+ * We'll consider it so always, as we don't want \s in our Unix paths either.
 2297+ *
 2298+ * @param string $path
 2299+ * @param string $suffix to remove if present
 2300+ * @return string
 2301+ */
 2302+function wfBaseName( $path, $suffix='' ) {
 2303+ $encSuffix = ($suffix == '')
 2304+ ? ''
 2305+ : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
 2306+ $matches = array();
 2307+ if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
 2308+ return $matches[1];
 2309+ } else {
 2310+ return '';
 2311+ }
 2312+}
 2313+
 2314+/**
 2315+ * Generate a relative path name to the given file.
 2316+ * May explode on non-matching case-insensitive paths,
 2317+ * funky symlinks, etc.
 2318+ *
 2319+ * @param string $path Absolute destination path including target filename
 2320+ * @param string $from Absolute source path, directory only
 2321+ * @return string
 2322+ */
 2323+function wfRelativePath( $path, $from ) {
 2324+ // Normalize mixed input on Windows...
 2325+ $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
 2326+ $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
 2327+
 2328+ // Trim trailing slashes -- fix for drive root
 2329+ $path = rtrim( $path, DIRECTORY_SEPARATOR );
 2330+ $from = rtrim( $from, DIRECTORY_SEPARATOR );
 2331+
 2332+ $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
 2333+ $against = explode( DIRECTORY_SEPARATOR, $from );
 2334+
 2335+ if( $pieces[0] !== $against[0] ) {
 2336+ // Non-matching Windows drive letters?
 2337+ // Return a full path.
 2338+ return $path;
 2339+ }
 2340+
 2341+ // Trim off common prefix
 2342+ while( count( $pieces ) && count( $against )
 2343+ && $pieces[0] == $against[0] ) {
 2344+ array_shift( $pieces );
 2345+ array_shift( $against );
 2346+ }
 2347+
 2348+ // relative dots to bump us to the parent
 2349+ while( count( $against ) ) {
 2350+ array_unshift( $pieces, '..' );
 2351+ array_shift( $against );
 2352+ }
 2353+
 2354+ array_push( $pieces, wfBaseName( $path ) );
 2355+
 2356+ return implode( DIRECTORY_SEPARATOR, $pieces );
 2357+}
 2358+
 2359+/**
 2360+ * Backwards array plus for people who haven't bothered to read the PHP manual
 2361+ * XXX: will not darn your socks for you.
 2362+ *
 2363+ * @param array $array1, [$array2, [...]]
 2364+ * @return array
 2365+ */
 2366+function wfArrayMerge( $array1/* ... */ ) {
 2367+ $args = func_get_args();
 2368+ $args = array_reverse( $args, true );
 2369+ $out = array();
 2370+ foreach ( $args as $arg ) {
 2371+ $out += $arg;
 2372+ }
 2373+ return $out;
 2374+}
 2375+
 2376+/**
 2377+ * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
 2378+ * e.g.
 2379+ * wfMergeErrorArrays(
 2380+ * array( array( 'x' ) ),
 2381+ * array( array( 'x', '2' ) ),
 2382+ * array( array( 'x' ) ),
 2383+ * array( array( 'y') )
 2384+ * );
 2385+ * returns:
 2386+ * array(
 2387+ * array( 'x', '2' ),
 2388+ * array( 'x' ),
 2389+ * array( 'y' )
 2390+ * )
 2391+ */
 2392+function wfMergeErrorArrays(/*...*/) {
 2393+ $args = func_get_args();
 2394+ $out = array();
 2395+ foreach ( $args as $errors ) {
 2396+ foreach ( $errors as $params ) {
 2397+ $spec = implode( "\t", $params );
 2398+ $out[$spec] = $params;
 2399+ }
 2400+ }
 2401+ return array_values( $out );
 2402+}
 2403+
 2404+/**
 2405+ * parse_url() work-alike, but non-broken. Differences:
 2406+ *
 2407+ * 1) Does not raise warnings on bad URLs (just returns false)
 2408+ * 2) Handles protocols that don't use :// (e.g., mailto: and news:) correctly
 2409+ * 3) Adds a "delimiter" element to the array, either '://' or ':' (see (2))
 2410+ *
 2411+ * @param string $url A URL to parse
 2412+ * @return array Bits of the URL in an associative array, per PHP docs
 2413+ */
 2414+function wfParseUrl( $url ) {
 2415+ global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
 2416+ wfSuppressWarnings();
 2417+ $bits = parse_url( $url );
 2418+ wfRestoreWarnings();
 2419+ if ( !$bits ) {
 2420+ return false;
 2421+ }
 2422+
 2423+ // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
 2424+ if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
 2425+ $bits['delimiter'] = '://';
 2426+ } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
 2427+ $bits['delimiter'] = ':';
 2428+ // parse_url detects for news: and mailto: the host part of an url as path
 2429+ // We have to correct this wrong detection
 2430+ if ( isset ( $bits['path'] ) ) {
 2431+ $bits['host'] = $bits['path'];
 2432+ $bits['path'] = '';
 2433+ }
 2434+ } else {
 2435+ return false;
 2436+ }
 2437+
 2438+ return $bits;
 2439+}
 2440+
 2441+/**
 2442+ * Make a URL index, appropriate for the el_index field of externallinks.
 2443+ */
 2444+function wfMakeUrlIndex( $url ) {
 2445+ $bits = wfParseUrl( $url );
 2446+
 2447+ // Reverse the labels in the hostname, convert to lower case
 2448+ // For emails reverse domainpart only
 2449+ if ( $bits['scheme'] == 'mailto' ) {
 2450+ $mailparts = explode( '@', $bits['host'], 2 );
 2451+ if ( count($mailparts) === 2 ) {
 2452+ $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
 2453+ } else {
 2454+ // No domain specified, don't mangle it
 2455+ $domainpart = '';
 2456+ }
 2457+ $reversedHost = $domainpart . '@' . $mailparts[0];
 2458+ } else {
 2459+ $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
 2460+ }
 2461+ // Add an extra dot to the end
 2462+ // Why? Is it in wrong place in mailto links?
 2463+ if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
 2464+ $reversedHost .= '.';
 2465+ }
 2466+ // Reconstruct the pseudo-URL
 2467+ $prot = $bits['scheme'];
 2468+ $index = $prot . $bits['delimiter'] . $reversedHost;
 2469+ // Leave out user and password. Add the port, path, query and fragment
 2470+ if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port'];
 2471+ if ( isset( $bits['path'] ) ) {
 2472+ $index .= $bits['path'];
 2473+ } else {
 2474+ $index .= '/';
 2475+ }
 2476+ if ( isset( $bits['query'] ) ) $index .= '?' . $bits['query'];
 2477+ if ( isset( $bits['fragment'] ) ) $index .= '#' . $bits['fragment'];
 2478+ return $index;
 2479+}
 2480+
 2481+/**
 2482+ * Do any deferred updates and clear the list
 2483+ * TODO: This could be in Wiki.php if that class made any sense at all
 2484+ */
 2485+function wfDoUpdates()
 2486+{
 2487+ global $wgPostCommitUpdateList, $wgDeferredUpdateList;
 2488+ foreach ( $wgDeferredUpdateList as $update ) {
 2489+ $update->doUpdate();
 2490+ }
 2491+ foreach ( $wgPostCommitUpdateList as $update ) {
 2492+ $update->doUpdate();
 2493+ }
 2494+ $wgDeferredUpdateList = array();
 2495+ $wgPostCommitUpdateList = array();
 2496+}
 2497+
 2498+/**
 2499+ * @deprecated use StringUtils::explodeMarkup
 2500+ */
 2501+function wfExplodeMarkup( $separator, $text ) {
 2502+ return StringUtils::explodeMarkup( $separator, $text );
 2503+}
 2504+
 2505+/**
 2506+ * Convert an arbitrarily-long digit string from one numeric base
 2507+ * to another, optionally zero-padding to a minimum column width.
 2508+ *
 2509+ * Supports base 2 through 36; digit values 10-36 are represented
 2510+ * as lowercase letters a-z. Input is case-insensitive.
 2511+ *
 2512+ * @param $input string of digits
 2513+ * @param $sourceBase int 2-36
 2514+ * @param $destBase int 2-36
 2515+ * @param $pad int 1 or greater
 2516+ * @param $lowercase bool
 2517+ * @return string or false on invalid input
 2518+ */
 2519+function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true ) {
 2520+ $input = strval( $input );
 2521+ if( $sourceBase < 2 ||
 2522+ $sourceBase > 36 ||
 2523+ $destBase < 2 ||
 2524+ $destBase > 36 ||
 2525+ $pad < 1 ||
 2526+ $sourceBase != intval( $sourceBase ) ||
 2527+ $destBase != intval( $destBase ) ||
 2528+ $pad != intval( $pad ) ||
 2529+ !is_string( $input ) ||
 2530+ $input == '' ) {
 2531+ return false;
 2532+ }
 2533+ $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 2534+ $inDigits = array();
 2535+ $outChars = '';
 2536+
 2537+ // Decode and validate input string
 2538+ $input = strtolower( $input );
 2539+ for( $i = 0; $i < strlen( $input ); $i++ ) {
 2540+ $n = strpos( $digitChars, $input{$i} );
 2541+ if( $n === false || $n > $sourceBase ) {
 2542+ return false;
 2543+ }
 2544+ $inDigits[] = $n;
 2545+ }
 2546+
 2547+ // Iterate over the input, modulo-ing out an output digit
 2548+ // at a time until input is gone.
 2549+ while( count( $inDigits ) ) {
 2550+ $work = 0;
 2551+ $workDigits = array();
 2552+
 2553+ // Long division...
 2554+ foreach( $inDigits as $digit ) {
 2555+ $work *= $sourceBase;
 2556+ $work += $digit;
 2557+
 2558+ if( $work < $destBase ) {
 2559+ // Gonna need to pull another digit.
 2560+ if( count( $workDigits ) ) {
 2561+ // Avoid zero-padding; this lets us find
 2562+ // the end of the input very easily when
 2563+ // length drops to zero.
 2564+ $workDigits[] = 0;
 2565+ }
 2566+ } else {
 2567+ // Finally! Actual division!
 2568+ $workDigits[] = intval( $work / $destBase );
 2569+
 2570+ // Isn't it annoying that most programming languages
 2571+ // don't have a single divide-and-remainder operator,
 2572+ // even though the CPU implements it that way?
 2573+ $work = $work % $destBase;
 2574+ }
 2575+ }
 2576+
 2577+ // All that division leaves us with a remainder,
 2578+ // which is conveniently our next output digit.
 2579+ $outChars .= $digitChars[$work];
 2580+
 2581+ // And we continue!
 2582+ $inDigits = $workDigits;
 2583+ }
 2584+
 2585+ while( strlen( $outChars ) < $pad ) {
 2586+ $outChars .= '0';
 2587+ }
 2588+
 2589+ return strrev( $outChars );
 2590+}
 2591+
 2592+/**
 2593+ * Create an object with a given name and an array of construct parameters
 2594+ * @param string $name
 2595+ * @param array $p parameters
 2596+ */
 2597+function wfCreateObject( $name, $p ){
 2598+ $p = array_values( $p );
 2599+ switch ( count( $p ) ) {
 2600+ case 0:
 2601+ return new $name;
 2602+ case 1:
 2603+ return new $name( $p[0] );
 2604+ case 2:
 2605+ return new $name( $p[0], $p[1] );
 2606+ case 3:
 2607+ return new $name( $p[0], $p[1], $p[2] );
 2608+ case 4:
 2609+ return new $name( $p[0], $p[1], $p[2], $p[3] );
 2610+ case 5:
 2611+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4] );
 2612+ case 6:
 2613+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4], $p[5] );
 2614+ default:
 2615+ throw new MWException( "Too many arguments to construtor in wfCreateObject" );
 2616+ }
 2617+}
 2618+
 2619+/**
 2620+ * Alias for modularized function
 2621+ * @deprecated Use Http::get() instead
 2622+ */
 2623+function wfGetHTTP( $url, $timeout = 'default' ) {
 2624+ wfDeprecated(__FUNCTION__);
 2625+ return Http::get( $url, $timeout );
 2626+}
 2627+
 2628+/**
 2629+ * Alias for modularized function
 2630+ * @deprecated Use Http::isLocalURL() instead
 2631+ */
 2632+function wfIsLocalURL( $url ) {
 2633+ wfDeprecated(__FUNCTION__);
 2634+ return Http::isLocalURL( $url );
 2635+}
 2636+
 2637+function wfHttpOnlySafe() {
 2638+ global $wgHttpOnlyBlacklist;
 2639+ if( !version_compare("5.2", PHP_VERSION, "<") )
 2640+ return false;
 2641+
 2642+ if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
 2643+ foreach( $wgHttpOnlyBlacklist as $regex ) {
 2644+ if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
 2645+ return false;
 2646+ }
 2647+ }
 2648+ }
 2649+
 2650+ return true;
 2651+}
 2652+
 2653+/**
 2654+ * Initialise php session
 2655+ */
 2656+function wfSetupSession() {
 2657+ global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly;
 2658+ if( $wgSessionsInMemcached ) {
 2659+ require_once( 'MemcachedSessions.php' );
 2660+ } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
 2661+ # If it's left on 'user' or another setting from another
 2662+ # application, it will end up failing. Try to recover.
 2663+ ini_set ( 'session.save_handler', 'files' );
 2664+ }
 2665+ $httpOnlySafe = wfHttpOnlySafe();
 2666+ wfDebugLog( 'cookie',
 2667+ 'session_set_cookie_params: "' . implode( '", "',
 2668+ array(
 2669+ 0,
 2670+ $wgCookiePath,
 2671+ $wgCookieDomain,
 2672+ $wgCookieSecure,
 2673+ $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' );
 2674+ if( $httpOnlySafe && $wgCookieHttpOnly ) {
 2675+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly );
 2676+ } else {
 2677+ // PHP 5.1 throws warnings if you pass the HttpOnly parameter for 5.2.
 2678+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
 2679+ }
 2680+ session_cache_limiter( 'private, must-revalidate' );
 2681+ wfSuppressWarnings();
 2682+ session_start();
 2683+ wfRestoreWarnings();
 2684+}
 2685+
 2686+/**
 2687+ * Get an object from the precompiled serialized directory
 2688+ *
 2689+ * @return mixed The variable on success, false on failure
 2690+ */
 2691+function wfGetPrecompiledData( $name ) {
 2692+ global $IP;
 2693+
 2694+ $file = "$IP/serialized/$name";
 2695+ if ( file_exists( $file ) ) {
 2696+ $blob = file_get_contents( $file );
 2697+ if ( $blob ) {
 2698+ return unserialize( $blob );
 2699+ }
 2700+ }
 2701+ return false;
 2702+}
 2703+
 2704+function wfGetCaller( $level = 2 ) {
 2705+ $backtrace = wfDebugBacktrace();
 2706+ if ( isset( $backtrace[$level] ) ) {
 2707+ return wfFormatStackFrame($backtrace[$level]);
 2708+ } else {
 2709+ $caller = 'unknown';
 2710+ }
 2711+ return $caller;
 2712+}
 2713+
 2714+/** Return a string consisting all callers in stack, somewhat useful sometimes for profiling specific points */
 2715+function wfGetAllCallers() {
 2716+ return implode('/', array_map('wfFormatStackFrame',array_reverse(wfDebugBacktrace())));
 2717+}
 2718+
 2719+/** Return a string representation of frame */
 2720+function wfFormatStackFrame($frame) {
 2721+ return isset( $frame["class"] )?
 2722+ $frame["class"]."::".$frame["function"]:
 2723+ $frame["function"];
 2724+}
 2725+
 2726+/**
 2727+ * Get a cache key
 2728+ */
 2729+function wfMemcKey( /*... */ ) {
 2730+ $args = func_get_args();
 2731+ $key = wfWikiID() . ':' . implode( ':', $args );
 2732+ return $key;
 2733+}
 2734+
 2735+/**
 2736+ * Get a cache key for a foreign DB
 2737+ */
 2738+function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
 2739+ $args = array_slice( func_get_args(), 2 );
 2740+ if ( $prefix ) {
 2741+ $key = "$db-$prefix:" . implode( ':', $args );
 2742+ } else {
 2743+ $key = $db . ':' . implode( ':', $args );
 2744+ }
 2745+ return $key;
 2746+}
 2747+
 2748+/**
 2749+ * Get an ASCII string identifying this wiki
 2750+ * This is used as a prefix in memcached keys
 2751+ */
 2752+function wfWikiID( $db = null ) {
 2753+ if( $db instanceof Database ) {
 2754+ return $db->getWikiID();
 2755+ } else {
 2756+ global $wgDBprefix, $wgDBname;
 2757+ if ( $wgDBprefix ) {
 2758+ return "$wgDBname-$wgDBprefix";
 2759+ } else {
 2760+ return $wgDBname;
 2761+ }
 2762+ }
 2763+}
 2764+
 2765+/**
 2766+ * Split a wiki ID into DB name and table prefix
 2767+ */
 2768+function wfSplitWikiID( $wiki ) {
 2769+ $bits = explode( '-', $wiki, 2 );
 2770+ if ( count( $bits ) < 2 ) {
 2771+ $bits[] = '';
 2772+ }
 2773+ return $bits;
 2774+}
 2775+
 2776+/*
 2777+ * Get a Database object.
 2778+ * @param integer $db Index of the connection to get. May be DB_MASTER for the
 2779+ * master (for write queries), DB_SLAVE for potentially lagged
 2780+ * read queries, or an integer >= 0 for a particular server.
 2781+ *
 2782+ * @param mixed $groups Query groups. An array of group names that this query
 2783+ * belongs to. May contain a single string if the query is only
 2784+ * in one group.
 2785+ *
 2786+ * @param string $wiki The wiki ID, or false for the current wiki
 2787+ *
 2788+ * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
 2789+ * will always return the same object, unless the underlying connection or load
 2790+ * balancer is manually destroyed.
 2791+ */
 2792+function &wfGetDB( $db, $groups = array(), $wiki = false ) {
 2793+ return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
 2794+}
 2795+
 2796+/**
 2797+ * Get a load balancer object.
 2798+ *
 2799+ * @param array $groups List of query groups
 2800+ * @param string $wiki Wiki ID, or false for the current wiki
 2801+ * @return LoadBalancer
 2802+ */
 2803+function wfGetLB( $wiki = false ) {
 2804+ return wfGetLBFactory()->getMainLB( $wiki );
 2805+}
 2806+
 2807+/**
 2808+ * Get the load balancer factory object
 2809+ */
 2810+function &wfGetLBFactory() {
 2811+ return LBFactory::singleton();
 2812+}
 2813+
 2814+/**
 2815+ * Find a file.
 2816+ * Shortcut for RepoGroup::singleton()->findFile()
 2817+ * @param mixed $title Title object or string. May be interwiki.
 2818+ * @param mixed $time Requested time for an archived image, or false for the
 2819+ * current version. An image object will be returned which
 2820+ * was created at the specified time.
 2821+ * @param mixed $flags FileRepo::FIND_ flags
 2822+ * @param boolean $bypass Bypass the file cache even if it could be used
 2823+ * @return File, or false if the file does not exist
 2824+ */
 2825+function wfFindFile( $title, $time = false, $flags = 0, $bypass = false ) {
 2826+ if( !$time && !$flags && !$bypass ) {
 2827+ return FileCache::singleton()->findFile( $title );
 2828+ } else {
 2829+ return RepoGroup::singleton()->findFile( $title, $time, $flags );
 2830+ }
 2831+}
 2832+
 2833+/**
 2834+ * Get an object referring to a locally registered file.
 2835+ * Returns a valid placeholder object if the file does not exist.
 2836+ */
 2837+function wfLocalFile( $title ) {
 2838+ return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
 2839+}
 2840+
 2841+/**
 2842+ * Should low-performance queries be disabled?
 2843+ *
 2844+ * @return bool
 2845+ */
 2846+function wfQueriesMustScale() {
 2847+ global $wgMiserMode;
 2848+ return $wgMiserMode
 2849+ || ( SiteStats::pages() > 100000
 2850+ && SiteStats::edits() > 1000000
 2851+ && SiteStats::users() > 10000 );
 2852+}
 2853+
 2854+/**
 2855+ * Get the path to a specified script file, respecting file
 2856+ * extensions; this is a wrapper around $wgScriptExtension etc.
 2857+ *
 2858+ * @param string $script Script filename, sans extension
 2859+ * @return string
 2860+ */
 2861+function wfScript( $script = 'index' ) {
 2862+ global $wgScriptPath, $wgScriptExtension;
 2863+ return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
 2864+}
 2865+
 2866+/**
 2867+ * Convenience function converts boolean values into "true"
 2868+ * or "false" (string) values
 2869+ *
 2870+ * @param bool $value
 2871+ * @return string
 2872+ */
 2873+function wfBoolToStr( $value ) {
 2874+ return $value ? 'true' : 'false';
 2875+}
 2876+
 2877+/**
 2878+ * Load an extension messages file
 2879+ *
 2880+ * @param string $extensionName Name of extension to load messages from\for.
 2881+ * @param string $langcode Language to load messages for, or false for default
 2882+ * behvaiour (en, content language and user language).
 2883+ * @since r24808 (v1.11) Using this method of loading extension messages will not work
 2884+ * on MediaWiki prior to that
 2885+ */
 2886+function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
 2887+ global $wgExtensionMessagesFiles, $wgMessageCache, $wgLang, $wgContLang;
 2888+
 2889+ #For recording whether extension message files have been loaded in a given language.
 2890+ static $loaded = array();
 2891+
 2892+ if( !array_key_exists( $extensionName, $loaded ) ) {
 2893+ $loaded[$extensionName] = array();
 2894+ }
 2895+
 2896+ if ( !isset($wgExtensionMessagesFiles[$extensionName]) ) {
 2897+ throw new MWException( "Messages file for extensions $extensionName is not defined" );
 2898+ }
 2899+
 2900+ if( !$langcode && !array_key_exists( '*', $loaded[$extensionName] ) ) {
 2901+ # Just do en, content language and user language.
 2902+ $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], false );
 2903+ # Mark that they have been loaded.
 2904+ $loaded[$extensionName]['en'] = true;
 2905+ $loaded[$extensionName][$wgLang->getCode()] = true;
 2906+ $loaded[$extensionName][$wgContLang->getCode()] = true;
 2907+ # Mark that this part has been done to avoid weird if statements.
 2908+ $loaded[$extensionName]['*'] = true;
 2909+ } elseif( is_string( $langcode ) && !array_key_exists( $langcode, $loaded[$extensionName] ) ) {
 2910+ # Load messages for specified language.
 2911+ $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], $langcode );
 2912+ # Mark that they have been loaded.
 2913+ $loaded[$extensionName][$langcode] = true;
 2914+ }
 2915+}
 2916+
 2917+/**
 2918+ * Get a platform-independent path to the null file, e.g.
 2919+ * /dev/null
 2920+ *
 2921+ * @return string
 2922+ */
 2923+function wfGetNull() {
 2924+ return wfIsWindows()
 2925+ ? 'NUL'
 2926+ : '/dev/null';
 2927+}
 2928+
 2929+/**
 2930+ * Displays a maxlag error
 2931+ *
 2932+ * @param string $host Server that lags the most
 2933+ * @param int $lag Maxlag (actual)
 2934+ * @param int $maxLag Maxlag (requested)
 2935+ */
 2936+function wfMaxlagError( $host, $lag, $maxLag ) {
 2937+ global $wgShowHostnames;
 2938+ header( 'HTTP/1.1 503 Service Unavailable' );
 2939+ header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
 2940+ header( 'X-Database-Lag: ' . intval( $lag ) );
 2941+ header( 'Content-Type: text/plain' );
 2942+ if( $wgShowHostnames ) {
 2943+ echo "Waiting for $host: $lag seconds lagged\n";
 2944+ } else {
 2945+ echo "Waiting for a database server: $lag seconds lagged\n";
 2946+ }
 2947+}
 2948+
 2949+/**
 2950+ * Throws an E_USER_NOTICE saying that $function is deprecated
 2951+ * @param string $function
 2952+ * @return null
 2953+ */
 2954+function wfDeprecated( $function ) {
 2955+ global $wgDebugLogFile;
 2956+ if ( !$wgDebugLogFile ) {
 2957+ return;
 2958+ }
 2959+ $callers = wfDebugBacktrace();
 2960+ if( isset( $callers[2] ) ){
 2961+ $callerfunc = $callers[2];
 2962+ $callerfile = $callers[1];
 2963+ if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ){
 2964+ $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
 2965+ } else {
 2966+ $file = '(internal function)';
 2967+ }
 2968+ $func = '';
 2969+ if( isset( $callerfunc['class'] ) )
 2970+ $func .= $callerfunc['class'] . '::';
 2971+ $func .= @$callerfunc['function'];
 2972+ $msg = "Use of $function is deprecated. Called from $func in $file";
 2973+ } else {
 2974+ $msg = "Use of $function is deprecated.";
 2975+ }
 2976+ wfDebug( "$msg\n" );
 2977+}
 2978+
 2979+/**
 2980+ * Sleep until the worst slave's replication lag is less than or equal to
 2981+ * $maxLag, in seconds. Use this when updating very large numbers of rows, as
 2982+ * in maintenance scripts, to avoid causing too much lag. Of course, this is
 2983+ * a no-op if there are no slaves.
 2984+ *
 2985+ * Every time the function has to wait for a slave, it will print a message to
 2986+ * that effect (and then sleep for a little while), so it's probably not best
 2987+ * to use this outside maintenance scripts in its present form.
 2988+ *
 2989+ * @param int $maxLag
 2990+ * @return null
 2991+ */
 2992+function wfWaitForSlaves( $maxLag ) {
 2993+ if( $maxLag ) {
 2994+ $lb = wfGetLB();
 2995+ list( $host, $lag ) = $lb->getMaxLag();
 2996+ while( $lag > $maxLag ) {
 2997+ $name = @gethostbyaddr( $host );
 2998+ if( $name !== false ) {
 2999+ $host = $name;
 3000+ }
 3001+ print "Waiting for $host (lagged $lag seconds)...\n";
 3002+ sleep($maxLag);
 3003+ list( $host, $lag ) = $lb->getMaxLag();
 3004+ }
 3005+ }
 3006+}
 3007+
 3008+/**
 3009+ * Output some plain text in command-line mode or in the installer (updaters.inc).
 3010+ * Do not use it in any other context, its behaviour is subject to change.
 3011+ */
 3012+function wfOut( $s ) {
 3013+ static $lineStarted = false;
 3014+ global $wgCommandLineMode;
 3015+ if ( $wgCommandLineMode && !defined( 'MEDIAWIKI_INSTALL' ) ) {
 3016+ echo $s;
 3017+ } else {
 3018+ echo htmlspecialchars( $s );
 3019+ }
 3020+ flush();
 3021+}
 3022+
 3023+/** Generate a random 32-character hexadecimal token.
 3024+ * @param mixed $salt Some sort of salt, if necessary, to add to random characters before hashing.
 3025+ */
 3026+function wfGenerateToken( $salt = '' ) {
 3027+ $salt = serialize($salt);
 3028+
 3029+ return md5( mt_rand( 0, 0x7fffffff ) . $salt );
 3030+}
 3031+
 3032+/**
 3033+ * Replace all invalid characters with -
 3034+ * @param mixed $title Filename to process
 3035+ */
 3036+function wfStripIllegalFilenameChars( $name ) {
 3037+ $name = wfBaseName( $name );
 3038+ $name = preg_replace ( "/[^".Title::legalChars()."]/", '-', $name );
 3039+ return $name;
 3040+}
Property changes on: trunk/extensions/NSFileRepo/REL1_15_0/phase3/includes/GlobalFunctions.php
___________________________________________________________________
Added: svn:eol-style
13041 + native
Index: trunk/extensions/NSFileRepo/REL1_15_0/phase3/includes/GlobalFunctions.patch
@@ -0,0 +1,12 @@
 2+Index: GlobalFunctions.php
 3+===================================================================
 4+--- GlobalFunctions.php (revision 53111)
 5+@@ -3034,6 +3034,6 @@
 6+ */
 7+ function wfStripIllegalFilenameChars( $name ) {
 8+ $name = wfBaseName( $name );
 9+- $name = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $name );
 10++ $name = preg_replace ( "/[^".Title::legalChars()."]/", '-', $name );
 11+ return $name;
 12+ }
Index: trunk/extensions/NSFileRepo/NSFileRepo.i18n.php
@@ -0,0 +1,24 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for DiscussionThreading extension.
 5+ *
 6+ * @addtogroup Extensions
 7+*/
 8+
 9+$messages = array();
 10+
 11+/** English
 12+ * @author Jack D. Pond
 13+ */
 14+$messages['en'] = array(
 15+ 'NSFileRepo-desc' => 'Add threading to talk pages'
 16+);
 17+
 18+/** Message documentation (Message documentation)
 19+ * @author Fryed-peach
 20+ * @author Purodha
 21+ */
 22+$messages['qqq'] = array(
 23+ 'NSFileRepo-desc' => 'Provide NameSpace-based features to uploaded files and images.'
 24+);
 25+
Property changes on: trunk/extensions/NSFileRepo/NSFileRepo.i18n.php
___________________________________________________________________
Added: svn:eol-style
126 + native
Index: trunk/extensions/NSFileRepo/NSFileRepo.php
@@ -0,0 +1,221 @@
 2+<?php
 3+/**
 4+ * The purpose of this extension is to provide NameSpace-based features to uploaded files in the local file repositories (FileRepo)
 5+
 6+ * The optimal solution would be a clean extension that is easily maintainable as the trunk of MW moves foward.
 7+ *
 8+ * @author Jack D. Pond <jack.pond@psitex.com>
 9+ * @addtogroup Extensions
 10+ * @copyright 2009 Jack D. pond
 11+ * @url http://www.mediawiki.org/wiki/Manual:Extension:NSFileRepo
 12+ * @licence GNU General Public Licence 2.0 or later
 13+ *
 14+ * This extension extends and is dependent on extension Lockdown - see http://www.mediawiki.org/wiki/Extension:Lockdown
 15+ * It must be included(required) after Lockdown!
 16+ */
 17+
 18+if (!defined('MEDIAWIKI')) die('Not an entry point.');
 19+
 20+# Internationalisation file
 21+$wgExtensionMessagesFiles['NSFileRepo'] = dirname(__FILE__) . '/NSFileRepo.i18n.php';
 22+
 23+
 24+$wgExtensionFunctions[] = 'NSFileRepoSetup';
 25+$wgExtensionCredits['media'][] = array(
 26+ 'path' => __FILE__,
 27+ 'name' => 'NSFileRepo',
 28+ 'author' => 'Jack D. Pond',
 29+ 'version' => '0.0',
 30+ 'url' => 'http://www.mediawiki.org/wiki/Extension:NSFileRepo',
 31+ 'description' => 'Provide NameSpace-based features to uploaded files',
 32+ 'descriptionmsg' => 'NSFileRepo-desc'
 33+);
 34+
 35+
 36+/**
 37+ * Set up hooks for NSFileRepo
 38+ *
 39+ */
 40+
 41+$wgHooks['UploadForm:BeforeProcessing'][] = 'NSFileRepoNSCheck';
 42+/**
 43+Note, this must be AFTER lockdown has been included - thus assuming that the user has access to files in general + files at this particular namespace.
 44+*/
 45+$wgHooks['userCan'][] = 'NSFileRepolockdownUserCan';
 46+$wgHooks['ImgAuthBeforeStream'][] = 'NSFileRepoImgAuthCheck';
 47+
 48+
 49+class NSLocalRepo extends LocalRepo {
 50+ var $fileFactory = array( 'NSLocalFile', 'newFromTitle' );
 51+ var $oldFileFactory = array( 'NSOldLocalFile', 'newFromTitle' );
 52+ var $fileFromRowFactory = array( 'NSLocalFile', 'newFromRow' );
 53+ var $oldFileFromRowFactory = array( 'NSOldLocalFile', 'newFromRow' );
 54+
 55+ static function getHashPathForLevel( $name, $levels ) {
 56+ global $wgContLang;
 57+ $bits=explode(':',$name);
 58+ $filename = $bits[count($bits)-1];
 59+ $path = parent::getHashPathForLevel( $filename, $levels );
 60+ return ((count($bits) > 1) ? $wgContLang->getNsIndex($bits[0]).'/'.$path : $path);
 61+ }
 62+ /**
 63+ * Get a relative path including trailing slash, e.g. f/fa/
 64+ * If the repo is not hashed, returns an empty string
 65+ * This is needed because self:: will call parent if not included - exact same as in FSRepo
 66+ */
 67+ function getHashPath( $name ) {
 68+ return self::getHashPathForLevel( $name, $this->hashLevels );
 69+ }
 70+ /**
 71+ * Pick a random name in the temp zone and store a file to it.
 72+ * @param string $originalName The base name of the file as specified
 73+ * by the user. The file extension will be maintained.
 74+ * @param string $srcPath The current location of the file.
 75+ * @return FileRepoStatus object with the URL in the value.
 76+ */
 77+ function storeTemp( $originalName, $srcPath ) {
 78+ $date = gmdate( "YmdHis" );
 79+ $hashPath = $this->getHashPath( $originalName );
 80+ $bits=explode(':',$originalName);
 81+ $filename = $bits[count($bits)-1];
 82+ $dstRel = "$hashPath$date!$filename";
 83+ $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $filename );
 84+ $result = $this->store( $srcPath, 'temp', $dstRel );
 85+ $result->value = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
 86+ return $result;
 87+ }
 88+}
 89+
 90+class NSLocalFile extends LocalFile
 91+{
 92+ /**
 93+ * Get the path of the file relative to the public zone root
 94+ */
 95+ function getRel() {
 96+ $bits=explode(':',$this->getName());
 97+ $filename = $bits[count($bits)-1];
 98+ return $this->getHashPath() . $filename;
 99+ }
 100+
 101+ /**
 102+ * Get urlencoded relative path of the file
 103+ */
 104+ function getUrlRel() {
 105+ $bits=explode(':',$this->getName());
 106+ $filename = $bits[count($bits)-1];
 107+ return $this->getHashPath() . rawurlencode( $filename );
 108+ }
 109+ /** Instantiating this class using "self"
 110+ * If you're reading this, you're problably wondering why on earth are the following static functions, which are copied
 111+ * verbatim from the original extended class "LocalFIle" included here?
 112+ * The answer is that "self", will instantiate the class the code is physically in, not the class extended from it.
 113+ * Without the inclusion of these methods in "NSLocalFile, "self" would instantiate a "LocalFile" class, not the
 114+ * "NSLocalFile" class we want it to. Since there are only two methods within the "LocalFile" class that use "self",
 115+ * I just copied that code into the new "NSLocalFile" extended class, and the copied code will instantiate the "NSLocalFIle"
 116+ * class instead of the "LocalFile" class (at least in PHP 5.2.4)
 117+ */
 118+
 119+ /**
 120+ * Create a NSLocalFile from a title
 121+ * Do not call this except from inside a repo class.
 122+ *
 123+ * Note: $unused param is only here to avoid an E_STRICT
 124+ */
 125+ static function newFromTitle( $title, $repo, $unused = null ) {
 126+ return new self( $title, $repo );
 127+ }
 128+ /**
 129+ * Create a NSLocalFile from a title
 130+ * Do not call this except from inside a repo class.
 131+ */
 132+
 133+ static function newFromRow( $row, $repo ) {
 134+ $title = Title::makeTitle( NS_FILE, $row->img_name );
 135+ $file = new self( $title, $repo );
 136+ $file->loadFromRow( $row );
 137+ return $file;
 138+ }
 139+}
 140+class NSOldLocalFile extends OldLocalFile
 141+{
 142+ function getRel( $name, $levels ) {
 143+ return(NSLocalFile::getRel());
 144+ }
 145+ function getUrlRel( $name, $levels ) {
 146+ return(NSLocalFile::getUrlRel());
 147+ }
 148+
 149+ /** See comment about Instantiating this class using "self", above */
 150+
 151+ static function newFromTitle( $title, $repo, $time = null ) {
 152+ # The null default value is only here to avoid an E_STRICT
 153+ if( $time === null )
 154+ throw new MWException( __METHOD__.' got null for $time parameter' );
 155+ return new self( $title, $repo, $time, null );
 156+ }
 157+
 158+ static function newFromArchiveName( $title, $repo, $archiveName ) {
 159+ return new self( $title, $repo, null, $archiveName );
 160+ }
 161+
 162+ static function newFromRow( $row, $repo ) {
 163+ $title = Title::makeTitle( NS_FILE, $row->oi_name );
 164+ $file = new self( $title, $repo, null, $row->oi_archive_name );
 165+ $file->loadFromRow( $row, 'oi_' );
 166+ return $file;
 167+ }
 168+}
 169+
 170+
 171+/**
 172+ * Initial setup, add .i18n. messages from $IP/extensions/DiscussionThreading/DiscussionThreading.i18n.php
 173+*/
 174+function NSFileRepoSetup() {
 175+ global $wgLocalFileRepo;
 176+ wfLoadExtensionMessages( 'NSFileRepo' );
 177+ $wgLocalFileRepo['class'] = "NSLocalRepo";
 178+ RepoGroup::destroySingleton();
 179+}
 180+/*
 181+ * Check for Namespace in Title Line
 182+*/
 183+function NSFileRepoNSCheck($UploadForm) {
 184+ $title = Title::newFromText($UploadForm->mDesiredDestName);
 185+ if ($title->mNamespace < 100) {
 186+ $UploadForm->mDesiredDestName = preg_replace ( "/:/", '-', $UploadForm->mDesiredDestName);
 187+ } else {
 188+ $bits=explode(':',$UploadForm->mDesiredDestName);
 189+ $ns = array_shift($bits);
 190+ $UploadForm->mDesiredDestName = $ns.":".implode("-",$bits);
 191+ }
 192+ return (true);
 193+}
 194+
 195+
 196+// If Extension:Lockdown has been activated (recommend), check individual namespace protection
 197+
 198+function NSFileRepolockdownUserCan($title, $user, $action, &$result) {
 199+ if (function_exists('lockdownUserCan')){
 200+ if($title->getNamespace() == NS_FILE) {
 201+ $ntitle = Title::newFromText($title->mDbkeyform);
 202+ return ($ntitle->mNamespace < 100) ? true : lockdownUserCan($ntitle, $user, $action, $result);
 203+ }
 204+ }
 205+ return true;
 206+}
 207+
 208+function NSFileRepoImgAuthCheck($title, $path, $name, $result) {
 209+ global $wgContLang;
 210+
 211+# See if stored in a NS path
 212+
 213+ $subdirs = explode('/',$_SERVER['PATH_INFO']);
 214+ if (strlen($subdirs[1]) == 3 && is_numeric($subdirs[1]) && $subdirs[1] >= 100) {
 215+ $title = Title::makeTitleSafe( NS_FILE, $wgContLang->getNsText($subdirs[1]).":".$name );
 216+ if( !$title instanceof Title ) {
 217+ $result = array(wfMsgHTML('image_auth-accessdenied'),wfMsgHTML('image_auth-badtitle',$name));
 218+ return false;
 219+ }
 220+ }
 221+ return true;
 222+}
\ No newline at end of file
Property changes on: trunk/extensions/NSFileRepo/NSFileRepo.php
___________________________________________________________________
Added: svn:eol-style
1223 + native
Index: trunk/extensions/NSFileRepo/README
@@ -0,0 +1,144 @@
 2+{{Page security extension disclaimer}}
 3+{{Extension by patch warning|version=}}
 4+{{Extension|templatemode=
 5+|name = NSFileRepo
 6+|status = beta
 7+|type1 = user rights
 8+|type2 =
 9+|hook1 = userCan
 10+|hook2 = ImgAuthBeforeStream
 11+|username = [[User:Jpond|Jack D. Pond]]
 12+|author = <!-- add only if different from user name -->
 13+|description = implements per-namespace group permissions for image and file rights protection
 14+|image =
 15+|version = 0.0
 16+|update = 2009-07-11
 17+|mediawiki = 1.13, 1.14, 1.15
 18+|license = GNU General Public Licence 2.0
 19+|download = {{WikimediaDownload|NSFileRepo}}
 20+|readme = [http://svn.wikimedia.org/svnroot/mediawiki/trunk/extensions/NSFileRepo/README README]
 21+|changelog = [http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/NSFileRepo/?view=log log]
 22+|parameters = <!-- configuration parameters, for use in LocalSettings.php -->
 23+|rights = <!-- user rights, for use with $wgGroupPermissions -->
 24+|example =
 25+}}
 26+
 27+==What can this extension do?==
 28+
 29+The '''NSFileRepo''' extension implements a way to restrict access to specific files and images to a given set of user groups. This provides a more fine grained security model and allows access restriction to users in specified groups.
 30+
 31+__TOC__
 32+==Usage==
 33+
 34+Generically, you use the same syntax as a normal file/image reference link, adding the namespace between the file specifier ("File","Image", or "Media"), and the file name:
 35+
 36+<pre>[[{FILE_NS}:{Namespace}:{Filename}]]</pre>
 37+
 38+Example(Where "Project" is the protected namespace and "ProjectFile.pdf" is the file to which you wish to limit access):
 39+
 40+<pre>[[File:Project:ProjectFile.pdf]]</pre>
 41+<br>
 42+The standard for accessing files/images is generally:
 43+<pre>
 44+[[File:Filename.txt]]
 45+[[Image:Filename.jpg]]
 46+[[Media:Filename.pdf]]
 47+</pre>
 48+
 49+This extension allows you to protect access to files/images, by adding the namespace text identifier after the file namespace identifier, for example(Where "Project" is the protected namespace and "ProjectFile.xxx" is the file to which you wish to limit access):
 50+<pre>
 51+[[File:Project:Filename.txt]]
 52+[[Image:Project:Filename.jpg]]
 53+[[Media:Project:Filename.pdf]]
 54+</pre>
 55+
 56+It may be helpful to understand the default security model used by MediaWiki using the instructions below:
 57+
 58+* [[Manual:User rights]]
 59+* [[Manual:$wgGroupPermissions]]
 60+
 61+Limitations of security are the same as for Extension Lockdown. To review these limitations, [[Extension:Lockdown#Additional_measures | please reference here]].
 62+
 63+To use the full capabilities of this extension (e.g., specific namespace protections), you will need to install and use the namespace protections provided through [[Extension:Lockdown]].
 64+
 65+This extension was made possible by the introduction of Repository Classes by Tim Starling - an elegant and brilliant implementation. It uses a new Local Repository class mechanism. Technical details on how this extension works can be found [[Extension:NSFileRepo/DOC | here]].
 66+
 67+== Announcements ==
 68+
 69+* The first version of this (Rel 0.0) was released 2009-07-11. The following activities are underway to make this extension easier to install and use, including:
 70+** Modifying and updating the standard version of img_auth.php to include localization and a hook necessary for this extension. Will hopefully be approved for version 1.16
 71+** Discussing ways to allow modification of wfStripIllegalFilenameChars so that future patching will not be needed.
 72+
 73+==Download instructions==
 74+
 75+This Extension and the necessary patch/files may be downloaded from one of the following (SVN preferred)
 76+
 77+* Revision 0.0 (beta)
 78+** [http://wiki.montcopa.org/PublicDownloads/NSFileRepo.tar tar] (May require eol conversion)
 79+** [http://wiki.montcopa.org/PublicDownloads/NSFileRepo.tar zip]
 80+** [http://svn.wikimedia.org/svnroot/mediawiki/trunk/extensions/NSFileRepo SVN]
 81+
 82+Copy all files and directories into directory:
 83+
 84+<pre>
 85+$IP/extensions/NSFileRepo
 86+</pre>
 87+
 88+==Installation==
 89+
 90+You will need to read and understand two other required enhancements to MediaWiki:
 91+
 92+* [[Manual:Image Authorization | Image Authorization]].
 93+* [[Extension:Lockdown | Extension Lockdown]]
 94+
 95+Please read and understand the above before executing the following instructions
 96+
 97+# Download and install [[Extension:Lockdown | Extension Lockdown]]
 98+# Download and copy the NSFileRepo extension into directory <nowiki>$IP/extensions/NSFileRepo</nowiki>
 99+# Activate the Image Authorization according to instructions found in [[Manual:Image Authorization | Image Authorization]]
 100+# Copy the img_auth.php and img_auth.i18.php from the distribution in directory <nowiki>{release}/phase3/</nowiki> to your wiki code base directory ($IP). This will overwrite the existing img_auth.php file. Alternately you could copy img_auth.php to another name in the same directory, then use that file name instead of img_auth.php (but still must be in the $IP directory and the localization file must still be img_auth.i18.php).
 101+# <nowiki>$IP/include/GlobalFunctions.php</nowiki> Must be patched. This is a very minor patch to remove the disabling of colons (':'). You can do this one of three ways (whichever you're most comfortable with):
 102+## Edit the file according to instructions [[#Patch_GlobalFunctions.php | below]]
 103+## If you have not otherwise patched the file, you may want to copy it from the distribution, which will be in a directory corresponding to the release you are using under <nowiki>{release}/phase3/includes/GlobalFunctions.php</nowiki>
 104+## Apply the patch which will be in a directory corresponding to the release you are using under <nowiki>{release}/phase3/includes/GlobalFunctions.patch</nowiki>
 105+# To activate this extension, add the following to [[Manual:LocalSettings.php|LocalSettings.php]]:
 106+<source lang="php">
 107+require_once("$IP/extensions/NSFileRepo/NSFileRepo.php");
 108+</source>
 109+
 110+===Configuration parameters===
 111+
 112+The user rights and configuration requiremements are are the same as described in [[Extension:Lockdown#Configuration | Extension Lockdown]].
 113+
 114+==Patch GlobalFunctions.php==
 115+
 116+In version 1_13_0, a new function wfStripIllegalFilenameChars was added to <nowiki>includes/GlobalFunctions.php</nowiki>. This prevents the protection namespace from being detectd.
 117+
 118+<pre>
 119+Index: GlobalFunctions.php
 120+===================================================================
 121+--- GlobalFunctions.php (revision 52849)
 122+@@ -3034,6 +3034,6 @@
 123+ */
 124+ function wfStripIllegalFilenameChars( $name ) {
 125+ $name = wfBaseName( $name );
 126+- $name = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $name );
 127++ $name = preg_replace ( "/[^".Title::legalChars()."]/", '-', $name );
 128+ return $name;
 129+ }
 130+</pre>
 131+<br>
 132+<br>
 133+You need to remove the "or :" clause from the REGEX expression of wfStripIllegalFilenameChars by deleting the characters <nowiki>"|:"</nowiki>
 134+<br>
 135+
 136+==See also==
 137+
 138+* [[Manual:Image Authorization | Image Authorization]].
 139+* [[Extension:Lockdown | Extension Lockdown]]
 140+
 141+[[Category:View page extensions]]
 142+[[Category:Edit extensions]]
 143+
 144+{{languages}}

Status & tagging log