require_once dirname(__FILE__) . '/normal/UtfNormalUtil.php';
require_once dirname(__FILE__) . '/XmlFunctions.php';
+// Hide compatibility functions from Doxygen
+/// @cond
+
/**
* Compatibility functions
*
}
}
+/// @endcond
+
+
/**
* Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
*/
}
/**
- * We want / and : to be included as literal characters in our title URLs.
+ * We want some things to be included as literal characters in our title URLs
+ * for prettiness, which urlencode encodes by default. According to RFC 1738,
+ * all of the following should be safe:
+ *
+ * ;:@&=$-_.+!*'(),
+ *
+ * But + is not safe because it's used to indicate a space; &= are only safe in
+ * paths and not in queries (and we don't distinguish here); ' seems kind of
+ * scary; and urlencode() doesn't touch -_. to begin with. Plus, although /
+ * is reserved, we don't care. So the list we unescape is:
+ *
+ * ;:@$!*(),/
+ *
* %2F in the page titles seems to fatally break for some reason.
*
* @param $s String:
* @return string
*/
-function wfUrlencode ( $s ) {
+function wfUrlencode( $s ) {
$s = urlencode( $s );
- $s = preg_replace( '/%3[Aa]/', ':', $s );
- $s = preg_replace( '/%2[Ff]/', '/', $s );
+ $s = str_ireplace(
+ array( '%3B','%3A','%40','%24','%21','%2A','%28','%29','%2C','%2F' ),
+ array( ';', ':', '@', '$', '!', '*', '(', ')', ',', '/' ),
+ $s
+ );
return $s;
}
}
}
+/**
+ * Send a line giving PHP memory usage.
+ * @param $exact Bool : print exact values instead of kilobytes (default: false)
+ */
+function wfDebugMem( $exact = false ) {
+ $mem = memory_get_usage();
+ if( !$exact ) {
+ $mem = floor( $mem / 1024 ) . ' kilobytes';
+ } else {
+ $mem .= ' bytes';
+ }
+ wfDebug( "Memory usage: $mem\n" );
+}
+
/**
* Send a line to a supplementary debug log file, if configured, or main debug log if not.
* $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
* log file is specified, (default true)
*/
function wfDebugLog( $logGroup, $text, $public = true ) {
- global $wgDebugLogGroups;
+ global $wgDebugLogGroups, $wgShowHostnames;
if( $text{strlen( $text ) - 1} != "\n" ) $text .= "\n";
if( isset( $wgDebugLogGroups[$logGroup] ) ) {
$time = wfTimestamp( TS_DB );
$wiki = wfWikiID();
- wfErrorLog( "$time $wiki: $text", $wgDebugLogGroups[$logGroup] );
+ if ( $wgShowHostnames ) {
+ $host = wfHostname();
+ } else {
+ $host = '';
+ }
+ wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
} else if ( $public === true ) {
wfDebug( $text, true );
}
}
/**
- * Log to a file without getting "file size exceeded" signals
+ * Log to a file without getting "file size exceeded" signals.
+ *
+ * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
+ * send lines to the specified port, prefixed by the specified prefix and a space.
*/
function wfErrorLog( $text, $file ) {
- wfSuppressWarnings();
- $exists = file_exists( $file );
- $size = $exists ? filesize( $file ) : false;
- if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
- error_log( $text, 3, $file );
+ if ( substr( $file, 0, 4 ) == 'udp:' ) {
+ if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
+ // IPv6 bracketed host
+ $protocol = $m[1];
+ $host = $m[2];
+ $port = $m[3];
+ $prefix = isset( $m[4] ) ? $m[4] : false;
+ } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
+ $protocol = $m[1];
+ $host = $m[2];
+ $port = $m[3];
+ $prefix = isset( $m[4] ) ? $m[4] : false;
+ } else {
+ throw new MWException( __METHOD__.": Invalid UDP specification" );
+ }
+ // Clean it up for the multiplexer
+ if ( strval( $prefix ) !== '' ) {
+ $text = preg_replace( '/^/m', $prefix . ' ', $text );
+ if ( substr( $text, -1 ) != "\n" ) {
+ $text .= "\n";
+ }
+ }
+
+ $sock = fsockopen( "$protocol://$host", $port );
+ if ( !$sock ) {
+ return;
+ }
+ fwrite( $sock, $text );
+ fclose( $sock );
+ } else {
+ wfSuppressWarnings();
+ $exists = file_exists( $file );
+ $size = $exists ? filesize( $file ) : false;
+ if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
+ error_log( $text, 3, $file );
+ }
+ wfRestoreWarnings();
}
- wfRestoreWarnings();
}
/**
return $wgReadOnly;
}
+/**
+ * Return a Language object from $langcode
+ * @param $langcode Mixed: either:
+ * - a Language object
+ * - code of the language to get the message for, if it is
+ * a valid code create a language for that language, if
+ * it is a string but not a valid code then make a basic
+ * language object
+ * - a boolean: if it's false then use the current users
+ * language (as a fallback for the old parameter
+ * functionality), or if it is true then use the wikis
+ * @return Language object
+ */
+function wfGetLangObj( $langcode = false ){
+ # Identify which language to get or create a language object for.
+ if( $langcode instanceof Language )
+ # Great, we already have the object!
+ return $langcode;
+
+ global $wgContLang;
+ if( $langcode === $wgContLang->getCode() || $langcode === true )
+ # $langcode is the language code of the wikis content language object.
+ # or it is a boolean and value is true
+ return $wgContLang;
+
+ global $wgLang;
+ if( $langcode === $wgLang->getCode() || $langcode === false )
+ # $langcode is the language code of user language object.
+ # or it was a boolean and value is false
+ return $wgLang;
+
+ $validCodes = array_keys( Language::getLanguageNames() );
+ if( in_array( $langcode, $validCodes ) )
+ # $langcode corresponds to a valid language.
+ return Language::factory( $langcode );
+
+ # $langcode is a string, but not a valid language code; use content language.
+ wfDebug( 'Invalid language code passed to wfGetLangObj, falling back to content language.' );
+ return $wgContLang;
+}
+
/**
* Get a message from anywhere, for the current user language.
*
* @private
*/
function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
- global $wgParser, $wgContLang, $wgMessageCache, $wgLang;
+ global $wgContLang, $wgMessageCache;
wfRunHooks('NormalizeMessageKey', array(&$key, &$useDB, &$langCode, &$transform));
$message = $wgMessageCache->transform( $message );
}
} else {
- if( $langCode === true ) {
- $lang = &$wgContLang;
- } 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.' );
- }
- }
+ $lang = wfGetLangObj( $langCode );
# MessageCache::get() does this already, Language::getMessage() doesn't
# ISSUE: Should we try to handle "message/lang" here too?
/**
* Returns message in the requested format
* @param string $key Key of the message
- * @param array $options Processing rules:
- * <i>parse</i>: parses wikitext to html
- * <i>parseinline</i>: parses wikitext to html and removes the surrounding p's added by parser or tidy
- * <i>escape</i>: filters message through htmlspecialchars
- * <i>escapenoentities</i>: same, but allows entity references like through
- * <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.
+ * @param array $options Processing rules. Can take the following options:
+ * <i>parse</i>: parses wikitext to html
+ * <i>parseinline</i>: parses wikitext to html and removes the surrounding
+ * p's added by parser or tidy
+ * <i>escape</i>: filters message through htmlspecialchars
+ * <i>escapenoentities</i>: same, but allows entity references like through
+ * <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
+ * Also can accept a single associative argument, of the form 'language' => 'xx':
+ * <i>language</i>: Language object or 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 ) {
$args = func_get_args();
array_shift( $args );
array_shift( $args );
-
- if( !is_array($options) ) {
- $options = array($options);
+ $options = (array)$options;
+
+ foreach( $options as $arrayKey => $option ) {
+ if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
+ # An unknown index, neither numeric nor "language"
+ trigger_error( "wfMsgExt called with incorrect parameter key $arrayKey", E_USER_WARNING );
+ } elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
+ array( 'parse', 'parseinline', 'escape', 'escapenoentities',
+ 'replaceafter', 'parsemag', 'content' ) ) ) {
+ # A numeric index with unknown value
+ trigger_error( "wfMsgExt called with incorrect parameter $option", E_USER_WARNING );
+ }
}
if( in_array('content', $options) ) {
$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';
- }
+ $langCode = wfGetLangObj( $options['language'] );
} else {
$forContent = false;
$langCode = false;
if ( in_array('escape', $options) ) {
$string = htmlspecialchars ( $string );
} elseif ( in_array( 'escapenoentities', $options ) ) {
- $string = htmlspecialchars( $string );
- $string = str_replace( '&', '&', $string );
- $string = Sanitizer::normalizeCharReferences( $string );
+ $string = Sanitizer::escapeHtmlAllowEntities( $string );
}
if( in_array('replaceafter', $options) ) {
* @return string
*/
function wfHostname() {
- if ( function_exists( 'posix_uname' ) ) {
- // This function not present on Windows
- $uname = @posix_uname();
- } else {
- $uname = false;
- }
- if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
- return $uname['nodename'];
- } else {
- # This may be a virtual server.
- return $_SERVER['SERVER_NAME'];
+ static $host;
+ if ( is_null( $host ) ) {
+ if ( function_exists( 'posix_uname' ) ) {
+ // This function not present on Windows
+ $uname = @posix_uname();
+ } else {
+ $uname = false;
+ }
+ if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
+ $host = $uname['nodename'];
+ } elseif ( getenv( 'COMPUTERNAME' ) ) {
+ # Windows computer name
+ $host = getenv( 'COMPUTERNAME' );
+ } else {
+ # This may be a virtual server.
+ $host = $_SERVER['SERVER_NAME'];
+ }
}
+ return $host;
}
/**
return $cgi;
}
+/**
+ * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
+ * its argument and returns the same string in array form. This allows compa-
+ * tibility with legacy functions that accept raw query strings instead of nice
+ * arrays. Of course, keys and values are urldecode()d. Don't try passing in-
+ * valid query strings, or it will explode.
+ *
+ * @param $query string Query string
+ * @return array Array version of input
+ */
+function wfCgiToArray( $query ) {
+ if( isset( $query[0] ) and $query[0] == '?' ) {
+ $query = substr( $query, 1 );
+ }
+ $bits = explode( '&', $query );
+ $ret = array();
+ foreach( $bits as $bit ) {
+ if( $bit === '' ) {
+ continue;
+ }
+ list( $key, $value ) = explode( '=', $bit );
+ $key = urldecode( $key );
+ $value = urldecode( $value );
+ $ret[$key] = $value;
+ }
+ return $ret;
+}
+
/**
* Append a query string to an existing URL, which may or may not already
* have query string parameters already. If so, they will be combined.
/**
* Windows-compatible version of escapeshellarg()
* Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
- * function puts single quotes in regardless of OS
+ * function puts single quotes in regardless of OS.
+ *
+ * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
+ * earlier distro releases of PHP)
*/
function wfEscapeShellArg( ) {
+ wfInitShellLocale();
+
$args = func_get_args();
$first = true;
$retVal = '';
# This check may also protect against code injection in
# case of broken installations.
- if(! file_exists( $wgDiff3 ) ){
+ if( !$wgDiff3 || !file_exists( $wgDiff3 ) ) {
wfDebug( "diff3 not found\n" );
return false;
}
// Reset the 'Content-Encoding' field set by this handler
// so we can start fresh.
header( 'Content-Encoding:' );
+ break;
}
}
}
}
function wfGetCachedNotice( $name ) {
- global $wgOut, $parserMemc;
+ global $wgOut, $wgRenderHashAppend, $parserMemc;
$fname = 'wfGetCachedNotice';
wfProfileIn( $fname );
}
}
- $cachedNotice = $parserMemc->get( wfMemcKey( $name ) );
+ // Use the extra hash appender to let eg SSL variants separately cache.
+ $key = wfMemcKey( $name . $wgRenderHashAppend );
+ $cachedNotice = $parserMemc->get( $key );
if( is_array( $cachedNotice ) ) {
if( md5( $notice ) == $cachedNotice['hash'] ) {
$notice = $cachedNotice['html'];
if( $needParse ) {
if( is_object( $wgOut ) ) {
$parsed = $wgOut->parse( $notice );
- $parserMemc->set( wfMemcKey( $name ), array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
+ $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
$notice = $parsed;
} else {
wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' );
return $namespaceNotice;
}
+/**
+ * Gets and returns the site-wide notice
+ * @return $siteNotice The site-wide notice as set by $wgSiteNotice or MediaWiki:Sitenotice interface message
+ */
function wfGetSiteNotice() {
- global $wgUser, $wgSiteNotice;
+ global $wgUser, $wgMajorSiteNoticeID, $wgTitle;
+ global $wgCookiePrefix, $wgCookieExpiration, $wgCookiePath;
$fname = 'wfGetSiteNotice';
wfProfileIn( $fname );
$siteNotice = '';
+ $loggedIn = false;
+ $spTitle = SpecialPage::getTitleFor( 'DismissNotice' );
+ $spUrl = $spTitle->escapeFullURL( array( 'returnto' => $wgTitle->getPrefixedURL() ) );
+
+ if( $wgUser instanceOf User && $wgUser->isLoggedIn() ) {
+ $loggedIn = true;
+ $siteNotice = wfGetCachedNotice('sitenotice');
+ if($siteNotice === false)
+ return '';
+ } else {
+ $siteNotice = wfGetCachedNotice('anonnotice');
+ if($siteNotice === false) {
+ $siteNotice = wfGetCachedNotice('sitenotice');
+ if($siteNotice === false)
+ return '';
+ }
+ }
+
+ $msgClose = wfMsg( 'sitenotice_close' );
+ $id = intval( $wgMajorSiteNoticeID ) . "." . intval( wfMsgForContent( 'sitenotice_id' ) );
if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
- if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
- $siteNotice = wfGetCachedNotice( 'sitenotice' );
- } else {
- $anonNotice = wfGetCachedNotice( 'anonnotice' );
- if( !$anonNotice ) {
- $siteNotice = wfGetCachedNotice( 'sitenotice' );
- } else {
- $siteNotice = $anonNotice;
- }
+ if( $loggedIn ) {
+ //it is hidden
+ if( isset($_COOKIE[$wgCookiePrefix . 'DismissSiteNotice']) && $_COOKIE[$wgCookiePrefix . 'DismissSiteNotice'] == $id )
+ return '';
+ $siteNotice = <<<EOT
+<table width="100%" id="mw-dismissable-notice"><tr><td width="80%">$siteNotice</td>
+<td width="20%" align="right">[<a id="dismissLink" href="$spUrl">$msgClose</a>]</td></tr></table>
+<script type="text/javascript" language="JavaScript">
+<!--
+var cookieName = "{$wgCookiePrefix}DismissSiteNotice=";
+var cookiePos = document.cookie.indexOf(cookieName);
+var siteNoticeID = "{$id}";
+
+var dismissLink = document.getElementById('dismissLink');
+dismissLink.href = "javascript:dismissNotice();";
+
+function dismissNotice() {
+ var date = new Date();
+ date.setTime(date.getTime() + {$wgCookieExpiration});
+ document.cookie = cookieName + siteNoticeID + "; expires="+date.toGMTString() + "; path={$wgCookiePath}";
+ var element = document.getElementById('siteNotice');
+ element.parentNode.removeChild(element);
+}
+-->
+</script>
+EOT;
}
if( !$siteNotice ) {
$siteNotice = wfGetCachedNotice( 'default' );
$retval = 1;
return "Unable to run external programs in safe mode.";
}
+ wfInitShellLocale();
if ( php_uname( 's' ) == 'Linux' ) {
$time = intval( ini_get( 'max_execution_time' ) );
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 );
+ }
}
/**
* 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 ) {
+function &wfGetDB( $db, $groups = array(), $wiki = false ) {
return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
}
* Get an object referring to a locally registered file.
* Returns a valid placeholder object if the file does not exist.
*/
-function wfLocalFile( $title ) {
- return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
+function wfLocalFile( $title, $time = false ) {
+ return RepoGroup::singleton()->getLocalRepo()->findFile( $title, $time );
}
/**
* @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).
+ * @since r24808 (v1.11) Using this method of loading extension messages will not work
+ * on MediaWiki prior to that
*/
function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
global $wgExtensionMessagesFiles, $wgMessageCache, $wgLang, $wgContLang;