diff options
Diffstat (limited to 'includes/GlobalFunctions.php')
-rw-r--r-- | includes/GlobalFunctions.php | 723 |
1 files changed, 398 insertions, 325 deletions
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 27f7cacb..240cb97b 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -24,13 +24,17 @@ if ( !defined( 'MEDIAWIKI' ) ) { die( "This file is part of MediaWiki, it is not a valid entry point" ); } +use Liuggio\StatsdClient\StatsdClient; +use Liuggio\StatsdClient\Sender\SocketSender; +use MediaWiki\Logger\LoggerFactory; + // Hide compatibility functions from Doxygen /// @cond /** * Compatibility functions * - * We support PHP 5.3.2 and up. + * We support PHP 5.3.3 and up. * Re-implementations of newer functions or functions in non-standard * PHP extensions may be included here. */ @@ -161,6 +165,72 @@ if ( !function_exists( 'hash_equals' ) ) { /// @endcond /** + * Load an extension + * + * This queues an extension to be loaded through + * the ExtensionRegistry system. + * + * @param string $ext Name of the extension to load + * @param string|null $path Absolute path of where to find the extension.json file + */ +function wfLoadExtension( $ext, $path = null ) { + if ( !$path ) { + global $wgExtensionDirectory; + $path = "$wgExtensionDirectory/$ext/extension.json"; + } + ExtensionRegistry::getInstance()->queue( $path ); +} + +/** + * Load multiple extensions at once + * + * Same as wfLoadExtension, but more efficient if you + * are loading multiple extensions. + * + * If you want to specify custom paths, you should interact with + * ExtensionRegistry directly. + * + * @see wfLoadExtension + * @param string[] $exts Array of extension names to load + */ +function wfLoadExtensions( array $exts ) { + global $wgExtensionDirectory; + $registry = ExtensionRegistry::getInstance(); + foreach ( $exts as $ext ) { + $registry->queue( "$wgExtensionDirectory/$ext/extension.json" ); + } +} + +/** + * Load a skin + * + * @see wfLoadExtension + * @param string $skin Name of the extension to load + * @param string|null $path Absolute path of where to find the skin.json file + */ +function wfLoadSkin( $skin, $path = null ) { + if ( !$path ) { + global $wgStyleDirectory; + $path = "$wgStyleDirectory/$skin/skin.json"; + } + ExtensionRegistry::getInstance()->queue( $path ); +} + +/** + * Load multiple skins at once + * + * @see wfLoadExtensions + * @param string[] $skins Array of extension names to load + */ +function wfLoadSkins( array $skins ) { + global $wgStyleDirectory; + $registry = ExtensionRegistry::getInstance(); + foreach ( $skins as $skin ) { + $registry->queue( "$wgStyleDirectory/$skin/skin.json" ); + } +} + +/** * Like array_diff( $a, $b ) except that it works with two-dimensional arrays. * @param array $a * @param array $b @@ -950,44 +1020,40 @@ function wfMatchesDomainList( $url, $domains ) { * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output. * $wgDebugComments - if on, some debug items may appear in comments in the HTML output. * + * @since 1.25 support for additional context data + * * @param string $text - * @param string|bool $dest Destination of the message: - * - 'all': both to the log and HTML (debug toolbar or HTML comments) - * - 'log': only to the log and not in HTML - * For backward compatibility, it can also take a boolean: - * - true: same as 'all' - * - false: same as 'log' + * @param string|bool $dest Unused + * @param array $context Additional logging context data */ -function wfDebug( $text, $dest = 'all' ) { - global $wgDebugLogFile, $wgDebugRawPage, $wgDebugLogPrefix; +function wfDebug( $text, $dest = 'all', array $context = array() ) { + global $wgDebugRawPage, $wgDebugLogPrefix; + global $wgDebugTimestamps, $wgRequestTime; if ( !$wgDebugRawPage && wfIsDebugRawPage() ) { return; } - // Turn $dest into a string if it's a boolean (for b/c) - if ( $dest === true ) { - $dest = 'all'; - } elseif ( $dest === false ) { - $dest = 'log'; - } + $text = trim( $text ); - $timer = wfDebugTimer(); - if ( $timer !== '' ) { - $text = preg_replace( '/[^\n]/', $timer . '\0', $text, 1 ); + // Inline logic from deprecated wfDebugTimer() + if ( $wgDebugTimestamps ) { + $context['seconds_elapsed'] = sprintf( + '%6.4f', + microtime( true ) - $wgRequestTime + ); + $context['memory_used'] = sprintf( + '%5.1fM', + ( memory_get_usage( true ) / ( 1024 * 1024 ) ) + ); } - if ( $dest === 'all' ) { - MWDebug::debugMsg( $text ); + if ( $wgDebugLogPrefix !== '' ) { + $context['prefix'] = $wgDebugLogPrefix; } - if ( $wgDebugLogFile != '' ) { - # Strip unprintables; they can switch terminal modes when binary data - # gets dumped, which is pretty annoying. - $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text ); - $text = $wgDebugLogPrefix . $text; - wfErrorLog( $text, $wgDebugLogFile ); - } + $logger = LoggerFactory::getInstance( 'wfDebug' ); + $logger->debug( $text, $context ); } /** @@ -1016,11 +1082,14 @@ function wfIsDebugRawPage() { /** * Get microsecond timestamps for debug logs * + * @deprecated since 1.25 * @return string */ function wfDebugTimer() { global $wgDebugTimestamps, $wgRequestTime; + wfDeprecated( __METHOD__, '1.25' ); + if ( !$wgDebugTimestamps ) { return ''; } @@ -1046,30 +1115,34 @@ function wfDebugMem( $exact = false ) { } /** - * Send a line to a supplementary debug log file, if configured, or main debug log if not. - * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to a string - * filename or an associative array mapping 'destination' to the desired filename. The - * associative array may also contain a 'sample' key with an integer value, specifying - * a sampling factor. + * Send a line to a supplementary debug log file, if configured, or main debug + * log if not. + * + * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to + * a string filename or an associative array mapping 'destination' to the + * desired filename. The associative array may also contain a 'sample' key + * with an integer value, specifying a sampling factor. Sampled log events + * will be emitted with a 1 in N random chance. * * @since 1.23 support for sampling log messages via $wgDebugLogGroups. + * @since 1.25 support for additional context data + * @since 1.25 sample behavior dependent on configured $wgMWLoggerDefaultSpi * * @param string $logGroup * @param string $text * @param string|bool $dest Destination of the message: * - 'all': both to the log and HTML (debug toolbar or HTML comments) * - 'log': only to the log and not in HTML - * - 'private': only to the specifc log if set in $wgDebugLogGroups and + * - 'private': only to the specific log if set in $wgDebugLogGroups and * discarded otherwise * For backward compatibility, it can also take a boolean: * - true: same as 'all' * - false: same as 'private' + * @param array $context Additional logging context data */ -function wfDebugLog( $logGroup, $text, $dest = 'all' ) { - global $wgDebugLogGroups; - - $text = trim( $text ) . "\n"; - +function wfDebugLog( + $logGroup, $text, $dest = 'all', array $context = array() +) { // Turn $dest into a string if it's a boolean (for b/c) if ( $dest === true ) { $dest = 'all'; @@ -1077,66 +1150,24 @@ function wfDebugLog( $logGroup, $text, $dest = 'all' ) { $dest = 'private'; } - if ( !isset( $wgDebugLogGroups[$logGroup] ) ) { - if ( $dest !== 'private' ) { - wfDebug( "[$logGroup] $text", $dest ); - } - return; - } - - if ( $dest === 'all' ) { - MWDebug::debugMsg( "[$logGroup] $text" ); - } - - $logConfig = $wgDebugLogGroups[$logGroup]; - if ( $logConfig === false ) { - return; - } - if ( is_array( $logConfig ) ) { - if ( isset( $logConfig['sample'] ) && mt_rand( 1, $logConfig['sample'] ) !== 1 ) { - return; - } - $destination = $logConfig['destination']; - } else { - $destination = strval( $logConfig ); - } + $text = trim( $text ); - $time = wfTimestamp( TS_DB ); - $wiki = wfWikiID(); - $host = wfHostname(); - wfErrorLog( "$time $host $wiki: $text", $destination ); + $logger = LoggerFactory::getInstance( $logGroup ); + $context['private'] = ( $dest === 'private' ); + $logger->info( $text, $context ); } /** * Log for database errors * + * @since 1.25 support for additional context data + * * @param string $text Database error message. + * @param array $context Additional logging context data */ -function wfLogDBError( $text ) { - global $wgDBerrorLog, $wgDBerrorLogTZ; - static $logDBErrorTimeZoneObject = null; - - if ( $wgDBerrorLog ) { - $host = wfHostname(); - $wiki = wfWikiID(); - - if ( $wgDBerrorLogTZ && !$logDBErrorTimeZoneObject ) { - $logDBErrorTimeZoneObject = new DateTimeZone( $wgDBerrorLogTZ ); - } - - // Workaround for https://bugs.php.net/bug.php?id=52063 - // Can be removed when min PHP > 5.3.2 - if ( $logDBErrorTimeZoneObject === null ) { - $d = date_create( "now" ); - } else { - $d = date_create( "now", $logDBErrorTimeZoneObject ); - } - - $date = $d->format( 'D M j G:i:s T Y' ); - - $text = "$date\t$host\t$wiki\t" . trim( $text ) . "\n"; - wfErrorLog( $text, $wgDBerrorLog ); - } +function wfLogDBError( $text, array $context = array() ) { + $logger = LoggerFactory::getInstance( 'wfLogDBError' ); + $logger->error( trim( $text ), $context ); } /** @@ -1188,138 +1219,93 @@ function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) { * * 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. + * @since 1.25 support for additional context data * * @param string $text * @param string $file Filename + * @param array $context Additional logging context data * @throws MWException + * @deprecated since 1.25 Use MediaWiki\Logger\LegacyLogger::emit or UDPTransport */ -function wfErrorLog( $text, $file ) { - if ( substr( $file, 0, 4 ) == 'udp:' ) { - # Needs the sockets extension - if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) { - // IPv6 bracketed host - $host = $m[2]; - $port = intval( $m[3] ); - $prefix = isset( $m[4] ) ? $m[4] : false; - $domain = AF_INET6; - } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) { - $host = $m[2]; - if ( !IP::isIPv4( $host ) ) { - $host = gethostbyname( $host ); - } - $port = intval( $m[3] ); - $prefix = isset( $m[4] ) ? $m[4] : false; - $domain = AF_INET; - } else { - throw new MWException( __METHOD__ . ': Invalid UDP specification' ); - } - - // Clean it up for the multiplexer - if ( strval( $prefix ) !== '' ) { - $text = preg_replace( '/^/m', $prefix . ' ', $text ); - - // Limit to 64KB - if ( strlen( $text ) > 65506 ) { - $text = substr( $text, 0, 65506 ); - } - - if ( substr( $text, -1 ) != "\n" ) { - $text .= "\n"; - } - } elseif ( strlen( $text ) > 65507 ) { - $text = substr( $text, 0, 65507 ); - } - - $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP ); - if ( !$sock ) { - return; - } - - socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port ); - socket_close( $sock ); - } else { - wfSuppressWarnings(); - $exists = file_exists( $file ); - $size = $exists ? filesize( $file ) : false; - if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) { - file_put_contents( $file, $text, FILE_APPEND ); - } - wfRestoreWarnings(); - } +function wfErrorLog( $text, $file, array $context = array() ) { + wfDeprecated( __METHOD__, '1.25' ); + $logger = LoggerFactory::getInstance( 'wfErrorLog' ); + $context['destination'] = $file; + $logger->info( trim( $text ), $context ); } /** * @todo document */ function wfLogProfilingData() { - global $wgRequestTime, $wgDebugLogFile, $wgDebugLogGroups, $wgDebugRawPage; - global $wgProfileLimit, $wgUser, $wgRequest; + global $wgDebugLogGroups, $wgDebugRawPage; - StatCounter::singleton()->flush(); + $context = RequestContext::getMain(); + $request = $context->getRequest(); $profiler = Profiler::instance(); + $profiler->setContext( $context ); + $profiler->logData(); - # Profiling must actually be enabled... - if ( $profiler->isStub() ) { - return; + $config = $context->getConfig(); + if ( $config->has( 'StatsdServer' ) ) { + $statsdServer = explode( ':', $config->get( 'StatsdServer' ) ); + $statsdHost = $statsdServer[0]; + $statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125; + $statsdSender = new SocketSender( $statsdHost, $statsdPort ); + $statsdClient = new StatsdClient( $statsdSender ); + $statsdClient->send( $context->getStats()->getBuffer() ); } - // Get total page request time and only show pages that longer than - // $wgProfileLimit time (default is 0) - $elapsed = microtime( true ) - $wgRequestTime; - if ( $elapsed <= $wgProfileLimit ) { + # Profiling must actually be enabled... + if ( $profiler instanceof ProfilerStub ) { return; } - $profiler->logData(); - - // Check whether this should be logged in the debug file. if ( isset( $wgDebugLogGroups['profileoutput'] ) && $wgDebugLogGroups['profileoutput'] === false ) { - // Explicitely disabled - return; - } - if ( !isset( $wgDebugLogGroups['profileoutput'] ) && $wgDebugLogFile == '' ) { - // Logging not enabled; no point going further + // Explicitly disabled return; } if ( !$wgDebugRawPage && wfIsDebugRawPage() ) { return; } - $forward = ''; + $ctx = array( 'elapsed' => $request->getElapsedTime() ); if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { - $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR']; + $ctx['forwarded_for'] = $_SERVER['HTTP_X_FORWARDED_FOR']; } if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) { - $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP']; + $ctx['client_ip'] = $_SERVER['HTTP_CLIENT_IP']; } if ( !empty( $_SERVER['HTTP_FROM'] ) ) { - $forward .= ' from ' . $_SERVER['HTTP_FROM']; + $ctx['from'] = $_SERVER['HTTP_FROM']; } - if ( $forward ) { - $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})"; + if ( isset( $ctx['forwarded_for'] ) || + isset( $ctx['client_ip'] ) || + isset( $ctx['from'] ) ) { + $ctx['proxy'] = $_SERVER['REMOTE_ADDR']; } + // Don't load $wgUser at this late stage just for statistics purposes - // @todo FIXME: We can detect some anons even if it is not loaded. See User::getId() - if ( $wgUser->isItemLoaded( 'id' ) && $wgUser->isAnon() ) { - $forward .= ' anon'; - } + // @todo FIXME: We can detect some anons even if it is not loaded. + // See User::getId() + $user = $context->getUser(); + $ctx['anon'] = $user->isItemLoaded( 'id' ) && $user->isAnon(); // Command line script uses a FauxRequest object which does not have // any knowledge about an URL and throw an exception instead. try { - $requestUrl = $wgRequest->getRequestURL(); - } catch ( MWException $e ) { - $requestUrl = 'n/a'; + $ctx['url'] = urldecode( $request->getRequestURL() ); + } catch ( Exception $ignored ) { + // no-op } - $log = sprintf( "%s\t%04.3f\t%s\n", - gmdate( 'YmdHis' ), $elapsed, - urldecode( $requestUrl . $forward ) ); + $ctx['output'] = $profiler->getOutput(); - wfDebugLog( 'profileoutput', $log . $profiler->getOutput() ); + $log = LoggerFactory::getInstance( 'profileoutput' ); + $log->info( "Elapsed: {elapsed}; URL: <{url}>\n{output}", $ctx ); } /** @@ -1330,7 +1316,8 @@ function wfLogProfilingData() { * @return void */ function wfIncrStats( $key, $count = 1 ) { - StatCounter::singleton()->incr( $key, $count ); + $stats = RequestContext::getMain()->getStats(); + $stats->updateCount( $key, $count ); } /** @@ -1573,10 +1560,8 @@ function wfMsgForContentNoTrans( $key ) { function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) { wfDeprecated( __METHOD__, '1.21' ); - wfProfileIn( __METHOD__ ); $message = wfMsgGetKey( $key, $useDB, $forContent, $transform ); $message = wfMsgReplaceArgs( $message, $args ); - wfProfileOut( __METHOD__ ); return $message; } @@ -1595,7 +1580,7 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) { wfDeprecated( __METHOD__, '1.21' ); - wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) ); + Hooks::run( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) ); $cache = MessageCache::singleton(); $message = $cache->get( $key, $useDB, $langCode ); @@ -1710,15 +1695,15 @@ function wfMsgExt( $key, $options ) { array_shift( $args ); array_shift( $args ); $options = (array)$options; + $validOptions = array( 'parse', 'parseinline', 'escape', 'escapenoentities', 'replaceafter', + 'parsemag', 'content' ); foreach ( $options as $arrayKey => $option ) { if ( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) { - # An unknown index, neither numeric nor "language" + // An unknown index, neither numeric nor "language" wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, 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 + } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option, $validOptions ) ) { + // A numeric index with unknown value wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING ); } } @@ -1879,52 +1864,37 @@ function wfDebugBacktrace( $limit = 0 ) { /** * Get a debug backtrace as a string * + * @param bool|null $raw If true, the return value is plain text. If false, HTML. + * Defaults to $wgCommandLineMode if unset. * @return string + * @since 1.25 Supports $raw parameter. */ -function wfBacktrace() { +function wfBacktrace( $raw = null ) { global $wgCommandLineMode; - if ( $wgCommandLineMode ) { - $msg = ''; - } else { - $msg = "<ul>\n"; + if ( $raw === null ) { + $raw = $wgCommandLineMode; } - $backtrace = wfDebugBacktrace(); - foreach ( $backtrace as $call ) { - if ( isset( $call['file'] ) ) { - $f = explode( DIRECTORY_SEPARATOR, $call['file'] ); - $file = $f[count( $f ) - 1]; - } else { - $file = '-'; - } - if ( isset( $call['line'] ) ) { - $line = $call['line']; - } else { - $line = '-'; - } - if ( $wgCommandLineMode ) { - $msg .= "$file line $line calls "; - } else { - $msg .= '<li>' . $file . ' line ' . $line . ' calls '; - } - if ( !empty( $call['class'] ) ) { - $msg .= $call['class'] . $call['type']; - } - $msg .= $call['function'] . '()'; - if ( $wgCommandLineMode ) { - $msg .= "\n"; - } else { - $msg .= "</li>\n"; - } - } - if ( $wgCommandLineMode ) { - $msg .= "\n"; + if ( $raw ) { + $frameFormat = "%s line %s calls %s()\n"; + $traceFormat = "%s"; } else { - $msg .= "</ul>\n"; + $frameFormat = "<li>%s line %s calls %s()</li>\n"; + $traceFormat = "<ul>\n%s</ul>\n"; } - return $msg; + $frames = array_map( function ( $frame ) use ( $frameFormat ) { + $file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-'; + $line = isset( $frame['line'] ) ? $frame['line'] : '-'; + $call = $frame['function']; + if ( !empty( $frame['class'] ) ) { + $call = $frame['class'] . $frame['type'] . $call; + } + return sprintf( $frameFormat, $file, $line, $call ); + }, wfDebugBacktrace() ); + + return sprintf( $traceFormat, implode( '', $frames ) ); } /** @@ -2148,10 +2118,12 @@ function wfVarDump( $var ) { */ function wfHttpError( $code, $label, $desc ) { global $wgOut; - $wgOut->disable(); header( "HTTP/1.0 $code $label" ); header( "Status: $code $label" ); - $wgOut->sendCacheControl(); + if ( $wgOut ) { + $wgOut->disable(); + $wgOut->sendCacheControl(); + } header( 'Content-type: text/html; charset=utf-8' ); print "<!doctype html>" . @@ -2496,20 +2468,6 @@ function wfIsHHVM() { } /** - * Swap two variables - * - * @deprecated since 1.24 - * @param mixed $x - * @param mixed $y - */ -function swap( &$x, &$y ) { - wfDeprecated( __FUNCTION__, '1.24' ); - $z = $x; - $x = $y; - $y = $z; -} - -/** * Tries to get the system directory for temporary files. First * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP * environment variables are then checked in sequence, and if none are @@ -2659,13 +2617,19 @@ function wfIniGetBool( $setting ) { * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to * earlier distro releases of PHP) * - * @param string $args,... + * @param string ... strings to escape and glue together, or a single array of strings parameter * @return string */ function wfEscapeShellArg( /*...*/ ) { wfInitShellLocale(); $args = func_get_args(); + if ( count( $args ) === 1 && is_array( reset( $args ) ) ) { + // If only one argument has been passed, and that argument is an array, + // treat it as a list of arguments + $args = reset( $args ); + } + $first = true; $retVal = ''; foreach ( $args as $arg ) { @@ -2756,6 +2720,8 @@ function wfShellExecDisabled() { * @param array $options Array of options: * - duplicateStderr: Set this to true to duplicate stderr to stdout, * including errors from limit.sh + * - profileMethod: By default this function will profile based on the calling + * method. Set this to a string for an alternative method to profile from * * @return string Collected stdout as a string */ @@ -2774,6 +2740,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), } $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr']; + $profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller(); wfInitShellLocale(); @@ -2795,12 +2762,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), } } if ( is_array( $cmd ) ) { - // Command line may be given as an array, escape each value and glue them together with a space - $cmdVals = array(); - foreach ( $cmd as $val ) { - $cmdVals[] = wfEscapeShellArg( $val ); - } - $cmd = implode( ' ', $cmdVals ); + $cmd = wfEscapeShellArg( $cmd ); } $cmd = $envcmd . $cmd; @@ -2847,6 +2809,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $desc[3] = array( 'pipe', 'w' ); } $pipes = null; + $scoped = Profiler::instance()->scopedProfileIn( __FUNCTION__ . '-' . $profileMethod ); $proc = proc_open( $cmd, $desc, $pipes ); if ( !$proc ) { wfDebugLog( 'exec', "proc_open() failed: $cmd" ); @@ -2986,7 +2949,9 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), * function, as all the arguments to wfShellExec can become unwieldy. * * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded. - * @param string $cmd Command line, properly escaped for shell. + * @param string|string[] $cmd If string, a properly shell-escaped command line, + * or an array of unescaped arguments, in which case each value will be escaped + * Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'" * @param null|mixed &$retval Optional, will receive the program's exit code. * (non-zero is usually failure) * @param array $environ Optional environment variables which should be @@ -2996,7 +2961,8 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), * @return string Collected stdout and stderr as a string */ function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) { - return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) ); + return wfShellExec( $cmd, $retval, $environ, $limits, + array( 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ) ); } /** @@ -3017,15 +2983,6 @@ function wfInitShellLocale() { } /** - * Alias to wfShellWikiCmd() - * - * @see wfShellWikiCmd() - */ -function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) { - return wfShellWikiCmd( $script, $parameters, $options ); -} - -/** * Generate a shell-escaped command line string to run a MediaWiki cli script. * Note that $parameters should be a flat array and an option with an argument * should consist of two consecutive items in the array (do not use "--option value"). @@ -3041,14 +2998,14 @@ function wfShellWikiCmd( $script, array $parameters = array(), array $options = global $wgPhpCli; // Give site config file a chance to run the script in a wrapper. // The caller may likely want to call wfBasename() on $script. - wfRunHooks( 'wfShellWikiCmd', array( &$script, &$parameters, &$options ) ); + Hooks::run( 'wfShellWikiCmd', array( &$script, &$parameters, &$options ) ); $cmd = isset( $options['php'] ) ? array( $options['php'] ) : array( $wgPhpCli ); if ( isset( $options['wrapper'] ) ) { $cmd[] = $options['wrapper']; } $cmd[] = $script; // Escape each parameter for shell - return implode( " ", array_map( 'wfEscapeShellArg', array_merge( $cmd, $parameters ) ) ); + return wfEscapeShellArg( array_merge( $cmd, $parameters ) ); } /** @@ -3093,10 +3050,8 @@ function wfMerge( $old, $mine, $yours, &$result ) { fclose( $yourtextFile ); # Check for a conflict - $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a --overlap-only ' . - wfEscapeShellArg( $mytextName ) . ' ' . - wfEscapeShellArg( $oldtextName ) . ' ' . - wfEscapeShellArg( $yourtextName ); + $cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName, + $oldtextName, $yourtextName ); $handle = popen( $cmd, 'r' ); if ( fgets( $handle, 1024 ) ) { @@ -3107,8 +3062,8 @@ function wfMerge( $old, $mine, $yours, &$result ) { pclose( $handle ); # Merge differences - $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a -e --merge ' . - wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName ); + $cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName, + $oldtextName, $yourtextName ); $handle = popen( $cmd, 'r' ); $result = ''; do { @@ -3132,7 +3087,9 @@ function wfMerge( $old, $mine, $yours, &$result ) { /** * Returns unified plain-text diff of two texts. - * Useful for machine processing of diffs. + * "Useful" for machine processing of diffs. + * + * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly * * @param string $before The text before the changes. * @param string $after The text after the changes. @@ -3172,6 +3129,11 @@ function wfDiff( $before, $after, $params = '-u' ) { $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName ); $h = popen( $cmd, 'r' ); + if ( !$h ) { + unlink( $oldtextName ); + unlink( $newtextName ); + throw new Exception( __METHOD__ . '(): popen() failed' ); + } $diff = ''; @@ -3493,7 +3455,7 @@ function wfResetSessionID() { $_SESSION = $tmp; } $newSessionId = session_id(); - wfRunHooks( 'ResetSessionID', array( $oldSessionId, $newSessionId ) ); + Hooks::run( 'ResetSessionID', array( $oldSessionId, $newSessionId ) ); } /** @@ -3628,7 +3590,7 @@ function wfSplitWikiID( $wiki ) { * * @return DatabaseBase */ -function &wfGetDB( $db, $groups = array(), $wiki = false ) { +function wfGetDB( $db, $groups = array(), $wiki = false ) { return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki ); } @@ -3647,7 +3609,7 @@ function wfGetLB( $wiki = false ) { * * @return LBFactory */ -function &wfGetLBFactory() { +function wfGetLBFactory() { return LBFactory::singleton(); } @@ -3656,19 +3618,7 @@ function &wfGetLBFactory() { * Shortcut for RepoGroup::singleton()->findFile() * * @param string $title String or Title object - * @param array $options Associative array of options: - * 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. - * - * ignoreRedirect: If true, do not follow file redirects - * - * private: If true, return restricted (deleted) files if the current - * user is allowed to view them. Otherwise, such files will not - * be found. - * - * bypassCache: If true, do not use the process-local cache of File objects - * + * @param array $options Associative array of options (see RepoGroup::findFile) * @return File|bool File, or false if the file does not exist */ function wfFindFile( $title, $options = array() ) { @@ -3763,46 +3713,79 @@ function wfGetNull() { } /** - * Modern version of wfWaitForSlaves(). Instead of looking at replication lag - * and waiting for it to go down, this waits for the slaves to catch up to the - * master position. 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. + * Waits for the slaves to catch up to the master position + * + * 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. + * + * By default this waits on the main DB cluster of the current wiki. + * If $cluster is set to "*" it will wait on all DB clusters, including + * external ones. If the lag being waiting on is caused by the code that + * does this check, it makes since to use $ifWritesSince, particularly if + * cluster is "*", to avoid excess overhead. + * + * Never call this function after a big DB write that is still in a transaction. + * This only makes sense after the possible lag inducing changes were committed. * * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp * @param string|bool $wiki Wiki identifier accepted by wfGetLB * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false. + * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web) * @return bool Success (able to connect and no timeouts reached) */ -function wfWaitForSlaves( $ifWritesSince = false, $wiki = false, $cluster = false ) { +function wfWaitForSlaves( + $ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null +) { // B/C: first argument used to be "max seconds of lag"; ignore such values - $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : false; + $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null; - if ( $cluster !== false ) { - $lb = wfGetLBFactory()->getExternalLB( $cluster ); - } else { - $lb = wfGetLB( $wiki ); + if ( $timeout === null ) { + $timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10; } - // bug 27975 - Don't try to wait for slaves if there are none - // Prevents permission error when getting master position - if ( $lb->getServerCount() > 1 ) { - if ( $ifWritesSince && !$lb->hasMasterConnection() ) { - return true; // assume no writes done - } - $dbw = $lb->getConnection( DB_MASTER, array(), $wiki ); - if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) { - return true; // no writes since the last wait + // Figure out which clusters need to be checked + $lbs = array(); + if ( $cluster === '*' ) { + wfGetLBFactory()->forEachLB( function ( LoadBalancer $lb ) use ( &$lbs ) { + $lbs[] = $lb; + } ); + } elseif ( $cluster !== false ) { + $lbs[] = wfGetLBFactory()->getExternalLB( $cluster ); + } else { + $lbs[] = wfGetLB( $wiki ); + } + + // Get all the master positions of applicable DBs right now. + // This can be faster since waiting on one cluster reduces the + // time needed to wait on the next clusters. + $masterPositions = array_fill( 0, count( $lbs ), false ); + foreach ( $lbs as $i => $lb ) { + // bug 27975 - Don't try to wait for slaves if there are none + // Prevents permission error when getting master position + if ( $lb->getServerCount() > 1 ) { + if ( $ifWritesSince && !$lb->hasMasterConnection() ) { + continue; // assume no writes done + } + // Use the empty string to not trigger selectDB() since the connection + // may have been to a server that does not have a DB for the current wiki. + $dbw = $lb->getConnection( DB_MASTER, array(), '' ); + if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) { + continue; // no writes since the last wait + } + $masterPositions[$i] = $dbw->getMasterPos(); } - $pos = $dbw->getMasterPos(); - // The DBMS may not support getMasterPos() or the whole - // load balancer might be fake (e.g. $wgAllDBsAreLocalhost). - if ( $pos !== false ) { - return $lb->waitForAll( $pos, PHP_SAPI === 'cli' ? 86400 : null ); + } + + $ok = true; + foreach ( $lbs as $i => $lb ) { + if ( $masterPositions[$i] ) { + // The DBMS may not support getMasterPos() or the whole + // load balancer might be fake (e.g. $wgAllDBsAreLocalhost). + $ok = $lb->waitForAll( $masterPositions[$i], $timeout ) && $ok; } } - return true; + return $ok; } /** @@ -3935,7 +3918,7 @@ function wfBCP47( $code ) { /** * Get a cache object. * - * @param int $inputType Cache type, one the the CACHE_* constants. + * @param int $inputType Cache type, one of the CACHE_* constants. * @return BagOStuff */ function wfGetCache( $inputType ) { @@ -3973,16 +3956,6 @@ function wfGetParserCacheStorage() { } /** - * Get the cache object used by the language converter - * - * @return BagOStuff - */ -function wfGetLangConverterCacheStorage() { - global $wgLanguageConverterCacheType; - return ObjectCache::getInstance( $wgLanguageConverterCacheType ); -} - -/** * Call hook functions defined in $wgHooks * * @param string $event Event name @@ -3990,6 +3963,7 @@ function wfGetLangConverterCacheStorage() { * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number * * @return bool True if no handler aborted the hook + * @deprecated 1.25 - use Hooks::run */ function wfRunHooks( $event, array $args = array(), $deprecatedVersion = null ) { return Hooks::run( $event, $args, $deprecatedVersion ); @@ -4047,7 +4021,6 @@ function wfUnpack( $format, $data, $length = false ) { */ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { static $badImageCache = null; // based on bad_image_list msg - wfProfileIn( __METHOD__ ); # Handle redirects $redirectTitle = RepoGroup::singleton()->checkRedirect( Title::makeTitle( NS_FILE, $name ) ); @@ -4057,8 +4030,7 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { # Run the extension hook $bad = false; - if ( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) { - wfProfileOut( __METHOD__ ); + if ( !Hooks::run( 'BadImage', array( $name, &$bad ) ) ) { return $bad; } @@ -4108,7 +4080,6 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { $contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false; $bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] ); - wfProfileOut( __METHOD__ ); return $bad; } @@ -4121,11 +4092,23 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { */ function wfCanIPUseHTTPS( $ip ) { $canDo = true; - wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) ); + Hooks::run( 'CanIPUseHTTPS', array( $ip, &$canDo ) ); return !!$canDo; } /** + * Determine input string is represents as infinity + * + * @param string $str The string to determine + * @return bool + * @since 1.25 + */ +function wfIsInfinity( $str ) { + $infinityValues = array( 'infinite', 'indefinite', 'infinity', 'never' ); + return in_array( $str, $infinityValues ); +} + +/** * Work out the IP address based on various globals * For trusted proxies, use the XFF client IP (first of the chain) * @@ -4148,6 +4131,7 @@ function wfGetIP() { * @return bool */ function wfIsTrustedProxy( $ip ) { + wfDeprecated( __METHOD__, '1.24' ); return IP::isTrustedProxy( $ip ); } @@ -4160,5 +4144,94 @@ function wfIsTrustedProxy( $ip ) { * @since 1.23 Supports CIDR ranges in $wgSquidServersNoPurge */ function wfIsConfiguredProxy( $ip ) { + wfDeprecated( __METHOD__, '1.24' ); return IP::isConfiguredProxy( $ip ); } + +/** + * Returns true if these thumbnail parameters match one that MediaWiki + * requests from file description pages and/or parser output. + * + * $params is considered non-standard if they involve a non-standard + * width or any non-default parameters aside from width and page number. + * The number of possible files with standard parameters is far less than + * that of all combinations; rate-limiting for them can thus be more generious. + * + * @param File $file + * @param array $params + * @return bool + * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25 + */ +function wfThumbIsStandard( File $file, array $params ) { + global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages; + + $multipliers = array( 1 ); + if ( $wgResponsiveImages ) { + // These available sizes are hardcoded currently elsewhere in MediaWiki. + // @see Linker::processResponsiveImages + $multipliers[] = 1.5; + $multipliers[] = 2; + } + + $handler = $file->getHandler(); + if ( !$handler || !isset( $params['width'] ) ) { + return false; + } + + $basicParams = array(); + if ( isset( $params['page'] ) ) { + $basicParams['page'] = $params['page']; + } + + $thumbLimits = array(); + $imageLimits = array(); + // Expand limits to account for multipliers + foreach ( $multipliers as $multiplier ) { + $thumbLimits = array_merge( $thumbLimits, array_map( + function ( $width ) use ( $multiplier ) { + return round( $width * $multiplier ); + }, $wgThumbLimits ) + ); + $imageLimits = array_merge( $imageLimits, array_map( + function ( $pair ) use ( $multiplier ) { + return array( + round( $pair[0] * $multiplier ), + round( $pair[1] * $multiplier ), + ); + }, $wgImageLimits ) + ); + } + + // Check if the width matches one of $wgThumbLimits + if ( in_array( $params['width'], $thumbLimits ) ) { + $normalParams = $basicParams + array( 'width' => $params['width'] ); + // Append any default values to the map (e.g. "lossy", "lossless", ...) + $handler->normaliseParams( $file, $normalParams ); + } else { + // If not, then check if the width matchs one of $wgImageLimits + $match = false; + foreach ( $imageLimits as $pair ) { + $normalParams = $basicParams + array( 'width' => $pair[0], 'height' => $pair[1] ); + // Decide whether the thumbnail should be scaled on width or height. + // Also append any default values to the map (e.g. "lossy", "lossless", ...) + $handler->normaliseParams( $file, $normalParams ); + // Check if this standard thumbnail size maps to the given width + if ( $normalParams['width'] == $params['width'] ) { + $match = true; + break; + } + } + if ( !$match ) { + return false; // not standard for description pages + } + } + + // Check that the given values for non-page, non-width, params are just defaults + foreach ( $params as $key => $value ) { + if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) { + return false; + } + } + + return true; +} |