Completed
Branch master (939199)
by
unknown
39:35
created

GlobalFunctions.php ➔ wfEscapeWikiText()   C

Complexity

Conditions 8
Paths 2

Size

Total Lines 45
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 33
nc 2
nop 1
dl 0
loc 45
rs 5.3846
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by Brion Vibber
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 70 and the first side effect is on line 24.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Global functions used everywhere.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
23
if ( !defined( 'MEDIAWIKI' ) ) {
24
	die( "This file is part of MediaWiki, it is not a valid entry point" );
25
}
26
27
use Liuggio\StatsdClient\Sender\SocketSender;
28
use MediaWiki\Logger\LoggerFactory;
29
use MediaWiki\Session\SessionManager;
30
use Wikimedia\ScopedCallback;
0 ignored issues
show
Bug introduced by Kunal Mehta
This use statement conflicts with another class in this namespace, ScopedCallback.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
31
32
// Hide compatibility functions from Doxygen
33
/// @cond
34
/**
35
 * Compatibility functions
36
 *
37
 * We support PHP 5.5.9 and up.
38
 * Re-implementations of newer functions or functions in non-standard
39
 * PHP extensions may be included here.
40
 */
41
42
// hash_equals function only exists in PHP >= 5.6.0
43
// http://php.net/hash_equals
44
if ( !function_exists( 'hash_equals' ) ) {
45
	/**
46
	 * Check whether a user-provided string is equal to a fixed-length secret string
47
	 * without revealing bytes of the secret string through timing differences.
48
	 *
49
	 * The usual way to compare strings (PHP's === operator or the underlying memcmp()
50
	 * function in C) is to compare corresponding bytes and stop at the first difference,
51
	 * which would take longer for a partial match than for a complete mismatch. This
52
	 * is not secure when one of the strings (e.g. an HMAC or token) must remain secret
53
	 * and the other may come from an attacker. Statistical analysis of timing measurements
54
	 * over many requests may allow the attacker to guess the string's bytes one at a time
55
	 * (and check his guesses) even if the timing differences are extremely small.
56
	 *
57
	 * When making such a security-sensitive comparison, it is essential that the sequence
58
	 * in which instructions are executed and memory locations are accessed not depend on
59
	 * the secret string's value. HOWEVER, for simplicity, we do not attempt to minimize
60
	 * the inevitable leakage of the string's length. That is generally known anyway as
61
	 * a chararacteristic of the hash function used to compute the secret value.
62
	 *
63
	 * Longer explanation: http://www.emerose.com/timing-attacks-explained
64
	 *
65
	 * @codeCoverageIgnore
66
	 * @param string $known_string Fixed-length secret string to compare against
67
	 * @param string $user_string User-provided string
68
	 * @return bool True if the strings are the same, false otherwise
69
	 */
70
	function hash_equals( $known_string, $user_string ) {
71
		// Strict type checking as in PHP's native implementation
72
		if ( !is_string( $known_string ) ) {
73
			trigger_error( 'hash_equals(): Expected known_string to be a string, ' .
74
				gettype( $known_string ) . ' given', E_USER_WARNING );
75
76
			return false;
77
		}
78
79
		if ( !is_string( $user_string ) ) {
80
			trigger_error( 'hash_equals(): Expected user_string to be a string, ' .
81
				gettype( $user_string ) . ' given', E_USER_WARNING );
82
83
			return false;
84
		}
85
86
		$known_string_len = strlen( $known_string );
87
		if ( $known_string_len !== strlen( $user_string ) ) {
88
			return false;
89
		}
90
91
		$result = 0;
92
		for ( $i = 0; $i < $known_string_len; $i++ ) {
93
			$result |= ord( $known_string[$i] ) ^ ord( $user_string[$i] );
94
		}
95
96
		return ( $result === 0 );
97
	}
98
}
99
/// @endcond
100
101
/**
102
 * Load an extension
103
 *
104
 * This queues an extension to be loaded through
105
 * the ExtensionRegistry system.
106
 *
107
 * @param string $ext Name of the extension to load
108
 * @param string|null $path Absolute path of where to find the extension.json file
109
 * @since 1.25
110
 */
111
function wfLoadExtension( $ext, $path = null ) {
112
	if ( !$path ) {
0 ignored issues
show
Bug Best Practice introduced by Kunal Mehta
The expression $path of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
113
		global $wgExtensionDirectory;
114
		$path = "$wgExtensionDirectory/$ext/extension.json";
115
	}
116
	ExtensionRegistry::getInstance()->queue( $path );
117
}
118
119
/**
120
 * Load multiple extensions at once
121
 *
122
 * Same as wfLoadExtension, but more efficient if you
123
 * are loading multiple extensions.
124
 *
125
 * If you want to specify custom paths, you should interact with
126
 * ExtensionRegistry directly.
127
 *
128
 * @see wfLoadExtension
129
 * @param string[] $exts Array of extension names to load
130
 * @since 1.25
131
 */
