X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FGlobalFunctions.php;h=5a9202e34f8e6049ce72f7f574bee0f39625533a;hb=390637ead5fbee8e30e85b9395d5b11dd951e1e3;hp=cbe28c26e111b513d412e6a787cfdd81c535e88d;hpb=d5eacdd950d9bac814f8354754614786525db64f;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index cbe28c26e1..5a9202e34f 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -296,7 +296,7 @@ function wfUrlencode( $s ) { static $needle; if ( is_null( $s ) ) { $needle = null; - return; + return ''; } if ( is_null( $needle ) ) { @@ -444,8 +444,11 @@ function wfAppendQuery( $url, $query ) { * like "subdir/foo.html", etc. * * @param $url String: either fully-qualified or a local path + query - * @param $defaultProto Mixed: one of the PROTO_* constants. Determines the protocol to use if $url or $wgServer is protocol-relative - * @return string Fully-qualified URL + * @param $defaultProto Mixed: one of the PROTO_* constants. Determines the + * protocol to use if $url or $wgServer is + * protocol-relative + * @return string Fully-qualified URL, current-path-relative URL or false if + * no valid URL can be constructed */ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) { global $wgServer, $wgCanonicalServer, $wgInternalServer; @@ -477,21 +480,130 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) { $defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 ); - if( substr( $url, 0, 2 ) == '//' ) { - return $defaultProtoWithoutSlashes . $url; - } elseif( substr( $url, 0, 1 ) == '/' ) { + if ( substr( $url, 0, 2 ) == '//' ) { + $url = $defaultProtoWithoutSlashes . $url; + } elseif ( substr( $url, 0, 1 ) == '/' ) { // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes, otherwise leave it alone - return ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url; - } else { + $url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url; + } + + $bits = wfParseUrl( $url ); + if ( $bits && isset( $bits['path'] ) ) { + $bits['path'] = wfRemoveDotSegments( $bits['path'] ); + return wfAssembleUrl( $bits ); + } elseif ( $bits ) { + # No path to expand return $url; + } elseif ( substr( $url, 0, 1 ) != '/' ) { + # URL is a relative path + return wfRemoveDotSegments( $url ); + } + + # Expanded URL is not valid. + return false; +} + +/** + * This function will reassemble a URL parsed with wfParseURL. This is useful + * if you need to edit part of a URL and put it back together. + * + * This is the basic structure used (brackets contain keys for $urlParts): + * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment] + * + * @todo Need to integrate this into wfExpandUrl (bug 32168) + * + * @param $urlParts Array URL parts, as output from wfParseUrl + * @return string URL assembled from its component parts + */ +function wfAssembleUrl( $urlParts ) { + $result = ''; + + if ( isset( $urlParts['delimiter'] ) ) { + if ( isset( $urlParts['scheme'] ) ) { + $result .= $urlParts['scheme']; + } + + $result .= $urlParts['delimiter']; + } + + if ( isset( $urlParts['host'] ) ) { + if ( isset( $urlParts['user'] ) ) { + $result .= $urlParts['user']; + if ( isset( $urlParts['pass'] ) ) { + $result .= ':' . $urlParts['pass']; + } + $result .= '@'; + } + + $result .= $urlParts['host']; + + if ( isset( $urlParts['port'] ) ) { + $result .= ':' . $urlParts['port']; + } + } + + if ( isset( $urlParts['path'] ) ) { + $result .= $urlParts['path']; + } + + if ( isset( $urlParts['query'] ) ) { + $result .= '?' . $urlParts['query']; + } + + if ( isset( $urlParts['fragment'] ) ) { + $result .= '#' . $urlParts['fragment']; + } + + return $result; +} + +/** + * Remove all dot-segments in the provided URL path. For example, + * '/a/./b/../c/' becomes '/a/c/'. For details on the algorithm, please see + * RFC3986 section 5.2.4. + * + * @todo Need to integrate this into wfExpandUrl (bug 32168) + * + * @param $urlPath String URL path, potentially containing dot-segments + * @return string URL path with all dot-segments removed + */ +function wfRemoveDotSegments( $urlPath ) { + $output = ''; + + while ( $urlPath ) { + $matches = null; + if ( preg_match('%^\.\.?/%', $urlPath, $matches) ) { + # Step A, remove leading "../" or "./" + $urlPath = substr( $urlPath, strlen( $matches[0] ) ); + } elseif ( preg_match( '%^/\.(/|$)%', $urlPath, $matches ) ) { + # Step B, replace leading "/.$" or "/./" with "/" + $start = strlen( $matches[0] ); + $urlPath = '/' . substr( $urlPath, $start ); + } elseif ( preg_match( '%^/\.\.(/|$)%', $urlPath, $matches ) ) { + # Step C, replace leading "/..$" or "/../" with "/" and + # remove last path component in output + $start = strlen( $matches[0] ); + $urlPath = '/' . substr( $urlPath, $start ); + $output = preg_replace('%(^|/)[^/]*$%', '', $output); + } elseif ( preg_match( '%^\.\.?$%', $urlPath, $matches ) ) { + # Step D, remove "^..$" or "^.$" + $urlPath = ''; + } else { + # Step E, move leading path segment to output + preg_match( '%^/?[^/]*%', $urlPath, $matches ); + $urlPath = substr( $urlPath, strlen( $matches[0] ) ); + $output .= $matches[0]; + } } + + return $output; } /** * Returns a regular expression of url protocols * * @param $includeProtocolRelative bool If false, remove '//' from the returned protocol list. - * DO NOT USE this directy, use wfUrlProtocolsWithoutProtRel() instead + * DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead * @return String */ function wfUrlProtocols( $includeProtocolRelative = true ) { @@ -537,6 +649,7 @@ function wfUrlProtocols( $includeProtocolRelative = true ) { * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if * you need a regex that matches all URL protocols but does not match protocol- * relative URLs + * @return String */ function wfUrlProtocolsWithoutProtRel() { return wfUrlProtocols( false ); @@ -604,12 +717,12 @@ function wfParseUrl( $url ) { } /** - * Make a URL index, appropriate for the el_index field of externallinks. + * Make URL indexes, appropriate for the el_index field of externallinks. * * @param $url String - * @return String + * @return array */ -function wfMakeUrlIndex( $url ) { +function wfMakeUrlIndexes( $url ) { $bits = wfParseUrl( $url ); // Reverse the labels in the hostname, convert to lower case @@ -649,7 +762,12 @@ function wfMakeUrlIndex( $url ) { if ( isset( $bits['fragment'] ) ) { $index .= '#' . $bits['fragment']; } - return $index; + + if ( $prot == '' ) { + return array( "http:$index", "https:$index" ); + } else { + return array( $index ); + } } /** @@ -709,7 +827,7 @@ function wfDebug( $text, $logonly = false ) { if ( $wgDebugLogFile != '' && !$wgProfileOnly ) { # 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 = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1a\x1c-\x1f]!', ' ', $text ); $text = $wgDebugLogPrefix . $text; wfErrorLog( $text, $wgDebugLogFile ); } @@ -852,15 +970,15 @@ function wfErrorLog( $text, $file ) { $text = preg_replace( '/^/m', $prefix . ' ', $text ); // Limit to 64KB - if ( strlen( $text ) > 65534 ) { - $text = substr( $text, 0, 65534 ); + if ( strlen( $text ) > 65506 ) { + $text = substr( $text, 0, 65506 ); } if ( substr( $text, -1 ) != "\n" ) { $text .= "\n"; } - } elseif ( strlen( $text ) > 65535 ) { - $text = substr( $text, 0, 65535 ); + } elseif ( strlen( $text ) > 65507 ) { + $text = substr( $text, 0, 65507 ); } $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP ); @@ -868,13 +986,7 @@ function wfErrorLog( $text, $file ) { return; } - $len = strlen( $text ); - $maxLen = socket_get_option( $sock, SOL_SOCKET, SO_SNDBUF ); - - if ( $len > $maxLen ) { - $len = $maxLen - 1; - } - socket_sendto( $sock, $text, $len, 0, $host, $port ); + socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port ); socket_close( $sock ); } else { wfSuppressWarnings(); @@ -966,6 +1078,9 @@ function wfReadOnly() { return (bool)$wgReadOnly; } +/** + * @return bool + */ function wfReadOnlyReason() { global $wgReadOnly; wfReadOnly(); @@ -1359,7 +1474,7 @@ function wfEmptyMsg( $key ) { * Throw a debugging exception. This function previously once exited the process, * but now throws an exception instead, with similar results. * - * @param $msg String: message shown when dieing. + * @param $msg String: message shown when dying. */ function wfDebugDieBacktrace( $msg = '' ) { throw new MWException( $msg ); @@ -1581,80 +1696,38 @@ function wfShowingResults( $offset, $limit ) { * @param $query String: optional URL query parameter string * @param $atend Bool: optional param for specified if this is the last page * @return String + * @deprecated in 1.19; use Language::viewPrevNext() instead */ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) { global $wgLang; - $fmtLimit = $wgLang->formatNum( $limit ); - // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip? - # Get prev/next link display text - $prev = wfMsgExt( 'prevn', array( 'parsemag', 'escape' ), $fmtLimit ); - $next = wfMsgExt( 'nextn', array( 'parsemag', 'escape' ), $fmtLimit ); - # Get prev/next link title text - $pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit ); - $nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit ); - # Fetch the title object + + $query = wfCgiToArray( $query ); + if( is_object( $link ) ) { - $title =& $link; + $title = $link; } else { $title = Title::newFromText( $link ); if( is_null( $title ) ) { return false; } } - # Make 'previous' link - if( 0 != $offset ) { - $po = $offset - $limit; - $po = max( $po, 0 ); - $q = "limit={$limit}&offset={$po}"; - if( $query != '' ) { - $q .= '&' . $query; - } - $plink = '{$prev}"; - } else { - $plink = $prev; - } - # Make 'next' link - $no = $offset + $limit; - $q = "limit={$limit}&offset={$no}"; - if( $query != '' ) { - $q .= '&' . $query; - } - if( $atend ) { - $nlink = $next; - } else { - $nlink = '{$next}"; - } - # Make links to set number of items per page - $nums = $wgLang->pipeList( array( - wfNumLink( $offset, 20, $title, $query ), - wfNumLink( $offset, 50, $title, $query ), - wfNumLink( $offset, 100, $title, $query ), - wfNumLink( $offset, 250, $title, $query ), - wfNumLink( $offset, 500, $title, $query ) - ) ); - return wfMsgHtml( 'viewprevnext', $plink, $nlink, $nums ); + + return $wgLang->viewPrevNext( $title, $offset, $limit, $query, $atend ); } /** - * Generate links for (20|50|100...) items-per-page links + * Make a list item, used by various special pages * - * @param $offset String - * @param $limit Integer - * @param $title Title - * @param $query String: optional URL query parameter string + * @param $page String Page link + * @param $details String Text between brackets + * @param $oppositedm Boolean Add the direction mark opposite to your + * language, to display text properly + * @return String + * @deprecated since 1.19; use Language::specialList() instead */ -function wfNumLink( $offset, $limit, $title, $query = '' ) { +function wfSpecialList( $page, $details, $oppositedm = true ) { global $wgLang; - if( $query == '' ) { - $q = ''; - } else { - $q = $query.'&'; - } - $q .= "limit={$limit}&offset={$offset}"; - $fmtLimit = $wgLang->formatNum( $limit ); - $lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $limit ); - $s = '{$fmtLimit}"; - return $s; + return $wgLang->specialList( $page, $details, $oppositedm ); } /** @@ -1681,7 +1754,7 @@ function wfClientAcceptsGzip( $force = false ) { $result = false; return $result; } - wfDebug( " accepts gzip\n" ); + wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" ); $result = true; } } @@ -1764,220 +1837,9 @@ function wfSetBit( &$dest, $bit, $state = true ) { $dest |= $bit; } else { $dest &= ~$bit; - } - } - return $temp; -} - -/** - * 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. - * - * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to - * earlier distro releases of PHP) - * - * @param varargs - * @return String - */ -function wfEscapeShellArg( ) { - wfInitShellLocale(); - - $args = func_get_args(); - $first = true; - $retVal = ''; - foreach ( $args as $arg ) { - if ( !$first ) { - $retVal .= ' '; - } else { - $first = false; - } - - if ( wfIsWindows() ) { - // Escaping for an MSVC-style command line parser and CMD.EXE - // Refs: - // * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html - // * http://technet.microsoft.com/en-us/library/cc723564.aspx - // * Bug #13518 - // * CR r63214 - // Double the backslashes before any double quotes. Escape the double quotes. - $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE ); - $arg = ''; - $iteration = 0; - foreach ( $tokens as $token ) { - if ( $iteration % 2 == 1 ) { - // Delimiter, a double quote preceded by zero or more slashes - $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"'; - } elseif ( $iteration % 4 == 2 ) { - // ^ in $token will be outside quotes, need to be escaped - $arg .= str_replace( '^', '^^', $token ); - } else { // $iteration % 4 == 0 - // ^ in $token will appear inside double quotes, so leave as is - $arg .= $token; - } - $iteration++; - } - // Double the backslashes before the end of the string, because - // we will soon add a quote - $m = array(); - if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) { - $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] ); - } - - // Add surrounding quotes - $retVal .= '"' . $arg . '"'; - } else { - $retVal .= escapeshellarg( $arg ); - } - } - return $retVal; -} - -/** - * wfMerge attempts to merge differences between three texts. - * Returns true for a clean merge and false for failure or a conflict. - * - * @param $old String - * @param $mine String - * @param $yours String - * @param $result String - * @return Bool - */ -function wfMerge( $old, $mine, $yours, &$result ) { - global $wgDiff3; - - # This check may also protect against code injection in - # case of broken installations. - wfSuppressWarnings(); - $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); - wfRestoreWarnings(); - - if( !$haveDiff3 ) { - wfDebug( "diff3 not found\n" ); - return false; - } - - # Make temporary files - $td = wfTempDir(); - $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' ); - $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' ); - $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' ); - - fwrite( $oldtextFile, $old ); - fclose( $oldtextFile ); - fwrite( $mytextFile, $mine ); - fclose( $mytextFile ); - fwrite( $yourtextFile, $yours ); - fclose( $yourtextFile ); - - # Check for a conflict - $cmd = $wgDiff3 . ' -a --overlap-only ' . - wfEscapeShellArg( $mytextName ) . ' ' . - wfEscapeShellArg( $oldtextName ) . ' ' . - wfEscapeShellArg( $yourtextName ); - $handle = popen( $cmd, 'r' ); - - if( fgets( $handle, 1024 ) ) { - $conflict = true; - } else { - $conflict = false; - } - pclose( $handle ); - - # Merge differences - $cmd = $wgDiff3 . ' -a -e --merge ' . - wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName ); - $handle = popen( $cmd, 'r' ); - $result = ''; - do { - $data = fread( $handle, 8192 ); - if ( strlen( $data ) == 0 ) { - break; - } - $result .= $data; - } while ( true ); - pclose( $handle ); - unlink( $mytextName ); - unlink( $oldtextName ); - unlink( $yourtextName ); - - if ( $result === '' && $old !== '' && !$conflict ) { - wfDebug( "Unexpected null result from diff3. Command: $cmd\n" ); - $conflict = true; - } - return !$conflict; -} - -/** - * 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' ) { - if ( $before == $after ) { - return ''; - } - - global $wgDiff; - wfSuppressWarnings(); - $haveDiff = $wgDiff && file_exists( $wgDiff ); - wfRestoreWarnings(); - - # This check may also protect against code injection in - # case of broken installations. - if( !$haveDiff ) { - 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; + return $temp; } /** @@ -2515,7 +2377,7 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) { global $wgDirectoryMode; if ( !is_null( $caller ) ) { - wfDebug( "$caller: called wfMkdirParents($dir)" ); + wfDebug( "$caller: called wfMkdirParents($dir)\n" ); } if( strval( $dir ) === '' || file_exists( $dir ) ) { @@ -2617,23 +2479,6 @@ function in_string( $needle, $str, $insensitive = false ) { return $func( $str, $needle ) !== false; } -/** - * Make a list item, used by various special pages - * - * @param $page String Page link - * @param $details String Text between brackets - * @param $oppositedm Boolean Add the direction mark opposite to your - * language, to display text properly - * @return String - */ -function wfSpecialList( $page, $details, $oppositedm = true ) { - global $wgLang; - $dirmark = ( $oppositedm ? $wgLang->getDirMark( true ) : '' ) . - $wgLang->getDirMark(); - $details = $details ? $dirmark . " ($details)" : ''; - return $page . $details; -} - /** * Safety wrapper around ini_get() for boolean settings. * The values returned from ini_get() are pre-normalized for settings @@ -2701,6 +2546,70 @@ function wfDl( $extension, $fileName = null ) { return extension_loaded( $extension ); } +/** + * 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. + * + * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to + * earlier distro releases of PHP) + * + * @param varargs + * @return String + */ +function wfEscapeShellArg( ) { + wfInitShellLocale(); + + $args = func_get_args(); + $first = true; + $retVal = ''; + foreach ( $args as $arg ) { + if ( !$first ) { + $retVal .= ' '; + } else { + $first = false; + } + + if ( wfIsWindows() ) { + // Escaping for an MSVC-style command line parser and CMD.EXE + // Refs: + // * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html + // * http://technet.microsoft.com/en-us/library/cc723564.aspx + // * Bug #13518 + // * CR r63214 + // Double the backslashes before any double quotes. Escape the double quotes. + $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE ); + $arg = ''; + $iteration = 0; + foreach ( $tokens as $token ) { + if ( $iteration % 2 == 1 ) { + // Delimiter, a double quote preceded by zero or more slashes + $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"'; + } elseif ( $iteration % 4 == 2 ) { + // ^ in $token will be outside quotes, need to be escaped + $arg .= str_replace( '^', '^^', $token ); + } else { // $iteration % 4 == 0 + // ^ in $token will appear inside double quotes, so leave as is + $arg .= $token; + } + $iteration++; + } + // Double the backslashes before the end of the string, because + // we will soon add a quote + $m = array(); + if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) { + $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] ); + } + + // Add surrounding quotes + $retVal .= '"' . $arg . '"'; + } else { + $retVal .= escapeshellarg( $arg ); + } + } + return $retVal; +} + /** * Execute a shell command, with time and memory limits mirrored from the PHP * configuration if supported. @@ -2810,6 +2719,178 @@ function wfInitShellLocale() { } } +/** + * Generate a shell-escaped command line string to run a maintenance 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"). + * @param $script string MediaWiki maintenance script path + * @param $parameters Array Arguments and options to the script + * @param $options Array Associative array of options: + * 'php': The path to the php executable + * 'wrapper': Path to a PHP wrapper to handle the maintenance script + * @return Array + */ +function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) { + 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( 'wfShellMaintenanceCmd', 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 ) ) ); +} + +/** + * wfMerge attempts to merge differences between three texts. + * Returns true for a clean merge and false for failure or a conflict. + * + * @param $old String + * @param $mine String + * @param $yours String + * @param $result String + * @return Bool + */ +function wfMerge( $old, $mine, $yours, &$result ) { + global $wgDiff3; + + # This check may also protect against code injection in + # case of broken installations. + wfSuppressWarnings(); + $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); + wfRestoreWarnings(); + + if( !$haveDiff3 ) { + wfDebug( "diff3 not found\n" ); + return false; + } + + # Make temporary files + $td = wfTempDir(); + $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' ); + $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' ); + $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' ); + + fwrite( $oldtextFile, $old ); + fclose( $oldtextFile ); + fwrite( $mytextFile, $mine ); + fclose( $mytextFile ); + fwrite( $yourtextFile, $yours ); + fclose( $yourtextFile ); + + # Check for a conflict + $cmd = $wgDiff3 . ' -a --overlap-only ' . + wfEscapeShellArg( $mytextName ) . ' ' . + wfEscapeShellArg( $oldtextName ) . ' ' . + wfEscapeShellArg( $yourtextName ); + $handle = popen( $cmd, 'r' ); + + if( fgets( $handle, 1024 ) ) { + $conflict = true; + } else { + $conflict = false; + } + pclose( $handle ); + + # Merge differences + $cmd = $wgDiff3 . ' -a -e --merge ' . + wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName ); + $handle = popen( $cmd, 'r' ); + $result = ''; + do { + $data = fread( $handle, 8192 ); + if ( strlen( $data ) == 0 ) { + break; + } + $result .= $data; + } while ( true ); + pclose( $handle ); + unlink( $mytextName ); + unlink( $oldtextName ); + unlink( $yourtextName ); + + if ( $result === '' && $old !== '' && !$conflict ) { + wfDebug( "Unexpected null result from diff3. Command: $cmd\n" ); + $conflict = true; + } + return !$conflict; +} + +/** + * 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' ) { + if ( $before == $after ) { + return ''; + } + + global $wgDiff; + wfSuppressWarnings(); + $haveDiff = $wgDiff && file_exists( $wgDiff ); + wfRestoreWarnings(); + + # This check may also protect against code injection in + # case of broken installations. + if( !$haveDiff ) { + 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; +} + /** * 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 @@ -2928,6 +3009,7 @@ function wfRelativePath( $path, $from ) { * * @deprecated since 1.19 * @see DeferredUpdates::doUpdate() + * @param $commit string */ function wfDoUpdates( $commit = '' ) { DeferredUpdates::doUpdates( $commit ); @@ -3025,6 +3107,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t * * @param $name String * @param $p Array: parameters + * @return object * @deprecated since 1.18, warnings in 1.18, removal in 1.20 */ function wfCreateObject( $name, $p ) { @@ -3032,6 +3115,9 @@ function wfCreateObject( $name, $p ) { return MWFunction::newObj( $name, $p ); } +/** + * @return bool + */ function wfHttpOnlySafe() { global $wgHttpOnlyBlacklist; @@ -3162,7 +3248,6 @@ function wfWikiID() { * Split a wiki ID into DB name and table prefix * * @param $wiki String - * @param $bits String * * @return array */ @@ -3248,7 +3333,7 @@ function wfFindFile( $title, $options = array() ) { * Returns a valid placeholder object if the file does not exist. * * @param $title Title or String - * @return File, or null if passed an invalid Title + * @return File|null A File, or null if passed an invalid Title */ function wfLocalFile( $title ) { return RepoGroup::singleton()->getLocalRepo()->newFile( $title ); @@ -3422,7 +3507,6 @@ function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) { * * @param $maxLag Integer (deprecated) * @param $wiki mixed Wiki identifier accepted by wfGetLB - * @return null */ function wfWaitForSlaves( $maxLag = false, $wiki = false ) { $lb = wfGetLB( $wiki ); @@ -3454,6 +3538,7 @@ function wfOut( $s ) { * Count down from $n to zero on the terminal, with a one-second pause * between showing each number. For use in command-line scripts. * @codeCoverageIgnore + * @param $n int */ function wfCountDown( $n ) { for ( $i = $n; $i >= 0; $i-- ) { @@ -3562,7 +3647,7 @@ function wfShorthandToInteger( $string = '' ) { * See unit test for examples. * * @param $code String: The language code. - * @return $langCode String: The language code which complying with BCP 47 standards. + * @return String: The language code which complying with BCP 47 standards. */ function wfBCP47( $code ) { $codeSegment = explode( '-', $code ); @@ -3641,3 +3726,39 @@ function wfGetParserCacheStorage() { function wfRunHooks( $event, $args = array() ) { return Hooks::run( $event, $args ); } + +/** + * Wrapper around php's unpack. + * + * @param $format String: The format string (See php's docs) + * @param $data: A binary string of binary data + * @param $length integer or false: The minimun length of $data. This is to + * prevent reading beyond the end of $data. false to disable the check. + * + * Also be careful when using this function to read unsigned 32 bit integer + * because php might make it negative. + * + * @throws MWException if $data not long enough, or if unpack fails + * @return Associative array of the extracted data + */ +function wfUnpack( $format, $data, $length=false ) { + if ( $length !== false ) { + $realLen = strlen( $data ); + if ( $realLen < $length ) { + throw new MWException( "Tried to use wfUnpack on a " + . "string of length $realLen, but needed one " + . "of at least length $length." + ); + } + } + + wfSuppressWarnings(); + $result = unpack( $format, $data ); + wfRestoreWarnings(); + + if ( $result === false ) { + // If it cannot extract the packed data. + throw new MWException( "unpack could not unpack binary data" ); + } + return $result; +}