Completed
Branch master (33c24b)
by
unknown
30:03
created

GlobalFunctions.php ➔ wfResetOutputBuffers()   D

Complexity

Conditions 10
Paths 38

Size

Total Lines 41
Code Lines 22

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 10
eloc 22
nc 38
nop 1
dl 0
loc 41
rs 4.8196

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
31
// Hide compatibility functions from Doxygen
32
/// @cond
33
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
 *		array( array( 'x' ) ),
227
 *		array( array( 'x', '2' ) ),
228
 *		array( array( 'x' ) ),
229
 *		array( array( 'y' ) )
230
 *	);
231
 * returns:
232
 * 		array(
233
 *   		array( 'x', '2' ),
234
 *   		array( 'x' ),
235
 *   		array( '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 (Bug 22709),
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
			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 (bug 32168)
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 (bug 32168)
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[] Bits of the URL in an associative array, per PHP docs
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") == array( '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 View Code Duplication
		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 bug 28627) */
855
	if ( !isset( $bits['host'] ) ) {
856
		$bits['host'] = '';
857
858
		// bug 45069
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->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...
Deprecated Code introduced by
The method RequestContext::getStats() has been deprecated with message: since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

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

Loading history...
1200
		} catch ( Exception $ex ) {
1201
			MWExceptionHandler::logException( $ex );
1202
		}
1203
	}
1204
1205
	# Profiling must actually be enabled...
1206
	if ( $profiler instanceof ProfilerStub ) {
1207
		return;
1208
	}
1209
1210
	if ( isset( $wgDebugLogGroups['profileoutput'] )
1211
		&& $wgDebugLogGroups['profileoutput'] === false
1212
	) {
1213
		// Explicitly disabled
1214
		return;
1215
	}
1216
	if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
1217
		return;
1218
	}
1219
1220
	$ctx = [ 'elapsed' => $request->getElapsedTime() ];
1221
	if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1222
		$ctx['forwarded_for'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
1223
	}
1224
	if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
1225
		$ctx['client_ip'] = $_SERVER['HTTP_CLIENT_IP'];
1226
	}
1227
	if ( !empty( $_SERVER['HTTP_FROM'] ) ) {
1228
		$ctx['from'] = $_SERVER['HTTP_FROM'];
1229
	}
1230
	if ( isset( $ctx['forwarded_for'] ) ||
1231
		isset( $ctx['client_ip'] ) ||
1232
		isset( $ctx['from'] ) ) {
1233
		$ctx['proxy'] = $_SERVER['REMOTE_ADDR'];
1234
	}
1235
1236
	// Don't load $wgUser at this late stage just for statistics purposes
1237
	// @todo FIXME: We can detect some anons even if it is not loaded.
1238
	// See User::getId()
1239
	$user = $context->getUser();
1240
	$ctx['anon'] = $user->isItemLoaded( 'id' ) && $user->isAnon();
1241
1242
	// Command line script uses a FauxRequest object which does not have
1243
	// any knowledge about an URL and throw an exception instead.
1244
	try {
1245
		$ctx['url'] = urldecode( $request->getRequestURL() );
1246
	} catch ( Exception $ignored ) {
1247
		// no-op
1248
	}
1249
1250
	$ctx['output'] = $profiler->getOutput();
1251
1252
	$log = LoggerFactory::getInstance( 'profileoutput' );
1253
	$log->info( "Elapsed: {elapsed}; URL: <{url}>\n{output}", $ctx );
1254
}
1255
1256
/**
1257
 * Increment a statistics counter
1258
 *
1259
 * @param string $key
1260
 * @param int $count
1261
 * @return void
1262
 */
1263
function wfIncrStats( $key, $count = 1 ) {
1264
	$stats = RequestContext::getMain()->getStats();
0 ignored issues
show
Deprecated Code introduced by
The method RequestContext::getStats() has been deprecated with message: since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

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

Loading history...
1265
	$stats->updateCount( $key, $count );
1266
}
1267
1268
/**
1269
 * Check whether the wiki is in read-only mode.
1270
 *
1271
 * @return bool
1272
 */
1273
function wfReadOnly() {
1274
	return wfReadOnlyReason() !== false;
1275
}
1276
1277
/**
1278
 * Check if the site is in read-only mode and return the message if so
1279
 *
1280
 * This checks wfConfiguredReadOnlyReason() and the main load balancer
1281
 * for slave lag. This may result in DB_SLAVE connection being made.
1282
 *
1283
 * @return string|bool String when in read-only mode; false otherwise
1284
 */
1285
function wfReadOnlyReason() {
1286
	$readOnly = wfConfiguredReadOnlyReason();
1287
	if ( $readOnly !== false ) {
1288
		return $readOnly;
1289
	}
1290
1291
	static $lbReadOnly = null;
1292
	if ( $lbReadOnly === null ) {
1293
		// Callers use this method to be aware that data presented to a user
1294
		// may be very stale and thus allowing submissions can be problematic.
1295
		$lbReadOnly = wfGetLB()->getReadOnlyReason();
1296
	}
1297
1298
	return $lbReadOnly;
1299
}
1300
1301
/**
1302
 * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
1303
 *
1304
 * @return string|bool String when in read-only mode; false otherwise
1305
 * @since 1.27
1306
 */
1307
function wfConfiguredReadOnlyReason() {
1308
	global $wgReadOnly, $wgReadOnlyFile;
1309
1310
	if ( $wgReadOnly === null ) {
1311
		// Set $wgReadOnly for faster access next time
1312
		if ( is_file( $wgReadOnlyFile ) && filesize( $wgReadOnlyFile ) > 0 ) {
1313
			$wgReadOnly = file_get_contents( $wgReadOnlyFile );
1314
		} else {
1315
			$wgReadOnly = false;
1316
		}
1317
	}
1318
1319
	return $wgReadOnly;
1320
}
1321
1322
/**
1323
 * Return a Language object from $langcode
1324
 *
1325
 * @param Language|string|bool $langcode Either:
1326
 *                  - a Language object
1327
 *                  - code of the language to get the message for, if it is
1328
 *                    a valid code create a language for that language, if
1329
 *                    it is a string but not a valid code then make a basic
1330
 *                    language object
1331
 *                  - a boolean: if it's false then use the global object for
1332
 *                    the current user's language (as a fallback for the old parameter
1333
 *                    functionality), or if it is true then use global object
1334
 *                    for the wiki's content language.
1335
 * @return Language
1336
 */
1337
function wfGetLangObj( $langcode = false ) {
1338
	# Identify which language to get or create a language object for.
1339
	# Using is_object here due to Stub objects.
1340
	if ( is_object( $langcode ) ) {
1341
		# Great, we already have the object (hopefully)!
1342
		return $langcode;
1343
	}
1344
1345
	global $wgContLang, $wgLanguageCode;
1346
	if ( $langcode === true || $langcode === $wgLanguageCode ) {
1347
		# $langcode is the language code of the wikis content language object.
1348
		# or it is a boolean and value is true
1349
		return $wgContLang;
1350
	}
1351
1352
	global $wgLang;
1353
	if ( $langcode === false || $langcode === $wgLang->getCode() ) {
1354
		# $langcode is the language code of user language object.
1355
		# or it was a boolean and value is false
1356
		return $wgLang;
1357
	}
1358
1359
	$validCodes = array_keys( Language::fetchLanguageNames() );
1360
	if ( in_array( $langcode, $validCodes ) ) {
1361
		# $langcode corresponds to a valid language.
1362
		return Language::factory( $langcode );
0 ignored issues
show
Bug introduced by
It seems like $langcode defined by parameter $langcode on line 1337 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...
1363
	}
1364
1365
	# $langcode is a string, but not a valid language code; use content language.
1366
	wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
1367
	return $wgContLang;
1368
}
1369
1370
/**
1371
 * This is the function for getting translated interface messages.
1372
 *
1373
 * @see Message class for documentation how to use them.
1374
 * @see https://www.mediawiki.org/wiki/Manual:Messages_API
1375
 *
1376
 * This function replaces all old wfMsg* functions.
1377
 *
1378
 * @param string|string[]|MessageSpecifier $key Message key, or array of keys, or a MessageSpecifier
1379
 * @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...
1380
 * @return Message
1381
 *
1382
 * @since 1.17
1383
 *
1384
 * @see Message::__construct
1385
 */
1386
function wfMessage( $key /*...*/ ) {
1387
	$params = func_get_args();
1388
	array_shift( $params );
1389
	if ( isset( $params[0] ) && is_array( $params[0] ) ) {
1390
		$params = $params[0];
1391
	}
1392
	return new Message( $key, $params );
1393
}
1394
1395
/**
1396
 * This function accepts multiple message keys and returns a message instance
1397
 * for the first message which is non-empty. If all messages are empty then an
1398
 * instance of the first message key is returned.
1399
 *
1400
 * @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...
1401
 * @return Message
1402
 *
1403
 * @since 1.18
1404
 *
1405
 * @see Message::newFallbackSequence
1406
 */
1407
function wfMessageFallback( /*...*/ ) {
1408
	$args = func_get_args();
1409
	return call_user_func_array( 'Message::newFallbackSequence', $args );
1410
}
1411
1412
/**
1413
 * Replace message parameter keys on the given formatted output.
1414
 *
1415
 * @param string $message
1416
 * @param array $args
1417
 * @return string
1418
 * @private
1419
 */
1420
function wfMsgReplaceArgs( $message, $args ) {
1421
	# Fix windows line-endings
1422
	# Some messages are split with explode("\n", $msg)
1423
	$message = str_replace( "\r", '', $message );
1424
1425
	// Replace arguments
1426
	if ( is_array( $args ) && $args ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1427
		if ( is_array( $args[0] ) ) {
1428
			$args = array_values( $args[0] );
1429
		}
1430
		$replacementKeys = [];
1431
		foreach ( $args as $n => $param ) {
1432
			$replacementKeys['$' . ( $n + 1 )] = $param;
1433
		}
1434
		$message = strtr( $message, $replacementKeys );
1435
	}
1436
1437
	return $message;
1438
}
1439
1440
/**
1441
 * Fetch server name for use in error reporting etc.
1442
 * Use real server name if available, so we know which machine
1443
 * in a server farm generated the current page.
1444
 *
1445
 * @return string
1446
 */
1447
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...
1448
	static $host;
1449
	if ( is_null( $host ) ) {
1450
1451
		# Hostname overriding
1452
		global $wgOverrideHostname;
1453
		if ( $wgOverrideHostname !== false ) {
1454
			# Set static and skip any detection
1455
			$host = $wgOverrideHostname;
1456
			return $host;
1457
		}
1458
1459
		if ( function_exists( 'posix_uname' ) ) {
1460
			// This function not present on Windows
1461
			$uname = posix_uname();
1462
		} else {
1463
			$uname = false;
1464
		}
1465
		if ( is_array( $uname ) && isset( $uname['nodename'] ) ) {
1466
			$host = $uname['nodename'];
1467
		} elseif ( getenv( 'COMPUTERNAME' ) ) {
1468
			# Windows computer name
1469
			$host = getenv( 'COMPUTERNAME' );
1470
		} else {
1471
			# This may be a virtual server.
1472
			$host = $_SERVER['SERVER_NAME'];
1473
		}
1474
	}
1475
	return $host;
