Completed
Branch master (e3c97b)
by
unknown
25:00
created

GlobalFunctions.php ➔ wfRemoveDotSegments()   C

Complexity

Conditions 18
Paths 37

Size

Total Lines 70
Code Lines 48

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 18
eloc 48
nc 37
nop 1
dl 0
loc 70
rs 5.5704

How to fix   Long Method    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 View Code Duplication
		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 View Code Duplication
		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
		if ( false === strpos( $url, '?' ) ) {
505
			$url .= '?';
506
		} else {
507
			$url .= '&';
508
		}
509
		$url .= $query;
510
	}
511
	return $url;
512
}
513
514
/**
515
 * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
516
 * is correct.
517
 *
518
 * The meaning of the PROTO_* constants is as follows:
519
 * PROTO_HTTP: Output a URL starting with http://
520
 * PROTO_HTTPS: Output a URL starting with https://
521
 * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
522
 * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending
523
 *    on which protocol was used for the current incoming request
524
 * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer.
525
 *    For protocol-relative URLs, use the protocol of $wgCanonicalServer
526
 * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
527
 *
528
 * @todo this won't work with current-path-relative URLs
529
 * like "subdir/foo.html", etc.
530
 *
531
 * @param string $url Either fully-qualified or a local path + query
532
 * @param string $defaultProto One of the PROTO_* constants. Determines the
533
 *    protocol to use if $url or $wgServer is protocol-relative
534
 * @return string Fully-qualified URL, current-path-relative URL or false if
535
 *    no valid URL can be constructed
536
 */
537
function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
538
	global $wgServer, $wgCanonicalServer, $wgInternalServer, $wgRequest,
539
		$wgHttpsPort;
540
	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...
541
		$serverUrl = $wgCanonicalServer;
542
	} 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...
543
		// Make $wgInternalServer fall back to $wgServer if not set
544
		$serverUrl = $wgInternalServer;
545
	} else {
546
		$serverUrl = $wgServer;
547
		if ( $defaultProto === PROTO_CURRENT ) {
548
			$defaultProto = $wgRequest->getProtocol() . '://';
549
		}
550
	}
551
552
	// Analyze $serverUrl to obtain its protocol
553
	$bits = wfParseUrl( $serverUrl );
554
	$serverHasProto = $bits && $bits['scheme'] != '';
555
556
	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...
557
		if ( $serverHasProto ) {
558
			$defaultProto = $bits['scheme'] . '://';
559
		} else {
560
			// $wgCanonicalServer or $wgInternalServer doesn't have a protocol.
561
			// This really isn't supposed to happen. Fall back to HTTP in this
562
			// ridiculous case.
563
			$defaultProto = PROTO_HTTP;
564
		}
565
	}
566
567
	$defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 );
568
569
	if ( substr( $url, 0, 2 ) == '//' ) {
570
		$url = $defaultProtoWithoutSlashes . $url;
571
	} elseif ( substr( $url, 0, 1 ) == '/' ) {
572
		// If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes,
573
		// otherwise leave it alone.
574
		$url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
575
	}
576
577
	$bits = wfParseUrl( $url );
578
579
	// ensure proper port for HTTPS arrives in URL
580
	// https://phabricator.wikimedia.org/T67184
581
	if ( $defaultProto === PROTO_HTTPS && $wgHttpsPort != 443 ) {
582
		$bits['port'] = $wgHttpsPort;
583
	}
584
585
	if ( $bits && isset( $bits['path'] ) ) {
586
		$bits['path'] = wfRemoveDotSegments( $bits['path'] );
587
		return wfAssembleUrl( $bits );
588
	} elseif ( $bits ) {
589
		# No path to expand
590
		return $url;
591
	} elseif ( substr( $url, 0, 1 ) != '/' ) {
592
		# URL is a relative path
593
		return wfRemoveDotSegments( $url );
594
	}
595
596
	# Expanded URL is not valid.
597
	return false;
598
}
599
600
/**
601
 * This function will reassemble a URL parsed with wfParseURL.  This is useful
602
 * if you need to edit part of a URL and put it back together.
603
 *
604
 * This is the basic structure used (brackets contain keys for $urlParts):
605
 * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
606
 *
607
 * @todo Need to integrate this into wfExpandUrl (bug 32168)
608
 *
609
 * @since 1.19
610
 * @param array $urlParts URL parts, as output from wfParseUrl
611
 * @return string URL assembled from its component parts
612
 */
613
function wfAssembleUrl( $urlParts ) {
614
	$result = '';
615
616
	if ( isset( $urlParts['delimiter'] ) ) {
617
		if ( isset( $urlParts['scheme'] ) ) {
618
			$result .= $urlParts['scheme'];
619
		}
620
621
		$result .= $urlParts['delimiter'];
622
	}
623
624
	if ( isset( $urlParts['host'] ) ) {
625
		if ( isset( $urlParts['user'] ) ) {
626
			$result .= $urlParts['user'];
627
			if ( isset( $urlParts['pass'] ) ) {
628
				$result .= ':' . $urlParts['pass'];
629
			}
630
			$result .= '@';
631
		}
632
633
		$result .= $urlParts['host'];
634
635
		if ( isset( $urlParts['port'] ) ) {
636
			$result .= ':' . $urlParts['port'];
637
		}
638
	}
639
640
	if ( isset( $urlParts['path'] ) ) {
641
		$result .= $urlParts['path'];
642
	}
643
644
	if ( isset( $urlParts['query'] ) ) {
645
		$result .= '?' . $urlParts['query'];
646
	}
647
648
	if ( isset( $urlParts['fragment'] ) ) {
649
		$result .= '#' . $urlParts['fragment'];
650
	}
651
652
	return $result;
653
}
654
655
/**
656
 * Remove all dot-segments in the provided URL path.  For example,
657
 * '/a/./b/../c/' becomes '/a/c/'.  For details on the algorithm, please see
658
 * RFC3986 section 5.2.4.
659
 *
660
 * @todo Need to integrate this into wfExpandUrl (bug 32168)
661
 *
662
 * @param string $urlPath URL path, potentially containing dot-segments
663
 * @return string URL path with all dot-segments removed
664
 */
665
function wfRemoveDotSegments( $urlPath ) {
666
	$output = '';
667
	$inputOffset = 0;
668
	$inputLength = strlen( $urlPath );
669
670
	while ( $inputOffset < $inputLength ) {
671
		$prefixLengthOne = substr( $urlPath, $inputOffset, 1 );
672
		$prefixLengthTwo = substr( $urlPath, $inputOffset, 2 );
673
		$prefixLengthThree = substr( $urlPath, $inputOffset, 3 );
674
		$prefixLengthFour = substr( $urlPath, $inputOffset, 4 );
675
		$trimOutput = false;
676
677
		if ( $prefixLengthTwo == './' ) {
678
			# Step A, remove leading "./"
679
			$inputOffset += 2;
680
		} elseif ( $prefixLengthThree == '../' ) {
681
			# Step A, remove leading "../"
682
			$inputOffset += 3;
683
		} elseif ( ( $prefixLengthTwo == '/.' ) && ( $inputOffset + 2 == $inputLength ) ) {
684
			# Step B, replace leading "/.$" with "/"
685
			$inputOffset += 1;
686
			$urlPath[$inputOffset] = '/';
687
		} elseif ( $prefixLengthThree == '/./' ) {
688
			# Step B, replace leading "/./" with "/"
689
			$inputOffset += 2;
690
		} elseif ( $prefixLengthThree == '/..' && ( $inputOffset + 3 == $inputLength ) ) {
691
			# Step C, replace leading "/..$" with "/" and
692
			# remove last path component in output
693
			$inputOffset += 2;
694
			$urlPath[$inputOffset] = '/';
695
			$trimOutput = true;
696
		} elseif ( $prefixLengthFour == '/../' ) {
697
			# Step C, replace leading "/../" with "/" and
698
			# remove last path component in output
699
			$inputOffset += 3;
700
			$trimOutput = true;
701
		} elseif ( ( $prefixLengthOne == '.' ) && ( $inputOffset + 1 == $inputLength ) ) {
702
			# Step D, remove "^.$"
703
			$inputOffset += 1;
704
		} elseif ( ( $prefixLengthTwo == '..' ) && ( $inputOffset + 2 == $inputLength ) ) {
705
			# Step D, remove "^..$"
706
			$inputOffset += 2;
707
		} else {
708
			# Step E, move leading path segment to output
709
			if ( $prefixLengthOne == '/' ) {
710
				$slashPos = strpos( $urlPath, '/', $inputOffset + 1 );
711
			} else {
712
				$slashPos = strpos( $urlPath, '/', $inputOffset );
713
			}
714
			if ( $slashPos === false ) {
715
				$output .= substr( $urlPath, $inputOffset );
716
				$inputOffset = $inputLength;
717
			} else {
718
				$output .= substr( $urlPath, $inputOffset, $slashPos - $inputOffset );
719
				$inputOffset += $slashPos - $inputOffset;
720
			}
721
		}
722
723
		if ( $trimOutput ) {
724
			$slashPos = strrpos( $output, '/' );
725
			if ( $slashPos === false ) {
726
				$output = '';
727
			} else {
728
				$output = substr( $output, 0, $slashPos );
729
			}
730
		}
731
	}
732
733
	return $output;
734
}
735
736
/**
737
 * Returns a regular expression of url protocols
738
 *
739
 * @param bool $includeProtocolRelative If false, remove '//' from the returned protocol list.
740
 *        DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
741
 * @return string
742
 */
743
function wfUrlProtocols( $includeProtocolRelative = true ) {
744
	global $wgUrlProtocols;
745
746
	// Cache return values separately based on $includeProtocolRelative
747
	static $withProtRel = null, $withoutProtRel = null;
748
	$cachedValue = $includeProtocolRelative ? $withProtRel : $withoutProtRel;
749
	if ( !is_null( $cachedValue ) ) {
750
		return $cachedValue;
751
	}
752
753
	// Support old-style $wgUrlProtocols strings, for backwards compatibility
754
	// with LocalSettings files from 1.5
755
	if ( is_array( $wgUrlProtocols ) ) {
756
		$protocols = [];
757
		foreach ( $wgUrlProtocols as $protocol ) {
758
			// Filter out '//' if !$includeProtocolRelative
759
			if ( $includeProtocolRelative || $protocol !== '//' ) {
760
				$protocols[] = preg_quote( $protocol, '/' );
761
			}
762
		}
763
764
		$retval = implode( '|', $protocols );
765
	} else {
766
		// Ignore $includeProtocolRelative in this case
767
		// This case exists for pre-1.6 compatibility, and we can safely assume
768
		// that '//' won't appear in a pre-1.6 config because protocol-relative
769
		// URLs weren't supported until 1.18
770
		$retval = $wgUrlProtocols;
771
	}
772
773
	// Cache return value
774
	if ( $includeProtocolRelative ) {
775
		$withProtRel = $retval;
776
	} else {
777
		$withoutProtRel = $retval;
778
	}
779
	return $retval;
780
}
781
782
/**
783
 * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
784
 * you need a regex that matches all URL protocols but does not match protocol-
785
 * relative URLs
786
 * @return string
787
 */
788
function wfUrlProtocolsWithoutProtRel() {
789
	return wfUrlProtocols( false );
790
}
791
792
/**
793
 * parse_url() work-alike, but non-broken.  Differences:
794
 *
795
 * 1) Does not raise warnings on bad URLs (just returns false).
796
 * 2) Handles protocols that don't use :// (e.g., mailto: and news:, as well as
797
 *    protocol-relative URLs) correctly.
798
 * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2)).
799
 *
800
 * @param string $url A URL to parse
801
 * @return string[] Bits of the URL in an associative array, per PHP docs
802
 */
803
function wfParseUrl( $url ) {
804
	global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
805
806
	// Protocol-relative URLs are handled really badly by parse_url(). It's so
807
	// bad that the easiest way to handle them is to just prepend 'http:' and
808
	// strip the protocol out later.
809
	$wasRelative = substr( $url, 0, 2 ) == '//';
810
	if ( $wasRelative ) {
811
		$url = "http:$url";
812
	}
813
	MediaWiki\suppressWarnings();
814
	$bits = parse_url( $url );
815
	MediaWiki\restoreWarnings();
816
	// parse_url() returns an array without scheme for some invalid URLs, e.g.
817
	// parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
818
	if ( !$bits || !isset( $bits['scheme'] ) ) {
819
		return false;
820
	}
821
822
	// parse_url() incorrectly handles schemes case-sensitively. Convert it to lowercase.
823
	$bits['scheme'] = strtolower( $bits['scheme'] );
824
825
	// most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
826
	if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
827
		$bits['delimiter'] = '://';
828
	} elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
829
		$bits['delimiter'] = ':';
830
		// parse_url detects for news: and mailto: the host part of an url as path
831
		// We have to correct this wrong detection
832 View Code Duplication
		if ( isset( $bits['path'] ) ) {
833
			$bits['host'] = $bits['path'];
834
			$bits['path'] = '';
835
		}
836
	} else {
837
		return false;
838
	}
839
840
	/* Provide an empty host for eg. file:/// urls (see bug 28627) */
841
	if ( !isset( $bits['host'] ) ) {
842
		$bits['host'] = '';
843
844
		// bug 45069
845
		if ( isset( $bits['path'] ) ) {
846
			/* parse_url loses the third / for file:///c:/ urls (but not on variants) */
847
			if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
848
				$bits['path'] = '/' . $bits['path'];
849
			}
850
		} else {
851
			$bits['path'] = '';
852
		}
853
	}
854
855
	// If the URL was protocol-relative, fix scheme and delimiter
856
	if ( $wasRelative ) {
857
		$bits['scheme'] = '';
858
		$bits['delimiter'] = '//';
859
	}
860
	return $bits;
861
}
862
863
/**
864
 * Take a URL, make sure it's expanded to fully qualified, and replace any
865
 * encoded non-ASCII Unicode characters with their UTF-8 original forms
866
 * for more compact display and legibility for local audiences.
867
 *
868
 * @todo handle punycode domains too
869
 *
870
 * @param string $url
871
 * @return string
872
 */
873
function wfExpandIRI( $url ) {
874
	return preg_replace_callback(
875
		'/((?:%[89A-F][0-9A-F])+)/i',
876
		'wfExpandIRI_callback',
877
		wfExpandUrl( $url )
878
	);
879
}
880
881
/**
882
 * Private callback for wfExpandIRI
883
 * @param array $matches
884
 * @return string
885
 */