132
function wfLoadExtensions( array $exts ) {
133
	global $wgExtensionDirectory;
134
	$registry = ExtensionRegistry::getInstance();
135
	foreach ( $exts as $ext ) {
136
		$registry->queue( "$wgExtensionDirectory/$ext/extension.json" );
137
	}
138
}
139
140
/**
141
 * Load a skin
142
 *
143
 * @see wfLoadExtension
144
 * @param string $skin Name of the extension to load
145
 * @param string|null $path Absolute path of where to find the skin.json file
146
 * @since 1.25
147
 */
148
function wfLoadSkin( $skin, $path = null ) {
149
	if ( !$path ) {
0 ignored issues
show
Bug Best Practice introduced by Kunal Mehta
The expression $path of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
150
		global $wgStyleDirectory;
151
		$path = "$wgStyleDirectory/$skin/skin.json";
152
	}
153
	ExtensionRegistry::getInstance()->queue( $path );
154
}
155
156
/**
157
 * Load multiple skins at once
158
 *
159
 * @see wfLoadExtensions
160
 * @param string[] $skins Array of extension names to load
161
 * @since 1.25
162
 */
163
function wfLoadSkins( array $skins ) {
164
	global $wgStyleDirectory;
165
	$registry = ExtensionRegistry::getInstance();
166
	foreach ( $skins as $skin ) {
167
		$registry->queue( "$wgStyleDirectory/$skin/skin.json" );
168
	}
169
}
170
171
/**
172
 * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
173
 * @param array $a
174
 * @param array $b
175
 * @return array
176
 */
177
function wfArrayDiff2( $a, $b ) {
178
	return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
179
}
180
181
/**
182
 * @param array|string $a
183
 * @param array|string $b
184
 * @return int
185
 */
186
function wfArrayDiff2_cmp( $a, $b ) {
187
	if ( is_string( $a ) && is_string( $b ) ) {
188
		return strcmp( $a, $b );
189
	} elseif ( count( $a ) !== count( $b ) ) {
190
		return count( $a ) < count( $b ) ? -1 : 1;
191
	} else {
192
		reset( $a );
193
		reset( $b );
194
		while ( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
195
			$cmp = strcmp( $valueA, $valueB );
196
			if ( $cmp !== 0 ) {
197
				return $cmp;
198
			}
199
		}
200
		return 0;
201
	}
202
}
203
204
/**
205
 * Appends to second array if $value differs from that in $default
206
 *
207
 * @param string|int $key
208
 * @param mixed $value
209
 * @param mixed $default
210
 * @param array $changed Array to alter
211
 * @throws MWException
212
 */
213
function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
214
	if ( is_null( $changed ) ) {
215
		throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
216
	}
217
	if ( $default[$key] !== $value ) {
218
		$changed[$key] = $value;
219
	}
220
}
221
222
/**
223
 * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
224
 * e.g.
225
 *	wfMergeErrorArrays(
226
 *		[ [ 'x' ] ],
227
 *		[ [ 'x', '2' ] ],
228
 *		[ [ 'x' ] ],
229
 *		[ [ 'y' ] ]
230
 *	);
231
 * returns:
232
 * 		[
233
 *   		[ 'x', '2' ],
234
 *   		[ 'x' ],
235
 *   		[ 'y' ]
236
 *   	]
237
 *
238
 * @param array $array1,...
0 ignored issues
show
Bug introduced by umherirrender
There is no parameter named $array1,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
239
 * @return array
240
 */
241
function wfMergeErrorArrays( /*...*/ ) {
242
	$args = func_get_args();
243
	$out = [];
244
	foreach ( $args as $errors ) {
245
		foreach ( $errors as $params ) {
246
			$originalParams = $params;
247
			if ( $params[0] instanceof MessageSpecifier ) {
248
				$msg = $params[0];
249
				$params = array_merge( [ $msg->getKey() ], $msg->getParams() );
250
			}
251
			# @todo FIXME: Sometimes get nested arrays for $params,
252
			# which leads to E_NOTICEs
253
			$spec = implode( "\t", $params );
254
			$out[$spec] = $originalParams;
255
		}
256
	}
257
	return array_values( $out );
258
}
259
260
/**
261
 * Insert array into another array after the specified *KEY*
262
 *
263
 * @param array $array The array.
264
 * @param array $insert The array to insert.
265
 * @param mixed $after The key to insert after
266
 * @return array
267
 */
268
function wfArrayInsertAfter( array $array, array $insert, $after ) {
269
	// Find the offset of the element to insert after.
270
	$keys = array_keys( $array );
271
	$offsetByKey = array_flip( $keys );
272
273
	$offset = $offsetByKey[$after];
274
275
	// Insert at the specified offset
276
	$before = array_slice( $array, 0, $offset + 1, true );
277
	$after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
278
279
	$output = $before + $insert + $after;
280
281
	return $output;
282
}
283
284
/**
285
 * Recursively converts the parameter (an object) to an array with the same data
286
 *
287
 * @param object|array $objOrArray
288
 * @param bool $recursive
289
 * @return array
290
 */