1476
}
1477
1478
/**
1479
 * Returns a script tag that stores the amount of time it took MediaWiki to
1480
 * handle the request in milliseconds as 'wgBackendResponseTime'.
1481
 *
1482
 * If $wgShowHostnames is true, the script will also set 'wgHostname' to the
1483
 * hostname of the server handling the request.
1484
 *
1485
 * @return string
1486
 */
1487
function wfReportTime() {
1488
	global $wgRequestTime, $wgShowHostnames;
1489
1490
	$responseTime = round( ( microtime( true ) - $wgRequestTime ) * 1000 );
1491
	$reportVars = [ 'wgBackendResponseTime' => $responseTime ];
1492
	if ( $wgShowHostnames ) {
1493
		$reportVars['wgHostname'] = wfHostname();
1494
	}
1495
	return Skin::makeVariablesScript( $reportVars );
1496
}
1497
1498
/**
1499
 * Safety wrapper for debug_backtrace().
1500
 *
1501
 * Will return an empty array if debug_backtrace is disabled, otherwise
1502
 * the output from debug_backtrace() (trimmed).
1503
 *
1504
 * @param int $limit This parameter can be used to limit the number of stack frames returned
1505
 *
1506
 * @return array Array of backtrace information
1507
 */
1508
function wfDebugBacktrace( $limit = 0 ) {
1509
	static $disabled = null;
1510
1511
	if ( is_null( $disabled ) ) {
1512
		$disabled = !function_exists( 'debug_backtrace' );
1513
		if ( $disabled ) {
1514
			wfDebug( "debug_backtrace() is disabled\n" );
1515
		}
1516
	}
1517
	if ( $disabled ) {
1518
		return [];
1519
	}
1520
1521
	if ( $limit ) {
1522
		return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 );
1523
	} else {
1524
		return array_slice( debug_backtrace(), 1 );
1525
	}
1526
}
1527
1528
/**
1529
 * Get a debug backtrace as a string
1530
 *
1531
 * @param bool|null $raw If true, the return value is plain text. If false, HTML.
1532
 *   Defaults to $wgCommandLineMode if unset.
1533
 * @return string
1534
 * @since 1.25 Supports $raw parameter.
1535
 */
1536
function wfBacktrace( $raw = null ) {
1537
	global $wgCommandLineMode;
1538
1539
	if ( $raw === null ) {
1540
		$raw = $wgCommandLineMode;
1541
	}
1542
1543
	if ( $raw ) {
1544
		$frameFormat = "%s line %s calls %s()\n";
1545
		$traceFormat = "%s";
1546
	} else {
1547
		$frameFormat = "<li>%s line %s calls %s()</li>\n";
1548
		$traceFormat = "<ul>\n%s</ul>\n";
1549
	}
1550
1551
	$frames = array_map( function ( $frame ) use ( $frameFormat ) {
1552
		$file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-';
1553
		$line = isset( $frame['line'] ) ? $frame['line'] : '-';
1554
		$call = $frame['function'];
1555
		if ( !empty( $frame['class'] ) ) {
1556
			$call = $frame['class'] . $frame['type'] . $call;
1557
		}
1558
		return sprintf( $frameFormat, $file, $line, $call );
1559
	}, wfDebugBacktrace() );
1560
1561
	return sprintf( $traceFormat, implode( '', $frames ) );
1562
}
1563
1564
/**
1565
 * Get the name of the function which called this function
1566
 * wfGetCaller( 1 ) is the function with the wfGetCaller() call (ie. __FUNCTION__)
1567
 * wfGetCaller( 2 ) [default] is the caller of the function running wfGetCaller()
1568
 * wfGetCaller( 3 ) is the parent of that.
1569
 *
1570
 * @param int $level
1571
 * @return string
1572
 */
1573
function wfGetCaller( $level = 2 ) {
1574
	$backtrace = wfDebugBacktrace( $level + 1 );
1575
	if ( isset( $backtrace[$level] ) ) {
1576
		return wfFormatStackFrame( $backtrace[$level] );
1577
	} else {
1578
		return 'unknown';
1579
	}
1580
}
1581
1582
/**
1583
 * Return a string consisting of callers in the stack. Useful sometimes
1584
 * for profiling specific points.
1585
 *
1586
 * @param int $limit The maximum depth of the stack frame to return, or false for the entire stack.
1587
 * @return string
1588
 */
1589
function wfGetAllCallers( $limit = 3 ) {
1590
	$trace = array_reverse( wfDebugBacktrace() );
1591
	if ( !$limit || $limit > count( $trace ) - 1 ) {
1592
		$limit = count( $trace ) - 1;
1593
	}
1594
	$trace = array_slice( $trace, -$limit - 1, $limit );
1595
	return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
1596
}
1597
1598
/**
1599
 * Return a string representation of frame
1600
 *
1601
 * @param array $frame
1602
 * @return string
1603
 */
1604
function wfFormatStackFrame( $frame ) {
1605
	if ( !isset( $frame['function'] ) ) {
1606
		return 'NO_FUNCTION_GIVEN';
1607
	}
1608
	return isset( $frame['class'] ) && isset( $frame['type'] ) ?
1609
		$frame['class'] . $frame['type'] . $frame['function'] :
1610
		$frame['function'];
1611
}
1612
1613
/* Some generic result counters, pulled out of SearchEngine */
1614
1615
/**
1616
 * @todo document
1617
 *
1618
 * @param int $offset
1619
 * @param int $limit
1620
 * @return string
1621
 */
1622
function wfShowingResults( $offset, $limit ) {
1623
	return wfMessage( 'showingresults' )->numParams( $limit, $offset + 1 )->parse();
1624
}
1625
1626
/**
1627
 * @todo document
1628
 * @todo FIXME: We may want to blacklist some broken browsers
1629
 *
1630
 * @param bool $force
1631
 * @return bool Whereas client accept gzip compression
1632
 */
1633
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...
1634
	static $result = null;
1635
	if ( $result === null || $force ) {
1636
		$result = false;
1637
		if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
1638
			# @todo FIXME: We may want to blacklist some broken browsers
1639
			$m = [];
1640
			if ( preg_match(
1641
					'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
1642
					$_SERVER['HTTP_ACCEPT_ENCODING'],
1643
					$m
1644
				)
1645
			) {
1646
				if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
1647
					$result = false;
1648
					return $result;
1649
				}
1650
				wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
1651
				$result = true;
1652
			}
1653
		}
1654
	}
1655
	return $result;
1656
}
1657
1658
/**
1659
 * Escapes the given text so that it may be output using addWikiText()
1660
 * without any linking, formatting, etc. making its way through. This
1661
 * is achieved by substituting certain characters with HTML entities.
1662
 * As required by the callers, "<nowiki>" is not used.
1663
 *
1664
 * @param string $text Text to be escaped
1665
 * @return string
1666
 */
1667
function wfEscapeWikiText( $text ) {
1668
	static $repl = null, $repl2 = null;
1669
	if ( $repl === null ) {
1670
		$repl = [
1671
			'"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
1672
			'=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
1673
			'{' => '&#123;', '|' => '&#124;', '}' => '&#125;', ';' => '&#59;',
1674
			"\n#" => "\n&#35;", "\r#" => "\r&#35;",
1675
			"\n*" => "\n&#42;", "\r*" => "\r&#42;",
1676
			"\n:" => "\n&#58;", "\r:" => "\r&#58;",
1677
			"\n " => "\n&#32;", "\r " => "\r&#32;",
1678
			"\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
1679
			"\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
1680
			"\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
1681
			"\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
1682
			'__' => '_&#95;', '://' => '&#58;//',
1683
		];
1684
1685
		// We have to catch everything "\s" matches in PCRE
1686
		foreach ( [ 'ISBN', 'RFC', 'PMID' ] as $magic ) {
1687
			$repl["$magic "] = "$magic&#32;";
1688
			$repl["$magic\t"] = "$magic&#9;";
1689
			$repl["$magic\r"] = "$magic&#13;";
1690
			$repl["$magic\n"] = "$magic&#10;";
1691
			$repl["$magic\f"] = "$magic&#12;";
1692
		}
1693
1694
		// And handle protocols that don't use "://"
1695
		global $wgUrlProtocols;
1696
		$repl2 = [];
1697
		foreach ( $wgUrlProtocols as $prot ) {
1698
			if ( substr( $prot, -1 ) === ':' ) {
1699
				$repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
1700
			}
1701
		}
1702
		$repl2 = $repl2 ? '/\b(' . implode( '|', $repl2 ) . '):/i' : '/^(?!)/';
1703
	}
1704
	$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...
1705
	$text = preg_replace( $repl2, '$1&#58;', $text );
1706
	return $text;
1707
}
1708
1709
/**
1710
 * Sets dest to source and returns the original value of dest
1711
 * If source is NULL, it just returns the value, it doesn't set the variable
1712
 * If force is true, it will set the value even if source is NULL
1713
 *
1714
 * @param mixed $dest
1715
 * @param mixed $source
1716
 * @param bool $force
1717
 * @return mixed
1718
 */
1719
function wfSetVar( &$dest, $source, $force = false ) {
1720
	$temp = $dest;
1721
	if ( !is_null( $source ) || $force ) {
1722
		$dest = $source;
1723
	}
1724
	return $temp;
1725
}
1726
1727
/**
1728
 * As for wfSetVar except setting a bit
1729
 *
1730
 * @param int $dest
1731
 * @param int $bit
1732
 * @param bool $state
1733
 *
1734
 * @return bool
1735
 */
1736
function wfSetBit( &$dest, $bit, $state = true ) {
1737
	$temp = (bool)( $dest & $bit );
1738
	if ( !is_null( $state ) ) {
1739
		if ( $state ) {
1740
			$dest |= $bit;
1741
		} else {
1742
			$dest &= ~$bit;
1743
		}
1744
	}
1745
	return $temp;
1746
}
1747
1748
/**
1749
 * A wrapper around the PHP function var_export().
1750
 * Either print it or add it to the regular output ($wgOut).
1751
 *
1752
 * @param mixed $var A PHP variable to dump.
1753
 */
1754
function wfVarDump( $var ) {
1755
	global $wgOut;
1756
	$s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
1757
	if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
1758
		print $s;
1759
	} else {
1760
		$wgOut->addHTML( $s );
1761
	}
1762
}
1763
1764
/**
1765
 * Provide a simple HTTP error.
1766
 *
1767
 * @param int|string $code
1768
 * @param string $label
1769
 * @param string $desc
1770
 */
1771
function wfHttpError( $code, $label, $desc ) {
1772
	global $wgOut;
1773
	HttpStatus::header( $code );
1774
	if ( $wgOut ) {
1775
		$wgOut->disable();
1776
		$wgOut->sendCacheControl();
1777
	}
1778
1779
	header( 'Content-type: text/html; charset=utf-8' );
1780
	print '<!DOCTYPE html>' .
1781
		'<html><head><title>' .
1782
		htmlspecialchars( $label ) .
1783
		'</title></head><body><h1>' .
1784
		htmlspecialchars( $label ) .
1785
		'</h1><p>' .
1786
		nl2br( htmlspecialchars( $desc ) ) .
1787
		"</p></body></html>\n";
1788
}
1789
1790
/**
1791
 * Clear away any user-level output buffers, discarding contents.
1792
 *
1793
 * Suitable for 'starting afresh', for instance when streaming
1794
 * relatively large amounts of data without buffering, or wanting to
1795
 * output image files without ob_gzhandler's compression.
1796
 *
1797
 * The optional $resetGzipEncoding parameter controls suppression of
1798
 * the Content-Encoding header sent by ob_gzhandler; by default it
1799
 * is left. See comments for wfClearOutputBuffers() for why it would
1800
 * be used.
1801
 *
1802
 * Note that some PHP configuration options may add output buffer
1803
 * layers which cannot be removed; these are left in place.
1804
 *
1805
 * @param bool $resetGzipEncoding
1806
 */