886
function wfExpandIRI_callback( $matches ) {
887
	return urldecode( $matches[1] );
888
}
889
890
/**
891
 * Make URL indexes, appropriate for the el_index field of externallinks.
892
 *
893
 * @param string $url
894
 * @return array
895
 */
896
function wfMakeUrlIndexes( $url ) {
897
	$bits = wfParseUrl( $url );
898
899
	// Reverse the labels in the hostname, convert to lower case
900
	// For emails reverse domainpart only
901
	if ( $bits['scheme'] == 'mailto' ) {
902
		$mailparts = explode( '@', $bits['host'], 2 );
903
		if ( count( $mailparts ) === 2 ) {
904
			$domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
905
		} else {
906
			// No domain specified, don't mangle it
907
			$domainpart = '';
908
		}
909
		$reversedHost = $domainpart . '@' . $mailparts[0];
910
	} else {
911
		$reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
912
	}
913
	// Add an extra dot to the end
914
	// Why? Is it in wrong place in mailto links?
915
	if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
916
		$reversedHost .= '.';
917
	}
918
	// Reconstruct the pseudo-URL
919
	$prot = $bits['scheme'];
920
	$index = $prot . $bits['delimiter'] . $reversedHost;
921
	// Leave out user and password. Add the port, path, query and fragment
922
	if ( isset( $bits['port'] ) ) {
923
		$index .= ':' . $bits['port'];
924
	}
925
	if ( isset( $bits['path'] ) ) {
926
		$index .= $bits['path'];
927
	} else {
928
		$index .= '/';
929
	}
930
	if ( isset( $bits['query'] ) ) {
931
		$index .= '?' . $bits['query'];
932
	}
933
	if ( isset( $bits['fragment'] ) ) {
934
		$index .= '#' . $bits['fragment'];
935
	}
936
937
	if ( $prot == '' ) {
938
		return [ "http:$index", "https:$index" ];
939
	} else {
940
		return [ $index ];
941
	}
942
}
943
944
/**
945
 * Check whether a given URL has a domain that occurs in a given set of domains
946
 * @param string $url URL
947
 * @param array $domains Array of domains (strings)
948
 * @return bool True if the host part of $url ends in one of the strings in $domains
949
 */
950
function wfMatchesDomainList( $url, $domains ) {
951
	$bits = wfParseUrl( $url );
952
	if ( is_array( $bits ) && isset( $bits['host'] ) ) {
953
		$host = '.' . $bits['host'];
954
		foreach ( (array)$domains as $domain ) {
955
			$domain = '.' . $domain;
956
			if ( substr( $host, -strlen( $domain ) ) === $domain ) {
957
				return true;
958
			}
959
		}
960
	}
961
	return false;
962
}
963
964
/**
965
 * Sends a line to the debug log if enabled or, optionally, to a comment in output.
966
 * In normal operation this is a NOP.
967
 *
968
 * Controlling globals:
969
 * $wgDebugLogFile - points to the log file
970
 * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
971
 * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
972
 *
973
 * @since 1.25 support for additional context data
974
 *
975
 * @param string $text
976
 * @param string|bool $dest Destination of the message:
977
 *     - 'all': both to the log and HTML (debug toolbar or HTML comments)
978
 *     - 'private': excluded from HTML output
979
 *   For backward compatibility, it can also take a boolean:
980
 *     - true: same as 'all'
981
 *     - false: same as 'private'
982
 * @param array $context Additional logging context data
983
 */
984
function wfDebug( $text, $dest = 'all', array $context = [] ) {
985
	global $wgDebugRawPage, $wgDebugLogPrefix;
986
	global $wgDebugTimestamps, $wgRequestTime;
987
988
	if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
989
		return;
990
	}
991
992
	$text = trim( $text );
993
994
	if ( $wgDebugTimestamps ) {
995
		$context['seconds_elapsed'] = sprintf(
996
			'%6.4f',
997
			microtime( true ) - $wgRequestTime
998
		);
999
		$context['memory_used'] = sprintf(
1000
			'%5.1fM',
1001
			( memory_get_usage( true ) / ( 1024 * 1024 ) )
1002
		);
1003
	}
1004
1005
	if ( $wgDebugLogPrefix !== '' ) {
1006
		$context['prefix'] = $wgDebugLogPrefix;
1007
	}
1008
	$context['private'] = ( $dest === false || $dest === 'private' );
1009
1010
	$logger = LoggerFactory::getInstance( 'wfDebug' );
1011
	$logger->debug( $text, $context );
1012
}
1013
1014
/**
1015
 * Returns true if debug logging should be suppressed if $wgDebugRawPage = false
1016
 * @return bool
1017
 */
1018
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...
1019
	static $cache;
1020
	if ( $cache !== null ) {
1021
		return $cache;
1022
	}
1023
	# Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
1024
	if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
1025
		|| (
1026
			isset( $_SERVER['SCRIPT_NAME'] )
1027
			&& substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
1028
		)
1029
	) {
1030
		$cache = true;
1031
	} else {
1032
		$cache = false;
1033
	}
1034
	return $cache;
1035
}
1036
1037
/**
1038
 * Send a line giving PHP memory usage.
1039
 *
1040
 * @param bool $exact Print exact byte values instead of kibibytes (default: false)
1041
 */
1042
function wfDebugMem( $exact = false ) {
1043
	$mem = memory_get_usage();
1044
	if ( !$exact ) {
1045
		$mem = floor( $mem / 1024 ) . ' KiB';
1046
	} else {
1047
		$mem .= ' B';
1048
	}
1049
	wfDebug( "Memory usage: $mem\n" );
1050
}
1051
1052
/**
1053
 * Send a line to a supplementary debug log file, if configured, or main debug
1054
 * log if not.
1055
 *
1056
 * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to
1057
 * a string filename or an associative array mapping 'destination' to the
1058
 * desired filename. The associative array may also contain a 'sample' key
1059
 * with an integer value, specifying a sampling factor. Sampled log events
1060
 * will be emitted with a 1 in N random chance.
1061
 *
1062
 * @since 1.23 support for sampling log messages via $wgDebugLogGroups.
1063
 * @since 1.25 support for additional context data
1064
 * @since 1.25 sample behavior dependent on configured $wgMWLoggerDefaultSpi
1065
 *
1066
 * @param string $logGroup
1067
 * @param string $text
1068
 * @param string|bool $dest Destination of the message:
1069
 *     - 'all': both to the log and HTML (debug toolbar or HTML comments)
1070
 *     - 'private': only to the specific log if set in $wgDebugLogGroups and
1071
 *       discarded otherwise
1072
 *   For backward compatibility, it can also take a boolean:
1073
 *     - true: same as 'all'
1074
 *     - false: same as 'private'
1075
 * @param array $context Additional logging context data
1076
 */
1077
function wfDebugLog(
1078
	$logGroup, $text, $dest = 'all', array $context = []
1079
) {
1080
	$text = trim( $text );
1081
1082
	$logger = LoggerFactory::getInstance( $logGroup );
1083
	$context['private'] = ( $dest === false || $dest === 'private' );
1084
	$logger->info( $text, $context );
1085
}
1086
1087
/**
1088
 * Log for database errors
1089
 *
1090
 * @since 1.25 support for additional context data
1091
 *
1092
 * @param string $text Database error message.
1093
 * @param array $context Additional logging context data
1094
 */
1095
function wfLogDBError( $text, array $context = [] ) {
1096
	$logger = LoggerFactory::getInstance( 'wfLogDBError' );
1097
	$logger->error( trim( $text ), $context );
1098
}
1099
1100
/**
1101
 * Throws a warning that $function is deprecated
1102
 *
1103
 * @param string $function
1104
 * @param string|bool $version Version of MediaWiki that the function
1105
 *    was deprecated in (Added in 1.19).
1106
 * @param string|bool $component Added in 1.19.
1107
 * @param int $callerOffset How far up the call stack is the original
1108
 *    caller. 2 = function that called the function that called
1109
 *    wfDeprecated (Added in 1.20)
1110
 *
1111
 * @return null
1112
 */
1113
function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
1114
	MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
1115
}
1116
1117
/**
1118
 * Send a warning either to the debug log or in a PHP error depending on
1119
 * $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead.
1120
 *
1121
 * @param string $msg Message to send
1122
 * @param int $callerOffset Number of items to go back in the backtrace to
1123
 *        find the correct caller (1 = function calling wfWarn, ...)
1124
 * @param int $level PHP error level; defaults to E_USER_NOTICE;
1125
 *        only used when $wgDevelopmentWarnings is true
1126
 */
1127
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
1128
	MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' );
1129
}
1130
1131
/**
1132
 * Send a warning as a PHP error and the debug log. This is intended for logging
1133
 * warnings in production. For logging development warnings, use WfWarn 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 wfLogWarning, ...)
1138
 * @param int $level PHP error level; defaults to E_USER_WARNING
1139
 */
1140
function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
1141
	MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
1142
}
1143
1144
/**
1145
 * Log to a file without getting "file size exceeded" signals.
1146
 *
1147
 * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
1148
 * send lines to the specified port, prefixed by the specified prefix and a space.
1149
 * @since 1.25 support for additional context data
1150
 *
1151
 * @param string $text
1152
 * @param string $file Filename
1153
 * @param array $context Additional logging context data
1154
 * @throws MWException
1155
 * @deprecated since 1.25 Use \MediaWiki\Logger\LegacyLogger::emit or UDPTransport
1156
 */
1157
function wfErrorLog( $text, $file, array $context = [] ) {
1158
	wfDeprecated( __METHOD__, '1.25' );
1159
	$logger = LoggerFactory::getInstance( 'wfErrorLog' );
1160
	$context['destination'] = $file;
1161
	$logger->info( trim( $text ), $context );
1162
}
1163
1164
/**
1165
 * @todo document
1166
 */
1167
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...
1168
	global $wgDebugLogGroups, $wgDebugRawPage;
1169
1170
	$context = RequestContext::getMain();
1171
	$request = $context->getRequest();
1172
1173
	$profiler = Profiler::instance();
1174
	$profiler->setContext( $context );
1175
	$profiler->logData();
1176
1177
	$config = $context->getConfig();
1178
	if ( $config->get( 'StatsdServer' ) ) {
1179
		try {
1180
			$statsdServer = explode( ':', $config->get( 'StatsdServer' ) );
1181
			$statsdHost = $statsdServer[0];
1182
			$statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125;
1183
			$statsdSender = new SocketSender( $statsdHost, $statsdPort );
1184
			$statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
1185
			$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...
1186
		} catch ( Exception $ex ) {
1187
			MWExceptionHandler::logException( $ex );
1188
		}
1189
	}
1190
1191
	# Profiling must actually be enabled...
1192
	if ( $profiler instanceof ProfilerStub ) {
1193
		return;
1194
	}
1195
1196
	if ( isset( $wgDebugLogGroups['profileoutput'] )
1197
		&& $wgDebugLogGroups['profileoutput'] === false
1198
	) {
1199
		// Explicitly disabled
1200
		return;
1201
	}
1202
	if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
1203
		return;
1204
	}
1205
1206
	$ctx = [ 'elapsed' => $request->getElapsedTime() ];
1207
	if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1208
		$ctx['forwarded_for'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
1209
	}
1210
	if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
1211
		$ctx['client_ip'] = $_SERVER['HTTP_CLIENT_IP'];
1212
	}
1213
	if ( !empty( $_SERVER['HTTP_FROM'] ) ) {
1214
		$ctx['from'] = $_SERVER['HTTP_FROM'];
1215
	}
1216
	if ( isset( $ctx['forwarded_for'] ) ||
1217
		isset( $ctx['client_ip'] ) ||
1218
		isset( $ctx['from'] ) ) {
1219
		$ctx['proxy'] = $_SERVER['REMOTE_ADDR'];
1220
	}
1221
1222
	// Don't load $wgUser at this late stage just for statistics purposes
1223
	// @todo FIXME: We can detect some anons even if it is not loaded.
1224
	// See User::getId()
1225
	$user = $context->getUser();
1226
	$ctx['anon'] = $user->isItemLoaded( 'id' ) && $user->isAnon();
1227
1228
	// Command line script uses a FauxRequest object which does not have
1229
	// any knowledge about an URL and throw an exception instead.
1230
	try {
1231
		$ctx['url'] = urldecode( $request->getRequestURL() );
1232
	} catch ( Exception $ignored ) {
1233
		// no-op
1234
	}
1235
1236
	$ctx['output'] = $profiler->getOutput();
1237
1238
	$log = LoggerFactory::getInstance( 'profileoutput' );
1239
	$log->info( "Elapsed: {elapsed}; URL: <{url}>\n{output}", $ctx );
1240
}
1241
1242
/**
1243
 * Increment a statistics counter
1244
 *
1245
 * @param string $key
1246
 * @param int $count
1247
 * @return void
1248
 */
1249
function wfIncrStats( $key, $count = 1 ) {
1250
	$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...
1251
	$stats->updateCount( $key, $count );
1252
}
1253
1254
/**
1255
 * Check whether the wiki is in read-only mode.
1256
 *
1257
 * @return bool
1258
 */
1259
function wfReadOnly() {
1260
	return wfReadOnlyReason() !== false;
1261
}
1262
1263
/**
1264
 * Check if the site is in read-only mode and return the message if so
1265
 *
1266
 * This checks wfConfiguredReadOnlyReason() and the main load balancer
1267
 * for slave lag. This may result in DB_SLAVE connection being made.
1268
 *
1269
 * @return string|bool String when in read-only mode; false otherwise
1270
 */
1271
function wfReadOnlyReason() {
1272
	$readOnly = wfConfiguredReadOnlyReason();
1273
	if ( $readOnly !== false ) {
1274
		return $readOnly;
1275
	}
1276
1277
	static $lbReadOnly = null;
1278
	if ( $lbReadOnly === null ) {
1279
		// Callers use this method to be aware that data presented to a user
1280
		// may be very stale and thus allowing submissions can be problematic.
1281
		$lbReadOnly = wfGetLB()->getReadOnlyReason();
1282
	}
1283
1284
	return $lbReadOnly;
1285
}
1286
1287
/**
1288
 * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
1289
 *
1290
 * @return string|bool String when in read-only mode; false otherwise
1291
 * @since 1.27
1292
 */
