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

GlobalFunctions.php ➔ wfGetMainCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
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
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
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
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
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
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
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
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
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
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
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
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
1090
 */
1091
function wfDebugLog(
1092
	$logGroup, $text, $dest = 'all', array $context = []
1093
) {
1094
	$text = trim( $text );
1095
1096
	$logger = LoggerFactory::getInstance( $logGroup );
1097
	$context['private'] = ( $dest === false || $dest === 'private' );
1098
	$logger->info( $text, $context );
1099
}
1100
1101
/**
1102
 * Log for database errors
1103
 *
1104
 * @since 1.25 support for additional context data
1105
 *
1106
 * @param string $text Database error message.
1107
 * @param array $context Additional logging context data
1108
 */
1109
function wfLogDBError( $text, array $context = [] ) {
1110
	$logger = LoggerFactory::getInstance( 'wfLogDBError' );
1111
	$logger->error( trim( $text ), $context );
1112
}
1113
1114
/**
1115
 * Throws a warning that $function is deprecated
1116
 *
1117
 * @param string $function
1118
 * @param string|bool $version Version of MediaWiki that the function
1119
 *    was deprecated in (Added in 1.19).
1120
 * @param string|bool $component Added in 1.19.
1121
 * @param int $callerOffset How far up the call stack is the original
1122
 *    caller. 2 = function that called the function that called
1123
 *    wfDeprecated (Added in 1.20)
1124
 *
1125
 * @return null
1126
 */
1127
function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
1128
	MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
1129
}
1130
1131
/**
1132
 * Send a warning either to the debug log or in a PHP error depending on
1133
 * $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead.
1134
 *
1135
 * @param string $msg Message to send
1136
 * @param int $callerOffset Number of items to go back in the backtrace to
1137
 *        find the correct caller (1 = function calling wfWarn, ...)
1138
 * @param int $level PHP error level; defaults to E_USER_NOTICE;
1139
 *        only used when $wgDevelopmentWarnings is true
1140
 */
1141
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
1142
	MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' );
1143
}
1144
1145
/**
1146
 * Send a warning as a PHP error and the debug log. This is intended for logging
1147
 * warnings in production. For logging development warnings, use WfWarn instead.
1148
 *
1149
 * @param string $msg Message to send
1150
 * @param int $callerOffset Number of items to go back in the backtrace to
1151
 *        find the correct caller (1 = function calling wfLogWarning, ...)
1152
 * @param int $level PHP error level; defaults to E_USER_WARNING
1153
 */
1154
function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
1155
	MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
1156
}
1157
1158
/**
1159
 * Log to a file without getting "file size exceeded" signals.
1160
 *
1161
 * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
1162
 * send lines to the specified port, prefixed by the specified prefix and a space.
1163
 * @since 1.25 support for additional context data
1164
 *
1165
 * @param string $text
1166
 * @param string $file Filename
1167
 * @param array $context Additional logging context data
1168
 * @throws MWException
1169
 * @deprecated since 1.25 Use \MediaWiki\Logger\LegacyLogger::emit or UDPTransport
1170
 */
1171
function wfErrorLog( $text, $file, array $context = [] ) {
1172
	wfDeprecated( __METHOD__, '1.25' );
1173
	$logger = LoggerFactory::getInstance( 'wfErrorLog' );
1174
	$context['destination'] = $file;
1175
	$logger->info( trim( $text ), $context );
1176
}
1177
1178
/**
1179
 * @todo document
1180
 */
1181
function wfLogProfilingData() {
0 ignored issues
show
Coding Style introduced by
wfLogProfilingData 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...
1182
	global $wgDebugLogGroups, $wgDebugRawPage;
1183
1184
	$context = RequestContext::getMain();
1185
	$request = $context->getRequest();
1186
1187
	$profiler = Profiler::instance();
1188
	$profiler->setContext( $context );
1189
	$profiler->logData();
1190
1191
	$config = $context->getConfig();
1192
	if ( $config->get( 'StatsdServer' ) ) {
1193
		try {
1194
			$statsdServer = explode( ':', $config->get( 'StatsdServer' ) );
1195
			$statsdHost = $statsdServer[0];
1196
			$statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125;
1197
			$statsdSender = new SocketSender( $statsdHost, $statsdPort );
1198
			$statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
1199
			$statsdClient->setSamplingRates( $config->get( 'StatsdSamplingRates' ) );
1200
			$statsdClient->send( $context->getStats()->getBuffer() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Liuggio\StatsdClient\Factory\StatsdDataFactory as the method getBuffer() does only exist in the following sub-classes of Liuggio\StatsdClient\Factory\StatsdDataFactory: BufferingStatsdDataFactory. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1201
		} catch ( Exception $ex ) {
1202
			MWExceptionHandler::logException( $ex );
1203
		}
1204
	}
1205
1206
	# Profiling must actually be enabled...
1207
	if ( $profiler instanceof ProfilerStub ) {
1208
		return;
1209
	}
1210
1211
	if ( isset( $wgDebugLogGroups['profileoutput'] )
1212
		&& $wgDebugLogGroups['profileoutput'] === false
1213
	) {
1214
		// Explicitly disabled
1215
		return;
1216
	}
1217
	if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
1218
		return;
1219
	}
1220
1221
	$ctx = [ 'elapsed' => $request->getElapsedTime() ];
1222
	if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1223
		$ctx['forwarded_for'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
1224
	}
1225
	if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
1226
		$ctx['client_ip'] = $_SERVER['HTTP_CLIENT_IP'];
1227
	}
1228
	if ( !empty( $_SERVER['HTTP_FROM'] ) ) {
1229
		$ctx['from'] = $_SERVER['HTTP_FROM'];
1230
	}
1231
	if ( isset( $ctx['forwarded_for'] ) ||
1232
		isset( $ctx['client_ip'] ) ||
1233
		isset( $ctx['from'] ) ) {
1234
		$ctx['proxy'] = $_SERVER['REMOTE_ADDR'];
1235
	}
1236
1237
	// Don't load $wgUser at this late stage just for statistics purposes
1238
	// @todo FIXME: We can detect some anons even if it is not loaded.
1239
	// See User::getId()
1240
	$user = $context->getUser();
1241
	$ctx['anon'] = $user->isItemLoaded( 'id' ) && $user->isAnon();
1242
1243
	// Command line script uses a FauxRequest object which does not have
1244
	// any knowledge about an URL and throw an exception instead.
1245
	try {
1246
		$ctx['url'] = urldecode( $request->getRequestURL() );
1247
	} catch ( Exception $ignored ) {
1248
		// no-op
1249
	}
1250
1251
	$ctx['output'] = $profiler->getOutput();
1252
1253
	$log = LoggerFactory::getInstance( 'profileoutput' );
1254
	$log->info( "Elapsed: {elapsed}; URL: <{url}>\n{output}", $ctx );
1255
}
1256
1257
/**
1258
 * Increment a statistics counter
1259
 *
1260
 * @param string $key
1261
 * @param int $count
1262
 * @return void
1263
 */
1264
function wfIncrStats( $key, $count = 1 ) {
1265
	$stats = RequestContext::getMain()->getStats();
1266
	$stats->updateCount( $key, $count );
1267
}
1268
1269
/**
1270
 * Check whether the wiki is in read-only mode.
1271
 *
1272
 * @return bool
1273
 */
1274
function wfReadOnly() {
1275
	return wfReadOnlyReason() !== false;
1276
}
1277
1278
/**
1279
 * Check if the site is in read-only mode and return the message if so
1280
 *
1281
 * This checks wfConfiguredReadOnlyReason() and the main load balancer
1282
 * for replica DB lag. This may result in DB connection being made.
1283
 *
1284
 * @return string|bool String when in read-only mode; false otherwise
1285
 */
1286
function wfReadOnlyReason() {
1287
	$readOnly = wfConfiguredReadOnlyReason();
1288
	if ( $readOnly !== false ) {
1289
		return $readOnly;
1290
	}
1291
1292
	static $lbReadOnly = null;
1293
	if ( $lbReadOnly === null ) {
1294
		// Callers use this method to be aware that data presented to a user
1295
		// may be very stale and thus allowing submissions can be problematic.
1296
		$lbReadOnly = wfGetLB()->getReadOnlyReason();
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
1297
	}
1298
1299
	return $lbReadOnly;
1300
}
1301
1302
/**
1303
 * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
1304
 *
1305
 * @return string|bool String when in read-only mode; false otherwise
1306
 * @since 1.27
1307
 */
1308
function wfConfiguredReadOnlyReason() {
1309
	global $wgReadOnly, $wgReadOnlyFile;
1310
1311
	if ( $wgReadOnly === null ) {
1312
		// Set $wgReadOnly for faster access next time
1313
		if ( is_file( $wgReadOnlyFile ) && filesize( $wgReadOnlyFile ) > 0 ) {
1314
			$wgReadOnly = file_get_contents( $wgReadOnlyFile );
1315
		} else {
1316
			$wgReadOnly = false;
1317
		}
1318
	}
1319
1320
	return $wgReadOnly;
1321
}
1322
1323
/**
1324
 * Return a Language object from $langcode
1325
 *
1326
 * @param Language|string|bool $langcode Either:
1327
 *                  - a Language object
1328
 *                  - code of the language to get the message for, if it is
1329
 *                    a valid code create a language for that language, if
1330
 *                    it is a string but not a valid code then make a basic
1331
 *                    language object
1332
 *                  - a boolean: if it's false then use the global object for
1333
 *                    the current user's language (as a fallback for the old parameter
1334
 *                    functionality), or if it is true then use global object
1335
 *                    for the wiki's content language.
1336
 * @return Language
1337
 */
1338
function wfGetLangObj( $langcode = false ) {
1339
	# Identify which language to get or create a language object for.
1340
	# Using is_object here due to Stub objects.
1341
	if ( is_object( $langcode ) ) {
1342
		# Great, we already have the object (hopefully)!
1343
		return $langcode;
1344
	}
1345
1346
	global $wgContLang, $wgLanguageCode;
1347
	if ( $langcode === true || $langcode === $wgLanguageCode ) {
1348
		# $langcode is the language code of the wikis content language object.
1349
		# or it is a boolean and value is true
1350
		return $wgContLang;
1351
	}
1352
1353
	global $wgLang;
1354
	if ( $langcode === false || $langcode === $wgLang->getCode() ) {
1355
		# $langcode is the language code of user language object.
1356
		# or it was a boolean and value is false
1357
		return $wgLang;
1358
	}
1359
1360
	$validCodes = array_keys( Language::fetchLanguageNames() );
1361
	if ( in_array( $langcode, $validCodes ) ) {
1362
		# $langcode corresponds to a valid language.
1363
		return Language::factory( $langcode );
0 ignored issues
show
Bug introduced by
It seems like $langcode defined by parameter $langcode on line 1338 can also be of type boolean; however, Language::factory() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1364
	}
1365
1366
	# $langcode is a string, but not a valid language code; use content language.
1367
	wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
1368
	return $wgContLang;
1369
}
1370
1371
/**
1372
 * This is the function for getting translated interface messages.
1373
 *
1374
 * @see Message class for documentation how to use them.
1375
 * @see https://www.mediawiki.org/wiki/Manual:Messages_API
1376
 *
1377
 * This function replaces all old wfMsg* functions.
1378
 *
1379
 * @param string|string[]|MessageSpecifier $key Message key, or array of keys, or a MessageSpecifier
1380
 * @param mixed $params,... Normal message parameters
0 ignored issues
show
Bug introduced by
There is no parameter named $params,.... 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...
1381
 * @return Message
1382
 *
1383
 * @since 1.17
1384
 *
1385
 * @see Message::__construct
1386
 */
