MediaWiki master
GlobalFunctions.php
Go to the documentation of this file.
1<?php
27use Wikimedia\RequestTimeout\RequestTimeout;
28use Wikimedia\Timestamp\ConvertibleTimestamp;
29use Wikimedia\Timestamp\TimestampFormat as TS;
30
41function wfLoadExtension( $ext, $path = null ) {
42 if ( !$path ) {
44 $path = "$wgExtensionDirectory/$ext/extension.json";
45 }
46 ExtensionRegistry::getInstance()->queue( $path );
47}
48
62function wfLoadExtensions( array $exts ) {
64 $registry = ExtensionRegistry::getInstance();
65 foreach ( $exts as $ext ) {
66 $registry->queue( "$wgExtensionDirectory/$ext/extension.json" );
67 }
68}
69
78function wfLoadSkin( $skin, $path = null ) {
79 if ( !$path ) {
80 global $wgStyleDirectory;
81 $path = "$wgStyleDirectory/$skin/skin.json";
82 }
83 ExtensionRegistry::getInstance()->queue( $path );
84}
85
93function wfLoadSkins( array $skins ) {
94 global $wgStyleDirectory;
95 $registry = ExtensionRegistry::getInstance();
96 foreach ( $skins as $skin ) {
97 $registry->queue( "$wgStyleDirectory/$skin/skin.json" );
98 }
99}
100
111function wfArrayInsertAfter( array $array, array $insert, $after ) {
112 wfDeprecated( __FUNCTION__, '1.46' );
113 return ArrayUtils::insertAfter( $array, $insert, $after );
114}
115
124function wfObjectToArray( $objOrArray, $recursive = true ) {
125 $array = [];
126 if ( is_object( $objOrArray ) ) {
127 $objOrArray = get_object_vars( $objOrArray );
128 }
129 foreach ( $objOrArray as $key => $value ) {
130 if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
131 $value = wfObjectToArray( $value );
132 }
133
134 $array[$key] = $value;
135 }
136
137 return $array;
138}
139
150function wfRandom() {
151 // The maximum random value is "only" 2^31-1, so get two random
152 // values to reduce the chance of dupes
153 $max = mt_getrandmax() + 1;
154 $rand = number_format( ( mt_rand() * $max + mt_rand() ) / $max / $max, 12, '.', '' );
155 return $rand;
156}
157
168function wfRandomString( $length = 32 ) {
169 $str = '';
170 for ( $n = 0; $n < $length; $n += 7 ) {
171 $str .= sprintf( '%07x', mt_rand() & 0xfffffff );
172 }
173 return substr( $str, 0, $length );
174}
175
203function wfUrlencode( $s ) {
204 static $needle;
205
206 if ( $s === null ) {
207 // Reset $needle for testing.
208 $needle = null;
209 return '';
210 }
211
212 if ( $needle === null ) {
213 $needle = [ '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F', '%7E' ];
214 if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) ||
215 !str_contains( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' )
216 ) {
217 $needle[] = '%3A';
218 }
219 }
220
221 $s = urlencode( $s );
222 $s = str_ireplace(
223 $needle,
224 [ ';', '@', '$', '!', '*', '(', ')', ',', '/', '~', ':' ],
225 $s
226 );
227
228 return $s;
229}
230
241function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
242 if ( $array2 !== null ) {
243 $array1 += $array2;
244 }
245
246 $cgi = '';
247 foreach ( $array1 as $key => $value ) {
248 if ( $value !== null && $value !== false ) {
249 if ( $cgi != '' ) {
250 $cgi .= '&';
251 }
252 if ( $prefix !== '' ) {
253 $key = $prefix . "[$key]";
254 }
255 if ( is_array( $value ) ) {
256 $firstTime = true;
257 foreach ( $value as $k => $v ) {
258 $cgi .= $firstTime ? '' : '&';
259 if ( is_array( $v ) ) {
260 $cgi .= wfArrayToCgi( $v, null, $key . "[$k]" );
261 } else {
262 $cgi .= urlencode( $key . "[$k]" ) . '=' . urlencode( $v );
263 }
264 $firstTime = false;
265 }
266 } else {
267 if ( is_object( $value ) ) {
268 $value = $value->__toString();
269 }
270 $cgi .= urlencode( $key ) . '=' . urlencode( $value );
271 }
272 }
273 }
274 return $cgi;
275}
276
286function wfCgiToArray( $query ) {
287 if ( isset( $query[0] ) && $query[0] == '?' ) {
288 $query = substr( $query, 1 );
289 }
290 $bits = explode( '&', $query );
291 $ret = [];
292 foreach ( $bits as $bit ) {
293 if ( $bit === '' ) {
294 continue;
295 }
296 if ( !str_contains( $bit, '=' ) ) {
297 // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
298 $key = $bit;
299 $value = '';
300 } else {
301 [ $key, $value ] = explode( '=', $bit );
302 }
303 $key = urldecode( $key );
304 $value = urldecode( $value );
305 if ( str_contains( $key, '[' ) ) {
306 $keys = array_reverse( explode( '[', $key ) );
307 $key = array_pop( $keys );
308 $temp = $value;
309 foreach ( $keys as $k ) {
310 $k = substr( $k, 0, -1 );
311 $temp = [ $k => $temp ];
312 }
313 if ( isset( $ret[$key] ) && is_array( $ret[$key] ) ) {
314 $ret[$key] = array_merge( $ret[$key], $temp );
315 } else {
316 $ret[$key] = $temp;
317 }
318 } else {
319 $ret[$key] = $value;
320 }
321 }
322 return $ret;
323}
324
333function wfAppendQuery( $url, $query ) {
334 if ( is_array( $query ) ) {
335 $query = wfArrayToCgi( $query );
336 }
337 if ( $query != '' ) {
338 // Remove the fragment, if there is one
339 $fragment = false;
340 $hashPos = strpos( $url, '#' );
341 if ( $hashPos !== false ) {
342 $fragment = substr( $url, $hashPos );
343 $url = substr( $url, 0, $hashPos );
344 }
345
346 // Add parameter
347 if ( !str_contains( $url, '?' ) ) {
348 $url .= '?';
349 } else {
350 $url .= '&';
351 }
352 $url .= $query;
353
354 // Put the fragment back
355 if ( $fragment !== false ) {
356 $url .= $fragment;
357 }
358 }
359 return $url;
360}
361
382function wfDebug( $text, $dest = 'all', array $context = [] ) {
384
385 if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
386 return;
387 }
388
389 $text = trim( $text );
390
391 if ( $wgDebugLogPrefix !== '' ) {
392 $context['prefix'] = $wgDebugLogPrefix;
393 }
394 $context['private'] = ( $dest === false || $dest === 'private' );
395
396 $logger = LoggerFactory::getInstance( 'wfDebug' );
397 $logger->debug( $text, $context );
398}
399
405 static $cache;
406 if ( $cache !== null ) {
407 return $cache;
408 }
409 // Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
410 // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
411 if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
412 || MW_ENTRY_POINT === 'load'
413 ) {
414 $cache = true;
415 } else {
416 $cache = false;
417 }
418 return $cache;
419}
420
446function wfDebugLog(
447 $logGroup, $text, $dest = 'all', array $context = []
448) {
449 $text = trim( $text );
450
451 $logger = LoggerFactory::getInstance( $logGroup );
452 $context['private'] = ( $dest === false || $dest === 'private' );
453 $logger->info( $text, $context );
454}
455
464function wfLogDBError( $text, array $context = [] ) {
465 $logger = LoggerFactory::getInstance( 'wfLogDBError' );
466 $logger->error( trim( $text ), $context );
467}
468
485function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
486 if ( !is_string( $version ) && $version !== false ) {
487 throw new InvalidArgumentException(
488 "MediaWiki version must either be a string or false. " .
489 "Example valid version: '1.33'"
490 );
491 }
492
493 MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
494}
495
516function wfDeprecatedMsg( $msg, $version = false, $component = false, $callerOffset = 2 ) {
517 MWDebug::deprecatedMsg( $msg, $version, $component,
518 $callerOffset === false ? false : $callerOffset + 1 );
519}
520
531function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
532 MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' );
533}
534
544function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
545 MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
546}
547
570function wfMessage( $key, ...$params ) {
571 if ( is_array( $key ) ) {
572 // Fallback keys are not allowed in message specifiers
573 $message = wfMessageFallback( ...$key );
574 } else {
575 $message = Message::newFromSpecifier( $key );
576 }
577
578 // We call Message::params() to reduce code duplication
579 if ( $params ) {
580 $message->params( ...$params );
581 }
582
583 return $message;
584}
585
598function wfMessageFallback( ...$keys ) {
599 return Message::newFallbackSequence( ...$keys );
600}
601
610function wfMsgReplaceArgs( $message, $args ) {
611 # Fix windows line-endings
612 # Some messages are split with explode("\n", $msg)
613 $message = str_replace( "\r", '', $message );
614
615 // Replace arguments
616 if ( is_array( $args ) && $args ) {
617 if ( is_array( $args[0] ) ) {
618 $args = array_values( $args[0] );
619 }
620 $replacementKeys = [];
621 foreach ( $args as $n => $param ) {
622 $replacementKeys['$' . ( $n + 1 )] = $param;
623 }
624 $message = strtr( $message, $replacementKeys );
625 }
626
627 return $message;
628}
629
638function wfHostname() {
639 // Hostname overriding
640 global $wgOverrideHostname;
641 if ( $wgOverrideHostname !== false ) {
642 return $wgOverrideHostname;
643 }
644
645 return php_uname( 'n' ) ?: 'unknown';
646}
647
658function wfDebugBacktrace( $limit = 0 ) {
659 static $disabled = null;
660
661 if ( $disabled === null ) {
662 $disabled = !function_exists( 'debug_backtrace' );
663 if ( $disabled ) {
664 wfDebug( "debug_backtrace() is disabled" );
665 }
666 }
667 if ( $disabled ) {
668 return [];
669 }
670
671 if ( $limit ) {
672 return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 );
673 } else {
674 return array_slice( debug_backtrace(), 1 );
675 }
676}
677
686function wfBacktrace( $raw = null ) {
687 $raw ??= MW_ENTRY_POINT === 'cli';
688 if ( $raw ) {
689 $frameFormat = "%s line %s calls %s()\n";
690 $traceFormat = "%s";
691 } else {
692 $frameFormat = "<li>%s line %s calls %s()</li>\n";
693 $traceFormat = "<ul>\n%s</ul>\n";
694 }
695
696 $frames = array_map( static function ( $frame ) use ( $frameFormat ) {
697 $file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-';
698 $line = $frame['line'] ?? '-';
699 $call = $frame['function'];
700 if ( !empty( $frame['class'] ) ) {
701 $call = $frame['class'] . $frame['type'] . $call;
702 }
703 return sprintf( $frameFormat, $file, $line, $call );
704 }, wfDebugBacktrace() );
705
706 return sprintf( $traceFormat, implode( '', $frames ) );
707}
708
719function wfGetCaller( $level = 2 ) {
720 $backtrace = wfDebugBacktrace( $level + 1 );
721 if ( isset( $backtrace[$level] ) ) {
722 return wfFormatStackFrame( $backtrace[$level] );
723 } else {
724 return 'unknown';
725 }
726}
727
735function wfGetAllCallers( $limit = 3 ) {
736 $limit = $limit ? $limit + 1 : 0;
737 // Strip the own "wfGetAllCallers" from the list
738 $trace = array_reverse( array_slice( wfDebugBacktrace( $limit ), 1 ) );
739 return implode( '/', array_map( wfFormatStackFrame( ... ), $trace ) );
740}
741
754function wfFormatStackFrame( $frame ) {
755 if ( !isset( $frame['function'] ) ) {
756 return 'NO_FUNCTION_GIVEN';
757 }
758 return isset( $frame['class'] ) && isset( $frame['type'] ) ?
759 $frame['class'] . $frame['type'] . $frame['function'] :
760 $frame['function'];
761}
762
772function wfClientAcceptsGzip( $force = false ) {
773 static $result = null;
774 if ( $result === null || $force ) {
775 $result = false;
776 if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
777 # @todo FIXME: We may want to disallow some broken browsers
778 $m = [];
779 if ( preg_match(
780 '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
781 $_SERVER['HTTP_ACCEPT_ENCODING'],
782 $m
783 )
784 ) {
785 if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
786 return $result;
787 }
788 wfDebug( "wfClientAcceptsGzip: client accepts gzip." );
789 $result = true;
790 }
791 }
792 }
793 return $result;
794}
795
806function wfEscapeWikiText( $input ): string {
807 global $wgEnableMagicLinks;
808 static $repl = null, $repl2 = null, $repl3 = null, $repl4 = null;
809 if ( $repl === null || defined( 'MW_PHPUNIT_TEST' ) ) {
810 // Tests depend upon being able to change $wgEnableMagicLinks, so don't cache
811 // in those situations
812 $repl = [
813 '"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
814 '=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
815 '{' => '&#123;', '|' => '&#124;', '}' => '&#125;',
816 ';' => '&#59;', // a token inside language converter brackets
817 '!!' => '&#33;!', // a token inside table context
818 "\n!" => "\n&#33;", "\r!" => "\r&#33;", // a token inside table context
819 "\n#" => "\n&#35;", "\r#" => "\r&#35;",
820 "\n*" => "\n&#42;", "\r*" => "\r&#42;",
821 "\n:" => "\n&#58;", "\r:" => "\r&#58;",
822 "\n " => "\n&#32;", "\r " => "\r&#32;",
823 "\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
824 "\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
825 "\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
826 "\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
827 '__' => '_&#95;', '://' => '&#58;//',
828 // Japanese magic words start w/ wide underscore
829 '_' => '&#xFF3F;',
830 '~~~' => '~~&#126;', // protect from PST, just to be safe(r)
831 ];
832
833 $magicLinks = array_keys( array_filter( $wgEnableMagicLinks ) );
834 // We have to catch everything "\s" matches in PCRE
835 foreach ( $magicLinks as $magic ) {
836 $repl["$magic "] = "$magic&#32;";
837 $repl["$magic\t"] = "$magic&#9;";
838 $repl["$magic\r"] = "$magic&#13;";
839 $repl["$magic\n"] = "$magic&#10;";
840 $repl["$magic\f"] = "$magic&#12;";
841 }
842 // Additionally escape the following characters at the beginning of the
843 // string, in case they merge to form tokens when spliced into a
844 // string. Tokens like -{ {{ [[ {| etc are already escaped because
845 // the second character is escaped above, but the following tokens
846 // are handled here: |+ |- __FOO__ ~~~
847 // (Only single-byte characters can go here; multibyte characters
848 // like 'wide underscore' must go into $repl above.)
849 $repl3 = [
850 '+' => '&#43;', '-' => '&#45;', '_' => '&#95;', '~' => '&#126;',
851 ];
852 // Similarly, protect the following characters at the end of the
853 // string, which could turn form the start of `__FOO__` or `~~~~`
854 // A trailing newline could also form the unintended start of a
855 // paragraph break if it is glued to a newline in the following
856 // context. Again, only single-byte characters can be protected
857 // here; 'wide underscore' is protected by $repl above.
858 $repl4 = [
859 '_' => '&#95;', '~' => '&#126;',
860 "\n" => "&#10;", "\r" => "&#13;",
861 "\t" => "&#9;", // "\n\t\n" is treated like "\n\n"
862 ];
863
864 // And handle protocols that don't use "://"
865 global $wgUrlProtocols;
866 $repl2 = [];
867 foreach ( $wgUrlProtocols as $prot ) {
868 if ( substr( $prot, -1 ) === ':' ) {
869 $repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
870 }
871 }
872 $repl2 = $repl2 ? '/\b(' . implode( '|', $repl2 ) . '):/i' : '/^(?!)/';
873 }
874 // Tell phan that $repl2, $repl3 and $repl4 will also be non-null here
875 '@phan-var string $repl2';
876 '@phan-var string $repl3';
877 '@phan-var string $repl4';
878 // This will also stringify input in case it's not a string
879 $text = substr( strtr( "\n$input", $repl ), 1 );
880 if ( $text === '' ) {
881 return $text;
882 }
883 $first = strtr( $text[0], $repl3 ); // protect first character
884 if ( strlen( $text ) > 1 ) {
885 $text = $first . substr( $text, 1, -1 ) .
886 strtr( substr( $text, -1 ), $repl4 ); // protect last character
887 } else {
888 // special case for single-character strings
889 $text = strtr( $first, $repl4 ); // protect last character
890 }
891 $text = preg_replace( $repl2, '$1&#58;', $text );
892 return $text;
893}
894
905function wfSetVar( &$dest, $source, $force = false ) {
906 $temp = $dest;
907 if ( $source !== null || $force ) {
908 $dest = $source;
909 }
910 return $temp;
911}
912
922function wfSetBit( &$dest, $bit, $state = true ) {
923 $temp = (bool)( $dest & $bit );
924 if ( $state !== null ) {
925 if ( $state ) {
926 $dest |= $bit;
927 } else {
928 $dest &= ~$bit;
929 }
930 }
931 return $temp;
932}
933
940function wfVarDump( $var ) {
941 global $wgOut;
942 $s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
943 if ( headers_sent() || $wgOut === null || !is_object( $wgOut ) ) {
944 print $s;
945 } else {
946 $wgOut->addHTML( $s );
947 }
948}
949
957function wfHttpError( $code, $label, $desc ) {
958 global $wgOut;
959 HttpStatus::header( $code );
960 if ( $wgOut ) {
961 $wgOut->disable();
962 $wgOut->sendCacheControl();
963 }
964
965 \MediaWiki\Request\HeaderCallback::warnIfHeadersSent();
966 header( 'Content-type: text/html; charset=utf-8' );
967 ContentSecurityPolicy::sendRestrictiveHeader();
968 ob_start();
969 print '<!DOCTYPE html>' .
970 '<html><head><title>' .
971 htmlspecialchars( $label ) .
972 '</title><meta name="color-scheme" content="light dark" /></head><body><h1>' .
973 htmlspecialchars( $label ) .
974 '</h1><p>' .
975 nl2br( htmlspecialchars( $desc ) ) .
976 "</p></body></html>\n";
977 header( 'Content-Length: ' . ob_get_length() );
978 ob_end_flush();
979}
980
1001function wfResetOutputBuffers( $resetGzipEncoding = true ) {
1002 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
1003 while ( $status = ob_get_status() ) {
1004 if ( isset( $status['flags'] ) ) {
1005 $flags = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
1006 $deletable = ( $status['flags'] & $flags ) === $flags;
1007 } elseif ( isset( $status['del'] ) ) {
1008 $deletable = $status['del'];
1009 } else {
1010 // Guess that any PHP-internal setting can't be removed.
1011 $deletable = $status['type'] !== 0; /* PHP_OUTPUT_HANDLER_INTERNAL */
1012 }
1013 if ( !$deletable ) {
1014 // Give up, and hope the result doesn't break
1015 // output behavior.
1016 break;
1017 }
1018 if ( $status['name'] === 'MediaWikiIntegrationTestCase::wfResetOutputBuffersBarrier' ) {
1019 // Unit testing barrier to prevent this function from breaking PHPUnit.
1020 break;
1021 }
1022 if ( !ob_end_clean() ) {
1023 // Could not remove output buffer handler; abort now
1024 // to avoid getting in some kind of infinite loop.
1025 break;
1026 }
1027 if ( $resetGzipEncoding && $status['name'] == 'ob_gzhandler' ) {
1028 // Reset the 'Content-Encoding' field set by this handler
1029 // so we can start fresh.
1030 header_remove( 'Content-Encoding' );
1031 break;
1032 }
1033 }
1034}
1035
1046function wfTimestamp( $outputtype = TS::UNIX, $ts = 0 ) {
1047 $ret = ConvertibleTimestamp::convert( $outputtype, $ts );
1048 if ( $ret === false ) {
1049 if ( $outputtype instanceof TS ) {
1050 $outputtype = $outputtype->name;
1051 }
1052 wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts" );
1053 }
1054 return $ret;
1055}
1056
1065function wfTimestampOrNull( $outputtype = TS::UNIX, $ts = null ) {
1066 if ( $ts === null ) {
1067 return null;
1068 } else {
1069 return wfTimestamp( $outputtype, $ts );
1070 }
1071}
1072
1078function wfTimestampNow() {
1079 return ConvertibleTimestamp::now( TS::MW );
1080}
1081
1093function wfTempDir() {
1094 global $wgTmpDirectory;
1095
1096 if ( $wgTmpDirectory !== false ) {
1097 return $wgTmpDirectory;
1098 }
1099
1100 return TempFSFile::getUsableTempDirectory();
1101}
1102
1111function wfMkdirParents( $dir, $mode = null, $caller = null ) {
1112 global $wgDirectoryMode;
1113
1114 if ( FileBackend::isStoragePath( $dir ) ) {
1115 throw new LogicException( __FUNCTION__ . " given storage path '$dir'." );
1116 }
1117 if ( $caller !== null ) {
1118 wfDebug( "$caller: called wfMkdirParents($dir)" );
1119 }
1120 if ( strval( $dir ) === '' ) {
1121 return true;
1122 }
1123
1124 $dir = str_replace( [ '\\', '/' ], DIRECTORY_SEPARATOR, $dir );
1125 $mode ??= $wgDirectoryMode;
1126
1127 // Turn off the normal warning, we're doing our own below
1128 // PHP doesn't include the path in its warning message, so we add our own to aid in diagnosis.
1129 //
1130 // Repeat existence check if creation failed so that we silently recover in case of
1131 // a race condition where another request created it since the first check.
1132 //
1133 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1134 $ok = is_dir( $dir ) || @mkdir( $dir, $mode, true ) || is_dir( $dir );
1135 if ( !$ok ) {
1136 trigger_error( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ), E_USER_WARNING );
1137 }
1138
1139 return $ok;
1140}
1141
1147function wfRecursiveRemoveDir( $dir ) {
1148 // taken from https://www.php.net/manual/en/function.rmdir.php#98622
1149 if ( is_dir( $dir ) ) {
1150 $objects = scandir( $dir );
1151 foreach ( $objects as $object ) {
1152 if ( $object != "." && $object != ".." ) {
1153 if ( filetype( $dir . '/' . $object ) == "dir" ) {
1154 wfRecursiveRemoveDir( $dir . '/' . $object );
1155 } else {
1156 unlink( $dir . '/' . $object );
1157 }
1158 }
1159 }
1160 rmdir( $dir );
1161 }
1162}
1163
1173function wfPercent( $nr, int $acc = 2, bool $round = true ) {
1174 wfDeprecated( __FUNCTION__, '1.46' );
1175 $accForFormat = $acc >= 0 ? $acc : 0;
1176 $ret = sprintf( "%.{$accForFormat}f", $nr );
1177 return $round ? round( (float)$ret, $acc ) . '%' : "$ret%";
1178}
1179
1203function wfIniGetBool( $setting ) {
1204 return wfStringToBool( ini_get( $setting ) );
1205}
1206
1219function wfStringToBool( $val ) {
1220 $val = strtolower( $val );
1221 // 'on' and 'true' can't have whitespace around them, but '1' can.
1222 return $val == 'on'
1223 || $val == 'true'
1224 || $val == 'yes'
1225 || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
1226}
1227
1241function wfEscapeShellArg( ...$args ) {
1242 wfDeprecated( __FUNCTION__, '1.30' );
1243 return Shell::escape( ...$args );
1244}
1245
1270function wfShellExec( $cmd, &$retval = null, $environ = [],
1271 $limits = [], $options = []
1272) {
1273 wfDeprecated( __FUNCTION__, '1.30' );
1274 if ( Shell::isDisabled() ) {
1275 $retval = 1;
1276 // Backwards compatibility be upon us...
1277 return 'Unable to run external programs, proc_open() is disabled.';
1278 }
1279
1280 if ( is_array( $cmd ) ) {
1281 $cmd = Shell::escape( $cmd );
1282 }
1283
1284 $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
1285 $profileMethod = $options['profileMethod'] ?? wfGetCaller();
1286
1287 try {
1288 $result = Shell::command( [] )
1289 ->unsafeParams( (array)$cmd )
1290 ->environment( $environ )
1291 ->limits( $limits )
1292 ->includeStderr( $includeStderr )
1293 ->profileMethod( $profileMethod )
1294 // For b/c
1295 ->restrict( Shell::RESTRICT_NONE )
1296 ->execute();
1297 } catch ( ProcOpenError ) {
1298 $retval = -1;
1299 return '';
1300 }
1301
1302 $retval = $result->getExitCode();
1303
1304 return $result->getStdout();
1305}
1306
1324function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = [] ) {
1325 wfDeprecated( __FUNCTION__, '1.30' );
1326 return wfShellExec( $cmd, $retval, $environ, $limits,
1327 [ 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ] );
1328}
1329
1345function wfShellWikiCmd( $script, array $parameters = [], array $options = [] ) {
1346 wfDeprecated( __FUNCTION__, '1.30' );
1347 global $wgPhpCli;
1348 // Give site config file a chance to run the script in a wrapper.
1349 // The caller may likely want to call wfBasename() on $script.
1350 ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
1351 ->onWfShellWikiCmd( $script, $parameters, $options );
1352 $cmd = [ $options['php'] ?? $wgPhpCli ];
1353 if ( isset( $options['wrapper'] ) ) {
1354 $cmd[] = $options['wrapper'];
1355 }
1356 $cmd[] = $script;
1357 // Escape each parameter for shell
1358 return Shell::escape( array_merge( $cmd, $parameters ) );
1359}
1360
1377function wfMerge(
1378 string $old,
1379 string $mine,
1380 string $yours,
1381 ?string &$simplisticMergeAttempt,
1382 ?string &$mergeLeftovers = null
1383): bool {
1384 global $wgDiff3;
1385
1386 # This check may also protect against code injection in
1387 # case of broken installations.
1388 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1389 $haveDiff3 = $wgDiff3 && @file_exists( $wgDiff3 );
1390
1391 if ( !$haveDiff3 ) {
1392 wfDebug( "diff3 not found" );
1393 return false;
1394 }
1395
1396 # Make temporary files
1397 $td = wfTempDir();
1398 $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
1399 $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
1400 $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
1401
1402 # NOTE: diff3 issues a warning to stderr if any of the files does not end with
1403 # a newline character. To avoid this, we normalize the trailing whitespace before
1404 # creating the diff.
1405
1406 fwrite( $oldtextFile, rtrim( $old ) . "\n" );
1407 fclose( $oldtextFile );
1408 fwrite( $mytextFile, rtrim( $mine ) . "\n" );
1409 fclose( $mytextFile );
1410 fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
1411 fclose( $yourtextFile );
1412
1413 # Check for a conflict
1414 $cmd = Shell::escape( $wgDiff3, '--text', '--overlap-only', $mytextName,
1415 $oldtextName, $yourtextName );
1416 $handle = popen( $cmd, 'r' );
1417
1418 $mergeLeftovers = '';
1419 do {
1420 $data = fread( $handle, 8192 );
1421 if ( $data === false || $data === '' ) {
1422 break;
1423 }
1424 $mergeLeftovers .= $data;
1425 } while ( true );
1426 pclose( $handle );
1427
1428 $conflict = $mergeLeftovers !== '';
1429
1430 # Merge differences automatically where possible, preferring "my" text for conflicts.
1431 $cmd = Shell::escape( $wgDiff3, '--text', '--ed', '--merge', $mytextName,
1432 $oldtextName, $yourtextName );
1433 $handle = popen( $cmd, 'r' );
1434 $simplisticMergeAttempt = '';
1435 do {
1436 $data = fread( $handle, 8192 );
1437 if ( $data === false || $data === '' ) {
1438 break;
1439 }
1440 $simplisticMergeAttempt .= $data;
1441 } while ( true );
1442 pclose( $handle );
1443 unlink( $mytextName );
1444 unlink( $oldtextName );
1445 unlink( $yourtextName );
1446
1447 if ( $simplisticMergeAttempt === '' && $old !== '' && !$conflict ) {
1448 wfDebug( "Unexpected null result from diff3. Command: $cmd" );
1449 $conflict = true;
1450 }
1451 return !$conflict;
1452}
1453
1466function wfBaseName( $path, $suffix = '' ) {
1467 if ( $suffix == '' ) {
1468 $encSuffix = '';
1469 } else {
1470 $encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
1471 }
1472
1473 $matches = [];
1474 if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
1475 return $matches[1];
1476 } else {
1477 return '';
1478 }
1479}
1480
1490function wfRelativePath( $path, $from ) {
1491 // Normalize mixed input on Windows...
1492 $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
1493 $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
1494
1495 // Trim trailing slashes -- fix for drive root
1496 $path = rtrim( $path, DIRECTORY_SEPARATOR );
1497 $from = rtrim( $from, DIRECTORY_SEPARATOR );
1498
1499 $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
1500 $against = explode( DIRECTORY_SEPARATOR, $from );
1501
1502 if ( $pieces[0] !== $against[0] ) {
1503 // Non-matching Windows drive letters?
1504 // Return a full path.
1505 return $path;
1506 }
1507
1508 // Trim off common prefix
1509 while ( count( $pieces ) && count( $against )
1510 && $pieces[0] == $against[0] ) {
1511 array_shift( $pieces );
1512 array_shift( $against );
1513 }
1514
1515 // relative dots to bump us to the parent
1516 while ( count( $against ) ) {
1517 array_unshift( $pieces, '..' );
1518 array_shift( $against );
1519 }
1520
1521 $pieces[] = wfBaseName( $path );
1522
1523 return implode( DIRECTORY_SEPARATOR, $pieces );
1524}
1525
1535function wfScript( $script = 'index' ) {
1537 if ( $script === 'index' ) {
1538 return $wgScript;
1539 } elseif ( $script === 'load' ) {
1540 return $wgLoadScript;
1541 } else {
1542 return "{$wgScriptPath}/{$script}.php";
1543 }
1544}
1545
1553function wfBoolToStr( $value ) {
1554 return $value ? 'true' : 'false';
1555}
1556
1562function wfGetNull() {
1563 return wfIsWindows() ? 'NUL' : '/dev/null';
1564}
1565
1575 global $wgIllegalFileChars;
1576 $illegalFileChars = $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '';
1577 $name = preg_replace(
1578 "/[^" . Title::legalChars() . "]" . $illegalFileChars . "/",
1579 '-',
1580 $name
1581 );
1582 // $wgIllegalFileChars may not include '/' and '\', so we still need to do this
1583 $name = wfBaseName( $name );
1584 return $name;
1585}
1586
1593function wfMemoryLimit( $newLimit ) {
1594 $oldLimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
1595 // If the INI config is already unlimited, there is nothing larger
1596 if ( $oldLimit != -1 ) {
1597 $newLimit = wfShorthandToInteger( (string)$newLimit );
1598 if ( $newLimit == -1 ) {
1599 wfDebug( "Removing PHP's memory limit" );
1600 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1601 @ini_set( 'memory_limit', $newLimit );
1602 } elseif ( $newLimit > $oldLimit ) {
1603 wfDebug( "Raising PHP's memory limit to $newLimit bytes" );
1604 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1605 @ini_set( 'memory_limit', $newLimit );
1606 }
1607 }
1608}
1609
1618
1619 $timeout = RequestTimeout::singleton();
1620 $timeLimit = $timeout->getWallTimeLimit();
1621 if ( $timeLimit !== INF ) {
1622 // RequestTimeout library is active
1623 if ( $wgTransactionalTimeLimit > $timeLimit ) {
1624 $timeout->setWallTimeLimit( $wgTransactionalTimeLimit );
1625 }
1626 } else {
1627 // Fallback case, likely $wgRequestTimeLimit === null
1628 $timeLimit = (int)ini_get( 'max_execution_time' );
1629 // Note that CLI scripts use 0
1630 if ( $timeLimit > 0 && $wgTransactionalTimeLimit > $timeLimit ) {
1631 $timeout->setWallTimeLimit( $wgTransactionalTimeLimit );
1632 }
1633 }
1634 ignore_user_abort( true ); // ignore client disconnects
1635
1636 return $timeLimit;
1637}
1638
1646function wfShorthandToInteger( ?string $string = '', int $default = -1 ): int {
1647 $string = trim( $string ?? '' );
1648 if ( $string === '' ) {
1649 return $default;
1650 }
1651 $last = substr( $string, -1 );
1652 $val = intval( $string );
1653 switch ( $last ) {
1654 case 'g':
1655 case 'G':
1656 $val *= 1024;
1657 // break intentionally missing
1658 case 'm':
1659 case 'M':
1660 $val *= 1024;
1661 // break intentionally missing
1662 case 'k':
1663 case 'K':
1664 $val *= 1024;
1665 }
1666
1667 return $val;
1668}
1669
1677function wfIsInfinity( $str ) {
1678 // The INFINITY_VALS are hardcoded elsewhere in MediaWiki (e.g. mediawiki.special.block.js).
1679 return in_array( $str, ExpiryDef::INFINITY_VALS );
1680}
1681
1696function wfThumbIsStandard( File $file, array $params ) {
1698
1699 $multipliers = [ 1 ];
1700 if ( $wgResponsiveImages ) {
1701 // These available sizes are hardcoded currently elsewhere in MediaWiki.
1702 // @see Linker::processResponsiveImages
1703 $multipliers[] = 2;
1704 }
1705
1706 $handler = $file->getHandler();
1707 if ( !$handler || !isset( $params['width'] ) ) {
1708 return false;
1709 }
1710
1711 $basicParams = [];
1712 if ( isset( $params['page'] ) ) {
1713 $basicParams['page'] = $params['page'];
1714 }
1715
1716 $thumbLimits = [];
1717 $imageLimits = [];
1718 // Expand limits to account for multipliers
1719 foreach ( $multipliers as $multiplier ) {
1720 $thumbLimits = array_merge( $thumbLimits, array_map(
1721 static function ( $width ) use ( $multiplier ) {
1722 return round( $width * $multiplier );
1723 }, $wgThumbLimits )
1724 );
1725 $imageLimits = array_merge( $imageLimits, array_map(
1726 static function ( $pair ) use ( $multiplier ) {
1727 return [
1728 round( $pair[0] * $multiplier ),
1729 round( $pair[1] * $multiplier ),
1730 ];
1731 }, $wgImageLimits )
1732 );
1733 }
1734
1735 // Check if the width matches one of $wgThumbLimits
1736 if ( in_array( $params['width'], $thumbLimits ) ) {
1737 $normalParams = $basicParams + [ 'width' => $params['width'] ];
1738 // Append any default values to the map (e.g. "lossy", "lossless", ...)
1739 $handler->normaliseParams( $file, $normalParams );
1740 } else {
1741 // If not, then check if the width matches one of $wgImageLimits
1742 $match = false;
1743 foreach ( $imageLimits as $pair ) {
1744 $normalParams = $basicParams + [ 'width' => $pair[0], 'height' => $pair[1] ];
1745 // Decide whether the thumbnail should be scaled on width or height.
1746 // Also append any default values to the map (e.g. "lossy", "lossless", ...)
1747 $handler->normaliseParams( $file, $normalParams );
1748 // Check if this standard thumbnail size maps to the given width
1749 if ( $normalParams['width'] == $params['width'] ) {
1750 $match = true;
1751 break;
1752 }
1753 }
1754 if ( !$match ) {
1755 return false; // not standard for description pages
1756 }
1757 }
1758
1759 // Check that the given values for non-page, non-width, params are just defaults
1760 foreach ( $params as $key => $value ) {
1761 if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
1762 return false;
1763 }
1764 }
1765
1766 return true;
1767}
1768
1782function wfArrayPlus2d( array $baseArray, array $newValues ) {
1783 wfDeprecated( __FUNCTION__, '1,46' );
1784 return ArrayUtils::arrayPlus2d( $baseArray, $newValues );
1785}
wfIsWindows()
Check if the operating system is Windows.
wfThumbIsStandard(File $file, array $params)
Returns true if these thumbnail parameters match one that MediaWiki requests from file description pa...
wfVarDump( $var)
A wrapper around the PHP function var_export().
wfTimestampOrNull( $outputtype=TS::UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfRandom()
Get a random decimal value in the domain of [0, 1), in a way not likely to give duplicate values for ...
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfTempDir()
Tries to get the system directory for temporary files.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
wfBaseName( $path, $suffix='')
Return the final portion of a pathname.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfClientAcceptsGzip( $force=false)
Whether the client accept gzip encoding.
wfEscapeShellArg(... $args)
Locale-independent version of escapeshellarg()
wfLogDBError( $text, array $context=[])
Log for database errors.
wfLoadSkins(array $skins)
Load multiple skins at once.
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfRecursiveRemoveDir( $dir)
Remove a directory and all its content.
wfLoadExtension( $ext, $path=null)
Load an extension.
wfMemoryLimit( $newLimit)
Raise PHP's memory limit (if needed).
wfSetBit(&$dest, $bit, $state=true)
As for wfSetVar except setting a bit.
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
wfShorthandToInteger(?string $string='', int $default=-1)
Converts shorthand byte notation to integer form.
wfBacktrace( $raw=null)
Get a debug backtrace as a string.
wfGetCaller( $level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfShellExec( $cmd, &$retval=null, $environ=[], $limits=[], $options=[])
Execute a shell command, with time and memory limits mirrored from the PHP configuration if supported...
wfIsDebugRawPage()
Returns true if debug logging should be suppressed if $wgDebugRawPage = false.
wfHostname()
Get host name of the current machine, for use in error reporting.
wfShellWikiCmd( $script, array $parameters=[], array $options=[])
Generate a shell-escaped command line string to run a MediaWiki cli script.
wfPercent( $nr, int $acc=2, bool $round=true)
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
wfGetNull()
Get a platform-independent path to the null file, e.g.
wfRelativePath( $path, $from)
Generate a relative path name to the given file.
wfHttpError( $code, $label, $desc)
Provide a simple HTTP error.
wfMessageFallback(... $keys)
This function accepts multiple message keys and returns a message instance for the first message whic...
wfMerge(string $old, string $mine, string $yours, ?string &$simplisticMergeAttempt, ?string &$mergeLeftovers=null)
wfMerge attempts to merge differences between three texts.
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
wfArrayPlus2d(array $baseArray, array $newValues)
Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfTransactionalTimeLimit()
Raise the request time limit to $wgTransactionalTimeLimit.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfObjectToArray( $objOrArray, $recursive=true)
Recursively converts the parameter (an object) to an array with the same data.
wfLoadSkin( $skin, $path=null)
Load a skin.
wfMsgReplaceArgs( $message, $args)
Replace message parameter keys on the given formatted output.
wfStringToBool( $val)
Convert string value to boolean, when the following are interpreted as true:
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDebugBacktrace( $limit=0)
Safety wrapper for debug_backtrace().
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfStripIllegalFilenameChars( $name)
Replace all invalid characters with '-'.
wfFormatStackFrame( $frame)
Return a string representation of frame.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
wfLoadExtensions(array $exts)
Load multiple extensions at once.
wfBoolToStr( $value)
Convenience function converts boolean values into "true" or "false" (string) values.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
wfArrayInsertAfter(array $array, array $insert, $after)
Insert an array into another array after the specified key.
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
if(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli') global $wgOut
Definition Setup.php:527
const MW_ENTRY_POINT
Definition api.php:21
Debug toolbar.
Definition MWDebug.php:35
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:80
getHandler(?Language $lang=null)
Get a MediaHandler instance for this file.
Definition File.php:1614
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Create PSR-3 logger objects.
Service locator for MediaWiki core services.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
Load JSON files, and uses a Processor to extract information.
Handle sending Content-Security-Policy headers.
Executes shell commands.
Definition Shell.php:32
Represents a title within MediaWiki.
Definition Title.php:69
A collection of static methods to play with arrays.
This class is used to hold the location and do limited manipulation of files stored temporarily (this...
Base class for all file backend classes (including multi-write backends).
Value object representing a message parameter with one of the types from {.
Type definition for expiry timestamps.
Definition ExpiryDef.php:18
$wgScript
Config variable stub for the Script setting, for use by phpdoc and IDEs.
$wgThumbLimits
Config variable stub for the ThumbLimits setting, for use by phpdoc and IDEs.
$wgDebugLogPrefix
Config variable stub for the DebugLogPrefix setting, for use by phpdoc and IDEs.
$wgPhpCli
Config variable stub for the PhpCli setting, for use by phpdoc and IDEs.
$wgOverrideHostname
Config variable stub for the OverrideHostname setting, for use by phpdoc and IDEs.
$wgImageLimits
Config variable stub for the ImageLimits setting, for use by phpdoc and IDEs.
$wgTmpDirectory
Config variable stub for the TmpDirectory setting, for use by phpdoc and IDEs.
$wgStyleDirectory
Config variable stub for the StyleDirectory setting, for use by phpdoc and IDEs.
$wgTransactionalTimeLimit
Config variable stub for the TransactionalTimeLimit setting, for use by phpdoc and IDEs.
$wgIllegalFileChars
Config variable stub for the IllegalFileChars setting, for use by phpdoc and IDEs.
$wgDirectoryMode
Config variable stub for the DirectoryMode setting, for use by phpdoc and IDEs.
$wgDiff3
Config variable stub for the Diff3 setting, for use by phpdoc and IDEs.
$wgUrlProtocols
Config variable stub for the UrlProtocols setting, for use by phpdoc and IDEs.
$wgResponsiveImages
Config variable stub for the ResponsiveImages setting, for use by phpdoc and IDEs.
$wgDebugRawPage
Config variable stub for the DebugRawPage setting, for use by phpdoc and IDEs.
$wgEnableMagicLinks
Config variable stub for the EnableMagicLinks setting, for use by phpdoc and IDEs.
$wgScriptPath
Config variable stub for the ScriptPath setting, for use by phpdoc and IDEs.
$wgExtensionDirectory
Config variable stub for the ExtensionDirectory setting, for use by phpdoc and IDEs.
$wgLoadScript
Config variable stub for the LoadScript setting, for use by phpdoc and IDEs.
$source