1293
function wfConfiguredReadOnlyReason() {
1294
	global $wgReadOnly, $wgReadOnlyFile;
1295
1296
	if ( $wgReadOnly === null ) {
1297
		// Set $wgReadOnly for faster access next time
1298
		if ( is_file( $wgReadOnlyFile ) && filesize( $wgReadOnlyFile ) > 0 ) {
1299
			$wgReadOnly = file_get_contents( $wgReadOnlyFile );
1300
		} else {
1301
			$wgReadOnly = false;
1302
		}
1303
	}
1304
1305
	return $wgReadOnly;
1306
}
1307
1308
/**
1309
 * Return a Language object from $langcode
1310
 *
1311
 * @param Language|string|bool $langcode Either:
1312
 *                  - a Language object
1313
 *                  - code of the language to get the message for, if it is
1314
 *                    a valid code create a language for that language, if
1315
 *                    it is a string but not a valid code then make a basic
1316
 *                    language object
1317
 *                  - a boolean: if it's false then use the global object for
1318
 *                    the current user's language (as a fallback for the old parameter
1319
 *                    functionality), or if it is true then use global object
1320
 *                    for the wiki's content language.
1321
 * @return Language
1322
 */
1323
function wfGetLangObj( $langcode = false ) {
1324
	# Identify which language to get or create a language object for.
1325
	# Using is_object here due to Stub objects.
1326
	if ( is_object( $langcode ) ) {
1327
		# Great, we already have the object (hopefully)!
1328
		return $langcode;
1329
	}
1330
1331
	global $wgContLang, $wgLanguageCode;
1332
	if ( $langcode === true || $langcode === $wgLanguageCode ) {
1333
		# $langcode is the language code of the wikis content language object.
1334
		# or it is a boolean and value is true
1335
		return $wgContLang;
1336
	}
1337
1338
	global $wgLang;
1339
	if ( $langcode === false || $langcode === $wgLang->getCode() ) {
1340
		# $langcode is the language code of user language object.
1341
		# or it was a boolean and value is false
1342
		return $wgLang;
1343
	}
1344
1345
	$validCodes = array_keys( Language::fetchLanguageNames() );
1346
	if ( in_array( $langcode, $validCodes ) ) {
1347
		# $langcode corresponds to a valid language.
1348
		return Language::factory( $langcode );
0 ignored issues
show
Bug introduced by
It seems like $langcode defined by parameter $langcode on line 1323 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...
1349
	}
1350
1351
	# $langcode is a string, but not a valid language code; use content language.
1352
	wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
1353
	return $wgContLang;
1354
}
1355
1356
/**
1357
 * This is the function for getting translated interface messages.
1358
 *
1359
 * @see Message class for documentation how to use them.
1360
 * @see https://www.mediawiki.org/wiki/Manual:Messages_API
1361
 *
1362
 * This function replaces all old wfMsg* functions.
1363
 *
1364
 * @param string|string[]|MessageSpecifier $key Message key, or array of keys, or a MessageSpecifier
1365
 * @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...
1366
 * @return Message
1367
 *
1368
 * @since 1.17
1369
 *
1370
 * @see Message::__construct
1371
 */
1372
function wfMessage( $key /*...*/ ) {
1373
	$params = func_get_args();
1374
	array_shift( $params );
1375
	if ( isset( $params[0] ) && is_array( $params[0] ) ) {
1376
		$params = $params[0];
1377
	}
1378
	return new Message( $key, $params );
1379
}
1380
1381
/**
1382
 * This function accepts multiple message keys and returns a message instance
1383
 * for the first message which is non-empty. If all messages are empty then an
1384
 * instance of the first message key is returned.
1385
 *
1386
 * @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...
1387
 * @return Message
1388
 *
1389
 * @since 1.18
1390
 *
1391
 * @see Message::newFallbackSequence
1392
 */
1393
function wfMessageFallback( /*...*/ ) {
1394
	$args = func_get_args();
1395
	return call_user_func_array( 'Message::newFallbackSequence', $args );
1396
}
1397
1398
/**
1399
 * Replace message parameter keys on the given formatted output.
1400
 *
1401
 * @param string $message
1402
 * @param array $args
1403
 * @return string
1404
 * @private
1405
 */
1406
function wfMsgReplaceArgs( $message, $args ) {
1407
	# Fix windows line-endings
1408
	# Some messages are split with explode("\n", $msg)
1409
	$message = str_replace( "\r", '', $message );
1410
1411
	// Replace arguments
1412
	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...
1413
		if ( is_array( $args[0] ) ) {
1414
			$args = array_values( $args[0] );
1415
		}
1416
		$replacementKeys = [];
1417
		foreach ( $args as $n => $param ) {
1418
			$replacementKeys['$' . ( $n + 1 )] = $param;
1419
		}
1420
		$message = strtr( $message, $replacementKeys );
1421
	}
1422
1423
	return $message;
1424
}
1425
1426
/**
1427
 * Fetch server name for use in error reporting etc.
1428
 * Use real server name if available, so we know which machine
1429
 * in a server farm generated the current page.
1430
 *
1431
 * @return string
1432
 */
1433
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...
1434
	static $host;
1435
	if ( is_null( $host ) ) {
1436
1437
		# Hostname overriding
1438
		global $wgOverrideHostname;
1439
		if ( $wgOverrideHostname !== false ) {
1440
			# Set static and skip any detection
1441
			$host = $wgOverrideHostname;
1442
			return $host;
1443
		}
1444
1445
		if ( function_exists( 'posix_uname' ) ) {
1446
			// This function not present on Windows
1447
			$uname = posix_uname();
1448
		} else {
1449
			$uname = false;
1450
		}
1451
		if ( is_array( $uname ) && isset( $uname['nodename'] ) ) {
1452
			$host = $uname['nodename'];
1453
		} elseif ( getenv( 'COMPUTERNAME' ) ) {
1454
			# Windows computer name
1455
			$host = getenv( 'COMPUTERNAME' );
1456
		} else {
1457
			# This may be a virtual server.
1458
			$host = $_SERVER['SERVER_NAME'];
1459
		}
1460
	}
1461
	return $host;
1462
}
1463
1464
/**
1465
 * Returns a script tag that stores the amount of time it took MediaWiki to
1466
 * handle the request in milliseconds as 'wgBackendResponseTime'.
1467
 *
1468
 * If $wgShowHostnames is true, the script will also set 'wgHostname' to the
1469
 * hostname of the server handling the request.
1470
 *
1471
 * @return string
1472
 */
1473
function wfReportTime() {
1474
	global $wgRequestTime, $wgShowHostnames;
1475
1476
	$responseTime = round( ( microtime( true ) - $wgRequestTime ) * 1000 );
1477
	$reportVars = [ 'wgBackendResponseTime' => $responseTime ];
1478
	if ( $wgShowHostnames ) {
1479
		$reportVars['wgHostname'] = wfHostname();
1480
	}
1481
	return Skin::makeVariablesScript( $reportVars );
1482
}
1483
1484
/**
1485
 * Safety wrapper for debug_backtrace().
1486
 *
1487
 * Will return an empty array if debug_backtrace is disabled, otherwise
1488
 * the output from debug_backtrace() (trimmed).
1489
 *
1490
 * @param int $limit This parameter can be used to limit the number of stack frames returned
1491
 *
1492
 * @return array Array of backtrace information
1493
 */
1494
function wfDebugBacktrace( $limit = 0 ) {
1495
	static $disabled = null;
1496
1497
	if ( is_null( $disabled ) ) {
1498
		$disabled = !function_exists( 'debug_backtrace' );
1499
		if ( $disabled ) {
1500
			wfDebug( "debug_backtrace() is disabled\n" );
1501
		}
1502
	}
1503
	if ( $disabled ) {
1504
		return [];
1505
	}
1506
1507
	if ( $limit ) {
1508
		return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 );
1509
	} else {
1510
		return array_slice( debug_backtrace(), 1 );
1511
	}
1512
}
1513
1514
/**
1515
 * Get a debug backtrace as a string
1516
 *
1517
 * @param bool|null $raw If true, the return value is plain text. If false, HTML.
1518
 *   Defaults to $wgCommandLineMode if unset.
1519
 * @return string
1520
 * @since 1.25 Supports $raw parameter.
1521
 */
1522
function wfBacktrace( $raw = null ) {
1523
	global $wgCommandLineMode;
1524
1525
	if ( $raw === null ) {
1526
		$raw = $wgCommandLineMode;
1527
	}
1528
1529
	if ( $raw ) {
1530
		$frameFormat = "%s line %s calls %s()\n";
1531
		$traceFormat = "%s";
1532
	} else {
1533
		$frameFormat = "<li>%s line %s calls %s()</li>\n";
1534
		$traceFormat = "<ul>\n%s</ul>\n";
1535
	}
1536
1537
	$frames = array_map( function ( $frame ) use ( $frameFormat ) {
1538
		$file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-';
1539
		$line = isset( $frame['line'] ) ? $frame['line'] : '-';
1540
		$call = $frame['function'];
1541
		if ( !empty( $frame['class'] ) ) {
1542
			$call = $frame['class'] . $frame['type'] . $call;
1543
		}
1544
		return sprintf( $frameFormat, $file, $line, $call );
1545
	}, wfDebugBacktrace() );
1546
1547
	return sprintf( $traceFormat, implode( '', $frames ) );
1548
}
1549
1550
/**
1551
 * Get the name of the function which called this function
1552
 * wfGetCaller( 1 ) is the function with the wfGetCaller() call (ie. __FUNCTION__)
1553
 * wfGetCaller( 2 ) [default] is the caller of the function running wfGetCaller()
1554
 * wfGetCaller( 3 ) is the parent of that.
1555
 *
1556
 * @param int $level
1557
 * @return string
1558
 */
1559
function wfGetCaller( $level = 2 ) {
1560
	$backtrace = wfDebugBacktrace( $level + 1 );
1561
	if ( isset( $backtrace[$level] ) ) {
1562
		return wfFormatStackFrame( $backtrace[$level] );
1563
	} else {
1564
		return 'unknown';
1565
	}
1566
}
1567
1568
/**
1569
 * Return a string consisting of callers in the stack. Useful sometimes
1570
 * for profiling specific points.
1571
 *
1572
 * @param int $limit The maximum depth of the stack frame to return, or false for the entire stack.
1573
 * @return string
1574
 */
1575
function wfGetAllCallers( $limit = 3 ) {
1576
	$trace = array_reverse( wfDebugBacktrace() );
1577
	if ( !$limit || $limit > count( $trace ) - 1 ) {
1578
		$limit = count( $trace ) - 1;
1579
	}
1580
	$trace = array_slice( $trace, -$limit - 1, $limit );
1581
	return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
1582
}
1583
1584
/**
1585
 * Return a string representation of frame
1586
 *
1587
 * @param array $frame
1588
 * @return string
1589
 */
1590
function wfFormatStackFrame( $frame ) {
1591
	if ( !isset( $frame['function'] ) ) {
1592
		return 'NO_FUNCTION_GIVEN';
1593
	}
1594
	return isset( $frame['class'] ) && isset( $frame['type'] ) ?
1595
		$frame['class'] . $frame['type'] . $frame['function'] :
1596
		$frame['function'];
1597
}
1598
1599
/* Some generic result counters, pulled out of SearchEngine */
1600
1601
/**
1602
 * @todo document
1603
 *
1604
 * @param int $offset
1605
 * @param int $limit
1606
 * @return string
1607
 */
1608
function wfShowingResults( $offset, $limit ) {
1609
	return wfMessage( 'showingresults' )->numParams( $limit, $offset + 1 )->parse();
1610
}
1611
1612
/**
1613
 * @todo document
1614
 * @todo FIXME: We may want to blacklist some broken browsers
1615
 *
1616
 * @param bool $force
1617
 * @return bool Whereas client accept gzip compression
1618
 */
1619
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...
1620
	static $result = null;
1621
	if ( $result === null || $force ) {
1622
		$result = false;
1623
		if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
1624
			# @todo FIXME: We may want to blacklist some broken browsers
1625
			$m = [];
1626
			if ( preg_match(
1627
					'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
1628
					$_SERVER['HTTP_ACCEPT_ENCODING'],
1629
					$m
1630
				)
1631
			) {
1632
				if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
1633
					$result = false;
1634
					return $result;
1635
				}
1636
				wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
1637
				$result = true;
1638
			}
1639
		}
1640
	}
1641
	return $result;
1642
}
1643
1644
/**
1645
 * Escapes the given text so that it may be output using addWikiText()
1646
 * without any linking, formatting, etc. making its way through. This
1647
 * is achieved by substituting certain characters with HTML entities.
1648
 * As required by the callers, "<nowiki>" is not used.
1649
 *
1650
 * @param string $text Text to be escaped
1651
 * @return string
1652
 */
1653
function wfEscapeWikiText( $text ) {
1654
	static $repl = null, $repl2 = null;
1655
	if ( $repl === null ) {
1656
		$repl = [
1657
			'"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
1658
			'=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
1659
			'{' => '&#123;', '|' => '&#124;', '}' => '&#125;', ';' => '&#59;',
1660
			"\n#" => "\n&#35;", "\r#" => "\r&#35;",
1661
			"\n*" => "\n&#42;", "\r*" => "\r&#42;",
1662
			"\n:" => "\n&#58;", "\r:" => "\r&#58;",
1663
			"\n " => "\n&#32;", "\r " => "\r&#32;",
1664
			"\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
1665
			"\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
1666
			"\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
1667
			"\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
1668
			'__' => '_&#95;', '://' => '&#58;//',
1669
		];
1670
1671
		// We have to catch everything "\s" matches in PCRE
1672
		foreach ( [ 'ISBN', 'RFC', 'PMID' ] as $magic ) {
1673
			$repl["$magic "] = "$magic&#32;";
1674
			$repl["$magic\t"] = "$magic&#9;";
1675
			$repl["$magic\r"] = "$magic&#13;";
1676
			$repl["$magic\n"] = "$magic&#10;";
1677
			$repl["$magic\f"] = "$magic&#12;";
1678
		}
1679
1680
		// And handle protocols that don't use "://"
1681
		global $wgUrlProtocols;
1682
		$repl2 = [];
1683
		foreach ( $wgUrlProtocols as $prot ) {
1684
			if ( substr( $prot, -1 ) === ':' ) {
1685
				$repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
1686
			}
1687
		}
1688
		$repl2 = $repl2 ? '/\b(' . implode( '|', $repl2 ) . '):/i' : '/^(?!)/';
1689
	}
1690
	$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...
1691
	$text = preg_replace( $repl2, '$1&#58;', $text );
1692
	return $text;
1693
}
1694
1695
/**
1696
 * Sets dest to source and returns the original value of dest
1697
 * If source is NULL, it just returns the value, it doesn't set the variable
1698
 * If force is true, it will set the value even if source is NULL
1699
 *
1700
 * @param mixed $dest
1701
 * @param mixed $source
1702
 * @param bool $force
1703
 * @return mixed
1704
 */