1387
function wfMessage( $key /*...*/ ) {
1388
	$params = func_get_args();
1389
	array_shift( $params );
1390
	if ( isset( $params[0] ) && is_array( $params[0] ) ) {
1391
		$params = $params[0];
1392
	}
1393
	return new Message( $key, $params );
1394
}
1395
1396
/**
1397
 * This function accepts multiple message keys and returns a message instance
1398
 * for the first message which is non-empty. If all messages are empty then an
1399
 * instance of the first message key is returned.
1400
 *
1401
 * @param string|string[] $keys,... Message keys
0 ignored issues
show
Bug introduced by
There is no parameter named $keys,.... 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...
1402
 * @return Message
1403
 *
1404
 * @since 1.18
1405
 *
1406
 * @see Message::newFallbackSequence
1407
 */
1408
function wfMessageFallback( /*...*/ ) {
1409
	$args = func_get_args();
1410
	return call_user_func_array( 'Message::newFallbackSequence', $args );
1411
}
1412
1413
/**
1414
 * Replace message parameter keys on the given formatted output.
1415
 *
1416
 * @param string $message
1417
 * @param array $args
1418
 * @return string
1419
 * @private
1420
 */
1421
function wfMsgReplaceArgs( $message, $args ) {
1422
	# Fix windows line-endings
1423
	# Some messages are split with explode("\n", $msg)
1424
	$message = str_replace( "\r", '', $message );
1425
1426
	// Replace arguments
1427
	if ( is_array( $args ) && $args ) {
1428
		if ( is_array( $args[0] ) ) {
1429
			$args = array_values( $args[0] );
1430
		}
1431
		$replacementKeys = [];
1432
		foreach ( $args as $n => $param ) {
1433
			$replacementKeys['$' . ( $n + 1 )] = $param;
1434
		}
1435
		$message = strtr( $message, $replacementKeys );
1436
	}
1437
1438
	return $message;
1439
}
1440
1441
/**
1442
 * Fetch server name for use in error reporting etc.
1443
 * Use real server name if available, so we know which machine
1444
 * in a server farm generated the current page.
1445
 *
1446
 * @return string
1447
 */
1448
function wfHostname() {
0 ignored issues
show
Coding Style introduced by
wfHostname 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...
1449
	static $host;
1450
	if ( is_null( $host ) ) {
1451
1452
		# Hostname overriding
1453
		global $wgOverrideHostname;
1454
		if ( $wgOverrideHostname !== false ) {
1455
			# Set static and skip any detection
1456
			$host = $wgOverrideHostname;
1457
			return $host;
1458
		}
1459
1460
		if ( function_exists( 'posix_uname' ) ) {
1461
			// This function not present on Windows
1462
			$uname = posix_uname();
1463
		} else {
1464
			$uname = false;
1465
		}
1466
		if ( is_array( $uname ) && isset( $uname['nodename'] ) ) {
1467
			$host = $uname['nodename'];
1468
		} elseif ( getenv( 'COMPUTERNAME' ) ) {
1469
			# Windows computer name
1470
			$host = getenv( 'COMPUTERNAME' );
1471
		} else {
1472
			# This may be a virtual server.
1473
			$host = $_SERVER['SERVER_NAME'];
1474
		}
1475
	}
1476
	return $host;
1477
}
1478
1479
/**
1480
 * Returns a script tag that stores the amount of time it took MediaWiki to
1481
 * handle the request in milliseconds as 'wgBackendResponseTime'.
1482
 *
1483
 * If $wgShowHostnames is true, the script will also set 'wgHostname' to the
1484
 * hostname of the server handling the request.
1485
 *
1486
 * @return string
1487
 */
1488
function wfReportTime() {
1489
	global $wgRequestTime, $wgShowHostnames;
1490
1491
	$responseTime = round( ( microtime( true ) - $wgRequestTime ) * 1000 );
1492
	$reportVars = [ 'wgBackendResponseTime' => $responseTime ];
1493
	if ( $wgShowHostnames ) {
1494
		$reportVars['wgHostname'] = wfHostname();
1495
	}
1496
	return Skin::makeVariablesScript( $reportVars );
1497
}
1498
1499
/**
1500
 * Safety wrapper for debug_backtrace().
1501
 *
1502
 * Will return an empty array if debug_backtrace is disabled, otherwise
1503
 * the output from debug_backtrace() (trimmed).
1504
 *
1505
 * @param int $limit This parameter can be used to limit the number of stack frames returned
1506
 *
1507
 * @return array Array of backtrace information
1508
 */
1509
function wfDebugBacktrace( $limit = 0 ) {
1510
	static $disabled = null;
1511
1512
	if ( is_null( $disabled ) ) {
1513
		$disabled = !function_exists( 'debug_backtrace' );
1514
		if ( $disabled ) {
1515
			wfDebug( "debug_backtrace() is disabled\n" );
1516
		}
1517
	}
1518
	if ( $disabled ) {
1519
		return [];
1520
	}
1521
1522
	if ( $limit ) {
1523
		return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 );
1524
	} else {
1525
		return array_slice( debug_backtrace(), 1 );
1526
	}
1527
}
1528
1529
/**
1530
 * Get a debug backtrace as a string
1531
 *
1532
 * @param bool|null $raw If true, the return value is plain text. If false, HTML.
1533
 *   Defaults to $wgCommandLineMode if unset.
1534
 * @return string
1535
 * @since 1.25 Supports $raw parameter.
1536
 */
1537
function wfBacktrace( $raw = null ) {
1538
	global $wgCommandLineMode;
1539
1540
	if ( $raw === null ) {
1541
		$raw = $wgCommandLineMode;
1542
	}
1543
1544
	if ( $raw ) {
1545
		$frameFormat = "%s line %s calls %s()\n";
1546
		$traceFormat = "%s";
1547
	} else {
1548
		$frameFormat = "<li>%s line %s calls %s()</li>\n";
1549
		$traceFormat = "<ul>\n%s</ul>\n";
1550
	}
1551
1552
	$frames = array_map( function ( $frame ) use ( $frameFormat ) {
1553
		$file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-';
1554
		$line = isset( $frame['line'] ) ? $frame['line'] : '-';
1555
		$call = $frame['function'];
1556
		if ( !empty( $frame['class'] ) ) {
1557
			$call = $frame['class'] . $frame['type'] . $call;
1558
		}
1559
		return sprintf( $frameFormat, $file, $line, $call );
1560
	}, wfDebugBacktrace() );
1561
1562
	return sprintf( $traceFormat, implode( '', $frames ) );
1563
}
1564
1565
/**
1566
 * Get the name of the function which called this function
1567
 * wfGetCaller( 1 ) is the function with the wfGetCaller() call (ie. __FUNCTION__)
1568
 * wfGetCaller( 2 ) [default] is the caller of the function running wfGetCaller()
1569
 * wfGetCaller( 3 ) is the parent of that.
1570
 *
1571
 * @param int $level
1572
 * @return string
1573
 */
1574
function wfGetCaller( $level = 2 ) {
1575
	$backtrace = wfDebugBacktrace( $level + 1 );
1576
	if ( isset( $backtrace[$level] ) ) {
1577
		return wfFormatStackFrame( $backtrace[$level] );
1578
	} else {
1579
		return 'unknown';
1580
	}
1581
}
1582
1583
/**
1584
 * Return a string consisting of callers in the stack. Useful sometimes
1585
 * for profiling specific points.
1586
 *
1587
 * @param int $limit The maximum depth of the stack frame to return, or false for the entire stack.
1588
 * @return string
1589
 */
1590
function wfGetAllCallers( $limit = 3 ) {
1591
	$trace = array_reverse( wfDebugBacktrace() );
1592
	if ( !$limit || $limit > count( $trace ) - 1 ) {
1593
		$limit = count( $trace ) - 1;
1594
	}
1595
	$trace = array_slice( $trace, -$limit - 1, $limit );
1596
	return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
1597
}
1598
1599
/**
1600
 * Return a string representation of frame
1601
 *
1602
 * @param array $frame
1603
 * @return string
1604
 */
1605
function wfFormatStackFrame( $frame ) {
1606
	if ( !isset( $frame['function'] ) ) {
1607
		return 'NO_FUNCTION_GIVEN';
1608
	}
1609
	return isset( $frame['class'] ) && isset( $frame['type'] ) ?
1610
		$frame['class'] . $frame['type'] . $frame['function'] :
1611
		$frame['function'];
1612
}
1613
1614
/* Some generic result counters, pulled out of SearchEngine */
1615
1616
/**
1617
 * @todo document
1618
 *
1619
 * @param int $offset
1620
 * @param int $limit
1621
 * @return string
1622
 */
1623
function wfShowingResults( $offset, $limit ) {
1624
	return wfMessage( 'showingresults' )->numParams( $limit, $offset + 1 )->parse();
1625
}
1626
1627
/**
1628
 * Whether the client accept gzip encoding
1629
 *
1630
 * Uses the Accept-Encoding header to check if the client supports gzip encoding.
1631
 * Use this when considering to send a gzip-encoded response to the client.
1632
 *
1633
 * @param bool $force Forces another check even if we already have a cached result.
1634
 * @return bool
1635
 */
1636
function wfClientAcceptsGzip( $force = false ) {
0 ignored issues
show
Coding Style introduced by
wfClientAcceptsGzip 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...
1637
	static $result = null;
1638
	if ( $result === null || $force ) {
1639
		$result = false;
1640
		if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
1641
			# @todo FIXME: We may want to blacklist some broken browsers
1642
			$m = [];
1643
			if ( preg_match(
1644
					'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
1645
					$_SERVER['HTTP_ACCEPT_ENCODING'],
1646
					$m
1647
				)
1648
			) {
1649
				if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
1650
					$result = false;
1651
					return $result;
1652
				}
1653
				wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
1654
				$result = true;
1655
			}
1656
		}
1657
	}
1658
	return $result;
1659
}
1660
1661
/**
1662
 * Escapes the given text so that it may be output using addWikiText()
1663
 * without any linking, formatting, etc. making its way through. This
1664
 * is achieved by substituting certain characters with HTML entities.
1665
 * As required by the callers, "<nowiki>" is not used.
1666
 *
1667
 * @param string $text Text to be escaped
1668
 * @return string
1669
 */
1670
function wfEscapeWikiText( $text ) {
1671
	global $wgEnableMagicLinks;
1672
	static $repl = null, $repl2 = null;
1673
	if ( $repl === null || defined( 'MW_PARSER_TEST' ) || defined( 'MW_PHPUNIT_TEST' ) ) {
1674
		// Tests depend upon being able to change $wgEnableMagicLinks, so don't cache
1675
		// in those situations
1676
		$repl = [
1677
			'"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
1678
			'=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
1679
			'{' => '&#123;', '|' => '&#124;', '}' => '&#125;', ';' => '&#59;',
1680
			"\n#" => "\n&#35;", "\r#" => "\r&#35;",
1681
			"\n*" => "\n&#42;", "\r*" => "\r&#42;",
1682
			"\n:" => "\n&#58;", "\r:" => "\r&#58;",
1683
			"\n " => "\n&#32;", "\r " => "\r&#32;",
1684
			"\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
1685
			"\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
1686
			"\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
1687
			"\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
1688
			'__' => '_&#95;', '://' => '&#58;//',
1689
		];
1690
1691
		$magicLinks = array_keys( array_filter( $wgEnableMagicLinks ) );
1692
		// We have to catch everything "\s" matches in PCRE
1693
		foreach ( $magicLinks as $magic ) {
1694
			$repl["$magic "] = "$magic&#32;";
1695
			$repl["$magic\t"] = "$magic&#9;";
1696
			$repl["$magic\r"] = "$magic&#13;";
1697
			$repl["$magic\n"] = "$magic&#10;";
1698
			$repl["$magic\f"] = "$magic&#12;";
1699
		}
1700
1701
		// And handle protocols that don't use "://"
1702
		global $wgUrlProtocols;
1703
		$repl2 = [];
1704
		foreach ( $wgUrlProtocols as $prot ) {
1705
			if ( substr( $prot, -1 ) === ':' ) {
1706
				$repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
1707
			}
1708
		}
1709
		$repl2 = $repl2 ? '/\b(' . implode( '|', $repl2 ) . '):/i' : '/^(?!)/';
1710
	}
1711
	$text = substr( strtr( "\n$text", $repl ), 1 );
0 ignored issues
show
Bug introduced by
It seems like $repl can also be of type null; however, strtr() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1712
	$text = preg_replace( $repl2, '$1&#58;', $text );
1713
	return $text;
1714
}
1715
1716
/**
1717
 * Sets dest to source and returns the original value of dest
1718
 * If source is NULL, it just returns the value, it doesn't set the variable
1719
 * If force is true, it will set the value even if source is NULL
1720
 *
1721
 * @param mixed $dest
1722
 * @param mixed $source
1723
 * @param bool $force
1724
 * @return mixed
1725
 */
