X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FGlobalFunctions.php;h=a9bf015df4712d30b46fc47a3e714ebeb6303423;hb=5516fef269e71b5962afa34bd7e7403b13b57d12;hp=312d1903dd5f7402e7b68eda5e61a056dad69aaf;hpb=b20844540e6f102329782c449f678c5b1aa9ce5b;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 312d1903dd..a9bf015df4 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -87,6 +87,29 @@ if ( !function_exists( 'array_diff_key' ) ) { } } +/** + * Like array_diff( $a, $b ) except that it works with two-dimensional arrays. + */ +function wfArrayDiff2( $a, $b ) { + return array_udiff( $a, $b, 'wfArrayDiff2_cmp' ); +} +function wfArrayDiff2_cmp( $a, $b ) { + if ( !is_array( $a ) ) { + return strcmp( $a, $b ); + } elseif ( count( $a ) !== count( $b ) ) { + return count( $a ) < count( $b ) ? -1 : 1; + } else { + reset( $a ); + reset( $b ); + while( ( list( $keyA, $valueA ) = each( $a ) ) && ( list( $keyB, $valueB ) = each( $b ) ) ) { + $cmp = strcmp( $valueA, $valueB ); + if ( $cmp !== 0 ) { + return $cmp; + } + } + return 0; + } +} /** * Wrapper for clone(), for compatibility with PHP4-friendly extensions. @@ -232,29 +255,30 @@ function wfErrorLog( $text, $file ) { */ function wfLogProfilingData() { global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest; - global $wgProfiling, $wgUser; - if ( $wgProfiling ) { - $now = wfTime(); - $elapsed = $now - $wgRequestTime; - $prof = wfGetProfilingOutput( $wgRequestTime, $elapsed ); - $forward = ''; - if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) - $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR']; - if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) - $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP']; - if( !empty( $_SERVER['HTTP_FROM'] ) ) - $forward .= ' from ' . $_SERVER['HTTP_FROM']; - if( $forward ) - $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})"; - // Don't unstub $wgUser at this late stage just for statistics purposes - if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() ) - $forward .= ' anon'; - $log = sprintf( "%s\t%04.3f\t%s\n", - gmdate( 'YmdHis' ), $elapsed, - urldecode( $wgRequest->getRequestURL() . $forward ) ); - if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) { - wfErrorLog( $log . $prof, $wgDebugLogFile ); - } + global $wgProfiler, $wgUser; + if ( !isset( $wgProfiler ) ) + return; + + $now = wfTime(); + $elapsed = $now - $wgRequestTime; + $prof = wfGetProfilingOutput( $wgRequestTime, $elapsed ); + $forward = ''; + if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) + $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR']; + if( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) + $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP']; + if( !empty( $_SERVER['HTTP_FROM'] ) ) + $forward .= ' from ' . $_SERVER['HTTP_FROM']; + if( $forward ) + $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})"; + // Don't unstub $wgUser at this late stage just for statistics purposes + if( StubObject::isRealObject($wgUser) && $wgUser->isAnon() ) + $forward .= ' anon'; + $log = sprintf( "%s\t%04.3f\t%s\n", + gmdate( 'YmdHis' ), $elapsed, + urldecode( $wgRequest->getRequestURL() . $forward ) ); + if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) { + wfErrorLog( $log . $prof, $wgDebugLogFile ); } } @@ -282,6 +306,11 @@ function wfReadOnly() { return (bool)$wgReadOnly; } +function wfReadOnlyReason() { + global $wgReadOnly; + wfReadOnly(); + return $wgReadOnly; +} /** * Get a message from anywhere, for the current user language. @@ -289,16 +318,11 @@ function wfReadOnly() { * Use wfMsgForContent() instead if the message should NOT * change depending on the user preferences. * - * Note that the message may contain HTML, and is therefore - * not safe for insertion anywhere. Some functions such as - * addWikiText will do the escaping for you. Use wfMsgHtml() - * if you need an escaped message. - * * @param $key String: lookup key for the message, usually * defined in languages/Language.php - * - * This function also takes extra optional parameters (not - * shown in the function definition), which can by used to + * + * This function also takes extra optional parameters (not + * shown in the function definition), which can by used to * insert variable text into the predefined message. */ function wfMsg( $key ) { @@ -420,58 +444,49 @@ function wfMsgWeirdKey ( $key ) { * Fetch a message string value, but don't replace any keys yet. * @param string $key * @param bool $useDB - * @param bool $forContent + * @param string $langcode Code of the language to get the message for, or + * behaves as a content language switch if it is a + * boolean. * @return string * @private */ -function wfMsgGetKey( $key, $useDB, $forContent = false, $transform = true ) { +function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) { global $wgParser, $wgContLang, $wgMessageCache, $wgLang; - /* btw, is all that code in wfMsgGetKey() that check - * if the message cache exists of not really necessary, or is - * it just paranoia? - * Vyznev: it's probably not necessary - * I think I wrote it in an attempt to report DB - * connection errors properly - * but eventually we gave up on using the - * message cache for that and just hard-coded the strings - * it may have other uses, it's not mere paranoia - */ - - if ( is_object( $wgMessageCache ) ) - $transstat = $wgMessageCache->getTransform(); - + # If $wgMessageCache isn't initialised yet, try to return something sensible. if( is_object( $wgMessageCache ) ) { - if ( ! $transform ) - $wgMessageCache->disableTransform(); - $message = $wgMessageCache->get( $key, $useDB, $forContent ); + $message = $wgMessageCache->get( $key, $useDB, $langCode ); + if ( $transform ) { + $message = $wgMessageCache->transform( $message ); + } } else { - if( $forContent ) { + if( $langCode === true ) { $lang = &$wgContLang; - } else { + } elseif( $langCode === false ) { $lang = &$wgLang; + } else { + $validCodes = array_keys( Language::getLanguageNames() ); + if( in_array( $langCode, $validCodes ) ) { + # $langcode corresponds to a valid language. + $lang = Language::factory( $langCode ); + } else { + # $langcode is a string, but not a valid language code; use content language. + $lang =& $wgContLang; + wfDebug( 'Invalid language code passed to wfMsgGetKey, falling back to content language.' ); + } } # MessageCache::get() does this already, Language::getMessage() doesn't # ISSUE: Should we try to handle "message/lang" here too? $key = str_replace( ' ' , '_' , $wgContLang->lcfirst( $key ) ); - wfSuppressWarnings(); if( is_object( $lang ) ) { $message = $lang->getMessage( $key ); } else { $message = false; } - wfRestoreWarnings(); - - if ( $transform && strstr( $message, '{{' ) !== false ) { - $message = $wgParser->transformMsg($message, $wgMessageCache->getParserOptions() ); - } } - if ( is_object( $wgMessageCache ) && ! $transform ) - $wgMessageCache->setTransform( $transstat ); - return $message; } @@ -491,15 +506,13 @@ function wfMsgReplaceArgs( $message, $args ) { // Replace arguments if ( count( $args ) ) { if ( is_array( $args[0] ) ) { - foreach ( $args[0] as $key => $val ) { - $message = str_replace( '$' . $key, $val, $message ); - } - } else { - foreach( $args as $n => $param ) { - $replacementKeys['$' . ($n + 1)] = $param; - } - $message = strtr( $message, $replacementKeys ); + $args = array_values( $args[0] ); } + $replacementKeys = array(); + foreach( $args as $n => $param ) { + $replacementKeys['$' . ($n + 1)] = $param; + } + $message = strtr( $message, $replacementKeys ); } return $message; @@ -546,10 +559,14 @@ function wfMsgWikiHtml( $key ) { * @param array $options Processing rules: * parse: parses wikitext to html * parseinline: parses wikitext to html and removes the surrounding p's added by parser or tidy - * escape: filters message trough htmlspecialchars + * escape: filters message through htmlspecialchars + * escapenoentities: same, but allows entity references like   through * replaceafter: parameters are substituted after parsing or escaping * parsemag: transform the message using magic phrases * content: fetch message for content language instead of interface + * language: language code to fetch message for (overriden by content), its behaviour + * with parser, parseinline and parsemag is undefined. + * Behavior for conflicting options (e.g., parse+parseinline) is undefined. */ function wfMsgExt( $key, $options ) { global $wgOut, $wgParser; @@ -562,12 +579,22 @@ function wfMsgExt( $key, $options ) { $options = array($options); } - $forContent = false; if( in_array('content', $options) ) { $forContent = true; + $langCode = true; + } elseif( array_key_exists('language', $options) ) { + $forContent = false; + $langCode = $options['language']; + $validCodes = array_keys( Language::getLanguageNames() ); + if( !in_array($options['language'], $validCodes) ) { + $langCode = false; + } + } else { + $forContent = false; + $langCode = false; } - $string = wfMsgGetKey( $key, /*DB*/true, $forContent, /*Transform*/false ); + $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false ); if( !in_array('replaceafter', $options) ) { $string = wfMsgReplaceArgs( $string, $args ); @@ -590,6 +617,10 @@ function wfMsgExt( $key, $options ) { if ( in_array('escape', $options) ) { $string = htmlspecialchars ( $string ); + } elseif ( in_array( 'escapenoentities', $options ) ) { + $string = htmlspecialchars( $string ); + $string = str_replace( '&', '&', $string ); + $string = Sanitizer::normalizeCharReferences( $string ); } if( in_array('replaceafter', $options) ) { @@ -607,7 +638,6 @@ function wfMsgExt( $key, $options ) { * @deprecated Please return control to the caller or throw an exception */ function wfAbruptExit( $error = false ){ - global $wgLoadBalancer; static $called = false; if ( $called ){ exit( -1 ); @@ -628,7 +658,7 @@ function wfAbruptExit( $error = false ){ wfLogProfilingData(); if ( !$error ) { - $wgLoadBalancer->closeAll(); + wfGetLB()->closeAll(); } exit( -1 ); } @@ -651,7 +681,7 @@ function wfDie( $msg='' ) { } /** - * Throw a debugging exception. This function previously once exited the process, + * Throw a debugging exception. This function previously once exited the process, * but now throws an exception instead, with similar results. * * @param string $msg Message shown when dieing. @@ -870,7 +900,7 @@ function wfClientAcceptsGzip() { * @param $deflimit Default limit if none supplied * @param $optionname Name of a user preference to check against * @return array - * + * */ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) { global $wgRequest; @@ -889,8 +919,8 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) { */ function wfEscapeWikiText( $text ) { $text = str_replace( - array( '[', '|', '\'', 'ISBN ', 'RFC ', '://', "\n=", '{{' ), - array( '[', '|', ''', 'ISBN ', 'RFC ', '://', "\n=", '{{' ), + array( '[', '|', ']', '\'', 'ISBN ', 'RFC ', '://', "\n=", '{{' ), + array( '[', '|', ']', ''', 'ISBN ', 'RFC ', '://', "\n=", '{{' ), htmlspecialchars($text) ); return $text; } @@ -995,6 +1025,21 @@ function wfAppendQuery( $url, $query ) { return $url; } +/** + * Expand a potentially local URL to a fully-qualified URL. + * Assumes $wgServer is correct. :) + * @param string $url, either fully-qualified or a local path + query + * @return string Fully-qualified URL + */ +function wfExpandUrl( $url ) { + if( substr( $url, 0, 1 ) == '/' ) { + global $wgServer; + return $wgServer . $url; + } else { + return $url; + } +} + /** * This is obsolete, use SquidUpdate::purge() * @deprecated @@ -1422,41 +1467,35 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) { $uts=time(); } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) { # TS_DB - $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], - (int)$da[2],(int)$da[3],(int)$da[1]); } elseif (preg_match('/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D',$ts,$da)) { # TS_EXIF - $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], - (int)$da[2],(int)$da[3],(int)$da[1]); } elseif (preg_match('/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D',$ts,$da)) { # TS_MW - $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], - (int)$da[2],(int)$da[3],(int)$da[1]); - } elseif (preg_match('/^(\d{1,13})$/D',$ts,$da)) { + } elseif (preg_match('/^\d{1,13}$/D',$ts)) { # TS_UNIX $uts = $ts; - } elseif (preg_match('/^(\d{1,2})-(...)-(\d\d(\d\d)?) (\d\d)\.(\d\d)\.(\d\d)/', $ts, $da)) { + } elseif (preg_match('/^\d{1,2}-...-\d\d(?:\d\d)? \d\d\.\d\d\.\d\d/', $ts)) { # TS_ORACLE $uts = strtotime(preg_replace('/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3", str_replace("+00:00", "UTC", $ts))); } elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/', $ts, $da)) { # TS_ISO_8601 - $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], - (int)$da[2],(int)$da[3],(int)$da[1]); } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)[\+\- ](\d\d)$/',$ts,$da)) { # TS_POSTGRES - $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], - (int)$da[2],(int)$da[3],(int)$da[1]); } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/',$ts,$da)) { # TS_POSTGRES - $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], - (int)$da[2],(int)$da[3],(int)$da[1]); } else { # Bogus value; fall back to the epoch... wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n"); $uts = 0; } + if (count( $da ) ) { + // Warning! gmmktime() acts oddly if the month or day is set to 0 + // We may want to handle that explicitly at some point + $uts=gmmktime((int)$da[4],(int)$da[5],(int)$da[6], + (int)$da[2],(int)$da[3],(int)$da[1]); + } switch($outputtype) { case TS_UNIX: @@ -1522,9 +1561,9 @@ function wfGetCachedNotice( $name ) { global $wgOut, $parserMemc; $fname = 'wfGetCachedNotice'; wfProfileIn( $fname ); - + $needParse = false; - + if( $name === 'default' ) { // special case global $wgSiteNotice; @@ -1540,7 +1579,7 @@ function wfGetCachedNotice( $name ) { return( false ); } } - + $cachedNotice = $parserMemc->get( wfMemcKey( $name ) ); if( is_array( $cachedNotice ) ) { if( md5( $notice ) == $cachedNotice['hash'] ) { @@ -1551,7 +1590,7 @@ function wfGetCachedNotice( $name ) { } else { $needParse = true; } - + if( $needParse ) { if( is_object( $wgOut ) ) { $parsed = $wgOut->parse( $notice ); @@ -1562,21 +1601,21 @@ function wfGetCachedNotice( $name ) { $notice = ''; } } - + wfProfileOut( $fname ); return $notice; } function wfGetNamespaceNotice() { global $wgTitle; - + # Paranoia if ( !isset( $wgTitle ) || !is_object( $wgTitle ) ) return ""; $fname = 'wfGetNamespaceNotice'; wfProfileIn( $fname ); - + $key = "namespacenotice-" . $wgTitle->getNsText(); $namespaceNotice = wfGetCachedNotice( $key ); if ( $namespaceNotice && substr ( $namespaceNotice , 0 ,7 ) != "