1705
function wfSetVar( &$dest, $source, $force = false ) {
1706
	$temp = $dest;
1707
	if ( !is_null( $source ) || $force ) {
1708
		$dest = $source;
1709
	}
1710
	return $temp;
1711
}
1712
1713
/**
1714
 * As for wfSetVar except setting a bit
1715
 *
1716
 * @param int $dest
1717
 * @param int $bit
1718
 * @param bool $state
1719
 *
1720
 * @return bool
1721
 */
1722
function wfSetBit( &$dest, $bit, $state = true ) {
1723
	$temp = (bool)( $dest & $bit );
1724
	if ( !is_null( $state ) ) {
1725
		if ( $state ) {
1726
			$dest |= $bit;
1727
		} else {
1728
			$dest &= ~$bit;
1729
		}
1730
	}
1731
	return $temp;
1732
}
1733
1734
/**
1735
 * A wrapper around the PHP function var_export().
1736
 * Either print it or add it to the regular output ($wgOut).
1737
 *
1738
 * @param mixed $var A PHP variable to dump.
1739
 */
1740
function wfVarDump( $var ) {
1741
	global $wgOut;
1742
	$s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
1743
	if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
1744
		print $s;
1745
	} else {
1746
		$wgOut->addHTML( $s );
1747
	}
1748
}
1749
1750
/**
1751
 * Provide a simple HTTP error.
1752
 *
1753
 * @param int|string $code
1754
 * @param string $label
1755
 * @param string $desc
1756
 */
1757
function wfHttpError( $code, $label, $desc ) {
1758
	global $wgOut;
1759
	HttpStatus::header( $code );
1760
	if ( $wgOut ) {
1761
		$wgOut->disable();
1762
		$wgOut->sendCacheControl();
1763
	}
1764
1765
	header( 'Content-type: text/html; charset=utf-8' );
1766
	print '<!DOCTYPE html>' .
1767
		'<html><head><title>' .
1768
		htmlspecialchars( $label ) .
1769
		'</title></head><body><h1>' .
1770
		htmlspecialchars( $label ) .
1771
		'</h1><p>' .
1772
		nl2br( htmlspecialchars( $desc ) ) .
1773
		"</p></body></html>\n";
1774
}
1775
1776
/**
1777
 * Clear away any user-level output buffers, discarding contents.
1778
 *
1779
 * Suitable for 'starting afresh', for instance when streaming
1780
 * relatively large amounts of data without buffering, or wanting to
1781
 * output image files without ob_gzhandler's compression.
1782
 *
1783
 * The optional $resetGzipEncoding parameter controls suppression of
1784
 * the Content-Encoding header sent by ob_gzhandler; by default it
1785
 * is left. See comments for wfClearOutputBuffers() for why it would
1786
 * be used.
1787
 *
1788
 * Note that some PHP configuration options may add output buffer
1789
 * layers which cannot be removed; these are left in place.
1790
 *
1791
 * @param bool $resetGzipEncoding
1792
 */
1793
function wfResetOutputBuffers( $resetGzipEncoding = true ) {
1794
	if ( $resetGzipEncoding ) {
1795
		// Suppress Content-Encoding and Content-Length
1796
		// headers from 1.10+s wfOutputHandler
1797
		global $wgDisableOutputCompression;
1798
		$wgDisableOutputCompression = true;
1799
	}
1800
	while ( $status = ob_get_status() ) {
1801
		if ( isset( $status['flags'] ) ) {
1802
			$flags = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
1803
			$deleteable = ( $status['flags'] & $flags ) === $flags;
1804
		} elseif ( isset( $status['del'] ) ) {
1805
			$deleteable = $status['del'];
1806
		} else {
1807
			// Guess that any PHP-internal setting can't be removed.
1808
			$deleteable = $status['type'] !== 0; /* PHP_OUTPUT_HANDLER_INTERNAL */
1809
		}
1810
		if ( !$deleteable ) {
1811
			// Give up, and hope the result doesn't break
1812
			// output behavior.
1813
			break;
1814
		}
1815
		if ( $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) {
1816
			// Unit testing barrier to prevent this function from breaking PHPUnit.
1817
			break;
1818
		}
1819
		if ( !ob_end_clean() ) {
1820
			// Could not remove output buffer handler; abort now
1821
			// to avoid getting in some kind of infinite loop.
1822
			break;
1823
		}
1824
		if ( $resetGzipEncoding ) {
1825
			if ( $status['name'] == 'ob_gzhandler' ) {
1826
				// Reset the 'Content-Encoding' field set by this handler
1827
				// so we can start fresh.
1828
				header_remove( 'Content-Encoding' );
1829
				break;
1830
			}
1831
		}
1832
	}
1833
}
1834
1835
/**
1836
 * More legible than passing a 'false' parameter to wfResetOutputBuffers():
1837
 *
1838
 * Clear away output buffers, but keep the Content-Encoding header
1839
 * produced by ob_gzhandler, if any.
1840
 *
1841
 * This should be used for HTTP 304 responses, where you need to
1842
 * preserve the Content-Encoding header of the real result, but
1843
 * also need to suppress the output of ob_gzhandler to keep to spec
1844
 * and avoid breaking Firefox in rare cases where the headers and
1845
 * body are broken over two packets.
1846
 */
1847
function wfClearOutputBuffers() {
1848
	wfResetOutputBuffers( false );
1849
}
1850
1851
/**
1852
 * Converts an Accept-* header into an array mapping string values to quality
1853
 * factors
1854
 *
1855
 * @param string $accept
1856
 * @param string $def Default
1857
 * @return float[] Associative array of string => float pairs
1858
 */
1859
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
1860
	# No arg means accept anything (per HTTP spec)
1861
	if ( !$accept ) {
1862
		return [ $def => 1.0 ];
1863
	}
1864
1865
	$prefs = [];
1866
1867
	$parts = explode( ',', $accept );
1868
1869
	foreach ( $parts as $part ) {
1870
		# @todo FIXME: Doesn't deal with params like 'text/html; level=1'
1871
		$values = explode( ';', trim( $part ) );
1872
		$match = [];
1873
		if ( count( $values ) == 1 ) {
1874
			$prefs[$values[0]] = 1.0;
1875
		} elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
1876
			$prefs[$values[0]] = floatval( $match[1] );
1877
		}
1878
	}
1879
1880
	return $prefs;
1881
}
1882
1883
/**
1884
 * Checks if a given MIME type matches any of the keys in the given
1885
 * array. Basic wildcards are accepted in the array keys.
1886
 *
1887
 * Returns the matching MIME type (or wildcard) if a match, otherwise
1888
 * NULL if no match.
1889
 *
1890
 * @param string $type
1891
 * @param array $avail
1892
 * @return string
1893
 * @private
1894
 */
1895
function mimeTypeMatch( $type, $avail ) {
1896
	if ( array_key_exists( $type, $avail ) ) {
1897
		return $type;
1898
	} else {
1899
		$mainType = explode( '/', $type )[0];
1900
		if ( array_key_exists( "$mainType/*", $avail ) ) {
1901
			return "$mainType/*";
1902
		} elseif ( array_key_exists( '*/*', $avail ) ) {
1903
			return '*/*';
1904
		} else {
1905
			return null;
1906
		}
1907
	}
1908
}
1909
1910
/**
1911
 * Returns the 'best' match between a client's requested internet media types
1912
 * and the server's list of available types. Each list should be an associative
1913
 * array of type to preference (preference is a float between 0.0 and 1.0).
1914
 * Wildcards in the types are acceptable.
1915
 *
1916
 * @param array $cprefs Client's acceptable type list
1917
 * @param array $sprefs Server's offered types
1918
 * @return string
1919
 *
1920
 * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
1921
 * XXX: generalize to negotiate other stuff
1922
 */
1923
function wfNegotiateType( $cprefs, $sprefs ) {
1924
	$combine = [];
1925
1926 View Code Duplication
	foreach ( array_keys( $sprefs ) as $type ) {
1927
		$subType = explode( '/', $type )[1];
1928
		if ( $subType != '*' ) {
1929
			$ckey = mimeTypeMatch( $type, $cprefs );
1930
			if ( $ckey ) {
1931
				$combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1932
			}
1933
		}
1934
	}
1935
1936 View Code Duplication
	foreach ( array_keys( $cprefs ) as $type ) {
1937
		$subType = explode( '/', $type )[1];
1938
		if ( $subType != '*' && !array_key_exists( $type, $sprefs ) ) {
1939
			$skey = mimeTypeMatch( $type, $sprefs );
1940
			if ( $skey ) {
1941
				$combine[$type] = $sprefs[$skey] * $cprefs[$type];
1942
			}
1943
		}
1944
	}
1945
1946
	$bestq = 0;
1947
	$besttype = null;
1948
1949
	foreach ( array_keys( $combine ) as $type ) {
1950
		if ( $combine[$type] > $bestq ) {
1951
			$besttype = $type;
1952
			$bestq = $combine[$type];
1953
		}
1954
	}
1955
1956
	return $besttype;
1957
}
1958
1959
/**
1960
 * Reference-counted warning suppression
1961
 *
1962
 * @deprecated since 1.26, use MediaWiki\suppressWarnings() directly
1963
 * @param bool $end
1964
 */
1965
function wfSuppressWarnings( $end = false ) {
1966
	MediaWiki\suppressWarnings( $end );
1967
}
1968
1969
/**
1970
 * @deprecated since 1.26, use MediaWiki\restoreWarnings() directly
1971
 * Restore error level to previous value
1972
 */
1973
function wfRestoreWarnings() {
1974
	MediaWiki\suppressWarnings( true );
1975
}
1976
1977
# Autodetect, convert and provide timestamps of various types
1978
1979
/**
1980
 * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
1981
 */
1982
define( 'TS_UNIX', 0 );
1983
1984
/**
1985
 * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
1986
 */
1987
define( 'TS_MW', 1 );
1988
1989
/**
1990
 * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
1991
 */
1992
define( 'TS_DB', 2 );
1993
1994
/**
1995
 * RFC 2822 format, for E-mail and HTTP headers
1996
 */
1997
define( 'TS_RFC2822', 3 );
1998
1999
/**
2000
 * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
2001
 *
2002
 * This is used by Special:Export
2003
 */
2004
define( 'TS_ISO_8601', 4 );
2005
2006
/**
2007
 * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
2008
 *
2009
 * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
2010
 *       DateTime tag and page 36 for the DateTimeOriginal and
2011
 *       DateTimeDigitized tags.
2012
 */
2013
define( 'TS_EXIF', 5 );
2014
2015
/**
2016
 * Oracle format time.
2017
 */
2018
define( 'TS_ORACLE', 6 );
2019
2020
/**
2021
 * Postgres format time.
2022
 */
2023
define( 'TS_POSTGRES', 7 );
2024
2025
/**
2026
 * ISO 8601 basic format with no timezone: 19860209T200000Z.  This is used by ResourceLoader
2027
 */
2028
define( 'TS_ISO_8601_BASIC', 9 );
2029
2030
/**
2031
 * Get a timestamp string in one of various formats
2032
 *
2033
 * @param mixed $outputtype A timestamp in one of the supported formats, the
2034
 *   function will autodetect which format is supplied and act accordingly.
2035
 * @param mixed $ts Optional timestamp to convert, default 0 for the current time
2036
 * @return string|bool String / false The same date in the format specified in $outputtype or false
2037
 */
2038
function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
2039
	try {
2040
		$timestamp = new MWTimestamp( $ts );
2041
		return $timestamp->getTimestamp( $outputtype );
2042
	} catch ( TimestampException $e ) {
2043
		wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
2044
		return false;
2045
	}
2046
}
2047
2048
/**
2049
 * Return a formatted timestamp, or null if input is null.
2050
 * For dealing with nullable timestamp columns in the database.
2051
 *
2052
 * @param int $outputtype
2053
 * @param string $ts
2054
 * @return string
2055
 */
2056
function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
2057
	if ( is_null( $ts ) ) {
2058
		return null;
2059
	} else {
2060
		return wfTimestamp( $outputtype, $ts );
2061
	}
2062
}
2063
2064
/**
2065
 * Convenience function; returns MediaWiki timestamp for the present time.
2066
 *
2067
 * @return string
2068
 */
2069
function wfTimestampNow() {
2070
	# return NOW
2071
	return wfTimestamp( TS_MW, time() );
2072
}
2073
2074
/**
2075
 * Check if the operating system is Windows
2076
 *
2077
 * @return bool True if it's Windows, false otherwise.
2078
 */
2079
function wfIsWindows() {
2080
	static $isWindows = null;
2081
	if ( $isWindows === null ) {
2082
		$isWindows = strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN';
2083
	}
2084
	return $isWindows;
2085
}
2086
2087
/**
2088
 * Check if we are running under HHVM
2089
 *
2090
 * @return bool
2091
 */
2092
function wfIsHHVM() {
2093
	return defined( 'HHVM_VERSION' );
2094
}
2095
2096
/**
2097
 * Tries to get the system directory for temporary files. First
2098
 * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
2099
 * environment variables are then checked in sequence, then
2100
 * sys_get_temp_dir(), then upload_tmp_dir from php.ini.
2101
 *
2102
 * NOTE: When possible, use instead the tmpfile() function to create
2103
 * temporary files to avoid race conditions on file creation, etc.
2104
 *
2105
 * @return string
2106
 */