1726
function wfSetVar( &$dest, $source, $force = false ) {
1727
	$temp = $dest;
1728
	if ( !is_null( $source ) || $force ) {
1729
		$dest = $source;
1730
	}
1731
	return $temp;
1732
}
1733
1734
/**
1735
 * As for wfSetVar except setting a bit
1736
 *
1737
 * @param int $dest
1738
 * @param int $bit
1739
 * @param bool $state
1740
 *
1741
 * @return bool
1742
 */
1743
function wfSetBit( &$dest, $bit, $state = true ) {
1744
	$temp = (bool)( $dest & $bit );
1745
	if ( !is_null( $state ) ) {
1746
		if ( $state ) {
1747
			$dest |= $bit;
1748
		} else {
1749
			$dest &= ~$bit;
1750
		}
1751
	}
1752
	return $temp;
1753
}
1754
1755
/**
1756
 * A wrapper around the PHP function var_export().
1757
 * Either print it or add it to the regular output ($wgOut).
1758
 *
1759
 * @param mixed $var A PHP variable to dump.
1760
 */
1761
function wfVarDump( $var ) {
1762
	global $wgOut;
1763
	$s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
1764
	if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
1765
		print $s;
1766
	} else {
1767
		$wgOut->addHTML( $s );
1768
	}
1769
}
1770
1771
/**
1772
 * Provide a simple HTTP error.
1773
 *
1774
 * @param int|string $code
1775
 * @param string $label
1776
 * @param string $desc
1777
 */
1778
function wfHttpError( $code, $label, $desc ) {
1779
	global $wgOut;
1780
	HttpStatus::header( $code );
1781
	if ( $wgOut ) {
1782
		$wgOut->disable();
1783
		$wgOut->sendCacheControl();
1784
	}
1785
1786
	header( 'Content-type: text/html; charset=utf-8' );
1787
	print '<!DOCTYPE html>' .
1788
		'<html><head><title>' .
1789
		htmlspecialchars( $label ) .
1790
		'</title></head><body><h1>' .
1791
		htmlspecialchars( $label ) .
1792
		'</h1><p>' .
1793
		nl2br( htmlspecialchars( $desc ) ) .
1794
		"</p></body></html>\n";
1795
}
1796
1797
/**
1798
 * Clear away any user-level output buffers, discarding contents.
1799
 *
1800
 * Suitable for 'starting afresh', for instance when streaming
1801
 * relatively large amounts of data without buffering, or wanting to
1802
 * output image files without ob_gzhandler's compression.
1803
 *
1804
 * The optional $resetGzipEncoding parameter controls suppression of
1805
 * the Content-Encoding header sent by ob_gzhandler; by default it
1806
 * is left. See comments for wfClearOutputBuffers() for why it would
1807
 * be used.
1808
 *
1809
 * Note that some PHP configuration options may add output buffer
1810
 * layers which cannot be removed; these are left in place.
1811
 *
1812
 * @param bool $resetGzipEncoding
1813
 */
1814
function wfResetOutputBuffers( $resetGzipEncoding = true ) {
1815
	if ( $resetGzipEncoding ) {
1816
		// Suppress Content-Encoding and Content-Length
1817
		// headers from 1.10+s wfOutputHandler
1818
		global $wgDisableOutputCompression;
1819
		$wgDisableOutputCompression = true;
1820
	}
1821
	while ( $status = ob_get_status() ) {
1822
		if ( isset( $status['flags'] ) ) {
1823
			$flags = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
1824
			$deleteable = ( $status['flags'] & $flags ) === $flags;
1825
		} elseif ( isset( $status['del'] ) ) {
1826
			$deleteable = $status['del'];
1827
		} else {
1828
			// Guess that any PHP-internal setting can't be removed.
1829
			$deleteable = $status['type'] !== 0; /* PHP_OUTPUT_HANDLER_INTERNAL */
1830
		}
1831
		if ( !$deleteable ) {
1832
			// Give up, and hope the result doesn't break
1833
			// output behavior.
1834
			break;
1835
		}
1836
		if ( $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) {
1837
			// Unit testing barrier to prevent this function from breaking PHPUnit.
1838
			break;
1839
		}
1840
		if ( !ob_end_clean() ) {
1841
			// Could not remove output buffer handler; abort now
1842
			// to avoid getting in some kind of infinite loop.
1843
			break;
1844
		}
1845
		if ( $resetGzipEncoding ) {
1846
			if ( $status['name'] == 'ob_gzhandler' ) {
1847
				// Reset the 'Content-Encoding' field set by this handler
1848
				// so we can start fresh.
1849
				header_remove( 'Content-Encoding' );
1850
				break;
1851
			}
1852
		}
1853
	}
1854
}
1855
1856
/**
1857
 * More legible than passing a 'false' parameter to wfResetOutputBuffers():
1858
 *
1859
 * Clear away output buffers, but keep the Content-Encoding header
1860
 * produced by ob_gzhandler, if any.
1861
 *
1862
 * This should be used for HTTP 304 responses, where you need to
1863
 * preserve the Content-Encoding header of the real result, but
1864
 * also need to suppress the output of ob_gzhandler to keep to spec
1865
 * and avoid breaking Firefox in rare cases where the headers and
1866
 * body are broken over two packets.
1867
 */
1868
function wfClearOutputBuffers() {
1869
	wfResetOutputBuffers( false );
1870
}
1871
1872
/**
1873
 * Converts an Accept-* header into an array mapping string values to quality
1874
 * factors
1875
 *
1876
 * @param string $accept
1877
 * @param string $def Default
1878
 * @return float[] Associative array of string => float pairs
1879
 */
1880
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
1881
	# No arg means accept anything (per HTTP spec)
1882
	if ( !$accept ) {
1883
		return [ $def => 1.0 ];
1884
	}
1885
1886
	$prefs = [];
1887
1888
	$parts = explode( ',', $accept );
1889
1890
	foreach ( $parts as $part ) {
1891
		# @todo FIXME: Doesn't deal with params like 'text/html; level=1'
1892
		$values = explode( ';', trim( $part ) );
1893
		$match = [];
1894
		if ( count( $values ) == 1 ) {
1895
			$prefs[$values[0]] = 1.0;
1896
		} elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
1897
			$prefs[$values[0]] = floatval( $match[1] );
1898
		}
1899
	}
1900
1901
	return $prefs;
1902
}
1903
1904
/**
1905
 * Checks if a given MIME type matches any of the keys in the given
1906
 * array. Basic wildcards are accepted in the array keys.
1907
 *
1908
 * Returns the matching MIME type (or wildcard) if a match, otherwise
1909
 * NULL if no match.
1910
 *
1911
 * @param string $type
1912
 * @param array $avail
1913
 * @return string
1914
 * @private
1915
 */
1916
function mimeTypeMatch( $type, $avail ) {
1917
	if ( array_key_exists( $type, $avail ) ) {
1918
		return $type;
1919
	} else {
1920
		$mainType = explode( '/', $type )[0];
1921
		if ( array_key_exists( "$mainType/*", $avail ) ) {
1922
			return "$mainType/*";
1923
		} elseif ( array_key_exists( '*/*', $avail ) ) {
1924
			return '*/*';
1925
		} else {
1926
			return null;
1927
		}
1928
	}
1929
}
1930
1931
/**
1932
 * Returns the 'best' match between a client's requested internet media types
1933
 * and the server's list of available types. Each list should be an associative
1934
 * array of type to preference (preference is a float between 0.0 and 1.0).
1935
 * Wildcards in the types are acceptable.
1936
 *
1937
 * @param array $cprefs Client's acceptable type list
1938
 * @param array $sprefs Server's offered types
1939
 * @return string
1940
 *
1941
 * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
1942
 * XXX: generalize to negotiate other stuff
1943
 */
1944
function wfNegotiateType( $cprefs, $sprefs ) {
1945
	$combine = [];
1946
1947 View Code Duplication
	foreach ( array_keys( $sprefs ) as $type ) {
1948
		$subType = explode( '/', $type )[1];
1949
		if ( $subType != '*' ) {
1950
			$ckey = mimeTypeMatch( $type, $cprefs );
1951
			if ( $ckey ) {
1952
				$combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1953
			}
1954
		}
1955
	}
1956
1957 View Code Duplication
	foreach ( array_keys( $cprefs ) as $type ) {
1958
		$subType = explode( '/', $type )[1];
1959
		if ( $subType != '*' && !array_key_exists( $type, $sprefs ) ) {
1960
			$skey = mimeTypeMatch( $type, $sprefs );
1961
			if ( $skey ) {
1962
				$combine[$type] = $sprefs[$skey] * $cprefs[$type];
1963
			}
1964
		}
1965
	}
1966
1967
	$bestq = 0;
1968
	$besttype = null;
1969
1970
	foreach ( array_keys( $combine ) as $type ) {
1971
		if ( $combine[$type] > $bestq ) {
1972
			$besttype = $type;
1973
			$bestq = $combine[$type];
1974
		}
1975
	}
1976
1977
	return $besttype;
1978
}
1979
1980
/**
1981
 * Reference-counted warning suppression
1982
 *
1983
 * @deprecated since 1.26, use MediaWiki\suppressWarnings() directly
1984
 * @param bool $end
1985
 */
1986
function wfSuppressWarnings( $end = false ) {
1987
	MediaWiki\suppressWarnings( $end );
1988
}
1989
1990
/**
1991
 * @deprecated since 1.26, use MediaWiki\restoreWarnings() directly
1992
 * Restore error level to previous value
1993
 */
1994
function wfRestoreWarnings() {
1995
	MediaWiki\suppressWarnings( true );
1996
}
1997
1998
# Autodetect, convert and provide timestamps of various types
1999
2000
require_once __DIR__ . '/libs/time/defines.php';
2001
2002
/**
2003
 * Get a timestamp string in one of various formats
2004
 *
2005
 * @param mixed $outputtype A timestamp in one of the supported formats, the
2006
 *   function will autodetect which format is supplied and act accordingly.
2007
 * @param mixed $ts Optional timestamp to convert, default 0 for the current time
2008
 * @return string|bool String / false The same date in the format specified in $outputtype or false
2009
 */
2010
function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
2011
	$ret = MWTimestamp::convert( $outputtype, $ts );
2012
	if ( $ret === false ) {
2013
		wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
2014
	}
2015
	return $ret;
2016
}
2017
2018
/**
2019
 * Return a formatted timestamp, or null if input is null.
2020
 * For dealing with nullable timestamp columns in the database.
2021
 *
2022
 * @param int $outputtype
2023
 * @param string $ts
2024
 * @return string
2025
 */
2026
function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
2027
	if ( is_null( $ts ) ) {
2028
		return null;
2029
	} else {
2030
		return wfTimestamp( $outputtype, $ts );
2031
	}
2032
}
2033
2034
/**
2035
 * Convenience function; returns MediaWiki timestamp for the present time.
2036
 *
2037
 * @return string
2038
 */
2039
function wfTimestampNow() {
2040
	# return NOW
2041
	return MWTimestamp::now( TS_MW );
2042
}
2043
2044
/**
2045
 * Check if the operating system is Windows
2046
 *
2047
 * @return bool True if it's Windows, false otherwise.
2048
 */
2049
function wfIsWindows() {
2050
	static $isWindows = null;
2051
	if ( $isWindows === null ) {
2052
		$isWindows = strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN';
2053
	}
2054
	return $isWindows;
2055
}
2056
2057
/**
2058
 * Check if we are running under HHVM
2059
 *
2060
 * @return bool
2061
 */