1807
function wfResetOutputBuffers( $resetGzipEncoding = true ) {
1808
	if ( $resetGzipEncoding ) {
1809
		// Suppress Content-Encoding and Content-Length
1810
		// headers from 1.10+s wfOutputHandler
1811
		global $wgDisableOutputCompression;
1812
		$wgDisableOutputCompression = true;
1813
	}
1814
	while ( $status = ob_get_status() ) {
1815
		if ( isset( $status['flags'] ) ) {
1816
			$flags = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
1817
			$deleteable = ( $status['flags'] & $flags ) === $flags;
1818
		} elseif ( isset( $status['del'] ) ) {
1819
			$deleteable = $status['del'];
1820
		} else {
1821
			// Guess that any PHP-internal setting can't be removed.
1822
			$deleteable = $status['type'] !== 0; /* PHP_OUTPUT_HANDLER_INTERNAL */
1823
		}
1824
		if ( !$deleteable ) {
1825
			// Give up, and hope the result doesn't break
1826
			// output behavior.
1827
			break;
1828
		}
1829
		if ( $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) {
1830
			// Unit testing barrier to prevent this function from breaking PHPUnit.
1831
			break;
1832
		}
1833
		if ( !ob_end_clean() ) {
1834
			// Could not remove output buffer handler; abort now
1835
			// to avoid getting in some kind of infinite loop.
1836
			break;
1837
		}
1838
		if ( $resetGzipEncoding ) {
1839
			if ( $status['name'] == 'ob_gzhandler' ) {
1840
				// Reset the 'Content-Encoding' field set by this handler
1841
				// so we can start fresh.
1842
				header_remove( 'Content-Encoding' );
1843
				break;
1844
			}
1845
		}
1846
	}
1847
}
1848
1849
/**
1850
 * More legible than passing a 'false' parameter to wfResetOutputBuffers():
1851
 *
1852
 * Clear away output buffers, but keep the Content-Encoding header
1853
 * produced by ob_gzhandler, if any.
1854
 *
1855
 * This should be used for HTTP 304 responses, where you need to
1856
 * preserve the Content-Encoding header of the real result, but
1857
 * also need to suppress the output of ob_gzhandler to keep to spec
1858
 * and avoid breaking Firefox in rare cases where the headers and
1859
 * body are broken over two packets.
1860
 */
1861
function wfClearOutputBuffers() {
1862
	wfResetOutputBuffers( false );
1863
}
1864
1865
/**
1866
 * Converts an Accept-* header into an array mapping string values to quality
1867
 * factors
1868
 *
1869
 * @param string $accept
1870
 * @param string $def Default
1871
 * @return float[] Associative array of string => float pairs
1872
 */
1873
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
1874
	# No arg means accept anything (per HTTP spec)
1875
	if ( !$accept ) {
1876
		return [ $def => 1.0 ];
1877
	}
1878
1879
	$prefs = [];
1880
1881
	$parts = explode( ',', $accept );
1882
1883
	foreach ( $parts as $part ) {
1884
		# @todo FIXME: Doesn't deal with params like 'text/html; level=1'
1885
		$values = explode( ';', trim( $part ) );
1886
		$match = [];
1887
		if ( count( $values ) == 1 ) {
1888
			$prefs[$values[0]] = 1.0;
1889
		} elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
1890
			$prefs[$values[0]] = floatval( $match[1] );
1891
		}
1892
	}
1893
1894
	return $prefs;
1895
}
1896
1897
/**
1898
 * Checks if a given MIME type matches any of the keys in the given
1899
 * array. Basic wildcards are accepted in the array keys.
1900
 *
1901
 * Returns the matching MIME type (or wildcard) if a match, otherwise
1902
 * NULL if no match.
1903
 *
1904
 * @param string $type
1905
 * @param array $avail
1906
 * @return string
1907
 * @private
1908
 */
1909
function mimeTypeMatch( $type, $avail ) {
1910
	if ( array_key_exists( $type, $avail ) ) {
1911
		return $type;
1912
	} else {
1913
		$mainType = explode( '/', $type )[0];
1914
		if ( array_key_exists( "$mainType/*", $avail ) ) {
1915
			return "$mainType/*";
1916
		} elseif ( array_key_exists( '*/*', $avail ) ) {
1917
			return '*/*';
1918
		} else {
1919
			return null;
1920
		}
1921
	}
1922
}
1923
1924
/**
1925
 * Returns the 'best' match between a client's requested internet media types
1926
 * and the server's list of available types. Each list should be an associative
1927
 * array of type to preference (preference is a float between 0.0 and 1.0).
1928
 * Wildcards in the types are acceptable.
1929
 *
1930
 * @param array $cprefs Client's acceptable type list
1931
 * @param array $sprefs Server's offered types
1932
 * @return string
1933
 *
1934
 * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
1935
 * XXX: generalize to negotiate other stuff
1936
 */
1937
function wfNegotiateType( $cprefs, $sprefs ) {
1938
	$combine = [];
1939
1940 View Code Duplication
	foreach ( array_keys( $sprefs ) as $type ) {
1941
		$subType = explode( '/', $type )[1];
1942
		if ( $subType != '*' ) {
1943
			$ckey = mimeTypeMatch( $type, $cprefs );
1944
			if ( $ckey ) {
1945
				$combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1946
			}
1947
		}
1948
	}
1949
1950 View Code Duplication
	foreach ( array_keys( $cprefs ) as $type ) {
1951
		$subType = explode( '/', $type )[1];
1952
		if ( $subType != '*' && !array_key_exists( $type, $sprefs ) ) {
1953
			$skey = mimeTypeMatch( $type, $sprefs );
1954
			if ( $skey ) {
1955
				$combine[$type] = $sprefs[$skey] * $cprefs[$type];
1956
			}
1957
		}
1958
	}
1959
1960
	$bestq = 0;
1961
	$besttype = null;
1962
1963
	foreach ( array_keys( $combine ) as $type ) {
1964
		if ( $combine[$type] > $bestq ) {
1965
			$besttype = $type;
1966
			$bestq = $combine[$type];
1967
		}
1968
	}
1969
1970
	return $besttype;
1971
}
1972
1973
/**
1974
 * Reference-counted warning suppression
1975
 *
1976
 * @deprecated since 1.26, use MediaWiki\suppressWarnings() directly
1977
 * @param bool $end
1978
 */
1979
function wfSuppressWarnings( $end = false ) {
1980
	MediaWiki\suppressWarnings( $end );
1981
}
1982
1983
/**
1984
 * @deprecated since 1.26, use MediaWiki\restoreWarnings() directly
1985
 * Restore error level to previous value
1986
 */
1987
function wfRestoreWarnings() {
1988
	MediaWiki\suppressWarnings( true );
1989
}
1990
1991
# Autodetect, convert and provide timestamps of various types
1992
1993
/**
1994
 * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
1995
 */
1996
define( 'TS_UNIX', 0 );
1997
1998
/**
1999
 * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
2000
 */
2001
define( 'TS_MW', 1 );
2002
2003
/**
2004
 * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
2005
 */
2006
define( 'TS_DB', 2 );
2007
2008
/**
2009
 * RFC 2822 format, for E-mail and HTTP headers
2010
 */
2011
define( 'TS_RFC2822', 3 );
2012
2013
/**
2014
 * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
2015
 *
2016
 * This is used by Special:Export
2017
 */
2018
define( 'TS_ISO_8601', 4 );
2019
2020
/**
2021
 * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
2022
 *
2023
 * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
2024
 *       DateTime tag and page 36 for the DateTimeOriginal and
2025
 *       DateTimeDigitized tags.
2026
 */
2027
define( 'TS_EXIF', 5 );
2028
2029
/**
2030
 * Oracle format time.
2031
 */
2032
define( 'TS_ORACLE', 6 );
2033
2034
/**
2035
 * Postgres format time.
2036
 */
2037
define( 'TS_POSTGRES', 7 );
2038
2039
/**
2040
 * ISO 8601 basic format with no timezone: 19860209T200000Z.  This is used by ResourceLoader
2041
 */
2042
define( 'TS_ISO_8601_BASIC', 9 );
2043
2044
/**
2045
 * Get a timestamp string in one of various formats
2046
 *
2047
 * @param mixed $outputtype A timestamp in one of the supported formats, the
2048
 *   function will autodetect which format is supplied and act accordingly.
2049
 * @param mixed $ts Optional timestamp to convert, default 0 for the current time
2050
 * @return string|bool String / false The same date in the format specified in $outputtype or false
2051
 */
2052
function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
2053
	try {
2054
		$timestamp = new MWTimestamp( $ts );
2055
		return $timestamp->getTimestamp( $outputtype );
2056
	} catch ( TimestampException $e ) {
2057
		wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
2058
		return false;
2059
	}
2060
}
2061
2062
/**
2063
 * Return a formatted timestamp, or null if input is null.
2064
 * For dealing with nullable timestamp columns in the database.
2065
 *
2066
 * @param int $outputtype
2067
 * @param string $ts
2068
 * @return string
2069
 */
2070
function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
2071
	if ( is_null( $ts ) ) {
2072
		return null;
2073
	} else {
2074
		return wfTimestamp( $outputtype, $ts );
2075
	}
2076
}
2077
2078
/**
2079
 * Convenience function; returns MediaWiki timestamp for the present time.
2080
 *
2081
 * @return string
2082
 */
2083
function wfTimestampNow() {
2084
	# return NOW
2085
	return wfTimestamp( TS_MW, time() );
2086
}
2087
2088
/**
2089
 * Check if the operating system is Windows
2090
 *
2091
 * @return bool True if it's Windows, false otherwise.
2092
 */
2093
function wfIsWindows() {
2094
	static $isWindows = null;
2095
	if ( $isWindows === null ) {
2096
		$isWindows = strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN';
2097
	}
2098
	return $isWindows;
2099
}
2100
2101
/**
2102
 * Check if we are running under HHVM
2103
 *
2104
 * @return bool
2105
 */
2106
function wfIsHHVM() {
2107
	return defined( 'HHVM_VERSION' );
2108
}
2109
2110
/**
2111
 * Tries to get the system directory for temporary files. First
2112
 * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
2113
 * environment variables are then checked in sequence, then
2114
 * sys_get_temp_dir(), then upload_tmp_dir from php.ini.
2115
 *
2116
 * NOTE: When possible, use instead the tmpfile() function to create
2117
 * temporary files to avoid race conditions on file creation, etc.
2118
 *
2119
 * @return string
2120
 */
2121
function wfTempDir() {
2122
	global $wgTmpDirectory;
2123
2124
	if ( $wgTmpDirectory !== false ) {
2125
		return $wgTmpDirectory;
2126
	}
2127
2128
	$tmpDir = array_map( "getenv", [ 'TMPDIR', 'TMP', 'TEMP' ] );
2129
	$tmpDir[] = sys_get_temp_dir();
2130
	$tmpDir[] = ini_get( 'upload_tmp_dir' );
2131
2132
	foreach ( $tmpDir as $tmp ) {
2133
		if ( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
2134
			return $tmp;
2135
		}
2136
	}
2137
	throw new MWException( 'No writable temporary directory could be found. ' .
2138
		'Please set $wgTmpDirectory to a writable directory.' );
2139
}
2140
2141
/**
2142
 * Make directory, and make all parent directories if they don't exist
2143
 *
2144
 * @param string $dir Full path to directory to create
2145
 * @param int $mode Chmod value to use, default is $wgDirectoryMode
2146
 * @param string $caller Optional caller param for debugging.
2147
 * @throws MWException
2148
 * @return bool
2149
 */
