diff options
Diffstat (limited to 'includes/GlobalFunctions.php')
-rw-r--r-- | includes/GlobalFunctions.php | 632 |
1 files changed, 497 insertions, 135 deletions
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 2b9543b4..f5a2660c 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. @@ -153,12 +176,16 @@ function wfDebug( $text, $logonly = false ) { global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage; static $recursion = 0; + static $cache = array(); // Cache of unoutputted messages + # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) { return; } if ( $wgDebugComments && !$logonly ) { + $cache[] = $text; + if ( !isset( $wgOut ) ) { return; } @@ -170,7 +197,10 @@ function wfDebug( $text, $logonly = false ) { $wgOut->_unstub(); $recursion--; } - $wgOut->debug( $text ); + + // add the message and possible cached ones to the output + array_map( array( $wgOut, 'debug' ), $cache ); + $cache = array(); } if ( '' != $wgDebugLogFile && !$wgProfileOnly ) { # Strip unprintables; they can switch terminal modes when binary data @@ -232,29 +262,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 +313,11 @@ function wfReadOnly() { return (bool)$wgReadOnly; } +function wfReadOnlyReason() { + global $wgReadOnly; + wfReadOnly(); + return $wgReadOnly; +} /** * Get a message from anywhere, for the current user language. @@ -291,9 +327,9 @@ function wfReadOnly() { * * @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 ) { @@ -415,24 +451,38 @@ 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; + wfRunHooks('NormalizeMessageKey', array(&$key, &$useDB, &$langCode, &$transform)); + # If $wgMessageCache isn't initialised yet, try to return something sensible. if( is_object( $wgMessageCache ) ) { - $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 @@ -523,6 +573,8 @@ function wfMsgWikiHtml( $key ) { * <i>replaceafter</i>: parameters are substituted after parsing or escaping * <i>parsemag</i>: transform the message using magic phrases * <i>content</i>: fetch message for content language instead of interface + * <i>language</i>: language code to fetch message for (overriden by <i>content</i>), its behaviour + * with parser, parseinline and parsemag is undefined. * Behavior for conflicting options (e.g., parse+parseinline) is undefined. */ function wfMsgExt( $key, $options ) { @@ -536,12 +588,23 @@ 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) ) { + # Fallback to en, instead of whatever interface language we might have + $langCode = 'en'; + } + } 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 ); @@ -585,7 +648,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 ); @@ -606,7 +668,7 @@ function wfAbruptExit( $error = false ){ wfLogProfilingData(); if ( !$error ) { - $wgLoadBalancer->closeAll(); + wfGetLB()->closeAll(); } exit( -1 ); } @@ -629,7 +691,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. @@ -848,7 +910,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; @@ -947,7 +1009,20 @@ function wfArrayToCGI( $array1, $array2 = NULL ) if ( '' != $cgi ) { $cgi .= '&'; } - $cgi .= urlencode( $key ) . '=' . urlencode( $value ); + if(is_array($value)) + { + $firstTime = true; + foreach($value as $v) + { + $cgi .= ($firstTime ? '' : '&') . + urlencode( $key . '[]' ) . '=' . + urlencode( $v ); + $firstTime = false; + } + } + else + $cgi .= urlencode( $key ) . '=' . + urlencode( $value ); } } return $cgi; @@ -1104,6 +1179,68 @@ function wfMerge( $old, $mine, $yours, &$result ){ } /** + * Returns unified plain-text diff of two texts. + * Useful for machine processing of diffs. + * @param $before string The text before the changes. + * @param $after string The text after the changes. + * @param $params string Command-line options for the diff command. + * @return string Unified diff of $before and $after + */ +function wfDiff( $before, $after, $params = '-u' ) { + global $wgDiff; + + # This check may also protect against code injection in + # case of broken installations. + if( !file_exists( $wgDiff ) ){ + wfDebug( "diff executable not found\n" ); + $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) ); + $format = new UnifiedDiffFormatter(); + return $format->format( $diffs ); + } + + # Make temporary files + $td = wfTempDir(); + $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' ); + $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' ); + + fwrite( $oldtextFile, $before ); fclose( $oldtextFile ); + fwrite( $newtextFile, $after ); fclose( $newtextFile ); + + // Get the diff of the two files + $cmd = "$wgDiff " . $params . ' ' .wfEscapeShellArg( $oldtextName, $newtextName ); + + $h = popen( $cmd, 'r' ); + + $diff = ''; + + do { + $data = fread( $h, 8192 ); + if ( strlen( $data ) == 0 ) { + break; + } + $diff .= $data; + } while ( true ); + + // Clean up + pclose( $h ); + unlink( $oldtextName ); + unlink( $newtextName ); + + // Kill the --- and +++ lines. They're not useful. + $diff_lines = explode( "\n", $diff ); + if (strpos( $diff_lines[0], '---' ) === 0) { + unset($diff_lines[0]); + } + if (strpos( $diff_lines[1], '+++' ) === 0) { + unset($diff_lines[1]); + } + + $diff = implode( "\n", $diff_lines ); + + return $diff; +} + +/** * @todo document */ function wfVarDump( $var ) { @@ -1208,7 +1345,7 @@ function wfClearOutputBuffers() { function wfAcceptToPrefs( $accept, $def = '*/*' ) { # No arg means accept anything (per HTTP spec) if( !$accept ) { - return array( $def => 1 ); + return array( $def => 1.0 ); } $prefs = array(); @@ -1217,12 +1354,12 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) { foreach( $parts as $part ) { # FIXME: doesn't deal with params like 'text/html; level=1' - @list( $value, $qpart ) = explode( ';', $part ); + @list( $value, $qpart ) = explode( ';', trim( $part ) ); $match = array(); if( !isset( $qpart ) ) { - $prefs[$value] = 1; + $prefs[$value] = 1.0; } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) { - $prefs[$value] = $match[1]; + $prefs[$value] = floatval($match[1]); } } @@ -1415,41 +1552,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: @@ -1515,9 +1646,9 @@ function wfGetCachedNotice( $name ) { global $wgOut, $parserMemc; $fname = 'wfGetCachedNotice'; wfProfileIn( $fname ); - + $needParse = false; - + if( $name === 'default' ) { // special case global $wgSiteNotice; @@ -1533,7 +1664,7 @@ function wfGetCachedNotice( $name ) { return( false ); } } - + $cachedNotice = $parserMemc->get( wfMemcKey( $name ) ); if( is_array( $cachedNotice ) ) { if( md5( $notice ) == $cachedNotice['hash'] ) { @@ -1544,7 +1675,7 @@ function wfGetCachedNotice( $name ) { } else { $needParse = true; } - + if( $needParse ) { if( is_object( $wgOut ) ) { $parsed = $wgOut->parse( $notice ); @@ -1555,21 +1686,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 ) != "<p><" ) { @@ -1586,8 +1717,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' ); @@ -1609,7 +1740,7 @@ function wfGetSiteNotice() { return $siteNotice; } -/** +/** * BC wrapper for MimeMagic::singleton() * @deprecated */ @@ -1640,13 +1771,70 @@ function wfTempDir() { /** * Make directory, and make all parent directories if they don't exist + * + * @param string $fullDir Full path to directory to create + * @param int $mode Chmod value to use, default is $wgDirectoryMode + * @return bool */ -function wfMkdirParents( $fullDir, $mode = 0777 ) { +function wfMkdirParents( $fullDir, $mode = null ) { + global $wgDirectoryMode; if( strval( $fullDir ) === '' ) return true; if( file_exists( $fullDir ) ) return true; - return mkdir( str_replace( '/', DIRECTORY_SEPARATOR, $fullDir ), $mode, true ); + // If not defined or isn't an int, set to default + if ( is_null( $mode ) ) { + $mode = $wgDirectoryMode; + } + + + # Go back through the paths to find the first directory that exists + $currentDir = $fullDir; + $createList = array(); + while ( strval( $currentDir ) !== '' && !file_exists( $currentDir ) ) { + # Strip trailing slashes + $currentDir = rtrim( $currentDir, '/\\' ); + + # Add to create list + $createList[] = $currentDir; + + # Find next delimiter searching from the end + $p = max( strrpos( $currentDir, '/' ), strrpos( $currentDir, '\\' ) ); + if ( $p === false ) { + $currentDir = false; + } else { + $currentDir = substr( $currentDir, 0, $p ); + } + } + + if ( count( $createList ) == 0 ) { + # Directory specified already exists + return true; + } elseif ( $currentDir === false ) { + # Went all the way back to root and it apparently doesn't exist + wfDebugLog( 'mkdir', "Root doesn't exist?\n" ); + return false; + } + # Now go forward creating directories + $createList = array_reverse( $createList ); + + # Is the parent directory writable? + if ( $currentDir === '' ) { + $currentDir = '/'; + } + if ( !is_writable( $currentDir ) ) { + wfDebugLog( 'mkdir', "Not writable: $currentDir\n" ); + return false; + } + + foreach ( $createList as $dir ) { + # use chmod to override the umask, as suggested by the PHP manual + if ( !mkdir( $dir, $mode ) || !chmod( $dir, $mode ) ) { + wfDebugLog( 'mkdir', "Unable to create directory $dir\n" ); + return false; + } + } + return true; } /** @@ -1654,7 +1842,7 @@ function wfMkdirParents( $fullDir, $mode = 0777 ) { */ function wfIncrStats( $key ) { global $wgStatsMethod; - + if( $wgStatsMethod == 'udp' ) { global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname; static $socket; @@ -1693,15 +1881,12 @@ function wfPercent( $nr, $acc = 2, $round = true ) { * @param string $userid ID of the user * @param string $password Password of the user * @return string Hashed password + * @deprecated Use User::crypt() or User::oldCrypt() instead */ function wfEncryptPassword( $userid, $password ) { - global $wgPasswordSalt; - $p = md5( $password); - - if($wgPasswordSalt) - return md5( "{$userid}-{$p}" ); - else - return $p; + wfDeprecated(__FUNCTION__); + # Just wrap around User::oldCrypt() + return User::oldCrypt($password, $userid); } /** @@ -1809,7 +1994,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; @@ -1833,14 +2018,14 @@ function wfShellExec( $cmd, &$retval=null ) { $cmd = '"' . $cmd . '"'; } wfDebug( "wfShellExec: $cmd\n" ); - + $retval = 1; // error by default? ob_start(); passthru( $cmd, $retval ); $output = ob_get_contents(); ob_end_clean(); return $output; - + } /** @@ -1899,7 +2084,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 @@ -1929,14 +2114,14 @@ 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. @@ -2002,7 +2187,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'] = ''; } @@ -2099,7 +2284,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++ ) { @@ -2109,18 +2294,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 ) ) { @@ -2132,26 +2317,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 ); } @@ -2183,20 +2368,44 @@ function wfCreateObject( $name, $p ){ } /** - * Aliases for modularized functions + * Alias for modularized function + * @deprecated Use Http::get() instead */ -function wfGetHTTP( $url, $timeout = 'default' ) { - return Http::get( $url, $timeout ); +function wfGetHTTP( $url, $timeout = 'default' ) { + wfDeprecated(__FUNCTION__); + return Http::get( $url, $timeout ); } -function wfIsLocalURL( $url ) { - return Http::isLocalURL( $url ); + +/** + * Alias for modularized function + * @deprecated Use Http::isLocalURL() instead + */ +function wfIsLocalURL( $url ) { + wfDeprecated(__FUNCTION__); + 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' ) ) { @@ -2204,9 +2413,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(); } /** @@ -2253,13 +2478,8 @@ function wfFormatStackFrame($frame) { * Get a cache key */ function wfMemcKey( /*... */ ) { - global $wgDBprefix, $wgDBname; $args = func_get_args(); - if ( $wgDBprefix ) { - $key = "$wgDBname-$wgDBprefix:" . implode( ':', $args ); - } else { - $key = $wgDBname . ':' . implode( ':', $args ); - } + $key = wfWikiID() . ':' . implode( ':', $args ); return $key; } @@ -2280,42 +2500,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() ) { - global $wgLoadBalancer; - $ret = $wgLoadBalancer->getConnection( $db, true, $groups ); - return $ret; +function &wfGetDB( $db = DB_LAST, $groups = array(), $wiki = false ) { + return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki ); } /** - * Find a file. + * 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 &wfGetLBFactory() { + return LBFactory::singleton(); +} + +/** + * 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 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 ); } /** @@ -2364,13 +2622,39 @@ 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 ( !isset($wgExtensionMessagesFiles[$extensionName]) ) { + throw new MWException( "Messages file for extensions $extensionName is not defined" ); + } + + 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; } } @@ -2388,7 +2672,7 @@ function wfGetNull() { /** * Displays a maxlag error - * + * * @param string $host Server that lags the most * @param int $lag Maxlag (actual) * @param int $maxLag Maxlag (requested) @@ -2405,3 +2689,81 @@ function wfMaxlagError( $host, $lag, $maxLag ) { 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 ); +} + +/** + * Replace all invalid characters with - + * @param mixed $title Filename to process + */ +function wfStripIllegalFilenameChars( $name ) { + $name = wfBaseName( $name ); + $name = preg_replace ( "/[^".Title::legalChars()."]|:/", '-', $name ); + return $name; +} |