2062
function wfIsHHVM() {
2063
	return defined( 'HHVM_VERSION' );
2064
}
2065
2066
/**
2067
 * Tries to get the system directory for temporary files. First
2068
 * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
2069
 * environment variables are then checked in sequence, then
2070
 * sys_get_temp_dir(), then upload_tmp_dir from php.ini.
2071
 *
2072
 * NOTE: When possible, use instead the tmpfile() function to create
2073
 * temporary files to avoid race conditions on file creation, etc.
2074
 *
2075
 * @return string
2076
 */
2077
function wfTempDir() {
2078
	global $wgTmpDirectory;
2079
2080
	if ( $wgTmpDirectory !== false ) {
2081
		return $wgTmpDirectory;
2082
	}
2083
2084
	return TempFSFile::getUsableTempDirectory();
2085
}
2086
2087
/**
2088
 * Make directory, and make all parent directories if they don't exist
2089
 *
2090
 * @param string $dir Full path to directory to create
2091
 * @param int $mode Chmod value to use, default is $wgDirectoryMode
2092
 * @param string $caller Optional caller param for debugging.
2093
 * @throws MWException
2094
 * @return bool
2095
 */
2096
function wfMkdirParents( $dir, $mode = null, $caller = null ) {
2097
	global $wgDirectoryMode;
2098
2099
	if ( FileBackend::isStoragePath( $dir ) ) { // sanity
2100
		throw new MWException( __FUNCTION__ . " given storage path '$dir'." );
2101
	}
2102
2103
	if ( !is_null( $caller ) ) {
2104
		wfDebug( "$caller: called wfMkdirParents($dir)\n" );
2105
	}
2106
2107
	if ( strval( $dir ) === '' || is_dir( $dir ) ) {
2108
		return true;
2109
	}
2110
2111
	$dir = str_replace( [ '\\', '/' ], DIRECTORY_SEPARATOR, $dir );
2112
2113
	if ( is_null( $mode ) ) {
2114
		$mode = $wgDirectoryMode;
2115
	}
2116
2117
	// Turn off the normal warning, we're doing our own below
2118
	MediaWiki\suppressWarnings();
2119
	$ok = mkdir( $dir, $mode, true ); // PHP5 <3
2120
	MediaWiki\restoreWarnings();
2121
2122
	if ( !$ok ) {
2123
		// directory may have been created on another request since we last checked
2124
		if ( is_dir( $dir ) ) {
2125
			return true;
2126
		}
2127
2128
		// PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
2129
		wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) );
2130
	}
2131
	return $ok;
2132
}
2133
2134
/**
2135
 * Remove a directory and all its content.
2136
 * Does not hide error.
2137
 * @param string $dir
2138
 */
2139
function wfRecursiveRemoveDir( $dir ) {
2140
	wfDebug( __FUNCTION__ . "( $dir )\n" );
2141
	// taken from http://de3.php.net/manual/en/function.rmdir.php#98622
2142
	if ( is_dir( $dir ) ) {
2143
		$objects = scandir( $dir );
2144
		foreach ( $objects as $object ) {
2145
			if ( $object != "." && $object != ".." ) {
2146
				if ( filetype( $dir . '/' . $object ) == "dir" ) {
2147
					wfRecursiveRemoveDir( $dir . '/' . $object );
2148
				} else {
2149
					unlink( $dir . '/' . $object );
2150
				}
2151
			}
2152
		}
2153
		reset( $objects );
2154
		rmdir( $dir );
2155
	}
2156
}
2157
2158
/**
2159
 * @param int $nr The number to format
2160
 * @param int $acc The number of digits after the decimal point, default 2
2161
 * @param bool $round Whether or not to round the value, default true
2162
 * @return string
2163
 */
2164
function wfPercent( $nr, $acc = 2, $round = true ) {
2165
	$ret = sprintf( "%.${acc}f", $nr );
2166
	return $round ? round( $ret, $acc ) . '%' : "$ret%";
2167
}
2168
2169
/**
2170
 * Safety wrapper around ini_get() for boolean settings.
2171
 * The values returned from ini_get() are pre-normalized for settings
2172
 * set via php.ini or php_flag/php_admin_flag... but *not*
2173
 * for those set via php_value/php_admin_value.
2174
 *
2175
 * It's fairly common for people to use php_value instead of php_flag,
2176
 * which can leave you with an 'off' setting giving a false positive
2177
 * for code that just takes the ini_get() return value as a boolean.
2178
 *
2179
 * To make things extra interesting, setting via php_value accepts
2180
 * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
2181
 * Unrecognized values go false... again opposite PHP's own coercion
2182
 * from string to bool.
2183
 *
2184
 * Luckily, 'properly' set settings will always come back as '0' or '1',
2185
 * so we only have to worry about them and the 'improper' settings.
2186
 *
2187
 * I frickin' hate PHP... :P
2188
 *
2189
 * @param string $setting
2190
 * @return bool
2191
 */
2192
function wfIniGetBool( $setting ) {
2193
	$val = strtolower( ini_get( $setting ) );
2194
	// 'on' and 'true' can't have whitespace around them, but '1' can.
2195
	return $val == 'on'
2196
		|| $val == 'true'
2197
		|| $val == 'yes'
2198
		|| preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
2199
}
2200
2201
/**
2202
 * Version of escapeshellarg() that works better on Windows.
2203
 *
2204
 * Originally, this fixed the incorrect use of single quotes on Windows
2205
 * (https://bugs.php.net/bug.php?id=26285) and the locale problems on Linux in
2206
 * PHP 5.2.6+ (bug backported to earlier distro releases of PHP).
2207
 *
2208
 * @param string ... strings to escape and glue together, or a single array of strings parameter
2209
 * @return string
2210
 */
2211
function wfEscapeShellArg( /*...*/ ) {
2212
	wfInitShellLocale();
2213
2214
	$args = func_get_args();
2215
	if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
2216
		// If only one argument has been passed, and that argument is an array,
2217
		// treat it as a list of arguments
2218
		$args = reset( $args );
2219
	}
2220
2221
	$first = true;
2222
	$retVal = '';
2223
	foreach ( $args as $arg ) {
2224
		if ( !$first ) {
2225
			$retVal .= ' ';
2226
		} else {
2227
			$first = false;
2228
		}
2229
2230
		if ( wfIsWindows() ) {
2231
			// Escaping for an MSVC-style command line parser and CMD.EXE
2232
			// @codingStandardsIgnoreStart For long URLs
2233
			// Refs:
2234
			//  * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
2235
			//  * http://technet.microsoft.com/en-us/library/cc723564.aspx
2236
			//  * T15518
2237
			//  * CR r63214
2238
			// Double the backslashes before any double quotes. Escape the double quotes.
2239
			// @codingStandardsIgnoreEnd
2240
			$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
2241
			$arg = '';
2242
			$iteration = 0;
2243
			foreach ( $tokens as $token ) {
2244
				if ( $iteration % 2 == 1 ) {
2245
					// Delimiter, a double quote preceded by zero or more slashes
2246
					$arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
2247
				} elseif ( $iteration % 4 == 2 ) {
2248
					// ^ in $token will be outside quotes, need to be escaped
2249
					$arg .= str_replace( '^', '^^', $token );
2250
				} else { // $iteration % 4 == 0
2251
					// ^ in $token will appear inside double quotes, so leave as is
2252
					$arg .= $token;
2253
				}
2254
				$iteration++;
2255
			}
2256
			// Double the backslashes before the end of the string, because
2257
			// we will soon add a quote
2258
			$m = [];
2259
			if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
2260
				$arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
2261
			}
2262
2263
			// Add surrounding quotes
2264
			$retVal .= '"' . $arg . '"';
2265
		} else {
2266
			$retVal .= escapeshellarg( $arg );
2267
		}
2268
	}
2269
	return $retVal;
2270
}
2271
2272
/**
2273
 * Check if wfShellExec() is effectively disabled via php.ini config
2274
 *
2275
 * @return bool|string False or 'disabled'
2276
 * @since 1.22
2277
 */
2278
function wfShellExecDisabled() {
2279
	static $disabled = null;
2280
	if ( is_null( $disabled ) ) {
2281
		if ( !function_exists( 'proc_open' ) ) {
2282
			wfDebug( "proc_open() is disabled\n" );
2283
			$disabled = 'disabled';
2284
		} else {
2285
			$disabled = false;
2286
		}
2287
	}
2288
	return $disabled;
2289
}
2290
2291
/**
2292
 * Execute a shell command, with time and memory limits mirrored from the PHP
2293
 * configuration if supported.
2294
 *
2295
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2296
 *   or an array of unescaped arguments, in which case each value will be escaped
2297
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2298
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2299
 *   (non-zero is usually failure). If there is an error from
2300
 *   read, select, or proc_open(), this will be set to -1.
2301
 * @param array $environ Optional environment variables which should be
2302
 *   added to the executed command environment.
2303
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2304
 *   this overwrites the global wgMaxShell* limits.
2305
 * @param array $options Array of options:
2306
 *   - duplicateStderr: Set this to true to duplicate stderr to stdout,
2307
 *     including errors from limit.sh
2308
 *   - profileMethod: By default this function will profile based on the calling
2309
 *     method. Set this to a string for an alternative method to profile from
2310
 *
2311
 * @return string Collected stdout as a string
2312
 */
