Completed
Branch master (bb6f05)
by
unknown
31:42
created

GlobalFunctions.php ➔ wfTempDir()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 18
nc 12
nop 0
dl 0
loc 37
rs 5.1612
c 1
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 70 and the first side effect is on line 24.

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

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

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

Loading history...
2
/**
3
 * Global functions used everywhere.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
23
if ( !defined( 'MEDIAWIKI' ) ) {
24
	die( "This file is part of MediaWiki, it is not a valid entry point" );
25
}
26
27
use Liuggio\StatsdClient\Sender\SocketSender;
28
use MediaWiki\Logger\LoggerFactory;
29
use MediaWiki\Session\SessionManager;
30
31
// Hide compatibility functions from Doxygen
32
/// @cond
33
34
/**
35
 * Compatibility functions
36
 *
37
 * We support PHP 5.5.9 and up.
38
 * Re-implementations of newer functions or functions in non-standard
39
 * PHP extensions may be included here.
40
 */
41
42
// hash_equals function only exists in PHP >= 5.6.0
43
// http://php.net/hash_equals
44
if ( !function_exists( 'hash_equals' ) ) {
45
	/**
46
	 * Check whether a user-provided string is equal to a fixed-length secret string
47
	 * without revealing bytes of the secret string through timing differences.
48
	 *
49
	 * The usual way to compare strings (PHP's === operator or the underlying memcmp()
50
	 * function in C) is to compare corresponding bytes and stop at the first difference,
51
	 * which would take longer for a partial match than for a complete mismatch. This
52
	 * is not secure when one of the strings (e.g. an HMAC or token) must remain secret
53
	 * and the other may come from an attacker. Statistical analysis of timing measurements
54
	 * over many requests may allow the attacker to guess the string's bytes one at a time
55
	 * (and check his guesses) even if the timing differences are extremely small.
56
	 *
57
	 * When making such a security-sensitive comparison, it is essential that the sequence
58
	 * in which instructions are executed and memory locations are accessed not depend on
59
	 * the secret string's value. HOWEVER, for simplicity, we do not attempt to minimize
60
	 * the inevitable leakage of the string's length. That is generally known anyway as
61
	 * a chararacteristic of the hash function used to compute the secret value.
62
	 *
63
	 * Longer explanation: http://www.emerose.com/timing-attacks-explained
64
	 *
65
	 * @codeCoverageIgnore
66
	 * @param string $known_string Fixed-length secret string to compare against
67
	 * @param string $user_string User-provided string
68
	 * @return bool True if the strings are the same, false otherwise
69
	 */
70
	function hash_equals( $known_string, $user_string ) {
71
		// Strict type checking as in PHP's native implementation
72 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
		// Remove the fragment, if there is one
505
		$fragment = false;
506
		$hashPos = strpos( $url, '#' );
507
		if ( $hashPos !== false ) {
508
			$fragment = substr( $url, $hashPos );
509
			$url = substr( $url, 0, $hashPos );
510
		}
511
512
		// Add parameter
513
		if ( false === strpos( $url, '?' ) ) {
514
			$url .= '?';
515
		} else {
516
			$url .= '&';
517
		}
518
		$url .= $query;
519
520
		// Put the fragment back
521
		if ( $fragment !== false ) {
522
			$url .= $fragment;
523
		}
524
	}
525
	return $url;
526
}
527
528
/**
529
 * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
530
 * is correct.
531
 *
532
 * The meaning of the PROTO_* constants is as follows:
533
 * PROTO_HTTP: Output a URL starting with http://
534
 * PROTO_HTTPS: Output a URL starting with https://
535
 * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
536
 * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending
537
 *    on which protocol was used for the current incoming request
538
 * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer.
539
 *    For protocol-relative URLs, use the protocol of $wgCanonicalServer
540
 * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
541
 *
542
 * @todo this won't work with current-path-relative URLs
543
 * like "subdir/foo.html", etc.
544
 *
545
 * @param string $url Either fully-qualified or a local path + query
546
 * @param string $defaultProto One of the PROTO_* constants. Determines the
547
 *    protocol to use if $url or $wgServer is protocol-relative
548
 * @return string Fully-qualified URL, current-path-relative URL or false if
549
 *    no valid URL can be constructed
550
 */
551
function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
552
	global $wgServer, $wgCanonicalServer, $wgInternalServer, $wgRequest,
553
		$wgHttpsPort;
554
	if ( $defaultProto === PROTO_CANONICAL ) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_CANONICAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
555
		$serverUrl = $wgCanonicalServer;
556
	} elseif ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_INTERNAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
557
		// Make $wgInternalServer fall back to $wgServer if not set
558
		$serverUrl = $wgInternalServer;
559
	} else {
560
		$serverUrl = $wgServer;
561
		if ( $defaultProto === PROTO_CURRENT ) {
562
			$defaultProto = $wgRequest->getProtocol() . '://';
563
		}
564
	}
565
566
	// Analyze $serverUrl to obtain its protocol
567
	$bits = wfParseUrl( $serverUrl );
568
	$serverHasProto = $bits && $bits['scheme'] != '';
569
570
	if ( $defaultProto === PROTO_CANONICAL || $defaultProto === PROTO_INTERNAL ) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_CANONICAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $defaultProto (string) and PROTO_INTERNAL (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
571
		if ( $serverHasProto ) {
572
			$defaultProto = $bits['scheme'] . '://';
573
		} else {
574
			// $wgCanonicalServer or $wgInternalServer doesn't have a protocol.
575
			// This really isn't supposed to happen. Fall back to HTTP in this
576
			// ridiculous case.
577
			$defaultProto = PROTO_HTTP;
578
		}
579
	}
580
581
	$defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 );
582
583
	if ( substr( $url, 0, 2 ) == '//' ) {
584
		$url = $defaultProtoWithoutSlashes . $url;
585
	} elseif ( substr( $url, 0, 1 ) == '/' ) {
586
		// If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes,
587
		// otherwise leave it alone.
588
		$url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
589
	}
590
591
	$bits = wfParseUrl( $url );
592
593
	// ensure proper port for HTTPS arrives in URL
594
	// https://phabricator.wikimedia.org/T67184
595
	if ( $defaultProto === PROTO_HTTPS && $wgHttpsPort != 443 ) {
596
		$bits['port'] = $wgHttpsPort;
597
	}
598
599
	if ( $bits && isset( $bits['path'] ) ) {
600
		$bits['path'] = wfRemoveDotSegments( $bits['path'] );
601
		return wfAssembleUrl( $bits );
602
	} elseif ( $bits ) {
603
		# No path to expand
604
		return $url;
605
	} elseif ( substr( $url, 0, 1 ) != '/' ) {
606
		# URL is a relative path
607
		return wfRemoveDotSegments( $url );
608
	}
609
610
	# Expanded URL is not valid.
611
	return false;
612
}
613
614
/**
615
 * This function will reassemble a URL parsed with wfParseURL.  This is useful
616
 * if you need to edit part of a URL and put it back together.
617
 *
618
 * This is the basic structure used (brackets contain keys for $urlParts):
619
 * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
620
 *
621
 * @todo Need to integrate this into wfExpandUrl (bug 32168)
622
 *
623
 * @since 1.19
624
 * @param array $urlParts URL parts, as output from wfParseUrl
625
 * @return string URL assembled from its component parts
626
 */
627
function wfAssembleUrl( $urlParts ) {
628
	$result = '';
629
630
	if ( isset( $urlParts['delimiter'] ) ) {
631
		if ( isset( $urlParts['scheme'] ) ) {
632
			$result .= $urlParts['scheme'];
633
		}
634
635
		$result .= $urlParts['delimiter'];
636
	}
637
638
	if ( isset( $urlParts['host'] ) ) {
639
		if ( isset( $urlParts['user'] ) ) {
640
			$result .= $urlParts['user'];
641
			if ( isset( $urlParts['pass'] ) ) {
642
				$result .= ':' . $urlParts['pass'];
643
			}
644
			$result .= '@';
645
		}
646
647
		$result .= $urlParts['host'];
648
649
		if ( isset( $urlParts['port'] ) ) {
650
			$result .= ':' . $urlParts['port'];
651
		}
652
	}
653
654
	if ( isset( $urlParts['path'] ) ) {
655
		$result .= $urlParts['path'];
656
	}
657
658
	if ( isset( $urlParts['query'] ) ) {
659
		$result .= '?' . $urlParts['query'];
660
	}
661
662
	if ( isset( $urlParts['fragment'] ) ) {
663
		$result .= '#' . $urlParts['fragment'];
664
	}
665
666
	return $result;
667
}
668
669
/**
670
 * Remove all dot-segments in the provided URL path.  For example,
671
 * '/a/./b/../c/' becomes '/a/c/'.  For details on the algorithm, please see
672
 * RFC3986 section 5.2.4.
673
 *
674
 * @todo Need to integrate this into wfExpandUrl (bug 32168)
675
 *
676
 * @param string $urlPath URL path, potentially containing dot-segments
677
 * @return string URL path with all dot-segments removed
678
 */
679
function wfRemoveDotSegments( $urlPath ) {
680
	$output = '';
681
	$inputOffset = 0;
682
	$inputLength = strlen( $urlPath );
683
684
	while ( $inputOffset < $inputLength ) {
685
		$prefixLengthOne = substr( $urlPath, $inputOffset, 1 );
686
		$prefixLengthTwo = substr( $urlPath, $inputOffset, 2 );
687
		$prefixLengthThree = substr( $urlPath, $inputOffset, 3 );
688
		$prefixLengthFour = substr( $urlPath, $inputOffset, 4 );
689
		$trimOutput = false;
690
691
		if ( $prefixLengthTwo == './' ) {
692
			# Step A, remove leading "./"
693
			$inputOffset += 2;
694
		} elseif ( $prefixLengthThree == '../' ) {
695
			# Step A, remove leading "../"
696
			$inputOffset += 3;
697
		} elseif ( ( $prefixLengthTwo == '/.' ) && ( $inputOffset + 2 == $inputLength ) ) {
698
			# Step B, replace leading "/.$" with "/"
699
			$inputOffset += 1;
700
			$urlPath[$inputOffset] = '/';
701
		} elseif ( $prefixLengthThree == '/./' ) {
702
			# Step B, replace leading "/./" with "/"
703
			$inputOffset += 2;
704
		} elseif ( $prefixLengthThree == '/..' && ( $inputOffset + 3 == $inputLength ) ) {
705
			# Step C, replace leading "/..$" with "/" and
706
			# remove last path component in output
707
			$inputOffset += 2;
708
			$urlPath[$inputOffset] = '/';
709
			$trimOutput = true;
710
		} elseif ( $prefixLengthFour == '/../' ) {
711
			# Step C, replace leading "/../" with "/" and
712
			# remove last path component in output
713
			$inputOffset += 3;
714
			$trimOutput = true;
715
		} elseif ( ( $prefixLengthOne == '.' ) && ( $inputOffset + 1 == $inputLength ) ) {
716
			# Step D, remove "^.$"
717
			$inputOffset += 1;
718
		} elseif ( ( $prefixLengthTwo == '..' ) && ( $inputOffset + 2 == $inputLength ) ) {
719
			# Step D, remove "^..$"
720
			$inputOffset += 2;
721
		} else {
722
			# Step E, move leading path segment to output
723
			if ( $prefixLengthOne == '/' ) {
724
				$slashPos = strpos( $urlPath, '/', $inputOffset + 1 );
725
			} else {
726
				$slashPos = strpos( $urlPath, '/', $inputOffset );
727
			}
728
			if ( $slashPos === false ) {
729
				$output .= substr( $urlPath, $inputOffset );
730
				$inputOffset = $inputLength;
731
			} else {
732
				$output .= substr( $urlPath, $inputOffset, $slashPos - $inputOffset );
733
				$inputOffset += $slashPos - $inputOffset;
734
			}
735
		}
736
737
		if ( $trimOutput ) {
738
			$slashPos = strrpos( $output, '/' );
739
			if ( $slashPos === false ) {
740
				$output = '';
741
			} else {
742
				$output = substr( $output, 0, $slashPos );
743
			}
744
		}
745
	}
746
747
	return $output;
748
}
749
750
/**
751
 * Returns a regular expression of url protocols
752
 *
753
 * @param bool $includeProtocolRelative If false, remove '//' from the returned protocol list.
754
 *        DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
755
 * @return string
756
 */
757
function wfUrlProtocols( $includeProtocolRelative = true ) {
758
	global $wgUrlProtocols;
759
760
	// Cache return values separately based on $includeProtocolRelative
761
	static $withProtRel = null, $withoutProtRel = null;
762
	$cachedValue = $includeProtocolRelative ? $withProtRel : $withoutProtRel;
763
	if ( !is_null( $cachedValue ) ) {
764
		return $cachedValue;
765
	}
766
767
	// Support old-style $wgUrlProtocols strings, for backwards compatibility
768
	// with LocalSettings files from 1.5
769
	if ( is_array( $wgUrlProtocols ) ) {
770
		$protocols = [];
771
		foreach ( $wgUrlProtocols as $protocol ) {
772
			// Filter out '//' if !$includeProtocolRelative
773
			if ( $includeProtocolRelative || $protocol !== '//' ) {
774
				$protocols[] = preg_quote( $protocol, '/' );
775
			}
776
		}
777
778
		$retval = implode( '|', $protocols );
779
	} else {
780
		// Ignore $includeProtocolRelative in this case
781
		// This case exists for pre-1.6 compatibility, and we can safely assume
782
		// that '//' won't appear in a pre-1.6 config because protocol-relative
783
		// URLs weren't supported until 1.18
784
		$retval = $wgUrlProtocols;
785
	}
786
787
	// Cache return value
788
	if ( $includeProtocolRelative ) {
789
		$withProtRel = $retval;
790
	} else {
791
		$withoutProtRel = $retval;
792
	}
793
	return $retval;
794
}
795
796
/**
797
 * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
798
 * you need a regex that matches all URL protocols but does not match protocol-
799
 * relative URLs
800
 * @return string
801
 */
802
function wfUrlProtocolsWithoutProtRel() {
803
	return wfUrlProtocols( false );
804
}
805
806
/**
807
 * parse_url() work-alike, but non-broken.  Differences:
808
 *
809
 * 1) Does not raise warnings on bad URLs (just returns false).
810
 * 2) Handles protocols that don't use :// (e.g., mailto: and news:, as well as
811
 *    protocol-relative URLs) correctly.
812
 * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2)).
813
 *
814
 * @param string $url A URL to parse
815
 * @return string[] Bits of the URL in an associative array, per PHP docs
816
 */