2107
function wfTempDir() {
2108
	global $wgTmpDirectory;
2109
2110
	if ( $wgTmpDirectory !== false ) {
2111
		return $wgTmpDirectory;
2112
	}
2113
2114
	$tmpDir = array_map( "getenv", [ 'TMPDIR', 'TMP', 'TEMP' ] );
2115
	$tmpDir[] = sys_get_temp_dir();
2116
	$tmpDir[] = ini_get( 'upload_tmp_dir' );
2117
2118
	foreach ( $tmpDir as $tmp ) {
2119
		if ( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
2120
			return $tmp;
2121
		}
2122
	}
2123
	throw new MWException( 'No writable temporary directory could be found. ' .
2124
		'Please set $wgTmpDirectory to a writable directory.' );
2125
}
2126
2127
/**
2128
 * Make directory, and make all parent directories if they don't exist
2129
 *
2130
 * @param string $dir Full path to directory to create
2131
 * @param int $mode Chmod value to use, default is $wgDirectoryMode
2132
 * @param string $caller Optional caller param for debugging.
2133
 * @throws MWException
2134
 * @return bool
2135
 */
2136
function wfMkdirParents( $dir, $mode = null, $caller = null ) {
2137
	global $wgDirectoryMode;
2138
2139
	if ( FileBackend::isStoragePath( $dir ) ) { // sanity
2140
		throw new MWException( __FUNCTION__ . " given storage path '$dir'." );
2141
	}
2142
2143
	if ( !is_null( $caller ) ) {
2144
		wfDebug( "$caller: called wfMkdirParents($dir)\n" );
2145
	}
2146
2147
	if ( strval( $dir ) === '' || is_dir( $dir ) ) {
2148
		return true;
2149
	}
2150
2151
	$dir = str_replace( [ '\\', '/' ], DIRECTORY_SEPARATOR, $dir );
2152
2153
	if ( is_null( $mode ) ) {
2154
		$mode = $wgDirectoryMode;
2155
	}
2156
2157
	// Turn off the normal warning, we're doing our own below
2158
	MediaWiki\suppressWarnings();
2159
	$ok = mkdir( $dir, $mode, true ); // PHP5 <3
2160
	MediaWiki\restoreWarnings();
2161
2162
	if ( !$ok ) {
2163
		// directory may have been created on another request since we last checked
2164
		if ( is_dir( $dir ) ) {
2165
			return true;
2166
		}
2167
2168
		// PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
2169
		wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) );
2170
	}
2171
	return $ok;
2172
}
2173
2174
/**
2175
 * Remove a directory and all its content.
2176
 * Does not hide error.
2177
 * @param string $dir
2178
 */
2179
function wfRecursiveRemoveDir( $dir ) {
2180
	wfDebug( __FUNCTION__ . "( $dir )\n" );
2181
	// taken from http://de3.php.net/manual/en/function.rmdir.php#98622
2182
	if ( is_dir( $dir ) ) {
2183
		$objects = scandir( $dir );
2184
		foreach ( $objects as $object ) {
2185
			if ( $object != "." && $object != ".." ) {
2186
				if ( filetype( $dir . '/' . $object ) == "dir" ) {
2187
					wfRecursiveRemoveDir( $dir . '/' . $object );
2188
				} else {
2189
					unlink( $dir . '/' . $object );
2190
				}
2191
			}
2192
		}
2193
		reset( $objects );
2194
		rmdir( $dir );
2195
	}
2196
}
2197
2198
/**
2199
 * @param int $nr The number to format
2200
 * @param int $acc The number of digits after the decimal point, default 2
2201
 * @param bool $round Whether or not to round the value, default true
2202
 * @return string
2203
 */
2204
function wfPercent( $nr, $acc = 2, $round = true ) {
2205
	$ret = sprintf( "%.${acc}f", $nr );
2206
	return $round ? round( $ret, $acc ) . '%' : "$ret%";
2207
}
2208
2209
/**
2210
 * Safety wrapper around ini_get() for boolean settings.
2211
 * The values returned from ini_get() are pre-normalized for settings
2212
 * set via php.ini or php_flag/php_admin_flag... but *not*
2213
 * for those set via php_value/php_admin_value.
2214
 *
2215
 * It's fairly common for people to use php_value instead of php_flag,
2216
 * which can leave you with an 'off' setting giving a false positive
2217
 * for code that just takes the ini_get() return value as a boolean.
2218
 *
2219
 * To make things extra interesting, setting via php_value accepts
2220
 * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
2221
 * Unrecognized values go false... again opposite PHP's own coercion
2222
 * from string to bool.
2223
 *
2224
 * Luckily, 'properly' set settings will always come back as '0' or '1',
2225
 * so we only have to worry about them and the 'improper' settings.
2226
 *
2227
 * I frickin' hate PHP... :P
2228
 *
2229
 * @param string $setting
2230
 * @return bool
2231
 */
2232
function wfIniGetBool( $setting ) {
2233
	$val = strtolower( ini_get( $setting ) );
2234
	// 'on' and 'true' can't have whitespace around them, but '1' can.
2235
	return $val == 'on'
2236
		|| $val == 'true'
2237
		|| $val == 'yes'
2238
		|| preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
2239
}
2240
2241
/**
2242
 * Windows-compatible version of escapeshellarg()
2243
 * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
2244
 * function puts single quotes in regardless of OS.
2245
 *
2246
 * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
2247
 * earlier distro releases of PHP)
2248
 *
2249
 * @param string ... strings to escape and glue together, or a single array of strings parameter
2250
 * @return string
2251
 */
2252
function wfEscapeShellArg( /*...*/ ) {
2253
	wfInitShellLocale();
2254
2255
	$args = func_get_args();
2256
	if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
2257
		// If only one argument has been passed, and that argument is an array,
2258
		// treat it as a list of arguments
2259
		$args = reset( $args );
2260
	}
2261
2262
	$first = true;
2263
	$retVal = '';
2264
	foreach ( $args as $arg ) {
2265
		if ( !$first ) {
2266
			$retVal .= ' ';
2267
		} else {
2268
			$first = false;
2269
		}
2270
2271
		if ( wfIsWindows() ) {
2272
			// Escaping for an MSVC-style command line parser and CMD.EXE
2273
			// @codingStandardsIgnoreStart For long URLs
2274
			// Refs:
2275
			//  * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
2276
			//  * http://technet.microsoft.com/en-us/library/cc723564.aspx
2277
			//  * Bug #13518
2278
			//  * CR r63214
2279
			// Double the backslashes before any double quotes. Escape the double quotes.
2280
			// @codingStandardsIgnoreEnd
2281
			$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
2282
			$arg = '';
2283
			$iteration = 0;
2284
			foreach ( $tokens as $token ) {
2285
				if ( $iteration % 2 == 1 ) {
2286
					// Delimiter, a double quote preceded by zero or more slashes
2287
					$arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
2288
				} elseif ( $iteration % 4 == 2 ) {
2289
					// ^ in $token will be outside quotes, need to be escaped
2290
					$arg .= str_replace( '^', '^^', $token );
2291
				} else { // $iteration % 4 == 0
2292
					// ^ in $token will appear inside double quotes, so leave as is
2293
					$arg .= $token;
2294
				}
2295
				$iteration++;
2296
			}
2297
			// Double the backslashes before the end of the string, because
2298
			// we will soon add a quote
2299
			$m = [];
2300
			if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
2301
				$arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
2302
			}
2303
2304
			// Add surrounding quotes
2305
			$retVal .= '"' . $arg . '"';
2306
		} else {
2307
			$retVal .= escapeshellarg( $arg );
2308
		}
2309
	}
2310
	return $retVal;
2311
}
2312
2313
/**
2314
 * Check if wfShellExec() is effectively disabled via php.ini config
2315
 *
2316
 * @return bool|string False or 'disabled'
2317
 * @since 1.22
2318
 */
2319
function wfShellExecDisabled() {
2320
	static $disabled = null;
2321
	if ( is_null( $disabled ) ) {
2322
		if ( !function_exists( 'proc_open' ) ) {
2323
			wfDebug( "proc_open() is disabled\n" );
2324
			$disabled = 'disabled';
2325
		} else {
2326
			$disabled = false;
2327
		}
2328
	}
2329
	return $disabled;
2330
}
2331
2332
/**
2333
 * Execute a shell command, with time and memory limits mirrored from the PHP
2334
 * configuration if supported.
2335
 *
2336
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2337
 *   or an array of unescaped arguments, in which case each value will be escaped
2338
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2339
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2340
 *   (non-zero is usually failure). If there is an error from
2341
 *   read, select, or proc_open(), this will be set to -1.
2342
 * @param array $environ Optional environment variables which should be
2343
 *   added to the executed command environment.
2344
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2345
 *   this overwrites the global wgMaxShell* limits.
2346
 * @param array $options Array of options:
2347
 *   - duplicateStderr: Set this to true to duplicate stderr to stdout,
2348
 *     including errors from limit.sh
2349
 *   - profileMethod: By default this function will profile based on the calling
2350
 *     method. Set this to a string for an alternative method to profile from
2351
 *
2352
 * @return string Collected stdout as a string
2353
 */
2354
function wfShellExec( $cmd, &$retval = null, $environ = [],
2355
	$limits = [], $options = []
2356
) {
2357
	global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
2358
		$wgMaxShellWallClockTime, $wgShellCgroup;
2359
2360
	$disabled = wfShellExecDisabled();
2361
	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...
2362
		$retval = 1;
2363
		return 'Unable to run external programs, proc_open() is disabled.';
2364
	}
2365
2366
	$includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
2367
	$profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller();
2368
2369
	wfInitShellLocale();
2370
2371
	$envcmd = '';
2372
	foreach ( $environ as $k => $v ) {
2373
		if ( wfIsWindows() ) {
2374
			/* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
2375
			 * appear in the environment variable, so we must use carat escaping as documented in
2376
			 * http://technet.microsoft.com/en-us/library/cc723564.aspx
2377
			 * Note however that the quote isn't listed there, but is needed, and the parentheses
2378
			 * are listed there but doesn't appear to need it.
2379
			 */
2380
			$envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
2381
		} else {
2382
			/* Assume this is a POSIX shell, thus required to accept variable assignments before the command
2383
			 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
2384
			 */
2385
			$envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
2386
		}
2387
	}
2388
	if ( is_array( $cmd ) ) {
2389
		$cmd = wfEscapeShellArg( $cmd );
2390
	}
2391
2392
	$cmd = $envcmd . $cmd;
2393
2394
	$useLogPipe = false;
2395
	if ( is_executable( '/bin/bash' ) ) {
2396
		$time = intval( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
2397
		if ( isset( $limits['walltime'] ) ) {
2398
			$wallTime = intval( $limits['walltime'] );
2399
		} elseif ( isset( $limits['time'] ) ) {
2400
			$wallTime = $time;
2401
		} else {
2402
			$wallTime = intval( $wgMaxShellWallClockTime );
2403
		}
2404
		$mem = intval( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
2405
		$filesize = intval( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
2406
2407
		if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
2408
			$cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
2409
				escapeshellarg( $cmd ) . ' ' .
2410
				escapeshellarg(
2411
					"MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
2412
					"MW_CPU_LIMIT=$time; " .
2413
					'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
2414
					"MW_MEM_LIMIT=$mem; " .
2415
					"MW_FILE_SIZE_LIMIT=$filesize; " .
2416
					"MW_WALL_CLOCK_LIMIT=$wallTime; " .
2417
					"MW_USE_LOG_PIPE=yes"
2418
				);
2419
			$useLogPipe = true;
2420
		} elseif ( $includeStderr ) {
2421
			$cmd .= ' 2>&1';
2422
		}
2423
	} elseif ( $includeStderr ) {
2424
		$cmd .= ' 2>&1';
2425
	}
2426
	wfDebug( "wfShellExec: $cmd\n" );
2427
2428
	$desc = [
2429
		0 => [ 'file', 'php://stdin', 'r' ],
2430
		1 => [ 'pipe', 'w' ],
2431
		2 => [ 'file', 'php://stderr', 'w' ] ];
2432
	if ( $useLogPipe ) {
2433
		$desc[3] = [ 'pipe', 'w' ];
2434
	}
2435
	$pipes = null;
2436
	$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...
2437
	$proc = proc_open( $cmd, $desc, $pipes );
2438
	if ( !$proc ) {
2439
		wfDebugLog( 'exec', "proc_open() failed: $cmd" );
2440
		$retval = -1;
2441
		return '';
2442
	}
2443
	$outBuffer = $logBuffer = '';
2444
	$emptyArray = [];
2445
	$status = false;
2446
	$logMsg = false;
2447
2448
	/* According to the documentation, it is possible for stream_select()
2449
	 * to fail due to EINTR. I haven't managed to induce this in testing
2450
	 * despite sending various signals. If it did happen, the error
2451
	 * message would take the form:
2452
	 *
2453
	 * stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
2454
	 *
2455
	 * where [4] is the value of the macro EINTR and "Interrupted system
2456
	 * call" is string which according to the Linux manual is "possibly"
2457
	 * localised according to LC_MESSAGES.
2458
	 */
2459
	$eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
2460
	$eintrMessage = "stream_select(): unable to select [$eintr]";
2461
2462
	// Build a table mapping resource IDs to pipe FDs to work around a
2463
	// PHP 5.3 issue in which stream_select() does not preserve array keys
2464
	// <https://bugs.php.net/bug.php?id=53427>.
2465
	$fds = [];
2466
	foreach ( $pipes as $fd => $pipe ) {
2467
		$fds[(int)$pipe] = $fd;
2468
	}
2469
2470
	$running = true;
2471
	$timeout = null;
2472
	$numReadyPipes = 0;
2473
2474
	while ( $running === true || $numReadyPipes !== 0 ) {
2475
		if ( $running ) {
2476
			$status = proc_get_status( $proc );
2477
			// If the process has terminated, switch to nonblocking selects
2478
			// for getting any data still waiting to be read.
2479
			if ( !$status['running'] ) {
2480
				$running = false;
2481
				$timeout = 0;
2482
			}
2483
		}
2484
2485
		$readyPipes = $pipes;
2486
2487
		// Clear last error
2488
		// @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged
2489
		@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...
2490
		$numReadyPipes = @stream_select( $readyPipes, $emptyArray, $emptyArray, $timeout );
2491
		if ( $numReadyPipes === false ) {
2492
			// @codingStandardsIgnoreEnd
2493
			$error = error_get_last();
2494
			if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
2495
				continue;
2496
			} else {
2497
				trigger_error( $error['message'], E_USER_WARNING );
2498
				$logMsg = $error['message'];
2499
				break;
2500
			}
2501
		}
2502
		foreach ( $readyPipes as $pipe ) {
2503
			$block = fread( $pipe, 65536 );
2504
			$fd = $fds[(int)$pipe];
2505
			if ( $block === '' ) {
2506
				// End of file
2507
				fclose( $pipes[$fd] );
2508
				unset( $pipes[$fd] );
2509
				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...
2510
					break 2;
2511
				}
2512
			} elseif ( $block === false ) {
2513
				// Read error
2514
				$logMsg = "Error reading from pipe";
2515
				break 2;
2516
			} elseif ( $fd == 1 ) {
2517
				// From stdout
2518
				$outBuffer .= $block;
2519
			} elseif ( $fd == 3 ) {
2520
				// From log FD
2521
				$logBuffer .= $block;
2522
				if ( strpos( $block, "\n" ) !== false ) {
2523
					$lines = explode( "\n", $logBuffer );
2524
					$logBuffer = array_pop( $lines );
2525
					foreach ( $lines as $line ) {
2526
						wfDebugLog( 'exec', $line );
2527
					}
2528
				}
2529
			}
2530
		}
2531
	}