2313
function wfShellExec( $cmd, &$retval = null, $environ = [],
2314
	$limits = [], $options = []
2315
) {
2316
	global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
2317
		$wgMaxShellWallClockTime, $wgShellCgroup;
2318
2319
	$disabled = wfShellExecDisabled();
2320
	if ( $disabled ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $disabled of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
2321
		$retval = 1;
2322
		return 'Unable to run external programs, proc_open() is disabled.';
2323
	}
2324
2325
	$includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
2326
	$profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller();
2327
2328
	wfInitShellLocale();
2329
2330
	$envcmd = '';
2331
	foreach ( $environ as $k => $v ) {
2332
		if ( wfIsWindows() ) {
2333
			/* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
2334
			 * appear in the environment variable, so we must use carat escaping as documented in
2335
			 * http://technet.microsoft.com/en-us/library/cc723564.aspx
2336
			 * Note however that the quote isn't listed there, but is needed, and the parentheses
2337
			 * are listed there but doesn't appear to need it.
2338
			 */
2339
			$envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
2340
		} else {
2341
			/* Assume this is a POSIX shell, thus required to accept variable assignments before the command
2342
			 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
2343
			 */
2344
			$envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
2345
		}
2346
	}
2347
	if ( is_array( $cmd ) ) {
2348
		$cmd = wfEscapeShellArg( $cmd );
2349
	}
2350
2351
	$cmd = $envcmd . $cmd;
2352
2353
	$useLogPipe = false;
2354
	if ( is_executable( '/bin/bash' ) ) {
2355
		$time = intval( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
2356
		if ( isset( $limits['walltime'] ) ) {
2357
			$wallTime = intval( $limits['walltime'] );
2358
		} elseif ( isset( $limits['time'] ) ) {
2359
			$wallTime = $time;
2360
		} else {
2361
			$wallTime = intval( $wgMaxShellWallClockTime );
2362
		}
2363
		$mem = intval( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
2364
		$filesize = intval( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
2365
2366
		if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
2367
			$cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
2368
				escapeshellarg( $cmd ) . ' ' .
2369
				escapeshellarg(
2370
					"MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
2371
					"MW_CPU_LIMIT=$time; " .
2372
					'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
2373
					"MW_MEM_LIMIT=$mem; " .
2374
					"MW_FILE_SIZE_LIMIT=$filesize; " .
2375
					"MW_WALL_CLOCK_LIMIT=$wallTime; " .
2376
					"MW_USE_LOG_PIPE=yes"
2377
				);
2378
			$useLogPipe = true;
2379
		} elseif ( $includeStderr ) {
2380
			$cmd .= ' 2>&1';
2381
		}
2382
	} elseif ( $includeStderr ) {
2383
		$cmd .= ' 2>&1';
2384
	}
2385
	wfDebug( "wfShellExec: $cmd\n" );
2386
2387
	// Don't try to execute commands that exceed Linux's MAX_ARG_STRLEN.
2388
	// Other platforms may be more accomodating, but we don't want to be
2389
	// accomodating, because very long commands probably include user
2390
	// input. See T129506.
2391
	if ( strlen( $cmd ) > SHELL_MAX_ARG_STRLEN ) {
2392
		throw new Exception( __METHOD__ .
2393
			'(): total length of $cmd must not exceed SHELL_MAX_ARG_STRLEN' );
2394
	}
2395
2396
	$desc = [
2397
		0 => [ 'file', 'php://stdin', 'r' ],
2398
		1 => [ 'pipe', 'w' ],
2399
		2 => [ 'file', 'php://stderr', 'w' ] ];
2400
	if ( $useLogPipe ) {
2401
		$desc[3] = [ 'pipe', 'w' ];
2402
	}
2403
	$pipes = null;
2404
	$scoped = Profiler::instance()->scopedProfileIn( __FUNCTION__ . '-' . $profileMethod );
0 ignored issues
show
Unused Code introduced by
$scoped is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2405
	$proc = proc_open( $cmd, $desc, $pipes );
2406
	if ( !$proc ) {
2407
		wfDebugLog( 'exec', "proc_open() failed: $cmd" );
2408
		$retval = -1;
2409
		return '';
2410
	}
2411
	$outBuffer = $logBuffer = '';
2412
	$emptyArray = [];
2413
	$status = false;
2414
	$logMsg = false;
2415
2416
	/* According to the documentation, it is possible for stream_select()
2417
	 * to fail due to EINTR. I haven't managed to induce this in testing
2418
	 * despite sending various signals. If it did happen, the error
2419
	 * message would take the form:
2420
	 *
2421
	 * stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
2422
	 *
2423
	 * where [4] is the value of the macro EINTR and "Interrupted system
2424
	 * call" is string which according to the Linux manual is "possibly"
2425
	 * localised according to LC_MESSAGES.
2426
	 */
2427
	$eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
2428
	$eintrMessage = "stream_select(): unable to select [$eintr]";
2429
2430
	$running = true;
2431
	$timeout = null;
2432
	$numReadyPipes = 0;
2433
2434
	while ( $running === true || $numReadyPipes !== 0 ) {
2435
		if ( $running ) {
2436
			$status = proc_get_status( $proc );
2437
			// If the process has terminated, switch to nonblocking selects
2438
			// for getting any data still waiting to be read.
2439
			if ( !$status['running'] ) {
2440
				$running = false;
2441
				$timeout = 0;
2442
			}
2443
		}
2444
2445
		$readyPipes = $pipes;
2446
2447
		// Clear last error
2448
		// @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged
2449
		@trigger_error( '' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2450
		$numReadyPipes = @stream_select( $readyPipes, $emptyArray, $emptyArray, $timeout );
2451
		if ( $numReadyPipes === false ) {
2452
			// @codingStandardsIgnoreEnd
2453
			$error = error_get_last();
2454
			if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
2455
				continue;
2456
			} else {
2457
				trigger_error( $error['message'], E_USER_WARNING );
2458
				$logMsg = $error['message'];
2459
				break;
2460
			}
2461
		}
2462
		foreach ( $readyPipes as $fd => $pipe ) {
2463
			$block = fread( $pipe, 65536 );
2464
			if ( $block === '' ) {
2465
				// End of file
2466
				fclose( $pipes[$fd] );
2467
				unset( $pipes[$fd] );
2468
				if ( !$pipes ) {
2469
					break 2;
2470
				}
2471
			} elseif ( $block === false ) {
2472
				// Read error
2473
				$logMsg = "Error reading from pipe";
2474
				break 2;
2475
			} elseif ( $fd == 1 ) {
2476
				// From stdout
2477
				$outBuffer .= $block;
2478
			} elseif ( $fd == 3 ) {
2479
				// From log FD
2480
				$logBuffer .= $block;
2481
				if ( strpos( $block, "\n" ) !== false ) {
2482
					$lines = explode( "\n", $logBuffer );
2483
					$logBuffer = array_pop( $lines );
2484
					foreach ( $lines as $line ) {
2485
						wfDebugLog( 'exec', $line );
2486
					}
2487
				}
2488
			}
2489
		}
2490
	}
2491
2492
	foreach ( $pipes as $pipe ) {
2493
		fclose( $pipe );
2494
	}
2495
2496
	// Use the status previously collected if possible, since proc_get_status()
2497
	// just calls waitpid() which will not return anything useful the second time.
2498
	if ( $running ) {
2499
		$status = proc_get_status( $proc );
2500
	}
2501
2502
	if ( $logMsg !== false ) {
2503
		// Read/select error
2504
		$retval = -1;
2505
		proc_close( $proc );
2506
	} elseif ( $status['signaled'] ) {
2507
		$logMsg = "Exited with signal {$status['termsig']}";
2508
		$retval = 128 + $status['termsig'];
2509
		proc_close( $proc );
2510
	} else {
2511
		if ( $status['running'] ) {
2512
			$retval = proc_close( $proc );
2513
		} else {
2514
			$retval = $status['exitcode'];
2515
			proc_close( $proc );
2516
		}
2517
		if ( $retval == 127 ) {
2518
			$logMsg = "Possibly missing executable file";
2519
		} elseif ( $retval >= 129 && $retval <= 192 ) {
2520
			$logMsg = "Probably exited with signal " . ( $retval - 128 );
2521
		}
2522
	}
2523
2524
	if ( $logMsg !== false ) {
2525
		wfDebugLog( 'exec', "$logMsg: $cmd" );
2526
	}
2527
2528
	return $outBuffer;
2529
}
2530
2531
/**
2532
 * Execute a shell command, returning both stdout and stderr. Convenience
2533
 * function, as all the arguments to wfShellExec can become unwieldy.
2534
 *
2535
 * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
2536
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2537
 *   or an array of unescaped arguments, in which case each value will be escaped
2538
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2539
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2540
 *   (non-zero is usually failure)
2541
 * @param array $environ Optional environment variables which should be
2542
 *   added to the executed command environment.
2543
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2544
 *   this overwrites the global wgMaxShell* limits.
2545
 * @return string Collected stdout and stderr as a string
2546
 */
2547
function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = [] ) {
2548
	return wfShellExec( $cmd, $retval, $environ, $limits,
2549
		[ 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ] );
2550
}
2551
2552
/**
2553
 * Workaround for http://bugs.php.net/bug.php?id=45132
2554
 * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
2555
 */
2556
function wfInitShellLocale() {
2557
	static $done = false;
2558
	if ( $done ) {
2559
		return;
2560
	}
2561
	$done = true;
2562
	global $wgShellLocale;
2563
	putenv( "LC_CTYPE=$wgShellLocale" );
2564
	setlocale( LC_CTYPE, $wgShellLocale );
2565
}
2566
2567
/**
2568
 * Generate a shell-escaped command line string to run a MediaWiki cli script.
2569
 * Note that $parameters should be a flat array and an option with an argument
2570
 * should consist of two consecutive items in the array (do not use "--option value").
2571
 *
2572
 * @param string $script MediaWiki cli script path
2573
 * @param array $parameters Arguments and options to the script
2574
 * @param array $options Associative array of options:
2575
 * 		'php': The path to the php executable
2576
 * 		'wrapper': Path to a PHP wrapper to handle the maintenance script
2577
 * @return string
2578
 */
2579
function wfShellWikiCmd( $script, array $parameters = [], array $options = [] ) {
2580
	global $wgPhpCli;
2581
	// Give site config file a chance to run the script in a wrapper.
2582
	// The caller may likely want to call wfBasename() on $script.
2583
	Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
2584
	$cmd = isset( $options['php'] ) ? [ $options['php'] ] : [ $wgPhpCli ];
2585
	if ( isset( $options['wrapper'] ) ) {
2586
		$cmd[] = $options['wrapper'];
2587
	}
2588
	$cmd[] = $script;
2589
	// Escape each parameter for shell
2590
	return wfEscapeShellArg( array_merge( $cmd, $parameters ) );
2591
}
2592
2593
/**
2594
 * wfMerge attempts to merge differences between three texts.
2595
 * Returns true for a clean merge and false for failure or a conflict.
2596
 *
2597
 * @param string $old
2598
 * @param string $mine
2599
 * @param string $yours
2600
 * @param string $result
2601
 * @return bool
2602
 */
2603
function wfMerge( $old, $mine, $yours, &$result ) {
2604
	global $wgDiff3;
2605
2606
	# This check may also protect against code injection in
2607
	# case of broken installations.
2608
	MediaWiki\suppressWarnings();
2609
	$haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
2610
	MediaWiki\restoreWarnings();
2611
2612
	if ( !$haveDiff3 ) {
2613
		wfDebug( "diff3 not found\n" );
2614
		return false;
2615
	}
2616
2617
	# Make temporary files
2618
	$td = wfTempDir();
2619
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2620
	$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
2621
	$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
2622
2623
	# NOTE: diff3 issues a warning to stderr if any of the files does not end with
2624
	#       a newline character. To avoid this, we normalize the trailing whitespace before
2625
	#       creating the diff.
2626
2627
	fwrite( $oldtextFile, rtrim( $old ) . "\n" );
2628
	fclose( $oldtextFile );
2629
	fwrite( $mytextFile, rtrim( $mine ) . "\n" );
2630
	fclose( $mytextFile );
2631
	fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
2632
	fclose( $yourtextFile );
2633
2634
	# Check for a conflict
2635
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName,
2636
		$oldtextName, $yourtextName );
2637
	$handle = popen( $cmd, 'r' );
2638
2639
	if ( fgets( $handle, 1024 ) ) {
2640
		$conflict = true;
2641
	} else {
2642
		$conflict = false;
2643
	}
2644
	pclose( $handle );
2645
2646
	# Merge differences
2647
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName,
2648
		$oldtextName, $yourtextName );
2649
	$handle = popen( $cmd, 'r' );
2650
	$result = '';
2651
	do {
2652
		$data = fread( $handle, 8192 );
2653
		if ( strlen( $data ) == 0 ) {
2654
			break;
2655
		}
2656
		$result .= $data;
2657
	} while ( true );
2658
	pclose( $handle );
2659
	unlink( $mytextName );
2660
	unlink( $oldtextName );
2661
	unlink( $yourtextName );
2662
2663
	if ( $result === '' && $old !== '' && !$conflict ) {
2664
		wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
2665
		$conflict = true;
2666
	}
2667
	return !$conflict;
2668
}
2669
2670
/**
2671
 * Returns unified plain-text diff of two texts.
2672
 * "Useful" for machine processing of diffs.
2673
 *
2674
 * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly
2675
 *
2676
 * @param string $before The text before the changes.
2677
 * @param string $after The text after the changes.
2678
 * @param string $params Command-line options for the diff command.
2679
 * @return string Unified diff of $before and $after
2680
 */