817
function wfParseUrl( $url ) {
818
	global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
819
820
	// Protocol-relative URLs are handled really badly by parse_url(). It's so
821
	// bad that the easiest way to handle them is to just prepend 'http:' and
822
	// strip the protocol out later.
823
	$wasRelative = substr( $url, 0, 2 ) == '//';
824
	if ( $wasRelative ) {
825
		$url = "http:$url";
826
	}
827
	MediaWiki\suppressWarnings();
828
	$bits = parse_url( $url );
829
	MediaWiki\restoreWarnings();
830
	// parse_url() returns an array without scheme for some invalid URLs, e.g.
831
	// parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
832
	if ( !$bits || !isset( $bits['scheme'] ) ) {
833
		return false;
834
	}
835
836
	// parse_url() incorrectly handles schemes case-sensitively. Convert it to lowercase.
837
	$bits['scheme'] = strtolower( $bits['scheme'] );
838
839
	// most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
840
	if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
841
		$bits['delimiter'] = '://';
842
	} elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
843
		$bits['delimiter'] = ':';
844
		// parse_url detects for news: and mailto: the host part of an url as path
845
		// We have to correct this wrong detection
846 View Code Duplication
		if ( isset( $bits['path'] ) ) {
847
			$bits['host'] = $bits['path'];
848
			$bits['path'] = '';
849
		}
850
	} else {
851
		return false;
852
	}
853
854
	/* Provide an empty host for eg. file:/// urls (see bug 28627) */
855
	if ( !isset( $bits['host'] ) ) {
856
		$bits['host'] = '';
857
858
		// bug 45069
859
		if ( isset( $bits['path'] ) ) {
860
			/* parse_url loses the third / for file:///c:/ urls (but not on variants) */
861
			if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
862
				$bits['path'] = '/' . $bits['path'];
863
			}
864
		} else {
865
			$bits['path'] = '';
866
		}
867
	}
868
869
	// If the URL was protocol-relative, fix scheme and delimiter
870
	if ( $wasRelative ) {
871
		$bits['scheme'] = '';
872
		$bits['delimiter'] = '//';
873
	}
874
	return $bits;
875
}
876
877
/**
878
 * Take a URL, make sure it's expanded to fully qualified, and replace any
879
 * encoded non-ASCII Unicode characters with their UTF-8 original forms
880
 * for more compact display and legibility for local audiences.
881
 *
882
 * @todo handle punycode domains too
883
 *
884
 * @param string $url
885
 * @return string
886
 */
887
function wfExpandIRI( $url ) {
888
	return preg_replace_callback(
889
		'/((?:%[89A-F][0-9A-F])+)/i',
890
		'wfExpandIRI_callback',
891
		wfExpandUrl( $url )
892
	);
893
}
894
895
/**
896
 * Private callback for wfExpandIRI
897
 * @param array $matches
898
 * @return string
899
 */
900
function wfExpandIRI_callback( $matches ) {
901
	return urldecode( $matches[1] );
902
}
903
904
/**
905
 * Make URL indexes, appropriate for the el_index field of externallinks.
906
 *
907
 * @param string $url
908
 * @return array
909
 */
910
function wfMakeUrlIndexes( $url ) {
911
	$bits = wfParseUrl( $url );
912
913
	// Reverse the labels in the hostname, convert to lower case
914
	// For emails reverse domainpart only
915
	if ( $bits['scheme'] == 'mailto' ) {
916
		$mailparts = explode( '@', $bits['host'], 2 );
917
		if ( count( $mailparts ) === 2 ) {
918
			$domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
919
		} else {
920
			// No domain specified, don't mangle it
921
			$domainpart = '';
922
		}
923
		$reversedHost = $domainpart . '@' . $mailparts[0];
924
	} else {
925
		$reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
926
	}
927
	// Add an extra dot to the end
928
	// Why? Is it in wrong place in mailto links?
929
	if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
930
		$reversedHost .= '.';
931
	}
932
	// Reconstruct the pseudo-URL
933
	$prot = $bits['scheme'];
934
	$index = $prot . $bits['delimiter'] . $reversedHost;
935
	// Leave out user and password. Add the port, path, query and fragment
936
	if ( isset( $bits['port'] ) ) {
937
		$index .= ':' . $bits['port'];
938
	}
939
	if ( isset( $bits['path'] ) ) {
940
		$index .= $bits['path'];
941
	} else {
942
		$index .= '/';
943
	}
944
	if ( isset( $bits['query'] ) ) {
945
		$index .= '?' . $bits['query'];
946
	}
947
	if ( isset( $bits['fragment'] ) ) {
948
		$index .= '#' . $bits['fragment'];
949
	}
950
951
	if ( $prot == '' ) {
952
		return [ "http:$index", "https:$index" ];
953
	} else {
954
		return [ $index ];
955
	}
956
}
957
958
/**
959
 * Check whether a given URL has a domain that occurs in a given set of domains
960
 * @param string $url URL
961
 * @param array $domains Array of domains (strings)
962
 * @return bool True if the host part of $url ends in one of the strings in $domains
963
 */
964
function wfMatchesDomainList( $url, $domains ) {
965
	$bits = wfParseUrl( $url );
966
	if ( is_array( $bits ) && isset( $bits['host'] ) ) {
967
		$host = '.' . $bits['host'];
968
		foreach ( (array)$domains as $domain ) {
969
			$domain = '.' . $domain;
970
			if ( substr( $host, -strlen( $domain ) ) === $domain ) {
971
				return true;
972
			}
973
		}
974
	}
975
	return false;
976
}
977
978
/**
979
 * Sends a line to the debug log if enabled or, optionally, to a comment in output.
980
 * In normal operation this is a NOP.
981
 *
982
 * Controlling globals:
983
 * $wgDebugLogFile - points to the log file
984
 * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
985
 * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
986
 *
987
 * @since 1.25 support for additional context data
988
 *
989
 * @param string $text
990
 * @param string|bool $dest Destination of the message:
991
 *     - 'all': both to the log and HTML (debug toolbar or HTML comments)
992
 *     - 'private': excluded from HTML output
993
 *   For backward compatibility, it can also take a boolean:
994
 *     - true: same as 'all'
995
 *     - false: same as 'private'
996
 * @param array $context Additional logging context data
997
 */
998
function wfDebug( $text, $dest = 'all', array $context = [] ) {
999
	global $wgDebugRawPage, $wgDebugLogPrefix;
1000
	global $wgDebugTimestamps, $wgRequestTime;
1001
1002
	if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
1003
		return;
1004
	}
1005
1006
	$text = trim( $text );
1007
1008
	if ( $wgDebugTimestamps ) {
1009
		$context['seconds_elapsed'] = sprintf(
1010
			'%6.4f',
1011
			microtime( true ) - $wgRequestTime
1012
		);
1013
		$context['memory_used'] = sprintf(
1014
			'%5.1fM',
1015
			( memory_get_usage( true ) / ( 1024 * 1024 ) )
1016
		);
1017
	}
1018
1019
	if ( $wgDebugLogPrefix !== '' ) {
1020
		$context['prefix'] = $wgDebugLogPrefix;
1021
	}
1022
	$context['private'] = ( $dest === false || $dest === 'private' );
1023
1024
	$logger = LoggerFactory::getInstance( 'wfDebug' );
1025
	$logger->debug( $text, $context );
1026
}
1027
1028
/**
1029
 * Returns true if debug logging should be suppressed if $wgDebugRawPage = false
1030
 * @return bool
1031
 */
1032
function wfIsDebugRawPage() {
0 ignored issues
show
Coding Style introduced by
wfIsDebugRawPage uses the super-global variable $_GET which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1033
	static $cache;
1034
	if ( $cache !== null ) {
1035
		return $cache;
1036
	}
1037
	# Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
1038
	if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
1039
		|| (
1040
			isset( $_SERVER['SCRIPT_NAME'] )
1041
			&& substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
1042
		)
1043
	) {
1044
		$cache = true;
1045
	} else {
1046
		$cache = false;
1047
	}
1048
	return $cache;
1049
}
1050
1051
/**
1052
 * Send a line giving PHP memory usage.
1053
 *
1054
 * @param bool $exact Print exact byte values instead of kibibytes (default: false)
1055
 */
1056
function wfDebugMem( $exact = false ) {
1057
	$mem = memory_get_usage();
1058
	if ( !$exact ) {
1059
		$mem = floor( $mem / 1024 ) . ' KiB';
1060
	} else {
1061
		$mem .= ' B';
1062
	}
1063
	wfDebug( "Memory usage: $mem\n" );
1064
}
1065
1066
/**
1067
 * Send a line to a supplementary debug log file, if configured, or main debug
1068
 * log if not.
1069
 *
1070
 * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to
1071
 * a string filename or an associative array mapping 'destination' to the
1072
 * desired filename. The associative array may also contain a 'sample' key
1073
 * with an integer value, specifying a sampling factor. Sampled log events
1074
 * will be emitted with a 1 in N random chance.
1075
 *
1076
 * @since 1.23 support for sampling log messages via $wgDebugLogGroups.
1077
 * @since 1.25 support for additional context data
1078
 * @since 1.25 sample behavior dependent on configured $wgMWLoggerDefaultSpi
1079
 *
1080
 * @param string $logGroup
1081
 * @param string $text
1082
 * @param string|bool $dest Destination of the message:
1083
 *     - 'all': both to the log and HTML (debug toolbar or HTML comments)
1084
 *     - 'private': only to the specific log if set in $wgDebugLogGroups and
1085
 *       discarded otherwise
1086
 *   For backward compatibility, it can also take a boolean:
1087
 *     - true: same as 'all'
1088
 *     - false: same as 'private'
1089
 * @param array $context Additional logging context data
1090
 */
1091
function wfDebugLog(
1092
	$logGroup, $text, $dest = 'all', array $context = []
1093
) {
1094
	$text = trim( $text );
1095
1096
	$logger = LoggerFactory::getInstance( $logGroup );
1097
	$context['private'] = ( $dest === false || $dest === 'private' );
1098
	$logger->info( $text, $context );
1099
}
1100
1101
/**
1102
 * Log for database errors
1103
 *
1104
 * @since 1.25 support for additional context data
1105
 *
1106
 * @param string $text Database error message.
1107
 * @param array $context Additional logging context data
1108
 */
1109
function wfLogDBError( $text, array $context = [] ) {
1110
	$logger = LoggerFactory::getInstance( 'wfLogDBError' );
1111
	$logger->error( trim( $text ), $context );
1112
}
1113
1114
/**
1115
 * Throws a warning that $function is deprecated
1116
 *
1117
 * @param string $function
1118
 * @param string|bool $version Version of MediaWiki that the function
1119
 *    was deprecated in (Added in 1.19).
1120
 * @param string|bool $component Added in 1.19.
1121
 * @param int $callerOffset How far up the call stack is the original
1122
 *    caller. 2 = function that called the function that called
1123
 *    wfDeprecated (Added in 1.20)
1124
 *
1125
 * @return null
1126
 */
1127
function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
1128
	MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
1129
}
1130
1131
/**
1132
 * Send a warning either to the debug log or in a PHP error depending on
1133
 * $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead.
1134
 *
1135
 * @param string $msg Message to send
1136
 * @param int $callerOffset Number of items to go back in the backtrace to
1137
 *        find the correct caller (1 = function calling wfWarn, ...)
1138
 * @param int $level PHP error level; defaults to E_USER_NOTICE;
1139
 *        only used when $wgDevelopmentWarnings is true
1140
 */
1141
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
1142
	MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' );
1143
}
1144
1145
/**
1146
 * Send a warning as a PHP error and the debug log. This is intended for logging
1147
 * warnings in production. For logging development warnings, use WfWarn instead.
1148
 *
1149
 * @param string $msg Message to send
1150
 * @param int $callerOffset Number of items to go back in the backtrace to
1151
 *        find the correct caller (1 = function calling wfLogWarning, ...)
1152
 * @param int $level PHP error level; defaults to E_USER_WARNING
1153
 */
1154
function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
1155
	MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
1156
}
1157
1158
/**
1159
 * Log to a file without getting "file size exceeded" signals.
1160
 *
1161
 * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
1162
 * send lines to the specified port, prefixed by the specified prefix and a space.
1163
 * @since 1.25 support for additional context data
1164
 *
1165
 * @param string $text
1166
 * @param string $file Filename
1167
 * @param array $context Additional logging context data
1168
 * @throws MWException
1169
 * @deprecated since 1.25 Use \MediaWiki\Logger\LegacyLogger::emit or UDPTransport
1170
 */
1171
function wfErrorLog( $text, $file, array $context = [] ) {
1172
	wfDeprecated( __METHOD__, '1.25' );
1173
	$logger = LoggerFactory::getInstance( 'wfErrorLog' );
1174
	$context['destination'] = $file;
1175
	$logger->info( trim( $text ), $context );
1176
}
1177
1178
/**
1179
 * @todo document
1180
 */
