]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/GlobalFunctions.php
MediaWiki 1.5.8 (initial commit)
[autoinstalls/mediawiki.git] / includes / GlobalFunctions.php
1 <?php
2
3 /**
4  * Global functions used everywhere
5  * @package MediaWiki
6  */
7
8 /**
9  * Some globals and requires needed
10  */
11  
12 /**
13  * Total number of articles
14  * @global integer $wgNumberOfArticles
15  */
16 $wgNumberOfArticles = -1; # Unset
17 /**
18  * Total number of views
19  * @global integer $wgTotalViews
20  */
21 $wgTotalViews = -1;
22 /**
23  * Total number of edits
24  * @global integer $wgTotalEdits
25  */
26 $wgTotalEdits = -1;
27
28
29 require_once( 'DatabaseFunctions.php' );
30 require_once( 'UpdateClasses.php' );
31 require_once( 'LogPage.php' );
32 require_once( 'normal/UtfNormalUtil.php' );
33
34 /**
35  * Compatibility functions
36  * PHP <4.3.x is not actively supported; 4.1.x and 4.2.x might or might not work.
37  * <4.1.x will not work, as we use a number of features introduced in 4.1.0
38  * such as the new autoglobals.
39  */
40 if( !function_exists('iconv') ) {
41         # iconv support is not in the default configuration and so may not be present.
42         # Assume will only ever use utf-8 and iso-8859-1.
43         # This will *not* work in all circumstances.
44         function iconv( $from, $to, $string ) {
45                 if(strcasecmp( $from, $to ) == 0) return $string;
46                 if(strcasecmp( $from, 'utf-8' ) == 0) return utf8_decode( $string );
47                 if(strcasecmp( $to, 'utf-8' ) == 0) return utf8_encode( $string );
48                 return $string;
49         }
50 }
51
52 if( !function_exists('file_get_contents') ) {
53         # Exists in PHP 4.3.0+
54         function file_get_contents( $filename ) {
55                 return implode( '', file( $filename ) );
56         }
57 }
58
59 if( !function_exists('is_a') ) {
60         # Exists in PHP 4.2.0+
61         function is_a( $object, $class_name ) {
62                 return
63                         (strcasecmp( get_class( $object ), $class_name ) == 0) ||
64                          is_subclass_of( $object, $class_name );
65         }
66 }
67
68 # UTF-8 substr function based on a PHP manual comment
69 if ( !function_exists( 'mb_substr' ) ) {
70         function mb_substr( $str, $start ) { 
71                 preg_match_all( '/./us', $str, $ar );
72
73                 if( func_num_args() >= 3 ) {
74                         $end = func_get_arg( 2 ); 
75                         return join( '', array_slice( $ar[0], $start, $end ) ); 
76                 } else { 
77                         return join( '', array_slice( $ar[0], $start ) ); 
78                 }
79         }
80 }
81
82 if( !function_exists( 'floatval' ) ) {
83         /**
84          * First defined in PHP 4.2.0
85          * @param mixed $var;
86          * @return float
87          */
88         function floatval( $var ) {
89                 return (float)$var;
90         }
91 }
92
93 /**
94  * Where as we got a random seed
95  * @var bool $wgTotalViews
96  */
97 $wgRandomSeeded = false;
98
99 /**
100  * Seed Mersenne Twister
101  * Only necessary in PHP < 4.2.0
102  *
103  * @return bool
104  */
105 function wfSeedRandom() {
106         global $wgRandomSeeded;
107
108         if ( ! $wgRandomSeeded && version_compare( phpversion(), '4.2.0' ) < 0 ) {
109                 $seed = hexdec(substr(md5(microtime()),-8)) & 0x7fffffff;
110                 mt_srand( $seed );
111                 $wgRandomSeeded = true;
112         }
113 }
114
115 /**
116  * Get a random decimal value between 0 and 1, in a way
117  * not likely to give duplicate values for any realistic
118  * number of articles.
119  *
120  * @return string
121  */
122 function wfRandom() {
123         # The maximum random value is "only" 2^31-1, so get two random
124         # values to reduce the chance of dupes
125         $max = mt_getrandmax();
126         $rand = number_format( (mt_rand() * $max + mt_rand())
127                 / $max / $max, 12, '.', '' );
128         return $rand;
129 }
130
131 /**
132  * We want / and : to be included as literal characters in our title URLs.
133  * %2F in the page titles seems to fatally break for some reason.
134  *
135  * @param string $s
136  * @return string
137 */
138 function wfUrlencode ( $s ) {
139         $s = urlencode( $s );
140         $s = preg_replace( '/%3[Aa]/', ':', $s );
141         $s = preg_replace( '/%2[Ff]/', '/', $s );
142
143         return $s;
144 }
145
146 /**
147  * Sends a line to the debug log if enabled or, optionally, to a comment in output.
148  * In normal operation this is a NOP.
149  *
150  * Controlling globals:
151  * $wgDebugLogFile - points to the log file
152  * $wgProfileOnly - if set, normal debug messages will not be recorded.
153  * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
154  * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
155  *
156  * @param string $text
157  * @param bool $logonly Set true to avoid appearing in HTML when $wgDebugComments is set
158  */
159 function wfDebug( $text, $logonly = false ) {
160         global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
161
162         # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
163         if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
164                 return;
165         }
166
167         if ( isset( $wgOut ) && $wgDebugComments && !$logonly ) {
168                 $wgOut->debug( $text );
169         }
170         if ( '' != $wgDebugLogFile && !$wgProfileOnly ) {
171                 # Strip unprintables; they can switch terminal modes when binary data
172                 # gets dumped, which is pretty annoying.
173                 $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
174                 @error_log( $text, 3, $wgDebugLogFile );
175         }
176 }
177
178 /**
179  * Send a line to a supplementary debug log file, if configured, or main debug log if not.
180  * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
181  * @param string $logGroup
182  * @param string $text
183  */
184 function wfDebugLog( $logGroup, $text ) {
185         global $wgDebugLogGroups, $wgDBname;
186         if( $text{strlen( $text ) - 1} != "\n" ) $text .= "\n";
187         if( isset( $wgDebugLogGroups[$logGroup] ) ) {
188                 @error_log( "$wgDBname: $text", 3, $wgDebugLogGroups[$logGroup] );
189         } else {
190                 wfDebug( $text, true );
191         }
192 }
193
194 /**
195  * Log for database errors
196  * @param string $text Database error message.
197  */
198 function wfLogDBError( $text ) {
199         global $wgDBerrorLog;
200         if ( $wgDBerrorLog ) {
201                 $text = date('D M j G:i:s T Y') . "\t".$text;
202                 error_log( $text, 3, $wgDBerrorLog );
203         }
204 }
205
206 /**
207  * @todo document
208  */
209 function logProfilingData() {
210         global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
211         global $wgProfiling, $wgProfileStack, $wgProfileLimit, $wgUser;
212         $now = wfTime();
213
214         list( $usec, $sec ) = explode( ' ', $wgRequestTime );
215         $start = (float)$sec + (float)$usec;
216         $elapsed = $now - $start;
217         if ( $wgProfiling ) {
218                 $prof = wfGetProfilingOutput( $start, $elapsed );
219                 $forward = '';
220                 if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
221                         $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
222                 if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) )
223                         $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
224                 if( !empty( $_SERVER['HTTP_FROM'] ) )
225                         $forward .= ' from ' . $_SERVER['HTTP_FROM'];
226                 if( $forward )
227                         $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
228                 if( $wgUser->isAnon() )
229                         $forward .= ' anon';
230                 $log = sprintf( "%s\t%04.3f\t%s\n",
231                   gmdate( 'YmdHis' ), $elapsed,
232                   urldecode( $_SERVER['REQUEST_URI'] . $forward ) );
233                 if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) {
234                         error_log( $log . $prof, 3, $wgDebugLogFile );
235                 }
236         }
237 }
238
239 /**
240  * Check if the wiki read-only lock file is present. This can be used to lock
241  * off editing functions, but doesn't guarantee that the database will not be
242  * modified.
243  * @return bool
244  */
245 function wfReadOnly() {
246         global $wgReadOnlyFile, $wgReadOnly;
247
248         if ( !is_null( $wgReadOnly ) ) {
249                 return (bool)$wgReadOnly;
250         }
251         if ( '' == $wgReadOnlyFile ) {
252                 return false;
253         }
254         
255         // Set $wgReadOnly for faster access next time
256         if ( is_file( $wgReadOnlyFile ) ) {
257                 $wgReadOnly = file_get_contents( $wgReadOnlyFile );
258         } else {
259                 $wgReadOnly = false;
260         }
261         return $wgReadOnly;
262 }
263
264
265 /**
266  * Get a message from anywhere, for the current user language
267  *
268  * @param string 
269  */
270 function wfMsg( $key ) {
271         $args = func_get_args();
272         array_shift( $args );
273         return wfMsgReal( $key, $args, true );
274 }
275
276 /**
277  * Get a message from anywhere, for the current global language
278  */
279 function wfMsgForContent( $key ) {
280         global $wgForceUIMsgAsContentMsg;
281         $args = func_get_args();
282         array_shift( $args );
283         $forcontent = true;
284         if( is_array( $wgForceUIMsgAsContentMsg ) &&
285                 in_array( $key, $wgForceUIMsgAsContentMsg ) )
286                 $forcontent = false;
287         return wfMsgReal( $key, $args, true, $forcontent );
288 }
289
290 /**
291  * Get a message from the language file, for the UI elements
292  */
293 function wfMsgNoDB( $key ) {
294         $args = func_get_args();
295         array_shift( $args );
296         return wfMsgReal( $key, $args, false );
297 }
298
299 /**
300  * Get a message from the language file, for the content
301  */
302 function wfMsgNoDBForContent( $key ) {
303         global $wgForceUIMsgAsContentMsg;
304         $args = func_get_args();
305         array_shift( $args );
306         $forcontent = true;
307         if( is_array( $wgForceUIMsgAsContentMsg ) &&
308                 in_array( $key, $wgForceUIMsgAsContentMsg ) )
309                 $forcontent = false;
310         return wfMsgReal( $key, $args, false, $forcontent );
311 }
312
313
314 /**
315  * Really get a message
316  */
317 function wfMsgReal( $key, $args, $useDB, $forContent=false ) {
318         $fname = 'wfMsgReal';
319         wfProfileIn( $fname );
320         
321         $message = wfMsgGetKey( $key, $useDB, $forContent );
322         $message = wfMsgReplaceArgs( $message, $args );
323         wfProfileOut( $fname );
324         return $message;
325 }
326
327 /**
328  * Fetch a message string value, but don't replace any keys yet.
329  * @param string $key
330  * @param bool $useDB
331  * @param bool $forContent
332  * @return string
333  * @access private
334  */
335 function wfMsgGetKey( $key, $useDB, $forContent = false ) {
336         global $wgParser, $wgMsgParserOptions;
337         global $wgContLang, $wgLanguageCode;
338         global $wgMessageCache, $wgLang;
339         
340         if( is_object( $wgMessageCache ) ) {
341                 $message = $wgMessageCache->get( $key, $useDB, $forContent );
342         } else {
343                 if( $forContent ) {
344                         $lang = &$wgContLang;
345                 } else {
346                         $lang = &$wgLang;
347                 }
348
349                 wfSuppressWarnings();
350                 
351                 if( is_object( $lang ) ) {
352                         $message = $lang->getMessage( $key );
353                 } else {
354                         $message = '';
355                 }
356                 wfRestoreWarnings();
357                 if(!$message)
358                         $message = Language::getMessage($key);
359                 if(strstr($message, '{{' ) !== false) {
360                         $message = $wgParser->transformMsg($message, $wgMsgParserOptions);
361                 }
362         }
363         return $message;
364 }
365
366 /**
367  * Replace message parameter keys on the given formatted output.
368  *
369  * @param string $message
370  * @param array $args
371  * @return string
372  * @access private
373  */
374 function wfMsgReplaceArgs( $message, $args ) {
375         static $replacementKeys = array( '$1', '$2', '$3', '$4', '$5', '$6', '$7', '$8', '$9' );
376         
377         # Fix windows line-endings
378         # Some messages are split with explode("\n", $msg)
379         $message = str_replace( "\r", '', $message );
380
381         # Replace arguments
382         if( count( $args ) ) {
383                 $message = str_replace( $replacementKeys, $args, $message );
384         }
385         return $message;
386 }
387
388 /**
389  * Return an HTML-escaped version of a message.
390  * Parameter replacements, if any, are done *after* the HTML-escaping,
391  * so parameters may contain HTML (eg links or form controls). Be sure
392  * to pre-escape them if you really do want plaintext, or just wrap
393  * the whole thing in htmlspecialchars().
394  *
395  * @param string $key
396  * @param string ... parameters
397  * @return string
398  */
399 function wfMsgHtml( $key ) {
400         $args = func_get_args();
401         array_shift( $args );
402         return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
403 }
404
405 /**
406  * Just like exit() but makes a note of it.
407  * Commits open transactions except if the error parameter is set
408  */
409 function wfAbruptExit( $error = false ){
410         global $wgLoadBalancer;
411         static $called = false;
412         if ( $called ){
413                 exit();
414         }
415         $called = true;
416
417         if( function_exists( 'debug_backtrace' ) ){ // PHP >= 4.3
418                 $bt = debug_backtrace();
419                 for($i = 0; $i < count($bt) ; $i++){
420                         $file = isset($bt[$i]['file']) ? $bt[$i]['file'] : "unknown";
421                         $line = isset($bt[$i]['line']) ? $bt[$i]['line'] : "unknown";
422                         wfDebug("WARNING: Abrupt exit in $file at line $line\n");
423                 }
424         } else {
425                 wfDebug('WARNING: Abrupt exit\n');
426         }
427         if ( !$error ) {
428                 $wgLoadBalancer->closeAll();
429         }
430         exit();
431 }
432
433 /**
434  * @todo document
435  */
436 function wfErrorExit() {
437         wfAbruptExit( true );
438 }
439
440 /**
441  * Die with a backtrace
442  * This is meant as a debugging aid to track down where bad data comes from.
443  * Shouldn't be used in production code except maybe in "shouldn't happen" areas.
444  *
445  * @param string $msg Message shown when dieing.
446  */
447 function wfDebugDieBacktrace( $msg = '' ) {
448         global $wgCommandLineMode;
449
450         $backtrace = wfBacktrace();
451         if ( $backtrace !== false ) {
452                 if ( $wgCommandLineMode ) {
453                         $msg .= "\nBacktrace:\n$backtrace";
454                 } else {
455                         $msg .= "\n<p>Backtrace:</p>\n$backtrace";
456                 }
457          }
458          echo $msg;
459          die( -1 );
460 }
461
462 function wfBacktrace() {
463         global $wgCommandLineMode;
464         if ( !function_exists( 'debug_backtrace' ) ) {
465                 return false;
466         }
467         
468         if ( $wgCommandLineMode ) {
469                 $msg = '';
470         } else {
471                 $msg = "<ul>\n";
472         }
473         $backtrace = debug_backtrace();
474         foreach( $backtrace as $call ) {
475                 if( isset( $call['file'] ) ) {
476                         $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
477                         $file = $f[count($f)-1];
478                 } else {
479                         $file = '-';
480                 }
481                 if( isset( $call['line'] ) ) {
482                         $line = $call['line'];
483                 } else {
484                         $line = '-';
485                 }
486                 if ( $wgCommandLineMode ) {
487                         $msg .= "$file line $line calls ";
488                 } else {
489                         $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
490                 }
491                 if( !empty( $call['class'] ) ) $msg .= $call['class'] . '::';
492                 $msg .= $call['function'] . '()';
493
494                 if ( $wgCommandLineMode ) {
495                         $msg .= "\n";
496                 } else {
497                         $msg .= "</li>\n";
498                 }
499         }
500         if ( $wgCommandLineMode ) {
501                 $msg .= "\n";
502         } else {
503                 $msg .= "</ul>\n";
504         }
505
506         return $msg;
507 }
508
509
510 /* Some generic result counters, pulled out of SearchEngine */
511
512
513 /**
514  * @todo document
515  */
516 function wfShowingResults( $offset, $limit ) {
517         global $wgLang;
518         return wfMsg( 'showingresults', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) );
519 }
520
521 /**
522  * @todo document
523  */
524 function wfShowingResultsNum( $offset, $limit, $num ) {
525         global $wgLang;
526         return wfMsg( 'showingresultsnum', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
527 }
528
529 /**
530  * @todo document
531  */
532 function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
533         global $wgUser, $wgLang;
534         $fmtLimit = $wgLang->formatNum( $limit );
535         $prev = wfMsg( 'prevn', $fmtLimit );
536         $next = wfMsg( 'nextn', $fmtLimit );
537         
538         if( is_object( $link ) ) {
539                 $title =& $link;
540         } else {
541                 $title = Title::newFromText( $link );
542                 if( is_null( $title ) ) {
543                         return false;
544                 }
545         }
546         
547         $sk = $wgUser->getSkin();
548         if ( 0 != $offset ) {
549                 $po = $offset - $limit;
550                 if ( $po < 0 ) { $po = 0; }
551                 $q = "limit={$limit}&offset={$po}";
552                 if ( '' != $query ) { $q .= '&'.$query; }
553                 $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\">{$prev}</a>";
554         } else { $plink = $prev; }
555
556         $no = $offset + $limit;
557         $q = 'limit='.$limit.'&offset='.$no;
558         if ( '' != $query ) { $q .= '&'.$query; }
559
560         if ( $atend ) {
561                 $nlink = $next;
562         } else {
563                 $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\">{$next}</a>";
564         }
565         $nums = wfNumLink( $offset, 20, $title, $query ) . ' | ' .
566           wfNumLink( $offset, 50, $title, $query ) . ' | ' .
567           wfNumLink( $offset, 100, $title, $query ) . ' | ' .
568           wfNumLink( $offset, 250, $title, $query ) . ' | ' .
569           wfNumLink( $offset, 500, $title, $query );
570
571         return wfMsg( 'viewprevnext', $plink, $nlink, $nums );
572 }
573
574 /**
575  * @todo document
576  */
577 function wfNumLink( $offset, $limit, &$title, $query = '' ) {
578         global $wgUser, $wgLang;
579         if ( '' == $query ) { $q = ''; }
580         else { $q = $query.'&'; }
581         $q .= 'limit='.$limit.'&offset='.$offset;
582
583         $fmtLimit = $wgLang->formatNum( $limit );
584         $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\">{$fmtLimit}</a>";
585         return $s;
586 }
587
588 /**
589  * @todo document
590  * @todo FIXME: we may want to blacklist some broken browsers
591  *
592  * @return bool Whereas client accept gzip compression
593  */
594 function wfClientAcceptsGzip() {
595         global $wgUseGzip;
596         if( $wgUseGzip ) {
597                 # FIXME: we may want to blacklist some broken browsers
598                 if( preg_match(
599                         '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
600                         $_SERVER['HTTP_ACCEPT_ENCODING'],
601                         $m ) ) {
602                         if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) return false;
603                         wfDebug( " accepts gzip\n" );
604                         return true;
605                 }
606         }
607         return false;
608 }
609
610 /**
611  * Yay, more global functions!
612  */
613 function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
614         global $wgRequest;
615         return $wgRequest->getLimitOffset( $deflimit, $optionname );
616 }
617
618 /**
619  * Escapes the given text so that it may be output using addWikiText()
620  * without any linking, formatting, etc. making its way through. This
621  * is achieved by substituting certain characters with HTML entities.
622  * As required by the callers, <nowiki> is not used. It currently does
623  * not filter out characters which have special meaning only at the
624  * start of a line, such as "*".
625  *
626  * @param string $text Text to be escaped
627  */
628 function wfEscapeWikiText( $text ) {
629         $text = str_replace( 
630                 array( '[',             '|',      '\'',    'ISBN '        , '://'         , "\n=", '{{' ),
631                 array( '&#91;', '&#124;', '&#39;', 'ISBN&#32;', '&#58;//' , "\n&#61;", '&#123;&#123;' ),
632                 htmlspecialchars($text) );
633         return $text;
634 }
635
636 /**
637  * @todo document
638  */
639 function wfQuotedPrintable( $string, $charset = '' ) {
640         # Probably incomplete; see RFC 2045
641         if( empty( $charset ) ) {
642                 global $wgInputEncoding;
643                 $charset = $wgInputEncoding;
644         }
645         $charset = strtoupper( $charset );
646         $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
647
648         $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
649         $replace = $illegal . '\t ?_';
650         if( !preg_match( "/[$illegal]/", $string ) ) return $string;
651         $out = "=?$charset?Q?";
652         $out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
653         $out .= '?=';
654         return $out;
655 }
656
657 /**
658  * Returns an escaped string suitable for inclusion in a string literal
659  * for JavaScript source code.
660  * Illegal control characters are assumed not to be present.
661  *
662  * @param string $string
663  * @return string
664  */
665 function wfEscapeJsString( $string ) {
666         // See ECMA 262 section 7.8.4 for string literal format
667         $pairs = array(
668                 "\\" => "\\\\",
669                 "\"" => "\\\"",
670                 '\'' => '\\\'',
671                 "\n" => "\\n",
672                 "\r" => "\\r",
673                 
674                 # To avoid closing the element or CDATA section
675                 "<" => "\\x3c",
676                 ">" => "\\x3e",
677         );
678         return strtr( $string, $pairs );
679 }
680
681 /**
682  * @todo document
683  * @return float
684  */
685 function wfTime() {
686         $st = explode( ' ', microtime() );
687         return (float)$st[0] + (float)$st[1];
688 }
689
690 /**
691  * Changes the first character to an HTML entity
692  */
693 function wfHtmlEscapeFirst( $text ) {
694         $ord = ord($text);
695         $newText = substr($text, 1);
696         return "&#$ord;$newText";
697 }
698
699 /**
700  * Sets dest to source and returns the original value of dest
701  * If source is NULL, it just returns the value, it doesn't set the variable
702  */
703 function wfSetVar( &$dest, $source ) {
704         $temp = $dest;
705         if ( !is_null( $source ) ) {
706                 $dest = $source;
707         }
708         return $temp;
709 }
710
711 /**
712  * As for wfSetVar except setting a bit
713  */
714 function wfSetBit( &$dest, $bit, $state = true ) {
715         $temp = (bool)($dest & $bit );
716         if ( !is_null( $state ) ) {
717                 if ( $state ) {
718                         $dest |= $bit;
719                 } else {
720                         $dest &= ~$bit;
721                 }
722         }
723         return $temp;
724 }
725
726 /**
727  * This function takes two arrays as input, and returns a CGI-style string, e.g.
728  * "days=7&limit=100". Options in the first array override options in the second.
729  * Options set to "" will not be output.
730  */
731 function wfArrayToCGI( $array1, $array2 = NULL )
732 {
733         if ( !is_null( $array2 ) ) {
734                 $array1 = $array1 + $array2;
735         }
736
737         $cgi = '';
738         foreach ( $array1 as $key => $value ) {
739                 if ( '' !== $value ) {
740                         if ( '' != $cgi ) {
741                                 $cgi .= '&';
742                         }
743                         $cgi .= urlencode( $key ) . '=' . urlencode( $value );
744                 }
745         }
746         return $cgi;
747 }
748
749 /**
750  * This is obsolete, use SquidUpdate::purge()
751  * @deprecated
752  */
753 function wfPurgeSquidServers ($urlArr) {
754         SquidUpdate::purge( $urlArr );
755 }
756
757 /**
758  * Windows-compatible version of escapeshellarg()
759  * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg() 
760  * function puts single quotes in regardless of OS
761  */
762 function wfEscapeShellArg( ) {
763         $args = func_get_args();
764         $first = true;
765         $retVal = '';
766         foreach ( $args as $arg ) {
767                 if ( !$first ) {
768                         $retVal .= ' ';
769                 } else {
770                         $first = false;
771                 }
772         
773                 if ( wfIsWindows() ) {
774                         $retVal .= '"' . str_replace( '"','\"', $arg ) . '"';
775                 } else {
776                         $retVal .= escapeshellarg( $arg );
777                 }
778         }
779         return $retVal;
780 }
781
782 /**
783  * wfMerge attempts to merge differences between three texts.
784  * Returns true for a clean merge and false for failure or a conflict.
785  */
786 function wfMerge( $old, $mine, $yours, &$result ){
787         global $wgDiff3;
788
789         # This check may also protect against code injection in
790         # case of broken installations.
791         if(! file_exists( $wgDiff3 ) ){
792                 wfDebug( "diff3 not found\n" );
793                 return false;
794         }
795
796         # Make temporary files
797         $td = wfTempDir();
798         $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
799         $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
800         $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
801
802         fwrite( $oldtextFile, $old ); fclose( $oldtextFile );
803         fwrite( $mytextFile, $mine ); fclose( $mytextFile );
804         fwrite( $yourtextFile, $yours ); fclose( $yourtextFile );
805
806         # Check for a conflict
807         $cmd = $wgDiff3 . ' -a --overlap-only ' .
808           wfEscapeShellArg( $mytextName ) . ' ' .
809           wfEscapeShellArg( $oldtextName ) . ' ' .
810           wfEscapeShellArg( $yourtextName );
811         $handle = popen( $cmd, 'r' );
812
813         if( fgets( $handle, 1024 ) ){
814                 $conflict = true;
815         } else {
816                 $conflict = false;
817         }
818         pclose( $handle );
819
820         # Merge differences
821         $cmd = $wgDiff3 . ' -a -e --merge ' .
822           wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
823         $handle = popen( $cmd, 'r' );
824         $result = '';
825         do {
826                 $data = fread( $handle, 8192 );
827                 if ( strlen( $data ) == 0 ) {
828                         break;
829                 }
830                 $result .= $data;
831         } while ( true );
832         pclose( $handle );
833         unlink( $mytextName ); unlink( $oldtextName ); unlink( $yourtextName );
834
835         if ( $result === '' && $old !== '' && $conflict == false ) {
836                 wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
837                 $conflict = true;
838         }
839         return ! $conflict;
840 }
841
842 /**
843  * @todo document
844  */
845 function wfVarDump( $var ) {
846         global $wgOut;
847         $s = str_replace("\n","<br />\n", var_export( $var, true ) . "\n");
848         if ( headers_sent() || !@is_object( $wgOut ) ) {
849                 print $s;
850         } else {
851                 $wgOut->addHTML( $s );
852         }
853 }
854
855 /**
856  * Provide a simple HTTP error.
857  */
858 function wfHttpError( $code, $label, $desc ) {
859         global $wgOut;
860         $wgOut->disable();
861         header( "HTTP/1.0 $code $label" );
862         header( "Status: $code $label" );
863         $wgOut->sendCacheControl();
864
865         header( 'Content-type: text/html' );
866         print "<html><head><title>" .
867                 htmlspecialchars( $label ) . 
868                 "</title></head><body><h1>" . 
869                 htmlspecialchars( $label ) .
870                 "</h1><p>" .
871                 htmlspecialchars( $desc ) .
872                 "</p></body></html>\n";
873 }
874
875 /**
876  * Converts an Accept-* header into an array mapping string values to quality
877  * factors
878  */
879 function wfAcceptToPrefs( $accept, $def = '*/*' ) {
880         # No arg means accept anything (per HTTP spec)
881         if( !$accept ) {
882                 return array( $def => 1 );
883         }
884
885         $prefs = array();
886
887         $parts = explode( ',', $accept );
888
889         foreach( $parts as $part ) {
890                 # FIXME: doesn't deal with params like 'text/html; level=1'
891                 @list( $value, $qpart ) = explode( ';', $part );
892                 if( !isset( $qpart ) ) {
893                         $prefs[$value] = 1;
894                 } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
895                         $prefs[$value] = $match[1];
896                 }
897         }
898
899         return $prefs;
900 }
901
902 /**
903  * Checks if a given MIME type matches any of the keys in the given
904  * array. Basic wildcards are accepted in the array keys.
905  *
906  * Returns the matching MIME type (or wildcard) if a match, otherwise
907  * NULL if no match.
908  *
909  * @param string $type
910  * @param array $avail
911  * @return string
912  * @access private
913  */
914 function mimeTypeMatch( $type, $avail ) {
915         if( array_key_exists($type, $avail) ) {
916                 return $type;
917         } else {
918                 $parts = explode( '/', $type );
919                 if( array_key_exists( $parts[0] . '/*', $avail ) ) {
920                         return $parts[0] . '/*';
921                 } elseif( array_key_exists( '*/*', $avail ) ) {
922                         return '*/*';
923                 } else {
924                         return NULL;
925                 }
926         }
927 }
928
929 /**
930  * Returns the 'best' match between a client's requested internet media types
931  * and the server's list of available types. Each list should be an associative
932  * array of type to preference (preference is a float between 0.0 and 1.0).
933  * Wildcards in the types are acceptable.
934  *
935  * @param array $cprefs Client's acceptable type list
936  * @param array $sprefs Server's offered types
937  * @return string
938  *
939  * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
940  * XXX: generalize to negotiate other stuff
941  */
942 function wfNegotiateType( $cprefs, $sprefs ) {
943         $combine = array();
944
945         foreach( array_keys($sprefs) as $type ) {
946                 $parts = explode( '/', $type );
947                 if( $parts[1] != '*' ) {
948                         $ckey = mimeTypeMatch( $type, $cprefs );
949                         if( $ckey ) {
950                                 $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
951                         }
952                 }
953         }
954
955         foreach( array_keys( $cprefs ) as $type ) {
956                 $parts = explode( '/', $type );
957                 if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
958                         $skey = mimeTypeMatch( $type, $sprefs );
959                         if( $skey ) {
960                                 $combine[$type] = $sprefs[$skey] * $cprefs[$type];
961                         }
962                 }
963         }
964
965         $bestq = 0;
966         $besttype = NULL;
967
968         foreach( array_keys( $combine ) as $type ) {
969                 if( $combine[$type] > $bestq ) {
970                         $besttype = $type;
971                         $bestq = $combine[$type];
972                 }
973         }
974
975         return $besttype;
976 }
977
978 /**
979  * Array lookup
980  * Returns an array where the values in the first array are replaced by the
981  * values in the second array with the corresponding keys
982  * 
983  * @return array
984  */
985 function wfArrayLookup( $a, $b ) {
986         return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
987 }
988
989 /**
990  * Convenience function; returns MediaWiki timestamp for the present time.
991  * @return string
992  */
993 function wfTimestampNow() {
994         # return NOW
995         return wfTimestamp( TS_MW, time() );
996 }
997
998 /**
999  * Reference-counted warning suppression
1000  */
1001 function wfSuppressWarnings( $end = false ) {
1002         static $suppressCount = 0;
1003         static $originalLevel = false;
1004
1005         if ( $end ) {
1006                 if ( $suppressCount ) {
1007                         $suppressCount --;
1008                         if ( !$suppressCount ) {
1009                                 error_reporting( $originalLevel );
1010                         }
1011                 }
1012         } else {
1013                 if ( !$suppressCount ) {
1014                         $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE ) );
1015                 }
1016                 $suppressCount++;
1017         }
1018 }
1019
1020 /**
1021  * Restore error level to previous value
1022  */
1023 function wfRestoreWarnings() {
1024         wfSuppressWarnings( true );
1025 }
1026
1027 # Autodetect, convert and provide timestamps of various types
1028
1029 /** 
1030  * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
1031  */
1032 define('TS_UNIX', 0);
1033
1034 /**
1035  * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
1036  */
1037 define('TS_MW', 1);
1038
1039 /**
1040  * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
1041  */
1042 define('TS_DB', 2);
1043
1044 /**
1045  * RFC 2822 format, for E-mail and HTTP headers
1046  */
1047 define('TS_RFC2822', 3);
1048
1049 /**
1050  * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
1051  *
1052  * @link http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
1053  *       DateTime tag and page 36 for the DateTimeOriginal and
1054  *       DateTimeDigitized tags.
1055  */
1056 define('TS_EXIF', 4);
1057
1058
1059 /**
1060  * @param mixed $outputtype A timestamp in one of the supported formats, the
1061  *                          function will autodetect which format is supplied
1062                             and act accordingly.
1063  * @return string Time in the format specified in $outputtype
1064  */
1065 function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
1066         $uts = 0;
1067         if ($ts==0) {
1068                 $uts=time();
1069         } elseif (preg_match("/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/",$ts,$da)) {
1070                 # TS_DB
1071                 $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1072                             (int)$da[2],(int)$da[3],(int)$da[1]);
1073         } elseif (preg_match("/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/",$ts,$da)) {
1074                 # TS_EXIF
1075                 $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1076                         (int)$da[2],(int)$da[3],(int)$da[1]);
1077         } elseif (preg_match("/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/",$ts,$da)) {
1078                 # TS_MW
1079                 $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6],
1080                             (int)$da[2],(int)$da[3],(int)$da[1]);
1081         } elseif (preg_match("/^(\d{1,13})$/",$ts,$datearray)) {
1082                 # TS_UNIX
1083                 $uts=$ts;
1084         } else {
1085                 # Bogus value; fall back to the epoch...
1086                 wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
1087                 $uts = 0;
1088         }
1089
1090                 
1091         switch($outputtype) {
1092                 case TS_UNIX:
1093                         return $uts;
1094                 case TS_MW:
1095                         return gmdate( 'YmdHis', $uts );
1096                 case TS_DB:
1097                         return gmdate( 'Y-m-d H:i:s', $uts );
1098                 // This shouldn't ever be used, but is included for completeness
1099                 case TS_EXIF:
1100                         return gmdate(  'Y:m:d H:i:s', $uts );
1101                 case TS_RFC2822:
1102                         return gmdate( 'D, d M Y H:i:s', $uts ) . ' GMT';
1103                 default:
1104                         wfDebugDieBacktrace( 'wfTimestamp() called with illegal output type.');
1105         }
1106 }
1107
1108 /**
1109  * Return a formatted timestamp, or null if input is null.
1110  * For dealing with nullable timestamp columns in the database.
1111  * @param int $outputtype
1112  * @param string $ts
1113  * @return string
1114  */
1115 function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
1116         if( is_null( $ts ) ) {
1117                 return null;
1118         } else {
1119                 return wfTimestamp( $outputtype, $ts );
1120         }
1121 }
1122
1123 /**
1124  * Check where as the operating system is Windows
1125  *
1126  * @return bool True if it's windows, False otherwise.
1127  */
1128 function wfIsWindows() {   
1129         if (substr(php_uname(), 0, 7) == 'Windows') {   
1130                 return true;   
1131         } else {   
1132                 return false;   
1133         }   
1134
1135
1136 /**
1137  * Swap two variables
1138  */
1139 function swap( &$x, &$y ) {
1140         $z = $x;
1141         $x = $y;
1142         $y = $z;
1143 }
1144
1145 function wfGetSiteNotice() {
1146         global $wgSiteNotice, $wgTitle, $wgOut;
1147         $fname = 'wfGetSiteNotice';
1148         wfProfileIn( $fname );
1149
1150         $notice = wfMsg( 'sitenotice' );
1151         if( $notice == '&lt;sitenotice&gt;' || $notice == '-' ) {
1152                 $notice = '';
1153         }
1154         if( $notice == '' ) {
1155                 # We may also need to override a message with eg downtime info
1156                 # FIXME: make this work!
1157                 $notice = $wgSiteNotice;
1158         }
1159         if($notice != '-' && $notice != '') {
1160                 $specialparser = new Parser();
1161                 $parserOutput = $specialparser->parse( $notice, $wgTitle, $wgOut->mParserOptions, false );
1162                 $notice = $parserOutput->getText();
1163         }
1164         wfProfileOut( $fname );
1165         return $notice;
1166 }
1167
1168 /**
1169  * Format an XML element with given attributes and, optionally, text content.
1170  * Element and attribute names are assumed to be ready for literal inclusion.
1171  * Strings are assumed to not contain XML-illegal characters; special
1172  * characters (<, >, &) are escaped but illegals are not touched.
1173  *
1174  * @param string $element
1175  * @param array $attribs Name=>value pairs. Values will be escaped.
1176  * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
1177  * @return string
1178  */
1179 function wfElement( $element, $attribs = null, $contents = '') {
1180         $out = '<' . $element;
1181         if( !is_null( $attribs ) ) {
1182                 foreach( $attribs as $name => $val ) {
1183                         $out .= ' ' . $name . '="' . htmlspecialchars( $val ) . '"';
1184                 }
1185         }
1186         if( is_null( $contents ) ) {
1187                 $out .= '>';
1188         } else {
1189                 if( $contents == '' ) {
1190                         $out .= ' />';
1191                 } else {
1192                         $out .= '>';
1193                         $out .= htmlspecialchars( $contents );
1194                         $out .= "</$element>";
1195                 }
1196         }
1197         return $out;
1198 }
1199
1200 /**
1201  * Format an XML element as with wfElement(), but run text through the
1202  * UtfNormal::cleanUp() validator first to ensure that no invalid UTF-8
1203  * is passed.
1204  *
1205  * @param string $element
1206  * @param array $attribs Name=>value pairs. Values will be escaped.
1207  * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
1208  * @return string
1209  */
1210 function wfElementClean( $element, $attribs = array(), $contents = '') {
1211         if( $attribs ) {
1212                 $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
1213         }
1214         if( $contents ) {
1215                 $contents = UtfNormal::cleanUp( $contents );
1216         }
1217         return wfElement( $element, $attribs, $contents );
1218 }
1219
1220 /**
1221  * Create a namespace selector
1222  *
1223  * @param mixed $selected The namespace which should be selected, default ''
1224  * @param string $allnamespaces Value of a special item denoting all namespaces. Null to not include (default)
1225  * @return Html string containing the namespace selector
1226  */
1227 function &HTMLnamespaceselector($selected = '', $allnamespaces = null) {
1228         global $wgContLang;
1229         $s = "<select name='namespace' class='namespaceselector'>\n";
1230         $arr = $wgContLang->getFormattedNamespaces();
1231         if( !is_null($allnamespaces) ) {
1232                 $arr = array($allnamespaces => wfMsgHtml('namespacesall')) + $arr;
1233         }
1234         foreach ($arr as $index => $name) {
1235                 if ($index < NS_MAIN) continue;
1236
1237                 $name = $index !== 0 ? $name : wfMsgHtml('blanknamespace');
1238
1239                 if ($index === $selected) {
1240                         $s .= wfElement("option",
1241                                         array("value" => $index, "selected" => "selected"),
1242                                         $name);
1243                 } else {
1244                         $s .= wfElement("option", array("value" => $index), $name);
1245                 }
1246         }
1247         $s .= "</select>\n";
1248         return $s;
1249 }
1250
1251 /** Global singleton instance of MimeMagic. This is initialized on demand,
1252 * please always use the wfGetMimeMagic() function to get the instance.
1253
1254 * @private
1255 */
1256 $wgMimeMagic= NULL;
1257
1258 /** Factory functions for the global MimeMagic object.
1259 * This function always returns the same singleton instance of MimeMagic.
1260 * That objects will be instantiated on the first call to this function.
1261 * If needed, the MimeMagic.php file is automatically included by this function.
1262 * @return MimeMagic the global MimeMagic objects.
1263 */
1264 function &wfGetMimeMagic() {
1265         global $wgMimeMagic;
1266         
1267         if (!is_null($wgMimeMagic)) {
1268                 return $wgMimeMagic;
1269         }
1270
1271         if (!class_exists("MimeMagic")) {
1272                 #include on demand
1273                 require_once("MimeMagic.php");
1274         }
1275         
1276         $wgMimeMagic= new MimeMagic();
1277         
1278         return $wgMimeMagic;
1279 }
1280
1281
1282 /**
1283  * Tries to get the system directory for temporary files.
1284  * The TMPDIR, TMP, and TEMP environment variables are checked in sequence,
1285  * and if none are set /tmp is returned as the generic Unix default.
1286  *
1287  * NOTE: When possible, use the tempfile() function to create temporary
1288  * files to avoid race conditions on file creation, etc.
1289  *
1290  * @return string
1291  */
1292 function wfTempDir() {
1293         foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
1294                 $tmp = getenv( $var );
1295                 if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
1296                         return $tmp;
1297                 }
1298         }
1299         # Hope this is Unix of some kind!
1300         return '/tmp';
1301 }
1302
1303 /**
1304  * Make directory, and make all parent directories if they don't exist
1305  */
1306 function wfMkdirParents( $fullDir, $mode ) {
1307         $parts = explode( '/', $fullDir );
1308         $path = '';
1309         $success = false;
1310         foreach ( $parts as $dir ) {
1311                 $path .= $dir . '/';
1312                 if ( !is_dir( $path ) ) {
1313                         if ( !mkdir( $path, $mode ) ) {
1314                                 return false;
1315                         }
1316                 }
1317         }
1318         return true;
1319 }
1320
1321 /**
1322  * Increment a statistics counter
1323  */
1324 function wfIncrStats( $key ) {
1325         global $wgDBname, $wgMemc;
1326         $key = "$wgDBname:stats:$key";
1327         if ( is_null( $wgMemc->incr( $key ) ) ) {
1328                 $wgMemc->add( $key, 1 );
1329         }
1330 }
1331
1332 /**
1333  * @param mixed $nr The number to format
1334  * @param int $acc The number of digits after the decimal point, default 2
1335  * @param bool $round Whether or not to round the value, default true
1336  * @return float
1337  */
1338 function wfPercent( $nr, $acc = 2, $round = true ) {
1339         $ret = sprintf( "%.${acc}f", $nr );
1340         return $round ? round( $ret, $acc ) . '%' : "$ret%";
1341 }
1342
1343 /**
1344  * Encrypt a username/password.
1345  *
1346  * @param string $userid ID of the user
1347  * @param string $password Password of the user
1348  * @return string Hashed password
1349  */
1350 function wfEncryptPassword( $userid, $password ) {
1351         global $wgPasswordSalt;
1352         $p = md5( $password);
1353
1354         if($wgPasswordSalt)
1355                 return md5( "{$userid}-{$p}" );
1356         else
1357                 return $p;
1358 }
1359
1360 /**
1361  * Appends to second array if $value differs from that in $default
1362  */
1363 function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
1364         if ( is_null( $changed ) ) {
1365                 wfDebugDieBacktrace('GlobalFunctions::wfAppendToArrayIfNotDefault got null');
1366         }
1367         if ( $default[$key] !== $value ) {
1368                 $changed[$key] = $value;
1369         }
1370 }
1371
1372 /**
1373  * Since wfMsg() and co suck, they don't return false if the message key they
1374  * looked up didn't exist but a XHTML string, this function checks for the
1375  * nonexistance of messages by looking at wfMsg() output
1376  *
1377  * @param $msg      The message key looked up
1378  * @param $wfMsgOut The output of wfMsg*()
1379  * @return bool
1380  */
1381 function wfNoMsg( $msg, $wfMsgOut ) {
1382         return $wfMsgOut === "&lt;$msg&gt;";
1383 }
1384 ?>