2681
function wfDiff( $before, $after, $params = '-u' ) {
2682
	if ( $before == $after ) {
2683
		return '';
2684
	}
2685
2686
	global $wgDiff;
2687
	MediaWiki\suppressWarnings();
2688
	$haveDiff = $wgDiff && file_exists( $wgDiff );
2689
	MediaWiki\restoreWarnings();
2690
2691
	# This check may also protect against code injection in
2692
	# case of broken installations.
2693
	if ( !$haveDiff ) {
2694
		wfDebug( "diff executable not found\n" );
2695
		$diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
2696
		$format = new UnifiedDiffFormatter();
2697
		return $format->format( $diffs );
2698
	}
2699
2700
	# Make temporary files
2701
	$td = wfTempDir();
2702
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2703
	$newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
2704
2705
	fwrite( $oldtextFile, $before );
2706
	fclose( $oldtextFile );
2707
	fwrite( $newtextFile, $after );
2708
	fclose( $newtextFile );
2709
2710
	// Get the diff of the two files
2711
	$cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
2712
2713
	$h = popen( $cmd, 'r' );
2714
	if ( !$h ) {
2715
		unlink( $oldtextName );
2716
		unlink( $newtextName );
2717
		throw new Exception( __METHOD__ . '(): popen() failed' );
2718
	}
2719
2720
	$diff = '';
2721
2722
	do {
2723
		$data = fread( $h, 8192 );
2724
		if ( strlen( $data ) == 0 ) {
2725
			break;
2726
		}
2727
		$diff .= $data;
2728
	} while ( true );
2729
2730
	// Clean up
2731
	pclose( $h );
2732
	unlink( $oldtextName );
2733
	unlink( $newtextName );
2734
2735
	// Kill the --- and +++ lines. They're not useful.
2736
	$diff_lines = explode( "\n", $diff );
2737 View Code Duplication
	if ( isset( $diff_lines[0] ) && strpos( $diff_lines[0], '---' ) === 0 ) {
2738
		unset( $diff_lines[0] );
2739
	}
2740 View Code Duplication
	if ( isset( $diff_lines[1] ) && strpos( $diff_lines[1], '+++' ) === 0 ) {
2741
		unset( $diff_lines[1] );
2742
	}
2743
2744
	$diff = implode( "\n", $diff_lines );
2745
2746
	return $diff;
2747
}
2748
2749
/**
2750
 * This function works like "use VERSION" in Perl, the program will die with a
2751
 * backtrace if the current version of PHP is less than the version provided
2752
 *
2753
 * This is useful for extensions which due to their nature are not kept in sync
2754
 * with releases, and might depend on other versions of PHP than the main code
2755
 *
2756
 * Note: PHP might die due to parsing errors in some cases before it ever
2757
 *       manages to call this function, such is life
2758
 *
2759
 * @see perldoc -f use
2760
 *
2761
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2762
 * @throws MWException
2763
 */
2764
function wfUsePHP( $req_ver ) {
2765
	$php_ver = PHP_VERSION;
2766
2767
	if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
2768
		throw new MWException( "PHP $req_ver required--this is only $php_ver" );
2769
	}
2770
}
2771
2772
/**
2773
 * This function works like "use VERSION" in Perl except it checks the version
2774
 * of MediaWiki, the program will die with a backtrace if the current version
2775
 * of MediaWiki is less than the version provided.
2776
 *
2777
 * This is useful for extensions which due to their nature are not kept in sync
2778
 * with releases
2779
 *
2780
 * Note: Due to the behavior of PHP's version_compare() which is used in this
2781
 * function, if you want to allow the 'wmf' development versions add a 'c' (or
2782
 * any single letter other than 'a', 'b' or 'p') as a post-fix to your
2783
 * targeted version number. For example if you wanted to allow any variation
2784
 * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
2785
 * not result in the same comparison due to the internal logic of
2786
 * version_compare().
2787
 *
2788
 * @see perldoc -f use
2789
 *
2790
 * @deprecated since 1.26, use the "requires' property of extension.json
2791
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2792
 * @throws MWException
2793
 */
2794
function wfUseMW( $req_ver ) {
2795
	global $wgVersion;
2796
2797
	if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
2798
		throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
2799
	}
2800
}
2801
2802
/**
2803
 * Return the final portion of a pathname.
2804
 * Reimplemented because PHP5's "basename()" is buggy with multibyte text.
2805
 * http://bugs.php.net/bug.php?id=33898
2806
 *
2807
 * PHP's basename() only considers '\' a pathchar on Windows and Netware.
2808
 * We'll consider it so always, as we don't want '\s' in our Unix paths either.
2809
 *
2810
 * @param string $path
2811
 * @param string $suffix String to remove if present
2812
 * @return string
2813
 */
2814
function wfBaseName( $path, $suffix = '' ) {
2815
	if ( $suffix == '' ) {
2816
		$encSuffix = '';
2817
	} else {
2818
		$encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
2819
	}
2820
2821
	$matches = [];
2822
	if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
2823
		return $matches[1];
2824
	} else {
2825
		return '';
2826
	}
2827
}
2828
2829
/**
2830
 * Generate a relative path name to the given file.
2831
 * May explode on non-matching case-insensitive paths,
2832
 * funky symlinks, etc.
2833
 *
2834
 * @param string $path Absolute destination path including target filename
2835
 * @param string $from Absolute source path, directory only
2836
 * @return string
2837
 */
2838
function wfRelativePath( $path, $from ) {
2839
	// Normalize mixed input on Windows...
2840
	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
2841
	$from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
2842
2843
	// Trim trailing slashes -- fix for drive root
2844
	$path = rtrim( $path, DIRECTORY_SEPARATOR );
2845
	$from = rtrim( $from, DIRECTORY_SEPARATOR );
2846
2847
	$pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
2848
	$against = explode( DIRECTORY_SEPARATOR, $from );
2849
2850
	if ( $pieces[0] !== $against[0] ) {
2851
		// Non-matching Windows drive letters?
2852
		// Return a full path.
2853
		return $path;
2854
	}
2855
2856
	// Trim off common prefix
2857
	while ( count( $pieces ) && count( $against )
2858
		&& $pieces[0] == $against[0] ) {
2859
		array_shift( $pieces );
2860
		array_shift( $against );
2861
	}
2862
2863
	// relative dots to bump us to the parent
2864
	while ( count( $against ) ) {
2865
		array_unshift( $pieces, '..' );
2866
		array_shift( $against );
2867
	}
2868
2869
	array_push( $pieces, wfBaseName( $path ) );
2870
2871
	return implode( DIRECTORY_SEPARATOR, $pieces );
2872
}
2873
2874
/**
2875
 * Convert an arbitrarily-long digit string from one numeric base
2876
 * to another, optionally zero-padding to a minimum column width.
2877
 *
2878
 * Supports base 2 through 36; digit values 10-36 are represented
2879
 * as lowercase letters a-z. Input is case-insensitive.
2880
 *
2881
 * @deprecated since 1.27 Use Wikimedia\base_convert() directly
2882
 *
2883
 * @param string $input Input number
2884
 * @param int $sourceBase Base of the input number
2885
 * @param int $destBase Desired base of the output
2886
 * @param int $pad Minimum number of digits in the output (pad with zeroes)
2887
 * @param bool $lowercase Whether to output in lowercase or uppercase
2888
 * @param string $engine Either "gmp", "bcmath", or "php"
2889
 * @return string|bool The output number as a string, or false on error
2890
 */
2891
function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1,
2892
	$lowercase = true, $engine = 'auto'
2893
) {
2894
	return Wikimedia\base_convert( $input, $sourceBase, $destBase, $pad, $lowercase, $engine );
2895
}
2896
2897
/**
2898
 * @deprecated since 1.27, PHP's session generation isn't used with
2899
 *  MediaWiki\Session\SessionManager
2900
 */
2901
function wfFixSessionID() {
2902
	wfDeprecated( __FUNCTION__, '1.27' );
2903
}
2904
2905
/**
2906
 * Reset the session id
2907
 *
2908
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead
2909
 * @since 1.22
2910
 */
2911
function wfResetSessionID() {
2912
	wfDeprecated( __FUNCTION__, '1.27' );
2913
	$session = SessionManager::getGlobalSession();
2914
	$delay = $session->delaySave();
2915
2916
	$session->resetId();
2917
2918
	// Make sure a session is started, since that's what the old
2919
	// wfResetSessionID() did.
2920
	if ( session_id() !== $session->getId() ) {
2921
		wfSetupSession( $session->getId() );
0 ignored issues
show
Deprecated Code introduced by
The function wfSetupSession() has been deprecated with message: since 1.27, use MediaWiki\Session\SessionManager instead. Generally, "using" SessionManager will be calling ->getSessionById() or ::getGlobalSession() (depending on whether you were passing $sessionId here), then calling $session->persist().

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
2922
	}
2923
2924
	ScopedCallback::consume( $delay );
2925
}
2926
2927
/**
2928
 * Initialise php session
2929
 *
2930
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead.
2931
 *  Generally, "using" SessionManager will be calling ->getSessionById() or
2932
 *  ::getGlobalSession() (depending on whether you were passing $sessionId
2933
 *  here), then calling $session->persist().
2934
 * @param bool|string $sessionId
2935
 */
2936
function wfSetupSession( $sessionId = false ) {
2937
	wfDeprecated( __FUNCTION__, '1.27' );
2938
2939
	if ( $sessionId ) {
2940
		session_id( $sessionId );
2941
	}
2942
2943
	$session = SessionManager::getGlobalSession();
2944
	$session->persist();
2945
2946
	if ( session_id() !== $session->getId() ) {
2947
		session_id( $session->getId() );
2948
	}
2949
	MediaWiki\quietCall( 'session_start' );
2950
}
2951
2952
/**
2953
 * Get an object from the precompiled serialized directory
2954
 *
2955
 * @param string $name
2956
 * @return mixed The variable on success, false on failure
2957
 */
2958
function wfGetPrecompiledData( $name ) {
2959
	global $IP;
2960
2961
	$file = "$IP/serialized/$name";
2962
	if ( file_exists( $file ) ) {
2963
		$blob = file_get_contents( $file );
2964
		if ( $blob ) {
2965
			return unserialize( $blob );
2966
		}
2967
	}
2968
	return false;
2969
}
2970
2971
/**
2972
 * Make a cache key for the local wiki.
2973
 *
2974
 * @param string $args,...
0 ignored issues
show
Bug introduced by
There is no parameter named $args,.... 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...
2975
 * @return string
2976
 */
2977
function wfMemcKey( /*...*/ ) {
2978
	return call_user_func_array(
2979
		[ ObjectCache::getLocalClusterInstance(), 'makeKey' ],
2980
		func_get_args()
2981
	);
2982
}
2983
2984
/**
2985
 * Make a cache key for a foreign DB.
2986
 *
2987
 * Must match what wfMemcKey() would produce in context of the foreign wiki.
2988
 *
2989
 * @param string $db
2990
 * @param string $prefix
2991
 * @param string $args,...
0 ignored issues
show
Bug introduced by
There is no parameter named $args,.... 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...
2992
 * @return string
2993
 */
2994
function wfForeignMemcKey( $db, $prefix /*...*/ ) {
2995
	$args = array_slice( func_get_args(), 2 );
2996
	$keyspace = $prefix ? "$db-$prefix" : $db;
2997
	return call_user_func_array(
2998
		[ ObjectCache::getLocalClusterInstance(), 'makeKeyInternal' ],
2999
		[ $keyspace, $args ]
3000
	);
3001
}
3002
3003
/**
3004
 * Make a cache key with database-agnostic prefix.
3005
 *
3006
 * Doesn't have a wiki-specific namespace. Uses a generic 'global' prefix
3007
 * instead. Must have a prefix as otherwise keys that use a database name
3008
 * in the first segment will clash with wfMemcKey/wfForeignMemcKey.
3009
 *
3010
 * @since 1.26
3011
 * @param string $args,...
0 ignored issues
show
Bug introduced by
There is no parameter named $args,.... 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...
3012
 * @return string
3013
 */
3014
function wfGlobalCacheKey( /*...*/ ) {
3015
	return call_user_func_array(
3016
		[ ObjectCache::getLocalClusterInstance(), 'makeGlobalKey' ],
3017
		func_get_args()
3018
	);
3019
}
3020
3021
/**
3022
 * Get an ASCII string identifying this wiki
3023
 * This is used as a prefix in memcached keys
3024
 *
3025
 * @return string
3026
 */
3027
function wfWikiID() {
3028
	global $wgDBprefix, $wgDBname;
3029
	if ( $wgDBprefix ) {
3030
		return "$wgDBname-$wgDBprefix";
3031
	} else {
3032
		return $wgDBname;
3033
	}
3034
}
3035
3036
/**
3037
 * Split a wiki ID into DB name and table prefix
3038
 *
3039
 * @param string $wiki
3040
 *
3041
 * @return array
3042
 */