1181
function wfLogProfilingData() {
0 ignored issues
show
Coding Style introduced by
wfLogProfilingData uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1182
	global $wgDebugLogGroups, $wgDebugRawPage;
1183
1184
	$context = RequestContext::getMain();
1185
	$request = $context->getRequest();
1186
1187
	$profiler = Profiler::instance();
1188
	$profiler->setContext( $context );
1189
	$profiler->logData();
1190
1191
	$config = $context->getConfig();
1192
	if ( $config->get( 'StatsdServer' ) ) {
1193
		try {
1194
			$statsdServer = explode( ':', $config->get( 'StatsdServer' ) );
1195
			$statsdHost = $statsdServer[0];
1196
			$statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125;
1197
			$statsdSender = new SocketSender( $statsdHost, $statsdPort );
1198
			$statsdClient = new SamplingStatsdClient( $statsdSender, true, false );
1199
			$statsdClient->send( $context->getStats()->getBuffer() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Liuggio\StatsdClient\Factory\StatsdDataFactory as the method getBuffer() does only exist in the following sub-classes of Liuggio\StatsdClient\Factory\StatsdDataFactory: BufferingStatsdDataFactory. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Deprecated Code introduced by
The method RequestContext::getStats() has been deprecated with message: since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected)

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

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

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

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

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

Loading history...
1265
	$stats->updateCount( $key, $count );
1266
}
1267
1268
/**
1269
 * Check whether the wiki is in read-only mode.
1270
 *
1271
 * @return bool
1272
 */
1273
function wfReadOnly() {
1274
	return wfReadOnlyReason() !== false;
1275
}
1276
1277
/**
1278
 * Check if the site is in read-only mode and return the message if so
1279
 *
1280
 * This checks wfConfiguredReadOnlyReason() and the main load balancer
1281
 * for slave lag. This may result in DB_SLAVE connection being made.
1282
 *
1283
 * @return string|bool String when in read-only mode; false otherwise
1284
 */
1285
function wfReadOnlyReason() {
1286
	$readOnly = wfConfiguredReadOnlyReason();
1287
	if ( $readOnly !== false ) {
1288
		return $readOnly;
1289
	}
1290
1291
	static $lbReadOnly = null;
1292
	if ( $lbReadOnly === null ) {
1293
		// Callers use this method to be aware that data presented to a user
1294
		// may be very stale and thus allowing submissions can be problematic.
1295
		$lbReadOnly = wfGetLB()->getReadOnlyReason();
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or MediaWikiServices::getDBLoadBalancerFactory() instead.

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

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

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

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

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

An additional type check may prevent trouble.

Loading history...
1363
	}
1364
1365
	# $langcode is a string, but not a valid language code; use content language.
1366
	wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
1367
	return $wgContLang;
1368
}
1369
1370
/**
1371
 * This is the function for getting translated interface messages.
1372
 *
1373
 * @see Message class for documentation how to use them.
1374
 * @see https://www.mediawiki.org/wiki/Manual:Messages_API
1375
 *
1376
 * This function replaces all old wfMsg* functions.
1377
 *
1378
 * @param string|string[]|MessageSpecifier $key Message key, or array of keys, or a MessageSpecifier
1379
 * @param mixed $params,... Normal message parameters
0 ignored issues
show
Bug introduced by
There is no parameter named $params,.... Was it maybe removed?

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

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

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

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

Loading history...
1380
 * @return Message
1381
 *
1382
 * @since 1.17
1383
 *
1384
 * @see Message::__construct
1385
 */
1386
function wfMessage( $key /*...*/ ) {
1387
	$params = func_get_args();
1388
	array_shift( $params );
1389
	if ( isset( $params[0] ) && is_array( $params[0] ) ) {
1390
		$params = $params[0];
1391
	}
1392
	return new Message( $key, $params );
1393
}
1394
1395
/**
1396
 * This function accepts multiple message keys and returns a message instance
1397
 * for the first message which is non-empty. If all messages are empty then an
1398
 * instance of the first message key is returned.
1399
 *
1400
 * @param string|string[] $keys,... Message keys
0 ignored issues
show
Bug introduced by
There is no parameter named $keys,.... Was it maybe removed?

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

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

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

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

Loading history...
1401
 * @return Message
1402
 *
1403
 * @since 1.18
1404
 *
1405
 * @see Message::newFallbackSequence
1406
 */
1407
function wfMessageFallback( /*...*/ ) {
1408
	$args = func_get_args();
1409
	return call_user_func_array( 'Message::newFallbackSequence', $args );
1410
}
1411
1412
/**
1413
 * Replace message parameter keys on the given formatted output.
1414
 *
1415
 * @param string $message
1416
 * @param array $args
1417
 * @return string
1418
 * @private
1419
 */
1420
function wfMsgReplaceArgs( $message, $args ) {
1421
	# Fix windows line-endings
1422
	# Some messages are split with explode("\n", $msg)
1423
	$message = str_replace( "\r", '', $message );
1424
1425
	// Replace arguments
1426
	if ( is_array( $args ) && $args ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1427
		if ( is_array( $args[0] ) ) {
1428
			$args = array_values( $args[0] );
1429
		}
1430
		$replacementKeys = [];
1431
		foreach ( $args as $n => $param ) {
1432
			$replacementKeys['$' . ( $n + 1 )] = $param;
1433
		}
1434
		$message = strtr( $message, $replacementKeys );
1435
	}
1436
1437
	return $message;
1438
}
1439
1440
/**
1441
 * Fetch server name for use in error reporting etc.
1442
 * Use real server name if available, so we know which machine
1443
 * in a server farm generated the current page.
1444
 *
1445
 * @return string
1446
 */
1447
function wfHostname() {
0 ignored issues
show
Coding Style introduced by
wfHostname uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1448
	static $host;
1449
	if ( is_null( $host ) ) {
1450
1451
		# Hostname overriding
1452
		global $wgOverrideHostname;
1453
		if ( $wgOverrideHostname !== false ) {
1454
			# Set static and skip any detection
1455
			$host = $wgOverrideHostname;
1456
			return $host;
1457
		}
1458
1459
		if ( function_exists( 'posix_uname' ) ) {
1460
			// This function not present on Windows
1461
			$uname = posix_uname();
1462
		} else {
1463
			$uname = false;
1464
		}
1465
		if ( is_array( $uname ) && isset( $uname['nodename'] ) ) {
1466
			$host = $uname['nodename'];
1467
		} elseif ( getenv( 'COMPUTERNAME' ) ) {
1468
			# Windows computer name
1469
			$host = getenv( 'COMPUTERNAME' );
1470
		} else {
1471
			# This may be a virtual server.
1472
			$host = $_SERVER['SERVER_NAME'];
1473
		}
1474
	}
1475
	return $host;
1476
}
1477
1478
/**
1479
 * Returns a script tag that stores the amount of time it took MediaWiki to
1480
 * handle the request in milliseconds as 'wgBackendResponseTime'.
1481
 *
1482
 * If $wgShowHostnames is true, the script will also set 'wgHostname' to the
1483
 * hostname of the server handling the request.
1484
 *
1485
 * @return string
1486
 */
1487
function wfReportTime() {
1488
	global $wgRequestTime, $wgShowHostnames;
1489
1490
	$responseTime = round( ( microtime( true ) - $wgRequestTime ) * 1000 );
1491
	$reportVars = [ 'wgBackendResponseTime' => $responseTime ];
1492
	if ( $wgShowHostnames ) {
1493
		$reportVars['wgHostname'] = wfHostname();
1494
	}
1495
	return Skin::makeVariablesScript( $reportVars );
1496
}
1497
1498
/**
1499
 * Safety wrapper for debug_backtrace().
1500
 *
1501
 * Will return an empty array if debug_backtrace is disabled, otherwise
1502
 * the output from debug_backtrace() (trimmed).
1503
 *
1504
 * @param int $limit This parameter can be used to limit the number of stack frames returned
1505
 *
1506
 * @return array Array of backtrace information
1507
 */
1508
function wfDebugBacktrace( $limit = 0 ) {
1509
	static $disabled = null;
1510
1511
	if ( is_null( $disabled ) ) {
1512
		$disabled = !function_exists( 'debug_backtrace' );
1513
		if ( $disabled ) {
1514
			wfDebug( "debug_backtrace() is disabled\n" );
1515
		}
1516
	}
1517
	if ( $disabled ) {
1518
		return [];
1519
	}
1520
1521
	if ( $limit ) {
1522
		return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 );
1523
	} else {
1524
		return array_slice( debug_backtrace(), 1 );
1525
	}
1526
}
1527
1528
/**
1529
 * Get a debug backtrace as a string
1530
 *
1531
 * @param bool|null $raw If true, the return value is plain text. If false, HTML.
1532
 *   Defaults to $wgCommandLineMode if unset.
1533
 * @return string
1534
 * @since 1.25 Supports $raw parameter.
1535
 */
1536
function wfBacktrace( $raw = null ) {
1537
	global $wgCommandLineMode;
1538
1539
	if ( $raw === null ) {
1540
		$raw = $wgCommandLineMode;
1541
	}
1542
1543
	if ( $raw ) {
1544
		$frameFormat = "%s line %s calls %s()\n";
1545
		$traceFormat = "%s";
1546
	} else {
1547
		$frameFormat = "<li>%s line %s calls %s()</li>\n";
1548
		$traceFormat = "<ul>\n%s</ul>\n";
1549
	}
1550
1551
	$frames = array_map( function ( $frame ) use ( $frameFormat ) {
1552
		$file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-';
1553
		$line = isset( $frame['line'] ) ? $frame['line'] : '-';
1554
		$call = $frame['function'];
1555
		if ( !empty( $frame['class'] ) ) {
1556
			$call = $frame['class'] . $frame['type'] . $call;
1557
		}
1558
		return sprintf( $frameFormat, $file, $line, $call );
1559
	}, wfDebugBacktrace() );
1560
1561
	return sprintf( $traceFormat, implode( '', $frames ) );
1562
}
1563
1564
/**
1565
 * Get the name of the function which called this function
1566
 * wfGetCaller( 1 ) is the function with the wfGetCaller() call (ie. __FUNCTION__)
1567
 * wfGetCaller( 2 ) [default] is the caller of the function running wfGetCaller()
1568
 * wfGetCaller( 3 ) is the parent of that.
1569
 *
1570
 * @param int $level
1571
 * @return string
1572
 */
1573
function wfGetCaller( $level = 2 ) {
1574
	$backtrace = wfDebugBacktrace( $level + 1 );
1575
	if ( isset( $backtrace[$level] ) ) {
1576
		return wfFormatStackFrame( $backtrace[$level] );
1577
	} else {
1578
		return 'unknown';
1579
	}
1580
}
1581
1582
/**
1583
 * Return a string consisting of callers in the stack. Useful sometimes
1584
 * for profiling specific points.
1585
 *
1586
 * @param int $limit The maximum depth of the stack frame to return, or false for the entire stack.
1587
 * @return string
1588
 */
1589
function wfGetAllCallers( $limit = 3 ) {
1590
	$trace = array_reverse( wfDebugBacktrace() );
1591
	if ( !$limit || $limit > count( $trace ) - 1 ) {
1592
		$limit = count( $trace ) - 1;
1593
	}
1594
	$trace = array_slice( $trace, -$limit - 1, $limit );
1595
	return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
1596
}
1597
1598
/**
1599
 * Return a string representation of frame
1600
 *
1601
 * @param array $frame
1602
 * @return string
1603
 */
1604
function wfFormatStackFrame( $frame ) {
1605
	if ( !isset( $frame['function'] ) ) {
1606
		return 'NO_FUNCTION_GIVEN';
1607
	}
1608
	return isset( $frame['class'] ) && isset( $frame['type'] ) ?
1609
		$frame['class'] . $frame['type'] . $frame['function'] :
1610
		$frame['function'];
1611
}
1612
1613
/* Some generic result counters, pulled out of SearchEngine */
1614
1615
/**
1616
 * @todo document
1617
 *
1618
 * @param int $offset
1619
 * @param int $limit
1620
 * @return string
1621
 */
1622
function wfShowingResults( $offset, $limit ) {
1623
	return wfMessage( 'showingresults' )->numParams( $limit, $offset + 1 )->parse();
1624
}
1625
1626
/**
1627
 * @todo document
1628
 * @todo FIXME: We may want to blacklist some broken browsers
1629
 *
1630
 * @param bool $force
1631
 * @return bool Whereas client accept gzip compression
1632
 */
1633
function wfClientAcceptsGzip( $force = false ) {
0 ignored issues
show
Coding Style introduced by
wfClientAcceptsGzip uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1634
	static $result = null;
1635
	if ( $result === null || $force ) {
1636
		$result = false;
1637
		if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
1638
			# @todo FIXME: We may want to blacklist some broken browsers
1639
			$m = [];
1640
			if ( preg_match(
1641
					'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
1642
					$_SERVER['HTTP_ACCEPT_ENCODING'],
1643
					$m
1644
				)
1645
			) {
1646
				if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
1647
					$result = false;
1648
					return $result;
1649
				}
1650
				wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
1651
				$result = true;
1652
			}
1653
		}
1654
	}
1655
	return $result;
1656
}
1657
1658
/**
1659
 * Escapes the given text so that it may be output using addWikiText()
1660
 * without any linking, formatting, etc. making its way through. This
1661
 * is achieved by substituting certain characters with HTML entities.
1662
 * As required by the callers, "<nowiki>" is not used.
1663
 *
1664
 * @param string $text Text to be escaped
1665
 * @return string
1666
 */
1667
function wfEscapeWikiText( $text ) {
1668
	static $repl = null, $repl2 = null;
1669
	if ( $repl === null ) {
1670
		$repl = [
1671
			'"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
1672
			'=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
1673
			'{' => '&#123;', '|' => '&#124;', '}' => '&#125;', ';' => '&#59;',
1674
			"\n#" => "\n&#35;", "\r#" => "\r&#35;",
1675
			"\n*" => "\n&#42;", "\r*" => "\r&#42;",
1676
			"\n:" => "\n&#58;", "\r:" => "\r&#58;",
1677
			"\n " => "\n&#32;", "\r " => "\r&#32;",
1678
			"\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
1679
			"\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
1680
			"\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
1681
			"\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
1682
			'__' => '_&#95;', '://' => '&#58;//',
1683
		];
1684
1685
		// We have to catch everything "\s" matches in PCRE
1686
		foreach ( [ 'ISBN', 'RFC', 'PMID' ] as $magic ) {
1687
			$repl["$magic "] = "$magic&#32;";
1688
			$repl["$magic\t"] = "$magic&#9;";
1689
			$repl["$magic\r"] = "$magic&#13;";
1690
			$repl["$magic\n"] = "$magic&#10;";
1691
			$repl["$magic\f"] = "$magic&#12;";
1692
		}
1693
1694
		// And handle protocols that don't use "://"
1695
		global $wgUrlProtocols;
1696
		$repl2 = [];
1697
		foreach ( $wgUrlProtocols as $prot ) {
1698
			if ( substr( $prot, -1 ) === ':' ) {
1699
				$repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
1700
			}
1701
		}
1702
		$repl2 = $repl2 ? '/\b(' . implode( '|', $repl2 ) . '):/i' : '/^(?!)/';
1703
	}
1704
	$text = substr( strtr( "\n$text", $repl ), 1 );
0 ignored issues
show
Bug introduced by
It seems like $repl can also be of type null; however, strtr() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1705
	$text = preg_replace( $repl2, '$1&#58;', $text );
1706
	return $text;
1707
}
1708
1709
/**
1710
 * Sets dest to source and returns the original value of dest
1711
 * If source is NULL, it just returns the value, it doesn't set the variable
1712
 * If force is true, it will set the value even if source is NULL
1713
 *
1714
 * @param mixed $dest
1715
 * @param mixed $source
1716
 * @param bool $force
1717
 * @return mixed
1718
 */