291
function wfObjectToArray( $objOrArray, $recursive = true ) {
292
	$array = [];
293
	if ( is_object( $objOrArray ) ) {
294
		$objOrArray = get_object_vars( $objOrArray );
295
	}
296
	foreach ( $objOrArray as $key => $value ) {
297
		if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
298
			$value = wfObjectToArray( $value );
299
		}
300
301
		$array[$key] = $value;
302
	}
303
304
	return $array;
305
}
306
307
/**
308
 * Get a random decimal value between 0 and 1, in a way
309
 * not likely to give duplicate values for any realistic
310
 * number of articles.
311
 *
312
 * @note This is designed for use in relation to Special:RandomPage
313
 *       and the page_random database field.
314
 *
315
 * @return string
316
 */
317
function wfRandom() {
318
	// The maximum random value is "only" 2^31-1, so get two random
319
	// values to reduce the chance of dupes
320
	$max = mt_getrandmax() + 1;
321
	$rand = number_format( ( mt_rand() * $max + mt_rand() ) / $max / $max, 12, '.', '' );
322
	return $rand;
323
}
324
325
/**
326
 * Get a random string containing a number of pseudo-random hex characters.
327
 *
328
 * @note This is not secure, if you are trying to generate some sort
329
 *       of token please use MWCryptRand instead.
330
 *
331
 * @param int $length The length of the string to generate
332
 * @return string
333
 * @since 1.20
334
 */
335
function wfRandomString( $length = 32 ) {
336
	$str = '';
337
	for ( $n = 0; $n < $length; $n += 7 ) {
338
		$str .= sprintf( '%07x', mt_rand() & 0xfffffff );
339
	}
340
	return substr( $str, 0, $length );
341
}
342
343
/**
344
 * We want some things to be included as literal characters in our title URLs
345
 * for prettiness, which urlencode encodes by default.  According to RFC 1738,
346
 * all of the following should be safe:
347
 *
348
 * ;:@&=$-_.+!*'(),
349
 *
350
 * RFC 1738 says ~ is unsafe, however RFC 3986 considers it an unreserved
351
 * character which should not be encoded. More importantly, google chrome
352
 * always converts %7E back to ~, and converting it in this function can
353
 * cause a redirect loop (T105265).
354
 *
355
 * But + is not safe because it's used to indicate a space; &= are only safe in
356
 * paths and not in queries (and we don't distinguish here); ' seems kind of
357
 * scary; and urlencode() doesn't touch -_. to begin with.  Plus, although /
358
 * is reserved, we don't care.  So the list we unescape is:
359
 *
360
 * ;:@$!*(),/~
361
 *
362
 * However, IIS7 redirects fail when the url contains a colon (see T24709),
363
 * so no fancy : for IIS7.
364
 *
365
 * %2F in the page titles seems to fatally break for some reason.
366
 *
367
 * @param string $s
368
 * @return string
369
 */
370
function wfUrlencode( $s ) {
0 ignored issues
show
Coding Style introduced by Aryeh Gregor
wfUrlencode uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
371
	static $needle;
372
373
	if ( is_null( $s ) ) {
374
		$needle = null;
375
		return '';
376
	}
377
378
	if ( is_null( $needle ) ) {
379
		$needle = [ '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F', '%7E' ];
380
		if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) ||
381
			( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false )
382
		) {
383
			$needle[] = '%3A';
384
		}
385
	}
386
387
	$s = urlencode( $s );
388
	$s = str_ireplace(
389
		$needle,
390
		[ ';', '@', '$', '!', '*', '(', ')', ',', '/', '~', ':' ],
391
		$s
392
	);
393
394
	return $s;
395
}
396
397
/**
398
 * This function takes one or two arrays as input, and returns a CGI-style string, e.g.
399
 * "days=7&limit=100". Options in the first array override options in the second.
400
 * Options set to null or false will not be output.
401
 *
402
 * @param array $array1 ( String|Array )
403
 * @param array|null $array2 ( String|Array )
404
 * @param string $prefix
405
 * @return string
406
 */
407
function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
408
	if ( !is_null( $array2 ) ) {
409
		$array1 = $array1 + $array2;
410
	}
411
412
	$cgi = '';
413
	foreach ( $array1 as $key => $value ) {
414
		if ( !is_null( $value ) && $value !== false ) {
415
			if ( $cgi != '' ) {
416
				$cgi .= '&';
417
			}
418
			if ( $prefix !== '' ) {
419
				$key = $prefix . "[$key]";
420
			}
421
			if ( is_array( $value ) ) {
422
				$firstTime = true;
423
				foreach ( $value as $k => $v ) {
424
					$cgi .= $firstTime ? '' : '&';
425
					if ( is_array( $v ) ) {
426
						$cgi .= wfArrayToCgi( $v, null, $key . "[$k]" );
427
					} else {
428
						$cgi .= urlencode( $key . "[$k]" ) . '=' . urlencode( $v );
429
					}
430
					$firstTime = false;
431
				}
432
			} else {
433
				if ( is_object( $value ) ) {
434
					$value = $value->__toString();
435
				}
436
				$cgi .= urlencode( $key ) . '=' . urlencode( $value );
437
			}
438
		}
439
	}
