Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
n/a
0 / 0
23.36% covered (danger)
23.36%
25 / 107
CRAP
47.75% covered (danger)
47.75%
436 / 913
wfLoadExtension
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
wfLoadExtensions
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
wfLoadSkin
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
wfLoadSkins
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
wfArrayDiff2
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
wfArrayDiff2_cmp
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 15
wfAppendToArrayIfNotDefault
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
wfMergeErrorArrays
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 10
wfArrayInsertAfter
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 7
wfObjectToArray
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 8
wfRandom
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
wfRandomString
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
wfUrlencode
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
15 / 15
wfArrayToCgi
0.00% covered (danger)
0.00%
0 / 1
12
95.24% covered (success)
95.24%
20 / 21
wfCgiToArray
0.00% covered (danger)
0.00%
0 / 1
9
96.00% covered (success)
96.00%
24 / 25
wfAppendQuery
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
15 / 15
wfExpandUrl
0.00% covered (danger)
0.00%
0 / 1
20.60
88.57% covered (warning)
88.57%
31 / 35
wfGetServerUrl
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
wfAssembleUrl
100.00% covered (success)
100.00%
1 / 1
10
100.00% covered (success)
100.00%
21 / 21
wfRemoveDotSegments
100.00% covered (success)
100.00%
1 / 1
18
100.00% covered (success)
100.00%
43 / 43
wfUrlProtocols
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 16
wfUrlProtocolsWithoutProtRel
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfParseUrl
0.00% covered (danger)
0.00%
0 / 1
11
96.15% covered (success)
96.15%
25 / 26
wfExpandIRI
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
wfMatchesDomainList
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
8 / 8
wfDebug
0.00% covered (danger)
0.00%
0 / 1
5.20
80.00% covered (warning)
80.00%
8 / 10
wfIsDebugRawPage
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 8
wfDebugMem
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
wfDebugLog
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
wfLogDBError
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
wfDeprecated
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
wfDeprecatedMsg
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
wfWarn
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
wfLogWarning
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
wfLogProfilingData
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 8
wfIncrStats
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
wfReadOnly
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
wfReadOnlyReason
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
wfConfiguredReadOnlyReason
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
wfGetLangObj
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 14
wfMessage
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
wfMessageFallback
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfMsgReplaceArgs
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 9
wfHostname
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 4
wfReportTime
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 9
wfDebugBacktrace
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 10
wfBacktrace
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 16
wfGetCaller
0.00% covered (danger)
0.00%
0 / 1
2.06
75.00% covered (warning)
75.00%
3 / 4
wfGetAllCallers
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 5
wfFormatStackFrame
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 5
wfShowingResults
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfClientAcceptsGzip
0.00% covered (danger)
0.00%
0 / 1
8.15
86.67% covered (warning)
86.67%
13 / 15
wfEscapeWikiText
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 20
wfSetVar
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 4
wfSetBit
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
wfVarDump
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 6
wfHttpError
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 17
wfResetOutputBuffers
0.00% covered (danger)
0.00%
0 / 1
110
0.00% covered (danger)
0.00%
0 / 20
wfClearOutputBuffers
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
wfAcceptToPrefs
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 12
mimeTypeMatch
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
8 / 8
wfNegotiateType
0.00% covered (danger)
0.00%
0 / 1
10
95.00% covered (success)
95.00%
19 / 20
wfTimestamp
0.00% covered (danger)
0.00%
0 / 1
2.06
75.00% covered (warning)
75.00%
3 / 4
wfTimestampOrNull
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
wfTimestampNow
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfIsWindows
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfIsCLI
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
wfTempDir
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
wfMkdirParents
0.00% covered (danger)
0.00%
0 / 1
8.70
77.78% covered (warning)
77.78%
14 / 18
wfRecursiveRemoveDir
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 11
wfPercent
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
wfIniGetBool
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfStringToBool
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
5 / 5
wfEscapeShellArg
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
wfShellExec
0.00% covered (danger)
0.00%
0 / 1
5.68
70.00% covered (warning)
70.00%
14 / 20
wfShellExecWithStderr
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
wfShellWikiCmd
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
wfMerge
0.00% covered (danger)
0.00%
0 / 1
10.07
91.11% covered (success)
91.11%
41 / 45
wfDiff
0.00% covered (danger)
0.00%
0 / 1
132
0.00% covered (danger)
0.00%
0 / 41
wfBaseName
0.00% covered (danger)
0.00%
0 / 1
3.21
71.43% covered (warning)
71.43%
5 / 7
wfRelativePath
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 17
wfGetPrecompiledData
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 7
wfMemcKey
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
wfForeignMemcKey
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
2 / 2
wfWikiID
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
wfGetDB
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfGetLB
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
wfFindFile
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfLocalFile
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
wfQueriesMustScale
n/a
0 / 0
4
n/a
0 / 0
wfScript
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
wfGetScriptUrl
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
wfBoolToStr
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
wfGetNull
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
wfStripIllegalFilenameChars
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 8
wfMemoryLimit
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 14
wfTransactionalTimeLimit
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
wfShorthandToInteger
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
16 / 16
wfGetCache
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfGetMainCache
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfUnpack
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 12
wfIsBadImage
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
wfCanIPUseHTTPS
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
wfIsInfinity
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
wfThumbIsStandard
0.00% covered (danger)
0.00%
0 / 1
13.03
94.59% covered (success)
94.59%
35 / 37
wfArrayPlus2d
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
wfGetRusage
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
<?php
/**
 * Global functions used everywhere.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */
if ( !defined( 'MEDIAWIKI' ) ) {
    die( "This file is part of MediaWiki, it is not a valid entry point" );
}
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\ProcOpenError;
use MediaWiki\Shell\Shell;
use Wikimedia\AtEase\AtEase;
use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
use Wikimedia\WrappedString;
/**
 * 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
 * @since 1.25
 */
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
 * @since 1.25
 */
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
 * @since 1.25
 */
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
 * @since 1.25
 */
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
 * @return array
 */
function wfArrayDiff2( $a, $b ) {
    return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
}
/**
 * @param array|string $a
 * @param array|string $b
 * @return int
 */
function wfArrayDiff2_cmp( $a, $b ) {
    if ( is_string( $a ) && is_string( $b ) ) {
        return strcmp( $a, $b );
    } elseif ( count( $a ) !== count( $b ) ) {
        return count( $a ) <=> count( $b );
    } else {
        reset( $a );
        reset( $b );
        while ( key( $a ) !== null && key( $b ) !== null ) {
            $valueA = current( $a );
            $valueB = current( $b );
            $cmp = strcmp( $valueA, $valueB );
            if ( $cmp !== 0 ) {
                return $cmp;
            }
            next( $a );
            next( $b );
        }
        return 0;
    }
}
/**
 * Appends to second array if $value differs from that in $default
 *
 * @param string|int $key
 * @param mixed $value
 * @param mixed $default
 * @param array &$changed Array to alter
 * @throws MWException
 */
function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
    if ( $changed === null ) {
        throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
    }
    if ( $default[$key] !== $value ) {
        $changed[$key] = $value;
    }
}
/**
 * Merge arrays in the style of PermissionManager::getPermissionErrors, with duplicate removal
 * e.g.
 *     wfMergeErrorArrays(
 *       [ [ 'x' ] ],
 *       [ [ 'x', '2' ] ],
 *       [ [ 'x' ] ],
 *       [ [ 'y' ] ]
 *     );
 * returns:
 *     [
 *       [ 'x', '2' ],
 *       [ 'x' ],
 *       [ 'y' ]
 *     ]
 *
 * @param array[] ...$args
 * @return array
 */
function wfMergeErrorArrays( ...$args ) {
    $out = [];
    foreach ( $args as $errors ) {
        foreach ( $errors as $params ) {
            $originalParams = $params;
            if ( $params[0] instanceof MessageSpecifier ) {
                $msg = $params[0];
                $params = array_merge( [ $msg->getKey() ], $msg->getParams() );
            }
            # @todo FIXME: Sometimes get nested arrays for $params,
            # which leads to E_NOTICEs
            $spec = implode( "\t", $params );
            $out[$spec] = $originalParams;
        }
    }
    return array_values( $out );
}
/**
 * Insert array into another array after the specified *KEY*
 *
 * @param array $array The array.
 * @param array $insert The array to insert.
 * @param mixed $after The key to insert after. Callers need to make sure the key is set.
 * @return array
 */
function wfArrayInsertAfter( array $array, array $insert, $after ) {
    // Find the offset of the element to insert after.
    $keys = array_keys( $array );
    $offsetByKey = array_flip( $keys );
    $offset = $offsetByKey[$after];
    // Insert at the specified offset
    $before = array_slice( $array, 0, $offset + 1, true );
    $after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
    $output = $before + $insert + $after;
    return $output;
}
/**
 * Recursively converts the parameter (an object) to an array with the same data
 *
 * @param object|array $objOrArray
 * @param bool $recursive
 * @return array
 */