1719
function wfSetVar( &$dest, $source, $force = false ) {
1720
	$temp = $dest;
1721
	if ( !is_null( $source ) || $force ) {
1722
		$dest = $source;
1723
	}
1724
	return $temp;
1725
}
1726
1727
/**
1728
 * As for wfSetVar except setting a bit
1729
 *
1730
 * @param int $dest
1731
 * @param int $bit
1732
 * @param bool $state
1733
 *
1734
 * @return bool
1735
 */
1736
function wfSetBit( &$dest, $bit, $state = true ) {
1737
	$temp = (bool)( $dest & $bit );
1738
	if ( !is_null( $state ) ) {
1739
		if ( $state ) {
1740
			$dest |= $bit;
1741
		} else {
1742
			$dest &= ~$bit;
1743
		}
1744
	}
1745
	return $temp;
1746
}
1747
1748
/**
1749
 * A wrapper around the PHP function var_export().
1750
 * Either print it or add it to the regular output ($wgOut).
1751
 *
1752
 * @param mixed $var A PHP variable to dump.
1753
 */
1754
function wfVarDump( $var ) {
1755
	global $wgOut;
1756
	$s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
1757
	if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
1758
		print $s;
1759
	} else {
1760
		$wgOut->addHTML( $s );
1761
	}
1762
}
1763
1764
/**
1765
 * Provide a simple HTTP error.
1766
 *
1767
 * @param int|string $code
1768
 * @param string $label
1769
 * @param string $desc
1770
 */
1771
function wfHttpError( $code, $label, $desc ) {
1772
	global $wgOut;
1773
	HttpStatus::header( $code );
1774
	if ( $wgOut ) {
1775
		$wgOut->disable();
1776
		$wgOut->sendCacheControl();
1777
	}
1778
1779
	header( 'Content-type: text/html; charset=utf-8' );
1780
	print '<!DOCTYPE html>' .
1781
		'<html><head><title>' .
1782
		htmlspecialchars( $label ) .
1783
		'</title></head><body><h1>' .
1784
		htmlspecialchars( $label ) .
1785
		'</h1><p>' .
1786
		nl2br( htmlspecialchars( $desc ) ) .
1787
		"</p></body></html>\n";
1788
}
1789
1790
/**
1791
 * Clear away any user-level output buffers, discarding contents.
1792
 *
1793
 * Suitable for 'starting afresh', for instance when streaming
1794
 * relatively large amounts of data without buffering, or wanting to
1795
 * output image files without ob_gzhandler's compression.
1796
 *
1797
 * The optional $resetGzipEncoding parameter controls suppression of
1798
 * the Content-Encoding header sent by ob_gzhandler; by default it
1799
 * is left. See comments for wfClearOutputBuffers() for why it would
1800
 * be used.
1801
 *
1802
 * Note that some PHP configuration options may add output buffer
1803
 * layers which cannot be removed; these are left in place.
1804
 *
1805
 * @param bool $resetGzipEncoding
1806
 */
1807
function wfResetOutputBuffers( $resetGzipEncoding = true ) {
1808
	if ( $resetGzipEncoding ) {
1809
		// Suppress Content-Encoding and Content-Length
1810
		// headers from 1.10+s wfOutputHandler
1811
		global $wgDisableOutputCompression;
1812
		$wgDisableOutputCompression = true;
1813
	}
1814
	while ( $status = ob_get_status() ) {
1815
		if ( isset( $status['flags'] ) ) {
1816
			$flags = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
1817
			$deleteable = ( $status['flags'] & $flags ) === $flags;
1818
		} elseif ( isset( $status['del'] ) ) {
1819
			$deleteable = $status['del'];
1820
		} else {
1821
			// Guess that any PHP-internal setting can't be removed.
1822
			$deleteable = $status['type'] !== 0; /* PHP_OUTPUT_HANDLER_INTERNAL */
1823
		}
1824
		if ( !$deleteable ) {
1825
			// Give up, and hope the result doesn't break
1826
			// output behavior.
1827
			break;
1828
		}
1829
		if ( $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) {
1830
			// Unit testing barrier to prevent this function from breaking PHPUnit.
1831
			break;
1832
		}
1833
		if ( !ob_end_clean() ) {
1834
			// Could not remove output buffer handler; abort now
1835
			// to avoid getting in some kind of infinite loop.
1836
			break;
1837
		}
1838
		if ( $resetGzipEncoding ) {
1839
			if ( $status['name'] == 'ob_gzhandler' ) {
1840
				// Reset the 'Content-Encoding' field set by this handler
1841
				// so we can start fresh.
1842
				header_remove( 'Content-Encoding' );
1843
				break;
1844
			}
1845
		}
1846
	}
1847
}
1848
1849
/**
1850
 * More legible than passing a 'false' parameter to wfResetOutputBuffers():
1851
 *
1852
 * Clear away output buffers, but keep the Content-Encoding header
1853
 * produced by ob_gzhandler, if any.
1854
 *
1855
 * This should be used for HTTP 304 responses, where you need to
1856
 * preserve the Content-Encoding header of the real result, but
1857
 * also need to suppress the output of ob_gzhandler to keep to spec
1858
 * and avoid breaking Firefox in rare cases where the headers and
1859
 * body are broken over two packets.
1860
 */
1861
function wfClearOutputBuffers() {
1862
	wfResetOutputBuffers( false );
1863
}
1864
1865
/**
1866
 * Converts an Accept-* header into an array mapping string values to quality
1867
 * factors
1868
 *
1869
 * @param string $accept
1870
 * @param string $def Default
1871
 * @return float[] Associative array of string => float pairs
1872
 */
1873
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
1874
	# No arg means accept anything (per HTTP spec)
1875
	if ( !$accept ) {
1876
		return [ $def => 1.0 ];
1877
	}
1878
1879
	$prefs = [];
1880
1881
	$parts = explode( ',', $accept );
1882
1883
	foreach ( $parts as $part ) {
1884
		# @todo FIXME: Doesn't deal with params like 'text/html; level=1'
1885
		$values = explode( ';', trim( $part ) );
1886
		$match = [];
1887
		if ( count( $values ) == 1 ) {
1888
			$prefs[$values[0]] = 1.0;
1889
		} elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
1890
			$prefs[$values[0]] = floatval( $match[1] );
1891
		}
1892
	}
1893
1894
	return $prefs;
1895
}
1896
1897
/**
1898
 * Checks if a given MIME type matches any of the keys in the given
1899
 * array. Basic wildcards are accepted in the array keys.
1900
 *
1901
 * Returns the matching MIME type (or wildcard) if a match, otherwise
1902
 * NULL if no match.
1903
 *
1904
 * @param string $type
1905
 * @param array $avail
1906
 * @return string
1907
 * @private
1908
 */
1909
function mimeTypeMatch( $type, $avail ) {
1910
	if ( array_key_exists( $type, $avail ) ) {
1911
		return $type;
1912
	} else {
1913
		$mainType = explode( '/', $type )[0];
1914
		if ( array_key_exists( "$mainType/*", $avail ) ) {
1915
			return "$mainType/*";
1916
		} elseif ( array_key_exists( '*/*', $avail ) ) {
1917
			return '*/*';
1918
		} else {
1919
			return null;
1920
		}
1921
	}
1922
}
1923
1924
/**
1925
 * Returns the 'best' match between a client's requested internet media types
1926
 * and the server's list of available types. Each list should be an associative
1927
 * array of type to preference (preference is a float between 0.0 and 1.0).
1928
 * Wildcards in the types are acceptable.
1929
 *
1930
 * @param array $cprefs Client's acceptable type list
1931
 * @param array $sprefs Server's offered types
1932
 * @return string
1933
 *
1934
 * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
1935
 * XXX: generalize to negotiate other stuff
1936
 */
1937
function wfNegotiateType( $cprefs, $sprefs ) {
1938
	$combine = [];
1939
1940 View Code Duplication
	foreach ( array_keys( $sprefs ) as $type ) {
1941
		$subType = explode( '/', $type )[1];
1942
		if ( $subType != '*' ) {
1943
			$ckey = mimeTypeMatch( $type, $cprefs );
1944
			if ( $ckey ) {
1945
				$combine[$type] = $sprefs[$type] * $cprefs[$ckey];
1946
			}
1947
		}
1948
	}
1949
1950 View Code Duplication
	foreach ( array_keys( $cprefs ) as $type ) {
1951
		$subType = explode( '/', $type )[1];
1952
		if ( $subType != '*' && !array_key_exists( $type, $sprefs ) ) {
1953
			$skey = mimeTypeMatch( $type, $sprefs );
1954
			if ( $skey ) {
1955
				$combine[$type] = $sprefs[$skey] * $cprefs[$type];
1956
			}
1957
		}
1958
	}
1959
1960
	$bestq = 0;
1961
	$besttype = null;
1962
1963
	foreach ( array_keys( $combine ) as $type ) {
1964
		if ( $combine[$type] > $bestq ) {
1965
			$besttype = $type;
1966
			$bestq = $combine[$type];
1967
		}
1968
	}
1969
1970
	return $besttype;
1971
}
1972
1973
/**
1974
 * Reference-counted warning suppression
1975
 *
1976
 * @deprecated since 1.26, use MediaWiki\suppressWarnings() directly
1977
 * @param bool $end
1978
 */
1979
function wfSuppressWarnings( $end = false ) {
1980
	MediaWiki\suppressWarnings( $end );
1981
}
1982
1983
/**
1984
 * @deprecated since 1.26, use MediaWiki\restoreWarnings() directly
1985
 * Restore error level to previous value
1986
 */
1987
function wfRestoreWarnings() {
1988
	MediaWiki\suppressWarnings( true );
1989
}
1990
1991
# Autodetect, convert and provide timestamps of various types
1992
1993
/**
1994
 * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
1995
 */
1996
define( 'TS_UNIX', 0 );
1997
1998
/**
1999
 * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
2000
 */
2001
define( 'TS_MW', 1 );
2002
2003
/**
2004
 * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
2005
 */
2006
define( 'TS_DB', 2 );
2007
2008
/**
2009
 * RFC 2822 format, for E-mail and HTTP headers
2010
 */
2011
define( 'TS_RFC2822', 3 );
2012
2013
/**
2014
 * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
2015
 *
2016
 * This is used by Special:Export
2017
 */
2018
define( 'TS_ISO_8601', 4 );
2019
2020
/**
2021
 * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
2022
 *
2023
 * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
2024
 *       DateTime tag and page 36 for the DateTimeOriginal and
2025
 *       DateTimeDigitized tags.
2026
 */
2027
define( 'TS_EXIF', 5 );
2028
2029
/**
2030
 * Oracle format time.
2031
 */
2032
define( 'TS_ORACLE', 6 );
2033
2034
/**
2035
 * Postgres format time.
2036
 */
2037
define( 'TS_POSTGRES', 7 );
2038
2039
/**
2040
 * ISO 8601 basic format with no timezone: 19860209T200000Z.  This is used by ResourceLoader
2041
 */
2042
define( 'TS_ISO_8601_BASIC', 9 );
2043
2044
/**
2045
 * Get a timestamp string in one of various formats
2046
 *
2047
 * @param mixed $outputtype A timestamp in one of the supported formats, the
2048
 *   function will autodetect which format is supplied and act accordingly.
2049
 * @param mixed $ts Optional timestamp to convert, default 0 for the current time
2050
 * @return string|bool String / false The same date in the format specified in $outputtype or false
2051
 */
2052
function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
2053
	try {
2054
		$timestamp = new MWTimestamp( $ts );
2055
		return $timestamp->getTimestamp( $outputtype );
2056
	} catch ( TimestampException $e ) {
2057
		wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
2058
		return false;
2059
	}
2060
}
2061
2062
/**
2063
 * Return a formatted timestamp, or null if input is null.
2064
 * For dealing with nullable timestamp columns in the database.
2065
 *
2066
 * @param int $outputtype
2067
 * @param string $ts
2068
 * @return string
2069
 */
2070
function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
2071
	if ( is_null( $ts ) ) {
2072
		return null;
2073
	} else {
2074
		return wfTimestamp( $outputtype, $ts );
2075
	}
2076
}
2077
2078
/**
2079
 * Convenience function; returns MediaWiki timestamp for the present time.
2080
 *
2081
 * @return string
2082
 */
2083
function wfTimestampNow() {
2084
	# return NOW
2085
	return wfTimestamp( TS_MW, time() );
2086
}
2087
2088
/**
2089
 * Check if the operating system is Windows
2090
 *
2091
 * @return bool True if it's Windows, false otherwise.
2092
 */
2093
function wfIsWindows() {
2094
	static $isWindows = null;
2095
	if ( $isWindows === null ) {
2096
		$isWindows = strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN';
2097
	}
2098
	return $isWindows;
2099
}
2100
2101
/**
2102
 * Check if we are running under HHVM
2103
 *
2104
 * @return bool
2105
 */
2106
function wfIsHHVM() {
2107
	return defined( 'HHVM_VERSION' );
2108
}
2109
2110
/**
2111
 * Tries to get the system directory for temporary files. First
2112
 * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
2113
 * environment variables are then checked in sequence, then
2114
 * sys_get_temp_dir(), then upload_tmp_dir from php.ini.
2115
 *
2116
 * NOTE: When possible, use instead the tmpfile() function to create
2117
 * temporary files to avoid race conditions on file creation, etc.
2118
 *
2119
 * @return string
2120
 */
2121
function wfTempDir() {
2122
	global $wgTmpDirectory;
2123
2124
	if ( $wgTmpDirectory !== false ) {
2125
		return $wgTmpDirectory;
2126
	}
2127
2128
	$tmpDir = array_map( "getenv", [ 'TMPDIR', 'TMP', 'TEMP' ] );
2129
	$tmpDir[] = sys_get_temp_dir();
2130
	$tmpDir[] = ini_get( 'upload_tmp_dir' );
2131
2132
	foreach ( $tmpDir as $tmp ) {
2133
		if ( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
2134
			return $tmp;
2135
		}
2136
	}
2137
2138
	/**
2139
	 * PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to it
2140
	 * so create a directory within that called 'mwtmp' with a suffix of the user running the
2141
	 * current process.
2142
	 * The user is included as if various scripts are run by different users they will likely
2143
	 * not be able to access each others temporary files.
2144
	 */
2145
	if ( wfIsWindows() ) {
2146
		$tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp' . '-' . get_current_user();
2147
		if ( !file_exists( $tmp ) ) {
2148
			mkdir( $tmp );
2149
		}
2150
		if ( file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
2151
			return $tmp;
2152
		}
2153
	}
2154
2155
	throw new MWException( 'No writable temporary directory could be found. ' .
2156
		'Please set $wgTmpDirectory to a writable directory.' );
2157
}
2158
2159
/**
2160
 * Make directory, and make all parent directories if they don't exist
2161
 *
2162
 * @param string $dir Full path to directory to create
2163
 * @param int $mode Chmod value to use, default is $wgDirectoryMode
2164
 * @param string $caller Optional caller param for debugging.
2165
 * @throws MWException
2166
 * @return bool
2167
 */