2532
2533
	foreach ( $pipes as $pipe ) {
2534
		fclose( $pipe );
2535
	}
2536
2537
	// Use the status previously collected if possible, since proc_get_status()
2538
	// just calls waitpid() which will not return anything useful the second time.
2539
	if ( $running ) {
2540
		$status = proc_get_status( $proc );
2541
	}
2542
2543
	if ( $logMsg !== false ) {
2544
		// Read/select error
2545
		$retval = -1;
2546
		proc_close( $proc );
2547
	} elseif ( $status['signaled'] ) {
2548
		$logMsg = "Exited with signal {$status['termsig']}";
2549
		$retval = 128 + $status['termsig'];
2550
		proc_close( $proc );
2551
	} else {
2552
		if ( $status['running'] ) {
2553
			$retval = proc_close( $proc );
2554
		} else {
2555
			$retval = $status['exitcode'];
2556
			proc_close( $proc );
2557
		}
2558
		if ( $retval == 127 ) {
2559
			$logMsg = "Possibly missing executable file";
2560
		} elseif ( $retval >= 129 && $retval <= 192 ) {
2561
			$logMsg = "Probably exited with signal " . ( $retval - 128 );
2562
		}
2563
	}
2564
2565
	if ( $logMsg !== false ) {
2566
		wfDebugLog( 'exec', "$logMsg: $cmd" );
2567
	}
2568
2569
	return $outBuffer;
2570
}
2571
2572
/**
2573
 * Execute a shell command, returning both stdout and stderr. Convenience
2574
 * function, as all the arguments to wfShellExec can become unwieldy.
2575
 *
2576
 * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
2577
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2578
 *   or an array of unescaped arguments, in which case each value will be escaped
2579
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2580
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2581
 *   (non-zero is usually failure)
2582
 * @param array $environ Optional environment variables which should be
2583
 *   added to the executed command environment.
2584
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2585
 *   this overwrites the global wgMaxShell* limits.
2586
 * @return string Collected stdout and stderr as a string
2587
 */
2588
function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = [] ) {
2589
	return wfShellExec( $cmd, $retval, $environ, $limits,
2590
		[ 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ] );
2591
}
2592
2593
/**
2594
 * Workaround for http://bugs.php.net/bug.php?id=45132
2595
 * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
2596
 */
2597
function wfInitShellLocale() {
2598
	static $done = false;
2599
	if ( $done ) {
2600
		return;
2601
	}
2602
	$done = true;
2603
	global $wgShellLocale;
2604
	putenv( "LC_CTYPE=$wgShellLocale" );
2605
	setlocale( LC_CTYPE, $wgShellLocale );
2606
}
2607
2608
/**
2609
 * Generate a shell-escaped command line string to run a MediaWiki cli script.
2610
 * Note that $parameters should be a flat array and an option with an argument
2611
 * should consist of two consecutive items in the array (do not use "--option value").
2612
 *
2613
 * @param string $script MediaWiki cli script path
2614
 * @param array $parameters Arguments and options to the script
2615
 * @param array $options Associative array of options:
2616
 * 		'php': The path to the php executable
2617
 * 		'wrapper': Path to a PHP wrapper to handle the maintenance script
2618
 * @return string
2619
 */
2620
function wfShellWikiCmd( $script, array $parameters = [], array $options = [] ) {
2621
	global $wgPhpCli;
2622
	// Give site config file a chance to run the script in a wrapper.
2623
	// The caller may likely want to call wfBasename() on $script.
2624
	Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
2625
	$cmd = isset( $options['php'] ) ? [ $options['php'] ] : [ $wgPhpCli ];
2626
	if ( isset( $options['wrapper'] ) ) {
2627
		$cmd[] = $options['wrapper'];
2628
	}
2629
	$cmd[] = $script;
2630
	// Escape each parameter for shell
2631
	return wfEscapeShellArg( array_merge( $cmd, $parameters ) );
2632
}
2633
2634
/**
2635
 * wfMerge attempts to merge differences between three texts.
2636
 * Returns true for a clean merge and false for failure or a conflict.
2637
 *
2638
 * @param string $old
2639
 * @param string $mine
2640
 * @param string $yours
2641
 * @param string $result
2642
 * @return bool
2643
 */
2644
function wfMerge( $old, $mine, $yours, &$result ) {
2645
	global $wgDiff3;
2646
2647
	# This check may also protect against code injection in
2648
	# case of broken installations.
2649
	MediaWiki\suppressWarnings();
2650
	$haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
2651
	MediaWiki\restoreWarnings();
2652
2653
	if ( !$haveDiff3 ) {
2654
		wfDebug( "diff3 not found\n" );
2655
		return false;
2656
	}
2657
2658
	# Make temporary files
2659
	$td = wfTempDir();
2660
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2661
	$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
2662
	$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
2663
2664
	# NOTE: diff3 issues a warning to stderr if any of the files does not end with
2665
	#       a newline character. To avoid this, we normalize the trailing whitespace before
2666
	#       creating the diff.
2667
2668
	fwrite( $oldtextFile, rtrim( $old ) . "\n" );
2669
	fclose( $oldtextFile );
2670
	fwrite( $mytextFile, rtrim( $mine ) . "\n" );
2671
	fclose( $mytextFile );
2672
	fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
2673
	fclose( $yourtextFile );
2674
2675
	# Check for a conflict
2676
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName,
2677
		$oldtextName, $yourtextName );
2678
	$handle = popen( $cmd, 'r' );
2679
2680
	if ( fgets( $handle, 1024 ) ) {
2681
		$conflict = true;
2682
	} else {
2683
		$conflict = false;
2684
	}
2685
	pclose( $handle );
2686
2687
	# Merge differences
2688
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName,
2689
		$oldtextName, $yourtextName );
2690
	$handle = popen( $cmd, 'r' );
2691
	$result = '';
2692
	do {
2693
		$data = fread( $handle, 8192 );
2694
		if ( strlen( $data ) == 0 ) {
2695
			break;
2696
		}
2697
		$result .= $data;
2698
	} while ( true );
2699
	pclose( $handle );
2700
	unlink( $mytextName );
2701
	unlink( $oldtextName );
2702
	unlink( $yourtextName );
2703
2704
	if ( $result === '' && $old !== '' && !$conflict ) {
2705
		wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
2706
		$conflict = true;
2707
	}
2708
	return !$conflict;
2709
}
2710
2711
/**
2712
 * Returns unified plain-text diff of two texts.
2713
 * "Useful" for machine processing of diffs.
2714
 *
2715
 * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly
2716
 *
2717
 * @param string $before The text before the changes.
2718
 * @param string $after The text after the changes.
2719
 * @param string $params Command-line options for the diff command.
2720
 * @return string Unified diff of $before and $after
2721
 */
2722
function wfDiff( $before, $after, $params = '-u' ) {
2723
	if ( $before == $after ) {
2724
		return '';
2725
	}
2726
2727
	global $wgDiff;
2728
	MediaWiki\suppressWarnings();
2729
	$haveDiff = $wgDiff && file_exists( $wgDiff );
2730
	MediaWiki\restoreWarnings();
2731
2732
	# This check may also protect against code injection in
2733
	# case of broken installations.
2734
	if ( !$haveDiff ) {
2735
		wfDebug( "diff executable not found\n" );
2736
		$diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
2737
		$format = new UnifiedDiffFormatter();
2738
		return $format->format( $diffs );
2739
	}
2740
2741
	# Make temporary files
2742
	$td = wfTempDir();
2743
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2744
	$newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
2745
2746
	fwrite( $oldtextFile, $before );
2747
	fclose( $oldtextFile );
2748
	fwrite( $newtextFile, $after );
2749
	fclose( $newtextFile );
2750
2751
	// Get the diff of the two files
2752
	$cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
2753
2754
	$h = popen( $cmd, 'r' );
2755
	if ( !$h ) {
2756
		unlink( $oldtextName );
2757
		unlink( $newtextName );
2758
		throw new Exception( __METHOD__ . '(): popen() failed' );
2759
	}
2760
2761
	$diff = '';
2762
2763
	do {
2764
		$data = fread( $h, 8192 );
2765
		if ( strlen( $data ) == 0 ) {
2766
			break;
2767
		}
2768
		$diff .= $data;
2769
	} while ( true );
2770
2771
	// Clean up
2772
	pclose( $h );
2773
	unlink( $oldtextName );
2774
	unlink( $newtextName );
2775
2776
	// Kill the --- and +++ lines. They're not useful.
2777
	$diff_lines = explode( "\n", $diff );
2778 View Code Duplication
	if ( isset( $diff_lines[0] ) && strpos( $diff_lines[0], '---' ) === 0 ) {
2779
		unset( $diff_lines[0] );
2780
	}
2781 View Code Duplication
	if ( isset( $diff_lines[1] ) && strpos( $diff_lines[1], '+++' ) === 0 ) {
2782
		unset( $diff_lines[1] );
2783
	}
2784
2785
	$diff = implode( "\n", $diff_lines );
2786
2787
	return $diff;
2788
}
2789
2790
/**
2791
 * This function works like "use VERSION" in Perl, the program will die with a
2792
 * backtrace if the current version of PHP is less than the version provided
2793
 *
2794
 * This is useful for extensions which due to their nature are not kept in sync
2795
 * with releases, and might depend on other versions of PHP than the main code
2796
 *
2797
 * Note: PHP might die due to parsing errors in some cases before it ever
2798
 *       manages to call this function, such is life
2799
 *
2800
 * @see perldoc -f use
2801
 *
2802
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2803
 * @throws MWException
2804
 */
2805
function wfUsePHP( $req_ver ) {
2806
	$php_ver = PHP_VERSION;
2807
2808
	if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
2809
		throw new MWException( "PHP $req_ver required--this is only $php_ver" );
2810
	}
2811
}
2812
2813
/**
2814
 * This function works like "use VERSION" in Perl except it checks the version
2815
 * of MediaWiki, the program will die with a backtrace if the current version
2816
 * of MediaWiki is less than the version provided.
2817
 *
2818
 * This is useful for extensions which due to their nature are not kept in sync
2819
 * with releases
2820
 *
2821
 * Note: Due to the behavior of PHP's version_compare() which is used in this
2822
 * function, if you want to allow the 'wmf' development versions add a 'c' (or
2823
 * any single letter other than 'a', 'b' or 'p') as a post-fix to your
2824
 * targeted version number. For example if you wanted to allow any variation
2825
 * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
2826
 * not result in the same comparison due to the internal logic of
2827
 * version_compare().
2828
 *
2829
 * @see perldoc -f use
2830
 *
2831
 * @deprecated since 1.26, use the "requires' property of extension.json
2832
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2833
 * @throws MWException
2834
 */
2835
function wfUseMW( $req_ver ) {
2836
	global $wgVersion;
2837
2838
	if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
2839
		throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
2840
	}
2841
}
2842
2843
/**
2844
 * Return the final portion of a pathname.
2845
 * Reimplemented because PHP5's "basename()" is buggy with multibyte text.
2846
 * http://bugs.php.net/bug.php?id=33898
2847
 *
2848
 * PHP's basename() only considers '\' a pathchar on Windows and Netware.
2849
 * We'll consider it so always, as we don't want '\s' in our Unix paths either.
2850
 *
2851
 * @param string $path
2852
 * @param string $suffix String to remove if present
2853
 * @return string
2854
 */
2855
function wfBaseName( $path, $suffix = '' ) {
2856
	if ( $suffix == '' ) {
2857
		$encSuffix = '';
2858
	} else {
2859
		$encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
2860
	}
2861
2862
	$matches = [];
2863
	if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
2864
		return $matches[1];
2865
	} else {
2866
		return '';
2867
	}
2868
}
2869
2870
/**
2871
 * Generate a relative path name to the given file.
2872
 * May explode on non-matching case-insensitive paths,
2873
 * funky symlinks, etc.
2874
 *
2875
 * @param string $path Absolute destination path including target filename
2876
 * @param string $from Absolute source path, directory only
2877
 * @return string
2878
 */