440
	return $cgi;
441
}
442
443
/**
444
 * This is the logical opposite of wfArrayToCgi(): it accepts a query string as
445
 * its argument and returns the same string in array form.  This allows compatibility
446
 * with legacy functions that accept raw query strings instead of nice
447
 * arrays.  Of course, keys and values are urldecode()d.
448
 *
449
 * @param string $query Query string
450
 * @return string[] Array version of input
451
 */
452
function wfCgiToArray( $query ) {
453
	if ( isset( $query[0] ) && $query[0] == '?' ) {
454
		$query = substr( $query, 1 );
455
	}
456
	$bits = explode( '&', $query );
457
	$ret = [];
458
	foreach ( $bits as $bit ) {
459
		if ( $bit === '' ) {
460
			continue;
461
		}
462
		if ( strpos( $bit, '=' ) === false ) {
463
			// Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
464
			$key = $bit;
465
			$value = '';
466
		} else {
467
			list( $key, $value ) = explode( '=', $bit );
468
		}
469
		$key = urldecode( $key );
470
		$value = urldecode( $value );
471
		if ( strpos( $key, '[' ) !== false ) {
472
			$keys = array_reverse( explode( '[', $key ) );
473
			$key = array_pop( $keys );
474
			$temp = $value;
475
			foreach ( $keys as $k ) {
476
				$k = substr( $k, 0, -1 );
477
				$temp = [ $k => $temp ];
478
			}
479 View Code Duplication
			if ( isset( $ret[$key] ) ) {
480
				$ret[$key] = array_merge( $ret[$key], $temp );
481
			} else {
482
				$ret[$key] = $temp;
483
			}
484
		} else {
485
			$ret[$key] = $value;
486
		}
487
	}
488
	return $ret;
489
}
490
491
/**
492
 * Append a query string to an existing URL, which may or may not already
493
 * have query string parameters already. If so, they will be combined.
494
 *
495
 * @param string $url
496
 * @param string|string[] $query String or associative array
497
 * @return string
498
 */
499
function wfAppendQuery( $url, $query ) {
500
	if ( is_array( $query ) ) {
501
		$query = wfArrayToCgi( $query );
502
	}
503
	if ( $query != '' ) {
504
		// Remove the fragment, if there is one
505
		$fragment = false;
506
		$hashPos = strpos( $url, '#' );
507
		if ( $hashPos !== false ) {
508
			$fragment = substr( $url, $hashPos );
509
			$url = substr( $url, 0, $hashPos );
510
		}
511
512
		// Add parameter
513
		if ( false === strpos( $url, '?' ) ) {
514
			$url .= '?';
515
		} else {
516
			$url .= '&';
517
		}
518
		$url .= $query;
519
520
		// Put the fragment back
521
		if ( $fragment !== false ) {
522
			$url .= $fragment;
523
		}
524
	}
525
	return $url;
526
}
527
528
/**
529
 * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
530
 * is correct.
531
 *
532
 * The meaning of the PROTO_* constants is as follows:
533
 * PROTO_HTTP: Output a URL starting with http://
534
 * PROTO_HTTPS: Output a URL starting with https://
535
 * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
536
 * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending
537
 *    on which protocol was used for the current incoming request
538
 * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer.
539
 *    For protocol-relative URLs, use the protocol of $wgCanonicalServer
540
 * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
541
 *
542
 * @todo this won't work with current-path-relative URLs
543
 * like "subdir/foo.html", etc.
544
 *
545
 * @param string $url Either fully-qualified or a local path + query
546
 * @param string $defaultProto One of the PROTO_* constants. Determines the
547
 *    protocol to use if $url or $wgServer is protocol-relative
548
 * @return string Fully-qualified URL, current-path-relative URL or false if
549
 *    no valid URL can be constructed
550
 */
551
function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
552
	global $wgServer, $wgCanonicalServer, $wgInternalServer, $wgRequest,
553
		$wgHttpsPort;
554
	if ( $defaultProto === PROTO_CANONICAL ) {
0 ignored issues
show
Unused Code Bug introduced by Roan Kattouw
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_CANONICAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
555
		$serverUrl = $wgCanonicalServer;
556
	} elseif ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
0 ignored issues
show
Unused Code Bug introduced by Yuri Astrakhan
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_INTERNAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
557
		// Make $wgInternalServer fall back to $wgServer if not set
558
		$serverUrl = $wgInternalServer;
559
	} else {
560
		$serverUrl = $wgServer;
561
		if ( $defaultProto === PROTO_CURRENT ) {
562
			$defaultProto = $wgRequest->getProtocol() . '://';
563
		}
564
	}
565
566
	// Analyze $serverUrl to obtain its protocol
567
	$bits = wfParseUrl( $serverUrl );