2168
function wfMkdirParents( $dir, $mode = null, $caller = null ) {
2169
	global $wgDirectoryMode;
2170
2171
	if ( FileBackend::isStoragePath( $dir ) ) { // sanity
2172
		throw new MWException( __FUNCTION__ . " given storage path '$dir'." );
2173
	}
2174
2175
	if ( !is_null( $caller ) ) {
2176
		wfDebug( "$caller: called wfMkdirParents($dir)\n" );
2177
	}
2178
2179
	if ( strval( $dir ) === '' || is_dir( $dir ) ) {
2180
		return true;
2181
	}
2182
2183
	$dir = str_replace( [ '\\', '/' ], DIRECTORY_SEPARATOR, $dir );
2184
2185
	if ( is_null( $mode ) ) {
2186
		$mode = $wgDirectoryMode;
2187
	}
2188
2189
	// Turn off the normal warning, we're doing our own below
2190
	MediaWiki\suppressWarnings();
2191
	$ok = mkdir( $dir, $mode, true ); // PHP5 <3
2192
	MediaWiki\restoreWarnings();
2193
2194
	if ( !$ok ) {
2195
		// directory may have been created on another request since we last checked
2196
		if ( is_dir( $dir ) ) {
2197
			return true;
2198
		}
2199
2200
		// PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
2201
		wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) );
2202
	}
2203
	return $ok;
2204
}
2205
2206
/**
2207
 * Remove a directory and all its content.
2208
 * Does not hide error.
2209
 * @param string $dir
2210
 */
2211
function wfRecursiveRemoveDir( $dir ) {
2212
	wfDebug( __FUNCTION__ . "( $dir )\n" );
2213
	// taken from http://de3.php.net/manual/en/function.rmdir.php#98622
2214
	if ( is_dir( $dir ) ) {
2215
		$objects = scandir( $dir );
2216
		foreach ( $objects as $object ) {
2217
			if ( $object != "." && $object != ".." ) {
2218
				if ( filetype( $dir . '/' . $object ) == "dir" ) {
2219
					wfRecursiveRemoveDir( $dir . '/' . $object );
2220
				} else {
2221
					unlink( $dir . '/' . $object );
2222
				}
2223
			}
2224
		}
2225
		reset( $objects );
2226
		rmdir( $dir );
2227
	}
2228
}
2229
2230
/**
2231
 * @param int $nr The number to format
2232
 * @param int $acc The number of digits after the decimal point, default 2
2233
 * @param bool $round Whether or not to round the value, default true
2234
 * @return string
2235
 */
2236
function wfPercent( $nr, $acc = 2, $round = true ) {
2237
	$ret = sprintf( "%.${acc}f", $nr );
2238
	return $round ? round( $ret, $acc ) . '%' : "$ret%";
2239
}
2240
2241
/**
2242
 * Safety wrapper around ini_get() for boolean settings.
2243
 * The values returned from ini_get() are pre-normalized for settings
2244
 * set via php.ini or php_flag/php_admin_flag... but *not*
2245
 * for those set via php_value/php_admin_value.
2246
 *
2247
 * It's fairly common for people to use php_value instead of php_flag,
2248
 * which can leave you with an 'off' setting giving a false positive
2249
 * for code that just takes the ini_get() return value as a boolean.
2250
 *
2251
 * To make things extra interesting, setting via php_value accepts
2252
 * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
2253
 * Unrecognized values go false... again opposite PHP's own coercion
2254
 * from string to bool.
2255
 *
2256
 * Luckily, 'properly' set settings will always come back as '0' or '1',
2257
 * so we only have to worry about them and the 'improper' settings.
2258
 *
2259
 * I frickin' hate PHP... :P
2260
 *
2261
 * @param string $setting
2262
 * @return bool
2263
 */
2264
function wfIniGetBool( $setting ) {
2265
	$val = strtolower( ini_get( $setting ) );
2266
	// 'on' and 'true' can't have whitespace around them, but '1' can.
2267
	return $val == 'on'
2268
		|| $val == 'true'
2269
		|| $val == 'yes'
2270
		|| preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
2271
}
2272
2273
/**
2274
 * Windows-compatible version of escapeshellarg()
2275
 * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
2276
 * function puts single quotes in regardless of OS.
2277
 *
2278
 * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
2279
 * earlier distro releases of PHP)
2280
 *
2281
 * @param string ... strings to escape and glue together, or a single array of strings parameter
2282
 * @return string
2283
 */
2284
function wfEscapeShellArg( /*...*/ ) {
2285
	wfInitShellLocale();
2286
2287
	$args = func_get_args();
2288
	if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
2289
		// If only one argument has been passed, and that argument is an array,
2290
		// treat it as a list of arguments
2291
		$args = reset( $args );
2292
	}
2293
2294
	$first = true;
2295
	$retVal = '';
2296
	foreach ( $args as $arg ) {
2297
		if ( !$first ) {
2298
			$retVal .= ' ';
2299
		} else {
2300
			$first = false;
2301
		}
2302
2303
		if ( wfIsWindows() ) {
2304
			// Escaping for an MSVC-style command line parser and CMD.EXE
2305
			// @codingStandardsIgnoreStart For long URLs
2306
			// Refs:
2307
			//  * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
2308
			//  * http://technet.microsoft.com/en-us/library/cc723564.aspx
2309
			//  * Bug #13518
2310
			//  * CR r63214
2311
			// Double the backslashes before any double quotes. Escape the double quotes.
2312
			// @codingStandardsIgnoreEnd
2313
			$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
2314
			$arg = '';
2315
			$iteration = 0;
2316
			foreach ( $tokens as $token ) {
2317
				if ( $iteration % 2 == 1 ) {
2318
					// Delimiter, a double quote preceded by zero or more slashes
2319
					$arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
2320
				} elseif ( $iteration % 4 == 2 ) {
2321
					// ^ in $token will be outside quotes, need to be escaped
2322
					$arg .= str_replace( '^', '^^', $token );
2323
				} else { // $iteration % 4 == 0
2324
					// ^ in $token will appear inside double quotes, so leave as is
2325
					$arg .= $token;
2326
				}
2327
				$iteration++;
2328
			}
2329
			// Double the backslashes before the end of the string, because
2330
			// we will soon add a quote
2331
			$m = [];
2332
			if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
2333
				$arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
2334
			}
2335
2336
			// Add surrounding quotes
2337
			$retVal .= '"' . $arg . '"';
2338
		} else {
2339
			$retVal .= escapeshellarg( $arg );
2340
		}
2341
	}
2342
	return $retVal;
2343
}
2344
2345
/**
2346
 * Check if wfShellExec() is effectively disabled via php.ini config
2347
 *
2348
 * @return bool|string False or 'disabled'
2349
 * @since 1.22
2350
 */
2351
function wfShellExecDisabled() {
2352
	static $disabled = null;
2353
	if ( is_null( $disabled ) ) {
2354
		if ( !function_exists( 'proc_open' ) ) {
2355
			wfDebug( "proc_open() is disabled\n" );
2356
			$disabled = 'disabled';
2357
		} else {
2358
			$disabled = false;
2359
		}
2360
	}
2361
	return $disabled;
2362
}
2363
2364
/**
2365
 * Execute a shell command, with time and memory limits mirrored from the PHP
2366
 * configuration if supported.
2367
 *
2368
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2369
 *   or an array of unescaped arguments, in which case each value will be escaped
2370
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2371
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2372
 *   (non-zero is usually failure). If there is an error from
2373
 *   read, select, or proc_open(), this will be set to -1.
2374
 * @param array $environ Optional environment variables which should be
2375
 *   added to the executed command environment.
2376
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2377
 *   this overwrites the global wgMaxShell* limits.
2378
 * @param array $options Array of options:
2379
 *   - duplicateStderr: Set this to true to duplicate stderr to stdout,
2380
 *     including errors from limit.sh
2381
 *   - profileMethod: By default this function will profile based on the calling
2382
 *     method. Set this to a string for an alternative method to profile from
2383
 *
2384
 * @return string Collected stdout as a string
2385
 */
2386
function wfShellExec( $cmd, &$retval = null, $environ = [],
2387
	$limits = [], $options = []
2388
) {
2389
	global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
2390
		$wgMaxShellWallClockTime, $wgShellCgroup;
2391
2392
	$disabled = wfShellExecDisabled();
2393
	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...
2394
		$retval = 1;
2395
		return 'Unable to run external programs, proc_open() is disabled.';
2396
	}
2397
2398
	$includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
2399
	$profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller();
2400
2401
	wfInitShellLocale();
2402
2403
	$envcmd = '';
2404
	foreach ( $environ as $k => $v ) {
2405
		if ( wfIsWindows() ) {
2406
			/* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
2407
			 * appear in the environment variable, so we must use carat escaping as documented in
2408
			 * http://technet.microsoft.com/en-us/library/cc723564.aspx
2409
			 * Note however that the quote isn't listed there, but is needed, and the parentheses
2410
			 * are listed there but doesn't appear to need it.
2411
			 */
2412
			$envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
2413
		} else {
2414
			/* Assume this is a POSIX shell, thus required to accept variable assignments before the command
2415
			 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
2416
			 */
2417
			$envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
2418
		}
2419
	}
2420
	if ( is_array( $cmd ) ) {
2421
		$cmd = wfEscapeShellArg( $cmd );
2422
	}
2423
2424
	$cmd = $envcmd . $cmd;
2425
2426
	$useLogPipe = false;
2427
	if ( is_executable( '/bin/bash' ) ) {
2428
		$time = intval( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
2429
		if ( isset( $limits['walltime'] ) ) {
2430
			$wallTime = intval( $limits['walltime'] );
2431
		} elseif ( isset( $limits['time'] ) ) {
2432
			$wallTime = $time;
2433
		} else {
2434
			$wallTime = intval( $wgMaxShellWallClockTime );
2435
		}
2436
		$mem = intval( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
2437
		$filesize = intval( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
2438
2439
		if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
2440
			$cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
2441
				escapeshellarg( $cmd ) . ' ' .
2442
				escapeshellarg(
2443
					"MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
2444
					"MW_CPU_LIMIT=$time; " .
2445
					'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
2446
					"MW_MEM_LIMIT=$mem; " .
2447
					"MW_FILE_SIZE_LIMIT=$filesize; " .
2448
					"MW_WALL_CLOCK_LIMIT=$wallTime; " .
2449
					"MW_USE_LOG_PIPE=yes"
2450
				);
2451
			$useLogPipe = true;
2452
		} elseif ( $includeStderr ) {
2453
			$cmd .= ' 2>&1';
2454
		}
2455
	} elseif ( $includeStderr ) {
2456
		$cmd .= ' 2>&1';
2457
	}
2458
	wfDebug( "wfShellExec: $cmd\n" );
2459
2460
	$desc = [
2461
		0 => [ 'file', 'php://stdin', 'r' ],
2462
		1 => [ 'pipe', 'w' ],
2463
		2 => [ 'file', 'php://stderr', 'w' ] ];
2464
	if ( $useLogPipe ) {
2465
		$desc[3] = [ 'pipe', 'w' ];
2466
	}
2467
	$pipes = null;
2468
	$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...
2469
	$proc = proc_open( $cmd, $desc, $pipes );
2470
	if ( !$proc ) {
2471
		wfDebugLog( 'exec', "proc_open() failed: $cmd" );
2472
		$retval = -1;
2473
		return '';
2474
	}
2475
	$outBuffer = $logBuffer = '';
2476
	$emptyArray = [];
2477
	$status = false;
2478
	$logMsg = false;
2479
2480
	/* According to the documentation, it is possible for stream_select()
2481
	 * to fail due to EINTR. I haven't managed to induce this in testing
2482
	 * despite sending various signals. If it did happen, the error
2483
	 * message would take the form:
2484
	 *
2485
	 * stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
2486
	 *
2487
	 * where [4] is the value of the macro EINTR and "Interrupted system
2488
	 * call" is string which according to the Linux manual is "possibly"
2489
	 * localised according to LC_MESSAGES.
2490
	 */
2491
	$eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
2492
	$eintrMessage = "stream_select(): unable to select [$eintr]";
2493
2494
	// Build a table mapping resource IDs to pipe FDs to work around a
2495
	// PHP 5.3 issue in which stream_select() does not preserve array keys
2496
	// <https://bugs.php.net/bug.php?id=53427>.
2497
	$fds = [];
2498
	foreach ( $pipes as $fd => $pipe ) {
2499
		$fds[(int)$pipe] = $fd;
2500
	}
2501
2502
	$running = true;
2503
	$timeout = null;
2504
	$numReadyPipes = 0;
2505
2506
	while ( $running === true || $numReadyPipes !== 0 ) {
2507
		if ( $running ) {
2508
			$status = proc_get_status( $proc );
2509
			// If the process has terminated, switch to nonblocking selects
2510
			// for getting any data still waiting to be read.
2511
			if ( !$status['running'] ) {
2512
				$running = false;
2513
				$timeout = 0;
2514
			}
2515
		}
2516
2517
		$readyPipes = $pipes;
2518
2519
		// Clear last error
2520
		// @codingStandardsIgnoreStart Generic.PHP.NoSilencedErrors.Discouraged
2521
		@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...
2522
		$numReadyPipes = @stream_select( $readyPipes, $emptyArray, $emptyArray, $timeout );
2523
		if ( $numReadyPipes === false ) {
2524
			// @codingStandardsIgnoreEnd
2525
			$error = error_get_last();
2526
			if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
2527
				continue;
2528
			} else {
2529
				trigger_error( $error['message'], E_USER_WARNING );
2530
				$logMsg = $error['message'];
2531
				break;
2532
			}
2533
		}
2534
		foreach ( $readyPipes as $pipe ) {
2535
			$block = fread( $pipe, 65536 );
2536
			$fd = $fds[(int)$pipe];
2537
			if ( $block === '' ) {
2538
				// End of file
2539
				fclose( $pipes[$fd] );
2540
				unset( $pipes[$fd] );
2541
				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...
2542
					break 2;
2543
				}
2544
			} elseif ( $block === false ) {
2545
				// Read error
2546
				$logMsg = "Error reading from pipe";
2547
				break 2;
2548
			} elseif ( $fd == 1 ) {
2549
				// From stdout
2550
				$outBuffer .= $block;
2551
			} elseif ( $fd == 3 ) {
2552
				// From log FD
2553
				$logBuffer .= $block;
2554
				if ( strpos( $block, "\n" ) !== false ) {
2555
					$lines = explode( "\n", $logBuffer );
2556
					$logBuffer = array_pop( $lines );
2557
					foreach ( $lines as $line ) {
2558
						wfDebugLog( 'exec', $line );
2559
					}
2560
				}
2561
			}
2562
		}
2563
	}
