]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/GlobalFunctions.php
MediaWiki 1.17.3
[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  * If force is true, it will set the value even if source is NULL
1345  */
1346 function wfSetVar( &$dest, $source, $force = false ) {
1347         $temp = $dest;
1348         if ( !is_null( $source ) || $force ) {
1349                 $dest = $source;
1350         }
1351         return $temp;
1352 }
1353
1354 /**
1355  * As for wfSetVar except setting a bit
1356  */
1357 function wfSetBit( &$dest, $bit, $state = true ) {
1358         $temp = (bool)( $dest & $bit );
1359         if ( !is_null( $state ) ) {
1360                 if ( $state ) {
1361                         $dest |= $bit;
1362                 } else {
1363                         $dest &= ~$bit;
1364                 }
1365         }
1366         return $temp;
1367 }
1368
1369 /**
1370  * This function takes two arrays as input, and returns a CGI-style string, e.g.
1371  * "days=7&limit=100". Options in the first array override options in the second.
1372  * Options set to "" will not be output.
1373  */
1374 function wfArrayToCGI( $array1, $array2 = null ) {
1375         if ( !is_null( $array2 ) ) {
1376                 $array1 = $array1 + $array2;
1377         }
1378
1379         $cgi = '';
1380         foreach ( $array1 as $key => $value ) {
1381                 if ( $value !== '' ) {
1382                         if ( $cgi != '' ) {
1383                                 $cgi .= '&';
1384                         }
1385                         if ( is_array( $value ) ) {
1386                                 $firstTime = true;
1387                                 foreach ( $value as $v ) {
1388                                         $cgi .= ( $firstTime ? '' : '&') .
1389                                                 urlencode( $key . '[]' ) . '=' .
1390                                                 urlencode( $v );
1391                                         $firstTime = false;
1392                                 }
1393                         } else {
1394                                 if ( is_object( $value ) ) {
1395                                         $value = $value->__toString();
1396                                 }
1397                                 $cgi .= urlencode( $key ) . '=' .
1398                                         urlencode( $value );
1399                         }
1400                 }
1401         }
1402         return $cgi;
1403 }
1404
1405 /**
1406  * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
1407  * its argument and returns the same string in array form.  This allows compa-
1408  * tibility with legacy functions that accept raw query strings instead of nice
1409  * arrays.  Of course, keys and values are urldecode()d.  Don't try passing in-
1410  * valid query strings, or it will explode.
1411  *
1412  * @param $query String: query string
1413  * @return array Array version of input
1414  */
1415 function wfCgiToArray( $query ) {
1416         if( isset( $query[0] ) && $query[0] == '?' ) {
1417                 $query = substr( $query, 1 );
1418         }
1419         $bits = explode( '&', $query );
1420         $ret = array();
1421         foreach( $bits as $bit ) {
1422                 if( $bit === '' ) {
1423                         continue;
1424                 }
1425                 list( $key, $value ) = explode( '=', $bit );
1426                 $key = urldecode( $key );
1427                 $value = urldecode( $value );
1428                 $ret[$key] = $value;
1429         }
1430         return $ret;
1431 }
1432
1433 /**
1434  * Append a query string to an existing URL, which may or may not already
1435  * have query string parameters already. If so, they will be combined.
1436  *
1437  * @param $url String
1438  * @param $query Mixed: string or associative array
1439  * @return string
1440  */
1441 function wfAppendQuery( $url, $query ) {
1442         if ( is_array( $query ) ) {
1443                 $query = wfArrayToCGI( $query );
1444         }
1445         if( $query != '' ) {
1446                 if( false === strpos( $url, '?' ) ) {
1447                         $url .= '?';
1448                 } else {
1449                         $url .= '&';
1450                 }
1451                 $url .= $query;
1452         }
1453         return $url;
1454 }
1455
1456 /**
1457  * Expand a potentially local URL to a fully-qualified URL.  Assumes $wgServer
1458  * and $wgProto are correct.
1459  *
1460  * @todo this won't work with current-path-relative URLs
1461  * like "subdir/foo.html", etc.
1462  *
1463  * @param $url String: either fully-qualified or a local path + query
1464  * @return string Fully-qualified URL
1465  */
1466 function wfExpandUrl( $url ) {
1467         if( substr( $url, 0, 2 ) == '//' ) {
1468                 global $wgProto;
1469                 return $wgProto . ':' . $url;
1470         } elseif( substr( $url, 0, 1 ) == '/' ) {
1471                 global $wgServer;
1472                 return $wgServer . $url;
1473         } else {
1474                 return $url;
1475         }
1476 }
1477
1478 /**
1479  * Windows-compatible version of escapeshellarg()
1480  * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
1481  * function puts single quotes in regardless of OS.
1482  *
1483  * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
1484  * earlier distro releases of PHP)
1485  */
1486 function wfEscapeShellArg( ) {
1487         wfInitShellLocale();
1488
1489         $args = func_get_args();
1490         $first = true;
1491         $retVal = '';
1492         foreach ( $args as $arg ) {
1493                 if ( !$first ) {
1494                         $retVal .= ' ';
1495                 } else {
1496                         $first = false;
1497                 }
1498
1499                 if ( wfIsWindows() ) {
1500                         // Escaping for an MSVC-style command line parser
1501                         // Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
1502                         // Double the backslashes before any double quotes. Escape the double quotes.
1503                         $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
1504                         $arg = '';
1505                         $iteration = 0;
1506                         foreach ( $tokens as $token ) {
1507                                 if ( $iteration % 2 == 1 ) {
1508                                         // Delimiter, a double quote preceded by zero or more slashes
1509                                         $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
1510                                 } elseif ( $iteration % 4 == 2 ) {
1511                                         // ^ in $token will be outside quotes, need to be escaped
1512                                         $arg .= str_replace( '^', '^^', $token );
1513                                 } else { // $iteration % 4 == 0
1514                                         // ^ in $token will appear inside double quotes, so leave as is
1515                                         $arg .= $token;
1516                                 }
1517                                 $iteration++;
1518                         }
1519                         // Double the backslashes before the end of the string, because
1520                         // we will soon add a quote
1521                         $m = array();
1522                         if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
1523                                 $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
1524                         }
1525
1526                         // Add surrounding quotes
1527                         $retVal .= '"' . $arg . '"';
1528                 } else {
1529                         $retVal .= escapeshellarg( $arg );
1530                 }
1531         }
1532         return $retVal;
1533 }
1534
1535 /**
1536  * wfMerge attempts to merge differences between three texts.
1537  * Returns true for a clean merge and false for failure or a conflict.
1538  */
1539 function wfMerge( $old, $mine, $yours, &$result ) {
1540         global $wgDiff3;
1541
1542         # This check may also protect against code injection in
1543         # case of broken installations.
1544         wfSuppressWarnings();
1545         $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
1546         wfRestoreWarnings();
1547
1548         if( !$haveDiff3 ) {
1549                 wfDebug( "diff3 not found\n" );
1550                 return false;
1551         }
1552
1553         # Make temporary files
1554         $td = wfTempDir();
1555         $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1556         $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
1557         $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
1558
1559         fwrite( $oldtextFile, $old );
1560         fclose( $oldtextFile );
1561         fwrite( $mytextFile, $mine );
1562         fclose( $mytextFile );
1563         fwrite( $yourtextFile, $yours );
1564         fclose( $yourtextFile );
1565
1566         # Check for a conflict
1567         $cmd = $wgDiff3 . ' -a --overlap-only ' .
1568                 wfEscapeShellArg( $mytextName ) . ' ' .
1569                 wfEscapeShellArg( $oldtextName ) . ' ' .
1570                 wfEscapeShellArg( $yourtextName );
1571         $handle = popen( $cmd, 'r' );
1572
1573         if( fgets( $handle, 1024 ) ) {
1574                 $conflict = true;
1575         } else {
1576                 $conflict = false;
1577         }
1578         pclose( $handle );
1579
1580         # Merge differences
1581         $cmd = $wgDiff3 . ' -a -e --merge ' .
1582                 wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
1583         $handle = popen( $cmd, 'r' );
1584         $result = '';
1585         do {
1586                 $data = fread( $handle, 8192 );
1587                 if ( strlen( $data ) == 0 ) {
1588                         break;
1589                 }
1590                 $result .= $data;
1591         } while ( true );
1592         pclose( $handle );
1593         unlink( $mytextName );
1594         unlink( $oldtextName );
1595         unlink( $yourtextName );
1596
1597         if ( $result === '' && $old !== '' && !$conflict ) {
1598                 wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
1599                 $conflict = true;
1600         }
1601         return !$conflict;
1602 }
1603
1604 /**
1605  * Returns unified plain-text diff of two texts.
1606  * Useful for machine processing of diffs.
1607  * @param $before String: the text before the changes.
1608  * @param $after String: the text after the changes.
1609  * @param $params String: command-line options for the diff command.
1610  * @return String: unified diff of $before and $after
1611  */
1612 function wfDiff( $before, $after, $params = '-u' ) {
1613         if ( $before == $after ) {
1614                 return '';
1615         }
1616
1617         global $wgDiff;
1618         wfSuppressWarnings();
1619         $haveDiff = $wgDiff && file_exists( $wgDiff );
1620         wfRestoreWarnings();
1621
1622         # This check may also protect against code injection in
1623         # case of broken installations.
1624         if( !$haveDiff ) {
1625                 wfDebug( "diff executable not found\n" );
1626                 $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
1627                 $format = new UnifiedDiffFormatter();
1628                 return $format->format( $diffs );
1629         }
1630
1631         # Make temporary files
1632         $td = wfTempDir();
1633         $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1634         $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
1635
1636         fwrite( $oldtextFile, $before );
1637         fclose( $oldtextFile );
1638         fwrite( $newtextFile, $after );
1639         fclose( $newtextFile );
1640
1641         // Get the diff of the two files
1642         $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
1643
1644         $h = popen( $cmd, 'r' );
1645
1646         $diff = '';
1647
1648         do {
1649                 $data = fread( $h, 8192 );
1650                 if ( strlen( $data ) == 0 ) {
1651                         break;
1652                 }
1653                 $diff .= $data;
1654         } while ( true );
1655
1656         // Clean up
1657         pclose( $h );
1658         unlink( $oldtextName );
1659         unlink( $newtextName );
1660
1661         // Kill the --- and +++ lines. They're not useful.
1662         $diff_lines = explode( "\n", $diff );
1663         if ( strpos( $diff_lines[0], '---' ) === 0 ) {
1664                 unset( $diff_lines[0] );
1665         }
1666         if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
1667                 unset( $diff_lines[1] );
1668         }
1669
1670         $diff = implode( "\n", $diff_lines );
1671
1672         return $diff;
1673 }
1674
1675 /**
1676  * A wrapper around the PHP function var_export().
1677  * Either print it or add it to the regular output ($wgOut).
1678  *
1679  * @param $var A PHP variable to dump.
1680  */
1681 function wfVarDump( $var ) {
1682         global $wgOut;
1683         $s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
1684         if ( headers_sent() || !@is_object( $wgOut ) ) {
1685                 print $s;
1686         } else {
1687                 $wgOut->addHTML( $s );
1688         }
1689 }
1690
1691 /**
1692  * Provide a simple HTTP error.
1693  */
1694 function wfHttpError( $code, $label, $desc ) {
1695         global $wgOut;
1696         $wgOut->disable();
1697         header( "HTTP/1.0 $code $label" );
1698         header( "Status: $code $label" );
1699         $wgOut->sendCacheControl();
1700
1701         header( 'Content-type: text/html; charset=utf-8' );
1702         print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
1703                 '<html><head><title>' .
1704                 htmlspecialchars( $label ) .
1705                 '</title></head><body><h1>' .
1706                 htmlspecialchars( $label ) .
1707                 '</h1><p>' .
1708                 nl2br( htmlspecialchars( $desc ) ) .
1709                 "</p></body></html>\n";
1710 }
1711
1712 /**
1713  * Clear away any user-level output buffers, discarding contents.
1714  *
1715  * Suitable for 'starting afresh', for instance when streaming
1716  * relatively large amounts of data without buffering, or wanting to
1717  * output image files without ob_gzhandler's compression.
1718  *
1719  * The optional $resetGzipEncoding parameter controls suppression of
1720  * the Content-Encoding header sent by ob_gzhandler; by default it
1721  * is left. See comments for wfClearOutputBuffers() for why it would
1722  * be used.
1723  *
1724  * Note that some PHP configuration options may add output buffer
1725  * layers which cannot be removed; these are left in place.
1726  *
1727  * @param $resetGzipEncoding Bool
1728  */
1729 function wfResetOutputBuffers( $resetGzipEncoding = true ) {
1730         if( $resetGzipEncoding ) {
1731                 // Suppress Content-Encoding and Content-Length
1732                 // headers from 1.10+s wfOutputHandler
1733                 global $wgDisableOutputCompression;
1734                 $wgDisableOutputCompression = true;
1735         }
1736         while( $status = ob_get_status() ) {
1737                 if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
1738                         // Probably from zlib.output_compression or other
1739                         // PHP-internal setting which can't be removed.
1740                         //
1741                         // Give up, and hope the result doesn't break
1742                         // output behavior.
1743                         break;
1744                 }
1745                 if( !ob_end_clean() ) {
1746                         // Could not remove output buffer handler; abort now
1747                         // to avoid getting in some kind of infinite loop.
1748                         break;
1749                 }
1750                 if( $resetGzipEncoding ) {
1751                         if( $status['name'] == 'ob_gzhandler' ) {
1752                                 // Reset the 'Content-Encoding' field set by this handler
1753                                 // so we can start fresh.
1754                                 if ( function_exists( 'header_remove' ) ) {
1755                                         // Available since PHP 5.3.0
1756                                         header_remove( 'Content-Encoding' );
1757                                 } else {
1758                                         // We need to provide a valid content-coding. See bug 28069
1759                                         header( 'Content-Encoding: identity' );
1760                                 }
1761                                 break;
1762                         }
1763                 }
1764         }
1765 }
1766
1767 /**
1768  * More legible than passing a 'false' parameter to wfResetOutputBuffers():
1769  *
1770  * Clear away output buffers, but keep the Content-Encoding header
1771  * produced by ob_gzhandler, if any.
1772  *
1773  * This should be used for HTTP 304 responses, where you need to
1774  * preserve the Content-Encoding header of the real result, but
1775  * also need to suppress the output of ob_gzhandler to keep to spec
1776  * and avoid breaking Firefox in rare cases where the headers and
1777  * body are broken over two packets.
1778  */
1779 function wfClearOutputBuffers() {
1780         wfResetOutputBuffers( false );
1781 }
1782
1783 /**
1784  * Converts an Accept-* header into an array mapping string values to quality
1785  * factors
1786  */
1787 function wfAcceptToPrefs( $accept, $def = '*/*' ) {
1788         # No arg means accept anything (per HTTP spec)
1789         if( !$accept ) {
1790                 return array( $def => 1.0 );
1791         }
1792
1793         $prefs = array();
1794
1795         $parts = explode( ',', $accept );
1796
1797         foreach( $parts as $part ) {
1798                 # FIXME: doesn't deal with params like 'text/html; level=1'
1799                 @list( $value, $qpart ) = explode( ';', trim( $part ) );
1800                 $match = array();
1801                 if( !isset( $qpart ) ) {
1802                         $prefs[$value] = 1.0;
1803                 } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
1804                         $prefs[$value] = floatval( $match[1] );
1805                 }
1806         }
1807
1808         return $prefs;
1809 }
1810
1811 /**
1812  * Checks if a given MIME type matches any of the keys in the given
1813  * array. Basic wildcards are accepted in the array keys.
1814  *
1815  * Returns the matching MIME type (or wildcard) if a match, otherwise
1816  * NULL if no match.
1817  *
1818  * @param $type String
1819  * @param $avail Array
1820  * @return string
1821  * @private
1822  */
1823 function mimeTypeMatch( $type, $avail ) {
1824         if( array_key_exists( $type, $avail ) ) {
1825                 return $type;
1826         } else {
1827                 $parts = explode( '/', $type );
1828                 if( array_key_exists( $parts[0] . '/*', $avail ) ) {
1829                         return $parts[0] . '/*';
1830                 } elseif( array_key_exists( '*/*', $avail ) ) {
1831                         return '*/*';
1832                 } else {
1833                         return null;
1834                 }
1835         }
1836 }
1837
1838 /**
1839  * Returns the 'best' match between a client's requested internet media types
1840  * and the server's list of available types. Each list should be an associative
1841  * array of type to preference (preference is a float between 0.0 and 1.0).
1842  * Wildcards in the types are acceptable.
1843  *
1844  * @param $cprefs Array: client's acceptable type list
1845  * @param $sprefs Array: server's offered types
1846  * @return string
1847  *
1848  * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
1849  * XXX: generalize to negotiate other stuff
1850  */
1851 function wfNegotiateType( $cprefs, $sprefs ) {
1852         $combine = array();
1853
1854         foreach( array_keys($sprefs) as $type ) {
1855                 $parts = explode( '/', $type );
1856                 if( $parts[1] != '*' ) {
1857                         $ckey = mimeTypeMatch( $type, $cprefs );
1858                         if( $ckey ) {
1859                                 $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1860                         }
1861                 }
1862         }
1863
1864         foreach( array_keys( $cprefs ) as $type ) {
1865                 $parts = explode( '/', $type );
1866                 if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
1867                         $skey = mimeTypeMatch( $type, $sprefs );
1868                         if( $skey ) {
1869                                 $combine[$type] = $sprefs[$skey] * $cprefs[$type];
1870                         }
1871                 }
1872         }
1873
1874         $bestq = 0;
1875         $besttype = null;
1876
1877         foreach( array_keys( $combine ) as $type ) {
1878                 if( $combine[$type] > $bestq ) {
1879                         $besttype = $type;
1880                         $bestq = $combine[$type];
1881                 }
1882         }
1883
1884         return $besttype;
1885 }
1886
1887 /**
1888  * Array lookup
1889  * Returns an array where the values in the first array are replaced by the
1890  * values in the second array with the corresponding keys
1891  *
1892  * @return array
1893  */
1894 function wfArrayLookup( $a, $b ) {
1895         return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
1896 }
1897
1898 /**
1899  * Convenience function; returns MediaWiki timestamp for the present time.
1900  * @return string
1901  */
1902 function wfTimestampNow() {
1903         # return NOW
1904         return wfTimestamp( TS_MW, time() );
1905 }
1906
1907 /**
1908  * Reference-counted warning suppression
1909  */
1910 function wfSuppressWarnings( $end = false ) {
1911         static $suppressCount = 0;
1912         static $originalLevel = false;
1913
1914         if ( $end ) {
1915                 if ( $suppressCount ) {
1916                         --$suppressCount;
1917                         if ( !$suppressCount ) {
1918                                 error_reporting( $originalLevel );
1919                         }
1920                 }
1921         } else {
1922                 if ( !$suppressCount ) {
1923                         $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE ) );
1924                 }
1925                 ++$suppressCount;
1926         }
1927 }
1928
1929 /**
1930  * Restore error level to previous value
1931  */
1932 function wfRestoreWarnings() {
1933         wfSuppressWarnings( true );
1934 }
1935
1936 # Autodetect, convert and provide timestamps of various types
1937
1938 /**
1939  * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
1940  */
1941 define( 'TS_UNIX', 0 );
1942
1943 /**
1944  * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
1945  */
1946 define( 'TS_MW', 1 );
1947
1948 /**
1949  * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
1950  */
1951 define( 'TS_DB', 2 );
1952
1953 /**
1954  * RFC 2822 format, for E-mail and HTTP headers
1955  */
1956 define( 'TS_RFC2822', 3 );
1957
1958 /**
1959  * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
1960  *
1961  * This is used by Special:Export
1962  */
1963 define( 'TS_ISO_8601', 4 );
1964
1965 /**
1966  * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
1967  *
1968  * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
1969  *       DateTime tag and page 36 for the DateTimeOriginal and
1970  *       DateTimeDigitized tags.
1971  */
1972 define( 'TS_EXIF', 5 );
1973
1974 /**
1975  * Oracle format time.
1976  */
1977 define( 'TS_ORACLE', 6 );
1978
1979 /**
1980  * Postgres format time.
1981  */
1982 define( 'TS_POSTGRES', 7 );
1983
1984 /**
1985  * DB2 format time
1986  */
1987 define( 'TS_DB2', 8 );
1988
1989 /**
1990  * ISO 8601 basic format with no timezone: 19860209T200000Z
1991  *
1992  * This is used by ResourceLoader
1993  */
1994 define( 'TS_ISO_8601_BASIC', 9 );
1995
1996 /**
1997  * @param $outputtype Mixed: A timestamp in one of the supported formats, the
1998  *                    function will autodetect which format is supplied and act
1999  *                    accordingly.
2000  * @param $ts Mixed: the timestamp to convert or 0 for the current timestamp
2001  * @return Mixed: String / false The same date in the format specified in $outputtype or false
2002  */
2003 function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
2004         $uts = 0;
2005         $da = array();
2006         $strtime = '';
2007
2008         if ( !$ts ) { // We want to catch 0, '', null... but not date strings starting with a letter.
2009                 $uts = time();
2010                 $strtime = "@$uts";
2011         } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
2012                 # TS_DB
2013         } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
2014                 # TS_EXIF
2015         } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
2016                 # TS_MW
2017         } elseif ( preg_match( '/^-?\d{1,13}$/D', $ts ) ) {
2018                 # TS_UNIX
2019                 $uts = $ts;
2020                 $strtime = "@$ts"; // Undocumented?
2021         } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
2022                 # TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
2023                 $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
2024                                 str_replace( '+00:00', 'UTC', $ts ) );
2025         } elseif ( preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
2026                 # TS_ISO_8601
2027         } elseif ( preg_match( '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
2028                 #TS_ISO_8601_BASIC
2029         } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/', $ts, $da ) ) {
2030                 # TS_POSTGRES
2031         } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/', $ts, $da ) ) {
2032                 # TS_POSTGRES
2033         } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.\d\d\d$/',$ts,$da)) {
2034                 # TS_DB2
2035         } elseif ( preg_match( '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' . # Day of week
2036                                                         '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .  # dd Mon yyyy
2037                                                         '[ \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
2038                 # TS_RFC2822, accepting a trailing comment. See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
2039                 # The regex is a superset of rfc2822 for readability 
2040                 $strtime = strtok( $ts, ';' );
2041         } 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 ) ) {
2042                 # TS_RFC850
2043                 $strtime = $ts;
2044         } 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 ) ) {
2045                 # asctime
2046                 $strtime = $ts;
2047         } else {
2048                 # Bogus value...
2049                 wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
2050                 
2051                 return false;
2052         }
2053
2054
2055
2056         static $formats = array(
2057                 TS_UNIX => 'U',
2058                 TS_MW => 'YmdHis',
2059                 TS_DB => 'Y-m-d H:i:s',
2060                 TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
2061                 TS_ISO_8601_BASIC => 'Ymd\THis\Z',
2062                 TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
2063                 TS_RFC2822 => 'D, d M Y H:i:s',
2064                 TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
2065                 TS_POSTGRES => 'Y-m-d H:i:s',
2066                 TS_DB2 => 'Y-m-d H:i:s',
2067         );
2068
2069         if ( !isset( $formats[$outputtype] ) ) {
2070                 throw new MWException( 'wfTimestamp() called with illegal output type.' );
2071         }
2072
2073         if ( function_exists( "date_create" ) ) {
2074                 if ( count( $da ) ) {
2075                         $ds = sprintf("%04d-%02d-%02dT%02d:%02d:%02d.00+00:00",
2076                                 (int)$da[1], (int)$da[2], (int)$da[3],
2077                                 (int)$da[4], (int)$da[5], (int)$da[6]);
2078
2079                         $d = date_create( $ds, new DateTimeZone( 'GMT' ) );
2080                 } elseif ( $strtime ) {
2081                         $d = date_create( $strtime, new DateTimeZone( 'GMT' ) );
2082                 } else {
2083                         return false;
2084                 }
2085                 
2086                 if ( !$d ) {
2087                         wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
2088                         return false;
2089                 }
2090                 
2091                 $output = $d->format( $formats[$outputtype] );
2092         } else {
2093                 if ( count( $da ) ) {
2094                         // Warning! gmmktime() acts oddly if the month or day is set to 0
2095                         // We may want to handle that explicitly at some point
2096                         $uts = gmmktime( (int)$da[4], (int)$da[5], (int)$da[6],
2097                                 (int)$da[2], (int)$da[3], (int)$da[1] );
2098                 } elseif ( $strtime ) {
2099                         $uts = strtotime( $strtime );
2100                 }
2101
2102                 if ( $uts === false ) {
2103                         wfDebug("wfTimestamp() can't parse the timestamp (non 32-bit time? Update php): $outputtype; $ts\n");
2104                         return false;
2105                 }
2106
2107                 if ( TS_UNIX == $outputtype ) {
2108                         return $uts;
2109                 }
2110                 $output = gmdate( $formats[$outputtype], $uts );
2111         }
2112
2113         if ( ( $outputtype == TS_RFC2822 ) || ( $outputtype == TS_POSTGRES ) ) {
2114                 $output .= ' GMT';
2115         }
2116
2117         return $output;
2118 }
2119
2120 /**
2121  * Return a formatted timestamp, or null if input is null.
2122  * For dealing with nullable timestamp columns in the database.
2123  * @param $outputtype Integer
2124  * @param $ts String
2125  * @return String
2126  */
2127 function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
2128         if( is_null( $ts ) ) {
2129                 return null;
2130         } else {
2131                 return wfTimestamp( $outputtype, $ts );
2132         }
2133 }
2134
2135 /**
2136  * Check if the operating system is Windows
2137  *
2138  * @return Bool: true if it's Windows, False otherwise.
2139  */
2140 function wfIsWindows() {
2141         static $isWindows = null;
2142         if ( $isWindows === null ) {
2143                 $isWindows = substr( php_uname(), 0, 7 ) == 'Windows';
2144         }
2145         return $isWindows;
2146 }
2147
2148 /**
2149  * Swap two variables
2150  */
2151 function swap( &$x, &$y ) {
2152         $z = $x;
2153         $x = $y;
2154         $y = $z;
2155 }
2156
2157 function wfGetCachedNotice( $name ) {
2158         global $wgOut, $wgRenderHashAppend, $parserMemc;
2159         $fname = 'wfGetCachedNotice';
2160         wfProfileIn( $fname );
2161
2162         $needParse = false;
2163
2164         if( $name === 'default' ) {
2165                 // special case
2166                 global $wgSiteNotice;
2167                 $notice = $wgSiteNotice;
2168                 if( empty( $notice ) ) {
2169                         wfProfileOut( $fname );
2170                         return false;
2171                 }
2172         } else {
2173                 $notice = wfMsgForContentNoTrans( $name );
2174                 if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
2175                         wfProfileOut( $fname );
2176                         return( false );
2177                 }
2178         }
2179
2180         // Use the extra hash appender to let eg SSL variants separately cache.
2181         $key = wfMemcKey( $name . $wgRenderHashAppend );
2182         $cachedNotice = $parserMemc->get( $key );
2183         if( is_array( $cachedNotice ) ) {
2184                 if( md5( $notice ) == $cachedNotice['hash'] ) {
2185                         $notice = $cachedNotice['html'];
2186                 } else {
2187                         $needParse = true;
2188                 }
2189         } else {
2190                 $needParse = true;
2191         }
2192
2193         if( $needParse ) {
2194                 if( is_object( $wgOut ) ) {
2195                         $parsed = $wgOut->parse( $notice );
2196                         $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
2197                         $notice = $parsed;
2198                 } else {
2199                         wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' . "\n" );
2200                         $notice = '';
2201                 }
2202         }
2203         $notice = '<div id="localNotice">' .$notice . '</div>';
2204         wfProfileOut( $fname );
2205         return $notice;
2206 }
2207
2208 function wfGetNamespaceNotice() {
2209         global $wgTitle;
2210
2211         # Paranoia
2212         if ( !isset( $wgTitle ) || !is_object( $wgTitle ) ) {
2213                 return '';
2214         }
2215
2216         $fname = 'wfGetNamespaceNotice';
2217         wfProfileIn( $fname );
2218
2219         $key = 'namespacenotice-' . $wgTitle->getNsText();
2220         $namespaceNotice = wfGetCachedNotice( $key );
2221         if ( $namespaceNotice && substr( $namespaceNotice, 0, 7 ) != '<p>&lt;' ) {
2222                 $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . '</div>';
2223         } else {
2224                 $namespaceNotice = '';
2225         }
2226
2227         wfProfileOut( $fname );
2228         return $namespaceNotice;
2229 }
2230
2231 function wfGetSiteNotice() {
2232         global $wgUser;
2233         $fname = 'wfGetSiteNotice';
2234         wfProfileIn( $fname );
2235         $siteNotice = '';
2236
2237         if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
2238                 if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
2239                         $siteNotice = wfGetCachedNotice( 'sitenotice' );
2240                 } else {
2241                         $anonNotice = wfGetCachedNotice( 'anonnotice' );
2242                         if( !$anonNotice ) {
2243                                 $siteNotice = wfGetCachedNotice( 'sitenotice' );
2244                         } else {
2245                                 $siteNotice = $anonNotice;
2246                         }
2247                 }
2248                 if( !$siteNotice ) {
2249                         $siteNotice = wfGetCachedNotice( 'default' );
2250                 }
2251         }
2252
2253         wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
2254         wfProfileOut( $fname );
2255         return $siteNotice;
2256 }
2257
2258 /**
2259  * BC wrapper for MimeMagic::singleton()
2260  * @deprecated No longer needed as of 1.17 (r68836).
2261  */
2262 function &wfGetMimeMagic() {
2263         wfDeprecated( __FUNCTION__ );
2264         return MimeMagic::singleton();
2265 }
2266
2267 /**
2268  * Tries to get the system directory for temporary files. The TMPDIR, TMP, and
2269  * TEMP environment variables are then checked in sequence, and if none are set
2270  * try sys_get_temp_dir() for PHP >= 5.2.1. All else fails, return /tmp for Unix
2271  * or C:\Windows\Temp for Windows and hope for the best.
2272  * It is common to call it with tempnam().
2273  *
2274  * NOTE: When possible, use instead the tmpfile() function to create
2275  * temporary files to avoid race conditions on file creation, etc.
2276  *
2277  * @return String
2278  */
2279 function wfTempDir() {
2280         foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
2281                 $tmp = getenv( $var );
2282                 if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
2283                         return $tmp;
2284                 }
2285         }
2286         if( function_exists( 'sys_get_temp_dir' ) ) {
2287                 return sys_get_temp_dir();
2288         }
2289         # Usual defaults
2290         return wfIsWindows() ? 'C:\Windows\Temp' : '/tmp';
2291 }
2292
2293 /**
2294  * Make directory, and make all parent directories if they don't exist
2295  *
2296  * @param $dir String: full path to directory to create
2297  * @param $mode Integer: chmod value to use, default is $wgDirectoryMode
2298  * @param $caller String: optional caller param for debugging.
2299  * @return bool
2300  */
2301 function wfMkdirParents( $dir, $mode = null, $caller = null ) {
2302         global $wgDirectoryMode;
2303
2304         if ( !is_null( $caller ) ) {
2305                 wfDebug( "$caller: called wfMkdirParents($dir)" );
2306         }
2307
2308         if( strval( $dir ) === '' || file_exists( $dir ) ) {
2309                 return true;
2310         }
2311
2312         $dir = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $dir );
2313
2314         if ( is_null( $mode ) ) {
2315                 $mode = $wgDirectoryMode;
2316         }
2317
2318         // Turn off the normal warning, we're doing our own below
2319         wfSuppressWarnings();
2320         $ok = mkdir( $dir, $mode, true ); // PHP5 <3
2321         wfRestoreWarnings();
2322
2323         if( !$ok ) {
2324                 // PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
2325                 trigger_error( __FUNCTION__ . ": failed to mkdir \"$dir\" mode $mode", E_USER_WARNING );
2326         }
2327         return $ok;
2328 }
2329
2330 /**
2331  * Increment a statistics counter
2332  */
2333 function wfIncrStats( $key ) {
2334         global $wgStatsMethod;
2335
2336         if( $wgStatsMethod == 'udp' ) {
2337                 global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname;
2338                 static $socket;
2339                 if ( !$socket ) {
2340                         $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
2341                         $statline = "stats/{$wgDBname} - 1 1 1 1 1 -total\n";
2342                         socket_sendto(
2343                                 $socket,
2344                                 $statline,
2345                                 strlen( $statline ),
2346                                 0,
2347                                 $wgUDPProfilerHost,
2348                                 $wgUDPProfilerPort
2349                         );
2350                 }
2351                 $statline = "stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
2352                 wfSuppressWarnings();
2353                 socket_sendto(
2354                         $socket,
2355                         $statline,
2356                         strlen( $statline ),
2357                         0,
2358                         $wgUDPProfilerHost,
2359                         $wgUDPProfilerPort
2360                 );
2361                 wfRestoreWarnings();
2362         } elseif( $wgStatsMethod == 'cache' ) {
2363                 global $wgMemc;
2364                 $key = wfMemcKey( 'stats', $key );
2365                 if ( is_null( $wgMemc->incr( $key ) ) ) {
2366                         $wgMemc->add( $key, 1 );
2367                 }
2368         } else {
2369                 // Disabled
2370         }
2371 }
2372
2373 /**
2374  * @param $nr Mixed: the number to format
2375  * @param $acc Integer: the number of digits after the decimal point, default 2
2376  * @param $round Boolean: whether or not to round the value, default true
2377  * @return float
2378  */
2379 function wfPercent( $nr, $acc = 2, $round = true ) {
2380         $ret = sprintf( "%.${acc}f", $nr );
2381         return $round ? round( $ret, $acc ) . '%' : "$ret%";
2382 }
2383
2384 /**
2385  * Encrypt a username/password.
2386  *
2387  * @param $userid Integer: ID of the user
2388  * @param $password String: password of the user
2389  * @return String: hashed password
2390  * @deprecated Use User::crypt() or User::oldCrypt() instead
2391  */
2392 function wfEncryptPassword( $userid, $password ) {
2393         wfDeprecated(__FUNCTION__);
2394         # Just wrap around User::oldCrypt()
2395         return User::oldCrypt( $password, $userid );
2396 }
2397
2398 /**
2399  * Appends to second array if $value differs from that in $default
2400  */
2401 function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
2402         if ( is_null( $changed ) ) {
2403                 throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
2404         }
2405         if ( $default[$key] !== $value ) {
2406                 $changed[$key] = $value;
2407         }
2408 }
2409
2410 /**
2411  * Since wfMsg() and co suck, they don't return false if the message key they
2412  * looked up didn't exist but a XHTML string, this function checks for the
2413  * nonexistance of messages by looking at wfMsg() output
2414  *
2415  * @param $key      String: the message key looked up
2416  * @return Boolean True if the message *doesn't* exist.
2417  */
2418 function wfEmptyMsg( $key ) {
2419         global $wgMessageCache;
2420         return $wgMessageCache->get( $key, /*useDB*/true, /*content*/false ) === false;
2421 }
2422
2423 /**
2424  * Find out whether or not a mixed variable exists in a string
2425  *
2426  * @param $needle String
2427  * @param $str String
2428  * @return Boolean
2429  */
2430 function in_string( $needle, $str ) {
2431         return strpos( $str, $needle ) !== false;
2432 }
2433
2434 function wfSpecialList( $page, $details ) {
2435         global $wgContLang;
2436         $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : '';
2437         return $page . $details;
2438 }
2439
2440 /**
2441  * Returns a regular expression of url protocols
2442  *
2443  * @return String
2444  */
2445 function wfUrlProtocols() {
2446         global $wgUrlProtocols;
2447
2448         static $retval = null;
2449         if ( !is_null( $retval ) ) {
2450                 return $retval;
2451         }
2452
2453         // Support old-style $wgUrlProtocols strings, for backwards compatibility
2454         // with LocalSettings files from 1.5
2455         if ( is_array( $wgUrlProtocols ) ) {
2456                 $protocols = array();
2457                 foreach ( $wgUrlProtocols as $protocol ) {
2458                         $protocols[] = preg_quote( $protocol, '/' );
2459                 }
2460
2461                 $retval = implode( '|', $protocols );
2462         } else {
2463                 $retval = $wgUrlProtocols;
2464         }
2465         return $retval;
2466 }
2467
2468 /**
2469  * Safety wrapper around ini_get() for boolean settings.
2470  * The values returned from ini_get() are pre-normalized for settings
2471  * set via php.ini or php_flag/php_admin_flag... but *not*
2472  * for those set via php_value/php_admin_value.
2473  *
2474  * It's fairly common for people to use php_value instead of php_flag,
2475  * which can leave you with an 'off' setting giving a false positive
2476  * for code that just takes the ini_get() return value as a boolean.
2477  *
2478  * To make things extra interesting, setting via php_value accepts
2479  * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
2480  * Unrecognized values go false... again opposite PHP's own coercion
2481  * from string to bool.
2482  *
2483  * Luckily, 'properly' set settings will always come back as '0' or '1',
2484  * so we only have to worry about them and the 'improper' settings.
2485  *
2486  * I frickin' hate PHP... :P
2487  *
2488  * @param $setting String
2489  * @return Bool
2490  */
2491 function wfIniGetBool( $setting ) {
2492         $val = ini_get( $setting );
2493         // 'on' and 'true' can't have whitespace around them, but '1' can.
2494         return strtolower( $val ) == 'on'
2495                 || strtolower( $val ) == 'true'
2496                 || strtolower( $val ) == 'yes'
2497                 || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
2498 }
2499
2500 /**
2501  * Wrapper function for PHP's dl(). This doesn't work in most situations from
2502  * PHP 5.3 onward, and is usually disabled in shared environments anyway.
2503  *
2504  * @param $extension String A PHP extension. The file suffix (.so or .dll)
2505  *                          should be omitted
2506  * @return Bool - Whether or not the extension is loaded
2507  */
2508 function wfDl( $extension ) {
2509         if( extension_loaded( $extension ) ) {
2510                 return true;
2511         }
2512
2513         $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
2514                 && wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
2515
2516         if( $canDl ) {
2517                 wfSuppressWarnings();
2518                 dl( $extension . '.' . PHP_SHLIB_SUFFIX );
2519                 wfRestoreWarnings();
2520         }
2521         return extension_loaded( $extension );
2522 }
2523
2524 /**
2525  * Execute a shell command, with time and memory limits mirrored from the PHP
2526  * configuration if supported.
2527  * @param $cmd String Command line, properly escaped for shell.
2528  * @param &$retval optional, will receive the program's exit code.
2529  *                 (non-zero is usually failure)
2530  * @param $environ Array optional environment variables which should be
2531  *                 added to the executed command environment.
2532  * @return collected stdout as a string (trailing newlines stripped)
2533  */
2534 function wfShellExec( $cmd, &$retval = null, $environ = array() ) {
2535         global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
2536
2537         static $disabled;
2538         if ( is_null( $disabled ) ) {
2539                 $disabled = false;
2540                 if( wfIniGetBool( 'safe_mode' ) ) {
2541                         wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
2542                         $disabled = 'safemode';
2543                 } else {
2544                         $functions = explode( ',', ini_get( 'disable_functions' ) );
2545                         $functions = array_map( 'trim', $functions );
2546                         $functions = array_map( 'strtolower', $functions );
2547                         if ( in_array( 'passthru', $functions ) ) {
2548                                 wfDebug( "passthru is in disabled_functions\n" );
2549                                 $disabled = 'passthru';
2550                         }
2551                 }
2552         }
2553         if ( $disabled ) {
2554                 $retval = 1;
2555                 return $disabled == 'safemode' ?
2556                         'Unable to run external programs in safe mode.' :
2557                         'Unable to run external programs, passthru() is disabled.';
2558         }
2559
2560         wfInitShellLocale();
2561
2562         $envcmd = '';
2563         foreach( $environ as $k => $v ) {
2564                 if ( wfIsWindows() ) {
2565                         /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
2566                          * appear in the environment variable, so we must use carat escaping as documented in
2567                          * http://technet.microsoft.com/en-us/library/cc723564.aspx
2568                          * Note however that the quote isn't listed there, but is needed, and the parentheses
2569                          * are listed there but doesn't appear to need it.
2570                          */
2571                         $envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
2572                 } else {
2573                         /* Assume this is a POSIX shell, thus required to accept variable assignments before the command
2574                          * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
2575                          */
2576                         $envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
2577                 }
2578         }
2579         $cmd = $envcmd . $cmd;
2580
2581         if ( wfIsWindows() ) {
2582                 if ( version_compare( PHP_VERSION, '5.3.0', '<' ) && /* Fixed in 5.3.0 :) */
2583                         ( version_compare( PHP_VERSION, '5.2.1', '>=' ) || php_uname( 's' ) == 'Windows NT' ) )
2584                 {
2585                         # Hack to work around PHP's flawed invocation of cmd.exe
2586                         # http://news.php.net/php.internals/21796
2587                         # Windows 9x doesn't accept any kind of quotes
2588                         $cmd = '"' . $cmd . '"';
2589                 }
2590         } elseif ( php_uname( 's' ) == 'Linux' ) {
2591                 $time = intval( $wgMaxShellTime );
2592                 $mem = intval( $wgMaxShellMemory );
2593                 $filesize = intval( $wgMaxShellFileSize );
2594
2595                 if ( $time > 0 && $mem > 0 ) {
2596                         $script = "$IP/bin/ulimit4.sh";
2597                         if ( is_executable( $script ) ) {
2598                                 $cmd = '/bin/bash ' . escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
2599                         }
2600                 }
2601         }
2602         wfDebug( "wfShellExec: $cmd\n" );
2603
2604         $retval = 1; // error by default?
2605         ob_start();
2606         passthru( $cmd, $retval );
2607         $output = ob_get_contents();
2608         ob_end_clean();
2609
2610         if ( $retval == 127 ) {
2611                 wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
2612         }
2613         return $output;
2614 }
2615
2616 /**
2617  * Workaround for http://bugs.php.net/bug.php?id=45132
2618  * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
2619  */
2620 function wfInitShellLocale() {
2621         static $done = false;
2622         if ( $done ) {
2623                 return;
2624         }
2625         $done = true;
2626         global $wgShellLocale;
2627         if ( !wfIniGetBool( 'safe_mode' ) ) {
2628                 putenv( "LC_CTYPE=$wgShellLocale" );
2629                 setlocale( LC_CTYPE, $wgShellLocale );
2630         }
2631 }
2632
2633 /**
2634  * This function works like "use VERSION" in Perl, the program will die with a
2635  * backtrace if the current version of PHP is less than the version provided
2636  *
2637  * This is useful for extensions which due to their nature are not kept in sync
2638  * with releases, and might depend on other versions of PHP than the main code
2639  *
2640  * Note: PHP might die due to parsing errors in some cases before it ever
2641  *       manages to call this function, such is life
2642  *
2643  * @see perldoc -f use
2644  *
2645  * @param $req_ver Mixed: the version to check, can be a string, an integer, or
2646  *                 a float
2647  */
2648 function wfUsePHP( $req_ver ) {
2649         $php_ver = PHP_VERSION;
2650
2651         if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
2652                 throw new MWException( "PHP $req_ver required--this is only $php_ver" );
2653         }
2654 }
2655
2656 /**
2657  * This function works like "use VERSION" in Perl except it checks the version
2658  * of MediaWiki, the program will die with a backtrace if the current version
2659  * of MediaWiki is less than the version provided.
2660  *
2661  * This is useful for extensions which due to their nature are not kept in sync
2662  * with releases
2663  *
2664  * @see perldoc -f use
2665  *
2666  * @param $req_ver Mixed: the version to check, can be a string, an integer, or
2667  *                 a float
2668  */
2669 function wfUseMW( $req_ver ) {
2670         global $wgVersion;
2671
2672         if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
2673                 throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
2674         }
2675 }
2676
2677 /**
2678  * Return the final portion of a pathname.
2679  * Reimplemented because PHP5's basename() is buggy with multibyte text.
2680  * http://bugs.php.net/bug.php?id=33898
2681  *
2682  * PHP's basename() only considers '\' a pathchar on Windows and Netware.
2683  * We'll consider it so always, as we don't want \s in our Unix paths either.
2684  *
2685  * @param $path String
2686  * @param $suffix String: to remove if present
2687  * @return String
2688  */
2689 function wfBaseName( $path, $suffix = '' ) {
2690         $encSuffix = ( $suffix == '' )
2691                 ? ''
2692                 : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
2693         $matches = array();
2694         if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
2695                 return $matches[1];
2696         } else {
2697                 return '';
2698         }
2699 }
2700
2701 /**
2702  * Generate a relative path name to the given file.
2703  * May explode on non-matching case-insensitive paths,
2704  * funky symlinks, etc.
2705  *
2706  * @param $path String: absolute destination path including target filename
2707  * @param $from String: Absolute source path, directory only
2708  * @return String
2709  */
2710 function wfRelativePath( $path, $from ) {
2711         // Normalize mixed input on Windows...
2712         $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
2713         $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
2714
2715         // Trim trailing slashes -- fix for drive root
2716         $path = rtrim( $path, DIRECTORY_SEPARATOR );
2717         $from = rtrim( $from, DIRECTORY_SEPARATOR );
2718
2719         $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
2720         $against = explode( DIRECTORY_SEPARATOR, $from );
2721
2722         if( $pieces[0] !== $against[0] ) {
2723                 // Non-matching Windows drive letters?
2724                 // Return a full path.
2725                 return $path;
2726         }
2727
2728         // Trim off common prefix
2729         while( count( $pieces ) && count( $against )
2730                 && $pieces[0] == $against[0] ) {
2731                 array_shift( $pieces );
2732                 array_shift( $against );
2733         }
2734
2735         // relative dots to bump us to the parent
2736         while( count( $against ) ) {
2737                 array_unshift( $pieces, '..' );
2738                 array_shift( $against );
2739         }
2740
2741         array_push( $pieces, wfBaseName( $path ) );
2742
2743         return implode( DIRECTORY_SEPARATOR, $pieces );
2744 }
2745
2746 /**
2747  * Backwards array plus for people who haven't bothered to read the PHP manual
2748  * XXX: will not darn your socks for you.
2749  *
2750  * @param $array1 Array
2751  * @param [$array2, [...]] Arrays
2752  * @return Array
2753  */
2754 function wfArrayMerge( $array1/* ... */ ) {
2755         $args = func_get_args();
2756         $args = array_reverse( $args, true );
2757         $out = array();
2758         foreach ( $args as $arg ) {
2759                 $out += $arg;
2760         }
2761         return $out;
2762 }
2763
2764 /**
2765  * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
2766  * e.g.
2767  *      wfMergeErrorArrays(
2768  *              array( array( 'x' ) ),
2769  *              array( array( 'x', '2' ) ),
2770  *              array( array( 'x' ) ),
2771  *              array( array( 'y') )
2772  *      );
2773  * returns:
2774  *              array(
2775  *              array( 'x', '2' ),
2776  *              array( 'x' ),
2777  *              array( 'y' )
2778  *      )
2779  */
2780 function wfMergeErrorArrays( /*...*/ ) {
2781         $args = func_get_args();
2782         $out = array();
2783         foreach ( $args as $errors ) {
2784                 foreach ( $errors as $params ) {
2785                         # FIXME: sometimes get nested arrays for $params,
2786                         # which leads to E_NOTICEs
2787                         $spec = implode( "\t", $params );
2788                         $out[$spec] = $params;
2789                 }
2790         }
2791         return array_values( $out );
2792 }
2793
2794 /**
2795  * parse_url() work-alike, but non-broken.  Differences:
2796  *
2797  * 1) Does not raise warnings on bad URLs (just returns false)
2798  * 2) Handles protocols that don't use :// (e.g., mailto: and news:) correctly
2799  * 3) Adds a "delimiter" element to the array, either '://' or ':' (see (2))
2800  *
2801  * @param $url String: a URL to parse
2802  * @return Array: bits of the URL in an associative array, per PHP docs
2803  */
2804 function wfParseUrl( $url ) {
2805         global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
2806         wfSuppressWarnings();
2807         $bits = parse_url( $url );
2808         wfRestoreWarnings();
2809         if ( !$bits ) {
2810                 return false;
2811         }
2812
2813         // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
2814         if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
2815                 $bits['delimiter'] = '://';
2816         } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
2817                 $bits['delimiter'] = ':';
2818                 // parse_url detects for news: and mailto: the host part of an url as path
2819                 // We have to correct this wrong detection
2820                 if ( isset( $bits['path'] ) ) {
2821                         $bits['host'] = $bits['path'];
2822                         $bits['path'] = '';
2823                 }
2824         } else {
2825                 return false;
2826         }
2827
2828         return $bits;
2829 }
2830
2831 /**
2832  * Make a URL index, appropriate for the el_index field of externallinks.
2833  */
2834 function wfMakeUrlIndex( $url ) {
2835         $bits = wfParseUrl( $url );
2836
2837         // Reverse the labels in the hostname, convert to lower case
2838         // For emails reverse domainpart only
2839         if ( $bits['scheme'] == 'mailto' ) {
2840                 $mailparts = explode( '@', $bits['host'], 2 );
2841                 if ( count( $mailparts ) === 2 ) {
2842                         $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
2843                 } else {
2844                         // No domain specified, don't mangle it
2845                         $domainpart = '';
2846                 }
2847                 $reversedHost = $domainpart . '@' . $mailparts[0];
2848         } else {
2849                 $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
2850         }
2851         // Add an extra dot to the end
2852         // Why? Is it in wrong place in mailto links?
2853         if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
2854                 $reversedHost .= '.';
2855         }
2856         // Reconstruct the pseudo-URL
2857         $prot = $bits['scheme'];
2858         $index = $prot . $bits['delimiter'] . $reversedHost;
2859         // Leave out user and password. Add the port, path, query and fragment
2860         if ( isset( $bits['port'] ) ) {
2861                 $index .= ':' . $bits['port'];
2862         }
2863         if ( isset( $bits['path'] ) ) {
2864                 $index .= $bits['path'];
2865         } else {
2866                 $index .= '/';
2867         }
2868         if ( isset( $bits['query'] ) ) {
2869                 $index .= '?' . $bits['query'];
2870         }
2871         if ( isset( $bits['fragment'] ) ) {
2872                 $index .= '#' . $bits['fragment'];
2873         }
2874         return $index;
2875 }
2876
2877 /**
2878  * Do any deferred updates and clear the list
2879  *
2880  * @param $commit Boolean: commit after every update to prevent lock contention
2881  */
2882 function wfDoUpdates( $commit = false ) {
2883         global $wgDeferredUpdateList;
2884
2885         wfProfileIn( __METHOD__ );
2886
2887         // No need to get master connections in case of empty updates array
2888         if ( !count( $wgDeferredUpdateList ) ) {
2889                 wfProfileOut( __METHOD__ );
2890                 return;
2891         }
2892
2893         if ( $commit ) {
2894                 $dbw = wfGetDB( DB_MASTER );
2895         }
2896
2897         foreach ( $wgDeferredUpdateList as $update ) {
2898                 $update->doUpdate();
2899
2900                 if ( $commit && $dbw->trxLevel() ) {
2901                         $dbw->commit();
2902                 }
2903         }
2904
2905         $wgDeferredUpdateList = array();
2906         wfProfileOut( __METHOD__ );
2907 }
2908
2909 /**
2910  * Convert an arbitrarily-long digit string from one numeric base
2911  * to another, optionally zero-padding to a minimum column width.
2912  *
2913  * Supports base 2 through 36; digit values 10-36 are represented
2914  * as lowercase letters a-z. Input is case-insensitive.
2915  *
2916  * @param $input String: of digits
2917  * @param $sourceBase Integer: 2-36
2918  * @param $destBase Integer: 2-36
2919  * @param $pad Integer: 1 or greater
2920  * @param $lowercase Boolean
2921  * @return String or false on invalid input
2922  */
2923 function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true ) {
2924         $input = strval( $input );
2925         if( $sourceBase < 2 ||
2926                 $sourceBase > 36 ||
2927                 $destBase < 2 ||
2928                 $destBase > 36 ||
2929                 $pad < 1 ||
2930                 $sourceBase != intval( $sourceBase ) ||
2931                 $destBase != intval( $destBase ) ||
2932                 $pad != intval( $pad ) ||
2933                 !is_string( $input ) ||
2934                 $input == '' ) {
2935                 return false;
2936         }
2937         $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
2938         $inDigits = array();
2939         $outChars = '';
2940
2941         // Decode and validate input string
2942         $input = strtolower( $input );
2943         for( $i = 0; $i < strlen( $input ); $i++ ) {
2944                 $n = strpos( $digitChars, $input{$i} );
2945                 if( $n === false || $n > $sourceBase ) {
2946                         return false;
2947                 }
2948                 $inDigits[] = $n;
2949         }
2950
2951         // Iterate over the input, modulo-ing out an output digit
2952         // at a time until input is gone.
2953         while( count( $inDigits ) ) {
2954                 $work = 0;
2955                 $workDigits = array();
2956
2957                 // Long division...
2958                 foreach( $inDigits as $digit ) {
2959                         $work *= $sourceBase;
2960                         $work += $digit;
2961
2962                         if( $work < $destBase ) {
2963                                 // Gonna need to pull another digit.
2964                                 if( count( $workDigits ) ) {
2965                                         // Avoid zero-padding; this lets us find
2966                                         // the end of the input very easily when
2967                                         // length drops to zero.
2968                                         $workDigits[] = 0;
2969                                 }
2970                         } else {
2971                                 // Finally! Actual division!
2972                                 $workDigits[] = intval( $work / $destBase );
2973
2974                                 // Isn't it annoying that most programming languages
2975                                 // don't have a single divide-and-remainder operator,
2976                                 // even though the CPU implements it that way?
2977                                 $work = $work % $destBase;
2978                         }
2979                 }
2980
2981                 // All that division leaves us with a remainder,
2982                 // which is conveniently our next output digit.
2983                 $outChars .= $digitChars[$work];
2984
2985                 // And we continue!
2986                 $inDigits = $workDigits;
2987         }
2988
2989         while( strlen( $outChars ) < $pad ) {
2990                 $outChars .= '0';
2991         }
2992
2993         return strrev( $outChars );
2994 }
2995
2996 /**
2997  * Create an object with a given name and an array of construct parameters
2998  * @param $name String
2999  * @param $p Array: parameters
3000  */
3001 function wfCreateObject( $name, $p ) {
3002         $p = array_values( $p );
3003         switch ( count( $p ) ) {
3004                 case 0:
3005                         return new $name;
3006                 case 1:
3007                         return new $name( $p[0] );
3008                 case 2:
3009                         return new $name( $p[0], $p[1] );
3010                 case 3:
3011                         return new $name( $p[0], $p[1], $p[2] );
3012                 case 4:
3013                         return new $name( $p[0], $p[1], $p[2], $p[3] );
3014                 case 5:
3015                         return new $name( $p[0], $p[1], $p[2], $p[3], $p[4] );
3016                 case 6:
3017                         return new $name( $p[0], $p[1], $p[2], $p[3], $p[4], $p[5] );
3018                 default:
3019                         throw new MWException( 'Too many arguments to construtor in wfCreateObject' );
3020         }
3021 }
3022
3023 function wfHttpOnlySafe() {
3024         global $wgHttpOnlyBlacklist;
3025         if( !version_compare( '5.2', PHP_VERSION, '<' ) ) {
3026                 return false;
3027         }
3028
3029         if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
3030                 foreach( $wgHttpOnlyBlacklist as $regex ) {
3031                         if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
3032                                 return false;
3033                         }
3034                 }
3035         }
3036
3037         return true;
3038 }
3039
3040 /**
3041  * Override session_id before session startup if php's built-in
3042  * session generation code is not secure.
3043  */
3044 function wfFixSessionID() {
3045         // If the cookie or session id is already set we already have a session and should abort
3046         if ( isset( $_COOKIE[ session_name() ] ) || session_id() ) {
3047                 return;
3048         }
3049
3050         // PHP's built-in session entropy is enabled if:
3051         // - entropy_file is set or you're on Windows with php 5.3.3+
3052         // - AND entropy_length is > 0
3053         // We treat it as disabled if it doesn't have an entropy length of at least 32
3054         $entropyEnabled = (
3055                         ( wfIsWindows() && version_compare( PHP_VERSION, '5.3.3', '>=' ) )
3056                         || ini_get( 'session.entropy_file' )
3057                 )
3058                 && intval( ini_get( 'session.entropy_length' ) ) >= 32;
3059         
3060         // If built-in entropy is not enabled or not sufficient override php's built in session id generation code
3061         if ( !$entropyEnabled ) {
3062                 wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, overriding session id generation using our cryptrand source.\n" );
3063                 session_id( MWCryptRand::generateHex( 32 ) );
3064         }
3065 }
3066
3067 /**
3068  * Initialise php session
3069  */
3070 function wfSetupSession( $sessionId = false ) {
3071         global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain,
3072                         $wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler;
3073         if( $wgSessionsInMemcached ) {
3074                 require_once( 'MemcachedSessions.php' );
3075         } elseif( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
3076                 # Only set this if $wgSessionHandler isn't null and session.save_handler
3077                 # hasn't already been set to the desired value (that causes errors)
3078                 ini_set( 'session.save_handler', $wgSessionHandler );
3079         }
3080         $httpOnlySafe = wfHttpOnlySafe();
3081         wfDebugLog( 'cookie',
3082                 'session_set_cookie_params: "' . implode( '", "',
3083                         array(
3084                                 0,
3085                                 $wgCookiePath,
3086                                 $wgCookieDomain,
3087                                 $wgCookieSecure,
3088                                 $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' );
3089         if( $httpOnlySafe && $wgCookieHttpOnly ) {
3090                 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly );
3091         } else {
3092                 // PHP 5.1 throws warnings if you pass the HttpOnly parameter for 5.2.
3093                 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
3094         }
3095         session_cache_limiter( 'private, must-revalidate' );
3096         if ( $sessionId ) {
3097                 session_id( $sessionId );
3098         } else {
3099                 wfFixSessionID();
3100         }
3101         wfSuppressWarnings();
3102         session_start();
3103         wfRestoreWarnings();
3104 }
3105
3106 /**
3107  * Get an object from the precompiled serialized directory
3108  *
3109  * @return Mixed: the variable on success, false on failure
3110  */
3111 function wfGetPrecompiledData( $name ) {
3112         global $IP;
3113
3114         $file = "$IP/serialized/$name";
3115         if ( file_exists( $file ) ) {
3116                 $blob = file_get_contents( $file );
3117                 if ( $blob ) {
3118                         return unserialize( $blob );
3119                 }
3120         }
3121         return false;
3122 }
3123
3124 function wfGetCaller( $level = 2 ) {
3125         $backtrace = wfDebugBacktrace();
3126         if ( isset( $backtrace[$level] ) ) {
3127                 return wfFormatStackFrame( $backtrace[$level] );
3128         } else {
3129                 $caller = 'unknown';
3130         }
3131         return $caller;
3132 }
3133
3134 /**
3135  * Return a string consisting of callers in the stack. Useful sometimes
3136  * for profiling specific points.
3137  *
3138  * @param $limit The maximum depth of the stack frame to return, or false for
3139  *               the entire stack.
3140  */
3141 function wfGetAllCallers( $limit = 3 ) {
3142         $trace = array_reverse( wfDebugBacktrace() );
3143         if ( !$limit || $limit > count( $trace ) - 1 ) {
3144                 $limit = count( $trace ) - 1;
3145         }
3146         $trace = array_slice( $trace, -$limit - 1, $limit );
3147         return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
3148 }
3149
3150 /**
3151  * Return a string representation of frame
3152  */
3153 function wfFormatStackFrame( $frame ) {
3154         return isset( $frame['class'] ) ?
3155                 $frame['class'] . '::' . $frame['function'] :
3156                 $frame['function'];
3157 }
3158
3159 /**
3160  * Get a cache key
3161  */
3162 function wfMemcKey( /*... */ ) {
3163         $args = func_get_args();
3164         $key = wfWikiID() . ':' . implode( ':', $args );
3165         $key = str_replace( ' ', '_', $key );
3166         return $key;
3167 }
3168
3169 /**
3170  * Get a cache key for a foreign DB
3171  */
3172 function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
3173         $args = array_slice( func_get_args(), 2 );
3174         if ( $prefix ) {
3175                 $key = "$db-$prefix:" . implode( ':', $args );
3176         } else {
3177                 $key = $db . ':' . implode( ':', $args );
3178         }
3179         return $key;
3180 }
3181
3182 /**
3183  * Get an ASCII string identifying this wiki
3184  * This is used as a prefix in memcached keys
3185  */
3186 function wfWikiID() {
3187         global $wgDBprefix, $wgDBname;
3188         if ( $wgDBprefix ) {
3189                 return "$wgDBname-$wgDBprefix";
3190         } else {
3191                 return $wgDBname;
3192         }
3193 }
3194
3195 /**
3196  * Split a wiki ID into DB name and table prefix
3197  */
3198 function wfSplitWikiID( $wiki ) {
3199         $bits = explode( '-', $wiki, 2 );
3200         if ( count( $bits ) < 2 ) {
3201                 $bits[] = '';
3202         }
3203         return $bits;
3204 }
3205
3206 /**
3207  * Get a Database object.
3208  * @param $db Integer: index of the connection to get. May be DB_MASTER for the
3209  *            master (for write queries), DB_SLAVE for potentially lagged read
3210  *            queries, or an integer >= 0 for a particular server.
3211  *
3212  * @param $groups Mixed: query groups. An array of group names that this query
3213  *                belongs to. May contain a single string if the query is only
3214  *                in one group.
3215  *
3216  * @param $wiki String: the wiki ID, or false for the current wiki
3217  *
3218  * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
3219  * will always return the same object, unless the underlying connection or load
3220  * balancer is manually destroyed.
3221  *
3222  * @return DatabaseBase
3223  */
3224 function &wfGetDB( $db, $groups = array(), $wiki = false ) {
3225         return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
3226 }
3227
3228 /**
3229  * Get a load balancer object.
3230  *
3231  * @param $wiki String: wiki ID, or false for the current wiki
3232  * @return LoadBalancer
3233  */
3234 function wfGetLB( $wiki = false ) {
3235         return wfGetLBFactory()->getMainLB( $wiki );
3236 }
3237
3238 /**
3239  * Get the load balancer factory object
3240  */
3241 function &wfGetLBFactory() {
3242         return LBFactory::singleton();
3243 }
3244
3245 /**
3246  * Find a file.
3247  * Shortcut for RepoGroup::singleton()->findFile()
3248  * @param $title String or Title object
3249  * @param $options Associative array of options:
3250  *     time:           requested time for an archived image, or false for the
3251  *                     current version. An image object will be returned which was
3252  *                     created at the specified time.
3253  *
3254  *     ignoreRedirect: If true, do not follow file redirects
3255  *
3256  *     private:        If true, return restricted (deleted) files if the current
3257  *                     user is allowed to view them. Otherwise, such files will not
3258  *                     be found.
3259  *
3260  *     bypassCache:    If true, do not use the process-local cache of File objects
3261  *
3262  * @return File, or false if the file does not exist
3263  */
3264 function wfFindFile( $title, $options = array() ) {
3265         return RepoGroup::singleton()->findFile( $title, $options );
3266 }
3267
3268 /**
3269  * Get an object referring to a locally registered file.
3270  * Returns a valid placeholder object if the file does not exist.
3271  * @param $title Title or String
3272  * @return File, or null if passed an invalid Title
3273  */
3274 function wfLocalFile( $title ) {
3275         return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
3276 }
3277
3278 /**
3279  * Should low-performance queries be disabled?
3280  *
3281  * @return Boolean
3282  */
3283 function wfQueriesMustScale() {
3284         global $wgMiserMode;
3285         return $wgMiserMode
3286                 || ( SiteStats::pages() > 100000
3287                 && SiteStats::edits() > 1000000
3288                 && SiteStats::users() > 10000 );
3289 }
3290
3291 /**
3292  * Get the path to a specified script file, respecting file
3293  * extensions; this is a wrapper around $wgScriptExtension etc.
3294  *
3295  * @param $script String: script filename, sans extension
3296  * @return String
3297  */
3298 function wfScript( $script = 'index' ) {
3299         global $wgScriptPath, $wgScriptExtension;
3300         return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
3301 }
3302
3303 /**
3304  * Get the script URL.
3305  *
3306  * @return script URL
3307  */
3308 function wfGetScriptUrl() {
3309         if( isset( $_SERVER['SCRIPT_NAME'] ) ) {
3310                 #
3311                 # as it was called, minus the query string.
3312                 #
3313                 # Some sites use Apache rewrite rules to handle subdomains,
3314                 # and have PHP set up in a weird way that causes PHP_SELF
3315                 # to contain the rewritten URL instead of the one that the
3316                 # outside world sees.
3317                 #
3318                 # If in this mode, use SCRIPT_URL instead, which mod_rewrite
3319                 # provides containing the "before" URL.
3320                 return $_SERVER['SCRIPT_NAME'];
3321         } else {
3322                 return $_SERVER['URL'];
3323         }
3324 }
3325
3326 /**
3327  * Convenience function converts boolean values into "true"
3328  * or "false" (string) values
3329  *
3330  * @param $value Boolean
3331  * @return String
3332  */
3333 function wfBoolToStr( $value ) {
3334         return $value ? 'true' : 'false';
3335 }
3336
3337 /**
3338  * Load an extension messages file
3339  * @deprecated in 1.16 (warnings in 1.18, removed in ?)
3340  */
3341 function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
3342 }
3343
3344 /**
3345  * Get a platform-independent path to the null file, e.g.
3346  * /dev/null
3347  *
3348  * @return string
3349  */
3350 function wfGetNull() {
3351         return wfIsWindows()
3352                 ? 'NUL'
3353                 : '/dev/null';
3354 }
3355
3356 /**
3357  * Displays a maxlag error
3358  *
3359  * @param $host String: server that lags the most
3360  * @param $lag Integer: maxlag (actual)
3361  * @param $maxLag Integer: maxlag (requested)
3362  */
3363 function wfMaxlagError( $host, $lag, $maxLag ) {
3364         global $wgShowHostnames;
3365         header( 'HTTP/1.1 503 Service Unavailable' );
3366         header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
3367         header( 'X-Database-Lag: ' . intval( $lag ) );
3368         header( 'Content-Type: text/plain' );
3369         if( $wgShowHostnames ) {
3370                 echo "Waiting for $host: $lag seconds lagged\n";
3371         } else {
3372                 echo "Waiting for a database server: $lag seconds lagged\n";
3373         }
3374 }
3375
3376 /**
3377  * Throws a warning that $function is deprecated
3378  * @param $function String
3379  * @return null
3380  */
3381 function wfDeprecated( $function ) {
3382         static $functionsWarned = array();
3383         if ( !isset( $functionsWarned[$function] ) ) {
3384                 $functionsWarned[$function] = true;
3385                 wfWarn( "Use of $function is deprecated.", 2 );
3386         }
3387 }
3388
3389 /**
3390  * Send a warning either to the debug log or in a PHP error depending on
3391  * $wgDevelopmentWarnings
3392  *
3393  * @param $msg String: message to send
3394  * @param $callerOffset Integer: number of itmes to go back in the backtrace to
3395  *        find the correct caller (1 = function calling wfWarn, ...)
3396  * @param $level Integer: PHP error level; only used when $wgDevelopmentWarnings
3397  *        is true
3398  */
3399 function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
3400         $callers = wfDebugBacktrace();
3401         if( isset( $callers[$callerOffset + 1] ) ){
3402                 $callerfunc = $callers[$callerOffset + 1];
3403                 $callerfile = $callers[$callerOffset];
3404                 if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
3405                         $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
3406                 } else {
3407                         $file = '(internal function)';
3408                 }
3409                 $func = '';
3410                 if( isset( $callerfunc['class'] ) ) {
3411                         $func .= $callerfunc['class'] . '::';
3412                 }
3413                 if( isset( $callerfunc['function'] ) ) {
3414                         $func .= $callerfunc['function'];
3415                 }
3416                 $msg .= " [Called from $func in $file]";
3417         }
3418
3419         global $wgDevelopmentWarnings;
3420         if ( $wgDevelopmentWarnings ) {
3421                 trigger_error( $msg, $level );
3422         } else {
3423                 wfDebug( "$msg\n" );
3424         }
3425 }
3426
3427 /**
3428  * Sleep until the worst slave's replication lag is less than or equal to
3429  * $maxLag, in seconds.  Use this when updating very large numbers of rows, as
3430  * in maintenance scripts, to avoid causing too much lag.  Of course, this is
3431  * a no-op if there are no slaves.
3432  *
3433  * Every time the function has to wait for a slave, it will print a message to
3434  * that effect (and then sleep for a little while), so it's probably not best
3435  * to use this outside maintenance scripts in its present form.
3436  *
3437  * @param $maxLag Integer
3438  * @param $wiki mixed Wiki identifier accepted by wfGetLB
3439  * @return null
3440  */
3441 function wfWaitForSlaves( $maxLag, $wiki = false ) {
3442         if( $maxLag ) {
3443                 $lb = wfGetLB( $wiki );
3444                 list( $host, $lag ) = $lb->getMaxLag( $wiki );
3445                 while( $lag > $maxLag ) {
3446                         wfSuppressWarnings();
3447                         $name = gethostbyaddr( $host );
3448                         wfRestoreWarnings();
3449                         if( $name !== false ) {
3450                                 $host = $name;
3451                         }
3452                         print "Waiting for $host (lagged $lag seconds)...\n";
3453                         sleep( $maxLag );
3454                         list( $host, $lag ) = $lb->getMaxLag();
3455                 }
3456         }
3457 }
3458
3459 /**
3460  * Used to be used for outputting text in the installer/updater
3461  * @deprecated Warnings in 1.19, removal in 1.20
3462  */
3463 function wfOut( $s ) {
3464         global $wgCommandLineMode;
3465         if ( $wgCommandLineMode && !defined( 'MEDIAWIKI_INSTALL' ) ) {
3466                 echo $s;
3467         } else {
3468                 echo htmlspecialchars( $s );
3469         }
3470         flush();
3471 }
3472
3473 /**
3474  * Count down from $n to zero on the terminal, with a one-second pause
3475  * between showing each number. For use in command-line scripts.
3476  */
3477 function wfCountDown( $n ) {
3478         for ( $i = $n; $i >= 0; $i-- ) {
3479                 if ( $i != $n ) {
3480                         echo str_repeat( "\x08", strlen( $i + 1 ) );
3481                 }
3482                 echo $i;
3483                 flush();
3484                 if ( $i ) {
3485                         sleep( 1 );
3486                 }
3487         }
3488         echo "\n";
3489 }
3490
3491 /**
3492  * Generate a random 32-character hexadecimal token.
3493  * @param $salt Mixed: some sort of salt, if necessary, to add to random
3494  *              characters before hashing.
3495  */
3496 function wfGenerateToken( $salt = '' ) {
3497         $salt = serialize( $salt );
3498         return md5( mt_rand( 0, 0x7fffffff ) . $salt );
3499 }
3500
3501 /**
3502  * Replace all invalid characters with -
3503  * @param $name Mixed: filename to process
3504  */
3505 function wfStripIllegalFilenameChars( $name ) {
3506         global $wgIllegalFileChars;
3507         $name = wfBaseName( $name );
3508         $name = preg_replace(
3509                 "/[^" . Title::legalChars() . "]" .
3510                         ( $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '' ) .
3511                         "/",
3512                 '-',
3513                 $name
3514         );
3515         return $name;
3516 }
3517
3518 /**
3519  * Insert array into another array after the specified *KEY*
3520  * @param $array Array: The array.
3521  * @param $insert Array: The array to insert.
3522  * @param $after Mixed: The key to insert after
3523  */
3524 function wfArrayInsertAfter( $array, $insert, $after ) {
3525         // Find the offset of the element to insert after.
3526         $keys = array_keys( $array );
3527         $offsetByKey = array_flip( $keys );
3528
3529         $offset = $offsetByKey[$after];
3530
3531         // Insert at the specified offset
3532         $before = array_slice( $array, 0, $offset + 1, true );
3533         $after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
3534
3535         $output = $before + $insert + $after;
3536
3537         return $output;
3538 }
3539
3540 /* Recursively converts the parameter (an object) to an array with the same data */
3541 function wfObjectToArray( $objOrArray, $recursive = true ) {
3542         $array = array();
3543         if( is_object( $objOrArray ) ) {
3544                 $objOrArray = get_object_vars( $objOrArray );
3545         }
3546         foreach ( $objOrArray as $key => $value ) {
3547                 if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
3548                         $value = wfObjectToArray( $value );
3549                 }
3550
3551                 $array[$key] = $value;
3552         }
3553
3554         return $array;
3555 }
3556
3557 /**
3558  * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit;
3559  * @return Integer value memory was set to.
3560  */
3561 function wfMemoryLimit() {
3562         global $wgMemoryLimit;
3563         $memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
3564         if( $memlimit != -1 ) {
3565                 $conflimit = wfShorthandToInteger( $wgMemoryLimit );
3566                 if( $conflimit == -1 ) {
3567                         wfDebug( "Removing PHP's memory limit\n" );
3568                         wfSuppressWarnings();
3569                         ini_set( 'memory_limit', $conflimit );
3570                         wfRestoreWarnings();
3571                         return $conflimit;
3572                 } elseif ( $conflimit > $memlimit ) {
3573                         wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
3574                         wfSuppressWarnings();
3575                         ini_set( 'memory_limit', $conflimit );
3576                         wfRestoreWarnings();
3577                         return $conflimit;
3578                 }
3579         }
3580         return $memlimit;
3581 }
3582
3583 /**
3584  * Converts shorthand byte notation to integer form
3585  * @param $string String
3586  * @return Integer
3587  */
3588 function wfShorthandToInteger( $string = '' ) {
3589         $string = trim( $string );
3590         if( $string === '' ) {
3591                 return -1;
3592         }
3593         $last = $string[strlen( $string ) - 1];
3594         $val = intval( $string );
3595         switch( $last ) {
3596                 case 'g':
3597                 case 'G':
3598                         $val *= 1024;
3599                         // break intentionally missing
3600                 case 'm':
3601                 case 'M':
3602                         $val *= 1024;
3603                         // break intentionally missing
3604                 case 'k':
3605                 case 'K':
3606                         $val *= 1024;
3607         }
3608
3609         return $val;
3610 }
3611
3612 /**
3613  * Get the normalised IETF language tag
3614  * @param $code String: The language code.
3615  * @return $langCode String: The language code which complying with BCP 47 standards.
3616  */
3617 function wfBCP47( $code ) {
3618         $codeSegment = explode( '-', $code );
3619         foreach ( $codeSegment as $segNo => $seg ) {
3620                 if ( count( $codeSegment ) > 0 ) {
3621                         // ISO 3166 country code
3622                         if ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
3623                                 $codeBCP[$segNo] = strtoupper( $seg );
3624                         // ISO 15924 script code
3625                         } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
3626                                 $codeBCP[$segNo] = ucfirst( $seg );
3627                         // Use lowercase for other cases
3628                         } else {
3629                                 $codeBCP[$segNo] = strtolower( $seg );
3630                         }
3631                 } else {
3632                 // Use lowercase for single segment
3633                         $codeBCP[$segNo] = strtolower( $seg );
3634                 }
3635         }
3636         $langCode = implode( '-', $codeBCP );
3637         return $langCode;
3638 }
3639
3640 function wfArrayMap( $function, $input ) {
3641         $ret = array_map( $function, $input );
3642         foreach ( $ret as $key => $value ) {
3643                 $taint = istainted( $input[$key] );
3644                 if ( $taint ) {
3645                         taint( $ret[$key], $taint );
3646                 }
3647         }
3648         return $ret;
3649 }