2150
function wfMkdirParents( $dir, $mode = null, $caller = null ) {
2151
	global $wgDirectoryMode;
2152
2153
	if ( FileBackend::isStoragePath( $dir ) ) { // sanity
2154
		throw new MWException( __FUNCTION__ . " given storage path '$dir'." );
2155
	}
2156
2157
	if ( !is_null( $caller ) ) {
2158
		wfDebug( "$caller: called wfMkdirParents($dir)\n" );
2159
	}
2160
2161
	if ( strval( $dir ) === '' || is_dir( $dir ) ) {
2162
		return true;
2163
	}
2164
2165
	$dir = str_replace( [ '\\', '/' ], DIRECTORY_SEPARATOR, $dir );
2166
2167
	if ( is_null( $mode ) ) {
2168
		$mode = $wgDirectoryMode;
2169
	}
2170
2171
	// Turn off the normal warning, we're doing our own below
2172
	MediaWiki\suppressWarnings();
2173
	$ok = mkdir( $dir, $mode, true ); // PHP5 <3
2174
	MediaWiki\restoreWarnings();
2175
2176
	if ( !$ok ) {
2177
		// directory may have been created on another request since we last checked
2178
		if ( is_dir( $dir ) ) {
2179
			return true;
2180
		}
2181
2182
		// PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
2183
		wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) );
2184
	}
2185
	return $ok;
2186
}
2187
2188
/**
2189
 * Remove a directory and all its content.
2190
 * Does not hide error.
2191
 * @param string $dir
2192
 */
2193
function wfRecursiveRemoveDir( $dir ) {
2194
	wfDebug( __FUNCTION__ . "( $dir )\n" );
2195
	// taken from http://de3.php.net/manual/en/function.rmdir.php#98622
2196
	if ( is_dir( $dir ) ) {
2197
		$objects = scandir( $dir );
2198
		foreach ( $objects as $object ) {
2199
			if ( $object != "." && $object != ".." ) {
2200
				if ( filetype( $dir . '/' . $object ) == "dir" ) {
2201
					wfRecursiveRemoveDir( $dir . '/' . $object );
2202
				} else {
2203
					unlink( $dir . '/' . $object );
2204
				}
2205
			}
2206
		}
2207
		reset( $objects );
2208
		rmdir( $dir );
2209
	}
2210
}
2211
2212
/**
2213
 * @param int $nr The number to format
2214
 * @param int $acc The number of digits after the decimal point, default 2
2215
 * @param bool $round Whether or not to round the value, default true
2216
 * @return string
2217
 */
2218
function wfPercent( $nr, $acc = 2, $round = true ) {
2219
	$ret = sprintf( "%.${acc}f", $nr );
2220
	return $round ? round( $ret, $acc ) . '%' : "$ret%";
2221
}
2222
2223
/**
2224
 * Safety wrapper around ini_get() for boolean settings.
2225
 * The values returned from ini_get() are pre-normalized for settings
2226
 * set via php.ini or php_flag/php_admin_flag... but *not*
2227
 * for those set via php_value/php_admin_value.
2228
 *
2229
 * It's fairly common for people to use php_value instead of php_flag,
2230
 * which can leave you with an 'off' setting giving a false positive
2231
 * for code that just takes the ini_get() return value as a boolean.
2232
 *
2233
 * To make things extra interesting, setting via php_value accepts
2234
 * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
2235
 * Unrecognized values go false... again opposite PHP's own coercion
2236
 * from string to bool.
2237
 *
2238
 * Luckily, 'properly' set settings will always come back as '0' or '1',
2239
 * so we only have to worry about them and the 'improper' settings.
2240
 *
2241
 * I frickin' hate PHP... :P
2242
 *
2243
 * @param string $setting
2244
 * @return bool
2245
 */
2246
function wfIniGetBool( $setting ) {
2247
	$val = strtolower( ini_get( $setting ) );
2248
	// 'on' and 'true' can't have whitespace around them, but '1' can.
2249
	return $val == 'on'
2250
		|| $val == 'true'
2251
		|| $val == 'yes'
2252
		|| preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
2253
}
2254
2255
/**
2256
 * Windows-compatible version of escapeshellarg()
2257
 * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
2258
 * function puts single quotes in regardless of OS.
2259
 *
2260
 * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
2261
 * earlier distro releases of PHP)
2262
 *
2263
 * @param string ... strings to escape and glue together, or a single array of strings parameter
2264
 * @return string
2265
 */
2266
function wfEscapeShellArg( /*...*/ ) {
2267
	wfInitShellLocale();
2268
2269
	$args = func_get_args();
2270
	if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
2271
		// If only one argument has been passed, and that argument is an array,
2272
		// treat it as a list of arguments
2273
		$args = reset( $args );
2274
	}
2275
2276
	$first = true;
2277
	$retVal = '';
2278
	foreach ( $args as $arg ) {
2279
		if ( !$first ) {
2280
			$retVal .= ' ';
2281
		} else {
2282
			$first = false;
2283
		}
2284
2285
		if ( wfIsWindows() ) {
2286
			// Escaping for an MSVC-style command line parser and CMD.EXE
2287
			// @codingStandardsIgnoreStart For long URLs
2288
			// Refs:
2289
			//  * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
2290
			//  * http://technet.microsoft.com/en-us/library/cc723564.aspx
2291
			//  * Bug #13518
2292
			//  * CR r63214
2293
			// Double the backslashes before any double quotes. Escape the double quotes.
2294
			// @codingStandardsIgnoreEnd
2295
			$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
2296
			$arg = '';
2297
			$iteration = 0;
2298
			foreach ( $tokens as $token ) {
2299
				if ( $iteration % 2 == 1 ) {
2300
					// Delimiter, a double quote preceded by zero or more slashes
2301
					$arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
2302
				} elseif ( $iteration % 4 == 2 ) {
2303
					// ^ in $token will be outside quotes, need to be escaped
2304
					$arg .= str_replace( '^', '^^', $token );
2305
				} else { // $iteration % 4 == 0
2306
					// ^ in $token will appear inside double quotes, so leave as is
2307
					$arg .= $token;
2308
				}
2309
				$iteration++;
2310
			}
2311
			// Double the backslashes before the end of the string, because
2312
			// we will soon add a quote
2313
			$m = [];
2314
			if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
2315
				$arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
2316
			}
2317
2318
			// Add surrounding quotes
2319
			$retVal .= '"' . $arg . '"';
2320
		} else {
2321
			$retVal .= escapeshellarg( $arg );
2322
		}
2323
	}
2324
	return $retVal;
2325
}
2326
2327
/**
2328
 * Check if wfShellExec() is effectively disabled via php.ini config
2329
 *
2330
 * @return bool|string False or 'disabled'
2331
 * @since 1.22
2332
 */
2333
function wfShellExecDisabled() {
2334
	static $disabled = null;
2335
	if ( is_null( $disabled ) ) {
2336
		if ( !function_exists( 'proc_open' ) ) {
2337
			wfDebug( "proc_open() is disabled\n" );
2338
			$disabled = 'disabled';
2339
		} else {
2340
			$disabled = false;
2341
		}
2342
	}
2343
	return $disabled;
2344
}
2345
2346
/**
2347
 * Execute a shell command, with time and memory limits mirrored from the PHP
2348
 * configuration if supported.
2349
 *
2350
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2351
 *   or an array of unescaped arguments, in which case each value will be escaped
2352
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2353
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2354
 *   (non-zero is usually failure). If there is an error from
2355
 *   read, select, or proc_open(), this will be set to -1.
2356
 * @param array $environ Optional environment variables which should be
2357
 *   added to the executed command environment.
2358
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2359
 *   this overwrites the global wgMaxShell* limits.
2360
 * @param array $options Array of options:
2361
 *   - duplicateStderr: Set this to true to duplicate stderr to stdout,
2362
 *     including errors from limit.sh
2363
 *   - profileMethod: By default this function will profile based on the calling
2364
 *     method. Set this to a string for an alternative method to profile from
2365
 *
2366
 * @return string Collected stdout as a string
2367
 */
2368
function wfShellExec( $cmd, &$retval = null, $environ = [],
2369
	$limits = [], $options = []
2370
) {
2371
	global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
2372
		$wgMaxShellWallClockTime, $wgShellCgroup;
2373
2374
	$disabled = wfShellExecDisabled();
2375
	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...
2376
		$retval = 1;
2377
		return 'Unable to run external programs, proc_open() is disabled.';
2378
	}
2379
2380
	$includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
2381
	$profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller();
2382
2383
	wfInitShellLocale();
2384
2385
	$envcmd = '';
2386
	foreach ( $environ as $k => $v ) {
2387
		if ( wfIsWindows() ) {
2388
			/* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
2389
			 * appear in the environment variable, so we must use carat escaping as documented in
2390
			 * http://technet.microsoft.com/en-us/library/cc723564.aspx
2391
			 * Note however that the quote isn't listed there, but is needed, and the parentheses
2392
			 * are listed there but doesn't appear to need it.
2393
			 */
2394
			$envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
2395
		} else {
2396
			/* Assume this is a POSIX shell, thus required to accept variable assignments before the command
2397
			 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
2398
			 */
2399
			$envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
2400
		}
2401
	}
2402
	if ( is_array( $cmd ) ) {
2403
		$cmd = wfEscapeShellArg( $cmd );
2404
	}
2405
2406
	$cmd = $envcmd . $cmd;
2407
2408
	$useLogPipe = false;