2564
2565
	foreach ( $pipes as $pipe ) {
2566
		fclose( $pipe );
2567
	}
2568
2569
	// Use the status previously collected if possible, since proc_get_status()
2570
	// just calls waitpid() which will not return anything useful the second time.
2571
	if ( $running ) {
2572
		$status = proc_get_status( $proc );
2573
	}
2574
2575
	if ( $logMsg !== false ) {
2576
		// Read/select error
2577
		$retval = -1;
2578
		proc_close( $proc );
2579
	} elseif ( $status['signaled'] ) {
2580
		$logMsg = "Exited with signal {$status['termsig']}";
2581
		$retval = 128 + $status['termsig'];
2582
		proc_close( $proc );
2583
	} else {
2584
		if ( $status['running'] ) {
2585
			$retval = proc_close( $proc );
2586
		} else {
2587
			$retval = $status['exitcode'];
2588
			proc_close( $proc );
2589
		}
2590
		if ( $retval == 127 ) {
2591
			$logMsg = "Possibly missing executable file";
2592
		} elseif ( $retval >= 129 && $retval <= 192 ) {
2593
			$logMsg = "Probably exited with signal " . ( $retval - 128 );
2594
		}
2595
	}
2596
2597
	if ( $logMsg !== false ) {
2598
		wfDebugLog( 'exec', "$logMsg: $cmd" );
2599
	}
2600
2601
	return $outBuffer;
2602
}
2603
2604
/**
2605
 * Execute a shell command, returning both stdout and stderr. Convenience
2606
 * function, as all the arguments to wfShellExec can become unwieldy.
2607
 *
2608
 * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
2609
 * @param string|string[] $cmd If string, a properly shell-escaped command line,
2610
 *   or an array of unescaped arguments, in which case each value will be escaped
2611
 *   Example:   [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
2612
 * @param null|mixed &$retval Optional, will receive the program's exit code.
2613
 *   (non-zero is usually failure)
2614
 * @param array $environ Optional environment variables which should be
2615
 *   added to the executed command environment.
2616
 * @param array $limits Optional array with limits(filesize, memory, time, walltime)
2617
 *   this overwrites the global wgMaxShell* limits.
2618
 * @return string Collected stdout and stderr as a string
2619
 */
2620
function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = [] ) {
2621
	return wfShellExec( $cmd, $retval, $environ, $limits,
2622
		[ 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ] );
2623
}
2624
2625
/**
2626
 * Workaround for http://bugs.php.net/bug.php?id=45132
2627
 * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
2628
 */
2629
function wfInitShellLocale() {
2630
	static $done = false;
2631
	if ( $done ) {
2632
		return;
2633
	}
2634
	$done = true;
2635
	global $wgShellLocale;
2636
	putenv( "LC_CTYPE=$wgShellLocale" );
2637
	setlocale( LC_CTYPE, $wgShellLocale );
2638
}
2639
2640
/**
2641
 * Generate a shell-escaped command line string to run a MediaWiki cli script.
2642
 * Note that $parameters should be a flat array and an option with an argument
2643
 * should consist of two consecutive items in the array (do not use "--option value").
2644
 *
2645
 * @param string $script MediaWiki cli script path
2646
 * @param array $parameters Arguments and options to the script
2647
 * @param array $options Associative array of options:
2648
 * 		'php': The path to the php executable
2649
 * 		'wrapper': Path to a PHP wrapper to handle the maintenance script
2650
 * @return string
2651
 */
2652
function wfShellWikiCmd( $script, array $parameters = [], array $options = [] ) {
2653
	global $wgPhpCli;
2654
	// Give site config file a chance to run the script in a wrapper.
2655
	// The caller may likely want to call wfBasename() on $script.
2656
	Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
2657
	$cmd = isset( $options['php'] ) ? [ $options['php'] ] : [ $wgPhpCli ];
2658
	if ( isset( $options['wrapper'] ) ) {
2659
		$cmd[] = $options['wrapper'];
2660
	}
2661
	$cmd[] = $script;
2662
	// Escape each parameter for shell
2663
	return wfEscapeShellArg( array_merge( $cmd, $parameters ) );
2664
}
2665
2666
/**
2667
 * wfMerge attempts to merge differences between three texts.
2668
 * Returns true for a clean merge and false for failure or a conflict.
2669
 *
2670
 * @param string $old
2671
 * @param string $mine
2672
 * @param string $yours
2673
 * @param string $result
2674
 * @return bool
2675
 */
2676
function wfMerge( $old, $mine, $yours, &$result ) {
2677
	global $wgDiff3;
2678
2679
	# This check may also protect against code injection in
2680
	# case of broken installations.
2681
	MediaWiki\suppressWarnings();
2682
	$haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
2683
	MediaWiki\restoreWarnings();
2684
2685
	if ( !$haveDiff3 ) {
2686
		wfDebug( "diff3 not found\n" );
2687
		return false;
2688
	}
2689
2690
	# Make temporary files
2691
	$td = wfTempDir();
2692
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2693
	$mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
2694
	$yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
2695
2696
	# NOTE: diff3 issues a warning to stderr if any of the files does not end with
2697
	#       a newline character. To avoid this, we normalize the trailing whitespace before
2698
	#       creating the diff.
2699
2700
	fwrite( $oldtextFile, rtrim( $old ) . "\n" );
2701
	fclose( $oldtextFile );
2702
	fwrite( $mytextFile, rtrim( $mine ) . "\n" );
2703
	fclose( $mytextFile );
2704
	fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
2705
	fclose( $yourtextFile );
2706
2707
	# Check for a conflict
2708
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName,
2709
		$oldtextName, $yourtextName );
2710
	$handle = popen( $cmd, 'r' );
2711
2712
	if ( fgets( $handle, 1024 ) ) {
2713
		$conflict = true;
2714
	} else {
2715
		$conflict = false;
2716
	}
2717
	pclose( $handle );
2718
2719
	# Merge differences
2720
	$cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName,
2721
		$oldtextName, $yourtextName );
2722
	$handle = popen( $cmd, 'r' );
2723
	$result = '';
2724
	do {
2725
		$data = fread( $handle, 8192 );
2726
		if ( strlen( $data ) == 0 ) {
2727
			break;
2728
		}
2729
		$result .= $data;
2730
	} while ( true );
2731
	pclose( $handle );
2732
	unlink( $mytextName );
2733
	unlink( $oldtextName );
2734
	unlink( $yourtextName );
2735
2736
	if ( $result === '' && $old !== '' && !$conflict ) {
2737
		wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
2738
		$conflict = true;
2739
	}
2740
	return !$conflict;
2741
}
2742
2743
/**
2744
 * Returns unified plain-text diff of two texts.
2745
 * "Useful" for machine processing of diffs.
2746
 *
2747
 * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly
2748
 *
2749
 * @param string $before The text before the changes.
2750
 * @param string $after The text after the changes.
2751
 * @param string $params Command-line options for the diff command.
2752
 * @return string Unified diff of $before and $after
2753
 */
2754
function wfDiff( $before, $after, $params = '-u' ) {
2755
	if ( $before == $after ) {
2756
		return '';
2757
	}
2758
2759
	global $wgDiff;
2760
	MediaWiki\suppressWarnings();
2761
	$haveDiff = $wgDiff && file_exists( $wgDiff );
2762
	MediaWiki\restoreWarnings();
2763
2764
	# This check may also protect against code injection in
2765
	# case of broken installations.
2766
	if ( !$haveDiff ) {
2767
		wfDebug( "diff executable not found\n" );
2768
		$diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
2769
		$format = new UnifiedDiffFormatter();
2770
		return $format->format( $diffs );
2771
	}
2772
2773
	# Make temporary files
2774
	$td = wfTempDir();
2775
	$oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2776
	$newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
2777
2778
	fwrite( $oldtextFile, $before );
2779
	fclose( $oldtextFile );
2780
	fwrite( $newtextFile, $after );
2781
	fclose( $newtextFile );
2782
2783
	// Get the diff of the two files
2784
	$cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
2785
2786
	$h = popen( $cmd, 'r' );
2787
	if ( !$h ) {
2788
		unlink( $oldtextName );
2789
		unlink( $newtextName );
2790
		throw new Exception( __METHOD__ . '(): popen() failed' );
2791
	}
2792
2793
	$diff = '';
2794
2795
	do {
2796
		$data = fread( $h, 8192 );
2797
		if ( strlen( $data ) == 0 ) {
2798
			break;
2799
		}
2800
		$diff .= $data;
2801
	} while ( true );
2802
2803
	// Clean up
2804
	pclose( $h );
2805
	unlink( $oldtextName );
2806
	unlink( $newtextName );
2807
2808
	// Kill the --- and +++ lines. They're not useful.
2809
	$diff_lines = explode( "\n", $diff );
2810 View Code Duplication
	if ( isset( $diff_lines[0] ) && strpos( $diff_lines[0], '---' ) === 0 ) {
2811
		unset( $diff_lines[0] );
2812
	}
2813 View Code Duplication
	if ( isset( $diff_lines[1] ) && strpos( $diff_lines[1], '+++' ) === 0 ) {
2814
		unset( $diff_lines[1] );
2815
	}
2816
2817
	$diff = implode( "\n", $diff_lines );
2818
2819
	return $diff;
2820
}
2821
2822
/**
2823
 * This function works like "use VERSION" in Perl, the program will die with a
2824
 * backtrace if the current version of PHP is less than the version provided
2825
 *
2826
 * This is useful for extensions which due to their nature are not kept in sync
2827
 * with releases, and might depend on other versions of PHP than the main code
2828
 *
2829
 * Note: PHP might die due to parsing errors in some cases before it ever
2830
 *       manages to call this function, such is life
2831
 *
2832
 * @see perldoc -f use
2833
 *
2834
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2835
 * @throws MWException
2836
 */
2837
function wfUsePHP( $req_ver ) {
2838
	$php_ver = PHP_VERSION;
2839
2840
	if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
2841
		throw new MWException( "PHP $req_ver required--this is only $php_ver" );
2842
	}
2843
}
2844
2845
/**
2846
 * This function works like "use VERSION" in Perl except it checks the version
2847
 * of MediaWiki, the program will die with a backtrace if the current version
2848
 * of MediaWiki is less than the version provided.
2849
 *
2850
 * This is useful for extensions which due to their nature are not kept in sync
2851
 * with releases
2852
 *
2853
 * Note: Due to the behavior of PHP's version_compare() which is used in this
2854
 * function, if you want to allow the 'wmf' development versions add a 'c' (or
2855
 * any single letter other than 'a', 'b' or 'p') as a post-fix to your
2856
 * targeted version number. For example if you wanted to allow any variation
2857
 * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
2858
 * not result in the same comparison due to the internal logic of
2859
 * version_compare().
2860
 *
2861
 * @see perldoc -f use
2862
 *
2863
 * @deprecated since 1.26, use the "requires' property of extension.json
2864
 * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
2865
 * @throws MWException
2866
 */
2867
function wfUseMW( $req_ver ) {
2868
	global $wgVersion;
2869
2870
	if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
2871
		throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
2872
	}
2873
}
2874
2875
/**
2876
 * Return the final portion of a pathname.
2877
 * Reimplemented because PHP5's "basename()" is buggy with multibyte text.
2878
 * http://bugs.php.net/bug.php?id=33898
2879
 *
2880
 * PHP's basename() only considers '\' a pathchar on Windows and Netware.
2881
 * We'll consider it so always, as we don't want '\s' in our Unix paths either.
2882
 *
2883
 * @param string $path
2884
 * @param string $suffix String to remove if present
2885
 * @return string
2886
 */
2887
function wfBaseName( $path, $suffix = '' ) {
2888
	if ( $suffix == '' ) {
2889
		$encSuffix = '';
2890
	} else {
2891
		$encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
2892
	}
2893
2894
	$matches = [];
2895
	if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
2896
		return $matches[1];
2897
	} else {
2898
		return '';
2899
	}
2900
}
2901
2902
/**
2903
 * Generate a relative path name to the given file.
2904
 * May explode on non-matching case-insensitive paths,
2905
 * funky symlinks, etc.
2906
 *
2907
 * @param string $path Absolute destination path including target filename
2908
 * @param string $from Absolute source path, directory only
2909
 * @return string
2910
 */
2911
function wfRelativePath( $path, $from ) {
2912
	// Normalize mixed input on Windows...
2913
	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
2914
	$from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
2915
2916
	// Trim trailing slashes -- fix for drive root
2917
	$path = rtrim( $path, DIRECTORY_SEPARATOR );
2918
	$from = rtrim( $from, DIRECTORY_SEPARATOR );
2919
2920
	$pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
2921
	$against = explode( DIRECTORY_SEPARATOR, $from );
2922
2923
	if ( $pieces[0] !== $against[0] ) {
2924
		// Non-matching Windows drive letters?
2925
		// Return a full path.
2926
		return $path;
2927
	}
2928
2929
	// Trim off common prefix
2930
	while ( count( $pieces ) && count( $against )
2931
		&& $pieces[0] == $against[0] ) {
2932
		array_shift( $pieces );
2933
		array_shift( $against );
2934
	}
2935
2936
	// relative dots to bump us to the parent
2937
	while ( count( $against ) ) {
2938
		array_unshift( $pieces, '..' );
2939
		array_shift( $against );
2940
	}
2941
2942
	array_push( $pieces, wfBaseName( $path ) );
2943
2944
	return implode( DIRECTORY_SEPARATOR, $pieces );
2945
}
2946
2947
/**
2948
 * Convert an arbitrarily-long digit string from one numeric base
2949
 * to another, optionally zero-padding to a minimum column width.
2950
 *
2951
 * Supports base 2 through 36; digit values 10-36 are represented
2952
 * as lowercase letters a-z. Input is case-insensitive.
2953
 *
2954
 * @deprecated 1.27 Use Wikimedia\base_convert() directly
2955
 *
2956
 * @param string $input Input number
2957
 * @param int $sourceBase Base of the input number
2958
 * @param int $destBase Desired base of the output
2959
 * @param int $pad Minimum number of digits in the output (pad with zeroes)
2960
 * @param bool $lowercase Whether to output in lowercase or uppercase
2961
 * @param string $engine Either "gmp", "bcmath", or "php"
2962
 * @return string|bool The output number as a string, or false on error
2963
 */