2879
function wfRelativePath( $path, $from ) {
2880
	// Normalize mixed input on Windows...
2881
	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
2882
	$from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
2883
2884
	// Trim trailing slashes -- fix for drive root
2885
	$path = rtrim( $path, DIRECTORY_SEPARATOR );
2886
	$from = rtrim( $from, DIRECTORY_SEPARATOR );
2887
2888
	$pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
2889
	$against = explode( DIRECTORY_SEPARATOR, $from );
2890
2891
	if ( $pieces[0] !== $against[0] ) {
2892
		// Non-matching Windows drive letters?
2893
		// Return a full path.
2894
		return $path;
2895
	}
2896
2897
	// Trim off common prefix
2898
	while ( count( $pieces ) && count( $against )
2899
		&& $pieces[0] == $against[0] ) {
2900
		array_shift( $pieces );
2901
		array_shift( $against );
2902
	}
2903
2904
	// relative dots to bump us to the parent
2905
	while ( count( $against ) ) {
2906
		array_unshift( $pieces, '..' );
2907
		array_shift( $against );
2908
	}
2909
2910
	array_push( $pieces, wfBaseName( $path ) );
2911
2912
	return implode( DIRECTORY_SEPARATOR, $pieces );
2913
}
2914
2915
/**
2916
 * Convert an arbitrarily-long digit string from one numeric base
2917
 * to another, optionally zero-padding to a minimum column width.
2918
 *
2919
 * Supports base 2 through 36; digit values 10-36 are represented
2920
 * as lowercase letters a-z. Input is case-insensitive.
2921
 *
2922
 * @deprecated 1.27 Use Wikimedia\base_convert() directly
2923
 *
2924
 * @param string $input Input number
2925
 * @param int $sourceBase Base of the input number
2926
 * @param int $destBase Desired base of the output
2927
 * @param int $pad Minimum number of digits in the output (pad with zeroes)
2928
 * @param bool $lowercase Whether to output in lowercase or uppercase
2929
 * @param string $engine Either "gmp", "bcmath", or "php"
2930
 * @return string|bool The output number as a string, or false on error
2931
 */
2932
function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1,
2933
	$lowercase = true, $engine = 'auto'
2934
) {
2935
	return Wikimedia\base_convert( $input, $sourceBase, $destBase, $pad, $lowercase, $engine );
2936
}
2937
2938
/**
2939
 * @deprecated since 1.27, PHP's session generation isn't used with
2940
 *  MediaWiki\Session\SessionManager
2941
 */
2942
function wfFixSessionID() {
2943
	wfDeprecated( __FUNCTION__, '1.27' );
2944
}
2945
2946
/**
2947
 * Reset the session id
2948
 *
2949
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead
2950
 * @since 1.22
2951
 */
2952
function wfResetSessionID() {
2953
	wfDeprecated( __FUNCTION__, '1.27' );
2954
	$session = SessionManager::getGlobalSession();
2955
	$delay = $session->delaySave();
2956
2957
	$session->resetId();
2958
2959
	// Make sure a session is started, since that's what the old
2960
	// wfResetSessionID() did.
2961
	if ( session_id() !== $session->getId() ) {
2962
		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...
2963
	}
2964
2965
	ScopedCallback::consume( $delay );
2966
}
2967
2968
/**
2969
 * Initialise php session
2970
 *
2971
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead.
2972
 *  Generally, "using" SessionManager will be calling ->getSessionById() or
2973
 *  ::getGlobalSession() (depending on whether you were passing $sessionId
2974
 *  here), then calling $session->persist().
2975
 * @param bool|string $sessionId
2976
 */
2977
function wfSetupSession( $sessionId = false ) {
2978
	wfDeprecated( __FUNCTION__, '1.27' );
2979
2980
	if ( $sessionId ) {
2981
		session_id( $sessionId );
2982
	}
2983
2984
	$session = SessionManager::getGlobalSession();
2985
	$session->persist();
2986
2987
	if ( session_id() !== $session->getId() ) {
2988
		session_id( $session->getId() );
2989
	}
2990
	MediaWiki\quietCall( 'session_start' );
2991
}
2992
2993
/**
2994
 * Get an object from the precompiled serialized directory
2995
 *
2996
 * @param string $name
2997
 * @return mixed The variable on success, false on failure
2998
 */
2999
function wfGetPrecompiledData( $name ) {
3000
	global $IP;
3001
3002
	$file = "$IP/serialized/$name";
3003
	if ( file_exists( $file ) ) {
3004
		$blob = file_get_contents( $file );
3005
		if ( $blob ) {
3006
			return unserialize( $blob );
3007
		}
3008
	}
3009
	return false;
3010
}
3011
3012
/**
3013
 * Make a cache key for the local wiki.
3014
 *
3015
 * @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...
3016
 * @return string
3017
 */
3018
function wfMemcKey( /*...*/ ) {
3019
	return call_user_func_array(
3020
		[ ObjectCache::getLocalClusterInstance(), 'makeKey' ],
3021
		func_get_args()
3022
	);
3023
}
3024
3025
/**
3026
 * Make a cache key for a foreign DB.
3027
 *
3028
 * Must match what wfMemcKey() would produce in context of the foreign wiki.
3029
 *
3030
 * @param string $db
3031
 * @param string $prefix
3032
 * @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...
3033
 * @return string
3034
 */
3035
function wfForeignMemcKey( $db, $prefix /*...*/ ) {
3036
	$args = array_slice( func_get_args(), 2 );
3037
	$keyspace = $prefix ? "$db-$prefix" : $db;
3038
	return call_user_func_array(
3039
		[ ObjectCache::getLocalClusterInstance(), 'makeKeyInternal' ],
3040
		[ $keyspace, $args ]
3041
	);
3042
}
3043
3044
/**
3045
 * Make a cache key with database-agnostic prefix.
3046
 *
3047
 * Doesn't have a wiki-specific namespace. Uses a generic 'global' prefix
3048
 * instead. Must have a prefix as otherwise keys that use a database name
3049
 * in the first segment will clash with wfMemcKey/wfForeignMemcKey.
3050
 *
3051
 * @since 1.26
3052
 * @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...
3053
 * @return string
3054
 */
3055
function wfGlobalCacheKey( /*...*/ ) {
3056
	return call_user_func_array(
3057
		[ ObjectCache::getLocalClusterInstance(), 'makeGlobalKey' ],
3058
		func_get_args()
3059
	);
3060
}
3061
3062
/**
3063
 * Get an ASCII string identifying this wiki
3064
 * This is used as a prefix in memcached keys
3065
 *
3066
 * @return string
3067
 */
3068
function wfWikiID() {
3069
	global $wgDBprefix, $wgDBname;
3070
	if ( $wgDBprefix ) {
3071
		return "$wgDBname-$wgDBprefix";
3072
	} else {
3073
		return $wgDBname;
3074
	}
3075
}
3076
3077
/**
3078
 * Split a wiki ID into DB name and table prefix
3079
 *
3080
 * @param string $wiki
3081
 *
3082
 * @return array
3083
 */
3084
function wfSplitWikiID( $wiki ) {
3085
	$bits = explode( '-', $wiki, 2 );
3086
	if ( count( $bits ) < 2 ) {
3087
		$bits[] = '';
3088
	}
3089
	return $bits;
3090
}
3091
3092
/**
3093
 * Get a Database object.
3094
 *
3095
 * @param int $db Index of the connection to get. May be DB_MASTER for the
3096
 *            master (for write queries), DB_SLAVE for potentially lagged read
3097
 *            queries, or an integer >= 0 for a particular server.
3098
 *
3099
 * @param string|string[] $groups Query groups. An array of group names that this query
3100
 *                belongs to. May contain a single string if the query is only
3101
 *                in one group.
3102
 *
3103
 * @param string|bool $wiki The wiki ID, or false for the current wiki
3104
 *
3105
 * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
3106
 * will always return the same object, unless the underlying connection or load
3107
 * balancer is manually destroyed.
3108
 *
3109
 * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
3110
 * updater to ensure that a proper database is being updated.
3111
 *
3112
 * @return DatabaseBase
3113
 */
3114
function wfGetDB( $db, $groups = [], $wiki = false ) {
3115
	return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
3116
}
3117
3118
/**
3119
 * Get a load balancer object.
3120
 *
3121
 * @param string|bool $wiki Wiki ID, or false for the current wiki
3122
 * @return LoadBalancer
3123
 */
3124
function wfGetLB( $wiki = false ) {
3125
	return wfGetLBFactory()->getMainLB( $wiki );
3126
}
3127
3128
/**
3129
 * Get the load balancer factory object
3130
 *
3131
 * @return LBFactory
3132
 */
3133
function wfGetLBFactory() {
3134
	return LBFactory::singleton();
3135
}
3136
3137
/**
3138
 * Find a file.
3139
 * Shortcut for RepoGroup::singleton()->findFile()
3140
 *
3141
 * @param string $title String or Title object
3142
 * @param array $options Associative array of options (see RepoGroup::findFile)
3143
 * @return File|bool File, or false if the file does not exist
3144
 */
3145
function wfFindFile( $title, $options = [] ) {
3146
	return RepoGroup::singleton()->findFile( $title, $options );
3147
}
3148
3149
/**
3150
 * Get an object referring to a locally registered file.
3151
 * Returns a valid placeholder object if the file does not exist.
3152
 *
3153
 * @param Title|string $title
3154
 * @return LocalFile|null A File, or null if passed an invalid Title
3155
 */
3156
function wfLocalFile( $title ) {
3157
	return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
3158
}
3159
3160
/**
3161
 * Should low-performance queries be disabled?
3162
 *
3163
 * @return bool
3164
 * @codeCoverageIgnore
3165
 */
3166
function wfQueriesMustScale() {
3167
	global $wgMiserMode;
3168
	return $wgMiserMode
3169
		|| ( SiteStats::pages() > 100000
3170
		&& SiteStats::edits() > 1000000
3171
		&& SiteStats::users() > 10000 );
3172
}
3173
3174
/**
3175
 * Get the path to a specified script file, respecting file
3176
 * extensions; this is a wrapper around $wgScriptPath etc.
3177
 * except for 'index' and 'load' which use $wgScript/$wgLoadScript
3178
 *
3179
 * @param string $script Script filename, sans extension
3180
 * @return string
3181
 */
3182
function wfScript( $script = 'index' ) {
3183
	global $wgScriptPath, $wgScript, $wgLoadScript;
3184
	if ( $script === 'index' ) {
3185
		return $wgScript;
3186
	} elseif ( $script === 'load' ) {
3187
		return $wgLoadScript;
3188
	} else {
3189
		return "{$wgScriptPath}/{$script}.php";
3190
	}
3191
}
3192
3193
/**
3194
 * Get the script URL.
3195
 *
3196
 * @return string Script URL
3197
 */
3198
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...
3199
	if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
3200
		/* as it was called, minus the query string.
3201
		 *
3202
		 * Some sites use Apache rewrite rules to handle subdomains,
3203
		 * and have PHP set up in a weird way that causes PHP_SELF
3204
		 * to contain the rewritten URL instead of the one that the
3205
		 * outside world sees.
3206
		 *
3207
		 * If in this mode, use SCRIPT_URL instead, which mod_rewrite
3208
		 * provides containing the "before" URL.
3209
		 */
3210
		return $_SERVER['SCRIPT_NAME'];
3211
	} else {
3212
		return $_SERVER['URL'];
3213
	}
3214
}
3215
3216
/**
3217
 * Convenience function converts boolean values into "true"
3218
 * or "false" (string) values
3219
 *
3220
 * @param bool $value
3221
 * @return string
3222
 */
3223
function wfBoolToStr( $value ) {
3224
	return $value ? 'true' : 'false';
3225
}
3226
3227
/**
3228
 * Get a platform-independent path to the null file, e.g. /dev/null
3229
 *
3230
 * @return string
3231
 */
3232
function wfGetNull() {
3233
	return wfIsWindows() ? 'NUL' : '/dev/null';
3234
}
3235
3236
/**
3237
 * Waits for the slaves to catch up to the master position
3238
 *
3239
 * Use this when updating very large numbers of rows, as in maintenance scripts,
3240
 * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
3241
 *
3242
 * By default this waits on the main DB cluster of the current wiki.
3243
 * If $cluster is set to "*" it will wait on all DB clusters, including
3244
 * external ones. If the lag being waiting on is caused by the code that
3245
 * does this check, it makes since to use $ifWritesSince, particularly if
3246
 * cluster is "*", to avoid excess overhead.
3247
 *
3248
 * Never call this function after a big DB write that is still in a transaction.
3249
 * This only makes sense after the possible lag inducing changes were committed.
3250
 *
3251
 * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
3252
 * @param string|bool $wiki Wiki identifier accepted by wfGetLB
3253
 * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
3254
 * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web)
3255
 * @return bool Success (able to connect and no timeouts reached)
3256
 * @deprecated since 1.27 Use LBFactory::waitForReplication
3257
 */
3258
function wfWaitForSlaves(
3259
	$ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
3260
) {
3261
	if ( $timeout === null ) {
3262
		$timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10;
3263
	}
3264
3265
	if ( $cluster === '*' ) {
3266
		$cluster = false;
3267
		$wiki = false;
3268
	} elseif ( $wiki === false ) {
3269
		$wiki = wfWikiID();
3270
	}
3271
3272
	try {
3273
		wfGetLBFactory()->waitForReplication( [
3274
			'wiki' => $wiki,
3275
			'cluster' => $cluster,
3276
			'timeout' => $timeout,
3277
			// B/C: first argument used to be "max seconds of lag"; ignore such values
3278
			'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
3279
		] );
3280
	} catch ( DBReplicationWaitError $e ) {
3281
		return false;
3282
	}
3283
3284
	return true;
3285
}
3286
3287
/**
3288
 * Count down from $seconds to zero on the terminal, with a one-second pause
3289
 * between showing each number. For use in command-line scripts.
3290
 *
3291
 * @codeCoverageIgnore
3292
 * @param int $seconds
3293
 */
3294
function wfCountDown( $seconds ) {
3295
	for ( $i = $seconds; $i >= 0; $i-- ) {
3296
		if ( $i != $seconds ) {
3297
			echo str_repeat( "\x08", strlen( $i + 1 ) );
3298
		}
3299
		echo $i;
3300
		flush();
3301
		if ( $i ) {
3302
			sleep( 1 );
3303
		}
3304
	}
3305
	echo "\n";
3306
}
3307
3308
/**
3309
 * Replace all invalid characters with -
3310
 * Additional characters can be defined in $wgIllegalFileChars (see bug 20489)
3311
 * By default, $wgIllegalFileChars = ':'
3312
 *
3313
 * @param string $name Filename to process
3314
 * @return string
3315
 */
3316
function wfStripIllegalFilenameChars( $name ) {
3317
	global $wgIllegalFileChars;
3318
	$illegalFileChars = $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '';
3319
	$name = wfBaseName( $name );
3320
	$name = preg_replace(
3321
		"/[^" . Title::legalChars() . "]" . $illegalFileChars . "/",
3322
		'-',
3323
		$name
3324
	);
3325
	return $name;
3326
}
3327
3328
/**
3329
 * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit
3330
 *
3331
 * @return int Resulting value of the memory limit.
3332
 */