2409
	if ( is_executable( '/bin/bash' ) ) {
2410
		$time = intval( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
2411
		if ( isset( $limits['walltime'] ) ) {
2412
			$wallTime = intval( $limits['walltime'] );
2413
		} elseif ( isset( $limits['time'] ) ) {
2414
			$wallTime = $time;
2415
		} else {
2416
			$wallTime = intval( $wgMaxShellWallClockTime );
2417
		}
2418
		$mem = intval( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
2419
		$filesize = intval( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
2420
2421
		if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
2422
			$cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
2423
				escapeshellarg( $cmd ) . ' ' .
2424
				escapeshellarg(
2425
					"MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
2426
					"MW_CPU_LIMIT=$time; " .
2427
					'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
2428
					"MW_MEM_LIMIT=$mem; " .
2429
					"MW_FILE_SIZE_LIMIT=$filesize; " .
2430
					"MW_WALL_CLOCK_LIMIT=$wallTime; " .
2431
					"MW_USE_LOG_PIPE=yes"
2432
				);
2433
			$useLogPipe = true;
2434
		} elseif ( $includeStderr ) {
2435
			$cmd .= ' 2>&1';
2436
		}
2437
	} elseif ( $includeStderr ) {
2438
		$cmd .= ' 2>&1';
2439
	}
2440
	wfDebug( "wfShellExec: $cmd\n" );
2441
2442
	$desc = [
2443
		0 => [ 'file', 'php://stdin', 'r' ],
2444
		1 => [ 'pipe', 'w' ],
2445
		2 => [ 'file', 'php://stderr', 'w' ] ];
2446
	if ( $useLogPipe ) {
2447
		$desc[3] = [ 'pipe', 'w' ];
2448
	}
2449
	$pipes = null;
2450
	$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...
2451
	$proc = proc_open( $cmd, $desc, $pipes );
2452
	if ( !$proc ) {
2453
		wfDebugLog( 'exec', "proc_open() failed: $cmd" );
2454
		$retval = -1;
2455
		return '';
2456
	}
2457
	$outBuffer = $logBuffer = '';
2458
	$emptyArray = [];
2459
	$status = false;
2460
	$logMsg = false;
2461
2462
	/* According to the documentation, it is possible for stream_select()
2463
	 * to fail due to EINTR. I haven't managed to induce this in testing
2464
	 * despite sending various signals. If it did happen, the error
2465
	 * message would take the form:
2466
	 *
2467
	 * stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
2468
	 *
2469
	 * where [4] is the value of the macro EINTR and "Interrupted system
2470
	 * call" is string which according to the Linux manual is "possibly"
2471
	 * localised according to LC_MESSAGES.
2472
	 */
2473
	$eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
2474
	$eintrMessage = "stream_select(): unable to select [$eintr]";
2475
2476
	// Build a table mapping resource IDs to pipe FDs to work around a
2477
	// PHP 5.3 issue in which stream_select() does not preserve array keys
2478
	// <https://bugs.php.net/bug.php?id=53427>.
2479
	$fds = [];
2480
	foreach ( $pipes as $fd => $pipe ) {
2481
		$fds[(int)$pipe] = $fd;
2482
	}
2483
2484
	$running = true;
2485
	$timeout = null;
2486
	$numReadyPipes = 0;
2487
2488
	while ( $running === true || $numReadyPipes !== 0 ) {
2489
		if ( $running ) {
2490
			$status = proc_get_status( $proc );
2491
			// If the process has terminated, switch to nonblocking selects
2492
			// for getting any data still waiting to be read.
2493
			if ( !$status['running'] ) {
2494
				$running = false;
2495
				$timeout = 0;
2496
			}
2497
		}
2498
2499
		$readyPipes = $pipes;
2500
2501
		// Clear last error
2502
		// @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged
2503
		@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...
2504
		$numReadyPipes = @stream_select( $readyPipes, $emptyArray, $emptyArray, $timeout );
2505
		if ( $numReadyPipes === false ) {
2506
			// @codingStandardsIgnoreEnd
2507
			$error = error_get_last();
2508
			if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
2509
				continue;
2510
			} else {
2511
				trigger_error( $error['message'], E_USER_WARNING );
2512
				$logMsg = $error['message'];
2513
				break;
2514
			}
2515
		}
2516
		foreach ( $readyPipes as $pipe ) {
2517
			$block = fread( $pipe, 65536 );
2518
			$fd = $fds[(int)$pipe];
2519
			if ( $block === '' ) {
2520
				// End of file
2521
				fclose( $pipes[$fd] );
2522
				unset( $pipes[$fd] );
2523
				if ( !$pipes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pipes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2524
					break 2;
2525
				}
2526
			} elseif ( $block === false ) {
2527
				// Read error
2528
				$logMsg = "Error reading from pipe";
2529
				break 2;
2530
			} elseif ( $fd == 1 ) {
2531
				// From stdout
2532
				$outBuffer .= $block;
2533
			} elseif ( $fd == 3 ) {
2534
				// From log FD
2535
				$logBuffer .= $block;
2536
				if ( strpos( $block, "\n" ) !== false ) {
2537
					$lines = explode( "\n", $logBuffer );
2538
					$logBuffer = array_pop( $lines );
2539
					foreach ( $lines as $line ) {
2540
						wfDebugLog( 'exec', $line );
2541
					}
2542
				}
2543
			}
2544
		}
2545
	}
2546
2547
	foreach ( $pipes as $pipe ) {
2548
		fclose( $pipe );
2549
	}
2550
2551
	// Use the status previously collected if possible, since proc_get_status()
2552
	// just calls waitpid() which will not return anything useful the second time.
2553
	if ( $running ) {
2554
		$status = proc_get_status( $proc );
2555
	}
2556
2557
	if ( $logMsg !== false ) {
2558
		// Read/select error
2559
		$retval = -1;
2560
		proc_close( $proc );
2561
	} elseif ( $status['signaled'] ) {
2562
		$logMsg = "Exited with signal {$status['termsig']}";
2563
		$retval = 128 + $status['termsig'];
2564
		proc_close( $proc );
2565
	} else {
2566
		if ( $status['running'] ) {
2567
			$retval = proc_close( $proc );
2568
		} else {
2569
			$retval = $status['exitcode'];
2570
			proc_close( $proc );
2571
		}
2572
		if ( $retval == 127 ) {
2573
			$logMsg = "Possibly missing executable file";
2574
		} elseif ( $retval >= 129 && $retval <= 192 ) {
2575
			$logMsg = "Probably exited with signal " . ( $retval - 128 );
2576
		}
2577
	}
2578
2579
	if ( $logMsg !== false ) {
2580
		wfDebugLog( 'exec', "$logMsg: $cmd" );
2581
	}
2582
2583
	return $outBuffer;
2584
}
2585
2586
/**
2587
 * Execute a shell command, returning both stdout and stderr. Convenience
2588
 * function, as all the arguments to wfShellExec can become unwieldy.
2589
 *
2590
 * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
2591
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2592
 *   or an array of unescaped arguments, in which case each value will be escaped
2593
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2594
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2595
 *   (non-zero is usually failure)
2596
 * @param array $environ Optional environment variables which should be
2597
 *   added to the executed command environment.
2598
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2599
 *   this overwrites the global wgMaxShell* limits.
2600
 * @return string Collected stdout and stderr as a string
2601
 */
2602
function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = [] ) {
2603
	return wfShellExec( $cmd, $retval, $environ, $limits,
2604
		[ 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ] );
2605
}
2606
2607
/**
2608
 * Workaround for http://bugs.php.net/bug.php?id=45132
2609
 * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
2610
 */
2611
function wfInitShellLocale() {
2612
	static $done = false;
2613
	if ( $done ) {
2614
		return;
2615
	}
2616
	$done = true;
2617
	global $wgShellLocale;
2618
	putenv( "LC_CTYPE=$wgShellLocale" );
2619
	setlocale( LC_CTYPE, $wgShellLocale );
2620
}
2621
2622
/**
2623
 * Generate a shell-escaped command line string to run a MediaWiki cli script.
2624
 * Note that $parameters should be a flat array and an option with an argument
2625
 * should consist of two consecutive items in the array (do not use "--option value").
2626
 *
2627
 * @param string $script MediaWiki cli script path
2628
 * @param array $parameters Arguments and options to the script
2629
 * @param array $options Associative array of options:
2630
 * 		'php': The path to the php executable
2631
 * 		'wrapper': Path to a PHP wrapper to handle the maintenance script
2632
 * @return string
2633
 */
2634
function wfShellWikiCmd( $script, array $parameters = [], array $options = [] ) {
2635
	global $wgPhpCli;
2636
	// Give site config file a chance to run the script in a wrapper.
2637
	// The caller may likely want to call wfBasename() on $script.
2638
	Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
2639
	$cmd = isset( $options['php'] ) ? [ $options['php'] ] : [ $wgPhpCli ];
2640
	if ( isset( $options['wrapper'] ) ) {
2641
		$cmd[] = $options['wrapper'];
2642
	}
2643
	$cmd[] = $script;
2644
	// Escape each parameter for shell
2645
	return wfEscapeShellArg( array_merge( $cmd, $parameters ) );
2646
}
2647
2648
/**
2649
 * wfMerge attempts to merge differences between three texts.
2650
 * Returns true for a clean merge and false for failure or a conflict.
2651
 *
2652
 * @param string $old
2653
 * @param string $mine
2654
 * @param string $yours
2655
 * @param string $result
2656
 * @return bool
2657
 */
2658
function wfMerge( $old, $mine, $yours, &$result ) {
2659
	global $wgDiff3;
2660
2661
	# This check may also protect against code injection in
2662
	# case of broken installations.
2663
	MediaWiki\suppressWarnings();
2664
	$haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
2665
	MediaWiki\restoreWarnings();
2666
2667
	if ( !$haveDiff3 ) {
2668
		wfDebug( "diff3 not found\n" );
2669
		return false;
2670
	}
2671
2672
	# Make temporary files
2673
	$td = wfTempDir();
2674
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2675
	$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
2676
	$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
2677
2678
	# NOTE: diff3 issues a warning to stderr if any of the files does not end with
2679
	#       a newline character. To avoid this, we normalize the trailing whitespace before
2680
	#       creating the diff.
2681
2682
	fwrite( $oldtextFile, rtrim( $old ) . "\n" );
2683
	fclose( $oldtextFile );
2684
	fwrite( $mytextFile, rtrim( $mine ) . "\n" );
2685
	fclose( $mytextFile );
2686
	fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
2687
	fclose( $yourtextFile );
2688
2689
	# Check for a conflict
2690
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName,
2691
		$oldtextName, $yourtextName );
2692
	$handle = popen( $cmd, 'r' );
2693
2694
	if ( fgets( $handle, 1024 ) ) {
2695
		$conflict = true;
2696
	} else {
2697
		$conflict = false;
2698
	}
2699
	pclose( $handle );
2700
2701
	# Merge differences
2702
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName,
2703
		$oldtextName, $yourtextName );
2704
	$handle = popen( $cmd, 'r' );
2705
	$result = '';
2706
	do {
2707
		$data = fread( $handle, 8192 );
2708
		if ( strlen( $data ) == 0 ) {
2709
			break;
2710
		}
2711
		$result .= $data;
2712
	} while ( true );
2713
	pclose( $handle );
2714
	unlink( $mytextName );
2715
	unlink( $oldtextName );
2716
	unlink( $yourtextName );
2717
2718
	if ( $result === '' && $old !== '' && !$conflict ) {
2719
		wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
2720
		$conflict = true;
2721
	}
2722
	return !$conflict;
2723
}
2724
2725
/**
2726
 * Returns unified plain-text diff of two texts.
2727
 * "Useful" for machine processing of diffs.
2728
 *
2729
 * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly
2730
 *
2731
 * @param string $before The text before the changes.
2732
 * @param string $after The text after the changes.
2733
 * @param string $params Command-line options for the diff command.
2734
 * @return string Unified diff of $before and $after
2735
 */