2964
function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1,
2965
	$lowercase = true, $engine = 'auto'
2966
) {
2967
	return Wikimedia\base_convert( $input, $sourceBase, $destBase, $pad, $lowercase, $engine );
2968
}
2969
2970
/**
2971
 * @deprecated since 1.27, PHP's session generation isn't used with
2972
 *  MediaWiki\Session\SessionManager
2973
 */
2974
function wfFixSessionID() {
2975
	wfDeprecated( __FUNCTION__, '1.27' );
2976
}
2977
2978
/**
2979
 * Reset the session id
2980
 *
2981
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead
2982
 * @since 1.22
2983
 */
2984
function wfResetSessionID() {
2985
	wfDeprecated( __FUNCTION__, '1.27' );
2986
	$session = SessionManager::getGlobalSession();
2987
	$delay = $session->delaySave();
2988
2989
	$session->resetId();
2990
2991
	// Make sure a session is started, since that's what the old
2992
	// wfResetSessionID() did.
2993
	if ( session_id() !== $session->getId() ) {
2994
		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...
2995
	}
2996
2997
	ScopedCallback::consume( $delay );
2998
}
2999
3000
/**
3001
 * Initialise php session
3002
 *
3003
 * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead.
3004
 *  Generally, "using" SessionManager will be calling ->getSessionById() or
3005
 *  ::getGlobalSession() (depending on whether you were passing $sessionId
3006
 *  here), then calling $session->persist().
3007
 * @param bool|string $sessionId
3008
 */
3009
function wfSetupSession( $sessionId = false ) {
3010
	wfDeprecated( __FUNCTION__, '1.27' );
3011
3012
	if ( $sessionId ) {
3013
		session_id( $sessionId );
3014
	}
3015
3016
	$session = SessionManager::getGlobalSession();
3017
	$session->persist();
3018
3019
	if ( session_id() !== $session->getId() ) {
3020
		session_id( $session->getId() );
3021
	}
3022
	MediaWiki\quietCall( 'session_start' );
3023
}
3024
3025
/**
3026
 * Get an object from the precompiled serialized directory
3027
 *
3028
 * @param string $name
3029
 * @return mixed The variable on success, false on failure
3030
 */
3031
function wfGetPrecompiledData( $name ) {
3032
	global $IP;
3033
3034
	$file = "$IP/serialized/$name";
3035
	if ( file_exists( $file ) ) {
3036
		$blob = file_get_contents( $file );
3037
		if ( $blob ) {
3038
			return unserialize( $blob );
3039
		}
3040
	}
3041
	return false;
3042
}
3043
3044
/**
3045
 * Make a cache key for the local wiki.
3046
 *
3047
 * @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...
3048
 * @return string
3049
 */
3050
function wfMemcKey( /*...*/ ) {
3051
	return call_user_func_array(
3052
		[ ObjectCache::getLocalClusterInstance(), 'makeKey' ],
3053
		func_get_args()
3054
	);
3055
}
3056
3057
/**
3058
 * Make a cache key for a foreign DB.
3059
 *
3060
 * Must match what wfMemcKey() would produce in context of the foreign wiki.
3061
 *
3062
 * @param string $db
3063
 * @param string $prefix
3064
 * @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...
3065
 * @return string
3066
 */
3067
function wfForeignMemcKey( $db, $prefix /*...*/ ) {
3068
	$args = array_slice( func_get_args(), 2 );
3069
	$keyspace = $prefix ? "$db-$prefix" : $db;
3070
	return call_user_func_array(
3071
		[ ObjectCache::getLocalClusterInstance(), 'makeKeyInternal' ],
3072
		[ $keyspace, $args ]
3073
	);
3074
}
3075
3076
/**
3077
 * Make a cache key with database-agnostic prefix.
3078
 *
3079
 * Doesn't have a wiki-specific namespace. Uses a generic 'global' prefix
3080
 * instead. Must have a prefix as otherwise keys that use a database name
3081
 * in the first segment will clash with wfMemcKey/wfForeignMemcKey.
3082
 *
3083
 * @since 1.26
3084
 * @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...
3085
 * @return string
3086
 */
3087
function wfGlobalCacheKey( /*...*/ ) {
3088
	return call_user_func_array(
3089
		[ ObjectCache::getLocalClusterInstance(), 'makeGlobalKey' ],
3090
		func_get_args()
3091
	);
3092
}
3093
3094
/**
3095
 * Get an ASCII string identifying this wiki
3096
 * This is used as a prefix in memcached keys
3097
 *
3098
 * @return string
3099
 */
3100
function wfWikiID() {
3101
	global $wgDBprefix, $wgDBname;
3102
	if ( $wgDBprefix ) {
3103
		return "$wgDBname-$wgDBprefix";
3104
	} else {
3105
		return $wgDBname;
3106
	}
3107
}
3108
3109
/**
3110
 * Split a wiki ID into DB name and table prefix
3111
 *
3112
 * @param string $wiki
3113
 *
3114
 * @return array
3115
 */
3116
function wfSplitWikiID( $wiki ) {
3117
	$bits = explode( '-', $wiki, 2 );
3118
	if ( count( $bits ) < 2 ) {
3119
		$bits[] = '';
3120
	}
3121
	return $bits;
3122
}
3123
3124
/**
3125
 * Get a Database object.
3126
 *
3127
 * @param int $db Index of the connection to get. May be DB_MASTER for the
3128
 *            master (for write queries), DB_SLAVE for potentially lagged read
3129
 *            queries, or an integer >= 0 for a particular server.
3130
 *
3131
 * @param string|string[] $groups Query groups. An array of group names that this query
3132
 *                belongs to. May contain a single string if the query is only
3133
 *                in one group.
3134
 *
3135
 * @param string|bool $wiki The wiki ID, or false for the current wiki
3136
 *
3137
 * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
3138
 * will always return the same object, unless the underlying connection or load
3139
 * balancer is manually destroyed.
3140
 *
3141
 * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
3142
 * updater to ensure that a proper database is being updated.
3143
 *
3144
 * @todo Replace calls to wfGetDB with calls to LoadBalancer::getConnection()
3145
 *       on an injected instance of LoadBalancer.
3146
 *
3147
 * @return DatabaseBase
3148
 */
3149
function wfGetDB( $db, $groups = [], $wiki = false ) {
3150
	return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or MediaWikiServices::getDBLoadBalancerFactory() instead.

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

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

Loading history...
3151
}
3152
3153
/**
3154
 * Get a load balancer object.
3155
 *
3156
 * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancer()
3157
 *              or MediaWikiServices::getDBLoadBalancerFactory() instead.
3158
 *
3159
 * @param string|bool $wiki Wiki ID, or false for the current wiki
3160
 * @return LoadBalancer
3161
 */
3162
function wfGetLB( $wiki = false ) {
3163
	if ( $wiki === false ) {
3164
		return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancer();
3165
	} else {
3166
		$factory = \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3167
		return $factory->getMainLB( $wiki );
3168
	}
3169
}
3170
3171
/**
3172
 * Get the load balancer factory object
3173
 *
3174
 * @deprecated since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.
3175
 *
3176
 * @return LBFactory
3177
 */
3178
function wfGetLBFactory() {
3179
	return \MediaWiki\MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
3180
}
3181
3182
/**
3183
 * Find a file.
3184
 * Shortcut for RepoGroup::singleton()->findFile()
3185
 *
3186
 * @param string $title String or Title object
3187
 * @param array $options Associative array of options (see RepoGroup::findFile)
3188
 * @return File|bool File, or false if the file does not exist
3189
 */
3190
function wfFindFile( $title, $options = [] ) {
3191
	return RepoGroup::singleton()->findFile( $title, $options );
3192
}
3193
3194
/**
3195
 * Get an object referring to a locally registered file.
3196
 * Returns a valid placeholder object if the file does not exist.
3197
 *
3198
 * @param Title|string $title
3199
 * @return LocalFile|null A File, or null if passed an invalid Title
3200
 */
3201
function wfLocalFile( $title ) {
3202
	return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
3203
}
3204
3205
/**
3206
 * Should low-performance queries be disabled?
3207
 *
3208
 * @return bool
3209
 * @codeCoverageIgnore
3210
 */
3211
function wfQueriesMustScale() {
3212
	global $wgMiserMode;
3213
	return $wgMiserMode
3214
		|| ( SiteStats::pages() > 100000
3215
		&& SiteStats::edits() > 1000000
3216
		&& SiteStats::users() > 10000 );
3217
}
3218
3219
/**
3220
 * Get the path to a specified script file, respecting file
3221
 * extensions; this is a wrapper around $wgScriptPath etc.
3222
 * except for 'index' and 'load' which use $wgScript/$wgLoadScript
3223
 *
3224
 * @param string $script Script filename, sans extension
3225
 * @return string
3226
 */
3227
function wfScript( $script = 'index' ) {
3228
	global $wgScriptPath, $wgScript, $wgLoadScript;
3229
	if ( $script === 'index' ) {
3230
		return $wgScript;
3231
	} elseif ( $script === 'load' ) {
3232
		return $wgLoadScript;
3233
	} else {
3234
		return "{$wgScriptPath}/{$script}.php";
3235
	}
3236
}
3237
3238
/**
3239
 * Get the script URL.
3240
 *
3241
 * @return string Script URL
3242
 */
3243
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...
3244
	if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
3245
		/* as it was called, minus the query string.
3246
		 *
3247
		 * Some sites use Apache rewrite rules to handle subdomains,
3248
		 * and have PHP set up in a weird way that causes PHP_SELF
3249
		 * to contain the rewritten URL instead of the one that the
3250
		 * outside world sees.
3251
		 *
3252
		 * If in this mode, use SCRIPT_URL instead, which mod_rewrite
3253
		 * provides containing the "before" URL.
3254
		 */
3255
		return $_SERVER['SCRIPT_NAME'];
3256
	} else {
3257
		return $_SERVER['URL'];
3258
	}
3259
}
3260
3261
/**
3262
 * Convenience function converts boolean values into "true"
3263
 * or "false" (string) values
3264
 *
3265
 * @param bool $value
3266
 * @return string
3267
 */
3268
function wfBoolToStr( $value ) {
3269
	return $value ? 'true' : 'false';
3270
}
3271
3272
/**
3273
 * Get a platform-independent path to the null file, e.g. /dev/null
3274
 *
3275
 * @return string
3276
 */
3277
function wfGetNull() {
3278
	return wfIsWindows() ? 'NUL' : '/dev/null';
3279
}
3280
3281
/**
3282
 * Waits for the slaves to catch up to the master position
3283
 *
3284
 * Use this when updating very large numbers of rows, as in maintenance scripts,
3285
 * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
3286
 *
3287
 * By default this waits on the main DB cluster of the current wiki.
3288
 * If $cluster is set to "*" it will wait on all DB clusters, including
3289
 * external ones. If the lag being waiting on is caused by the code that
3290
 * does this check, it makes since to use $ifWritesSince, particularly if
3291
 * cluster is "*", to avoid excess overhead.
3292
 *
3293
 * Never call this function after a big DB write that is still in a transaction.
3294
 * This only makes sense after the possible lag inducing changes were committed.
3295
 *
3296
 * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
3297
 * @param string|bool $wiki Wiki identifier accepted by wfGetLB
3298
 * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
3299
 * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web)
3300
 * @return bool Success (able to connect and no timeouts reached)
3301
 * @deprecated since 1.27 Use LBFactory::waitForReplication
3302
 */
3303
function wfWaitForSlaves(
3304
	$ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
3305
) {
3306
	if ( $timeout === null ) {
3307
		$timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10;
3308
	}
3309
3310
	if ( $cluster === '*' ) {
3311
		$cluster = false;
3312
		$wiki = false;
3313
	} elseif ( $wiki === false ) {
3314
		$wiki = wfWikiID();
3315
	}
3316
3317
	try {
3318
		wfGetLBFactory()->waitForReplication( [
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLBFactory() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.

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

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

Loading history...
3319
			'wiki' => $wiki,
3320
			'cluster' => $cluster,
3321
			'timeout' => $timeout,
3322
			// B/C: first argument used to be "max seconds of lag"; ignore such values
3323
			'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
3324
		] );
3325
	} catch ( DBReplicationWaitError $e ) {
3326
		return false;
3327
	}
3328
3329
	return true;
3330
}
3331
3332
/**
3333
 * Count down from $seconds to zero on the terminal, with a one-second pause
3334
 * between showing each number. For use in command-line scripts.
3335
 *
3336
 * @codeCoverageIgnore
3337
 * @param int $seconds
3338
 */
3339
function wfCountDown( $seconds ) {
3340
	for ( $i = $seconds; $i >= 0; $i-- ) {
3341
		if ( $i != $seconds ) {
3342
			echo str_repeat( "\x08", strlen( $i + 1 ) );
3343
		}
3344
		echo $i;
3345
		flush();
3346
		if ( $i ) {
3347
			sleep( 1 );
3348
		}
3349
	}
3350
	echo "\n";
3351
}
3352
3353
/**
3354
 * Replace all invalid characters with -
3355
 * Additional characters can be defined in $wgIllegalFileChars (see bug 20489)
3356
 * By default, $wgIllegalFileChars = ':'
3357
 *
3358
 * @param string $name Filename to process
3359
 * @return string
3360
 */
3361
function wfStripIllegalFilenameChars( $name ) {
3362
	global $wgIllegalFileChars;
3363
	$illegalFileChars = $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '';
3364
	$name = wfBaseName( $name );
3365
	$name = preg_replace(
3366
		"/[^" . Title::legalChars() . "]" . $illegalFileChars . "/",
3367
		'-',
3368
		$name
3369
	);
3370
	return $name;
3371
}
3372
3373
/**
3374
 * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit
3375
 *
3376
 * @return int Resulting value of the memory limit.
3377
 */