3043
function wfSplitWikiID( $wiki ) {
3044
	$bits = explode( '-', $wiki, 2 );
3045
	if ( count( $bits ) < 2 ) {
3046
		$bits[] = '';
3047
	}
3048
	return $bits;
3049
}
3050
3051
/**
3052
 * Get a Database object.
3053
 *
3054
 * @param int $db Index of the connection to get. May be DB_MASTER for the
3055
 *            master (for write queries), DB_REPLICA for potentially lagged read
3056
 *            queries, or an integer >= 0 for a particular server.
3057
 *
3058
 * @param string|string[] $groups Query groups. An array of group names that this query
3059
 *                belongs to. May contain a single string if the query is only
3060
 *                in one group.
3061
 *
3062
 * @param string|bool $wiki The wiki ID, or false for the current wiki
3063
 *
3064
 * Note: multiple calls to wfGetDB(DB_REPLICA) during the course of one request
3065
 * will always return the same object, unless the underlying connection or load
3066
 * balancer is manually destroyed.
3067
 *
3068
 * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
3069
 * updater to ensure that a proper database is being updated.
3070
 *
3071
 * @todo Replace calls to wfGetDB with calls to LoadBalancer::getConnection()
3072
 *       on an injected instance of LoadBalancer.
3073
 *
3074
 * @return Database
3075
 */
3076
function wfGetDB( $db, $groups = [], $wiki = false ) {
3077
	return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
0 ignored issues
show
Bug introduced by
It seems like $groups defined by parameter $groups on line 3076 can also be of type string; however, LoadBalancer::getConnection() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $wiki defined by parameter $wiki on line 3076 can also be of type string; however, LoadBalancer::getConnection() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Deprecated Code introduced by
The function wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
3078
}
3079
3080
/**
3081
 * Get a load balancer object.
3082
 *
3083
 * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancer()
3084
 *              or MediaWikiServices::getDBLoadBalancerFactory() instead.
3085
 *
3086
 * @param string|bool $wiki Wiki ID, or false for the current wiki
3087
 * @return LoadBalancer
3088
 */
3089
function wfGetLB( $wiki = false ) {
3090
	if ( $wiki === false ) {
3091
		return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancer();
3092
	} else {
3093
		$factory = \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3094
		return $factory->getMainLB( $wiki );
0 ignored issues
show
Bug introduced by
It seems like $wiki defined by parameter $wiki on line 3089 can also be of type string; however, LBFactory::getMainLB() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3095
	}
3096
}
3097
3098
/**
3099
 * Get the load balancer factory object
3100
 *
3101
 * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.
3102
 *
3103
 * @return LBFactory
3104
 */
3105
function wfGetLBFactory() {
3106
	return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3107
}
3108
3109
/**
3110
 * Find a file.
3111
 * Shortcut for RepoGroup::singleton()->findFile()
3112
 *
3113
 * @param string $title String or Title object
3114
 * @param array $options Associative array of options (see RepoGroup::findFile)
3115
 * @return File|bool File, or false if the file does not exist
3116
 */
3117
function wfFindFile( $title, $options = [] ) {
3118
	return RepoGroup::singleton()->findFile( $title, $options );
3119
}
3120
3121
/**
3122
 * Get an object referring to a locally registered file.
3123
 * Returns a valid placeholder object if the file does not exist.
3124
 *
3125
 * @param Title|string $title
3126
 * @return LocalFile|null A File, or null if passed an invalid Title
3127
 */
3128
function wfLocalFile( $title ) {
3129
	return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
3130
}
3131
3132
/**
3133
 * Should low-performance queries be disabled?
3134
 *
3135
 * @return bool
3136
 * @codeCoverageIgnore
3137
 */
3138
function wfQueriesMustScale() {
3139
	global $wgMiserMode;
3140
	return $wgMiserMode
3141
		|| ( SiteStats::pages() > 100000
3142
		&& SiteStats::edits() > 1000000
3143
		&& SiteStats::users() > 10000 );
3144
}
3145
3146
/**
3147
 * Get the path to a specified script file, respecting file
3148
 * extensions; this is a wrapper around $wgScriptPath etc.
3149
 * except for 'index' and 'load' which use $wgScript/$wgLoadScript
3150
 *
3151
 * @param string $script Script filename, sans extension
3152
 * @return string
3153
 */
3154
function wfScript( $script = 'index' ) {
3155
	global $wgScriptPath, $wgScript, $wgLoadScript;
3156
	if ( $script === 'index' ) {
3157
		return $wgScript;
3158
	} elseif ( $script === 'load' ) {
3159
		return $wgLoadScript;
3160
	} else {
3161
		return "{$wgScriptPath}/{$script}.php";
3162
	}
3163
}
3164
3165
/**
3166
 * Get the script URL.
3167
 *
3168
 * @return string Script URL
3169
 */
3170
function wfGetScriptUrl() {
0 ignored issues
show
Coding Style introduced by
wfGetScriptUrl 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...
3171
	if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
3172
		/* as it was called, minus the query string.
3173
		 *
3174
		 * Some sites use Apache rewrite rules to handle subdomains,
3175
		 * and have PHP set up in a weird way that causes PHP_SELF
3176
		 * to contain the rewritten URL instead of the one that the
3177
		 * outside world sees.
3178
		 *
3179
		 * If in this mode, use SCRIPT_URL instead, which mod_rewrite
3180
		 * provides containing the "before" URL.
3181
		 */
3182
		return $_SERVER['SCRIPT_NAME'];
3183
	} else {
3184
		return $_SERVER['URL'];
3185
	}
3186
}
3187
3188
/**
3189
 * Convenience function converts boolean values into "true"
3190
 * or "false" (string) values
3191
 *
3192
 * @param bool $value
3193
 * @return string
3194
 */
3195
function wfBoolToStr( $value ) {
3196
	return $value ? 'true' : 'false';
3197
}
3198
3199
/**
3200
 * Get a platform-independent path to the null file, e.g. /dev/null
3201
 *
3202
 * @return string
3203
 */
3204
function wfGetNull() {
3205
	return wfIsWindows() ? 'NUL' : '/dev/null';
3206
}
3207
3208
/**
3209
 * Waits for the replica DBs to catch up to the master position
3210
 *
3211
 * Use this when updating very large numbers of rows, as in maintenance scripts,
3212
 * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
3213
 *
3214
 * By default this waits on the main DB cluster of the current wiki.
3215
 * If $cluster is set to "*" it will wait on all DB clusters, including
3216
 * external ones. If the lag being waiting on is caused by the code that
3217
 * does this check, it makes since to use $ifWritesSince, particularly if
3218
 * cluster is "*", to avoid excess overhead.
3219
 *
3220
 * Never call this function after a big DB write that is still in a transaction.
3221
 * This only makes sense after the possible lag inducing changes were committed.
3222
 *
3223
 * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
3224
 * @param string|bool $wiki Wiki identifier accepted by wfGetLB
3225
 * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
3226
 * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web)
3227
 * @return bool Success (able to connect and no timeouts reached)
3228
 * @deprecated since 1.27 Use LBFactory::waitForReplication
3229
 */
3230
function wfWaitForSlaves(
3231
	$ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
3232
) {
3233
	if ( $timeout === null ) {
3234
		$timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10;
3235
	}
3236
3237
	if ( $cluster === '*' ) {
3238
		$cluster = false;
3239
		$wiki = false;
3240
	} elseif ( $wiki === false ) {
3241
		$wiki = wfWikiID();
3242
	}
3243
3244
	try {
3245
		wfGetLBFactory()->waitForReplication( [
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLBFactory() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
3246
			'wiki' => $wiki,
3247
			'cluster' => $cluster,
3248
			'timeout' => $timeout,
3249
			// B/C: first argument used to be "max seconds of lag"; ignore such values
3250
			'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
3251
		] );
3252
	} catch ( DBReplicationWaitError $e ) {
3253
		return false;
3254
	}
3255
3256
	return true;
3257
}
3258
3259
/**
3260
 * Count down from $seconds to zero on the terminal, with a one-second pause
3261
 * between showing each number. For use in command-line scripts.
3262
 *
3263
 * @codeCoverageIgnore
3264
 * @param int $seconds
3265
 */
3266
function wfCountDown( $seconds ) {
3267
	for ( $i = $seconds; $i >= 0; $i-- ) {
3268
		if ( $i != $seconds ) {
3269
			echo str_repeat( "\x08", strlen( $i + 1 ) );
3270
		}
3271
		echo $i;
3272
		flush();
3273
		if ( $i ) {
3274
			sleep( 1 );
3275
		}
3276
	}
3277
	echo "\n";
3278
}
3279
3280
/**
3281
 * Replace all invalid characters with '-'.
3282
 * Additional characters can be defined in $wgIllegalFileChars (see T22489).
3283
 * By default, $wgIllegalFileChars includes ':', '/', '\'.
3284
 *
3285
 * @param string $name Filename to process
3286
 * @return string
3287
 */
3288
function wfStripIllegalFilenameChars( $name ) {
3289
	global $wgIllegalFileChars;
3290
	$illegalFileChars = $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '';
3291
	$name = preg_replace(
3292
		"/[^" . Title::legalChars() . "]" . $illegalFileChars . "/",
3293
		'-',
3294
		$name
3295
	);
3296
	// $wgIllegalFileChars may not include '/' and '\', so we still need to do this
3297
	$name = wfBaseName( $name );
3298
	return $name;
3299
}
3300
3301
/**
3302
 * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit
3303
 *
3304
 * @return int Resulting value of the memory limit.
3305
 */
3306
function wfMemoryLimit() {
3307
	global $wgMemoryLimit;
3308
	$memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
3309
	if ( $memlimit != -1 ) {
3310
		$conflimit = wfShorthandToInteger( $wgMemoryLimit );
3311
		if ( $conflimit == -1 ) {
3312
			wfDebug( "Removing PHP's memory limit\n" );
3313
			MediaWiki\suppressWarnings();
3314
			ini_set( 'memory_limit', $conflimit );
3315
			MediaWiki\restoreWarnings();
3316
			return $conflimit;
3317
		} elseif ( $conflimit > $memlimit ) {
3318
			wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
3319
			MediaWiki\suppressWarnings();
3320
			ini_set( 'memory_limit', $conflimit );
3321
			MediaWiki\restoreWarnings();
3322
			return $conflimit;
3323
		}
3324
	}
3325
	return $memlimit;
3326
}
3327
3328
/**
3329
 * Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit
3330
 *
3331
 * @return int Prior time limit
3332
 * @since 1.26
3333
 */
3334
function wfTransactionalTimeLimit() {
3335
	global $wgTransactionalTimeLimit;
3336
3337
	$timeLimit = ini_get( 'max_execution_time' );
3338
	// Note that CLI scripts use 0
3339
	if ( $timeLimit > 0 && $wgTransactionalTimeLimit > $timeLimit ) {
3340
		set_time_limit( $wgTransactionalTimeLimit );
3341
	}
3342
3343
	ignore_user_abort( true ); // ignore client disconnects
3344
3345
	return $timeLimit;
3346
}
3347
3348
/**
3349
 * Converts shorthand byte notation to integer form
3350
 *
3351
 * @param string $string
3352
 * @param int $default Returned if $string is empty
3353
 * @return int
3354
 */
3355
function wfShorthandToInteger( $string = '', $default = -1 ) {
3356
	$string = trim( $string );
3357
	if ( $string === '' ) {
3358
		return $default;
3359
	}
3360
	$last = $string[strlen( $string ) - 1];
3361
	$val = intval( $string );
3362
	switch ( $last ) {
3363
		case 'g':
3364
		case 'G':
3365
			$val *= 1024;
3366
			// break intentionally missing
3367
		case 'm':
3368
		case 'M':
3369
			$val *= 1024;
3370
			// break intentionally missing
3371
		case 'k':
3372
		case 'K':
3373
			$val *= 1024;
3374
	}
3375
3376
	return $val;
3377
}
3378
3379
/**
3380
 * Get the normalised IETF language tag
3381
 * See unit test for examples.
3382
 *
3383
 * @param string $code The language code.
3384
 * @return string The language code which complying with BCP 47 standards.
3385
 */
