]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/GlobalFunctions.php
MediaWiki 1.17.0-scripts
[autoinstalls/mediawiki.git] / includes / GlobalFunctions.php
1 <?php
2 /**
3  * Global functions used everywhere
4  * @file
5  */
6
7 if ( !defined( 'MEDIAWIKI' ) ) {
8         die( "This file is part of MediaWiki, it is not a valid entry point" );
9 }
10
11 require_once dirname( __FILE__ ) . '/normal/UtfNormalUtil.php';
12
13 // Hide compatibility functions from Doxygen
14 /// @cond
15
16 /**
17  * Compatibility functions
18  *
19  * We support PHP 5.1.x and up.
20  * Re-implementations of newer functions or functions in non-standard
21  * PHP extensions may be included here.
22  */
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 ( substr( $to, -8 ) == '//IGNORE' ) {
29                         $to = substr( $to, 0, strlen( $to ) - 8 );
30                 }
31                 if( strcasecmp( $from, $to ) == 0 ) {
32                         return $string;
33                 }
34                 if( strcasecmp( $from, 'utf-8' ) == 0 ) {
35                         return utf8_decode( $string );
36                 }
37                 if( strcasecmp( $to, 'utf-8' ) == 0 ) {
38                         return utf8_encode( $string );
39                 }
40                 return $string;
41         }
42 }
43
44 if ( !function_exists( 'mb_substr' ) ) {
45         /**
46          * Fallback implementation for mb_substr, hardcoded to UTF-8.
47          * Attempts to be at least _moderately_ efficient; best optimized
48          * for relatively small offset and count values -- about 5x slower
49          * than native mb_string in my testing.
50          *
51          * Larger offsets are still fairly efficient for Latin text, but
52          * can be up to 100x slower than native if the text is heavily
53          * multibyte and we have to slog through a few hundred kb.
54          */
55         function mb_substr( $str, $start, $count='end' ) {
56                 if( $start != 0 ) {
57                         $split = mb_substr_split_unicode( $str, intval( $start ) );
58                         $str = substr( $str, $split );
59                 }
60
61                 if( $count !== 'end' ) {
62                         $split = mb_substr_split_unicode( $str, intval( $count ) );
63                         $str = substr( $str, 0, $split );
64                 }
65
66                 return $str;
67         }
68
69         function mb_substr_split_unicode( $str, $splitPos ) {
70                 if( $splitPos == 0 ) {
71                         return 0;
72                 }
73
74                 $byteLen = strlen( $str );
75
76                 if( $splitPos > 0 ) {
77                         if( $splitPos > 256 ) {
78                                 // Optimize large string offsets by skipping ahead N bytes.
79                                 // This will cut out most of our slow time on Latin-based text,
80                                 // and 1/2 to 1/3 on East European and Asian scripts.
81                                 $bytePos = $splitPos;
82                                 while ( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
83                                         ++$bytePos;
84                                 }
85                                 $charPos = mb_strlen( substr( $str, 0, $bytePos ) );
86                         } else {
87                                 $charPos = 0;
88                                 $bytePos = 0;
89                         }
90
91                         while( $charPos++ < $splitPos ) {
92                                 ++$bytePos;
93                                 // Move past any tail bytes
94                                 while ( $bytePos < $byteLen && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
95                                         ++$bytePos;
96                                 }
97                         }
98                 } else {
99                         $splitPosX = $splitPos + 1;
100                         $charPos = 0; // relative to end of string; we don't care about the actual char position here
101                         $bytePos = $byteLen;
102                         while( $bytePos > 0 && $charPos-- >= $splitPosX ) {
103                                 --$bytePos;
104                                 // Move past any tail bytes
105                                 while ( $bytePos > 0 && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
106                                         --$bytePos;
107                                 }
108                         }
109                 }
110
111                 return $bytePos;
112         }
113 }
114
115 if ( !function_exists( 'mb_strlen' ) ) {
116         /**
117          * Fallback implementation of mb_strlen, hardcoded to UTF-8.
118          * @param string $str
119          * @param string $enc optional encoding; ignored
120          * @return int
121          */
122         function mb_strlen( $str, $enc = '' ) {
123                 $counts = count_chars( $str );
124                 $total = 0;
125
126                 // Count ASCII bytes
127                 for( $i = 0; $i < 0x80; $i++ ) {
128                         $total += $counts[$i];
129                 }
130
131                 // Count multibyte sequence heads
132                 for( $i = 0xc0; $i < 0xff; $i++ ) {
133                         $total += $counts[$i];
134                 }
135                 return $total;
136         }
137 }
138
139
140 if( !function_exists( 'mb_strpos' ) ) {
141         /**
142          * Fallback implementation of mb_strpos, hardcoded to UTF-8.
143          * @param $haystack String
144          * @param $needle String
145          * @param $offset String: optional start position
146          * @param $encoding String: optional encoding; ignored
147          * @return int
148          */
149         function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
150                 $needle = preg_quote( $needle, '/' );
151
152                 $ar = array();
153                 preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
154
155                 if( isset( $ar[0][1] ) ) {
156                         return $ar[0][1];
157                 } else {
158                         return false;
159                 }
160         }
161 }
162
163 if( !function_exists( 'mb_strrpos' ) ) {
164         /**
165          * Fallback implementation of mb_strrpos, hardcoded to UTF-8.
166          * @param $haystack String
167          * @param $needle String
168          * @param $offset String: optional start position
169          * @param $encoding String: optional encoding; ignored
170          * @return int
171          */
172         function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
173                 $needle = preg_quote( $needle, '/' );
174
175                 $ar = array();
176                 preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
177
178                 if( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
179                         isset( $ar[0][count( $ar[0] ) - 1][1] ) ) {
180                         return $ar[0][count( $ar[0] ) - 1][1];
181                 } else {
182                         return false;
183                 }
184         }
185 }
186
187 // Support for Wietse Venema's taint feature
188 if ( !function_exists( 'istainted' ) ) {
189         function istainted( $var ) {
190                 return 0;
191         }
192         function taint( $var, $level = 0 ) {}
193         function untaint( $var, $level = 0 ) {}
194         define( 'TC_HTML', 1 );
195         define( 'TC_SHELL', 1 );
196         define( 'TC_MYSQL', 1 );
197         define( 'TC_PCRE', 1 );
198         define( 'TC_SELF', 1 );
199 }
200
201 // array_fill_keys() was only added in 5.2, but people use it anyway
202 // add a back-compat layer for 5.1. See bug 27781
203 if( !function_exists( 'array_fill_keys' ) ) {
204         function array_fill_keys( $keys, $value ) {
205                 return array_combine( $keys, array_fill( 0, count( $keys ), $value ) );
206         }
207 }
208
209
210 /// @endcond
211
212
213 /**
214  * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
215  */
216 function wfArrayDiff2( $a, $b ) {
217         return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
218 }
219 function wfArrayDiff2_cmp( $a, $b ) {
220         if ( !is_array( $a ) ) {
221                 return strcmp( $a, $b );
222         } elseif ( count( $a ) !== count( $b ) ) {
223                 return count( $a ) < count( $b ) ? -1 : 1;
224         } else {
225                 reset( $a );
226                 reset( $b );
227                 while( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
228                         $cmp = strcmp( $valueA, $valueB );
229                         if ( $cmp !== 0 ) {
230                                 return $cmp;
231                         }
232                 }
233                 return 0;
234         }
235 }
236
237 /**
238  * Seed Mersenne Twister
239  * No-op for compatibility; only necessary in PHP < 4.2.0
240  * @deprecated. Remove in 1.18
241  */
242 function wfSeedRandom() {
243         wfDeprecated(__FUNCTION__);
244 }
245
246 /**
247  * Get a random decimal value between 0 and 1, in a way
248  * not likely to give duplicate values for any realistic
249  * number of articles.
250  *
251  * @return string
252  */
253 function wfRandom() {
254         # The maximum random value is "only" 2^31-1, so get two random
255         # values to reduce the chance of dupes
256         $max = mt_getrandmax() + 1;
257         $rand = number_format( ( mt_rand() * $max + mt_rand() )
258                 / $max / $max, 12, '.', '' );
259         return $rand;
260 }
261
262 /**
263  * We want some things to be included as literal characters in our title URLs
264  * for prettiness, which urlencode encodes by default.  According to RFC 1738,
265  * all of the following should be safe:
266  *
267  * ;:@&=$-_.+!*'(),
268  *
269  * But + is not safe because it's used to indicate a space; &= are only safe in
270  * paths and not in queries (and we don't distinguish here); ' seems kind of
271  * scary; and urlencode() doesn't touch -_. to begin with.  Plus, although /
272  * is reserved, we don't care.  So the list we unescape is:
273  *
274  * ;:@$!*(),/
275  *
276  * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
277  * so no fancy : for IIS7.
278  *
279  * %2F in the page titles seems to fatally break for some reason.
280  *
281  * @param $s String:
282  * @return string
283 */
284 function wfUrlencode( $s ) {
285         static $needle;
286         if ( is_null( $needle ) ) {
287                 $needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
288                 if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) || ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false ) ) {
289                         $needle[] = '%3A';
290                 }
291         }
292
293         $s = urlencode( $s );
294         $s = str_ireplace(
295                 $needle,
296                 array( ';', '@', '$', '!', '*', '(', ')', ',', '/', ':' ),
297                 $s
298         );
299
300         return $s;
301 }
302
303 /**
304  * Sends a line to the debug log if enabled or, optionally, to a comment in output.
305  * In normal operation this is a NOP.
306  *
307  * Controlling globals:
308  * $wgDebugLogFile - points to the log file
309  * $wgProfileOnly - if set, normal debug messages will not be recorded.
310  * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
311  * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
312  *
313  * @param $text String
314  * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
315  */
316 function wfDebug( $text, $logonly = false ) {
317         global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
318         global $wgDebugLogPrefix, $wgShowDebug;
319         static $recursion = 0;
320
321         static $cache = array(); // Cache of unoutputted messages
322         $text = wfDebugTimer() . $text;
323
324         # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
325         if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
326                 return;
327         }
328
329         if ( ( $wgDebugComments || $wgShowDebug ) && !$logonly ) {
330                 $cache[] = $text;
331
332                 if ( !isset( $wgOut ) ) {
333                         return;
334                 }
335                 if ( !StubObject::isRealObject( $wgOut ) ) {
336                         if ( $recursion ) {
337                                 return;
338                         }
339                         $recursion++;
340                         $wgOut->_unstub();
341                         $recursion--;
342                 }
343
344                 // add the message and possible cached ones to the output
345                 array_map( array( $wgOut, 'debug' ), $cache );
346                 $cache = array();
347         }
348         if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
349                 # Strip unprintables; they can switch terminal modes when binary data
350                 # gets dumped, which is pretty annoying.
351                 $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
352                 $text = $wgDebugLogPrefix . $text;
353                 wfErrorLog( $text, $wgDebugLogFile );
354         }
355 }
356
357 function wfDebugTimer() {
358         global $wgDebugTimestamps;
359         if ( !$wgDebugTimestamps ) {
360                 return '';
361         }
362         static $start = null;
363
364         if ( $start === null ) {
365                 $start = microtime( true );
366                 $prefix = "\n$start";
367         } else {
368                 $prefix = sprintf( "%6.4f", microtime( true ) - $start );
369         }
370
371         return $prefix . '  ';
372 }
373
374 /**
375  * Send a line giving PHP memory usage.
376  * @param $exact Bool: print exact values instead of kilobytes (default: false)
377  */
378 function wfDebugMem( $exact = false ) {
379         $mem = memory_get_usage();
380         if( !$exact ) {
381                 $mem = floor( $mem / 1024 ) . ' kilobytes';
382         } else {
383                 $mem .= ' bytes';
384         }
385         wfDebug( "Memory usage: $mem\n" );
386 }
387
388 /**
389  * Send a line to a supplementary debug log file, if configured, or main debug log if not.
390  * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
391  *
392  * @param $logGroup String
393  * @param $text String
394  * @param $public Bool: whether to log the event in the public log if no private
395  *                     log file is specified, (default true)
396  */
397 function wfDebugLog( $logGroup, $text, $public = true ) {
398         global $wgDebugLogGroups, $wgShowHostnames;
399         $text = trim( $text ) . "\n";
400         if( isset( $wgDebugLogGroups[$logGroup] ) ) {
401                 $time = wfTimestamp( TS_DB );
402                 $wiki = wfWikiID();
403                 if ( $wgShowHostnames ) {
404                         $host = wfHostname();
405                 } else {
406                         $host = '';
407                 }
408                 wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
409         } elseif ( $public === true ) {
410                 wfDebug( $text, true );
411         }
412 }
413
414 /**
415  * Log for database errors
416  * @param $text String: database error message.
417  */
418 function wfLogDBError( $text ) {
419         global $wgDBerrorLog, $wgDBname;
420         if ( $wgDBerrorLog ) {
421                 $host = trim(`hostname`);
422                 $text = date( 'D M j G:i:s T Y' ) . "\t$host\t$wgDBname\t$text";
423                 wfErrorLog( $text, $wgDBerrorLog );
424         }
425 }
426
427 /**
428  * Log to a file without getting "file size exceeded" signals.
429  *
430  * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
431  * send lines to the specified port, prefixed by the specified prefix and a space.
432  */
433 function wfErrorLog( $text, $file ) {
434         if ( substr( $file, 0, 4 ) == 'udp:' ) {
435                 # Needs the sockets extension
436                 if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
437                         // IPv6 bracketed host
438                         $host = $m[2];
439                         $port = intval( $m[3] );
440                         $prefix = isset( $m[4] ) ? $m[4] : false;
441                         $domain = AF_INET6;
442                 } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
443                         $host = $m[2];
444                         if ( !IP::isIPv4( $host ) ) {
445                                 $host = gethostbyname( $host );
446                         }
447                         $port = intval( $m[3] );
448                         $prefix = isset( $m[4] ) ? $m[4] : false;
449                         $domain = AF_INET;
450                 } else {
451                         throw new MWException( __METHOD__ . ': Invalid UDP specification' );
452                 }
453                 // Clean it up for the multiplexer
454                 if ( strval( $prefix ) !== '' ) {
455                         $text = preg_replace( '/^/m', $prefix . ' ', $text );
456                         if ( substr( $text, -1 ) != "\n" ) {
457                                 $text .= "\n";
458                         }
459                 }
460
461                 $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
462                 if ( !$sock ) {
463                         return;
464                 }
465                 socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
466                 socket_close( $sock );
467         } else {
468                 wfSuppressWarnings();
469                 $exists = file_exists( $file );
470                 $size = $exists ? filesize( $file ) : false;
471                 if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
472                         error_log( $text, 3, $file );
473                 }
474                 wfRestoreWarnings();
475         }
476 }
477
478 /**
479  * @todo document
480  */
481 function wfLogProfilingData() {
482         global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
483         global $wgProfiler, $wgProfileLimit, $wgUser;
484         # Profiling must actually be enabled...
485         if( is_null( $wgProfiler ) ) {
486                 return;
487         }
488         # Get total page request time
489         $now = wfTime();
490         $elapsed = $now - $wgRequestTime;
491         # Only show pages that longer than $wgProfileLimit time (default is 0)
492         if( $elapsed <= $wgProfileLimit ) {
493                 return;
494         }
495         $prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
496         $forward = '';
497         if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
498                 $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
499         }
500         if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
501                 $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
502         }
503         if( !empty( $_SERVER['HTTP_FROM'] ) ) {
504                 $forward .= ' from ' . $_SERVER['HTTP_FROM'];
505         }
506         if( $forward ) {
507                 $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
508         }
509         // Don't unstub $wgUser at this late stage just for statistics purposes
510         // FIXME: We can detect some anons even if it is not loaded. See User::getId()
511         if( $wgUser->mDataLoaded && $wgUser->isAnon() ) {
512                 $forward .= ' anon';
513         }
514         $log = sprintf( "%s\t%04.3f\t%s\n",
515                 gmdate( 'YmdHis' ), $elapsed,
516                 urldecode( $wgRequest->getRequestURL() . $forward ) );
517         if ( $wgDebugLogFile != '' && ( $wgRequest->getVal( 'action' ) != 'raw' || $wgDebugRawPage ) ) {
518                 wfErrorLog( $log . $prof, $wgDebugLogFile );
519         }
520 }
521
522 /**
523  * Check if the wiki read-only lock file is present. This can be used to lock
524  * off editing functions, but doesn't guarantee that the database will not be
525  * modified.
526  * @return bool
527  */
528 function wfReadOnly() {
529         global $wgReadOnlyFile, $wgReadOnly;
530
531         if ( !is_null( $wgReadOnly ) ) {
532                 return (bool)$wgReadOnly;
533         }
534         if ( $wgReadOnlyFile == '' ) {
535                 return false;
536         }
537         // Set $wgReadOnly for faster access next time
538         if ( is_file( $wgReadOnlyFile ) ) {
539                 $wgReadOnly = file_get_contents( $wgReadOnlyFile );
540         } else {
541                 $wgReadOnly = false;
542         }
543         return (bool)$wgReadOnly;
544 }
545
546 function wfReadOnlyReason() {
547         global $wgReadOnly;
548         wfReadOnly();
549         return $wgReadOnly;
550 }
551
552 /**
553  * Return a Language object from $langcode
554  * @param $langcode Mixed: either:
555  *                  - a Language object
556  *                  - code of the language to get the message for, if it is
557  *                    a valid code create a language for that language, if
558  *                    it is a string but not a valid code then make a basic
559  *                    language object
560  *                  - a boolean: if it's false then use the current users
561  *                    language (as a fallback for the old parameter
562  *                    functionality), or if it is true then use the wikis
563  * @return Language object
564  */
565 function wfGetLangObj( $langcode = false ) {
566         # Identify which language to get or create a language object for.
567         # Using is_object here due to Stub objects.
568         if( is_object( $langcode ) ) {
569                 # Great, we already have the object (hopefully)!
570                 return $langcode;
571         }
572
573         global $wgContLang, $wgLanguageCode;
574         if( $langcode === true || $langcode === $wgLanguageCode ) {
575                 # $langcode is the language code of the wikis content language object.
576                 # or it is a boolean and value is true
577                 return $wgContLang;
578         }
579
580         global $wgLang;
581         if( $langcode === false || $langcode === $wgLang->getCode() ) {
582                 # $langcode is the language code of user language object.
583                 # or it was a boolean and value is false
584                 return $wgLang;
585         }
586
587         $validCodes = array_keys( Language::getLanguageNames() );
588         if( in_array( $langcode, $validCodes ) ) {
589                 # $langcode corresponds to a valid language.
590                 return Language::factory( $langcode );
591         }
592
593         # $langcode is a string, but not a valid language code; use content language.
594         wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
595         return $wgContLang;
596 }
597
598 /**
599  * Use this instead of $wgContLang, when working with user interface.
600  * User interface is currently hard coded according to wiki content language
601  * in many ways, especially regarding to text direction. There is lots stuff
602  * to fix, hence this function to keep the old behaviour unless the global
603  * $wgBetterDirectionality is enabled (or removed when everything works).
604  */
605 function wfUILang() {
606         global $wgBetterDirectionality;
607         return wfGetLangObj( !$wgBetterDirectionality );
608 }
609
610 /**
611  * This is the new function for getting translated interface messages.
612  * See the Message class for documentation how to use them.
613  * The intention is that this function replaces all old wfMsg* functions.
614  * @param $key \string Message key.
615  * Varargs: normal message parameters.
616  * @return \type{Message}
617  * @since 1.17
618  */
619 function wfMessage( $key /*...*/) {
620         $params = func_get_args();
621         array_shift( $params );
622         if ( isset( $params[0] ) && is_array( $params[0] ) ) {
623                 $params = $params[0];
624         }
625         return new Message( $key, $params );
626 }
627
628 /**
629  * Get a message from anywhere, for the current user language.
630  *
631  * Use wfMsgForContent() instead if the message should NOT
632  * change depending on the user preferences.
633  *
634  * @param $key String: lookup key for the message, usually
635  *    defined in languages/Language.php
636  *
637  * This function also takes extra optional parameters (not
638  * shown in the function definition), which can be used to
639  * insert variable text into the predefined message.
640  */
641 function wfMsg( $key ) {
642         $args = func_get_args();
643         array_shift( $args );
644         return wfMsgReal( $key, $args, true );
645 }
646
647 /**
648  * Same as above except doesn't transform the message
649  */
650 function wfMsgNoTrans( $key ) {
651         $args = func_get_args();
652         array_shift( $args );
653         return wfMsgReal( $key, $args, true, false, false );
654 }
655
656 /**
657  * Get a message from anywhere, for the current global language
658  * set with $wgLanguageCode.
659  *
660  * Use this if the message should NOT change dependent on the
661  * language set in the user's preferences. This is the case for
662  * most text written into logs, as well as link targets (such as
663  * the name of the copyright policy page). Link titles, on the
664  * other hand, should be shown in the UI language.
665  *
666  * Note that MediaWiki allows users to change the user interface
667  * language in their preferences, but a single installation
668  * typically only contains content in one language.
669  *
670  * Be wary of this distinction: If you use wfMsg() where you should
671  * use wfMsgForContent(), a user of the software may have to
672  * customize potentially hundreds of messages in
673  * order to, e.g., fix a link in every possible language.
674  *
675  * @param $key String: lookup key for the message, usually
676  *    defined in languages/Language.php
677  */
678 function wfMsgForContent( $key ) {
679         global $wgForceUIMsgAsContentMsg;
680         $args = func_get_args();
681         array_shift( $args );
682         $forcontent = true;
683         if( is_array( $wgForceUIMsgAsContentMsg ) &&
684                 in_array( $key, $wgForceUIMsgAsContentMsg ) )
685         {
686                 $forcontent = false;
687         }
688         return wfMsgReal( $key, $args, true, $forcontent );
689 }
690
691 /**
692  * Same as above except doesn't transform the message
693  */
694 function wfMsgForContentNoTrans( $key ) {
695         global $wgForceUIMsgAsContentMsg;
696         $args = func_get_args();
697         array_shift( $args );
698         $forcontent = true;
699         if( is_array( $wgForceUIMsgAsContentMsg ) &&
700                 in_array( $key, $wgForceUIMsgAsContentMsg ) )
701         {
702                 $forcontent = false;
703         }
704         return wfMsgReal( $key, $args, true, $forcontent, false );
705 }
706
707 /**
708  * Get a message from the language file, for the UI elements
709  */
710 function wfMsgNoDB( $key ) {
711         $args = func_get_args();
712         array_shift( $args );
713         return wfMsgReal( $key, $args, false );
714 }
715
716 /**
717  * Get a message from the language file, for the content
718  */
719 function wfMsgNoDBForContent( $key ) {
720         global $wgForceUIMsgAsContentMsg;
721         $args = func_get_args();
722         array_shift( $args );
723         $forcontent = true;
724         if( is_array( $wgForceUIMsgAsContentMsg ) &&
725                 in_array( $key, $wgForceUIMsgAsContentMsg ) )
726         {
727                 $forcontent = false;
728         }
729         return wfMsgReal( $key, $args, false, $forcontent );
730 }
731
732
733 /**
734  * Really get a message
735  * @param $key String: key to get.
736  * @param $args
737  * @param $useDB Boolean
738  * @param $forContent Mixed: Language code, or false for user lang, true for content lang.
739  * @param $transform Boolean: Whether or not to transform the message.
740  * @return String: the requested message.
741  */
742 function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
743         wfProfileIn( __METHOD__ );
744         $message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
745         $message = wfMsgReplaceArgs( $message, $args );
746         wfProfileOut( __METHOD__ );
747         return $message;
748 }
749
750 /**
751  * This function provides the message source for messages to be edited which are *not* stored in the database.
752  * @param $key String:
753  */
754 function wfMsgWeirdKey( $key ) {
755         $source = wfMsgGetKey( $key, false, true, false );
756         if ( wfEmptyMsg( $key, $source ) ) {
757                 return '';
758         } else {
759                 return $source;
760         }
761 }
762
763 /**
764  * Fetch a message string value, but don't replace any keys yet.
765  * @param $key String
766  * @param $useDB Bool
767  * @param $langCode String: Code of the language to get the message for, or
768  *                  behaves as a content language switch if it is a boolean.
769  * @param $transform Boolean: whether to parse magic words, etc.
770  * @return string
771  */
772 function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
773         global $wgMessageCache;
774
775         wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
776
777         if ( !is_object( $wgMessageCache ) ) {
778                 throw new MWException( 'Trying to get message before message cache is initialised' );
779         }
780
781         $message = $wgMessageCache->get( $key, $useDB, $langCode );
782         if( $message === false ) {
783                 $message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
784         } elseif ( $transform ) {
785                 $message = $wgMessageCache->transform( $message );
786         }
787         return $message;
788 }
789
790 /**
791  * Replace message parameter keys on the given formatted output.
792  *
793  * @param $message String
794  * @param $args Array
795  * @return string
796  * @private
797  */
798 function wfMsgReplaceArgs( $message, $args ) {
799         # Fix windows line-endings
800         # Some messages are split with explode("\n", $msg)
801         $message = str_replace( "\r", '', $message );
802
803         // Replace arguments
804         if ( count( $args ) ) {
805                 if ( is_array( $args[0] ) ) {
806                         $args = array_values( $args[0] );
807                 }
808                 $replacementKeys = array();
809                 foreach( $args as $n => $param ) {
810                         $replacementKeys['$' . ( $n + 1 )] = $param;
811                 }
812                 $message = strtr( $message, $replacementKeys );
813         }
814
815         return $message;
816 }
817
818 /**
819  * Return an HTML-escaped version of a message.
820  * Parameter replacements, if any, are done *after* the HTML-escaping,
821  * so parameters may contain HTML (eg links or form controls). Be sure
822  * to pre-escape them if you really do want plaintext, or just wrap
823  * the whole thing in htmlspecialchars().
824  *
825  * @param $key String
826  * @param string ... parameters
827  * @return string
828  */
829 function wfMsgHtml( $key ) {
830         $args = func_get_args();
831         array_shift( $args );
832         return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
833 }
834
835 /**
836  * Return an HTML version of message
837  * Parameter replacements, if any, are done *after* parsing the wiki-text message,
838  * so parameters may contain HTML (eg links or form controls). Be sure
839  * to pre-escape them if you really do want plaintext, or just wrap
840  * the whole thing in htmlspecialchars().
841  *
842  * @param $key String
843  * @param string ... parameters
844  * @return string
845  */
846 function wfMsgWikiHtml( $key ) {
847         global $wgOut;
848         $args = func_get_args();
849         array_shift( $args );
850         return wfMsgReplaceArgs( $wgOut->parse( wfMsgGetKey( $key, true ), /* can't be set to false */ true ), $args );
851 }
852
853 /**
854  * Returns message in the requested format
855  * @param $key String: key of the message
856  * @param $options Array: processing rules. Can take the following options:
857  *   <i>parse</i>: parses wikitext to HTML
858  *   <i>parseinline</i>: parses wikitext to HTML and removes the surrounding
859  *       p's added by parser or tidy
860  *   <i>escape</i>: filters message through htmlspecialchars
861  *   <i>escapenoentities</i>: same, but allows entity references like &#160; through
862  *   <i>replaceafter</i>: parameters are substituted after parsing or escaping
863  *   <i>parsemag</i>: transform the message using magic phrases
864  *   <i>content</i>: fetch message for content language instead of interface
865  * Also can accept a single associative argument, of the form 'language' => 'xx':
866  *   <i>language</i>: Language object or language code to fetch message for
867  *       (overriden by <i>content</i>), its behaviour with parse, parseinline
868  *       and parsemag is undefined.
869  * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
870  */
871 function wfMsgExt( $key, $options ) {
872         global $wgOut;
873
874         $args = func_get_args();
875         array_shift( $args );
876         array_shift( $args );
877         $options = (array)$options;
878
879         foreach( $options as $arrayKey => $option ) {
880                 if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
881                         # An unknown index, neither numeric nor "language"
882                         wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING );
883                 } elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
884                 array( 'parse', 'parseinline', 'escape', 'escapenoentities',
885                 'replaceafter', 'parsemag', 'content' ) ) ) {
886                         # A numeric index with unknown value
887                         wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING );
888                 }
889         }
890
891         if( in_array( 'content', $options, true ) ) {
892                 $forContent = true;
893                 $langCode = true;
894         } elseif( array_key_exists( 'language', $options ) ) {
895                 $forContent = false;
896                 $langCode = wfGetLangObj( $options['language'] );
897         } else {
898                 $forContent = false;
899                 $langCode = false;
900         }
901
902         $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
903
904         if( !in_array( 'replaceafter', $options, true ) ) {
905                 $string = wfMsgReplaceArgs( $string, $args );
906         }
907
908         if( in_array( 'parse', $options, true ) ) {
909                 $string = $wgOut->parse( $string, true, !$forContent );
910         } elseif ( in_array( 'parseinline', $options, true ) ) {
911                 $string = $wgOut->parse( $string, true, !$forContent );
912                 $m = array();
913                 if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
914                         $string = $m[1];
915                 }
916         } elseif ( in_array( 'parsemag', $options, true ) ) {
917                 global $wgMessageCache;
918                 if ( isset( $wgMessageCache ) ) {
919                         $string = $wgMessageCache->transform( $string,
920                                 !$forContent,
921                                 is_object( $langCode ) ? $langCode : null );
922                 }
923         }
924
925         if ( in_array( 'escape', $options, true ) ) {
926                 $string = htmlspecialchars ( $string );
927         } elseif ( in_array( 'escapenoentities', $options, true ) ) {
928                 $string = Sanitizer::escapeHtmlAllowEntities( $string );
929         }
930
931         if( in_array( 'replaceafter', $options, true ) ) {
932                 $string = wfMsgReplaceArgs( $string, $args );
933         }
934
935         return $string;
936 }
937
938
939 /**
940  * Just like exit() but makes a note of it.
941  * Commits open transactions except if the error parameter is set
942  *
943  * @deprecated Please return control to the caller or throw an exception. Will
944  *             be removed in 1.19.
945  */
946 function wfAbruptExit( $error = false ) {
947         static $called = false;
948         if ( $called ) {
949                 exit( -1 );
950         }
951         $called = true;
952
953         wfDeprecated( __FUNCTION__ );
954         $bt = wfDebugBacktrace();
955         if( $bt ) {
956                 for( $i = 0; $i < count( $bt ); $i++ ) {
957                         $file = isset( $bt[$i]['file'] ) ? $bt[$i]['file'] : 'unknown';
958                         $line = isset( $bt[$i]['line'] ) ? $bt[$i]['line'] : 'unknown';
959                         wfDebug( "WARNING: Abrupt exit in $file at line $line\n");
960                 }
961         } else {
962                 wfDebug( "WARNING: Abrupt exit\n" );
963         }
964
965         wfLogProfilingData();
966
967         if ( !$error ) {
968                 wfGetLB()->closeAll();
969         }
970         exit( -1 );
971 }
972
973 /**
974  * @deprecated Please return control the caller or throw an exception. Will
975  *             be removed in 1.19.
976  */
977 function wfErrorExit() {
978         wfDeprecated( __FUNCTION__ );
979         wfAbruptExit( true );
980 }
981
982 /**
983  * Print a simple message and die, returning nonzero to the shell if any.
984  * Plain die() fails to return nonzero to the shell if you pass a string.
985  * @param $msg String
986  */
987 function wfDie( $msg = '' ) {
988         echo $msg;
989         die( 1 );
990 }
991
992 /**
993  * Throw a debugging exception. This function previously once exited the process,
994  * but now throws an exception instead, with similar results.
995  *
996  * @param $msg String: message shown when dieing.
997  */
998 function wfDebugDieBacktrace( $msg = '' ) {
999         throw new MWException( $msg );
1000 }
1001
1002 /**
1003  * Fetch server name for use in error reporting etc.
1004  * Use real server name if available, so we know which machine
1005  * in a server farm generated the current page.
1006  * @return string
1007  */
1008 function wfHostname() {
1009         static $host;
1010         if ( is_null( $host ) ) {
1011                 if ( function_exists( 'posix_uname' ) ) {
1012                         // This function not present on Windows
1013                         $uname = @posix_uname();
1014                 } else {
1015                         $uname = false;
1016                 }
1017                 if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
1018                         $host = $uname['nodename'];
1019                 } elseif ( getenv( 'COMPUTERNAME' ) ) {
1020                         # Windows computer name
1021                         $host = getenv( 'COMPUTERNAME' );
1022                 } else {
1023                         # This may be a virtual server.
1024                         $host = $_SERVER['SERVER_NAME'];
1025                 }
1026         }
1027         return $host;
1028 }
1029
1030 /**
1031  * Returns a HTML comment with the elapsed time since request.
1032  * This method has no side effects.
1033  * @return string
1034  */
1035 function wfReportTime() {
1036         global $wgRequestTime, $wgShowHostnames;
1037
1038         $now = wfTime();
1039         $elapsed = $now - $wgRequestTime;
1040
1041         return $wgShowHostnames
1042                 ? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
1043                 : sprintf( '<!-- Served in %01.3f secs. -->', $elapsed );
1044 }
1045
1046 /**
1047  * Safety wrapper for debug_backtrace().
1048  *
1049  * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
1050  * murky circumstances, which may be triggered in part by stub objects
1051  * or other fancy talkin'.
1052  *
1053  * Will return an empty array if Zend Optimizer is detected or if
1054  * debug_backtrace is disabled, otherwise the output from
1055  * debug_backtrace() (trimmed).
1056  *
1057  * @return array of backtrace information
1058  */
1059 function wfDebugBacktrace() {
1060         static $disabled = null;
1061
1062         if( extension_loaded( 'Zend Optimizer' ) ) {
1063                 wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
1064                 return array();
1065         }
1066
1067         if ( is_null( $disabled ) ) {
1068                 $disabled = false;
1069                 $functions = explode( ',', ini_get( 'disable_functions' ) );
1070                 $functions = array_map( 'trim', $functions );
1071                 $functions = array_map( 'strtolower', $functions );
1072                 if ( in_array( 'debug_backtrace', $functions ) ) {
1073                         wfDebug( "debug_backtrace is in disabled_functions\n" );
1074                         $disabled = true;
1075                 }
1076         }
1077         if ( $disabled ) {
1078                 return array();
1079         }
1080
1081         return array_slice( debug_backtrace(), 1 );
1082 }
1083
1084 function wfBacktrace() {
1085         global $wgCommandLineMode;
1086
1087         if ( $wgCommandLineMode ) {
1088                 $msg = '';
1089         } else {
1090                 $msg = "<ul>\n";
1091         }
1092         $backtrace = wfDebugBacktrace();
1093         foreach( $backtrace as $call ) {
1094                 if( isset( $call['file'] ) ) {
1095                         $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
1096                         $file = $f[count( $f ) - 1];
1097                 } else {
1098                         $file = '-';
1099                 }
1100                 if( isset( $call['line'] ) ) {
1101                         $line = $call['line'];
1102                 } else {
1103                         $line = '-';
1104                 }
1105                 if ( $wgCommandLineMode ) {
1106                         $msg .= "$file line $line calls ";
1107                 } else {
1108                         $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
1109                 }
1110                 if( !empty( $call['class'] ) ) {
1111                         $msg .= $call['class'] . '::';
1112                 }
1113                 $msg .= $call['function'] . '()';
1114
1115                 if ( $wgCommandLineMode ) {
1116                         $msg .= "\n";
1117                 } else {
1118                         $msg .= "</li>\n";
1119                 }
1120         }
1121         if ( $wgCommandLineMode ) {
1122                 $msg .= "\n";
1123         } else {
1124                 $msg .= "</ul>\n";
1125         }
1126
1127         return $msg;
1128 }
1129
1130
1131 /* Some generic result counters, pulled out of SearchEngine */
1132
1133
1134 /**
1135  * @todo document
1136  */
1137 function wfShowingResults( $offset, $limit ) {
1138         global $wgLang;
1139         return wfMsgExt(
1140                 'showingresults',
1141                 array( 'parseinline' ),
1142                 $wgLang->formatNum( $limit ),
1143                 $wgLang->formatNum( $offset + 1 )
1144         );
1145 }
1146
1147 /**
1148  * @todo document
1149  */
1150 function wfShowingResultsNum( $offset, $limit, $num ) {
1151         global $wgLang;
1152         return wfMsgExt(
1153                 'showingresultsnum',
1154                 array( 'parseinline' ),
1155                 $wgLang->formatNum( $limit ),
1156                 $wgLang->formatNum( $offset + 1 ),
1157                 $wgLang->formatNum( $num )
1158         );
1159 }
1160
1161 /**
1162  * Generate (prev x| next x) (20|50|100...) type links for paging
1163  * @param $offset String
1164  * @param $limit Integer
1165  * @param $link String
1166  * @param $query String: optional URL query parameter string
1167  * @param $atend Bool: optional param for specified if this is the last page
1168  */
1169 function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
1170         global $wgLang;
1171         $fmtLimit = $wgLang->formatNum( $limit );
1172         // FIXME: Why on earth this needs one message for the text and another one for tooltip??
1173         # Get prev/next link display text
1174         $prev = wfMsgExt( 'prevn', array( 'parsemag', 'escape' ), $fmtLimit );
1175         $next = wfMsgExt( 'nextn', array( 'parsemag', 'escape' ), $fmtLimit );
1176         # Get prev/next link title text
1177         $pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit );
1178         $nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit );
1179         # Fetch the title object
1180         if( is_object( $link ) ) {
1181                 $title =& $link;
1182         } else {
1183                 $title = Title::newFromText( $link );
1184                 if( is_null( $title ) ) {
1185                         return false;
1186                 }
1187         }
1188         # Make 'previous' link
1189         if( 0 != $offset ) {
1190                 $po = $offset - $limit;
1191                 $po = max( $po, 0 );
1192                 $q = "limit={$limit}&offset={$po}";
1193                 if( $query != '' ) {
1194                         $q .= '&' . $query;
1195                 }
1196                 $plink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$pTitle}\" class=\"mw-prevlink\">{$prev}</a>";
1197         } else {
1198                 $plink = $prev;
1199         }
1200         # Make 'next' link
1201         $no = $offset + $limit;
1202         $q = "limit={$limit}&offset={$no}";
1203         if( $query != '' ) {
1204                 $q .= '&' . $query;
1205         }
1206         if( $atend ) {
1207                 $nlink = $next;
1208         } else {
1209                 $nlink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$nTitle}\" class=\"mw-nextlink\">{$next}</a>";
1210         }
1211         # Make links to set number of items per page
1212         $nums = $wgLang->pipeList( array(
1213                 wfNumLink( $offset, 20, $title, $query ),
1214                 wfNumLink( $offset, 50, $title, $query ),
1215                 wfNumLink( $offset, 100, $title, $query ),
1216                 wfNumLink( $offset, 250, $title, $query ),
1217                 wfNumLink( $offset, 500, $title, $query )
1218         ) );
1219         return wfMsgHtml( 'viewprevnext', $plink, $nlink, $nums );
1220 }
1221
1222 /**
1223  * Generate links for (20|50|100...) items-per-page links
1224  * @param $offset String
1225  * @param $limit Integer
1226  * @param $title Title
1227  * @param $query String: optional URL query parameter string
1228  */
1229 function wfNumLink( $offset, $limit, $title, $query = '' ) {
1230         global $wgLang;
1231         if( $query == '' ) {
1232                 $q = '';
1233         } else {
1234                 $q = $query.'&';
1235         }
1236         $q .= "limit={$limit}&offset={$offset}";
1237         $fmtLimit = $wgLang->formatNum( $limit );
1238         $lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $limit );
1239         $s = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>";
1240         return $s;
1241 }
1242
1243 /**
1244  * @todo document
1245  * @todo FIXME: we may want to blacklist some broken browsers
1246  *
1247  * @return bool Whereas client accept gzip compression
1248  */
1249 function wfClientAcceptsGzip() {
1250         static $result = null;
1251         if ( $result === null ) {
1252                 $result = false;
1253                 if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
1254                         # FIXME: we may want to blacklist some broken browsers
1255                         $m = array();
1256                         if( preg_match(
1257                                 '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
1258                                 $_SERVER['HTTP_ACCEPT_ENCODING'],
1259                                 $m )
1260                         )
1261                         {
1262                                 if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
1263                                         $result = false;
1264                                         return $result;
1265                                 }
1266                                 wfDebug( " accepts gzip\n" );
1267                                 $result = true;
1268                         }
1269                 }
1270         }
1271         return $result;
1272 }
1273
1274 /**
1275  * Obtain the offset and limit values from the request string;
1276  * used in special pages
1277  *
1278  * @param $deflimit Default limit if none supplied
1279  * @param $optionname Name of a user preference to check against
1280  * @return array
1281  *
1282  */
1283 function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
1284         global $wgRequest;
1285         return $wgRequest->getLimitOffset( $deflimit, $optionname );
1286 }
1287
1288 /**
1289  * Escapes the given text so that it may be output using addWikiText()
1290  * without any linking, formatting, etc. making its way through. This
1291  * is achieved by substituting certain characters with HTML entities.
1292  * As required by the callers, <nowiki> is not used. It currently does
1293  * not filter out characters which have special meaning only at the
1294  * start of a line, such as "*".
1295  *
1296  * @param $text String: text to be escaped
1297  */
1298 function wfEscapeWikiText( $text ) {
1299         $text = str_replace(
1300                 array( '[',     '|',      ']',     '\'',    'ISBN ',
1301                         'RFC ',     '://',     "\n=",     '{{',           '}}' ),
1302                 array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;',
1303                         'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;', '&#125;&#125;' ),
1304                 htmlspecialchars( $text )
1305         );
1306         return $text;
1307 }
1308
1309 /**
1310  * @todo document
1311  */
1312 function wfQuotedPrintable( $string, $charset = '' ) {
1313         # Probably incomplete; see RFC 2045
1314         if( empty( $charset ) ) {
1315                 global $wgInputEncoding;
1316                 $charset = $wgInputEncoding;
1317         }
1318         $charset = strtoupper( $charset );
1319         $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
1320
1321         $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
1322         $replace = $illegal . '\t ?_';
1323         if( !preg_match( "/[$illegal]/", $string ) ) {
1324                 return $string;
1325         }
1326         $out = "=?$charset?Q?";
1327         $out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
1328         $out .= '?=';
1329         return $out;
1330 }
1331
1332
1333 /**
1334  * @todo document
1335  * @return float
1336  */
1337 function wfTime() {
1338         return microtime( true );
1339 }
1340
1341 /**
1342  * Sets dest to source and returns the original value of dest
1343  * If source is NULL, it just returns the value, it doesn't set the variable
1344  */
1345 function wfSetVar( &$dest, $source ) {
1346         $temp = $dest;
1347         if ( !is_null( $source ) ) {
1348                 $dest = $source;
1349         }
1350         return $temp;
1351 }
1352
1353 /**
1354  * As for wfSetVar except setting a bit
1355  */
1356 function wfSetBit( &$dest, $bit, $state = true ) {
1357         $temp = (bool)( $dest & $bit );
1358         if ( !is_null( $state ) ) {
1359                 if ( $state ) {
1360                         $dest |= $bit;
1361                 } else {
1362                         $dest &= ~$bit;
1363                 }
1364         }
1365         return $temp;
1366 }
1367
1368 /**
1369  * This function takes two arrays as input, and returns a CGI-style string, e.g.
1370  * "days=7&limit=100". Options in the first array override options in the second.
1371  * Options set to "" will not be output.
1372  */
1373 function wfArrayToCGI( $array1, $array2 = null ) {
1374         if ( !is_null( $array2 ) ) {
1375                 $array1 = $array1 + $array2;
1376         }
1377
1378         $cgi = '';
1379         foreach ( $array1 as $key => $value ) {
1380                 if ( $value !== '' ) {
1381                         if ( $cgi != '' ) {
1382                                 $cgi .= '&';
1383                         }
1384                         if ( is_array( $value ) ) {
1385                                 $firstTime = true;
1386                                 foreach ( $value as $v ) {
1387                                         $cgi .= ( $firstTime ? '' : '&') .
1388                                                 urlencode( $key . '[]' ) . '=' .
1389                                                 urlencode( $v );
1390                                         $firstTime = false;
1391                                 }
1392                         } else {
1393                                 if ( is_object( $value ) ) {
1394                                         $value = $value->__toString();
1395                                 }
1396                                 $cgi .= urlencode( $key ) . '=' .
1397                                         urlencode( $value );
1398                         }
1399                 }
1400         }
1401         return $cgi;
1402 }
1403
1404 /**
1405  * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
1406  * its argument and returns the same string in array form.  This allows compa-
1407  * tibility with legacy functions that accept raw query strings instead of nice
1408  * arrays.  Of course, keys and values are urldecode()d.  Don't try passing in-
1409  * valid query strings, or it will explode.
1410  *
1411  * @param $query String: query string
1412  * @return array Array version of input
1413  */
1414 function wfCgiToArray( $query ) {
1415         if( isset( $query[0] ) && $query[0] == '?' ) {
1416                 $query = substr( $query, 1 );
1417         }
1418         $bits = explode( '&', $query );
1419         $ret = array();
1420         foreach( $bits as $bit ) {
1421                 if( $bit === '' ) {
1422                         continue;
1423                 }
1424                 list( $key, $value ) = explode( '=', $bit );
1425                 $key = urldecode( $key );
1426                 $value = urldecode( $value );
1427                 $ret[$key] = $value;
1428         }
1429         return $ret;
1430 }
1431
1432 /**
1433  * Append a query string to an existing URL, which may or may not already
1434  * have query string parameters already. If so, they will be combined.
1435  *
1436  * @param $url String
1437  * @param $query Mixed: string or associative array
1438  * @return string
1439  */
1440 function wfAppendQuery( $url, $query ) {
1441         if ( is_array( $query ) ) {
1442                 $query = wfArrayToCGI( $query );
1443         }
1444         if( $query != '' ) {
1445                 if( false === strpos( $url, '?' ) ) {
1446                         $url .= '?';
1447                 } else {
1448                         $url .= '&';
1449                 }
1450                 $url .= $query;
1451         }
1452         return $url;
1453 }
1454
1455 /**
1456  * Expand a potentially local URL to a fully-qualified URL.  Assumes $wgServer
1457  * and $wgProto are correct.
1458  *
1459  * @todo this won't work with current-path-relative URLs
1460  * like "subdir/foo.html", etc.
1461  *
1462  * @param $url String: either fully-qualified or a local path + query
1463  * @return string Fully-qualified URL
1464  */
1465 function wfExpandUrl( $url ) {
1466         if( substr( $url, 0, 2 ) == '//' ) {
1467                 global $wgProto;
1468                 return $wgProto . ':' . $url;
1469         } elseif( substr( $url, 0, 1 ) == '/' ) {
1470                 global $wgServer;
1471                 return $wgServer . $url;
1472         } else {
1473                 return $url;
1474         }
1475 }
1476
1477 /**
1478  * Windows-compatible version of escapeshellarg()
1479  * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
1480  * function puts single quotes in regardless of OS.
1481  *
1482  * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
1483  * earlier distro releases of PHP)
1484  */
1485 function wfEscapeShellArg( ) {
1486         wfInitShellLocale();
1487
1488         $args = func_get_args();
1489         $first = true;
1490         $retVal = '';
1491         foreach ( $args as $arg ) {
1492                 if ( !$first ) {
1493                         $retVal .= ' ';
1494                 } else {
1495                         $first = false;
1496                 }
1497
1498                 if ( wfIsWindows() ) {
1499                         // Escaping for an MSVC-style command line parser
1500                         // Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
1501                         // Double the backslashes before any double quotes. Escape the double quotes.
1502                         $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
1503                         $arg = '';
1504                         $iteration = 0;
1505                         foreach ( $tokens as $token ) {
1506                                 if ( $iteration % 2 == 1 ) {
1507                                         // Delimiter, a double quote preceded by zero or more slashes
1508                                         $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
1509                                 } elseif ( $iteration % 4 == 2 ) {
1510                                         // ^ in $token will be outside quotes, need to be escaped
1511                                         $arg .= str_replace( '^', '^^', $token );
1512                                 } else { // $iteration % 4 == 0
1513                                         // ^ in $token will appear inside double quotes, so leave as is
1514                                         $arg .= $token;
1515                                 }
1516                                 $iteration++;
1517                         }
1518                         // Double the backslashes before the end of the string, because
1519                         // we will soon add a quote
1520                         $m = array();
1521                         if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
1522                                 $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
1523                         }
1524
1525                         // Add surrounding quotes
1526                         $retVal .= '"' . $arg . '"';
1527                 } else {
1528                         $retVal .= escapeshellarg( $arg );
1529                 }
1530         }
1531         return $retVal;
1532 }
1533
1534 /**
1535  * wfMerge attempts to merge differences between three texts.
1536  * Returns true for a clean merge and false for failure or a conflict.
1537  */
1538 function wfMerge( $old, $mine, $yours, &$result ) {
1539         global $wgDiff3;
1540
1541         # This check may also protect against code injection in
1542         # case of broken installations.
1543         wfSuppressWarnings();
1544         $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
1545         wfRestoreWarnings();
1546
1547         if( !$haveDiff3 ) {
1548                 wfDebug( "diff3 not found\n" );
1549                 return false;
1550         }
1551
1552         # Make temporary files
1553         $td = wfTempDir();
1554         $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1555         $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
1556         $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
1557
1558         fwrite( $oldtextFile, $old );
1559         fclose( $oldtextFile );
1560         fwrite( $mytextFile, $mine );
1561         fclose( $mytextFile );
1562         fwrite( $yourtextFile, $yours );
1563         fclose( $yourtextFile );
1564
1565         # Check for a conflict
1566         $cmd = $wgDiff3 . ' -a --overlap-only ' .
1567                 wfEscapeShellArg( $mytextName ) . ' ' .
1568                 wfEscapeShellArg( $oldtextName ) . ' ' .
1569                 wfEscapeShellArg( $yourtextName );
1570         $handle = popen( $cmd, 'r' );
1571
1572         if( fgets( $handle, 1024 ) ) {
1573                 $conflict = true;
1574         } else {
1575                 $conflict = false;
1576         }
1577         pclose( $handle );
1578
1579         # Merge differences
1580         $cmd = $wgDiff3 . ' -a -e --merge ' .
1581                 wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
1582         $handle = popen( $cmd, 'r' );
1583         $result = '';
1584         do {
1585                 $data = fread( $handle, 8192 );
1586                 if ( strlen( $data ) == 0 ) {
1587                         break;
1588                 }
1589                 $result .= $data;
1590         } while ( true );
1591         pclose( $handle );
1592         unlink( $mytextName );
1593         unlink( $oldtextName );
1594         unlink( $yourtextName );
1595
1596         if ( $result === '' && $old !== '' && !$conflict ) {
1597                 wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
1598                 $conflict = true;
1599         }
1600         return !$conflict;
1601 }
1602
1603 /**
1604  * Returns unified plain-text diff of two texts.
1605  * Useful for machine processing of diffs.
1606  * @param $before String: the text before the changes.
1607  * @param $after String: the text after the changes.
1608  * @param $params String: command-line options for the diff command.
1609  * @return String: unified diff of $before and $after
1610  */
1611 function wfDiff( $before, $after, $params = '-u' ) {
1612         if ( $before == $after ) {
1613                 return '';
1614         }
1615
1616         global $wgDiff;
1617         wfSuppressWarnings();
1618         $haveDiff = $wgDiff && file_exists( $wgDiff );
1619         wfRestoreWarnings();
1620
1621         # This check may also protect against code injection in
1622         # case of broken installations.
1623         if( !$haveDiff ) {
1624                 wfDebug( "diff executable not found\n" );
1625                 $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
1626                 $format = new UnifiedDiffFormatter();
1627                 return $format->format( $diffs );
1628         }
1629
1630         # Make temporary files
1631         $td = wfTempDir();
1632         $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1633         $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
1634
1635         fwrite( $oldtextFile, $before );
1636         fclose( $oldtextFile );
1637         fwrite( $newtextFile, $after );
1638         fclose( $newtextFile );
1639
1640         // Get the diff of the two files
1641         $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
1642
1643         $h = popen( $cmd, 'r' );
1644
1645         $diff = '';
1646
1647         do {
1648                 $data = fread( $h, 8192 );
1649                 if ( strlen( $data ) == 0 ) {
1650                         break;
1651                 }
1652                 $diff .= $data;
1653         } while ( true );
1654
1655         // Clean up
1656         pclose( $h );
1657         unlink( $oldtextName );
1658         unlink( $newtextName );
1659
1660         // Kill the --- and +++ lines. They're not useful.
1661         $diff_lines = explode( "\n", $diff );
1662         if ( strpos( $diff_lines[0], '---' ) === 0 ) {
1663                 unset( $diff_lines[0] );
1664         }
1665         if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
1666                 unset( $diff_lines[1] );
1667         }
1668
1669         $diff = implode( "\n", $diff_lines );
1670
1671         return $diff;
1672 }
1673
1674 /**
1675  * A wrapper around the PHP function var_export().
1676  * Either print it or add it to the regular output ($wgOut).
1677  *
1678  * @param $var A PHP variable to dump.
1679  */
1680 function wfVarDump( $var ) {
1681         global $wgOut;
1682         $s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
1683         if ( headers_sent() || !@is_object( $wgOut ) ) {
1684                 print $s;
1685         } else {
1686                 $wgOut->addHTML( $s );
1687         }
1688 }
1689
1690 /**
1691  * Provide a simple HTTP error.
1692  */
1693 function wfHttpError( $code, $label, $desc ) {
1694         global $wgOut;
1695         $wgOut->disable();
1696         header( "HTTP/1.0 $code $label" );
1697         header( "Status: $code $label" );
1698         $wgOut->sendCacheControl();
1699
1700         header( 'Content-type: text/html; charset=utf-8' );
1701         print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
1702                 '<html><head><title>' .
1703                 htmlspecialchars( $label ) .
1704                 '</title></head><body><h1>' .
1705                 htmlspecialchars( $label ) .
1706                 '</h1><p>' .
1707                 nl2br( htmlspecialchars( $desc ) ) .
1708                 "</p></body></html>\n";
1709 }
1710
1711 /**
1712  * Clear away any user-level output buffers, discarding contents.
1713  *
1714  * Suitable for 'starting afresh', for instance when streaming
1715  * relatively large amounts of data without buffering, or wanting to
1716  * output image files without ob_gzhandler's compression.
1717  *
1718  * The optional $resetGzipEncoding parameter controls suppression of
1719  * the Content-Encoding header sent by ob_gzhandler; by default it
1720  * is left. See comments for wfClearOutputBuffers() for why it would
1721  * be used.
1722  *
1723  * Note that some PHP configuration options may add output buffer
1724  * layers which cannot be removed; these are left in place.
1725  *
1726  * @param $resetGzipEncoding Bool
1727  */
1728 function wfResetOutputBuffers( $resetGzipEncoding = true ) {
1729         if( $resetGzipEncoding ) {
1730                 // Suppress Content-Encoding and Content-Length
1731                 // headers from 1.10+s wfOutputHandler
1732                 global $wgDisableOutputCompression;
1733                 $wgDisableOutputCompression = true;
1734         }
1735         while( $status = ob_get_status() ) {
1736                 if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
1737                         // Probably from zlib.output_compression or other
1738                         // PHP-internal setting which can't be removed.
1739                         //
1740                         // Give up, and hope the result doesn't break
1741                         // output behavior.
1742                         break;
1743                 }
1744                 if( !ob_end_clean() ) {
1745                         // Could not remove output buffer handler; abort now
1746                         // to avoid getting in some kind of infinite loop.
1747                         break;
1748                 }
1749                 if( $resetGzipEncoding ) {
1750                         if( $status['name'] == 'ob_gzhandler' ) {
1751                                 // Reset the 'Content-Encoding' field set by this handler
1752                                 // so we can start fresh.
1753                                 if ( function_exists( 'header_remove' ) ) {
1754                                         // Available since PHP 5.3.0
1755                                         header_remove( 'Content-Encoding' );
1756                                 } else {
1757                                         // We need to provide a valid content-coding. See bug 28069
1758                                         header( 'Content-Encoding: identity' );
1759                                 }
1760                                 break;
1761                         }
1762                 }
1763         }
1764 }
1765
1766 /**
1767  * More legible than passing a 'false' parameter to wfResetOutputBuffers():
1768  *
1769  * Clear away output buffers, but keep the Content-Encoding header
1770  * produced by ob_gzhandler, if any.
1771  *
1772  * This should be used for HTTP 304 responses, where you need to
1773  * preserve the Content-Encoding header of the real result, but
1774  * also need to suppress the output of ob_gzhandler to keep to spec
1775  * and avoid breaking Firefox in rare cases where the headers and
1776  * body are broken over two packets.
1777  */
1778 function wfClearOutputBuffers() {
1779         wfResetOutputBuffers( false );
1780 }
1781
1782 /**
1783  * Converts an Accept-* header into an array mapping string values to quality
1784  * factors
1785  */
1786 function wfAcceptToPrefs( $accept, $def = '*/*' ) {
1787         # No arg means accept anything (per HTTP spec)
1788         if( !$accept ) {
1789                 return array( $def => 1.0 );
1790         }
1791
1792         $prefs = array();
1793
1794         $parts = explode( ',', $accept );
1795
1796         foreach( $parts as $part ) {
1797                 # FIXME: doesn't deal with params like 'text/html; level=1'
1798                 @list( $value, $qpart ) = explode( ';', trim( $part ) );
1799                 $match = array();
1800                 if( !isset( $qpart ) ) {
1801                         $prefs[$value] = 1.0;
1802                 } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
1803                         $prefs[$value] = floatval( $match[1] );
1804                 }
1805         }
1806
1807         return $prefs;
1808 }
1809
1810 /**
1811  * Checks if a given MIME type matches any of the keys in the given
1812  * array. Basic wildcards are accepted in the array keys.
1813  *
1814  * Returns the matching MIME type (or wildcard) if a match, otherwise
1815  * NULL if no match.
1816  *
1817  * @param $type String
1818  * @param $avail Array
1819  * @return string
1820  * @private
1821  */
1822 function mimeTypeMatch( $type, $avail ) {
1823         if( array_key_exists( $type, $avail ) ) {
1824                 return $type;
1825         } else {
1826                 $parts = explode( '/', $type );
1827                 if( array_key_exists( $parts[0] . '/*', $avail ) ) {
1828                         return $parts[0] . '/*';
1829                 } elseif( array_key_exists( '*/*', $avail ) ) {
1830                         return '*/*';
1831                 } else {
1832                         return null;
1833                 }
1834         }
1835 }
1836
1837 /**
1838  * Returns the 'best' match between a client's requested internet media types
1839  * and the server's list of available types. Each list should be an associative
1840  * array of type to preference (preference is a float between 0.0 and 1.0).
1841  * Wildcards in the types are acceptable.
1842  *
1843  * @param $cprefs Array: client's acceptable type list
1844  * @param $sprefs Array: server's offered types
1845  * @return string
1846  *
1847  * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
1848  * XXX: generalize to negotiate other stuff
1849  */
1850 function wfNegotiateType( $cprefs, $sprefs ) {
1851         $combine = array();
1852
1853         foreach( array_keys($sprefs) as $type ) {
1854                 $parts = explode( '/', $type );
1855                 if( $parts[1] != '*' ) {
1856                         $ckey = mimeTypeMatch( $type, $cprefs );
1857                         if( $ckey ) {
1858                                 $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1859                         }
1860                 }
1861         }
1862
1863         foreach( array_keys( $cprefs ) as $type ) {
1864                 $parts = explode( '/', $type );
1865                 if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
1866                         $skey = mimeTypeMatch( $type, $sprefs );
1867                         if( $skey ) {
1868                                 $combine[$type] = $sprefs[$skey] * $cprefs[$type];
1869                         }
1870                 }
1871         }
1872
1873         $bestq = 0;
1874         $besttype = null;
1875
1876         foreach( array_keys( $combine ) as $type ) {
1877                 if( $combine[$type] > $bestq ) {
1878                         $besttype = $type;
1879                         $bestq = $combine[$type];
1880                 }
1881         }
1882
1883         return $besttype;
1884 }
1885
1886 /**
1887  * Array lookup
1888  * Returns an array where the values in the first array are replaced by the
1889  * values in the second array with the corresponding keys
1890  *
1891  * @return array
1892  */
1893 function wfArrayLookup( $a, $b ) {
1894         return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
1895 }
1896
1897 /**
1898  * Convenience function; returns MediaWiki timestamp for the present time.
1899  * @return string
1900  */
1901 function wfTimestampNow() {
1902         # return NOW
1903         return wfTimestamp( TS_MW, time() );
1904 }
1905
1906 /**
1907  * Reference-counted warning suppression
1908  */
1909 function wfSuppressWarnings( $end = false ) {
1910         static $suppressCount = 0;
1911         static $originalLevel = false;
1912
1913         if ( $end ) {
1914                 if ( $suppressCount ) {
1915                         --$suppressCount;
1916                         if ( !$suppressCount ) {
1917                                 error_reporting( $originalLevel );
1918                         }
1919                 }
1920         } else {
1921                 if ( !$suppressCount ) {
1922                         $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE ) );
1923                 }
1924                 ++$suppressCount;
1925         }
1926 }
1927
1928 /**
1929  * Restore error level to previous value
1930  */
1931 function wfRestoreWarnings() {
1932         wfSuppressWarnings( true );
1933 }
1934
1935 # Autodetect, convert and provide timestamps of various types
1936
1937 /**
1938  * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
1939  */
1940 define( 'TS_UNIX', 0 );
1941
1942 /**
1943  * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
1944  */
1945 define( 'TS_MW', 1 );
1946
1947 /**
1948  * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
1949  */
1950 define( 'TS_DB', 2 );
1951
1952 /**
1953  * RFC 2822 format, for E-mail and HTTP headers
1954  */
1955 define( 'TS_RFC2822', 3 );
1956
1957 /**
1958  * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
1959  *
1960  * This is used by Special:Export
1961  */
1962 define( 'TS_ISO_8601', 4 );
1963
1964 /**
1965  * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
1966  *
1967  * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
1968  *       DateTime tag and page 36 for the DateTimeOriginal and
1969  *       DateTimeDigitized tags.
1970  */
1971 define( 'TS_EXIF', 5 );
1972
1973 /**
1974  * Oracle format time.
1975  */
1976 define( 'TS_ORACLE', 6 );
1977
1978 /**
1979  * Postgres format time.
1980  */
1981 define( 'TS_POSTGRES', 7 );
1982
1983 /**
1984  * DB2 format time
1985  */
1986 define( 'TS_DB2', 8 );
1987
1988 /**
1989  * ISO 8601 basic format with no timezone: 19860209T200000Z
1990  *
1991  * This is used by ResourceLoader
1992  */
1993 define( 'TS_ISO_8601_BASIC', 9 );
1994
1995 /**
1996  * @param $outputtype Mixed: A timestamp in one of the supported formats, the
1997  *                    function will autodetect which format is supplied and act
1998  *                    accordingly.
1999  * @param $ts Mixed: the timestamp to convert or 0 for the current timestamp
2000  * @return Mixed: String / false The same date in the format specified in $outputtype or false
2001  */
2002 function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
2003         $uts = 0;
2004         $da = array();
2005         $strtime = '';
2006
2007         if ( !$ts ) { // We want to catch 0, '', null... but not date strings starting with a letter.
2008                 $uts = time();
2009                 $strtime = "@$uts";
2010         } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
2011                 # TS_DB
2012         } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
2013                 # TS_EXIF
2014         } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
2015                 # TS_MW
2016         } elseif ( preg_match( '/^-?\d{1,13}$/D', $ts ) ) {
2017                 # TS_UNIX
2018                 $uts = $ts;
2019                 $strtime = "@$ts"; // Undocumented?
2020         } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
2021                 # TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
2022                 $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
2023                                 str_replace( '+00:00', 'UTC', $ts ) );
2024         } elseif ( preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
2025                 # TS_ISO_8601
2026         } elseif ( preg_match( '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
2027                 #TS_ISO_8601_BASIC
2028         } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/', $ts, $da ) ) {
2029                 # TS_POSTGRES
2030         } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/', $ts, $da ) ) {
2031                 # TS_POSTGRES
2032         } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.\d\d\d$/',$ts,$da)) {
2033                 # TS_DB2
2034         } elseif ( preg_match( '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' . # Day of week
2035                                                         '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .  # dd Mon yyyy
2036                                                         '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S', $ts ) ) { # hh:mm:ss
2037                 # TS_RFC2822, accepting a trailing comment. See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
2038                 # The regex is a superset of rfc2822 for readability 
2039                 $strtime = strtok( $ts, ';' );
2040         } elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
2041                 # TS_RFC850
2042                 $strtime = $ts;
2043         } elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
2044                 # asctime
2045                 $strtime = $ts;
2046         } else {
2047                 # Bogus value...
2048                 wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
2049                 
2050                 return false;
2051         }
2052
2053
2054
2055         static $formats = array(
2056                 TS_UNIX => 'U',
2057                 TS_MW => 'YmdHis',
2058                 TS_DB => 'Y-m-d H:i:s',
2059                 TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
2060                 TS_ISO_8601_BASIC => 'Ymd\THis\Z',
2061                 TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
2062                 TS_RFC2822 => 'D, d M Y H:i:s',
2063                 TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
2064                 TS_POSTGRES => 'Y-m-d H:i:s',
2065                 TS_DB2 => 'Y-m-d H:i:s',
2066         );
2067
2068         if ( !isset( $formats[$outputtype] ) ) {
2069                 throw new MWException( 'wfTimestamp() called with illegal output type.' );
2070         }
2071
2072         if ( function_exists( "date_create" ) ) {
2073                 if ( count( $da ) ) {
2074                         $ds = sprintf("%04d-%02d-%02dT%02d:%02d:%02d.00+00:00",
2075                                 (int)$da[1], (int)$da[2], (int)$da[3],
2076                                 (int)$da[4], (int)$da[5], (int)$da[6]);
2077
2078                         $d = date_create( $ds, new DateTimeZone( 'GMT' ) );
2079                 } elseif ( $strtime ) {
2080                         $d = date_create( $strtime, new DateTimeZone( 'GMT' ) );
2081                 } else {
2082                         return false;
2083                 }
2084                 
2085                 if ( !$d ) {
2086                         wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
2087                         return false;
2088                 }
2089                 
2090                 $output = $d->format( $formats[$outputtype] );
2091         } else {
2092                 if ( count( $da ) ) {
2093                         // Warning! gmmktime() acts oddly if the month or day is set to 0
2094                         // We may want to handle that explicitly at some point
2095                         $uts = gmmktime( (int)$da[4], (int)$da[5], (int)$da[6],
2096                                 (int)$da[2], (int)$da[3], (int)$da[1] );
2097                 } elseif ( $strtime ) {
2098                         $uts = strtotime( $strtime );
2099                 }
2100
2101                 if ( $uts === false ) {
2102                         wfDebug("wfTimestamp() can't parse the timestamp (non 32-bit time? Update php): $outputtype; $ts\n");
2103                         return false;
2104                 }
2105
2106                 if ( TS_UNIX == $outputtype ) {
2107                         return $uts;
2108                 }
2109                 $output = gmdate( $formats[$outputtype], $uts );
2110         }
2111
2112         if ( ( $outputtype == TS_RFC2822 ) || ( $outputtype == TS_POSTGRES ) ) {
2113                 $output .= ' GMT';
2114         }
2115
2116         return $output;
2117 }
2118
2119 /**
2120  * Return a formatted timestamp, or null if input is null.
2121  * For dealing with nullable timestamp columns in the database.
2122  * @param $outputtype Integer
2123  * @param $ts String
2124  * @return String
2125  */
2126 function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
2127         if( is_null( $ts ) ) {
2128                 return null;
2129         } else {
2130                 return wfTimestamp( $outputtype, $ts );
2131         }
2132 }
2133
2134 /**
2135  * Check if the operating system is Windows
2136  *
2137  * @return Bool: true if it's Windows, False otherwise.
2138  */
2139 function wfIsWindows() {
2140         static $isWindows = null;
2141         if ( $isWindows === null ) {
2142                 $isWindows = substr( php_uname(), 0, 7 ) == 'Windows';
2143         }
2144         return $isWindows;
2145 }
2146
2147 /**
2148  * Swap two variables
2149  */
2150 function swap( &$x, &$y ) {
2151         $z = $x;
2152         $x = $y;
2153         $y = $z;
2154 }
2155
2156 function wfGetCachedNotice( $name ) {
2157         global $wgOut, $wgRenderHashAppend, $parserMemc;
2158         $fname = 'wfGetCachedNotice';
2159         wfProfileIn( $fname );
2160
2161         $needParse = false;
2162
2163         if( $name === 'default' ) {
2164                 // special case
2165                 global $wgSiteNotice;
2166                 $notice = $wgSiteNotice;
2167                 if( empty( $notice ) ) {
2168                         wfProfileOut( $fname );
2169                         return false;
2170                 }
2171         } else {
2172                 $notice = wfMsgForContentNoTrans( $name );
2173                 if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
2174                         wfProfileOut( $fname );
2175                         return( false );
2176                 }
2177         }
2178
2179         // Use the extra hash appender to let eg SSL variants separately cache.
2180         $key = wfMemcKey( $name . $wgRenderHashAppend );
2181         $cachedNotice = $parserMemc->get( $key );
2182         if( is_array( $cachedNotice ) ) {
2183                 if( md5( $notice ) == $cachedNotice['hash'] ) {
2184                         $notice = $cachedNotice['html'];
2185                 } else {
2186                         $needParse = true;
2187                 }
2188         } else {
2189                 $needParse = true;
2190         }
2191
2192         if( $needParse ) {
2193                 if( is_object( $wgOut ) ) {
2194                         $parsed = $wgOut->parse( $notice );
2195                         $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
2196                         $notice = $parsed;
2197                 } else {
2198                         wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' . "\n" );
2199                         $notice = '';
2200                 }
2201         }
2202         $notice = '<div id="localNotice">' .$notice . '</div>';
2203         wfProfileOut( $fname );
2204         return $notice;
2205 }
2206
2207 function wfGetNamespaceNotice() {
2208         global $wgTitle;
2209
2210         # Paranoia
2211         if ( !isset( $wgTitle ) || !is_object( $wgTitle ) ) {
2212                 return '';
2213         }
2214
2215         $fname = 'wfGetNamespaceNotice';
2216         wfProfileIn( $fname );
2217
2218         $key = 'namespacenotice-' . $wgTitle->getNsText();
2219         $namespaceNotice = wfGetCachedNotice( $key );
2220         if ( $namespaceNotice && substr( $namespaceNotice, 0, 7 ) != '<p>&lt;' ) {
2221                 $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . '</div>';
2222         } else {
2223                 $namespaceNotice = '';
2224         }
2225
2226         wfProfileOut( $fname );
2227         return $namespaceNotice;
2228 }
2229
2230 function wfGetSiteNotice() {
2231         global $wgUser;
2232         $fname = 'wfGetSiteNotice';
2233         wfProfileIn( $fname );
2234         $siteNotice = '';
2235
2236         if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
2237                 if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
2238                         $siteNotice = wfGetCachedNotice( 'sitenotice' );
2239                 } else {
2240                         $anonNotice = wfGetCachedNotice( 'anonnotice' );
2241                         if( !$anonNotice ) {
2242                                 $siteNotice = wfGetCachedNotice( 'sitenotice' );
2243                         } else {
2244                                 $siteNotice = $anonNotice;
2245                         }
2246                 }
2247                 if( !$siteNotice ) {
2248                         $siteNotice = wfGetCachedNotice( 'default' );
2249                 }
2250         }
2251
2252         wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
2253         wfProfileOut( $fname );
2254         return $siteNotice;
2255 }
2256
2257 /**
2258  * BC wrapper for MimeMagic::singleton()
2259  * @deprecated No longer needed as of 1.17 (r68836).
2260  */
2261 function &wfGetMimeMagic() {
2262         wfDeprecated( __FUNCTION__ );
2263         return MimeMagic::singleton();
2264 }
2265
2266 /**
2267  * Tries to get the system directory for temporary files. The TMPDIR, TMP, and
2268  * TEMP environment variables are then checked in sequence, and if none are set
2269  * try sys_get_temp_dir() for PHP >= 5.2.1. All else fails, return /tmp for Unix
2270  * or C:\Windows\Temp for Windows and hope for the best.
2271  * It is common to call it with tempnam().
2272  *
2273  * NOTE: When possible, use instead the tmpfile() function to create
2274  * temporary files to avoid race conditions on file creation, etc.
2275  *
2276  * @return String
2277  */
2278 function wfTempDir() {
2279         foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
2280                 $tmp = getenv( $var );
2281                 if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
2282                         return $tmp;
2283                 }
2284         }
2285         if( function_exists( 'sys_get_temp_dir' ) ) {
2286                 return sys_get_temp_dir();
2287         }
2288         # Usual defaults
2289         return wfIsWindows() ? 'C:\Windows\Temp' : '/tmp';
2290 }
2291
2292 /**
2293  * Make directory, and make all parent directories if they don't exist
2294  *
2295  * @param $dir String: full path to directory to create
2296  * @param $mode Integer: chmod value to use, default is $wgDirectoryMode
2297  * @param $caller String: optional caller param for debugging.
2298  * @return bool
2299  */
2300 function wfMkdirParents( $dir, $mode = null, $caller = null ) {
2301         global $wgDirectoryMode;
2302
2303         if ( !is_null( $caller ) ) {
2304                 wfDebug( "$caller: called wfMkdirParents($dir)" );
2305         }
2306
2307         if( strval( $dir ) === '' || file_exists( $dir ) ) {
2308                 return true;
2309         }
2310
2311         $dir = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $dir );
2312
2313         if ( is_null( $mode ) ) {
2314                 $mode = $wgDirectoryMode;
2315         }
2316
2317         // Turn off the normal warning, we're doing our own below
2318         wfSuppressWarnings();
2319         $ok = mkdir( $dir, $mode, true ); // PHP5 <3
2320         wfRestoreWarnings();
2321
2322         if( !$ok ) {
2323                 // PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
2324                 trigger_error( __FUNCTION__ . ": failed to mkdir \"$dir\" mode $mode", E_USER_WARNING );
2325         }
2326         return $ok;
2327 }
2328
2329 /**
2330  * Increment a statistics counter
2331  */
2332 function wfIncrStats( $key ) {
2333         global $wgStatsMethod;
2334
2335         if( $wgStatsMethod == 'udp' ) {
2336                 global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname;
2337                 static $socket;
2338                 if ( !$socket ) {
2339                         $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
2340                         $statline = "stats/{$wgDBname} - 1 1 1 1 1 -total\n";
2341                         socket_sendto(
2342                                 $socket,
2343                                 $statline,
2344                                 strlen( $statline ),
2345                                 0,
2346                                 $wgUDPProfilerHost,
2347                                 $wgUDPProfilerPort
2348                         );
2349                 }
2350                 $statline = "stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
2351                 wfSuppressWarnings();
2352                 socket_sendto(
2353                         $socket,
2354                         $statline,
2355                         strlen( $statline ),
2356                         0,
2357                         $wgUDPProfilerHost,
2358                         $wgUDPProfilerPort
2359                 );
2360                 wfRestoreWarnings();
2361         } elseif( $wgStatsMethod == 'cache' ) {
2362                 global $wgMemc;
2363                 $key = wfMemcKey( 'stats', $key );
2364                 if ( is_null( $wgMemc->incr( $key ) ) ) {
2365                         $wgMemc->add( $key, 1 );
2366                 }
2367         } else {
2368                 // Disabled
2369         }
2370 }
2371
2372 /**
2373  * @param $nr Mixed: the number to format
2374  * @param $acc Integer: the number of digits after the decimal point, default 2
2375  * @param $round Boolean: whether or not to round the value, default true
2376  * @return float
2377  */
2378 function wfPercent( $nr, $acc = 2, $round = true ) {
2379         $ret = sprintf( "%.${acc}f", $nr );
2380         return $round ? round( $ret, $acc ) . '%' : "$ret%";
2381 }
2382
2383 /**
2384  * Encrypt a username/password.
2385  *
2386  * @param $userid Integer: ID of the user
2387  * @param $password String: password of the user
2388  * @return String: hashed password
2389  * @deprecated Use User::crypt() or User::oldCrypt() instead
2390  */
2391 function wfEncryptPassword( $userid, $password ) {
2392         wfDeprecated(__FUNCTION__);
2393         # Just wrap around User::oldCrypt()
2394         return User::oldCrypt( $password, $userid );
2395 }
2396
2397 /**
2398  * Appends to second array if $value differs from that in $default
2399  */
2400 function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
2401         if ( is_null( $changed ) ) {
2402                 throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
2403         }
2404         if ( $default[$key] !== $value ) {
2405                 $changed[$key] = $value;
2406         }
2407 }
2408
2409 /**
2410  * Since wfMsg() and co suck, they don't return false if the message key they
2411  * looked up didn't exist but a XHTML string, this function checks for the
2412  * nonexistance of messages by looking at wfMsg() output
2413  *
2414  * @param $key      String: the message key looked up
2415  * @return Boolean True if the message *doesn't* exist.
2416  */
2417 function wfEmptyMsg( $key ) {
2418         global $wgMessageCache;
2419         return $wgMessageCache->get( $key, /*useDB*/true, /*content*/false ) === false;
2420 }
2421
2422 /**
2423  * Find out whether or not a mixed variable exists in a string
2424  *
2425  * @param $needle String
2426  * @param $str String
2427  * @return Boolean
2428  */
2429 function in_string( $needle, $str ) {
2430         return strpos( $str, $needle ) !== false;
2431 }
2432
2433 function wfSpecialList( $page, $details ) {
2434         global $wgContLang;
2435         $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : '';
2436         return $page . $details;
2437 }
2438
2439 /**
2440  * Returns a regular expression of url protocols
2441  *
2442  * @return String
2443  */
2444 function wfUrlProtocols() {
2445         global $wgUrlProtocols;
2446
2447         static $retval = null;
2448         if ( !is_null( $retval ) ) {
2449                 return $retval;
2450         }
2451
2452         // Support old-style $wgUrlProtocols strings, for backwards compatibility
2453         // with LocalSettings files from 1.5
2454         if ( is_array( $wgUrlProtocols ) ) {
2455                 $protocols = array();
2456                 foreach ( $wgUrlProtocols as $protocol ) {
2457                         $protocols[] = preg_quote( $protocol, '/' );
2458                 }
2459
2460                 $retval = implode( '|', $protocols );
2461         } else {
2462                 $retval = $wgUrlProtocols;
2463         }
2464         return $retval;
2465 }
2466
2467 /**
2468  * Safety wrapper around ini_get() for boolean settings.
2469  * The values returned from ini_get() are pre-normalized for settings
2470  * set via php.ini or php_flag/php_admin_flag... but *not*
2471  * for those set via php_value/php_admin_value.
2472  *
2473  * It's fairly common for people to use php_value instead of php_flag,
2474  * which can leave you with an 'off' setting giving a false positive
2475  * for code that just takes the ini_get() return value as a boolean.
2476  *
2477  * To make things extra interesting, setting via php_value accepts
2478  * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
2479  * Unrecognized values go false... again opposite PHP's own coercion
2480  * from string to bool.
2481  *
2482  * Luckily, 'properly' set settings will always come back as '0' or '1',
2483  * so we only have to worry about them and the 'improper' settings.
2484  *
2485  * I frickin' hate PHP... :P
2486  *
2487  * @param $setting String
2488  * @return Bool
2489  */
2490 function wfIniGetBool( $setting ) {
2491         $val = ini_get( $setting );
2492         // 'on' and 'true' can't have whitespace around them, but '1' can.
2493         return strtolower( $val ) == 'on'
2494                 || strtolower( $val ) == 'true'
2495                 || strtolower( $val ) == 'yes'
2496                 || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
2497 }
2498
2499 /**
2500  * Wrapper function for PHP's dl(). This doesn't work in most situations from
2501  * PHP 5.3 onward, and is usually disabled in shared environments anyway.
2502  *
2503  * @param $extension String A PHP extension. The file suffix (.so or .dll)
2504  *                          should be omitted
2505  * @return Bool - Whether or not the extension is loaded
2506  */
2507 function wfDl( $extension ) {
2508         if( extension_loaded( $extension ) ) {
2509                 return true;
2510         }
2511
2512         $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
2513                 && wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
2514
2515         if( $canDl ) {
2516                 wfSuppressWarnings();
2517                 dl( $extension . '.' . PHP_SHLIB_SUFFIX );
2518                 wfRestoreWarnings();
2519         }
2520         return extension_loaded( $extension );
2521 }
2522
2523 /**
2524  * Execute a shell command, with time and memory limits mirrored from the PHP
2525  * configuration if supported.
2526  * @param $cmd String Command line, properly escaped for shell.
2527  * @param &$retval optional, will receive the program's exit code.
2528  *                 (non-zero is usually failure)
2529  * @param $environ Array optional environment variables which should be
2530  *                 added to the executed command environment.
2531  * @return collected stdout as a string (trailing newlines stripped)
2532  */
2533 function wfShellExec( $cmd, &$retval = null, $environ = array() ) {
2534         global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
2535
2536         static $disabled;
2537         if ( is_null( $disabled ) ) {
2538                 $disabled = false;
2539                 if( wfIniGetBool( 'safe_mode' ) ) {
2540                         wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
2541                         $disabled = 'safemode';
2542                 } else {
2543                         $functions = explode( ',', ini_get( 'disable_functions' ) );
2544                         $functions = array_map( 'trim', $functions );
2545                         $functions = array_map( 'strtolower', $functions );
2546                         if ( in_array( 'passthru', $functions ) ) {
2547                                 wfDebug( "passthru is in disabled_functions\n" );
2548                                 $disabled = 'passthru';
2549                         }
2550                 }
2551         }
2552         if ( $disabled ) {
2553                 $retval = 1;
2554                 return $disabled == 'safemode' ?
2555                         'Unable to run external programs in safe mode.' :
2556                         'Unable to run external programs, passthru() is disabled.';
2557         }
2558
2559         wfInitShellLocale();
2560
2561         $envcmd = '';
2562         foreach( $environ as $k => $v ) {
2563                 if ( wfIsWindows() ) {
2564                         /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
2565                          * appear in the environment variable, so we must use carat escaping as documented in
2566                          * http://technet.microsoft.com/en-us/library/cc723564.aspx
2567                          * Note however that the quote isn't listed there, but is needed, and the parentheses
2568                          * are listed there but doesn't appear to need it.
2569                          */
2570                         $envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
2571                 } else {
2572                         /* Assume this is a POSIX shell, thus required to accept variable assignments before the command
2573                          * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
2574                          */
2575                         $envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
2576                 }
2577         }
2578         $cmd = $envcmd . $cmd;
2579
2580         if ( wfIsWindows() ) {
2581                 if ( version_compare( PHP_VERSION, '5.3.0', '<' ) && /* Fixed in 5.3.0 :) */
2582                         ( version_compare( PHP_VERSION, '5.2.1', '>=' ) || php_uname( 's' ) == 'Windows NT' ) )
2583                 {
2584                         # Hack to work around PHP's flawed invocation of cmd.exe
2585                         # http://news.php.net/php.internals/21796
2586                         # Windows 9x doesn't accept any kind of quotes
2587                         $cmd = '"' . $cmd . '"';
2588                 }
2589         } elseif ( php_uname( 's' ) == 'Linux' ) {
2590                 $time = intval( $wgMaxShellTime );
2591                 $mem = intval( $wgMaxShellMemory );
2592                 $filesize = intval( $wgMaxShellFileSize );
2593
2594                 if ( $time > 0 && $mem > 0 ) {
2595                         $script = "$IP/bin/ulimit4.sh";
2596                         if ( is_executable( $script ) ) {
2597                                 $cmd = '/bin/bash ' . escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
2598                         }
2599                 }
2600         }
2601         wfDebug( "wfShellExec: $cmd\n" );
2602
2603         $retval = 1; // error by default?
2604         ob_start();
2605         passthru( $cmd, $retval );
2606         $output = ob_get_contents();
2607         ob_end_clean();
2608
2609         if ( $retval == 127 ) {
2610                 wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
2611         }
2612         return $output;
2613 }
2614
2615 /**
2616  * Workaround for http://bugs.php.net/bug.php?id=45132
2617  * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
2618  */
2619 function wfInitShellLocale() {
2620         static $done = false;
2621         if ( $done ) {
2622                 return;
2623         }
2624         $done = true;
2625         global $wgShellLocale;
2626         if ( !wfIniGetBool( 'safe_mode' ) ) {
2627                 putenv( "LC_CTYPE=$wgShellLocale" );
2628                 setlocale( LC_CTYPE, $wgShellLocale );
2629         }
2630 }
2631
2632 /**
2633  * This function works like "use VERSION" in Perl, the program will die with a
2634  * backtrace if the current version of PHP is less than the version provided
2635  *
2636  * This is useful for extensions which due to their nature are not kept in sync
2637  * with releases, and might depend on other versions of PHP than the main code
2638  *
2639  * Note: PHP might die due to parsing errors in some cases before it ever
2640  *       manages to call this function, such is life
2641  *
2642  * @see perldoc -f use
2643  *
2644  * @param $req_ver Mixed: the version to check, can be a string, an integer, or
2645  *                 a float
2646  */
2647 function wfUsePHP( $req_ver ) {
2648         $php_ver = PHP_VERSION;
2649
2650         if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
2651                 throw new MWException( "PHP $req_ver required--this is only $php_ver" );
2652         }
2653 }
2654
2655 /**
2656  * This function works like "use VERSION" in Perl except it checks the version
2657  * of MediaWiki, the program will die with a backtrace if the current version
2658  * of MediaWiki is less than the version provided.
2659  *
2660  * This is useful for extensions which due to their nature are not kept in sync
2661  * with releases
2662  *
2663  * @see perldoc -f use
2664  *
2665  * @param $req_ver Mixed: the version to check, can be a string, an integer, or
2666  *                 a float
2667  */
2668 function wfUseMW( $req_ver ) {
2669         global $wgVersion;
2670
2671         if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
2672                 throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
2673         }
2674 }
2675
2676 /**
2677  * Return the final portion of a pathname.
2678  * Reimplemented because PHP5's basename() is buggy with multibyte text.
2679  * http://bugs.php.net/bug.php?id=33898
2680  *
2681  * PHP's basename() only considers '\' a pathchar on Windows and Netware.
2682  * We'll consider it so always, as we don't want \s in our Unix paths either.
2683  *
2684  * @param $path String
2685  * @param $suffix String: to remove if present
2686  * @return String
2687  */
2688 function wfBaseName( $path, $suffix = '' ) {
2689         $encSuffix = ( $suffix == '' )
2690                 ? ''
2691                 : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
2692         $matches = array();
2693         if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
2694                 return $matches[1];
2695         } else {
2696                 return '';
2697         }
2698 }
2699
2700 /**
2701  * Generate a relative path name to the given file.
2702  * May explode on non-matching case-insensitive paths,
2703  * funky symlinks, etc.
2704  *
2705  * @param $path String: absolute destination path including target filename
2706  * @param $from String: Absolute source path, directory only
2707  * @return String
2708  */
2709 function wfRelativePath( $path, $from ) {
2710         // Normalize mixed input on Windows...
2711         $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
2712         $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
2713
2714         // Trim trailing slashes -- fix for drive root
2715         $path = rtrim( $path, DIRECTORY_SEPARATOR );
2716         $from = rtrim( $from, DIRECTORY_SEPARATOR );
2717
2718         $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
2719         $against = explode( DIRECTORY_SEPARATOR, $from );
2720
2721         if( $pieces[0] !== $against[0] ) {
2722                 // Non-matching Windows drive letters?
2723                 // Return a full path.
2724                 return $path;
2725         }
2726
2727         // Trim off common prefix
2728         while( count( $pieces ) && count( $against )
2729                 && $pieces[0] == $against[0] ) {
2730                 array_shift( $pieces );
2731                 array_shift( $against );
2732         }
2733
2734         // relative dots to bump us to the parent
2735         while( count( $against ) ) {
2736                 array_unshift( $pieces, '..' );
2737                 array_shift( $against );
2738         }
2739
2740         array_push( $pieces, wfBaseName( $path ) );
2741
2742         return implode( DIRECTORY_SEPARATOR, $pieces );
2743 }
2744
2745 /**
2746  * Backwards array plus for people who haven't bothered to read the PHP manual
2747  * XXX: will not darn your socks for you.
2748  *
2749  * @param $array1 Array
2750  * @param [$array2, [...]] Arrays
2751  * @return Array
2752  */
2753 function wfArrayMerge( $array1/* ... */ ) {
2754         $args = func_get_args();
2755         $args = array_reverse( $args, true );
2756         $out = array();
2757         foreach ( $args as $arg ) {
2758                 $out += $arg;
2759         }
2760         return $out;
2761 }
2762
2763 /**
2764  * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
2765  * e.g.
2766  *      wfMergeErrorArrays(
2767  *              array( array( 'x' ) ),
2768  *              array( array( 'x', '2' ) ),
2769  *              array( array( 'x' ) ),
2770  *              array( array( 'y') )
2771  *      );
2772  * returns:
2773  *              array(
2774  *              array( 'x', '2' ),
2775  *              array( 'x' ),
2776  *              array( 'y' )
2777  *      )
2778  */
2779 function wfMergeErrorArrays( /*...*/ ) {
2780         $args = func_get_args();
2781         $out = array();
2782         foreach ( $args as $errors ) {
2783                 foreach ( $errors as $params ) {
2784                         # FIXME: sometimes get nested arrays for $params,
2785                         # which leads to E_NOTICEs
2786                         $spec = implode( "\t", $params );
2787                         $out[$spec] = $params;
2788                 }
2789         }
2790         return array_values( $out );
2791 }
2792
2793 /**
2794  * parse_url() work-alike, but non-broken.  Differences:
2795  *
2796  * 1) Does not raise warnings on bad URLs (just returns false)
2797  * 2) Handles protocols that don't use :// (e.g., mailto: and news:) correctly
2798  * 3) Adds a "delimiter" element to the array, either '://' or ':' (see (2))
2799  *
2800  * @param $url String: a URL to parse
2801  * @return Array: bits of the URL in an associative array, per PHP docs
2802  */
2803 function wfParseUrl( $url ) {
2804         global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
2805         wfSuppressWarnings();
2806         $bits = parse_url( $url );
2807         wfRestoreWarnings();
2808         if ( !$bits ) {
2809                 return false;
2810         }
2811
2812         // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
2813         if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
2814                 $bits['delimiter'] = '://';
2815         } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
2816                 $bits['delimiter'] = ':';
2817                 // parse_url detects for news: and mailto: the host part of an url as path
2818                 // We have to correct this wrong detection
2819                 if ( isset( $bits['path'] ) ) {
2820                         $bits['host'] = $bits['path'];
2821                         $bits['path'] = '';
2822                 }
2823         } else {
2824                 return false;
2825         }
2826
2827         return $bits;
2828 }
2829
2830 /**
2831  * Make a URL index, appropriate for the el_index field of externallinks.
2832  */
2833 function wfMakeUrlIndex( $url ) {
2834         $bits = wfParseUrl( $url );
2835
2836         // Reverse the labels in the hostname, convert to lower case
2837         // For emails reverse domainpart only
2838         if ( $bits['scheme'] == 'mailto' ) {
2839                 $mailparts = explode( '@', $bits['host'], 2 );
2840                 if ( count( $mailparts ) === 2 ) {
2841                         $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
2842                 } else {
2843                         // No domain specified, don't mangle it
2844                         $domainpart = '';
2845                 }
2846                 $reversedHost = $domainpart . '@' . $mailparts[0];
2847         } else {
2848                 $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
2849         }
2850         // Add an extra dot to the end
2851         // Why? Is it in wrong place in mailto links?
2852         if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
2853                 $reversedHost .= '.';
2854         }
2855         // Reconstruct the pseudo-URL
2856         $prot = $bits['scheme'];
2857         $index = $prot . $bits['delimiter'] . $reversedHost;
2858         // Leave out user and password. Add the port, path, query and fragment
2859         if ( isset( $bits['port'] ) ) {
2860                 $index .= ':' . $bits['port'];
2861         }
2862         if ( isset( $bits['path'] ) ) {
2863                 $index .= $bits['path'];
2864         } else {
2865                 $index .= '/';
2866         }
2867         if ( isset( $bits['query'] ) ) {
2868                 $index .= '?' . $bits['query'];
2869         }
2870         if ( isset( $bits['fragment'] ) ) {
2871                 $index .= '#' . $bits['fragment'];
2872         }
2873         return $index;
2874 }
2875
2876 /**
2877  * Do any deferred updates and clear the list
2878  *
2879  * @param $commit Boolean: commit after every update to prevent lock contention
2880  */
2881 function wfDoUpdates( $commit = false ) {
2882         global $wgDeferredUpdateList;
2883
2884         wfProfileIn( __METHOD__ );
2885
2886         // No need to get master connections in case of empty updates array
2887         if ( !count( $wgDeferredUpdateList ) ) {
2888                 wfProfileOut( __METHOD__ );
2889                 return;
2890         }
2891
2892         if ( $commit ) {
2893                 $dbw = wfGetDB( DB_MASTER );
2894         }
2895
2896         foreach ( $wgDeferredUpdateList as $update ) {
2897                 $update->doUpdate();
2898
2899                 if ( $commit && $dbw->trxLevel() ) {
2900                         $dbw->commit();
2901                 }
2902         }
2903
2904         $wgDeferredUpdateList = array();
2905         wfProfileOut( __METHOD__ );
2906 }
2907
2908 /**
2909  * Convert an arbitrarily-long digit string from one numeric base
2910  * to another, optionally zero-padding to a minimum column width.
2911  *
2912  * Supports base 2 through 36; digit values 10-36 are represented
2913  * as lowercase letters a-z. Input is case-insensitive.
2914  *
2915  * @param $input String: of digits
2916  * @param $sourceBase Integer: 2-36
2917  * @param $destBase Integer: 2-36
2918  * @param $pad Integer: 1 or greater
2919  * @param $lowercase Boolean
2920  * @return String or false on invalid input
2921  */
2922 function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true ) {
2923         $input = strval( $input );
2924         if( $sourceBase < 2 ||
2925                 $sourceBase > 36 ||
2926                 $destBase < 2 ||
2927                 $destBase > 36 ||
2928                 $pad < 1 ||
2929                 $sourceBase != intval( $sourceBase ) ||
2930                 $destBase != intval( $destBase ) ||
2931                 $pad != intval( $pad ) ||
2932                 !is_string( $input ) ||
2933                 $input == '' ) {
2934                 return false;
2935         }
2936         $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
2937         $inDigits = array();
2938         $outChars = '';
2939
2940         // Decode and validate input string
2941         $input = strtolower( $input );
2942         for( $i = 0; $i < strlen( $input ); $i++ ) {
2943                 $n = strpos( $digitChars, $input{$i} );
2944                 if( $n === false || $n > $sourceBase ) {
2945                         return false;
2946                 }
2947                 $inDigits[] = $n;
2948         }
2949
2950         // Iterate over the input, modulo-ing out an output digit
2951         // at a time until input is gone.
2952         while( count( $inDigits ) ) {
2953                 $work = 0;
2954                 $workDigits = array();
2955
2956                 // Long division...
2957                 foreach( $inDigits as $digit ) {
2958                         $work *= $sourceBase;
2959                         $work += $digit;
2960
2961                         if( $work < $destBase ) {
2962                                 // Gonna need to pull another digit.
2963                                 if( count( $workDigits ) ) {
2964                                         // Avoid zero-padding; this lets us find
2965                                         // the end of the input very easily when
2966                                         // length drops to zero.
2967                                         $workDigits[] = 0;
2968                                 }
2969                         } else {
2970                                 // Finally! Actual division!
2971                                 $workDigits[] = intval( $work / $destBase );
2972
2973                                 // Isn't it annoying that most programming languages
2974                                 // don't have a single divide-and-remainder operator,
2975                                 // even though the CPU implements it that way?
2976                                 $work = $work % $destBase;
2977                         }
2978                 }
2979
2980                 // All that division leaves us with a remainder,
2981                 // which is conveniently our next output digit.
2982                 $outChars .= $digitChars[$work];
2983
2984                 // And we continue!
2985                 $inDigits = $workDigits;
2986         }
2987
2988         while( strlen( $outChars ) < $pad ) {
2989                 $outChars .= '0';
2990         }
2991
2992         return strrev( $outChars );
2993 }
2994
2995 /**
2996  * Create an object with a given name and an array of construct parameters
2997  * @param $name String
2998  * @param $p Array: parameters
2999  */
3000 function wfCreateObject( $name, $p ) {
3001         $p = array_values( $p );
3002         switch ( count( $p ) ) {
3003                 case 0:
3004                         return new $name;
3005                 case 1:
3006                         return new $name( $p[0] );
3007                 case 2:
3008                         return new $name( $p[0], $p[1] );
3009                 case 3:
3010                         return new $name( $p[0], $p[1], $p[2] );
3011                 case 4:
3012                         return new $name( $p[0], $p[1], $p[2], $p[3] );
3013                 case 5:
3014                         return new $name( $p[0], $p[1], $p[2], $p[3], $p[4] );
3015                 case 6:
3016                         return new $name( $p[0], $p[1], $p[2], $p[3], $p[4], $p[5] );
3017                 default:
3018                         throw new MWException( 'Too many arguments to construtor in wfCreateObject' );
3019         }
3020 }
3021
3022 function wfHttpOnlySafe() {
3023         global $wgHttpOnlyBlacklist;
3024         if( !version_compare( '5.2', PHP_VERSION, '<' ) ) {
3025                 return false;
3026         }
3027
3028         if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
3029                 foreach( $wgHttpOnlyBlacklist as $regex ) {
3030                         if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
3031                                 return false;
3032                         }
3033                 }
3034         }
3035
3036         return true;
3037 }
3038
3039 /**
3040  * Initialise php session
3041  */
3042 function wfSetupSession( $sessionId = false ) {
3043         global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain,
3044                         $wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler;
3045         if( $wgSessionsInMemcached ) {
3046                 require_once( 'MemcachedSessions.php' );
3047         } elseif( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
3048                 # Only set this if $wgSessionHandler isn't null and session.save_handler
3049                 # hasn't already been set to the desired value (that causes errors)
3050                 ini_set( 'session.save_handler', $wgSessionHandler );
3051         }
3052         $httpOnlySafe = wfHttpOnlySafe();
3053         wfDebugLog( 'cookie',
3054                 'session_set_cookie_params: "' . implode( '", "',
3055                         array(
3056                                 0,
3057                                 $wgCookiePath,
3058                                 $wgCookieDomain,
3059                                 $wgCookieSecure,
3060                                 $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' );
3061         if( $httpOnlySafe && $wgCookieHttpOnly ) {
3062                 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly );
3063         } else {
3064                 // PHP 5.1 throws warnings if you pass the HttpOnly parameter for 5.2.
3065                 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
3066         }
3067         session_cache_limiter( 'private, must-revalidate' );
3068         if ( $sessionId ) {
3069                 session_id( $sessionId );
3070         }
3071         wfSuppressWarnings();
3072         session_start();
3073         wfRestoreWarnings();
3074 }
3075
3076 /**
3077  * Get an object from the precompiled serialized directory
3078  *
3079  * @return Mixed: the variable on success, false on failure
3080  */
3081 function wfGetPrecompiledData( $name ) {
3082         global $IP;
3083
3084         $file = "$IP/serialized/$name";
3085         if ( file_exists( $file ) ) {
3086                 $blob = file_get_contents( $file );
3087                 if ( $blob ) {
3088                         return unserialize( $blob );
3089                 }
3090         }
3091         return false;
3092 }
3093
3094 function wfGetCaller( $level = 2 ) {
3095         $backtrace = wfDebugBacktrace();
3096         if ( isset( $backtrace[$level] ) ) {
3097                 return wfFormatStackFrame( $backtrace[$level] );
3098         } else {
3099                 $caller = 'unknown';
3100         }
3101         return $caller;
3102 }
3103
3104 /**
3105  * Return a string consisting of callers in the stack. Useful sometimes
3106  * for profiling specific points.
3107  *
3108  * @param $limit The maximum depth of the stack frame to return, or false for
3109  *               the entire stack.
3110  */
3111 function wfGetAllCallers( $limit = 3 ) {
3112         $trace = array_reverse( wfDebugBacktrace() );
3113         if ( !$limit || $limit > count( $trace ) - 1 ) {
3114                 $limit = count( $trace ) - 1;
3115         }
3116         $trace = array_slice( $trace, -$limit - 1, $limit );
3117         return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
3118 }
3119
3120 /**
3121  * Return a string representation of frame
3122  */
3123 function wfFormatStackFrame( $frame ) {
3124         return isset( $frame['class'] ) ?
3125                 $frame['class'] . '::' . $frame['function'] :
3126                 $frame['function'];
3127 }
3128
3129 /**
3130  * Get a cache key
3131  */
3132 function wfMemcKey( /*... */ ) {
3133         $args = func_get_args();
3134         $key = wfWikiID() . ':' . implode( ':', $args );
3135         $key = str_replace( ' ', '_', $key );
3136         return $key;
3137 }
3138
3139 /**
3140  * Get a cache key for a foreign DB
3141  */
3142 function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
3143         $args = array_slice( func_get_args(), 2 );
3144         if ( $prefix ) {
3145                 $key = "$db-$prefix:" . implode( ':', $args );
3146         } else {
3147                 $key = $db . ':' . implode( ':', $args );
3148         }
3149         return $key;
3150 }
3151
3152 /**
3153  * Get an ASCII string identifying this wiki
3154  * This is used as a prefix in memcached keys
3155  */
3156 function wfWikiID() {
3157         global $wgDBprefix, $wgDBname;
3158         if ( $wgDBprefix ) {
3159                 return "$wgDBname-$wgDBprefix";
3160         } else {
3161                 return $wgDBname;
3162         }
3163 }
3164
3165 /**
3166  * Split a wiki ID into DB name and table prefix
3167  */
3168 function wfSplitWikiID( $wiki ) {
3169         $bits = explode( '-', $wiki, 2 );
3170         if ( count( $bits ) < 2 ) {
3171                 $bits[] = '';
3172         }
3173         return $bits;
3174 }
3175
3176 /**
3177  * Get a Database object.
3178  * @param $db Integer: index of the connection to get. May be DB_MASTER for the
3179  *            master (for write queries), DB_SLAVE for potentially lagged read
3180  *            queries, or an integer >= 0 for a particular server.
3181  *
3182  * @param $groups Mixed: query groups. An array of group names that this query
3183  *                belongs to. May contain a single string if the query is only
3184  *                in one group.
3185  *
3186  * @param $wiki String: the wiki ID, or false for the current wiki
3187  *
3188  * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
3189  * will always return the same object, unless the underlying connection or load
3190  * balancer is manually destroyed.
3191  *
3192  * @return DatabaseBase
3193  */
3194 function &wfGetDB( $db, $groups = array(), $wiki = false ) {
3195         return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
3196 }
3197
3198 /**
3199  * Get a load balancer object.
3200  *
3201  * @param $wiki String: wiki ID, or false for the current wiki
3202  * @return LoadBalancer
3203  */
3204 function wfGetLB( $wiki = false ) {
3205         return wfGetLBFactory()->getMainLB( $wiki );
3206 }
3207
3208 /**
3209  * Get the load balancer factory object
3210  */
3211 function &wfGetLBFactory() {
3212         return LBFactory::singleton();
3213 }
3214
3215 /**
3216  * Find a file.
3217  * Shortcut for RepoGroup::singleton()->findFile()
3218  * @param $title String or Title object
3219  * @param $options Associative array of options:
3220  *     time:           requested time for an archived image, or false for the
3221  *                     current version. An image object will be returned which was
3222  *                     created at the specified time.
3223  *
3224  *     ignoreRedirect: If true, do not follow file redirects
3225  *
3226  *     private:        If true, return restricted (deleted) files if the current
3227  *                     user is allowed to view them. Otherwise, such files will not
3228  *                     be found.
3229  *
3230  *     bypassCache:    If true, do not use the process-local cache of File objects
3231  *
3232  * @return File, or false if the file does not exist
3233  */
3234 function wfFindFile( $title, $options = array() ) {
3235         return RepoGroup::singleton()->findFile( $title, $options );
3236 }
3237
3238 /**
3239  * Get an object referring to a locally registered file.
3240  * Returns a valid placeholder object if the file does not exist.
3241  * @param $title Title or String
3242  * @return File, or null if passed an invalid Title
3243  */
3244 function wfLocalFile( $title ) {
3245         return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
3246 }
3247
3248 /**
3249  * Should low-performance queries be disabled?
3250  *
3251  * @return Boolean
3252  */
3253 function wfQueriesMustScale() {
3254         global $wgMiserMode;
3255         return $wgMiserMode
3256                 || ( SiteStats::pages() > 100000
3257                 && SiteStats::edits() > 1000000
3258                 && SiteStats::users() > 10000 );
3259 }
3260
3261 /**
3262  * Get the path to a specified script file, respecting file
3263  * extensions; this is a wrapper around $wgScriptExtension etc.
3264  *
3265  * @param $script String: script filename, sans extension
3266  * @return String
3267  */
3268 function wfScript( $script = 'index' ) {
3269         global $wgScriptPath, $wgScriptExtension;
3270         return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
3271 }
3272
3273 /**
3274  * Get the script URL.
3275  *
3276  * @return script URL
3277  */
3278 function wfGetScriptUrl() {
3279         if( isset( $_SERVER['SCRIPT_NAME'] ) ) {
3280                 #
3281                 # as it was called, minus the query string.
3282                 #
3283                 # Some sites use Apache rewrite rules to handle subdomains,
3284                 # and have PHP set up in a weird way that causes PHP_SELF
3285                 # to contain the rewritten URL instead of the one that the
3286                 # outside world sees.
3287                 #
3288                 # If in this mode, use SCRIPT_URL instead, which mod_rewrite
3289                 # provides containing the "before" URL.
3290                 return $_SERVER['SCRIPT_NAME'];
3291         } else {
3292                 return $_SERVER['URL'];
3293         }
3294 }
3295
3296 /**
3297  * Convenience function converts boolean values into "true"
3298  * or "false" (string) values
3299  *
3300  * @param $value Boolean
3301  * @return String
3302  */
3303 function wfBoolToStr( $value ) {
3304         return $value ? 'true' : 'false';
3305 }
3306
3307 /**
3308  * Load an extension messages file
3309  * @deprecated in 1.16 (warnings in 1.18, removed in ?)
3310  */
3311 function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
3312 }
3313
3314 /**
3315  * Get a platform-independent path to the null file, e.g.
3316  * /dev/null
3317  *
3318  * @return string
3319  */
3320 function wfGetNull() {
3321         return wfIsWindows()
3322                 ? 'NUL'
3323                 : '/dev/null';
3324 }
3325
3326 /**
3327  * Displays a maxlag error
3328  *
3329  * @param $host String: server that lags the most
3330  * @param $lag Integer: maxlag (actual)
3331  * @param $maxLag Integer: maxlag (requested)
3332  */
3333 function wfMaxlagError( $host, $lag, $maxLag ) {
3334         global $wgShowHostnames;
3335         header( 'HTTP/1.1 503 Service Unavailable' );
3336         header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
3337         header( 'X-Database-Lag: ' . intval( $lag ) );
3338         header( 'Content-Type: text/plain' );
3339         if( $wgShowHostnames ) {
3340                 echo "Waiting for $host: $lag seconds lagged\n";
3341         } else {
3342                 echo "Waiting for a database server: $lag seconds lagged\n";
3343         }
3344 }
3345
3346 /**
3347  * Throws a warning that $function is deprecated
3348  * @param $function String
3349  * @return null
3350  */
3351 function wfDeprecated( $function ) {
3352         static $functionsWarned = array();
3353         if ( !isset( $functionsWarned[$function] ) ) {
3354                 $functionsWarned[$function] = true;
3355                 wfWarn( "Use of $function is deprecated.", 2 );
3356         }
3357 }
3358
3359 /**
3360  * Send a warning either to the debug log or in a PHP error depending on
3361  * $wgDevelopmentWarnings
3362  *
3363  * @param $msg String: message to send
3364  * @param $callerOffset Integer: number of itmes to go back in the backtrace to
3365  *        find the correct caller (1 = function calling wfWarn, ...)
3366  * @param $level Integer: PHP error level; only used when $wgDevelopmentWarnings
3367  *        is true
3368  */
3369 function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
3370         $callers = wfDebugBacktrace();
3371         if( isset( $callers[$callerOffset + 1] ) ){
3372                 $callerfunc = $callers[$callerOffset + 1];
3373                 $callerfile = $callers[$callerOffset];
3374                 if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
3375                         $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
3376                 } else {
3377                         $file = '(internal function)';
3378                 }
3379                 $func = '';
3380                 if( isset( $callerfunc['class'] ) ) {
3381                         $func .= $callerfunc['class'] . '::';
3382                 }
3383                 if( isset( $callerfunc['function'] ) ) {
3384                         $func .= $callerfunc['function'];
3385                 }
3386                 $msg .= " [Called from $func in $file]";
3387         }
3388
3389         global $wgDevelopmentWarnings;
3390         if ( $wgDevelopmentWarnings ) {
3391                 trigger_error( $msg, $level );
3392         } else {
3393                 wfDebug( "$msg\n" );
3394         }
3395 }
3396
3397 /**
3398  * Sleep until the worst slave's replication lag is less than or equal to
3399  * $maxLag, in seconds.  Use this when updating very large numbers of rows, as
3400  * in maintenance scripts, to avoid causing too much lag.  Of course, this is
3401  * a no-op if there are no slaves.
3402  *
3403  * Every time the function has to wait for a slave, it will print a message to
3404  * that effect (and then sleep for a little while), so it's probably not best
3405  * to use this outside maintenance scripts in its present form.
3406  *
3407  * @param $maxLag Integer
3408  * @param $wiki mixed Wiki identifier accepted by wfGetLB
3409  * @return null
3410  */
3411 function wfWaitForSlaves( $maxLag, $wiki = false ) {
3412         if( $maxLag ) {
3413                 $lb = wfGetLB( $wiki );
3414                 list( $host, $lag ) = $lb->getMaxLag( $wiki );
3415                 while( $lag > $maxLag ) {
3416                         wfSuppressWarnings();
3417                         $name = gethostbyaddr( $host );
3418                         wfRestoreWarnings();
3419                         if( $name !== false ) {
3420                                 $host = $name;
3421                         }
3422                         print "Waiting for $host (lagged $lag seconds)...\n";
3423                         sleep( $maxLag );
3424                         list( $host, $lag ) = $lb->getMaxLag();
3425                 }
3426         }
3427 }
3428
3429 /**
3430  * Used to be used for outputting text in the installer/updater
3431  * @deprecated Warnings in 1.19, removal in 1.20
3432  */
3433 function wfOut( $s ) {
3434         global $wgCommandLineMode;
3435         if ( $wgCommandLineMode && !defined( 'MEDIAWIKI_INSTALL' ) ) {
3436                 echo $s;
3437         } else {
3438                 echo htmlspecialchars( $s );
3439         }
3440         flush();
3441 }
3442
3443 /**
3444  * Count down from $n to zero on the terminal, with a one-second pause
3445  * between showing each number. For use in command-line scripts.
3446  */
3447 function wfCountDown( $n ) {
3448         for ( $i = $n; $i >= 0; $i-- ) {
3449                 if ( $i != $n ) {
3450                         echo str_repeat( "\x08", strlen( $i + 1 ) );
3451                 }
3452                 echo $i;
3453                 flush();
3454                 if ( $i ) {
3455                         sleep( 1 );
3456                 }
3457         }
3458         echo "\n";
3459 }
3460
3461 /**
3462  * Generate a random 32-character hexadecimal token.
3463  * @param $salt Mixed: some sort of salt, if necessary, to add to random
3464  *              characters before hashing.
3465  */
3466 function wfGenerateToken( $salt = '' ) {
3467         $salt = serialize( $salt );
3468         return md5( mt_rand( 0, 0x7fffffff ) . $salt );
3469 }
3470
3471 /**
3472  * Replace all invalid characters with -
3473  * @param $name Mixed: filename to process
3474  */
3475 function wfStripIllegalFilenameChars( $name ) {
3476         global $wgIllegalFileChars;
3477         $name = wfBaseName( $name );
3478         $name = preg_replace(
3479                 "/[^" . Title::legalChars() . "]" .
3480                         ( $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '' ) .
3481                         "/",
3482                 '-',
3483                 $name
3484         );
3485         return $name;
3486 }
3487
3488 /**
3489  * Insert array into another array after the specified *KEY*
3490  * @param $array Array: The array.
3491  * @param $insert Array: The array to insert.
3492  * @param $after Mixed: The key to insert after
3493  */
3494 function wfArrayInsertAfter( $array, $insert, $after ) {
3495         // Find the offset of the element to insert after.
3496         $keys = array_keys( $array );
3497         $offsetByKey = array_flip( $keys );
3498
3499         $offset = $offsetByKey[$after];
3500
3501         // Insert at the specified offset
3502         $before = array_slice( $array, 0, $offset + 1, true );
3503         $after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
3504
3505         $output = $before + $insert + $after;
3506
3507         return $output;
3508 }
3509
3510 /* Recursively converts the parameter (an object) to an array with the same data */
3511 function wfObjectToArray( $objOrArray, $recursive = true ) {
3512         $array = array();
3513         if( is_object( $objOrArray ) ) {
3514                 $objOrArray = get_object_vars( $objOrArray );
3515         }
3516         foreach ( $objOrArray as $key => $value ) {
3517                 if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
3518                         $value = wfObjectToArray( $value );
3519                 }
3520
3521                 $array[$key] = $value;
3522         }
3523
3524         return $array;
3525 }
3526
3527 /**
3528  * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit;
3529  * @return Integer value memory was set to.
3530  */
3531 function wfMemoryLimit() {
3532         global $wgMemoryLimit;
3533         $memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
3534         if( $memlimit != -1 ) {
3535                 $conflimit = wfShorthandToInteger( $wgMemoryLimit );
3536                 if( $conflimit == -1 ) {
3537                         wfDebug( "Removing PHP's memory limit\n" );
3538                         wfSuppressWarnings();
3539                         ini_set( 'memory_limit', $conflimit );
3540                         wfRestoreWarnings();
3541                         return $conflimit;
3542                 } elseif ( $conflimit > $memlimit ) {
3543                         wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
3544                         wfSuppressWarnings();
3545                         ini_set( 'memory_limit', $conflimit );
3546                         wfRestoreWarnings();
3547                         return $conflimit;
3548                 }
3549         }
3550         return $memlimit;
3551 }
3552
3553 /**
3554  * Converts shorthand byte notation to integer form
3555  * @param $string String
3556  * @return Integer
3557  */
3558 function wfShorthandToInteger( $string = '' ) {
3559         $string = trim( $string );
3560         if( $string === '' ) {
3561                 return -1;
3562         }
3563         $last = $string[strlen( $string ) - 1];
3564         $val = intval( $string );
3565         switch( $last ) {
3566                 case 'g':
3567                 case 'G':
3568                         $val *= 1024;
3569                         // break intentionally missing
3570                 case 'm':
3571                 case 'M':
3572                         $val *= 1024;
3573                         // break intentionally missing
3574                 case 'k':
3575                 case 'K':
3576                         $val *= 1024;
3577         }
3578
3579         return $val;
3580 }
3581
3582 /**
3583  * Get the normalised IETF language tag
3584  * @param $code String: The language code.
3585  * @return $langCode String: The language code which complying with BCP 47 standards.
3586  */
3587 function wfBCP47( $code ) {
3588         $codeSegment = explode( '-', $code );
3589         foreach ( $codeSegment as $segNo => $seg ) {
3590                 if ( count( $codeSegment ) > 0 ) {
3591                         // ISO 3166 country code
3592                         if ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
3593                                 $codeBCP[$segNo] = strtoupper( $seg );
3594                         // ISO 15924 script code
3595                         } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
3596                                 $codeBCP[$segNo] = ucfirst( $seg );
3597                         // Use lowercase for other cases
3598                         } else {
3599                                 $codeBCP[$segNo] = strtolower( $seg );
3600                         }
3601                 } else {
3602                 // Use lowercase for single segment
3603                         $codeBCP[$segNo] = strtolower( $seg );
3604                 }
3605         }
3606         $langCode = implode( '-', $codeBCP );
3607         return $langCode;
3608 }
3609
3610 function wfArrayMap( $function, $input ) {
3611         $ret = array_map( $function, $input );
3612         foreach ( $ret as $key => $value ) {
3613                 $taint = istainted( $input[$key] );
3614                 if ( $taint ) {
3615                         taint( $ret[$key], $taint );
3616                 }
3617         }
3618         return $ret;
3619 }