568
	$serverHasProto = $bits && $bits['scheme'] != '';
569
570
	if ( $defaultProto === PROTO_CANONICAL || $defaultProto === PROTO_INTERNAL ) {
0 ignored issues
show
Unused Code Bug introduced by Roan Kattouw
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_CANONICAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
Unused Code Bug introduced by Roan Kattouw
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_INTERNAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
571
		if ( $serverHasProto ) {
572
			$defaultProto = $bits['scheme'] . '://';
573
		} else {
574
			// $wgCanonicalServer or $wgInternalServer doesn't have a protocol.
575
			// This really isn't supposed to happen. Fall back to HTTP in this
576
			// ridiculous case.
577
			$defaultProto = PROTO_HTTP;
578
		}
579
	}
580
581
	$defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 );
582
583
	if ( substr( $url, 0, 2 ) == '//' ) {
584
		$url = $defaultProtoWithoutSlashes . $url;
585
	} elseif ( substr( $url, 0, 1 ) == '/' ) {
586
		// If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes,
587
		// otherwise leave it alone.
588
		$url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
589
	}
590
591
	$bits = wfParseUrl( $url );
592
593
	// ensure proper port for HTTPS arrives in URL
594
	// https://phabricator.wikimedia.org/T67184
595
	if ( $defaultProto === PROTO_HTTPS && $wgHttpsPort != 443 ) {
596
		$bits['port'] = $wgHttpsPort;
597
	}
598
599
	if ( $bits && isset( $bits['path'] ) ) {
600
		$bits['path'] = wfRemoveDotSegments( $bits['path'] );
601
		return wfAssembleUrl( $bits );
602
	} elseif ( $bits ) {
603
		# No path to expand
604
		return $url;
605
	} elseif ( substr( $url, 0, 1 ) != '/' ) {
606
		# URL is a relative path
607
		return wfRemoveDotSegments( $url );
608
	}
609
610
	# Expanded URL is not valid.
611
	return false;
612
}
613
614
/**
615
 * This function will reassemble a URL parsed with wfParseURL.  This is useful
616
 * if you need to edit part of a URL and put it back together.
617
 *
618
 * This is the basic structure used (brackets contain keys for $urlParts):
619
 * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
620
 *
621
 * @todo Need to integrate this into wfExpandUrl (see T34168)
622
 *
623
 * @since 1.19
624
 * @param array $urlParts URL parts, as output from wfParseUrl
625
 * @return string URL assembled from its component parts
626
 */
627
function wfAssembleUrl( $urlParts ) {
628
	$result = '';
629
630
	if ( isset( $urlParts['delimiter'] ) ) {
631
		if ( isset( $urlParts['scheme'] ) ) {
632
			$result .= $urlParts['scheme'];
633
		}
634
635
		$result .= $urlParts['delimiter'];
636
	}
637
638
	if ( isset( $urlParts['host'] ) ) {
639
		if ( isset( $urlParts['user'] ) ) {
640
			$result .= $urlParts['user'];
641
			if ( isset( $urlParts['pass'] ) ) {
642
				$result .= ':' . $urlParts['pass'];
643
			}
644
			$result .= '@';
645
		}
646
647
		$result .= $urlParts['host'];
648
649
		if ( isset( $urlParts['port'] ) ) {
650
			$result .= ':' . $urlParts['port'];
651
		}
652
	}
653
654
	if ( isset( $urlParts['path'] ) ) {
655
		$result .= $urlParts['path'];
656
	}
657
658
	if ( isset( $urlParts['query'] ) ) {
659
		$result .= '?' . $urlParts['query'];
660
	}
661
662
	if ( isset( $urlParts['fragment'] ) ) {
663
		$result .= '#' . $urlParts['fragment'];
664
	}
665
666
	return $result;
667
}
668
669
/**
670
 * Remove all dot-segments in the provided URL path.  For example,
671
 * '/a/./b/../c/' becomes '/a/c/'.  For details on the algorithm, please see
672
 * RFC3986 section 5.2.4.
673
 *
674
 * @todo Need to integrate this into wfExpandUrl (see T34168)
675
 *
676
 * @param string $urlPath URL path, potentially containing dot-segments
677
 * @return string URL path with all dot-segments removed
678
 */