3386
function wfBCP47( $code ) {
3387
	$codeSegment = explode( '-', $code );
3388
	$codeBCP = [];
3389
	foreach ( $codeSegment as $segNo => $seg ) {
3390
		// when previous segment is x, it is a private segment and should be lc
3391
		if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
3392
			$codeBCP[$segNo] = strtolower( $seg );
3393
		// ISO 3166 country code
3394
		} elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
3395
			$codeBCP[$segNo] = strtoupper( $seg );
3396
		// ISO 15924 script code
3397
		} elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
3398
			$codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
3399
		// Use lowercase for other cases
3400
		} else {
3401
			$codeBCP[$segNo] = strtolower( $seg );
3402
		}
3403
	}
3404
	$langCode = implode( '-', $codeBCP );
3405
	return $langCode;
3406
}
3407
3408
/**
3409
 * Get a specific cache object.
3410
 *
3411
 * @param int|string $cacheType A CACHE_* constants, or other key in $wgObjectCaches
3412
 * @return BagOStuff
3413
 */
3414
function wfGetCache( $cacheType ) {
3415
	return ObjectCache::getInstance( $cacheType );
3416
}
3417
3418
/**
3419
 * Get the main cache object
3420
 *
3421
 * @return BagOStuff
3422
 */
3423
function wfGetMainCache() {
3424
	global $wgMainCacheType;
3425
	return ObjectCache::getInstance( $wgMainCacheType );
3426
}
3427
3428
/**
3429
 * Get the cache object used by the message cache
3430
 *
3431
 * @return BagOStuff
3432
 */
3433
function wfGetMessageCacheStorage() {
3434
	global $wgMessageCacheType;
3435
	return ObjectCache::getInstance( $wgMessageCacheType );
3436
}
3437
3438
/**
3439
 * Get the cache object used by the parser cache
3440
 *
3441
 * @return BagOStuff
3442
 */
3443
function wfGetParserCacheStorage() {
3444
	global $wgParserCacheType;
3445
	return ObjectCache::getInstance( $wgParserCacheType );
3446
}
3447
3448
/**
3449
 * Call hook functions defined in $wgHooks
3450
 *
3451
 * @param string $event Event name
3452
 * @param array $args Parameters passed to hook functions
3453
 * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number
3454
 *
3455
 * @return bool True if no handler aborted the hook
3456
 * @deprecated since 1.25 - use Hooks::run
3457
 */
3458
function wfRunHooks( $event, array $args = [], $deprecatedVersion = null ) {
3459
	return Hooks::run( $event, $args, $deprecatedVersion );
3460
}
3461
3462
/**
3463
 * Wrapper around php's unpack.
3464
 *
3465
 * @param string $format The format string (See php's docs)
3466
 * @param string $data A binary string of binary data
3467
 * @param int|bool $length The minimum length of $data or false. This is to
3468
 *	prevent reading beyond the end of $data. false to disable the check.
3469
 *
3470
 * Also be careful when using this function to read unsigned 32 bit integer
3471
 * because php might make it negative.
3472
 *
3473
 * @throws MWException If $data not long enough, or if unpack fails
3474
 * @return array Associative array of the extracted data
3475
 */
3476
function wfUnpack( $format, $data, $length = false ) {
3477
	if ( $length !== false ) {
3478
		$realLen = strlen( $data );
3479
		if ( $realLen < $length ) {
3480
			throw new MWException( "Tried to use wfUnpack on a "
3481
				. "string of length $realLen, but needed one "
3482
				. "of at least length $length."
3483
			);
3484
		}
3485
	}
3486
3487
	MediaWiki\suppressWarnings();
3488
	$result = unpack( $format, $data );
3489
	MediaWiki\restoreWarnings();
3490
3491
	if ( $result === false ) {
3492
		// If it cannot extract the packed data.
3493
		throw new MWException( "unpack could not unpack binary data" );
3494
	}
3495
	return $result;
3496
}
3497
3498
/**
3499
 * Determine if an image exists on the 'bad image list'.
3500
 *
3501
 * The format of MediaWiki:Bad_image_list is as follows:
3502
 *    * Only list items (lines starting with "*") are considered
3503
 *    * The first link on a line must be a link to a bad image
3504
 *    * Any subsequent links on the same line are considered to be exceptions,
3505
 *      i.e. articles where the image may occur inline.
3506
 *
3507
 * @param string $name The image name to check
3508
 * @param Title|bool $contextTitle The page on which the image occurs, if known
3509
 * @param string $blacklist Wikitext of a file blacklist
3510
 * @return bool
3511
 */
3512
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
3513
	# Handle redirects; callers almost always hit wfFindFile() anyway,
3514
	# so just use that method because it has a fast process cache.
3515
	$file = wfFindFile( $name ); // get the final name
3516
	$name = $file ? $file->getTitle()->getDBkey() : $name;
3517
3518
	# Run the extension hook
3519
	$bad = false;
3520
	if ( !Hooks::run( 'BadImage', [ $name, &$bad ] ) ) {
3521
		return $bad;
3522
	}
3523
3524
	$cache = ObjectCache::getLocalServerInstance( 'hash' );
3525
	$key = wfMemcKey( 'bad-image-list', ( $blacklist === null ) ? 'default' : md5( $blacklist ) );
3526
	$badImages = $cache->get( $key );
3527
3528
	if ( $badImages === false ) { // cache miss
3529
		if ( $blacklist === null ) {
3530
			$blacklist = wfMessage( 'bad_image_list' )->inContentLanguage()->plain(); // site list
3531
		}
3532
		# Build the list now
3533
		$badImages = [];
3534
		$lines = explode( "\n", $blacklist );
3535
		foreach ( $lines as $line ) {
3536
			# List items only
3537
			if ( substr( $line, 0, 1 ) !== '*' ) {
3538
				continue;
3539
			}
3540
3541
			# Find all links
3542
			$m = [];
3543
			if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
3544
				continue;
3545
			}
3546
3547
			$exceptions = [];
3548
			$imageDBkey = false;
3549
			foreach ( $m[1] as $i => $titleText ) {
3550
				$title = Title::newFromText( $titleText );
3551
				if ( !is_null( $title ) ) {
3552
					if ( $i == 0 ) {
3553
						$imageDBkey = $title->getDBkey();
3554
					} else {
3555
						$exceptions[$title->getPrefixedDBkey()] = true;
3556
					}
3557
				}
3558
			}
3559
3560
			if ( $imageDBkey !== false ) {
3561
				$badImages[$imageDBkey] = $exceptions;
3562
			}
3563
		}
3564
		$cache->set( $key, $badImages, 60 );
3565
	}
3566
3567
	$contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false;
0 ignored issues
show
Bug introduced by
It seems like $contextTitle is not always an object, but can also be of type boolean. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
3568
	$bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
3569
3570
	return $bad;
3571
}
3572
3573
/**
3574
 * Determine whether the client at a given source IP is likely to be able to
3575
 * access the wiki via HTTPS.
3576
 *
3577
 * @param string $ip The IPv4/6 address in the normal human-readable form
3578
 * @return bool
3579
 */
3580
function wfCanIPUseHTTPS( $ip ) {
3581
	$canDo = true;
3582
	Hooks::run( 'CanIPUseHTTPS', [ $ip, &$canDo ] );
3583
	return !!$canDo;
3584
}
3585
3586
/**
3587
 * Determine input string is represents as infinity
3588
 *
3589
 * @param string $str The string to determine
3590
 * @return bool
3591
 * @since 1.25
3592
 */
3593
function wfIsInfinity( $str ) {
3594
	$infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ];
3595
	return in_array( $str, $infinityValues );
3596
}
3597
3598
/**
3599
 * Returns true if these thumbnail parameters match one that MediaWiki
3600
 * requests from file description pages and/or parser output.
3601
 *
3602
 * $params is considered non-standard if they involve a non-standard
3603
 * width or any non-default parameters aside from width and page number.
3604
 * The number of possible files with standard parameters is far less than
3605
 * that of all combinations; rate-limiting for them can thus be more generious.
3606
 *
3607
 * @param File $file
3608
 * @param array $params
3609
 * @return bool
3610
 * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25
3611
 */
3612
function wfThumbIsStandard( File $file, array $params ) {
3613
	global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages;
3614
3615
	$multipliers = [ 1 ];
3616
	if ( $wgResponsiveImages ) {
3617
		// These available sizes are hardcoded currently elsewhere in MediaWiki.
3618
		// @see Linker::processResponsiveImages
3619
		$multipliers[] = 1.5;
3620
		$multipliers[] = 2;
3621
	}
3622
3623
	$handler = $file->getHandler();
3624
	if ( !$handler || !isset( $params['width'] ) ) {
3625
		return false;
3626
	}
3627
3628
	$basicParams = [];
3629
	if ( isset( $params['page'] ) ) {
3630
		$basicParams['page'] = $params['page'];
3631
	}
3632
3633
	$thumbLimits = [];
3634
	$imageLimits = [];
3635
	// Expand limits to account for multipliers
3636
	foreach ( $multipliers as $multiplier ) {
3637
		$thumbLimits = array_merge( $thumbLimits, array_map(
3638
			function ( $width ) use ( $multiplier ) {
3639
				return round( $width * $multiplier );
3640
			}, $wgThumbLimits )
3641
		);
3642
		$imageLimits = array_merge( $imageLimits, array_map(
3643
			function ( $pair ) use ( $multiplier ) {
3644
				return [
3645
					round( $pair[0] * $multiplier ),
3646
					round( $pair[1] * $multiplier ),
3647
				];
3648
			}, $wgImageLimits )
3649
		);
3650
	}
3651
3652
	// Check if the width matches one of $wgThumbLimits
3653
	if ( in_array( $params['width'], $thumbLimits ) ) {
3654
		$normalParams = $basicParams + [ 'width' => $params['width'] ];
3655
		// Append any default values to the map (e.g. "lossy", "lossless", ...)
3656
		$handler->normaliseParams( $file, $normalParams );
3657
	} else {
3658
		// If not, then check if the width matchs one of $wgImageLimits
3659
		$match = false;
3660
		foreach ( $imageLimits as $pair ) {
3661
			$normalParams = $basicParams + [ 'width' => $pair[0], 'height' => $pair[1] ];
3662
			// Decide whether the thumbnail should be scaled on width or height.
3663
			// Also append any default values to the map (e.g. "lossy", "lossless", ...)
3664
			$handler->normaliseParams( $file, $normalParams );
3665
			// Check if this standard thumbnail size maps to the given width
3666
			if ( $normalParams['width'] == $params['width'] ) {
3667
				$match = true;
3668
				break;
3669
			}
3670
		}
3671
		if ( !$match ) {
3672
			return false; // not standard for description pages
3673
		}
3674
	}
3675
3676
	// Check that the given values for non-page, non-width, params are just defaults
3677
	foreach ( $params as $key => $value ) {
3678
		if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
3679
			return false;
3680
		}
3681
	}
3682
3683
	return true;
3684
}
3685
3686
/**
3687
 * Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
3688
 *
3689
 * Values that exist in both values will be combined with += (all values of the array
3690
 * of $newValues will be added to the values of the array of $baseArray, while values,
3691
 * that exists in both, the value of $baseArray will be used).
3692
 *
3693
 * @param array $baseArray The array where you want to add the values of $newValues to
3694
 * @param array $newValues An array with new values
3695
 * @return array The combined array
3696
 * @since 1.26
3697
 */
3698
function wfArrayPlus2d( array $baseArray, array $newValues ) {
3699
	// First merge items that are in both arrays
3700
	foreach ( $baseArray as $name => &$groupVal ) {
3701
		if ( isset( $newValues[$name] ) ) {
3702
			$groupVal += $newValues[$name];
3703
		}
3704
	}
3705
	// Now add items that didn't exist yet
3706
	$baseArray += $newValues;
3707
3708
	return $baseArray;
3709
}
3710