2736
function wfDiff( $before, $after, $params = '-u' ) {
2737
	if ( $before == $after ) {
2738
		return '';
2739
	}
2740
2741
	global $wgDiff;
2742
	MediaWiki\suppressWarnings();
2743
	$haveDiff = $wgDiff && file_exists( $wgDiff );
2744
	MediaWiki\restoreWarnings();
2745
2746
	# This check may also protect against code injection in
2747
	# case of broken installations.
2748
	if ( !$haveDiff ) {
2749
		wfDebug( "diff executable not found\n" );
2750
		$diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
2751
		$format = new UnifiedDiffFormatter();
2752
		return $format->format( $diffs );
2753
	}
2754
2755
	# Make temporary files
2756
	$td = wfTempDir();
2757
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2758
	$newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
2759
2760
	fwrite( $oldtextFile, $before );
2761
	fclose( $oldtextFile );
2762
	fwrite( $newtextFile, $after );
2763
	fclose( $newtextFile );
2764
2765
	// Get the diff of the two files
2766
	$cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
2767
2768
	$h = popen( $cmd, 'r' );
2769
	if ( !$h ) {
2770
		unlink( $oldtextName );
2771
		unlink( $newtextName );
2772
		throw new Exception( __METHOD__ . '(): popen() failed' );
2773
	}
2774
2775
	$diff = '';
2776
2777
	do {
2778
		$data = fread( $h, 8192 );
2779
		if ( strlen( $data ) == 0 ) {
2780
			break;
2781
		}
2782
		$diff .= $data;
2783
	} while ( true );
2784
2785
	// Clean up
2786
	pclose( $h );
2787
	unlink( $oldtextName );
2788
	unlink( $newtextName );
2789
2790
	// Kill the --- and +++ lines. They're not useful.
2791
	$diff_lines = explode( "\n", $diff );
2792 View Code Duplication
	if ( isset( $diff_lines[0] ) && strpos( $diff_lines[0], '---' ) === 0 ) {
2793
		unset( $diff_lines[0] );
2794
	}
2795 View Code Duplication
	if ( isset( $diff_lines[1] ) && strpos( $diff_lines[1], '+++' ) === 0 ) {
2796
		unset( $diff_lines[1] );
2797
	}
2798
2799
	$diff = implode( "\n", $diff_lines );
2800
2801
	return $diff;
2802
}
2803
2804
/**
2805
 * This function works like "use VERSION" in Perl, the program will die with a
2806
 * backtrace if the current version of PHP is less than the version provided
2807
 *
2808
 * This is useful for extensions which due to their nature are not kept in sync
2809
 * with releases, and might depend on other versions of PHP than the main code
2810
 *
2811
 * Note: PHP might die due to parsing errors in some cases before it ever
2812
 *       manages to call this function, such is life
2813
 *
2814
 * @see perldoc -f use
2815
 *
2816
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2817
 * @throws MWException
2818
 */
2819
function wfUsePHP( $req_ver ) {
2820
	$php_ver = PHP_VERSION;
2821
2822
	if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
2823
		throw new MWException( "PHP $req_ver required--this is only $php_ver" );
2824
	}
2825
}
2826
2827
/**
2828
 * This function works like "use VERSION" in Perl except it checks the version
2829
 * of MediaWiki, the program will die with a backtrace if the current version
2830
 * of MediaWiki is less than the version provided.
2831
 *
2832
 * This is useful for extensions which due to their nature are not kept in sync
2833
 * with releases
2834
 *
2835
 * Note: Due to the behavior of PHP's version_compare() which is used in this
2836
 * function, if you want to allow the 'wmf' development versions add a 'c' (or
2837
 * any single letter other than 'a', 'b' or 'p') as a post-fix to your
2838
 * targeted version number. For example if you wanted to allow any variation
2839
 * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
2840
 * not result in the same comparison due to the internal logic of
2841
 * version_compare().
2842
 *
2843
 * @see perldoc -f use
2844
 *
2845
 * @deprecated since 1.26, use the "requires' property of extension.json
2846
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2847
 * @throws MWException
2848
 */
2849
function wfUseMW( $req_ver ) {
2850
	global $wgVersion;
2851
2852
	if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
2853
		throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
2854
	}
2855
}
2856
2857
/**
2858
 * Return the final portion of a pathname.
2859
 * Reimplemented because PHP5's "basename()" is buggy with multibyte text.
2860
 * http://bugs.php.net/bug.php?id=33898
2861
 *
2862
 * PHP's basename() only considers '\' a pathchar on Windows and Netware.
2863
 * We'll consider it so always, as we don't want '\s' in our Unix paths either.
2864
 *
2865
 * @param string $path
2866
 * @param string $suffix String to remove if present
2867
 * @return string
2868
 */
2869
function wfBaseName( $path, $suffix = '' ) {
2870
	if ( $suffix == '' ) {
2871
		$encSuffix = '';
2872
	} else {
2873
		$encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
2874
	}
2875
2876
	$matches = [];
2877
	if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
2878
		return $matches[1];
2879
	} else {
2880
		return '';
2881
	}
2882
}
2883
2884
/**
2885
 * Generate a relative path name to the given file.
2886
 * May explode on non-matching case-insensitive paths,
2887
 * funky symlinks, etc.
2888
 *
2889
 * @param string $path Absolute destination path including target filename
2890
 * @param string $from Absolute source path, directory only
2891
 * @return string
2892
 */
2893
function wfRelativePath( $path, $from ) {
2894
	// Normalize mixed input on Windows...
2895
	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
2896
	$from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
2897
2898
	// Trim trailing slashes -- fix for drive root
2899
	$path = rtrim( $path, DIRECTORY_SEPARATOR );
2900
	$from = rtrim( $from, DIRECTORY_SEPARATOR );
2901
2902
	$pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
2903
	$against = explode( DIRECTORY_SEPARATOR, $from );
2904
2905
	if ( $pieces[0] !== $against[0] ) {
2906
		// Non-matching Windows drive letters?
2907
		// Return a full path.
2908
		return $path;
2909
	}
2910
2911
	// Trim off common prefix
2912
	while ( count( $pieces ) && count( $against )
2913
		&& $pieces[0] == $against[0] ) {
2914
		array_shift( $pieces );
2915
		array_shift( $against );
2916
	}
2917
2918
	// relative dots to bump us to the parent
2919
	while ( count( $against ) ) {
2920
		array_unshift( $pieces, '..' );
2921
		array_shift( $against );
2922
	}
2923
2924
	array_push( $pieces, wfBaseName( $path ) );
2925
2926
	return implode( DIRECTORY_SEPARATOR, $pieces );
2927
}
2928
2929
/**
2930
 * Convert an arbitrarily-long digit string from one numeric base
2931
 * to another, optionally zero-padding to a minimum column width.
2932
 *
2933
 * Supports base 2 through 36; digit values 10-36 are represented
2934
 * as lowercase letters a-z. Input is case-insensitive.
2935
 *
2936
 * @deprecated 1.27 Use Wikimedia\base_convert() directly
2937
 *
2938
 * @param string $input Input number
2939
 * @param int $sourceBase Base of the input number
2940
 * @param int $destBase Desired base of the output
2941
 * @param int $pad Minimum number of digits in the output (pad with zeroes)
2942
 * @param bool $lowercase Whether to output in lowercase or uppercase
2943
 * @param string $engine Either "gmp", "bcmath", or "php"
2944
 * @return string|bool The output number as a string, or false on error
2945
 */
2946
function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1,
2947
	$lowercase = true, $engine = 'auto'
2948
) {
2949
	return Wikimedia\base_convert( $input, $sourceBase, $destBase, $pad, $lowercase, $engine );
2950
}
2951
2952
/**
2953
 * @deprecated since 1.27, PHP's session generation isn't used with
2954
 *  MediaWiki\Session\SessionManager
2955
 */
2956
function wfFixSessionID() {
2957
	wfDeprecated( __FUNCTION__, '1.27' );
2958
}
2959
2960
/**
2961
 * Reset the session id
2962
 *
2963
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead
2964
 * @since 1.22
2965
 */
2966
function wfResetSessionID() {
2967
	wfDeprecated( __FUNCTION__, '1.27' );
2968
	$session = SessionManager::getGlobalSession();
2969
	$delay = $session->delaySave();
2970
2971
	$session->resetId();
2972
2973
	// Make sure a session is started, since that's what the old
2974
	// wfResetSessionID() did.
2975
	if ( session_id() !== $session->getId() ) {
2976
		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...
2977
	}
2978
2979
	ScopedCallback::consume( $delay );
2980
}
2981
2982
/**
2983
 * Initialise php session
2984
 *
2985
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead.
2986
 *  Generally, "using" SessionManager will be calling ->getSessionById() or
2987
 *  ::getGlobalSession() (depending on whether you were passing $sessionId
2988
 *  here), then calling $session->persist().
2989
 * @param bool|string $sessionId
2990
 */
2991
function wfSetupSession( $sessionId = false ) {
2992
	wfDeprecated( __FUNCTION__, '1.27' );
2993
2994
	if ( $sessionId ) {
2995
		session_id( $sessionId );
2996
	}
2997
2998
	$session = SessionManager::getGlobalSession();
2999
	$session->persist();
3000
3001
	if ( session_id() !== $session->getId() ) {
3002
		session_id( $session->getId() );
3003
	}
3004
	MediaWiki\quietCall( 'session_start' );
3005
}
3006
3007
/**
3008
 * Get an object from the precompiled serialized directory
3009
 *
3010
 * @param string $name
3011
 * @return mixed The variable on success, false on failure
3012
 */
3013
function wfGetPrecompiledData( $name ) {
3014
	global $IP;
3015
3016
	$file = "$IP/serialized/$name";
3017
	if ( file_exists( $file ) ) {
3018
		$blob = file_get_contents( $file );
3019
		if ( $blob ) {
3020
			return unserialize( $blob );
3021
		}
3022
	}
3023
	return false;
3024
}
3025
3026
/**
3027
 * Make a cache key for the local wiki.
3028
 *
3029
 * @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...
3030
 * @return string
3031
 */
3032
function wfMemcKey( /*...*/ ) {
3033
	return call_user_func_array(
3034
		[ ObjectCache::getLocalClusterInstance(), 'makeKey' ],
3035
		func_get_args()
3036
	);
3037
}
3038
3039
/**
3040
 * Make a cache key for a foreign DB.
3041
 *
3042
 * Must match what wfMemcKey() would produce in context of the foreign wiki.
3043
 *
3044
 * @param string $db
3045
 * @param string $prefix
3046
 * @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...
3047
 * @return string
3048
 */
3049
function wfForeignMemcKey( $db, $prefix /*...*/ ) {
3050
	$args = array_slice( func_get_args(), 2 );
3051
	$keyspace = $prefix ? "$db-$prefix" : $db;
3052
	return call_user_func_array(
3053
		[ ObjectCache::getLocalClusterInstance(), 'makeKeyInternal' ],
3054
		[ $keyspace, $args ]
3055
	);
3056
}
3057
3058
/**
3059
 * Make a cache key with database-agnostic prefix.
3060
 *
3061
 * Doesn't have a wiki-specific namespace. Uses a generic 'global' prefix
3062
 * instead. Must have a prefix as otherwise keys that use a database name
3063
 * in the first segment will clash with wfMemcKey/wfForeignMemcKey.
3064
 *
3065
 * @since 1.26
3066
 * @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...
3067
 * @return string
3068
 */
3069
function wfGlobalCacheKey( /*...*/ ) {
3070
	return call_user_func_array(
3071
		[ ObjectCache::getLocalClusterInstance(), 'makeGlobalKey' ],
3072
		func_get_args()
3073
	);
3074
}
3075
3076
/**
3077
 * Get an ASCII string identifying this wiki
3078
 * This is used as a prefix in memcached keys
3079
 *
3080
 * @return string
3081
 */
3082
function wfWikiID() {
3083
	global $wgDBprefix, $wgDBname;
3084
	if ( $wgDBprefix ) {
3085
		return "$wgDBname-$wgDBprefix";
3086
	} else {
3087
		return $wgDBname;
3088
	}
3089
}
3090
3091
/**
3092
 * Split a wiki ID into DB name and table prefix
3093
 *
3094
 * @param string $wiki
3095
 *
3096
 * @return array
3097
 */