3333
function wfMemoryLimit() {
3334
	global $wgMemoryLimit;
3335
	$memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
3336
	if ( $memlimit != -1 ) {
3337
		$conflimit = wfShorthandToInteger( $wgMemoryLimit );
3338
		if ( $conflimit == -1 ) {
3339
			wfDebug( "Removing PHP's memory limit\n" );
3340
			MediaWiki\suppressWarnings();
3341
			ini_set( 'memory_limit', $conflimit );
3342
			MediaWiki\restoreWarnings();
3343
			return $conflimit;
3344
		} elseif ( $conflimit > $memlimit ) {
3345
			wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
3346
			MediaWiki\suppressWarnings();
3347
			ini_set( 'memory_limit', $conflimit );
3348
			MediaWiki\restoreWarnings();
3349
			return $conflimit;
3350
		}
3351
	}
3352
	return $memlimit;
3353
}
3354
3355
/**
3356
 * Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit
3357
 *
3358
 * @return int Prior time limit
3359
 * @since 1.26
3360
 */
3361
function wfTransactionalTimeLimit() {
3362
	global $wgTransactionalTimeLimit;
3363
3364
	$timeLimit = ini_get( 'max_execution_time' );
3365
	// Note that CLI scripts use 0
3366
	if ( $timeLimit > 0 && $wgTransactionalTimeLimit > $timeLimit ) {
3367
		set_time_limit( $wgTransactionalTimeLimit );
3368
	}
3369
3370
	ignore_user_abort( true ); // ignore client disconnects
3371
3372
	return $timeLimit;
3373
}
3374
3375
/**
3376
 * Converts shorthand byte notation to integer form
3377
 *
3378
 * @param string $string
3379
 * @param int $default Returned if $string is empty
3380
 * @return int
3381
 */
3382
function wfShorthandToInteger( $string = '', $default = -1 ) {
3383
	$string = trim( $string );
3384
	if ( $string === '' ) {
3385
		return $default;
3386
	}
3387
	$last = $string[strlen( $string ) - 1];
3388
	$val = intval( $string );
3389
	switch ( $last ) {
3390
		case 'g':
3391
		case 'G':
3392
			$val *= 1024;
3393
			// break intentionally missing
3394
		case 'm':
3395
		case 'M':
3396
			$val *= 1024;
3397
			// break intentionally missing
3398
		case 'k':
3399
		case 'K':
3400
			$val *= 1024;
3401
	}
3402
3403
	return $val;
3404
}
3405
3406
/**
3407
 * Get the normalised IETF language tag
3408
 * See unit test for examples.
3409
 *
3410
 * @param string $code The language code.
3411
 * @return string The language code which complying with BCP 47 standards.
3412
 */
3413
function wfBCP47( $code ) {
3414
	$codeSegment = explode( '-', $code );
3415
	$codeBCP = [];
3416
	foreach ( $codeSegment as $segNo => $seg ) {
3417
		// when previous segment is x, it is a private segment and should be lc
3418
		if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
3419
			$codeBCP[$segNo] = strtolower( $seg );
3420
		// ISO 3166 country code
3421
		} elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
3422
			$codeBCP[$segNo] = strtoupper( $seg );
3423
		// ISO 15924 script code
3424
		} elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
3425
			$codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
3426
		// Use lowercase for other cases
3427
		} else {
3428
			$codeBCP[$segNo] = strtolower( $seg );
3429
		}
3430
	}
3431
	$langCode = implode( '-', $codeBCP );
3432
	return $langCode;
3433
}
3434
3435
/**
3436
 * Get a specific cache object.
3437
 *
3438
 * @param int|string $cacheType A CACHE_* constants, or other key in $wgObjectCaches
3439
 * @return BagOStuff
3440
 */
3441
function wfGetCache( $cacheType ) {
3442
	return ObjectCache::getInstance( $cacheType );
3443
}
3444
3445
/**
3446
 * Get the main cache object
3447
 *
3448
 * @return BagOStuff
3449
 */
3450
function wfGetMainCache() {
3451
	global $wgMainCacheType;
3452
	return ObjectCache::getInstance( $wgMainCacheType );
3453
}
3454
3455
/**
3456
 * Get the cache object used by the message cache
3457
 *
3458
 * @return BagOStuff
3459
 */
3460
function wfGetMessageCacheStorage() {
3461
	global $wgMessageCacheType;
3462
	return ObjectCache::getInstance( $wgMessageCacheType );
3463
}
3464
3465
/**
3466
 * Get the cache object used by the parser cache
3467
 *
3468
 * @return BagOStuff
3469
 */
3470
function wfGetParserCacheStorage() {
3471
	global $wgParserCacheType;
3472
	return ObjectCache::getInstance( $wgParserCacheType );
3473
}
3474
3475
/**
3476
 * Call hook functions defined in $wgHooks
3477
 *
3478
 * @param string $event Event name
3479
 * @param array $args Parameters passed to hook functions
3480
 * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number
3481
 *
3482
 * @return bool True if no handler aborted the hook
3483
 * @deprecated 1.25 - use Hooks::run
3484
 */
3485
function wfRunHooks( $event, array $args = [], $deprecatedVersion = null ) {
3486
	return Hooks::run( $event, $args, $deprecatedVersion );
3487
}
3488
3489
/**
3490
 * Wrapper around php's unpack.
3491
 *
3492
 * @param string $format The format string (See php's docs)
3493
 * @param string $data A binary string of binary data
3494
 * @param int|bool $length The minimum length of $data or false. This is to
3495
 *	prevent reading beyond the end of $data. false to disable the check.
3496
 *
3497
 * Also be careful when using this function to read unsigned 32 bit integer
3498
 * because php might make it negative.
3499
 *
3500
 * @throws MWException If $data not long enough, or if unpack fails
3501
 * @return array Associative array of the extracted data
3502
 */
3503
function wfUnpack( $format, $data, $length = false ) {
3504
	if ( $length !== false ) {
3505
		$realLen = strlen( $data );
3506
		if ( $realLen < $length ) {
3507
			throw new MWException( "Tried to use wfUnpack on a "
3508
				. "string of length $realLen, but needed one "
3509
				. "of at least length $length."
3510
			);
3511
		}
3512
	}
3513
3514
	MediaWiki\suppressWarnings();
3515
	$result = unpack( $format, $data );
3516
	MediaWiki\restoreWarnings();
3517
3518
	if ( $result === false ) {
3519
		// If it cannot extract the packed data.
3520
		throw new MWException( "unpack could not unpack binary data" );
3521
	}
3522
	return $result;
3523
}
3524
3525
/**
3526
 * Determine if an image exists on the 'bad image list'.
3527
 *
3528
 * The format of MediaWiki:Bad_image_list is as follows:
3529
 *    * Only list items (lines starting with "*") are considered
3530
 *    * The first link on a line must be a link to a bad image
3531
 *    * Any subsequent links on the same line are considered to be exceptions,
3532
 *      i.e. articles where the image may occur inline.
3533
 *
3534
 * @param string $name The image name to check
3535
 * @param Title|bool $contextTitle The page on which the image occurs, if known
3536
 * @param string $blacklist Wikitext of a file blacklist
3537
 * @return bool
3538
 */
3539
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
3540
	# Handle redirects; callers almost always hit wfFindFile() anyway,
3541
	# so just use that method because it has a fast process cache.
3542
	$file = wfFindFile( $name ); // get the final name
3543
	$name = $file ? $file->getTitle()->getDBkey() : $name;
3544
3545
	# Run the extension hook
3546
	$bad = false;
3547
	if ( !Hooks::run( 'BadImage', [ $name, &$bad ] ) ) {
3548
		return $bad;
3549
	}
3550
3551
	$cache = ObjectCache::getLocalServerInstance( 'hash' );
3552
	$key = wfMemcKey( 'bad-image-list', ( $blacklist === null ) ? 'default' : md5( $blacklist ) );
3553
	$badImages = $cache->get( $key );
3554
3555
	if ( $badImages === false ) { // cache miss
3556
		if ( $blacklist === null ) {
3557
			$blacklist = wfMessage( 'bad_image_list' )->inContentLanguage()->plain(); // site list
3558
		}
3559
		# Build the list now
3560
		$badImages = [];
3561
		$lines = explode( "\n", $blacklist );
3562
		foreach ( $lines as $line ) {
3563
			# List items only
3564
			if ( substr( $line, 0, 1 ) !== '*' ) {
3565
				continue;
3566
			}
3567
3568
			# Find all links
3569
			$m = [];
3570
			if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
3571
				continue;
3572
			}
3573
3574
			$exceptions = [];
3575
			$imageDBkey = false;
3576
			foreach ( $m[1] as $i => $titleText ) {
3577
				$title = Title::newFromText( $titleText );
3578
				if ( !is_null( $title ) ) {
3579
					if ( $i == 0 ) {
3580
						$imageDBkey = $title->getDBkey();
3581
					} else {
3582
						$exceptions[$title->getPrefixedDBkey()] = true;
3583
					}
3584
				}
3585
			}
3586
3587
			if ( $imageDBkey !== false ) {
3588
				$badImages[$imageDBkey] = $exceptions;
3589
			}
3590
		}
3591
		$cache->set( $key, $badImages, 60 );
3592
	}
3593
3594
	$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...
3595
	$bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
3596
3597
	return $bad;
3598
}
3599
3600
/**
3601
 * Determine whether the client at a given source IP is likely to be able to
3602
 * access the wiki via HTTPS.
3603
 *
3604
 * @param string $ip The IPv4/6 address in the normal human-readable form
3605
 * @return bool
3606
 */
3607
function wfCanIPUseHTTPS( $ip ) {
3608
	$canDo = true;
3609
	Hooks::run( 'CanIPUseHTTPS', [ $ip, &$canDo ] );
3610
	return !!$canDo;
3611
}
3612
3613
/**
3614
 * Determine input string is represents as infinity
3615
 *
3616
 * @param string $str The string to determine
3617
 * @return bool
3618
 * @since 1.25
3619
 */
3620
function wfIsInfinity( $str ) {
3621
	$infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ];
3622
	return in_array( $str, $infinityValues );
3623
}
3624
3625
/**
3626
 * Returns true if these thumbnail parameters match one that MediaWiki
3627
 * requests from file description pages and/or parser output.
3628
 *
3629
 * $params is considered non-standard if they involve a non-standard
3630
 * width or any non-default parameters aside from width and page number.
3631
 * The number of possible files with standard parameters is far less than
3632
 * that of all combinations; rate-limiting for them can thus be more generious.
3633
 *
3634
 * @param File $file
3635
 * @param array $params
3636
 * @return bool
3637
 * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25
3638
 */
3639
function wfThumbIsStandard( File $file, array $params ) {
3640
	global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages;
3641
3642
	$multipliers = [ 1 ];
3643
	if ( $wgResponsiveImages ) {
3644
		// These available sizes are hardcoded currently elsewhere in MediaWiki.
3645
		// @see Linker::processResponsiveImages
3646
		$multipliers[] = 1.5;
3647
		$multipliers[] = 2;
3648
	}
3649
3650
	$handler = $file->getHandler();
3651
	if ( !$handler || !isset( $params['width'] ) ) {
3652
		return false;
3653
	}
3654
3655
	$basicParams = [];
3656
	if ( isset( $params['page'] ) ) {
3657
		$basicParams['page'] = $params['page'];
3658
	}
3659
3660
	$thumbLimits = [];
3661
	$imageLimits = [];
3662
	// Expand limits to account for multipliers
3663
	foreach ( $multipliers as $multiplier ) {
3664
		$thumbLimits = array_merge( $thumbLimits, array_map(
3665
			function ( $width ) use ( $multiplier ) {
3666
				return round( $width * $multiplier );
3667
			}, $wgThumbLimits )
3668
		);
3669
		$imageLimits = array_merge( $imageLimits, array_map(
3670
			function ( $pair ) use ( $multiplier ) {
3671
				return [
3672
					round( $pair[0] * $multiplier ),
3673
					round( $pair[1] * $multiplier ),
3674
				];
3675
			}, $wgImageLimits )
3676
		);
3677
	}
3678
3679
	// Check if the width matches one of $wgThumbLimits
3680
	if ( in_array( $params['width'], $thumbLimits ) ) {
3681
		$normalParams = $basicParams + [ 'width' => $params['width'] ];
3682
		// Append any default values to the map (e.g. "lossy", "lossless", ...)
3683
		$handler->normaliseParams( $file, $normalParams );
3684
	} else {
3685
		// If not, then check if the width matchs one of $wgImageLimits
3686
		$match = false;
3687
		foreach ( $imageLimits as $pair ) {
3688
			$normalParams = $basicParams + [ 'width' => $pair[0], 'height' => $pair[1] ];
3689
			// Decide whether the thumbnail should be scaled on width or height.
3690
			// Also append any default values to the map (e.g. "lossy", "lossless", ...)
3691
			$handler->normaliseParams( $file, $normalParams );
3692
			// Check if this standard thumbnail size maps to the given width
3693
			if ( $normalParams['width'] == $params['width'] ) {
3694
				$match = true;
3695
				break;
3696
			}
3697
		}
3698
		if ( !$match ) {
3699
			return false; // not standard for description pages
3700
		}
3701
	}
3702
3703
	// Check that the given values for non-page, non-width, params are just defaults
3704
	foreach ( $params as $key => $value ) {
3705
		if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
3706
			return false;
3707
		}
3708
	}
3709
3710
	return true;
3711
}
3712
3713
/**
3714
 * Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
3715
 *
3716
 * Values that exist in both values will be combined with += (all values of the array
3717
 * of $newValues will be added to the values of the array of $baseArray, while values,
3718
 * that exists in both, the value of $baseArray will be used).
3719
 *
3720
 * @param array $baseArray The array where you want to add the values of $newValues to
3721
 * @param array $newValues An array with new values
3722
 * @return array The combined array
3723
 * @since 1.26
3724
 */
3725
function wfArrayPlus2d( array $baseArray, array $newValues ) {
3726
	// First merge items that are in both arrays
3727
	foreach ( $baseArray as $name => &$groupVal ) {
3728
		if ( isset( $newValues[$name] ) ) {
3729
			$groupVal += $newValues[$name];
3730
		}
3731
	}
3732
	// Now add items that didn't exist yet
3733
	$baseArray += $newValues;
3734
3735
	return $baseArray;
3736
}
3737