3378
function wfMemoryLimit() {
3379
	global $wgMemoryLimit;
3380
	$memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
3381
	if ( $memlimit != -1 ) {
3382
		$conflimit = wfShorthandToInteger( $wgMemoryLimit );
3383
		if ( $conflimit == -1 ) {
3384
			wfDebug( "Removing PHP's memory limit\n" );
3385
			MediaWiki\suppressWarnings();
3386
			ini_set( 'memory_limit', $conflimit );
3387
			MediaWiki\restoreWarnings();
3388
			return $conflimit;
3389
		} elseif ( $conflimit > $memlimit ) {
3390
			wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
3391
			MediaWiki\suppressWarnings();
3392
			ini_set( 'memory_limit', $conflimit );
3393
			MediaWiki\restoreWarnings();
3394
			return $conflimit;
3395
		}
3396
	}
3397
	return $memlimit;
3398
}
3399
3400
/**
3401
 * Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit
3402
 *
3403
 * @return int Prior time limit
3404
 * @since 1.26
3405
 */
3406
function wfTransactionalTimeLimit() {
3407
	global $wgTransactionalTimeLimit;
3408
3409
	$timeLimit = ini_get( 'max_execution_time' );
3410
	// Note that CLI scripts use 0
3411
	if ( $timeLimit > 0 && $wgTransactionalTimeLimit > $timeLimit ) {
3412
		set_time_limit( $wgTransactionalTimeLimit );
3413
	}
3414
3415
	ignore_user_abort( true ); // ignore client disconnects
3416
3417
	return $timeLimit;
3418
}
3419
3420
/**
3421
 * Converts shorthand byte notation to integer form
3422
 *
3423
 * @param string $string
3424
 * @param int $default Returned if $string is empty
3425
 * @return int
3426
 */
3427
function wfShorthandToInteger( $string = '', $default = -1 ) {
3428
	$string = trim( $string );
3429
	if ( $string === '' ) {
3430
		return $default;
3431
	}
3432
	$last = $string[strlen( $string ) - 1];
3433
	$val = intval( $string );
3434
	switch ( $last ) {
3435
		case 'g':
3436
		case 'G':
3437
			$val *= 1024;
3438
			// break intentionally missing
3439
		case 'm':
3440
		case 'M':
3441
			$val *= 1024;
3442
			// break intentionally missing
3443
		case 'k':
3444
		case 'K':
3445
			$val *= 1024;
3446
	}
3447
3448
	return $val;
3449
}
3450
3451
/**
3452
 * Get the normalised IETF language tag
3453
 * See unit test for examples.
3454
 *
3455
 * @param string $code The language code.
3456
 * @return string The language code which complying with BCP 47 standards.
3457
 */
3458
function wfBCP47( $code ) {
3459
	$codeSegment = explode( '-', $code );
3460
	$codeBCP = [];
3461
	foreach ( $codeSegment as $segNo => $seg ) {
3462
		// when previous segment is x, it is a private segment and should be lc
3463
		if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
3464
			$codeBCP[$segNo] = strtolower( $seg );
3465
		// ISO 3166 country code
3466
		} elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
3467
			$codeBCP[$segNo] = strtoupper( $seg );
3468
		// ISO 15924 script code
3469
		} elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
3470
			$codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
3471
		// Use lowercase for other cases
3472
		} else {
3473
			$codeBCP[$segNo] = strtolower( $seg );
3474
		}
3475
	}
3476
	$langCode = implode( '-', $codeBCP );
3477
	return $langCode;
3478
}
3479
3480
/**
3481
 * Get a specific cache object.
3482
 *
3483
 * @param int|string $cacheType A CACHE_* constants, or other key in $wgObjectCaches
3484
 * @return BagOStuff
3485
 */
3486
function wfGetCache( $cacheType ) {
3487
	return ObjectCache::getInstance( $cacheType );
3488
}
3489
3490
/**
3491
 * Get the main cache object
3492
 *
3493
 * @return BagOStuff
3494
 */
3495
function wfGetMainCache() {
3496
	global $wgMainCacheType;
3497
	return ObjectCache::getInstance( $wgMainCacheType );
3498
}
3499
3500
/**
3501
 * Get the cache object used by the message cache
3502
 *
3503
 * @return BagOStuff
3504
 */
3505
function wfGetMessageCacheStorage() {
3506
	global $wgMessageCacheType;
3507
	return ObjectCache::getInstance( $wgMessageCacheType );
3508
}
3509
3510
/**
3511
 * Get the cache object used by the parser cache
3512
 *
3513
 * @return BagOStuff
3514
 */
3515
function wfGetParserCacheStorage() {
3516
	global $wgParserCacheType;
3517
	return ObjectCache::getInstance( $wgParserCacheType );
3518
}
3519
3520
/**
3521
 * Call hook functions defined in $wgHooks
3522
 *
3523
 * @param string $event Event name
3524
 * @param array $args Parameters passed to hook functions
3525
 * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number
3526
 *
3527
 * @return bool True if no handler aborted the hook
3528
 * @deprecated 1.25 - use Hooks::run
3529
 */
3530
function wfRunHooks( $event, array $args = [], $deprecatedVersion = null ) {
3531
	return Hooks::run( $event, $args, $deprecatedVersion );
3532
}
3533
3534
/**
3535
 * Wrapper around php's unpack.
3536
 *
3537
 * @param string $format The format string (See php's docs)
3538
 * @param string $data A binary string of binary data
3539
 * @param int|bool $length The minimum length of $data or false. This is to
3540
 *	prevent reading beyond the end of $data. false to disable the check.
3541
 *
3542
 * Also be careful when using this function to read unsigned 32 bit integer
3543
 * because php might make it negative.
3544
 *
3545
 * @throws MWException If $data not long enough, or if unpack fails
3546
 * @return array Associative array of the extracted data
3547
 */
3548
function wfUnpack( $format, $data, $length = false ) {
3549
	if ( $length !== false ) {
3550
		$realLen = strlen( $data );
3551
		if ( $realLen < $length ) {
3552
			throw new MWException( "Tried to use wfUnpack on a "
3553
				. "string of length $realLen, but needed one "
3554
				. "of at least length $length."
3555
			);
3556
		}
3557
	}
3558
3559
	MediaWiki\suppressWarnings();
3560
	$result = unpack( $format, $data );
3561
	MediaWiki\restoreWarnings();
3562
3563
	if ( $result === false ) {
3564
		// If it cannot extract the packed data.
3565
		throw new MWException( "unpack could not unpack binary data" );
3566
	}
3567
	return $result;
3568
}
3569
3570
/**
3571
 * Determine if an image exists on the 'bad image list'.
3572
 *
3573
 * The format of MediaWiki:Bad_image_list is as follows:
3574
 *    * Only list items (lines starting with "*") are considered
3575
 *    * The first link on a line must be a link to a bad image
3576
 *    * Any subsequent links on the same line are considered to be exceptions,
3577
 *      i.e. articles where the image may occur inline.
3578
 *
3579
 * @param string $name The image name to check
3580
 * @param Title|bool $contextTitle The page on which the image occurs, if known
3581
 * @param string $blacklist Wikitext of a file blacklist
3582
 * @return bool
3583
 */
3584
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
3585
	# Handle redirects; callers almost always hit wfFindFile() anyway,
3586
	# so just use that method because it has a fast process cache.
3587
	$file = wfFindFile( $name ); // get the final name
3588
	$name = $file ? $file->getTitle()->getDBkey() : $name;
3589
3590
	# Run the extension hook
3591
	$bad = false;
3592
	if ( !Hooks::run( 'BadImage', [ $name, &$bad ] ) ) {
3593
		return $bad;
3594
	}
3595
3596
	$cache = ObjectCache::getLocalServerInstance( 'hash' );
3597
	$key = wfMemcKey( 'bad-image-list', ( $blacklist === null ) ? 'default' : md5( $blacklist ) );
3598
	$badImages = $cache->get( $key );
3599
3600
	if ( $badImages === false ) { // cache miss
3601
		if ( $blacklist === null ) {
3602
			$blacklist = wfMessage( 'bad_image_list' )->inContentLanguage()->plain(); // site list
3603
		}
3604
		# Build the list now
3605
		$badImages = [];
3606
		$lines = explode( "\n", $blacklist );
3607
		foreach ( $lines as $line ) {
3608
			# List items only
3609
			if ( substr( $line, 0, 1 ) !== '*' ) {
3610
				continue;
3611
			}
3612
3613
			# Find all links
3614
			$m = [];
3615
			if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
3616
				continue;
3617
			}
3618
3619
			$exceptions = [];
3620
			$imageDBkey = false;
3621
			foreach ( $m[1] as $i => $titleText ) {
3622
				$title = Title::newFromText( $titleText );
3623
				if ( !is_null( $title ) ) {
3624
					if ( $i == 0 ) {
3625
						$imageDBkey = $title->getDBkey();
3626
					} else {
3627
						$exceptions[$title->getPrefixedDBkey()] = true;
3628
					}
3629
				}
3630
			}
3631
3632
			if ( $imageDBkey !== false ) {
3633
				$badImages[$imageDBkey] = $exceptions;
3634
			}
3635
		}
3636
		$cache->set( $key, $badImages, 60 );
3637
	}
3638
3639
	$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...
3640
	$bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
3641
3642
	return $bad;
3643
}
3644
3645
/**
3646
 * Determine whether the client at a given source IP is likely to be able to
3647
 * access the wiki via HTTPS.
3648
 *
3649
 * @param string $ip The IPv4/6 address in the normal human-readable form
3650
 * @return bool
3651
 */
3652
function wfCanIPUseHTTPS( $ip ) {
3653
	$canDo = true;
3654
	Hooks::run( 'CanIPUseHTTPS', [ $ip, &$canDo ] );
3655
	return !!$canDo;
3656
}
3657
3658
/**
3659
 * Determine input string is represents as infinity
3660
 *
3661
 * @param string $str The string to determine
3662
 * @return bool
3663
 * @since 1.25
3664
 */
3665
function wfIsInfinity( $str ) {
3666
	$infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ];
3667
	return in_array( $str, $infinityValues );
3668
}
3669
3670
/**
3671
 * Returns true if these thumbnail parameters match one that MediaWiki
3672
 * requests from file description pages and/or parser output.
3673
 *
3674
 * $params is considered non-standard if they involve a non-standard
3675
 * width or any non-default parameters aside from width and page number.
3676
 * The number of possible files with standard parameters is far less than
3677
 * that of all combinations; rate-limiting for them can thus be more generious.
3678
 *
3679
 * @param File $file
3680
 * @param array $params
3681
 * @return bool
3682
 * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25
3683
 */
3684
function wfThumbIsStandard( File $file, array $params ) {
3685
	global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages;
3686
3687
	$multipliers = [ 1 ];
3688
	if ( $wgResponsiveImages ) {
3689
		// These available sizes are hardcoded currently elsewhere in MediaWiki.
3690
		// @see Linker::processResponsiveImages
3691
		$multipliers[] = 1.5;
3692
		$multipliers[] = 2;
3693
	}
3694
3695
	$handler = $file->getHandler();
3696
	if ( !$handler || !isset( $params['width'] ) ) {
3697
		return false;
3698
	}
3699
3700
	$basicParams = [];
3701
	if ( isset( $params['page'] ) ) {
3702
		$basicParams['page'] = $params['page'];
3703
	}
3704
3705
	$thumbLimits = [];
3706
	$imageLimits = [];
3707
	// Expand limits to account for multipliers
3708
	foreach ( $multipliers as $multiplier ) {
3709
		$thumbLimits = array_merge( $thumbLimits, array_map(
3710
			function ( $width ) use ( $multiplier ) {
3711
				return round( $width * $multiplier );
3712
			}, $wgThumbLimits )
3713
		);
3714
		$imageLimits = array_merge( $imageLimits, array_map(
3715
			function ( $pair ) use ( $multiplier ) {
3716
				return [
3717
					round( $pair[0] * $multiplier ),
3718
					round( $pair[1] * $multiplier ),
3719
				];
3720
			}, $wgImageLimits )
3721
		);
3722
	}
3723
3724
	// Check if the width matches one of $wgThumbLimits
3725
	if ( in_array( $params['width'], $thumbLimits ) ) {
3726
		$normalParams = $basicParams + [ 'width' => $params['width'] ];
3727
		// Append any default values to the map (e.g. "lossy", "lossless", ...)
3728
		$handler->normaliseParams( $file, $normalParams );
3729
	} else {
3730
		// If not, then check if the width matchs one of $wgImageLimits
3731
		$match = false;
3732
		foreach ( $imageLimits as $pair ) {
3733
			$normalParams = $basicParams + [ 'width' => $pair[0], 'height' => $pair[1] ];
3734
			// Decide whether the thumbnail should be scaled on width or height.
3735
			// Also append any default values to the map (e.g. "lossy", "lossless", ...)
3736
			$handler->normaliseParams( $file, $normalParams );
3737
			// Check if this standard thumbnail size maps to the given width
3738
			if ( $normalParams['width'] == $params['width'] ) {
3739
				$match = true;
3740
				break;
3741
			}
3742
		}
3743
		if ( !$match ) {
3744
			return false; // not standard for description pages
3745
		}
3746
	}
3747
3748
	// Check that the given values for non-page, non-width, params are just defaults
3749
	foreach ( $params as $key => $value ) {
3750
		if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
3751
			return false;
3752
		}
3753
	}
3754
3755
	return true;
3756
}
3757
3758
/**
3759
 * Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
3760
 *
3761
 * Values that exist in both values will be combined with += (all values of the array
3762
 * of $newValues will be added to the values of the array of $baseArray, while values,
3763
 * that exists in both, the value of $baseArray will be used).
3764
 *
3765
 * @param array $baseArray The array where you want to add the values of $newValues to
3766
 * @param array $newValues An array with new values
3767
 * @return array The combined array
3768
 * @since 1.26
3769
 */
3770
function wfArrayPlus2d( array $baseArray, array $newValues ) {
3771
	// First merge items that are in both arrays
3772
	foreach ( $baseArray as $name => &$groupVal ) {
3773
		if ( isset( $newValues[$name] ) ) {
3774
			$groupVal += $newValues[$name];
3775
		}
3776
	}
3777
	// Now add items that didn't exist yet
3778
	$baseArray += $newValues;
3779
3780
	return $baseArray;
3781
}
3782