3098
function wfSplitWikiID( $wiki ) {
3099
	$bits = explode( '-', $wiki, 2 );
3100
	if ( count( $bits ) < 2 ) {
3101
		$bits[] = '';
3102
	}
3103
	return $bits;
3104
}
3105
3106
/**
3107
 * Get a Database object.
3108
 *
3109
 * @param int $db Index of the connection to get. May be DB_MASTER for the
3110
 *            master (for write queries), DB_SLAVE for potentially lagged read
3111
 *            queries, or an integer >= 0 for a particular server.
3112
 *
3113
 * @param string|string[] $groups Query groups. An array of group names that this query
3114
 *                belongs to. May contain a single string if the query is only
3115
 *                in one group.
3116
 *
3117
 * @param string|bool $wiki The wiki ID, or false for the current wiki
3118
 *
3119
 * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
3120
 * will always return the same object, unless the underlying connection or load
3121
 * balancer is manually destroyed.
3122
 *
3123
 * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
3124
 * updater to ensure that a proper database is being updated.
3125
 *
3126
 * @return DatabaseBase
3127
 */
3128
function wfGetDB( $db, $groups = [], $wiki = false ) {
3129
	return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
3130
}
3131
3132
/**
3133
 * Get a load balancer object.
3134
 *
3135
 * @param string|bool $wiki Wiki ID, or false for the current wiki
3136
 * @return LoadBalancer
3137
 */
3138
function wfGetLB( $wiki = false ) {
3139
	return wfGetLBFactory()->getMainLB( $wiki );
3140
}
3141
3142
/**
3143
 * Get the load balancer factory object
3144
 *
3145
 * @return LBFactory
3146
 */
3147
function wfGetLBFactory() {
3148
	return LBFactory::singleton();
3149
}
3150
3151
/**
3152
 * Find a file.
3153
 * Shortcut for RepoGroup::singleton()->findFile()
3154
 *
3155
 * @param string $title String or Title object
3156
 * @param array $options Associative array of options (see RepoGroup::findFile)
3157
 * @return File|bool File, or false if the file does not exist
3158
 */
3159
function wfFindFile( $title, $options = [] ) {
3160
	return RepoGroup::singleton()->findFile( $title, $options );
3161
}
3162
3163
/**
3164
 * Get an object referring to a locally registered file.
3165
 * Returns a valid placeholder object if the file does not exist.
3166
 *
3167
 * @param Title|string $title
3168
 * @return LocalFile|null A File, or null if passed an invalid Title
3169
 */
3170
function wfLocalFile( $title ) {
3171
	return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
3172
}
3173
3174
/**
3175
 * Should low-performance queries be disabled?
3176
 *
3177
 * @return bool
3178
 * @codeCoverageIgnore
3179
 */
3180
function wfQueriesMustScale() {
3181
	global $wgMiserMode;
3182
	return $wgMiserMode
3183
		|| ( SiteStats::pages() > 100000
3184
		&& SiteStats::edits() > 1000000
3185
		&& SiteStats::users() > 10000 );
3186
}
3187
3188
/**
3189
 * Get the path to a specified script file, respecting file
3190
 * extensions; this is a wrapper around $wgScriptPath etc.
3191
 * except for 'index' and 'load' which use $wgScript/$wgLoadScript
3192
 *
3193
 * @param string $script Script filename, sans extension
3194
 * @return string
3195
 */
3196
function wfScript( $script = 'index' ) {
3197
	global $wgScriptPath, $wgScript, $wgLoadScript;
3198
	if ( $script === 'index' ) {
3199
		return $wgScript;
3200
	} elseif ( $script === 'load' ) {
3201
		return $wgLoadScript;
3202
	} else {
3203
		return "{$wgScriptPath}/{$script}.php";
3204
	}
3205
}
3206
3207
/**
3208
 * Get the script URL.
3209
 *
3210
 * @return string Script URL
3211
 */
3212
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...
3213
	if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
3214
		/* as it was called, minus the query string.
3215
		 *
3216
		 * Some sites use Apache rewrite rules to handle subdomains,
3217
		 * and have PHP set up in a weird way that causes PHP_SELF
3218
		 * to contain the rewritten URL instead of the one that the
3219
		 * outside world sees.
3220
		 *
3221
		 * If in this mode, use SCRIPT_URL instead, which mod_rewrite
3222
		 * provides containing the "before" URL.
3223
		 */
3224
		return $_SERVER['SCRIPT_NAME'];
3225
	} else {
3226
		return $_SERVER['URL'];
3227
	}
3228
}
3229
3230
/**
3231
 * Convenience function converts boolean values into "true"
3232
 * or "false" (string) values
3233
 *
3234
 * @param bool $value
3235
 * @return string
3236
 */
3237
function wfBoolToStr( $value ) {
3238
	return $value ? 'true' : 'false';
3239
}
3240
3241
/**
3242
 * Get a platform-independent path to the null file, e.g. /dev/null
3243
 *
3244
 * @return string
3245
 */
3246
function wfGetNull() {
3247
	return wfIsWindows() ? 'NUL' : '/dev/null';
3248
}
3249
3250
/**
3251
 * Waits for the slaves to catch up to the master position
3252
 *
3253
 * Use this when updating very large numbers of rows, as in maintenance scripts,
3254
 * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
3255
 *
3256
 * By default this waits on the main DB cluster of the current wiki.
3257
 * If $cluster is set to "*" it will wait on all DB clusters, including
3258
 * external ones. If the lag being waiting on is caused by the code that
3259
 * does this check, it makes since to use $ifWritesSince, particularly if
3260
 * cluster is "*", to avoid excess overhead.
3261
 *
3262
 * Never call this function after a big DB write that is still in a transaction.
3263
 * This only makes sense after the possible lag inducing changes were committed.
3264
 *
3265
 * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
3266
 * @param string|bool $wiki Wiki identifier accepted by wfGetLB
3267
 * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
3268
 * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web)
3269
 * @return bool Success (able to connect and no timeouts reached)
3270
 * @deprecated since 1.27 Use LBFactory::waitForReplication
3271
 */
3272
function wfWaitForSlaves(
3273
	$ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
3274
) {
3275
	if ( $timeout === null ) {
3276
		$timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10;
3277
	}
3278
3279
	if ( $cluster === '*' ) {
3280
		$cluster = false;
3281
		$wiki = false;
3282
	} elseif ( $wiki === false ) {
3283
		$wiki = wfWikiID();
3284
	}
3285
3286
	try {
3287
		wfGetLBFactory()->waitForReplication( [
3288
			'wiki' => $wiki,
3289
			'cluster' => $cluster,
3290
			'timeout' => $timeout,
3291
			// B/C: first argument used to be "max seconds of lag"; ignore such values
3292
			'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
3293
		] );
3294
	} catch ( DBReplicationWaitError $e ) {
3295
		return false;
3296
	}
3297
3298
	return true;
3299
}
3300
3301
/**
3302
 * Count down from $seconds to zero on the terminal, with a one-second pause
3303
 * between showing each number. For use in command-line scripts.
3304
 *
3305
 * @codeCoverageIgnore
3306
 * @param int $seconds
3307
 */
3308
function wfCountDown( $seconds ) {
3309
	for ( $i = $seconds; $i >= 0; $i-- ) {
3310
		if ( $i != $seconds ) {
3311
			echo str_repeat( "\x08", strlen( $i + 1 ) );
3312
		}
3313
		echo $i;
3314
		flush();
3315
		if ( $i ) {
3316
			sleep( 1 );
3317
		}
3318
	}
3319
	echo "\n";
3320
}
3321
3322
/**
3323
 * Replace all invalid characters with -
3324
 * Additional characters can be defined in $wgIllegalFileChars (see bug 20489)
3325
 * By default, $wgIllegalFileChars = ':'
3326
 *
3327
 * @param string $name Filename to process
3328
 * @return string
3329
 */
3330
function wfStripIllegalFilenameChars( $name ) {
3331
	global $wgIllegalFileChars;
3332
	$illegalFileChars = $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '';
3333
	$name = wfBaseName( $name );
3334
	$name = preg_replace(
3335
		"/[^" . Title::legalChars() . "]" . $illegalFileChars . "/",
3336
		'-',
3337
		$name
3338
	);
3339
	return $name;
3340
}
3341
3342
/**
3343
 * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit
3344
 *
3345
 * @return int Resulting value of the memory limit.
3346
 */
3347
function wfMemoryLimit() {
3348
	global $wgMemoryLimit;
3349
	$memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
3350
	if ( $memlimit != -1 ) {
3351
		$conflimit = wfShorthandToInteger( $wgMemoryLimit );
3352
		if ( $conflimit == -1 ) {
3353
			wfDebug( "Removing PHP's memory limit\n" );
3354
			MediaWiki\suppressWarnings();
3355
			ini_set( 'memory_limit', $conflimit );
3356
			MediaWiki\restoreWarnings();
3357
			return $conflimit;
3358
		} elseif ( $conflimit > $memlimit ) {
3359
			wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
3360
			MediaWiki\suppressWarnings();
3361
			ini_set( 'memory_limit', $conflimit );
3362
			MediaWiki\restoreWarnings();
3363
			return $conflimit;
3364
		}
3365
	}
3366
	return $memlimit;
3367
}
3368
3369
/**
3370
 * Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit
3371
 *
3372
 * @return int Prior time limit
3373
 * @since 1.26
3374
 */
3375
function wfTransactionalTimeLimit() {
3376
	global $wgTransactionalTimeLimit;
3377
3378
	$timeLimit = ini_get( 'max_execution_time' );
3379
	// Note that CLI scripts use 0
3380
	if ( $timeLimit > 0 && $wgTransactionalTimeLimit > $timeLimit ) {
3381
		set_time_limit( $wgTransactionalTimeLimit );
3382
	}
3383
3384
	ignore_user_abort( true ); // ignore client disconnects
3385
3386
	return $timeLimit;
3387
}
3388
3389
/**
3390
 * Converts shorthand byte notation to integer form
3391
 *
3392
 * @param string $string
3393
 * @param int $default Returned if $string is empty
3394
 * @return int
3395
 */
3396
function wfShorthandToInteger( $string = '', $default = -1 ) {
3397
	$string = trim( $string );
3398
	if ( $string === '' ) {
3399
		return $default;
3400
	}
3401
	$last = $string[strlen( $string ) - 1];
3402
	$val = intval( $string );
3403
	switch ( $last ) {
3404
		case 'g':
3405
		case 'G':
3406
			$val *= 1024;
3407
			// break intentionally missing
3408
		case 'm':
3409
		case 'M':
3410
			$val *= 1024;
3411
			// break intentionally missing
3412
		case 'k':
3413
		case 'K':
3414
			$val *= 1024;
3415
	}
3416
3417
	return $val;
3418
}
3419
3420
/**
3421
 * Get the normalised IETF language tag
3422
 * See unit test for examples.
3423
 *
3424
 * @param string $code The language code.
3425
 * @return string The language code which complying with BCP 47 standards.
3426
 */
3427
function wfBCP47( $code ) {
3428
	$codeSegment = explode( '-', $code );
3429
	$codeBCP = [];
3430
	foreach ( $codeSegment as $segNo => $seg ) {
3431
		// when previous segment is x, it is a private segment and should be lc
3432
		if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
3433
			$codeBCP[$segNo] = strtolower( $seg );
3434
		// ISO 3166 country code
3435
		} elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
3436
			$codeBCP[$segNo] = strtoupper( $seg );
3437
		// ISO 15924 script code
3438
		} elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