<" ) { @@ -1593,8 +1632,8 @@ function wfGetSiteNotice() { global $wgUser, $wgSiteNotice; $fname = 'wfGetSiteNotice'; wfProfileIn( $fname ); - $siteNotice = ''; - + $siteNotice = ''; + if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) { if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) { $siteNotice = wfGetCachedNotice( 'sitenotice' ); @@ -1616,7 +1655,7 @@ function wfGetSiteNotice() { return $siteNotice; } -/** +/** * BC wrapper for MimeMagic::singleton() * @deprecated */ @@ -1659,13 +1698,29 @@ function wfMkdirParents( $fullDir, $mode = 0777 ) { /** * Increment a statistics counter */ - function wfIncrStats( $key ) { - global $wgMemc; - $key = wfMemcKey( 'stats', $key ); - if ( is_null( $wgMemc->incr( $key ) ) ) { - $wgMemc->add( $key, 1 ); - } - } +function wfIncrStats( $key ) { + global $wgStatsMethod; + + if( $wgStatsMethod == 'udp' ) { + global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname; + static $socket; + if (!$socket) { + $socket=socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + $statline="stats/{$wgDBname} - 1 1 1 1 1 -total\n"; + socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort); + } + $statline="stats/{$wgDBname} - 1 1 1 1 1 {$key}\n"; + @socket_sendto($socket,$statline,strlen($statline),0,$wgUDPProfilerHost,$wgUDPProfilerPort); + } elseif( $wgStatsMethod == 'cache' ) { + global $wgMemc; + $key = wfMemcKey( 'stats', $key ); + if ( is_null( $wgMemc->incr( $key ) ) ) { + $wgMemc->add( $key, 1 ); + } + } else { + // Disabled + } +} /** * @param mixed $nr The number to format @@ -1800,7 +1855,7 @@ function wfIniGetBool( $setting ) { */ function wfShellExec( $cmd, &$retval=null ) { global $IP, $wgMaxShellMemory, $wgMaxShellFileSize; - + if( wfIniGetBool( 'safe_mode' ) ) { wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" ); $retval = 1; @@ -1824,12 +1879,14 @@ function wfShellExec( $cmd, &$retval=null ) { $cmd = '"' . $cmd . '"'; } wfDebug( "wfShellExec: $cmd\n" ); - - $output = array(); + $retval = 1; // error by default? - exec( $cmd, $output, $retval ); // returns the last line of output. - return implode( "\n", $output ); - + ob_start(); + passthru( $cmd, $retval ); + $output = ob_get_contents(); + ob_end_clean(); + return $output; + } /** @@ -1888,7 +1945,7 @@ function wfRegexReplacement( $string ) { * * PHP's basename() only considers '\' a pathchar on Windows and Netware. * We'll consider it so always, as we don't want \s in our Unix paths either. - * + * * @param string $path * @param string $suffix to remove if present * @return string @@ -1918,10 +1975,20 @@ function wfRelativePath( $path, $from ) { // Normalize mixed input on Windows... $path = str_replace( '/', DIRECTORY_SEPARATOR, $path ); $from = str_replace( '/', DIRECTORY_SEPARATOR, $from ); - + + // Trim trailing slashes -- fix for drive root + $path = rtrim( $path, DIRECTORY_SEPARATOR ); + $from = rtrim( $from, DIRECTORY_SEPARATOR ); + $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) ); $against = explode( DIRECTORY_SEPARATOR, $from ); + if( $pieces[0] !== $against[0] ) { + // Non-matching Windows drive letters? + // Return a full path. + return $path; + } + // Trim off common prefix while( count( $pieces ) && count( $against ) && $pieces[0] == $against[0] ) { @@ -1940,13 +2007,35 @@ function wfRelativePath( $path, $from ) { return implode( DIRECTORY_SEPARATOR, $pieces ); } +/** + * array_merge() does awful things with "numeric" indexes, including + * string indexes when happen to look like integers. When we want + * to merge arrays with arbitrary string indexes, we don't want our + * arrays to be randomly corrupted just because some of them consist + * of numbers. + * + * Fuck you, PHP. Fuck you in the ear! + * + * @param array $array1, [$array2, [...]] + * @return array + */ +function wfArrayMerge( $array1/* ... */ ) { + $out = $array1; + for( $i = 1; $i < func_num_args(); $i++ ) { + foreach( func_get_arg( $i ) as $key => $value ) { + $out[$key] = $value; + } + } + return $out; +} + /** * Make a URL index, appropriate for the el_index field of externallinks. */ function wfMakeUrlIndex( $url ) { global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php - $bits = parse_url( $url ); wfSuppressWarnings(); + $bits = parse_url( $url ); wfRestoreWarnings(); if ( !$bits ) { return false; @@ -1959,7 +2048,7 @@ function wfMakeUrlIndex( $url ) { $delimiter = ':'; // parse_url detects for news: and mailto: the host part of an url as path // We have to correct this wrong detection - if ( isset ( $bits['path'] ) ) { + if ( isset ( $bits['path'] ) ) { $bits['host'] = $bits['path']; $bits['path'] = ''; } @@ -1970,13 +2059,19 @@ function wfMakeUrlIndex( $url ) { // Reverse the labels in the hostname, convert to lower case // For emails reverse domainpart only if ( $bits['scheme'] == 'mailto' ) { - $mailparts = explode( '@', $bits['host'] ); - $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) ); + $mailparts = explode( '@', $bits['host'], 2 ); + if ( count($mailparts) === 2 ) { + $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) ); + } else { + // No domain specified, don't mangle it + $domainpart = ''; + } $reversedHost = $domainpart . '@' . $mailparts[0]; } else { $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) ); } // Add an extra dot to the end + // Why? Is it in wrong place in mailto links? if ( substr( $reversedHost, -1, 1 ) !== '.' ) { $reversedHost .= '.'; } @@ -2050,7 +2145,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $inDigits = array(); $outChars = ''; - + // Decode and validate input string $input = strtolower( $input ); for( $i = 0; $i < strlen( $input ); $i++ ) { @@ -2060,18 +2155,18 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true } $inDigits[] = $n; } - + // Iterate over the input, modulo-ing out an output digit // at a time until input is gone. while( count( $inDigits ) ) { $work = 0; $workDigits = array(); - + // Long division... foreach( $inDigits as $digit ) { $work *= $sourceBase; $work += $digit; - + if( $work < $destBase ) { // Gonna need to pull another digit. if( count( $workDigits ) ) { @@ -2083,26 +2178,26 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true } else { // Finally! Actual division! $workDigits[] = intval( $work / $destBase ); - + // Isn't it annoying that most programming languages // don't have a single divide-and-remainder operator, // even though the CPU implements it that way? $work = $work % $destBase; } } - + // All that division leaves us with a remainder, // which is conveniently our next output digit. $outChars .= $digitChars[$work]; - + // And we continue! $inDigits = $workDigits; } - + while( strlen( $outChars ) < $pad ) { $outChars .= '0'; } - + return strrev( $outChars ); } @@ -2136,18 +2231,34 @@ function wfCreateObject( $name, $p ){ /** * Aliases for modularized functions */ -function wfGetHTTP( $url, $timeout = 'default' ) { - return Http::get( $url, $timeout ); +function wfGetHTTP( $url, $timeout = 'default' ) { + return Http::get( $url, $timeout ); } -function wfIsLocalURL( $url ) { - return Http::isLocalURL( $url ); +function wfIsLocalURL( $url ) { + return Http::isLocalURL( $url ); +} + +function wfHttpOnlySafe() { + global $wgHttpOnlyBlacklist; + if( !version_compare("5.2", PHP_VERSION, "<") ) + return false; + + if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) { + foreach( $wgHttpOnlyBlacklist as $regex ) { + if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) { + return false; + } + } + } + + return true; } /** * Initialise php session */ function wfSetupSession() { - global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain, $wgCookieSecure; + global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly; if( $wgSessionsInMemcached ) { require_once( 'MemcachedSessions.php' ); } elseif( 'files' != ini_get( 'session.save_handler' ) ) { @@ -2155,9 +2266,25 @@ function wfSetupSession() { # application, it will end up failing. Try to recover. ini_set ( 'session.save_handler', 'files' ); } - session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure); + $httpOnlySafe = wfHttpOnlySafe(); + wfDebugLog( 'cookie', + 'session_set_cookie_params: "' . implode( '", "', + array( + 0, + $wgCookiePath, + $wgCookieDomain, + $wgCookieSecure, + $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' ); + if( $httpOnlySafe && $wgCookieHttpOnly ) { + session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly ); + } else { + // PHP 5.1 throws warnings if you pass the HttpOnly parameter for 5.2. + session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure ); + } session_cache_limiter( 'private, must-revalidate' ); - @session_start(); + wfSuppressWarnings(); + session_start(); + wfRestoreWarnings(); } /** @@ -2181,11 +2308,7 @@ function wfGetPrecompiledData( $name ) { function wfGetCaller( $level = 2 ) { $backtrace = wfDebugBacktrace(); if ( isset( $backtrace[$level] ) ) { - if ( isset( $backtrace[$level]['class'] ) ) { - $caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function']; - } else { - $caller = $backtrace[$level]['function']; - } + return wfFormatStackFrame($backtrace[$level]); } else { $caller = 'unknown'; } @@ -2194,27 +2317,31 @@ function wfGetCaller( $level = 2 ) { /** Return a string consisting all callers in stack, somewhat useful sometimes for profiling specific points */ function wfGetAllCallers() { - return implode('/', array_map( - create_function('$frame',' - return isset( $frame["class"] )? - $frame["class"]."::".$frame["function"]: - $frame["function"]; - '), - array_reverse(wfDebugBacktrace()))); + return implode('/', array_map('wfFormatStackFrame',array_reverse(wfDebugBacktrace()))); +} + +/** Return a string representation of frame */ +function wfFormatStackFrame($frame) { + return isset( $frame["class"] )? + $frame["class"]."::".$frame["function"]: + $frame["function"]; } /** * Get a cache key */ function wfMemcKey( /*... */ ) { - global $wgDBprefix, $wgDBname; $args = func_get_args(); - if ( $wgDBprefix ) { - $key = "$wgDBname-$wgDBprefix:" . implode( ':', $args ); + $key = wfWikiID() . ':' . implode( ':', $args ); + return $key; +} + +function wfMemcKeyLang( $key, $code ) { + if ( !is_string($code) ) { + return $key; } else { - $key = $wgDBname . ':' . implode( ':', $args ); + return $key . ';L:' . $code; } - return $key; } /** @@ -2234,42 +2361,80 @@ function wfForeignMemcKey( $db, $prefix /*, ... */ ) { * Get an ASCII string identifying this wiki * This is used as a prefix in memcached keys */ -function wfWikiID() { - global $wgDBprefix, $wgDBname; - if ( $wgDBprefix ) { - return "$wgDBname-$wgDBprefix"; +function wfWikiID( $db = null ) { + if( $db instanceof Database ) { + return $db->getWikiID(); } else { - return $wgDBname; + global $wgDBprefix, $wgDBname; + if ( $wgDBprefix ) { + return "$wgDBname-$wgDBprefix"; + } else { + return $wgDBname; + } + } +} + +/** + * Split a wiki ID into DB name and table prefix + */ +function wfSplitWikiID( $wiki ) { + $bits = explode( '-', $wiki, 2 ); + if ( count( $bits ) < 2 ) { + $bits[] = ''; } + return $bits; } /* - * Get a Database object - * @param integer $db Index of the connection to get. May be DB_MASTER for the - * master (for write queries), DB_SLAVE for potentially lagged + * Get a Database object. + * @param integer $db Index of the connection to get. May be DB_MASTER for the + * master (for write queries), DB_SLAVE for potentially lagged * read queries, or an integer >= 0 for a particular server. * - * @param mixed $groups Query groups. An array of group names that this query - * belongs to. May contain a single string if the query is only + * @param mixed $groups Query groups. An array of group names that this query + * belongs to. May contain a single string if the query is only * in one group. + * + * @param string $wiki The wiki ID, or false for the current wiki + * + * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request + * will always return the same object, unless the underlying connection or load + * balancer is manually destroyed. + */ +function &wfGetDB( $db = DB_LAST, $groups = array(), $wiki = false ) { + return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki ); +} + +/** + * Get a load balancer object. + * + * @param array $groups List of query groups + * @param string $wiki Wiki ID, or false for the current wiki + * @return LoadBalancer + */ +function wfGetLB( $wiki = false ) { + return wfGetLBFactory()->getMainLB( $wiki ); +} + +/** + * Get the load balancer factory object */ -function &wfGetDB( $db = DB_LAST, $groups = array() ) { - global $wgLoadBalancer; - $ret = $wgLoadBalancer->getConnection( $db, true, $groups ); - return $ret; +function &wfGetLBFactory() { + return LBFactory::singleton(); } /** - * Find a file. + * Find a file. * Shortcut for RepoGroup::singleton()->findFile() * @param mixed $title Title object or string. May be interwiki. - * @param mixed $time Requested time for an archived image, or false for the - * current version. An image object will be returned which - * existed at or before the specified time. + * @param mixed $time Requested time for an archived image, or false for the + * current version. An image object will be returned which + * was created at the specified time. + * @param mixed $flags FileRepo::FIND_ flags * @return File, or false if the file does not exist */ -function wfFindFile( $title, $time = false ) { - return RepoGroup::singleton()->findFile( $title, $time ); +function wfFindFile( $title, $time = false, $flags = 0 ) { + return RepoGroup::singleton()->findFile( $title, $time, $flags ); } /** @@ -2318,13 +2483,35 @@ function wfBoolToStr( $value ) { /** * Load an extension messages file + * + * @param string $extensionName Name of extension to load messages from\for. + * @param string $langcode Language to load messages for, or false for default + * behvaiour (en, content language and user language). */ -function wfLoadExtensionMessages( $extensionName ) { - global $wgExtensionMessagesFiles, $wgMessageCache; - if ( !empty( $wgExtensionMessagesFiles[$extensionName] ) ) { - $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName] ); - // Prevent double-loading - $wgExtensionMessagesFiles[$extensionName] = false; +function wfLoadExtensionMessages( $extensionName, $langcode = false ) { + global $wgExtensionMessagesFiles, $wgMessageCache, $wgLang, $wgContLang; + + #For recording whether extension message files have been loaded in a given language. + static $loaded = array(); + + if( !array_key_exists( $extensionName, $loaded ) ) { + $loaded[$extensionName] = array(); + } + + if( !$langcode && !array_key_exists( '*', $loaded[$extensionName] ) ) { + # Just do en, content language and user language. + $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], false ); + # Mark that they have been loaded. + $loaded[$extensionName]['en'] = true; + $loaded[$extensionName][$wgLang->getCode()] = true; + $loaded[$extensionName][$wgContLang->getCode()] = true; + # Mark that this part has been done to avoid weird if statements. + $loaded[$extensionName]['*'] = true; + } elseif( is_string( $langcode ) && !array_key_exists( $langcode, $loaded[$extensionName] ) ) { + # Load messages for specified language. + $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], $langcode ); + # Mark that they have been loaded. + $loaded[$extensionName][$langcode] = true; } } @@ -2338,4 +2525,92 @@ function wfGetNull() { return wfIsWindows() ? 'NUL' : '/dev/null'; -} \ No newline at end of file +} + +/** + * Displays a maxlag error + * + * @param string $host Server that lags the most + * @param int $lag Maxlag (actual) + * @param int $maxLag Maxlag (requested) + */ +function wfMaxlagError( $host, $lag, $maxLag ) { + global $wgShowHostnames; + header( 'HTTP/1.1 503 Service Unavailable' ); + header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); + header( 'X-Database-Lag: ' . intval( $lag ) ); + header( 'Content-Type: text/plain' ); + if( $wgShowHostnames ) { + echo "Waiting for $host: $lag seconds lagged\n"; + } else { + echo "Waiting for a database server: $lag seconds lagged\n"; + } +} + +/** + * Throws an E_USER_NOTICE saying that $function is deprecated + * @param string $function + * @return null + */ +function wfDeprecated( $function ) { + global $wgDebugLogFile; + if ( !$wgDebugLogFile ) { + return; + } + $callers = wfDebugBacktrace(); + if( isset( $callers[2] ) ){ + $callerfunc = $callers[2]; + $callerfile = $callers[1]; + if( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ){ + $file = $callerfile['file'] . ' at line ' . $callerfile['line']; + } else { + $file = '(internal function)'; + } + $func = ''; + if( isset( $callerfunc['class'] ) ) + $func .= $callerfunc['class'] . '::'; + $func .= @$callerfunc['function']; + $msg = "Use of $function is deprecated. Called from $func in $file"; + } else { + $msg = "Use of $function is deprecated."; + } + wfDebug( "$msg\n" ); +} + +/** + * Sleep until the worst slave's replication lag is less than or equal to + * $maxLag, in seconds. Use this when updating very large numbers of rows, as + * in maintenance scripts, to avoid causing too much lag. Of course, this is + * a no-op if there are no slaves. + * + * Every time the function has to wait for a slave, it will print a message to + * that effect (and then sleep for a little while), so it's probably not best + * to use this outside maintenance scripts in its present form. + * + * @param int $maxLag + * @return null + */ +function wfWaitForSlaves( $maxLag ) { + if( $maxLag ) { + $lb = wfGetLB(); + list( $host, $lag ) = $lb->getMaxLag(); + while( $lag > $maxLag ) { + $name = @gethostbyaddr( $host ); + if( $name !== false ) { + $host = $name; + } + print "Waiting for $host (lagged $lag seconds)...\n"; + sleep($maxLag); + list( $host, $lag ) = $lb->getMaxLag(); + } + } +} + +/** Generate a random 32-character hexadecimal token. + * @param mixed $salt Some sort of salt, if necessary, to add to random characters before hashing. + */ +function wfGenerateToken( $salt = '' ) { + $salt = serialize($salt); + + return md5( mt_rand( 0, 0x7fffffff ) . $salt ); +}