function wfObjectToArray( $objOrArray, $recursive = true ) {
    $array = [];
    if ( is_object( $objOrArray ) ) {
        $objOrArray = get_object_vars( $objOrArray );
    }
    foreach ( $objOrArray as $key => $value ) {
        if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
            $value = wfObjectToArray( $value );
        }
        $array[$key] = $value;
    }
    return $array;
}
/**
 * Get a random decimal value in the domain of [0, 1), in a way
 * not likely to give duplicate values for any realistic
 * number of articles.
 *
 * @note This is designed for use in relation to Special:RandomPage
 *       and the page_random database field.
 *
 * @return string
 */
function wfRandom() {
    // The maximum random value is "only" 2^31-1, so get two random
    // values to reduce the chance of dupes
    $max = mt_getrandmax() + 1;
    $rand = number_format( ( mt_rand() * $max + mt_rand() ) / $max / $max, 12, '.', '' );
    return $rand;
}
/**
 * Get a random string containing a number of pseudo-random hex characters.
 *
 * @note This is not secure, if you are trying to generate some sort
 *       of token please use MWCryptRand instead.
 *
 * @param int $length The length of the string to generate
 * @return string
 * @since 1.20
 */
function wfRandomString( $length = 32 ) {
    $str = '';
    for ( $n = 0; $n < $length; $n += 7 ) {
        $str .= sprintf( '%07x', mt_rand() & 0xfffffff );
    }
    return substr( $str, 0, $length );
}
/**
 * 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:
 *
 * ;:@&=$-_.+!*'(),
 *
 * RFC 1738 says ~ is unsafe, however RFC 3986 considers it an unreserved
 * character which should not be encoded. More importantly, google chrome
 * always converts %7E back to ~, and converting it in this function can
 * cause a redirect loop (T105265).
 *
 * 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:
 *
 * ;:@$!*(),/~
 *
 * However, IIS7 redirects fail when the url contains a colon (see T24709),
 * so no fancy : for IIS7.
 *
 * %2F in the page titles seems to fatally break for some reason.
 *
 * @param string $s
 * @return string
 */
function wfUrlencode( $s ) {
    static $needle;
    if ( $s === null ) {
        // Reset $needle for testing.
        $needle = null;
        return '';
    }
    if ( $needle === null ) {
        $needle = [ '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F', '%7E' ];
        if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) ||
            ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false )
        ) {
            $needle[] = '%3A';
        }
    }
    $s = urlencode( $s );
    $s = str_ireplace(
        $needle,
        [ ';', '@', '$', '!', '*', '(', ')', ',', '/', '~', ':' ],
        $s
    );
    return $s;
}
/**
 * This function takes one or two arrays as input, and returns a CGI-style string, e.g.
 * "days=7&limit=100". Options in the first array override options in the second.
 * Options set to null or false will not be output.
 *
 * @param array $array1 ( String|Array )
 * @param array|null $array2 ( String|Array )
 * @param string $prefix
 * @return string
 */
function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
    if ( $array2 !== null ) {
        $array1 += $array2;
    }
    $cgi = '';
    foreach ( $array1 as $key => $value ) {
        if ( $value !== null && $value !== false ) {
            if ( $cgi != '' ) {
                $cgi .= '&';
            }
            if ( $prefix !== '' ) {
                $key = $prefix . "[$key]";
            }
            if ( is_array( $value ) ) {
                $firstTime = true;
                foreach ( $value as $k => $v ) {
                    $cgi .= $firstTime ? '' : '&';
                    if ( is_array( $v ) ) {
                        $cgi .= wfArrayToCgi( $v, null, $key . "[$k]" );
                    } else {
                        $cgi .= urlencode( $key . "[$k]" ) . '=' . urlencode( $v );
                    }
                    $firstTime = false;
                }
            } else {
                if ( is_object( $value ) ) {
                    $value = $value->__toString();
                }
                $cgi .= urlencode( $key ) . '=' . urlencode( $value );
            }
        }
    }
    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 compatibility
 * with legacy functions that accept raw query strings instead of nice
 * arrays.  Of course, keys and values are urldecode()d.
 *
 * @param string $query Query string
 * @return string[] Array version of input
 */