3439
			$codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
3440
		// Use lowercase for other cases
3441
		} else {
3442
			$codeBCP[$segNo] = strtolower( $seg );
3443
		}
3444
	}
3445
	$langCode = implode( '-', $codeBCP );
3446
	return $langCode;
3447
}
3448
3449
/**
3450
 * Get a specific cache object.
3451
 *
3452
 * @param int|string $cacheType A CACHE_* constants, or other key in $wgObjectCaches
3453
 * @return BagOStuff
3454
 */
3455
function wfGetCache( $cacheType ) {
3456
	return ObjectCache::getInstance( $cacheType );
3457
}
3458
3459
/**
3460
 * Get the main cache object
3461
 *
3462
 * @return BagOStuff
3463
 */
3464
function wfGetMainCache() {
3465
	global $wgMainCacheType;
3466
	return ObjectCache::getInstance( $wgMainCacheType );
3467
}
3468
3469
/**
3470
 * Get the cache object used by the message cache
3471
 *
3472
 * @return BagOStuff
3473
 */
3474
function wfGetMessageCacheStorage() {
3475
	global $wgMessageCacheType;
3476
	return ObjectCache::getInstance( $wgMessageCacheType );
3477
}
3478
3479
/**
3480
 * Get the cache object used by the parser cache
3481
 *
3482
 * @return BagOStuff
3483
 */
3484
function wfGetParserCacheStorage() {
3485
	global $wgParserCacheType;
3486
	return ObjectCache::getInstance( $wgParserCacheType );
3487
}
3488
3489
/**
3490
 * Call hook functions defined in $wgHooks
3491
 *
3492
 * @param string $event Event name
3493
 * @param array $args Parameters passed to hook functions
3494
 * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number
3495
 *
3496
 * @return bool True if no handler aborted the hook
3497
 * @deprecated 1.25 - use Hooks::run
3498
 */
3499
function wfRunHooks( $event, array $args = [], $deprecatedVersion = null ) {
3500
	return Hooks::run( $event, $args, $deprecatedVersion );
3501
}
3502
3503
/**
3504
 * Wrapper around php's unpack.
3505
 *
3506
 * @param string $format The format string (See php's docs)
3507
 * @param string $data A binary string of binary data
3508
 * @param int|bool $length The minimum length of $data or false. This is to
3509
 *	prevent reading beyond the end of $data. false to disable the check.
3510
 *
3511
 * Also be careful when using this function to read unsigned 32 bit integer
3512
 * because php might make it negative.
3513
 *
3514
 * @throws MWException If $data not long enough, or if unpack fails
3515
 * @return array Associative array of the extracted data
3516
 */
3517
function wfUnpack( $format, $data, $length = false ) {
3518
	if ( $length !== false ) {
3519
		$realLen = strlen( $data );
3520
		if ( $realLen < $length ) {
3521
			throw new MWException( "Tried to use wfUnpack on a "
3522
				. "string of length $realLen, but needed one "
3523
				. "of at least length $length."
3524
			);
3525
		}
3526
	}
3527
3528
	MediaWiki\suppressWarnings();
3529
	$result = unpack( $format, $data );
3530
	MediaWiki\restoreWarnings();
3531
3532
	if ( $result === false ) {
3533
		// If it cannot extract the packed data.
3534
		throw new MWException( "unpack could not unpack binary data" );
3535
	}
3536
	return $result;
3537
}
3538
3539
/**
3540
 * Determine if an image exists on the 'bad image list'.
3541
 *
3542
 * The format of MediaWiki:Bad_image_list is as follows:
3543
 *    * Only list items (lines starting with "*") are considered
3544
 *    * The first link on a line must be a link to a bad image
3545
 *    * Any subsequent links on the same line are considered to be exceptions,
3546
 *      i.e. articles where the image may occur inline.
3547
 *
3548
 * @param string $name The image name to check
3549
 * @param Title|bool $contextTitle The page on which the image occurs, if known
3550
 * @param string $blacklist Wikitext of a file blacklist
3551
 * @return bool
3552
 */
3553
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
3554
	# Handle redirects; callers almost always hit wfFindFile() anyway,
3555
	# so just use that method because it has a fast process cache.
3556
	$file = wfFindFile( $name ); // get the final name
3557
	$name = $file ? $file->getTitle()->getDBkey() : $name;
3558
3559
	# Run the extension hook
3560
	$bad = false;
3561
	if ( !Hooks::run( 'BadImage', [ $name, &$bad ] ) ) {
3562
		return $bad;
3563
	}
3564
3565
	$cache = ObjectCache::getLocalServerInstance( 'hash' );
3566
	$key = wfMemcKey( 'bad-image-list', ( $blacklist === null ) ? 'default' : md5( $blacklist ) );
3567
	$badImages = $cache->get( $key );
3568
3569
	if ( $badImages === false ) { // cache miss
3570
		if ( $blacklist === null ) {
3571
			$blacklist = wfMessage( 'bad_image_list' )->inContentLanguage()->plain(); // site list
3572
		}
3573
		# Build the list now
3574
		$badImages = [];
3575
		$lines = explode( "\n", $blacklist );
3576
		foreach ( $lines as $line ) {
3577
			# List items only
3578
			if ( substr( $line, 0, 1 ) !== '*' ) {
3579
				continue;
3580
			}
3581
3582
			# Find all links
3583
			$m = [];
3584
			if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
3585
				continue;
3586
			}
3587
3588
			$exceptions = [];
3589
			$imageDBkey = false;
3590
			foreach ( $m[1] as $i => $titleText ) {
3591
				$title = Title::newFromText( $titleText );
3592
				if ( !is_null( $title ) ) {
3593
					if ( $i == 0 ) {
3594
						$imageDBkey = $title->getDBkey();
3595
					} else {
3596
						$exceptions[$title->getPrefixedDBkey()] = true;
3597
					}
3598
				}
3599
			}
3600
3601
			if ( $imageDBkey !== false ) {
3602
				$badImages[$imageDBkey] = $exceptions;
3603
			}
3604
		}
3605
		$cache->set( $key, $badImages, 60 );
3606
	}
3607
3608
	$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...
3609
	$bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
3610
3611
	return $bad;
3612
}
3613
3614
/**
3615
 * Determine whether the client at a given source IP is likely to be able to
3616
 * access the wiki via HTTPS.
3617
 *
3618
 * @param string $ip The IPv4/6 address in the normal human-readable form
3619
 * @return bool
3620
 */
3621
function wfCanIPUseHTTPS( $ip ) {
3622
	$canDo = true;
3623
	Hooks::run( 'CanIPUseHTTPS', [ $ip, &$canDo ] );
3624
	return !!$canDo;
3625
}
3626
3627
/**
3628
 * Determine input string is represents as infinity
3629
 *
3630
 * @param string $str The string to determine
3631
 * @return bool
3632
 * @since 1.25
3633
 */
3634
function wfIsInfinity( $str ) {
3635
	$infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ];
3636
	return in_array( $str, $infinityValues );
3637
}
3638
3639
/**
3640
 * Returns true if these thumbnail parameters match one that MediaWiki
3641
 * requests from file description pages and/or parser output.
3642
 *
3643
 * $params is considered non-standard if they involve a non-standard
3644
 * width or any non-default parameters aside from width and page number.
3645
 * The number of possible files with standard parameters is far less than
3646
 * that of all combinations; rate-limiting for them can thus be more generious.
3647
 *
3648
 * @param File $file
3649
 * @param array $params
3650
 * @return bool
3651
 * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25
3652
 */
3653
function wfThumbIsStandard( File $file, array $params ) {
3654
	global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages;
3655
3656
	$multipliers = [ 1 ];
3657
	if ( $wgResponsiveImages ) {
3658
		// These available sizes are hardcoded currently elsewhere in MediaWiki.
3659
		// @see Linker::processResponsiveImages
3660
		$multipliers[] = 1.5;
3661
		$multipliers[] = 2;
3662
	}
3663
3664
	$handler = $file->getHandler();
3665
	if ( !$handler || !isset( $params['width'] ) ) {
3666
		return false;
3667
	}
3668
3669
	$basicParams = [];
3670
	if ( isset( $params['page'] ) ) {
3671
		$basicParams['page'] = $params['page'];
3672
	}
3673
3674
	$thumbLimits = [];
3675
	$imageLimits = [];
3676
	// Expand limits to account for multipliers
3677
	foreach ( $multipliers as $multiplier ) {
3678
		$thumbLimits = array_merge( $thumbLimits, array_map(
3679
			function ( $width ) use ( $multiplier ) {
3680
				return round( $width * $multiplier );
3681
			}, $wgThumbLimits )
3682
		);
3683
		$imageLimits = array_merge( $imageLimits, array_map(
3684
			function ( $pair ) use ( $multiplier ) {
3685
				return [
3686
					round( $pair[0] * $multiplier ),
3687
					round( $pair[1] * $multiplier ),
3688
				];
3689
			}, $wgImageLimits )
3690
		);
3691
	}
3692
3693
	// Check if the width matches one of $wgThumbLimits
3694
	if ( in_array( $params['width'], $thumbLimits ) ) {
3695
		$normalParams = $basicParams + [ 'width' => $params['width'] ];
3696
		// Append any default values to the map (e.g. "lossy", "lossless", ...)
3697
		$handler->normaliseParams( $file, $normalParams );
3698
	} else {
3699
		// If not, then check if the width matchs one of $wgImageLimits
3700
		$match = false;
3701
		foreach ( $imageLimits as $pair ) {
3702
			$normalParams = $basicParams + [ 'width' => $pair[0], 'height' => $pair[1] ];
3703
			// Decide whether the thumbnail should be scaled on width or height.
3704
			// Also append any default values to the map (e.g. "lossy", "lossless", ...)
3705
			$handler->normaliseParams( $file, $normalParams );
3706
			// Check if this standard thumbnail size maps to the given width
3707
			if ( $normalParams['width'] == $params['width'] ) {
3708
				$match = true;
3709
				break;
3710
			}
3711
		}
3712
		if ( !$match ) {
3713
			return false; // not standard for description pages
3714
		}
3715
	}
3716
3717
	// Check that the given values for non-page, non-width, params are just defaults
3718
	foreach ( $params as $key => $value ) {
3719
		if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
3720
			return false;
3721
		}
3722
	}
3723
3724
	return true;
3725
}
3726
3727
/**
3728
 * Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
3729
 *
3730
 * Values that exist in both values will be combined with += (all values of the array
3731
 * of $newValues will be added to the values of the array of $baseArray, while values,
3732
 * that exists in both, the value of $baseArray will be used).
3733
 *
3734
 * @param array $baseArray The array where you want to add the values of $newValues to
3735
 * @param array $newValues An array with new values
3736
 * @return array The combined array
3737
 * @since 1.26
3738
 */
3739
function wfArrayPlus2d( array $baseArray, array $newValues ) {
3740
	// First merge items that are in both arrays
3741
	foreach ( $baseArray as $name => &$groupVal ) {
3742
		if ( isset( $newValues[$name] ) ) {
3743
			$groupVal += $newValues[$name];
3744
		}
3745
	}
3746
	// Now add items that didn't exist yet
3747
	$baseArray += $newValues;
3748
3749
	return $baseArray;
3750
}
3751