679
function wfRemoveDotSegments( $urlPath ) {
680
	$output = '';
681
	$inputOffset = 0;
682
	$inputLength = strlen( $urlPath );
683
684
	while ( $inputOffset < $inputLength ) {
685
		$prefixLengthOne = substr( $urlPath, $inputOffset, 1 );
686
		$prefixLengthTwo = substr( $urlPath, $inputOffset, 2 );
687
		$prefixLengthThree = substr( $urlPath, $inputOffset, 3 );
688
		$prefixLengthFour = substr( $urlPath, $inputOffset, 4 );
689
		$trimOutput = false;
690
691
		if ( $prefixLengthTwo == './' ) {
692
			# Step A, remove leading "./"
693
			$inputOffset += 2;
694
		} elseif ( $prefixLengthThree == '../' ) {
695
			# Step A, remove leading "../"
696
			$inputOffset += 3;
697
		} elseif ( ( $prefixLengthTwo == '/.' ) && ( $inputOffset + 2 == $inputLength ) ) {
698
			# Step B, replace leading "/.$" with "/"
699
			$inputOffset += 1;
700
			$urlPath[$inputOffset] = '/';
701
		} elseif ( $prefixLengthThree == '/./' ) {
702
			# Step B, replace leading "/./" with "/"
703
			$inputOffset += 2;
704
		} elseif ( $prefixLengthThree == '/..' && ( $inputOffset + 3 == $inputLength ) ) {
705
			# Step C, replace leading "/..$" with "/" and
706
			# remove last path component in output
707
			$inputOffset += 2;
708
			$urlPath[$inputOffset] = '/';
709
			$trimOutput = true;
710
		} elseif ( $prefixLengthFour == '/../' ) {
711
			# Step C, replace leading "/../" with "/" and
712
			# remove last path component in output
713
			$inputOffset += 3;
714
			$trimOutput = true;
715
		} elseif ( ( $prefixLengthOne == '.' ) && ( $inputOffset + 1 == $inputLength ) ) {
716
			# Step D, remove "^.$"
717
			$inputOffset += 1;
718
		} elseif ( ( $prefixLengthTwo == '..' ) && ( $inputOffset + 2 == $inputLength ) ) {
719
			# Step D, remove "^..$"
720
			$inputOffset += 2;
721
		} else {
722
			# Step E, move leading path segment to output
723
			if ( $prefixLengthOne == '/' ) {
724
				$slashPos = strpos( $urlPath, '/', $inputOffset + 1 );
725
			} else {
726
				$slashPos = strpos( $urlPath, '/', $inputOffset );
727
			}
728
			if ( $slashPos === false ) {
729
				$output .= substr( $urlPath, $inputOffset );
730
				$inputOffset = $inputLength;
731
			} else {
732
				$output .= substr( $urlPath, $inputOffset, $slashPos - $inputOffset );
733
				$inputOffset += $slashPos - $inputOffset;
734
			}
735
		}
736
737
		if ( $trimOutput ) {
738
			$slashPos = strrpos( $output, '/' );
739
			if ( $slashPos === false ) {
740
				$output = '';
741
			} else {
742
				$output = substr( $output, 0, $slashPos );
743
			}
744
		}
745
	}
746
747
	return $output;
748
}
749
750
/**
751
 * Returns a regular expression of url protocols
752
 *
753
 * @param bool $includeProtocolRelative If false, remove '//' from the returned protocol list.
754
 *        DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
755
 * @return string
756
 */
757
function wfUrlProtocols( $includeProtocolRelative = true ) {
758
	global $wgUrlProtocols;
759
760
	// Cache return values separately based on $includeProtocolRelative
761
	static $withProtRel = null, $withoutProtRel = null;
762
	$cachedValue = $includeProtocolRelative ? $withProtRel : $withoutProtRel;
763
	if ( !is_null( $cachedValue ) ) {
764
		return $cachedValue;
765
	}
766
767
	// Support old-style $wgUrlProtocols strings, for backwards compatibility
768
	// with LocalSettings files from 1.5
769
	if ( is_array( $wgUrlProtocols ) ) {
770
		$protocols = [];
771
		foreach ( $wgUrlProtocols as $protocol ) {
772
			// Filter out '//' if !$includeProtocolRelative
773
			if ( $includeProtocolRelative || $protocol !== '//' ) {
774
				$protocols[] = preg_quote( $protocol, '/' );
775
			}
776
		}
777
778
		$retval = implode( '|', $protocols );
779
	} else {
780
		// Ignore $includeProtocolRelative in this case
781
		// This case exists for pre-1.6 compatibility, and we can safely assume
782
		// that '//' won't appear in a pre-1.6 config because protocol-relative
783
		// URLs weren't supported until 1.18
784
		$retval = $wgUrlProtocols;
785
	}
786
787
	// Cache return value
788
	if ( $includeProtocolRelative ) {
789
		$withProtRel = $retval;
790
	} else {
791
		$withoutProtRel = $retval;
792
	}
793
	return $retval;
794
}
795
796
/**
797
 * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
798
 * you need a regex that matches all URL protocols but does not match protocol-
799
 * relative URLs
800
 * @return string
801
 */
802
function wfUrlProtocolsWithoutProtRel() {
803
	return wfUrlProtocols( false );
804
}
805
806
/**
807
 * parse_url() work-alike, but non-broken.  Differences:
808
 *
809
 * 1) Does not raise warnings on bad URLs (just returns false).
810
 * 2) Handles protocols that don't use :// (e.g., mailto: and news:, as well as
811
 *    protocol-relative URLs) correctly.
812
 * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2)).
813
 *
814
 * @param string $url A URL to parse
815
 * @return string[]|bool Bits of the URL in an associative array, per PHP docs, false on failure
816
 */