function wfCgiToArray( $query ) {
    if ( isset( $query[0] ) && $query[0] == '?' ) {
        $query = substr( $query, 1 );
    }
    $bits = explode( '&', $query );
    $ret = [];
    foreach ( $bits as $bit ) {
        if ( $bit === '' ) {
            continue;
        }
        if ( strpos( $bit, '=' ) === false ) {
            // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
            $key = $bit;
            $value = '';
        } else {
            list( $key, $value ) = explode( '=', $bit );
        }
        $key = urldecode( $key );
        $value = urldecode( $value );
        if ( strpos( $key, '[' ) !== false ) {
            $keys = array_reverse( explode( '[', $key ) );
            $key = array_pop( $keys );
            $temp = $value;
            foreach ( $keys as $k ) {
                $k = substr( $k, 0, -1 );
                $temp = [ $k => $temp ];
            }
            if ( isset( $ret[$key] ) ) {
                $ret[$key] = array_merge( $ret[$key], $temp );
            } else {
                $ret[$key] = $temp;
            }
        } else {
            $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.
 *
 * @param string $url
 * @param string|array $query String or associative array
 * @return string
 */
function wfAppendQuery( $url, $query ) {
    if ( is_array( $query ) ) {
        $query = wfArrayToCgi( $query );
    }
    if ( $query != '' ) {
        // Remove the fragment, if there is one
        $fragment = false;
        $hashPos = strpos( $url, '#' );
        if ( $hashPos !== false ) {
            $fragment = substr( $url, $hashPos );
            $url = substr( $url, 0, $hashPos );
        }
        // Add parameter
        if ( strpos( $url, '?' ) === false ) {
            $url .= '?';
        } else {
            $url .= '&';
        }
        $url .= $query;
        // Put the fragment back
        if ( $fragment !== false ) {
            $url .= $fragment;
        }
    }
    return $url;
}
/**
 * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
 * is correct.
 *
 * The meaning of the PROTO_* constants is as follows:
 * PROTO_HTTP: Output a URL starting with http://
 * PROTO_HTTPS: Output a URL starting with https://
 * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
 * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending
 *    on which protocol was used for the current incoming request
 * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer.
 *    For protocol-relative URLs, use the protocol of $wgCanonicalServer
 * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
 *
 * @todo this won't work with current-path-relative URLs
 * like "subdir/foo.html", etc.
 *
 * @param string $url Either fully-qualified or a local path + query
 * @param string|int|null $defaultProto One of the PROTO_* constants. Determines the
 *    protocol to use if $url or $wgServer is protocol-relative
 * @return string|false 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, $wgRequest,
        $wgHttpsPort;
    if ( $defaultProto === PROTO_CANONICAL ) {
        $serverUrl = $wgCanonicalServer;
    } elseif ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
        // Make $wgInternalServer fall back to $wgServer if not set
        $serverUrl = $wgInternalServer;
    } else {
        $serverUrl = $wgServer;
        if ( $defaultProto === PROTO_CURRENT ) {
            $defaultProto = $wgRequest->getProtocol() . '://';
        }
    }
    // Analyze $serverUrl to obtain its protocol
    $bits = wfParseUrl( $serverUrl );
    $serverHasProto = $bits && $bits['scheme'] != '';
    if ( $defaultProto === PROTO_CANONICAL || $defaultProto === PROTO_INTERNAL ) {
        if ( $serverHasProto ) {
            $defaultProto = $bits['scheme'] . '://';
        } else {
            // $wgCanonicalServer or $wgInternalServer doesn't have a protocol.
            // This really isn't supposed to happen. Fall back to HTTP in this
            // ridiculous case.
            $defaultProto = PROTO_HTTP;
        }
    }
    $defaultProtoWithoutSlashes = $defaultProto !== null ? substr( $defaultProto, 0, -2 ) : '';
    if ( substr( $url, 0, 2 ) == '//' ) {
        $url = $defaultProtoWithoutSlashes . $url;
    } elseif ( substr( $url, 0, 1 ) == '/' ) {
        // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes,
        // otherwise leave it alone.
        if ( $serverHasProto ) {
            $url = $serverUrl . $url;
        } else {
            // If an HTTPS URL is synthesized from a protocol-relative $wgServer, allow the
            // user to override the port number (T67184)
            if ( $defaultProto === PROTO_HTTPS && $wgHttpsPort != 443 ) {
                if ( isset( $bits['port'] ) ) {
                    throw new Exception( 'A protocol-relative $wgServer may not contain a port number' );
                }
                $url = $defaultProtoWithoutSlashes . $serverUrl . ':' . $wgHttpsPort . $url;
            } else {
                $url = $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;
}
/**
 * Get the wiki's "server", i.e. the protocol and host part of the URL, with a
 * protocol specified using a PROTO_* constant as in wfExpandUrl()
 *
 * @since 1.32
 * @param string|int|null $proto One of the PROTO_* constants.
 * @return string The URL
 */
function wfGetServerUrl( $proto ) {
    $url = wfExpandUrl( '/', $proto );
    return substr( $url, 0, -1 );
}
/**
 * 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 (see T34168)
 *
 * @since 1.19
 * @param array $urlParts URL parts, as output from wfParseUrl
 * @return string URL assembled from its component parts
 */
function wfAssembleUrl( $urlParts ) {
    $result = '';
    if ( isset( $urlParts['delimiter'] ) ) {