+ * @param $msg String: the message key looked up
+ * @param $wfMsgOut String: the output of wfMsg*()
+ * @return Boolean
+ */
+function wfEmptyMsg( $msg, $wfMsgOut ) {
+ return $wfMsgOut === htmlspecialchars( "<$msg>" );
+}
+
+/**
+ * Find out whether or not a mixed variable exists in a string
+ *
+ * @param $needle String
+ * @param $str String
+ * @return Boolean
+ */
+function in_string( $needle, $str ) {
+ return strpos( $str, $needle ) !== false;
+}
+
+function wfSpecialList( $page, $details ) {
+ global $wgContLang;
+ $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : "";
+ return $page . $details;
+}
+
+/**
+ * Returns a regular expression of url protocols
+ *
+ * @return String
+ */
+function wfUrlProtocols() {
+ global $wgUrlProtocols;
+
+ static $retval = null;
+ if ( !is_null( $retval ) )
+ return $retval;
+
+ // Support old-style $wgUrlProtocols strings, for backwards compatibility
+ // with LocalSettings files from 1.5
+ if ( is_array( $wgUrlProtocols ) ) {
+ $protocols = array();
+ foreach ($wgUrlProtocols as $protocol)
+ $protocols[] = preg_quote( $protocol, '/' );
+
+ $retval = implode( '|', $protocols );
+ } else {
+ $retval = $wgUrlProtocols;
+ }
+ return $retval;
+}
+
+/**
+ * Safety wrapper around ini_get() for boolean settings.
+ * The values returned from ini_get() are pre-normalized for settings
+ * set via php.ini or php_flag/php_admin_flag... but *not*
+ * for those set via php_value/php_admin_value.
+ *
+ * It's fairly common for people to use php_value instead of php_flag,
+ * which can leave you with an 'off' setting giving a false positive
+ * for code that just takes the ini_get() return value as a boolean.
+ *
+ * To make things extra interesting, setting via php_value accepts
+ * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
+ * Unrecognized values go false... again opposite PHP's own coercion
+ * from string to bool.
+ *
+ * Luckily, 'properly' set settings will always come back as '0' or '1',
+ * so we only have to worry about them and the 'improper' settings.
+ *
+ * I frickin' hate PHP... :P
+ *
+ * @param $setting String
+ * @return Bool
+ */
+function wfIniGetBool( $setting ) {
+ $val = ini_get( $setting );
+ // 'on' and 'true' can't have whitespace around them, but '1' can.
+ return strtolower( $val ) == 'on'
+ || strtolower( $val ) == 'true'
+ || strtolower( $val ) == 'yes'
+ || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
+}
+
+/**
+ * Execute a shell command, with time and memory limits mirrored from the PHP
+ * configuration if supported.
+ * @param $cmd Command line, properly escaped for shell.
+ * @param &$retval optional, will receive the program's exit code.
+ * (non-zero is usually failure)
+ * @return collected stdout as a string (trailing newlines stripped)
+ */
+function wfShellExec( $cmd, &$retval=null ) {
+ global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
+
+ static $disabled;
+ if ( is_null( $disabled ) ) {
+ $disabled = false;
+ if( wfIniGetBool( 'safe_mode' ) ) {
+ wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
+ $disabled = true;
+ }
+ $functions = explode( ',', ini_get( 'disable_functions' ) );
+ $functions = array_map( 'trim', $functions );
+ $functions = array_map( 'strtolower', $functions );
+ if ( in_array( 'passthru', $functions ) ) {
+ wfDebug( "passthru is in disabled_functions\n" );
+ $disabled = true;
+ }
+ }
+ if ( $disabled ) {
+ $retval = 1;
+ return "Unable to run external programs in safe mode.";
+ }
+
+ wfInitShellLocale();
+
+ if ( php_uname( 's' ) == 'Linux' ) {
+ $time = intval( $wgMaxShellTime );
+ $mem = intval( $wgMaxShellMemory );
+ $filesize = intval( $wgMaxShellFileSize );
+
+ if ( $time > 0 && $mem > 0 ) {
+ $script = "$IP/bin/ulimit4.sh";
+ if ( is_executable( $script ) ) {
+ $cmd = escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
+ }
+ }
+ } elseif ( php_uname( 's' ) == 'Windows NT' &&
+ version_compare( PHP_VERSION, '5.3.0', '<' ) )
+ {
+ # This is a hack to work around PHP's flawed invocation of cmd.exe
+ # http://news.php.net/php.internals/21796
+ # Which is fixed in 5.3.0 :)
+ $cmd = '"' . $cmd . '"';
+ }
+ wfDebug( "wfShellExec: $cmd\n" );
+
+ $retval = 1; // error by default?
+ ob_start();
+ passthru( $cmd, $retval );
+ $output = ob_get_contents();
+ ob_end_clean();
+
+ if ( $retval == 127 ) {
+ wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
+ }
+ return $output;
+}
+
+/**
+ * Workaround for http://bugs.php.net/bug.php?id=45132
+ * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
+ */
+function wfInitShellLocale() {
+ static $done = false;
+ if ( $done ) return;
+ $done = true;
+ global $wgShellLocale;
+ if ( !wfIniGetBool( 'safe_mode' ) ) {
+ putenv( "LC_CTYPE=$wgShellLocale" );
+ setlocale( LC_CTYPE, $wgShellLocale );
+ }
+}
+
+/**
+ * This function works like "use VERSION" in Perl, the program will die with a
+ * backtrace if the current version of PHP is less than the version provided
+ *
+ * This is useful for extensions which due to their nature are not kept in sync
+ * with releases, and might depend on other versions of PHP than the main code
+ *
+ * Note: PHP might die due to parsing errors in some cases before it ever
+ * manages to call this function, such is life
+ *
+ * @see perldoc -f use
+ *
+ * @param $req_ver Mixed: the version to check, can be a string, an integer, or
+ * a float
+ */
+function wfUsePHP( $req_ver ) {
+ $php_ver = PHP_VERSION;
+
+ if ( version_compare( $php_ver, (string)$req_ver, '<' ) )
+ throw new MWException( "PHP $req_ver required--this is only $php_ver" );
+}
+
+/**
+ * This function works like "use VERSION" in Perl except it checks the version
+ * of MediaWiki, the program will die with a backtrace if the current version
+ * of MediaWiki is less than the version provided.
+ *
+ * This is useful for extensions which due to their nature are not kept in sync
+ * with releases
+ *
+ * @see perldoc -f use
+ *
+ * @param $req_ver Mixed: the version to check, can be a string, an integer, or
+ * a float
+ */
+function wfUseMW( $req_ver ) {
+ global $wgVersion;
+
+ if ( version_compare( $wgVersion, (string)$req_ver, '<' ) )
+ throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
+}
+
+/**
+ * @deprecated use StringUtils::escapeRegexReplacement
+ */
+function wfRegexReplacement( $string ) {
+ return StringUtils::escapeRegexReplacement( $string );
+}
+
+/**
+ * Return the final portion of a pathname.
+ * Reimplemented because PHP5's basename() is buggy with multibyte text.
+ * http://bugs.php.net/bug.php?id=33898
+ *
+ * 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 $path String
+ * @param $suffix String: to remove if present
+ * @return String
+ */
+function wfBaseName( $path, $suffix='' ) {
+ $encSuffix = ($suffix == '')
+ ? ''
+ : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
+ $matches = array();
+ if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
+ return $matches[1];
+ } else {
+ return '';
+ }
+}
+
+/**
+ * Generate a relative path name to the given file.
+ * May explode on non-matching case-insensitive paths,
+ * funky symlinks, etc.
+ *
+ * @param $path String: absolute destination path including target filename
+ * @param $from String: Absolute source path, directory only
+ * @return String
+ */
+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] ) {
+ array_shift( $pieces );
+ array_shift( $against );
+ }
+
+ // relative dots to bump us to the parent
+ while( count( $against ) ) {
+ array_unshift( $pieces, '..' );
+ array_shift( $against );
+ }
+
+ array_push( $pieces, wfBaseName( $path ) );
+
+ return implode( DIRECTORY_SEPARATOR, $pieces );
+}
+
+/**
+ * Backwards array plus for people who haven't bothered to read the PHP manual
+ * XXX: will not darn your socks for you.
+ *
+ * @param $array1 Array
+ * @param [$array2, [...]] Arrays
+ * @return Array
+ */
+function wfArrayMerge( $array1/* ... */ ) {
+ $args = func_get_args();
+ $args = array_reverse( $args, true );
+ $out = array();
+ foreach ( $args as $arg ) {
+ $out += $arg;
+ }
+ return $out;
+}
+
+/**
+ * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
+ * e.g.
+ * wfMergeErrorArrays(
+ * array( array( 'x' ) ),
+ * array( array( 'x', '2' ) ),
+ * array( array( 'x' ) ),
+ * array( array( 'y') )
+ * );
+ * returns:
+ * array(
+ * array( 'x', '2' ),
+ * array( 'x' ),
+ * array( 'y' )
+ * )
+ */
+function wfMergeErrorArrays(/*...*/) {
+ $args = func_get_args();
+ $out = array();
+ foreach ( $args as $errors ) {
+ foreach ( $errors as $params ) {
+ $spec = implode( "\t", $params );
+ $out[$spec] = $params;
+ }
+ }
+ return array_values( $out );
+}
+
+/**
+ * parse_url() work-alike, but non-broken. Differences:
+ *
+ * 1) Does not raise warnings on bad URLs (just returns false)
+ * 2) Handles protocols that don't use :// (e.g., mailto: and news:) correctly
+ * 3) Adds a "delimiter" element to the array, either '://' or ':' (see (2))
+ *
+ * @param $url String: a URL to parse
+ * @return Array: bits of the URL in an associative array, per PHP docs
+ */
+function wfParseUrl( $url ) {
+ global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
+ wfSuppressWarnings();
+ $bits = parse_url( $url );
+ wfRestoreWarnings();
+ if ( !$bits ) {
+ return false;
+ }
+
+ // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
+ if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
+ $bits['delimiter'] = '://';
+ } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
+ $bits['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'] ) ) {
+ $bits['host'] = $bits['path'];
+ $bits['path'] = '';
+ }
+ } else {
+ return false;
+ }
+
+ return $bits;
+}
+
+/**
+ * Make a URL index, appropriate for the el_index field of externallinks.
+ */
+function wfMakeUrlIndex( $url ) {
+ $bits = wfParseUrl( $url );
+
+ // Reverse the labels in the hostname, convert to lower case
+ // For emails reverse domainpart only
+ if ( $bits['scheme'] == 'mailto' ) {
+ $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 .= '.';
+ }
+ // Reconstruct the pseudo-URL
+ $prot = $bits['scheme'];
+ $index = $prot . $bits['delimiter'] . $reversedHost;
+ // Leave out user and password. Add the port, path, query and fragment
+ if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port'];
+ if ( isset( $bits['path'] ) ) {
+ $index .= $bits['path'];
+ } else {
+ $index .= '/';
+ }
+ if ( isset( $bits['query'] ) ) $index .= '?' . $bits['query'];
+ if ( isset( $bits['fragment'] ) ) $index .= '#' . $bits['fragment'];
+ return $index;
+}
+
+/**
+ * Do any deferred updates and clear the list
+ * TODO: This could be in Wiki.php if that class made any sense at all
+ */
+function wfDoUpdates()
+{
+ global $wgPostCommitUpdateList, $wgDeferredUpdateList;
+ foreach ( $wgDeferredUpdateList as $update ) {
+ $update->doUpdate();
+ }
+ foreach ( $wgPostCommitUpdateList as $update ) {
+ $update->doUpdate();
+ }
+ $wgDeferredUpdateList = array();
+ $wgPostCommitUpdateList = array();
+}
+
+/**
+ * @deprecated use StringUtils::explodeMarkup
+ */
+function wfExplodeMarkup( $separator, $text ) {
+ return StringUtils::explodeMarkup( $separator, $text );
+}
+
+/**
+ * Convert an arbitrarily-long digit string from one numeric base
+ * to another, optionally zero-padding to a minimum column width.
+ *
+ * Supports base 2 through 36; digit values 10-36 are represented
+ * as lowercase letters a-z. Input is case-insensitive.
+ *
+ * @param $input String: of digits
+ * @param $sourceBase Integer: 2-36
+ * @param $destBase Integer: 2-36
+ * @param $pad Integer: 1 or greater
+ * @param $lowercase Boolean
+ * @return String or false on invalid input
+ */
+function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true ) {
+ $input = strval( $input );
+ if( $sourceBase < 2 ||
+ $sourceBase > 36 ||
+ $destBase < 2 ||
+ $destBase > 36 ||
+ $pad < 1 ||
+ $sourceBase != intval( $sourceBase ) ||
+ $destBase != intval( $destBase ) ||
+ $pad != intval( $pad ) ||
+ !is_string( $input ) ||
+ $input == '' ) {
+ return false;
+ }
+ $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ $inDigits = array();
+ $outChars = '';
+
+ // Decode and validate input string
+ $input = strtolower( $input );
+ for( $i = 0; $i < strlen( $input ); $i++ ) {
+ $n = strpos( $digitChars, $input{$i} );
+ if( $n === false || $n > $sourceBase ) {
+ return false;
+ }
+ $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 ) ) {
+ // Avoid zero-padding; this lets us find
+ // the end of the input very easily when
+ // length drops to zero.
+ $workDigits[] = 0;
+ }
+ } 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 );
+}
+
+/**
+ * Create an object with a given name and an array of construct parameters
+ * @param $name String
+ * @param $p Array: parameters
+ */
+function wfCreateObject( $name, $p ){
+ $p = array_values( $p );
+ switch ( count( $p ) ) {
+ case 0:
+ return new $name;
+ case 1:
+ return new $name( $p[0] );
+ case 2:
+ return new $name( $p[0], $p[1] );
+ case 3:
+ return new $name( $p[0], $p[1], $p[2] );
+ case 4:
+ return new $name( $p[0], $p[1], $p[2], $p[3] );
+ case 5:
+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4] );
+ case 6:
+ return new $name( $p[0], $p[1], $p[2], $p[3], $p[4], $p[5] );
+ default:
+ throw new MWException( "Too many arguments to construtor in wfCreateObject" );
+ }
+}
+
+/**
+ * Alias for modularized function
+ * @deprecated Use Http::get() instead
+ */
+function wfGetHTTP( $url ) {
+ wfDeprecated(__FUNCTION__);
+ return Http::get( $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, $wgCookieHttpOnly, $wgSessionHandler;
+ if( $wgSessionsInMemcached ) {
+ require_once( 'MemcachedSessions.php' );
+ } elseif( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
+ # Only set this if $wgSessionHandler isn't null and session.save_handler
+ # hasn't already been set to the desired value (that causes errors)
+ ini_set ( 'session.save_handler', $wgSessionHandler );
+ }
+ $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' );
+ wfSuppressWarnings();
+ session_start();
+ wfRestoreWarnings();
+}
+
+/**
+ * Get an object from the precompiled serialized directory
+ *
+ * @return Mixed: the variable on success, false on failure
+ */
+function wfGetPrecompiledData( $name ) {
+ global $IP;
+
+ $file = "$IP/serialized/$name";
+ if ( file_exists( $file ) ) {
+ $blob = file_get_contents( $file );
+ if ( $blob ) {
+ return unserialize( $blob );
+ }
+ }
+ return false;
+}
+
+function wfGetCaller( $level = 2 ) {
+ $backtrace = wfDebugBacktrace();
+ if ( isset( $backtrace[$level] ) ) {
+ return wfFormatStackFrame($backtrace[$level]);
+ } else {
+ $caller = 'unknown';
+ }
+ return $caller;
+}
+
+/**
+ * Return a string consisting all callers in stack, somewhat useful sometimes
+ * for profiling specific points
+ */
+function wfGetAllCallers() {
+ 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( /*... */ ) {
+ $args = func_get_args();
+ $key = wfWikiID() . ':' . implode( ':', $args );
+ $key = str_replace( ' ', '_', $key );
+ return $key;
+}
+
+/**
+ * Get a cache key for a foreign DB
+ */
+function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
+ $args = array_slice( func_get_args(), 2 );
+ if ( $prefix ) {
+ $key = "$db-$prefix:" . implode( ':', $args );
+ } else {
+ $key = $db . ':' . implode( ':', $args );
+ }
+ return $key;
+}
+
+/**
+ * Get an ASCII string identifying this wiki
+ * This is used as a prefix in memcached keys