817
function wfParseUrl( $url ) {
818
	global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
819
820
	// Protocol-relative URLs are handled really badly by parse_url(). It's so
821
	// bad that the easiest way to handle them is to just prepend 'http:' and
822
	// strip the protocol out later.
823
	$wasRelative = substr( $url, 0, 2 ) == '//';
824
	if ( $wasRelative ) {
825
		$url = "http:$url";
826
	}
827
	MediaWiki\suppressWarnings();
828
	$bits = parse_url( $url );
829
	MediaWiki\restoreWarnings();
830
	// parse_url() returns an array without scheme for some invalid URLs, e.g.
831
	// parse_url("%0Ahttp://example.com") == [ 'host' => '%0Ahttp', 'path' => 'example.com' ]
832
	if ( !$bits || !isset( $bits['scheme'] ) ) {
833
		return false;
834
	}
835
836
	// parse_url() incorrectly handles schemes case-sensitively. Convert it to lowercase.
837
	$bits['scheme'] = strtolower( $bits['scheme'] );
838
839
	// most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
840
	if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
841
		$bits['delimiter'] = '://';
842
	} elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
843
		$bits['delimiter'] = ':';
844
		// parse_url detects for news: and mailto: the host part of an url as path
845
		// We have to correct this wrong detection
846
		if ( isset( $bits['path'] ) ) {
847
			$bits['host'] = $bits['path'];
848
			$bits['path'] = '';
849
		}
850
	} else {
851
		return false;
852
	}
853
854
	/* Provide an empty host for eg. file:/// urls (see T30627) */
855
	if ( !isset( $bits['host'] ) ) {
856
		$bits['host'] = '';
857
858
		// See T47069
859
		if ( isset( $bits['path'] ) ) {
860
			/* parse_url loses the third / for file:///c:/ urls (but not on variants) */
861
			if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
862
				$bits['path'] = '/' . $bits['path'];
863
			}
864
		} else {
865
			$bits['path'] = '';
866
		}
867
	}
868
869
	// If the URL was protocol-relative, fix scheme and delimiter
870
	if ( $wasRelative ) {
871
		$bits['scheme'] = '';
872
		$bits['delimiter'] = '//';
873
	}
874
	return $bits;
875
}
876
877
/**
878
 * Take a URL, make sure it's expanded to fully qualified, and replace any
879
 * encoded non-ASCII Unicode characters with their UTF-8 original forms
880
 * for more compact display and legibility for local audiences.
881
 *
882
 * @todo handle punycode domains too
883
 *
884
 * @param string $url
885
 * @return string
886
 */
887
function wfExpandIRI( $url ) {
888
	return preg_replace_callback(
889
		'/((?:%[89A-F][0-9A-F])+)/i',
890
		'wfExpandIRI_callback',
891
		wfExpandUrl( $url )
892
	);
893
}
894
895
/**
896
 * Private callback for wfExpandIRI
897
 * @param array $matches
898
 * @return string
899
 */
900
function wfExpandIRI_callback( $matches ) {
901
	return urldecode( $matches[1] );
902
}
903
904
/**
905
 * Make URL indexes, appropriate for the el_index field of externallinks.
906
 *
907
 * @param string $url
908
 * @return array
909
 */
910
function wfMakeUrlIndexes( $url ) {
911
	$bits = wfParseUrl( $url );
912
913
	// Reverse the labels in the hostname, convert to lower case
914
	// For emails reverse domainpart only
915
	if ( $bits['scheme'] == 'mailto' ) {
916
		$mailparts = explode( '@', $bits['host'], 2 );
917
		if ( count( $mailparts ) === 2 ) {
918
			$domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
919
		} else {
920
			// No domain specified, don't mangle it
921
			$domainpart = '';
922
		}
923
		$reversedHost = $domainpart . '@' . $mailparts[0];
924
	} else {
925
		$reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
926
	}
927
	// Add an extra dot to the end
928
	// Why? Is it in wrong place in mailto links?
929
	if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
930
		$reversedHost .= '.';
931
	}
932
	// Reconstruct the pseudo-URL
933
	$prot = $bits['scheme'];
934
	$index = $prot . $bits['delimiter'] . $reversedHost;
935
	// Leave out user and password. Add the port, path, query and fragment
936
	if ( isset( $bits['port'] ) ) {
937
		$index .= ':' . $bits['port'];
938
	}
939
	if ( isset( $bits['path'] ) ) {
940
		$index .= $bits['path'];
941
	} else {
942
		$index .= '/';
943
	}
944
	if ( isset( $bits['query'] ) ) {
945
		$index .= '?' . $bits['query'];
946
	}
947
	if ( isset( $bits['fragment'] ) ) {
948
		$index .= '#' . $bits['fragment'];
949
	}
950
951
	if ( $prot == '' ) {
952
		return [ "http:$index", "https:$index" ];
953
	} else {
954
		return [ $index ];
955
	}
956
}
957
958
/**
959
 * Check whether a given URL has a domain that occurs in a given set of domains
960
 * @param string $url URL
961
 * @param array $domains Array of domains (strings)
962
 * @return bool True if the host part of $url ends in one of the strings in $domains
963
 */
964
function wfMatchesDomainList( $url, $domains ) {
965
	$bits = wfParseUrl( $url );
966
	if ( is_array( $bits ) && isset( $bits['host'] ) ) {
967
		$host = '.' . $bits['host'];
968
		foreach ( (array)$domains as $domain ) {
969
			$domain = '.' . $domain;
970
			if ( substr( $host, -strlen( $domain ) ) === $domain ) {
971
				return true;
972
			}
973
		}
974
	}
975
	return false;
976
}
977
978
/**
979
 * Sends a line to the debug log if enabled or, optionally, to a comment in output.
980
 * In normal operation this is a NOP.
981
 *
982
 * Controlling globals:
983
 * $wgDebugLogFile - points to the log file
984
 * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
985
 * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
986
 *
987
 * @since 1.25 support for additional context data
988
 *
989
 * @param string $text
990
 * @param string|bool $dest Destination of the message:
991
 *     - 'all': both to the log and HTML (debug toolbar or HTML comments)
992
 *     - 'private': excluded from HTML output
993
 *   For backward compatibility, it can also take a boolean:
994
 *     - true: same as 'all'
995
 *     - false: same as 'private'
996
 * @param array $context Additional logging context data
997
 */
998
function wfDebug( $text, $dest = 'all', array $context = [] ) {
999
	global $wgDebugRawPage, $wgDebugLogPrefix;
1000
	global $wgDebugTimestamps, $wgRequestTime;
1001
1002
	if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
1003
		return;
1004
	}
1005
1006
	$text = trim( $text );
1007
1008
	if ( $wgDebugTimestamps ) {
1009
		$context['seconds_elapsed'] = sprintf(
1010
			'%6.4f',
1011
			microtime( true ) - $wgRequestTime
1012
		);
1013
		$context['memory_used'] = sprintf(
1014
			'%5.1fM',
1015
			( memory_get_usage( true ) / ( 1024 * 1024 ) )
1016
		);
1017
	}
1018
1019
	if ( $wgDebugLogPrefix !== '' ) {
1020
		$context['prefix'] = $wgDebugLogPrefix;
1021
	}
1022
	$context['private'] = ( $dest === false || $dest === 'private' );
1023
1024
	$logger = LoggerFactory::getInstance( 'wfDebug' );
1025
	$logger->debug( $text, $context );
1026
}
1027
1028
/**
1029
 * Returns true if debug logging should be suppressed if $wgDebugRawPage = false
1030
 * @return bool
1031
 */
1032
function wfIsDebugRawPage() {
0 ignored issues
show
Coding Style introduced by Tim Starling
wfIsDebugRawPage uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by Tim Starling
wfIsDebugRawPage uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1033
	static $cache;
1034
	if ( $cache !== null ) {
1035
		return $cache;
1036
	}
1037
	# Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
1038
	if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
1039
		|| (
1040
			isset( $_SERVER['SCRIPT_NAME'] )
1041
			&& substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
1042
		)
1043
	) {
1044
		$cache = true;
1045
	} else {
1046
		$cache = false;
1047
	}
1048
	return $cache;
1049
}
1050
1051
/**
1052
 * Send a line giving PHP memory usage.
1053
 *
1054
 * @param bool $exact Print exact byte values instead of kibibytes (default: false)
1055
 */
1056
function wfDebugMem( $exact = false ) {
1057
	$mem = memory_get_usage();
1058
	if ( !$exact ) {
1059
		$mem = floor( $mem / 1024 ) . ' KiB';
1060
	} else {
1061
		$mem .= ' B';
1062
	}
1063
	wfDebug( "Memory usage: $mem\n" );
1064
}
1065
1066
/**
1067
 * Send a line to a supplementary debug log file, if configured, or main debug
1068
 * log if not.
1069
 *
1070
 * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to
1071
 * a string filename or an associative array mapping 'destination' to the
1072
 * desired filename. The associative array may also contain a 'sample' key
1073
 * with an integer value, specifying a sampling factor. Sampled log events
1074
 * will be emitted with a 1 in N random chance.
1075
 *
1076
 * @since 1.23 support for sampling log messages via $wgDebugLogGroups.
1077
 * @since 1.25 support for additional context data
1078
 * @since 1.25 sample behavior dependent on configured $wgMWLoggerDefaultSpi
1079
 *
1080
 * @param string $logGroup
1081
 * @param string $text
1082
 * @param string|bool $dest Destination of the message:
1083
 *     - 'all': both to the log and HTML (debug toolbar or HTML comments)
1084
 *     - 'private': only to the specific log if set in $wgDebugLogGroups and
1085
 *       discarded otherwise
1086
 *   For backward compatibility, it can also take a boolean:
1087
 *     - true: same as 'all'
1088
 *     - false: same as 'private'
1089
 * @param array $context Additional logging context data