PageRenderTime 119ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 1ms

/includes/GlobalFunctions.php

https://github.com/spenser-roark/OOUG-Wiki
PHP | 3884 lines | 2117 code | 343 blank | 1424 comment | 486 complexity | 93ac49f1c5c91e191077aca2b169746c MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Global functions used everywhere
  4. * @file
  5. */
  6. if ( !defined( 'MEDIAWIKI' ) ) {
  7. die( "This file is part of MediaWiki, it is not a valid entry point" );
  8. }
  9. // Hide compatibility functions from Doxygen
  10. /// @cond
  11. /**
  12. * Compatibility functions
  13. *
  14. * We support PHP 5.2.3 and up.
  15. * Re-implementations of newer functions or functions in non-standard
  16. * PHP extensions may be included here.
  17. */
  18. if( !function_exists( 'iconv' ) ) {
  19. /** @codeCoverageIgnore */
  20. function iconv( $from, $to, $string ) {
  21. return Fallback::iconv( $from, $to, $string );
  22. }
  23. }
  24. if ( !function_exists( 'mb_substr' ) ) {
  25. /** @codeCoverageIgnore */
  26. function mb_substr( $str, $start, $count='end' ) {
  27. return Fallback::mb_substr( $str, $start, $count );
  28. }
  29. /** @codeCoverageIgnore */
  30. function mb_substr_split_unicode( $str, $splitPos ) {
  31. return Fallback::mb_substr_split_unicode( $str, $splitPos );
  32. }
  33. }
  34. if ( !function_exists( 'mb_strlen' ) ) {
  35. /** @codeCoverageIgnore */
  36. function mb_strlen( $str, $enc = '' ) {
  37. return Fallback::mb_strlen( $str, $enc );
  38. }
  39. }
  40. if( !function_exists( 'mb_strpos' ) ) {
  41. /** @codeCoverageIgnore */
  42. function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
  43. return Fallback::mb_strpos( $haystack, $needle, $offset, $encoding );
  44. }
  45. }
  46. if( !function_exists( 'mb_strrpos' ) ) {
  47. /** @codeCoverageIgnore */
  48. function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
  49. return Fallback::mb_strrpos( $haystack, $needle, $offset, $encoding );
  50. }
  51. }
  52. // Support for Wietse Venema's taint feature
  53. if ( !function_exists( 'istainted' ) ) {
  54. /** @codeCoverageIgnore */
  55. function istainted( $var ) {
  56. return 0;
  57. }
  58. /** @codeCoverageIgnore */
  59. function taint( $var, $level = 0 ) {}
  60. /** @codeCoverageIgnore */
  61. function untaint( $var, $level = 0 ) {}
  62. define( 'TC_HTML', 1 );
  63. define( 'TC_SHELL', 1 );
  64. define( 'TC_MYSQL', 1 );
  65. define( 'TC_PCRE', 1 );
  66. define( 'TC_SELF', 1 );
  67. }
  68. /// @endcond
  69. /**
  70. * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
  71. * @param $a array
  72. * @param $b array
  73. * @return array
  74. */
  75. function wfArrayDiff2( $a, $b ) {
  76. return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
  77. }
  78. /**
  79. * @param $a
  80. * @param $b
  81. * @return int
  82. */
  83. function wfArrayDiff2_cmp( $a, $b ) {
  84. if ( !is_array( $a ) ) {
  85. return strcmp( $a, $b );
  86. } elseif ( count( $a ) !== count( $b ) ) {
  87. return count( $a ) < count( $b ) ? -1 : 1;
  88. } else {
  89. reset( $a );
  90. reset( $b );
  91. while( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
  92. $cmp = strcmp( $valueA, $valueB );
  93. if ( $cmp !== 0 ) {
  94. return $cmp;
  95. }
  96. }
  97. return 0;
  98. }
  99. }
  100. /**
  101. * Array lookup
  102. * Returns an array where the values in the first array are replaced by the
  103. * values in the second array with the corresponding keys
  104. *
  105. * @param $a Array
  106. * @param $b Array
  107. * @return array
  108. */
  109. function wfArrayLookup( $a, $b ) {
  110. return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
  111. }
  112. /**
  113. * Appends to second array if $value differs from that in $default
  114. *
  115. * @param $key String|Int
  116. * @param $value Mixed
  117. * @param $default Mixed
  118. * @param $changed Array to alter
  119. */
  120. function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
  121. if ( is_null( $changed ) ) {
  122. throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
  123. }
  124. if ( $default[$key] !== $value ) {
  125. $changed[$key] = $value;
  126. }
  127. }
  128. /**
  129. * Backwards array plus for people who haven't bothered to read the PHP manual
  130. * XXX: will not darn your socks for you.
  131. *
  132. * @param $array1 Array
  133. * @param [$array2, [...]] Arrays
  134. * @return Array
  135. */
  136. function wfArrayMerge( $array1/* ... */ ) {
  137. $args = func_get_args();
  138. $args = array_reverse( $args, true );
  139. $out = array();
  140. foreach ( $args as $arg ) {
  141. $out += $arg;
  142. }
  143. return $out;
  144. }
  145. /**
  146. * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
  147. * e.g.
  148. * wfMergeErrorArrays(
  149. * array( array( 'x' ) ),
  150. * array( array( 'x', '2' ) ),
  151. * array( array( 'x' ) ),
  152. * array( array( 'y' ) )
  153. * );
  154. * returns:
  155. * array(
  156. * array( 'x', '2' ),
  157. * array( 'x' ),
  158. * array( 'y' )
  159. * )
  160. * @param varargs
  161. * @return Array
  162. */
  163. function wfMergeErrorArrays( /*...*/ ) {
  164. $args = func_get_args();
  165. $out = array();
  166. foreach ( $args as $errors ) {
  167. foreach ( $errors as $params ) {
  168. # @todo FIXME: Sometimes get nested arrays for $params,
  169. # which leads to E_NOTICEs
  170. $spec = implode( "\t", $params );
  171. $out[$spec] = $params;
  172. }
  173. }
  174. return array_values( $out );
  175. }
  176. /**
  177. * Insert array into another array after the specified *KEY*
  178. *
  179. * @param $array Array: The array.
  180. * @param $insert Array: The array to insert.
  181. * @param $after Mixed: The key to insert after
  182. * @return Array
  183. */
  184. function wfArrayInsertAfter( $array, $insert, $after ) {
  185. // Find the offset of the element to insert after.
  186. $keys = array_keys( $array );
  187. $offsetByKey = array_flip( $keys );
  188. $offset = $offsetByKey[$after];
  189. // Insert at the specified offset
  190. $before = array_slice( $array, 0, $offset + 1, true );
  191. $after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
  192. $output = $before + $insert + $after;
  193. return $output;
  194. }
  195. /**
  196. * Recursively converts the parameter (an object) to an array with the same data
  197. *
  198. * @param $objOrArray Object|Array
  199. * @param $recursive Bool
  200. * @return Array
  201. */
  202. function wfObjectToArray( $objOrArray, $recursive = true ) {
  203. $array = array();
  204. if( is_object( $objOrArray ) ) {
  205. $objOrArray = get_object_vars( $objOrArray );
  206. }
  207. foreach ( $objOrArray as $key => $value ) {
  208. if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
  209. $value = wfObjectToArray( $value );
  210. }
  211. $array[$key] = $value;
  212. }
  213. return $array;
  214. }
  215. /**
  216. * Wrapper around array_map() which also taints variables
  217. *
  218. * @param $function Callback
  219. * @param $input Array
  220. * @return Array
  221. */
  222. function wfArrayMap( $function, $input ) {
  223. $ret = array_map( $function, $input );
  224. foreach ( $ret as $key => $value ) {
  225. $taint = istainted( $input[$key] );
  226. if ( $taint ) {
  227. taint( $ret[$key], $taint );
  228. }
  229. }
  230. return $ret;
  231. }
  232. /**
  233. * Get a random decimal value between 0 and 1, in a way
  234. * not likely to give duplicate values for any realistic
  235. * number of articles.
  236. *
  237. * @return string
  238. */
  239. function wfRandom() {
  240. # The maximum random value is "only" 2^31-1, so get two random
  241. # values to reduce the chance of dupes
  242. $max = mt_getrandmax() + 1;
  243. $rand = number_format( ( mt_rand() * $max + mt_rand() )
  244. / $max / $max, 12, '.', '' );
  245. return $rand;
  246. }
  247. /**
  248. * We want some things to be included as literal characters in our title URLs
  249. * for prettiness, which urlencode encodes by default. According to RFC 1738,
  250. * all of the following should be safe:
  251. *
  252. * ;:@&=$-_.+!*'(),
  253. *
  254. * But + is not safe because it's used to indicate a space; &= are only safe in
  255. * paths and not in queries (and we don't distinguish here); ' seems kind of
  256. * scary; and urlencode() doesn't touch -_. to begin with. Plus, although /
  257. * is reserved, we don't care. So the list we unescape is:
  258. *
  259. * ;:@$!*(),/
  260. *
  261. * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
  262. * so no fancy : for IIS7.
  263. *
  264. * %2F in the page titles seems to fatally break for some reason.
  265. *
  266. * @param $s String:
  267. * @return string
  268. */
  269. function wfUrlencode( $s ) {
  270. static $needle;
  271. if ( is_null( $s ) ) {
  272. $needle = null;
  273. return '';
  274. }
  275. if ( is_null( $needle ) ) {
  276. $needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
  277. if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) || ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false ) ) {
  278. $needle[] = '%3A';
  279. }
  280. }
  281. $s = urlencode( $s );
  282. $s = str_ireplace(
  283. $needle,
  284. array( ';', '@', '$', '!', '*', '(', ')', ',', '/', ':' ),
  285. $s
  286. );
  287. return $s;
  288. }
  289. /**
  290. * This function takes two arrays as input, and returns a CGI-style string, e.g.
  291. * "days=7&limit=100". Options in the first array override options in the second.
  292. * Options set to null or false will not be output.
  293. *
  294. * @param $array1 Array ( String|Array )
  295. * @param $array2 Array ( String|Array )
  296. * @param $prefix String
  297. * @return String
  298. */
  299. function wfArrayToCGI( $array1, $array2 = null, $prefix = '' ) {
  300. if ( !is_null( $array2 ) ) {
  301. $array1 = $array1 + $array2;
  302. }
  303. $cgi = '';
  304. foreach ( $array1 as $key => $value ) {
  305. if ( !is_null($value) && $value !== false ) {
  306. if ( $cgi != '' ) {
  307. $cgi .= '&';
  308. }
  309. if ( $prefix !== '' ) {
  310. $key = $prefix . "[$key]";
  311. }
  312. if ( is_array( $value ) ) {
  313. $firstTime = true;
  314. foreach ( $value as $k => $v ) {
  315. $cgi .= $firstTime ? '' : '&';
  316. if ( is_array( $v ) ) {
  317. $cgi .= wfArrayToCGI( $v, null, $key . "[$k]" );
  318. } else {
  319. $cgi .= urlencode( $key . "[$k]" ) . '=' . urlencode( $v );
  320. }
  321. $firstTime = false;
  322. }
  323. } else {
  324. if ( is_object( $value ) ) {
  325. $value = $value->__toString();
  326. }
  327. $cgi .= urlencode( $key ) . '=' . urlencode( $value );
  328. }
  329. }
  330. }
  331. return $cgi;
  332. }
  333. /**
  334. * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
  335. * its argument and returns the same string in array form. This allows compa-
  336. * tibility with legacy functions that accept raw query strings instead of nice
  337. * arrays. Of course, keys and values are urldecode()d.
  338. *
  339. * @param $query String: query string
  340. * @return array Array version of input
  341. */
  342. function wfCgiToArray( $query ) {
  343. if ( isset( $query[0] ) && $query[0] == '?' ) {
  344. $query = substr( $query, 1 );
  345. }
  346. $bits = explode( '&', $query );
  347. $ret = array();
  348. foreach ( $bits as $bit ) {
  349. if ( $bit === '' ) {
  350. continue;
  351. }
  352. if ( strpos( $bit, '=' ) === false ) {
  353. // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
  354. $key = $bit;
  355. $value = '';
  356. } else {
  357. list( $key, $value ) = explode( '=', $bit );
  358. }
  359. $key = urldecode( $key );
  360. $value = urldecode( $value );
  361. if ( strpos( $key, '[' ) !== false ) {
  362. $keys = array_reverse( explode( '[', $key ) );
  363. $key = array_pop( $keys );
  364. $temp = $value;
  365. foreach ( $keys as $k ) {
  366. $k = substr( $k, 0, -1 );
  367. $temp = array( $k => $temp );
  368. }
  369. if ( isset( $ret[$key] ) ) {
  370. $ret[$key] = array_merge( $ret[$key], $temp );
  371. } else {
  372. $ret[$key] = $temp;
  373. }
  374. } else {
  375. $ret[$key] = $value;
  376. }
  377. }
  378. return $ret;
  379. }
  380. /**
  381. * Append a query string to an existing URL, which may or may not already
  382. * have query string parameters already. If so, they will be combined.
  383. *
  384. * @param $url String
  385. * @param $query Mixed: string or associative array
  386. * @return string
  387. */
  388. function wfAppendQuery( $url, $query ) {
  389. if ( is_array( $query ) ) {
  390. $query = wfArrayToCGI( $query );
  391. }
  392. if( $query != '' ) {
  393. if( false === strpos( $url, '?' ) ) {
  394. $url .= '?';
  395. } else {
  396. $url .= '&';
  397. }
  398. $url .= $query;
  399. }
  400. return $url;
  401. }
  402. /**
  403. * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
  404. * is correct.
  405. *
  406. * The meaning of the PROTO_* constants is as follows:
  407. * PROTO_HTTP: Output a URL starting with http://
  408. * PROTO_HTTPS: Output a URL starting with https://
  409. * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
  410. * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending on which protocol was used for the current incoming request
  411. * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer. For protocol-relative URLs, use the protocol of $wgCanonicalServer
  412. * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
  413. *
  414. * @todo this won't work with current-path-relative URLs
  415. * like "subdir/foo.html", etc.
  416. *
  417. * @param $url String: either fully-qualified or a local path + query
  418. * @param $defaultProto Mixed: one of the PROTO_* constants. Determines the
  419. * protocol to use if $url or $wgServer is
  420. * protocol-relative
  421. * @return string Fully-qualified URL, current-path-relative URL or false if
  422. * no valid URL can be constructed
  423. */
  424. function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
  425. global $wgServer, $wgCanonicalServer, $wgInternalServer;
  426. $serverUrl = $wgServer;
  427. if ( $defaultProto === PROTO_CANONICAL ) {
  428. $serverUrl = $wgCanonicalServer;
  429. }
  430. // Make $wgInternalServer fall back to $wgServer if not set
  431. if ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
  432. $serverUrl = $wgInternalServer;
  433. }
  434. if ( $defaultProto === PROTO_CURRENT ) {
  435. $defaultProto = WebRequest::detectProtocol() . '://';
  436. }
  437. // Analyze $serverUrl to obtain its protocol
  438. $bits = wfParseUrl( $serverUrl );
  439. $serverHasProto = $bits && $bits['scheme'] != '';
  440. if ( $defaultProto === PROTO_CANONICAL || $defaultProto === PROTO_INTERNAL ) {
  441. if ( $serverHasProto ) {
  442. $defaultProto = $bits['scheme'] . '://';
  443. } else {
  444. // $wgCanonicalServer or $wgInternalServer doesn't have a protocol. This really isn't supposed to happen
  445. // Fall back to HTTP in this ridiculous case
  446. $defaultProto = PROTO_HTTP;
  447. }
  448. }
  449. $defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 );
  450. if ( substr( $url, 0, 2 ) == '//' ) {
  451. $url = $defaultProtoWithoutSlashes . $url;
  452. } elseif ( substr( $url, 0, 1 ) == '/' ) {
  453. // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes, otherwise leave it alone
  454. $url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
  455. }
  456. $bits = wfParseUrl( $url );
  457. if ( $bits && isset( $bits['path'] ) ) {
  458. $bits['path'] = wfRemoveDotSegments( $bits['path'] );
  459. return wfAssembleUrl( $bits );
  460. } elseif ( $bits ) {
  461. # No path to expand
  462. return $url;
  463. } elseif ( substr( $url, 0, 1 ) != '/' ) {
  464. # URL is a relative path
  465. return wfRemoveDotSegments( $url );
  466. }
  467. # Expanded URL is not valid.
  468. return false;
  469. }
  470. /**
  471. * This function will reassemble a URL parsed with wfParseURL. This is useful
  472. * if you need to edit part of a URL and put it back together.
  473. *
  474. * This is the basic structure used (brackets contain keys for $urlParts):
  475. * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
  476. *
  477. * @todo Need to integrate this into wfExpandUrl (bug 32168)
  478. *
  479. * @since 1.19
  480. * @param $urlParts Array URL parts, as output from wfParseUrl
  481. * @return string URL assembled from its component parts
  482. */
  483. function wfAssembleUrl( $urlParts ) {
  484. $result = '';
  485. if ( isset( $urlParts['delimiter'] ) ) {
  486. if ( isset( $urlParts['scheme'] ) ) {
  487. $result .= $urlParts['scheme'];
  488. }
  489. $result .= $urlParts['delimiter'];
  490. }
  491. if ( isset( $urlParts['host'] ) ) {
  492. if ( isset( $urlParts['user'] ) ) {
  493. $result .= $urlParts['user'];
  494. if ( isset( $urlParts['pass'] ) ) {
  495. $result .= ':' . $urlParts['pass'];
  496. }
  497. $result .= '@';
  498. }
  499. $result .= $urlParts['host'];
  500. if ( isset( $urlParts['port'] ) ) {
  501. $result .= ':' . $urlParts['port'];
  502. }
  503. }
  504. if ( isset( $urlParts['path'] ) ) {
  505. $result .= $urlParts['path'];
  506. }
  507. if ( isset( $urlParts['query'] ) ) {
  508. $result .= '?' . $urlParts['query'];
  509. }
  510. if ( isset( $urlParts['fragment'] ) ) {
  511. $result .= '#' . $urlParts['fragment'];
  512. }
  513. return $result;
  514. }
  515. /**
  516. * Remove all dot-segments in the provided URL path. For example,
  517. * '/a/./b/../c/' becomes '/a/c/'. For details on the algorithm, please see
  518. * RFC3986 section 5.2.4.
  519. *
  520. * @todo Need to integrate this into wfExpandUrl (bug 32168)
  521. *
  522. * @param $urlPath String URL path, potentially containing dot-segments
  523. * @return string URL path with all dot-segments removed
  524. */
  525. function wfRemoveDotSegments( $urlPath ) {
  526. $output = '';
  527. $inputOffset = 0;
  528. $inputLength = strlen( $urlPath );
  529. while ( $inputOffset < $inputLength ) {
  530. $prefixLengthOne = substr( $urlPath, $inputOffset, 1 );
  531. $prefixLengthTwo = substr( $urlPath, $inputOffset, 2 );
  532. $prefixLengthThree = substr( $urlPath, $inputOffset, 3 );
  533. $prefixLengthFour = substr( $urlPath, $inputOffset, 4 );
  534. $trimOutput = false;
  535. if ( $prefixLengthTwo == './' ) {
  536. # Step A, remove leading "./"
  537. $inputOffset += 2;
  538. } elseif ( $prefixLengthThree == '../' ) {
  539. # Step A, remove leading "../"
  540. $inputOffset += 3;
  541. } elseif ( ( $prefixLengthTwo == '/.' ) && ( $inputOffset + 2 == $inputLength ) ) {
  542. # Step B, replace leading "/.$" with "/"
  543. $inputOffset += 1;
  544. $urlPath[$inputOffset] = '/';
  545. } elseif ( $prefixLengthThree == '/./' ) {
  546. # Step B, replace leading "/./" with "/"
  547. $inputOffset += 2;
  548. } elseif ( $prefixLengthThree == '/..' && ( $inputOffset + 3 == $inputLength ) ) {
  549. # Step C, replace leading "/..$" with "/" and
  550. # remove last path component in output
  551. $inputOffset += 2;
  552. $urlPath[$inputOffset] = '/';
  553. $trimOutput = true;
  554. } elseif ( $prefixLengthFour == '/../' ) {
  555. # Step C, replace leading "/../" with "/" and
  556. # remove last path component in output
  557. $inputOffset += 3;
  558. $trimOutput = true;
  559. } elseif ( ( $prefixLengthOne == '.' ) && ( $inputOffset + 1 == $inputLength ) ) {
  560. # Step D, remove "^.$"
  561. $inputOffset += 1;
  562. } elseif ( ( $prefixLengthTwo == '..' ) && ( $inputOffset + 2 == $inputLength ) ) {
  563. # Step D, remove "^..$"
  564. $inputOffset += 2;
  565. } else {
  566. # Step E, move leading path segment to output
  567. if ( $prefixLengthOne == '/' ) {
  568. $slashPos = strpos( $urlPath, '/', $inputOffset + 1 );
  569. } else {
  570. $slashPos = strpos( $urlPath, '/', $inputOffset );
  571. }
  572. if ( $slashPos === false ) {
  573. $output .= substr( $urlPath, $inputOffset );
  574. $inputOffset = $inputLength;
  575. } else {
  576. $output .= substr( $urlPath, $inputOffset, $slashPos - $inputOffset );
  577. $inputOffset += $slashPos - $inputOffset;
  578. }
  579. }
  580. if ( $trimOutput ) {
  581. $slashPos = strrpos( $output, '/' );
  582. if ( $slashPos === false ) {
  583. $output = '';
  584. } else {
  585. $output = substr( $output, 0, $slashPos );
  586. }
  587. }
  588. }
  589. return $output;
  590. }
  591. /**
  592. * Returns a regular expression of url protocols
  593. *
  594. * @param $includeProtocolRelative bool If false, remove '//' from the returned protocol list.
  595. * DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
  596. * @return String
  597. */
  598. function wfUrlProtocols( $includeProtocolRelative = true ) {
  599. global $wgUrlProtocols;
  600. // Cache return values separately based on $includeProtocolRelative
  601. static $withProtRel = null, $withoutProtRel = null;
  602. $cachedValue = $includeProtocolRelative ? $withProtRel : $withoutProtRel;
  603. if ( !is_null( $cachedValue ) ) {
  604. return $cachedValue;
  605. }
  606. // Support old-style $wgUrlProtocols strings, for backwards compatibility
  607. // with LocalSettings files from 1.5
  608. if ( is_array( $wgUrlProtocols ) ) {
  609. $protocols = array();
  610. foreach ( $wgUrlProtocols as $protocol ) {
  611. // Filter out '//' if !$includeProtocolRelative
  612. if ( $includeProtocolRelative || $protocol !== '//' ) {
  613. $protocols[] = preg_quote( $protocol, '/' );
  614. }
  615. }
  616. $retval = implode( '|', $protocols );
  617. } else {
  618. // Ignore $includeProtocolRelative in this case
  619. // This case exists for pre-1.6 compatibility, and we can safely assume
  620. // that '//' won't appear in a pre-1.6 config because protocol-relative
  621. // URLs weren't supported until 1.18
  622. $retval = $wgUrlProtocols;
  623. }
  624. // Cache return value
  625. if ( $includeProtocolRelative ) {
  626. $withProtRel = $retval;
  627. } else {
  628. $withoutProtRel = $retval;
  629. }
  630. return $retval;
  631. }
  632. /**
  633. * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
  634. * you need a regex that matches all URL protocols but does not match protocol-
  635. * relative URLs
  636. * @return String
  637. */
  638. function wfUrlProtocolsWithoutProtRel() {
  639. return wfUrlProtocols( false );
  640. }
  641. /**
  642. * parse_url() work-alike, but non-broken. Differences:
  643. *
  644. * 1) Does not raise warnings on bad URLs (just returns false)
  645. * 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
  646. * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
  647. *
  648. * @param $url String: a URL to parse
  649. * @return Array: bits of the URL in an associative array, per PHP docs
  650. */
  651. function wfParseUrl( $url ) {
  652. global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
  653. // Protocol-relative URLs are handled really badly by parse_url(). It's so bad that the easiest
  654. // way to handle them is to just prepend 'http:' and strip the protocol out later
  655. $wasRelative = substr( $url, 0, 2 ) == '//';
  656. if ( $wasRelative ) {
  657. $url = "http:$url";
  658. }
  659. wfSuppressWarnings();
  660. $bits = parse_url( $url );
  661. wfRestoreWarnings();
  662. // parse_url() returns an array without scheme for some invalid URLs, e.g.
  663. // parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
  664. if ( !$bits || !isset( $bits['scheme'] ) ) {
  665. return false;
  666. }
  667. // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
  668. if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
  669. $bits['delimiter'] = '://';
  670. } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
  671. $bits['delimiter'] = ':';
  672. // parse_url detects for news: and mailto: the host part of an url as path
  673. // We have to correct this wrong detection
  674. if ( isset( $bits['path'] ) ) {
  675. $bits['host'] = $bits['path'];
  676. $bits['path'] = '';
  677. }
  678. } else {
  679. return false;
  680. }
  681. /* Provide an empty host for eg. file:/// urls (see bug 28627) */
  682. if ( !isset( $bits['host'] ) ) {
  683. $bits['host'] = '';
  684. /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
  685. if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
  686. $bits['path'] = '/' . $bits['path'];
  687. }
  688. }
  689. // If the URL was protocol-relative, fix scheme and delimiter
  690. if ( $wasRelative ) {
  691. $bits['scheme'] = '';
  692. $bits['delimiter'] = '//';
  693. }
  694. return $bits;
  695. }
  696. /**
  697. * Make URL indexes, appropriate for the el_index field of externallinks.
  698. *
  699. * @param $url String
  700. * @return array
  701. */
  702. function wfMakeUrlIndexes( $url ) {
  703. $bits = wfParseUrl( $url );
  704. // Reverse the labels in the hostname, convert to lower case
  705. // For emails reverse domainpart only
  706. if ( $bits['scheme'] == 'mailto' ) {
  707. $mailparts = explode( '@', $bits['host'], 2 );
  708. if ( count( $mailparts ) === 2 ) {
  709. $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
  710. } else {
  711. // No domain specified, don't mangle it
  712. $domainpart = '';
  713. }
  714. $reversedHost = $domainpart . '@' . $mailparts[0];
  715. } else {
  716. $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
  717. }
  718. // Add an extra dot to the end
  719. // Why? Is it in wrong place in mailto links?
  720. if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
  721. $reversedHost .= '.';
  722. }
  723. // Reconstruct the pseudo-URL
  724. $prot = $bits['scheme'];
  725. $index = $prot . $bits['delimiter'] . $reversedHost;
  726. // Leave out user and password. Add the port, path, query and fragment
  727. if ( isset( $bits['port'] ) ) {
  728. $index .= ':' . $bits['port'];
  729. }
  730. if ( isset( $bits['path'] ) ) {
  731. $index .= $bits['path'];
  732. } else {
  733. $index .= '/';
  734. }
  735. if ( isset( $bits['query'] ) ) {
  736. $index .= '?' . $bits['query'];
  737. }
  738. if ( isset( $bits['fragment'] ) ) {
  739. $index .= '#' . $bits['fragment'];
  740. }
  741. if ( $prot == '' ) {
  742. return array( "http:$index", "https:$index" );
  743. } else {
  744. return array( $index );
  745. }
  746. }
  747. /**
  748. * Check whether a given URL has a domain that occurs in a given set of domains
  749. * @param $url string URL
  750. * @param $domains array Array of domains (strings)
  751. * @return bool True if the host part of $url ends in one of the strings in $domains
  752. */
  753. function wfMatchesDomainList( $url, $domains ) {
  754. $bits = wfParseUrl( $url );
  755. if ( is_array( $bits ) && isset( $bits['host'] ) ) {
  756. foreach ( (array)$domains as $domain ) {
  757. // FIXME: This gives false positives. http://nds-nl.wikipedia.org will match nl.wikipedia.org
  758. // We should use something that interprets dots instead
  759. if ( substr( $bits['host'], -strlen( $domain ) ) === $domain ) {
  760. return true;
  761. }
  762. }
  763. }
  764. return false;
  765. }
  766. /**
  767. * Sends a line to the debug log if enabled or, optionally, to a comment in output.
  768. * In normal operation this is a NOP.
  769. *
  770. * Controlling globals:
  771. * $wgDebugLogFile - points to the log file
  772. * $wgProfileOnly - if set, normal debug messages will not be recorded.
  773. * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
  774. * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
  775. *
  776. * @param $text String
  777. * @param $logonly Bool: set true to avoid appearing in HTML when $wgDebugComments is set
  778. */
  779. function wfDebug( $text, $logonly = false ) {
  780. global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
  781. global $wgDebugLogPrefix, $wgShowDebug;
  782. static $cache = array(); // Cache of unoutputted messages
  783. $text = wfDebugTimer() . $text;
  784. if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
  785. return;
  786. }
  787. if ( ( $wgDebugComments || $wgShowDebug ) && !$logonly ) {
  788. $cache[] = $text;
  789. if ( isset( $wgOut ) && is_object( $wgOut ) ) {
  790. // add the message and any cached messages to the output
  791. array_map( array( $wgOut, 'debug' ), $cache );
  792. $cache = array();
  793. }
  794. }
  795. if ( wfRunHooks( 'Debug', array( $text, null /* no log group */ ) ) ) {
  796. if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
  797. # Strip unprintables; they can switch terminal modes when binary data
  798. # gets dumped, which is pretty annoying.
  799. $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
  800. $text = $wgDebugLogPrefix . $text;
  801. wfErrorLog( $text, $wgDebugLogFile );
  802. }
  803. }
  804. MWDebug::debugMsg( $text );
  805. }
  806. /**
  807. * Returns true if debug logging should be suppressed if $wgDebugRawPage = false
  808. */
  809. function wfIsDebugRawPage() {
  810. static $cache;
  811. if ( $cache !== null ) {
  812. return $cache;
  813. }
  814. # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
  815. if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
  816. || (
  817. isset( $_SERVER['SCRIPT_NAME'] )
  818. && substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
  819. ) )
  820. {
  821. $cache = true;
  822. } else {
  823. $cache = false;
  824. }
  825. return $cache;
  826. }
  827. /**
  828. * Get microsecond timestamps for debug logs
  829. *
  830. * @return string
  831. */
  832. function wfDebugTimer() {
  833. global $wgDebugTimestamps, $wgRequestTime;
  834. if ( !$wgDebugTimestamps ) {
  835. return '';
  836. }
  837. $prefix = sprintf( "%6.4f", microtime( true ) - $wgRequestTime );
  838. $mem = sprintf( "%5.1fM", ( memory_get_usage( true ) / ( 1024 * 1024 ) ) );
  839. return "$prefix $mem ";
  840. }
  841. /**
  842. * Send a line giving PHP memory usage.
  843. *
  844. * @param $exact Bool: print exact values instead of kilobytes (default: false)
  845. */
  846. function wfDebugMem( $exact = false ) {
  847. $mem = memory_get_usage();
  848. if( !$exact ) {
  849. $mem = floor( $mem / 1024 ) . ' kilobytes';
  850. } else {
  851. $mem .= ' bytes';
  852. }
  853. wfDebug( "Memory usage: $mem\n" );
  854. }
  855. /**
  856. * Send a line to a supplementary debug log file, if configured, or main debug log if not.
  857. * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
  858. *
  859. * @param $logGroup String
  860. * @param $text String
  861. * @param $public Bool: whether to log the event in the public log if no private
  862. * log file is specified, (default true)
  863. */
  864. function wfDebugLog( $logGroup, $text, $public = true ) {
  865. global $wgDebugLogGroups;
  866. $text = trim( $text ) . "\n";
  867. if( isset( $wgDebugLogGroups[$logGroup] ) ) {
  868. $time = wfTimestamp( TS_DB );
  869. $wiki = wfWikiID();
  870. $host = wfHostname();
  871. if ( wfRunHooks( 'Debug', array( $text, $logGroup ) ) ) {
  872. wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
  873. }
  874. } elseif ( $public === true ) {
  875. wfDebug( $text, true );
  876. }
  877. }
  878. /**
  879. * Log for database errors
  880. *
  881. * @param $text String: database error message.
  882. */
  883. function wfLogDBError( $text ) {
  884. global $wgDBerrorLog;
  885. if ( $wgDBerrorLog ) {
  886. $host = wfHostname();
  887. $wiki = wfWikiID();
  888. $text = date( 'D M j G:i:s T Y' ) . "\t$host\t$wiki\t$text";
  889. wfErrorLog( $text, $wgDBerrorLog );
  890. }
  891. }
  892. /**
  893. * Throws a warning that $function is deprecated
  894. *
  895. * @param $function String
  896. * @param $version String|false: Added in 1.19.
  897. * @param $component String|false: Added in 1.19.
  898. *
  899. * @return null
  900. */
  901. function wfDeprecated( $function, $version = false, $component = false ) {
  902. static $functionsWarned = array();
  903. MWDebug::deprecated( $function, $version, $component );
  904. if ( !isset( $functionsWarned[$function] ) ) {
  905. $functionsWarned[$function] = true;
  906. if ( $version ) {
  907. global $wgDeprecationReleaseLimit;
  908. if ( $wgDeprecationReleaseLimit && $component === false ) {
  909. # Strip -* off the end of $version so that branches can use the
  910. # format #.##-branchname to avoid issues if the branch is merged into
  911. # a version of MediaWiki later than what it was branched from
  912. $comparableVersion = preg_replace( '/-.*$/', '', $version );
  913. # If the comparableVersion is larger than our release limit then
  914. # skip the warning message for the deprecation
  915. if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
  916. return;
  917. }
  918. }
  919. $component = $component === false ? 'MediaWiki' : $component;
  920. wfWarn( "Use of $function was deprecated in $component $version.", 2 );
  921. } else {
  922. wfWarn( "Use of $function is deprecated.", 2 );
  923. }
  924. }
  925. }
  926. /**
  927. * Send a warning either to the debug log or in a PHP error depending on
  928. * $wgDevelopmentWarnings
  929. *
  930. * @param $msg String: message to send
  931. * @param $callerOffset Integer: number of items to go back in the backtrace to
  932. * find the correct caller (1 = function calling wfWarn, ...)
  933. * @param $level Integer: PHP error level; only used when $wgDevelopmentWarnings
  934. * is true
  935. */
  936. function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
  937. global $wgDevelopmentWarnings;
  938. MWDebug::warning( $msg, $callerOffset + 2 );
  939. $callers = wfDebugBacktrace();
  940. if ( isset( $callers[$callerOffset + 1] ) ) {
  941. $callerfunc = $callers[$callerOffset + 1];
  942. $callerfile = $callers[$callerOffset];
  943. if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
  944. $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
  945. } else {
  946. $file = '(internal function)';
  947. }
  948. $func = '';
  949. if ( isset( $callerfunc['class'] ) ) {
  950. $func .= $callerfunc['class'] . '::';
  951. }
  952. if ( isset( $callerfunc['function'] ) ) {
  953. $func .= $callerfunc['function'];
  954. }
  955. $msg .= " [Called from $func in $file]";
  956. }
  957. if ( $wgDevelopmentWarnings ) {
  958. trigger_error( $msg, $level );
  959. } else {
  960. wfDebug( "$msg\n" );
  961. }
  962. }
  963. /**
  964. * Log to a file without getting "file size exceeded" signals.
  965. *
  966. * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
  967. * send lines to the specified port, prefixed by the specified prefix and a space.
  968. *
  969. * @param $text String
  970. * @param $file String filename
  971. */
  972. function wfErrorLog( $text, $file ) {
  973. if ( substr( $file, 0, 4 ) == 'udp:' ) {
  974. # Needs the sockets extension
  975. if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
  976. // IPv6 bracketed host
  977. $host = $m[2];
  978. $port = intval( $m[3] );
  979. $prefix = isset( $m[4] ) ? $m[4] : false;
  980. $domain = AF_INET6;
  981. } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
  982. $host = $m[2];
  983. if ( !IP::isIPv4( $host ) ) {
  984. $host = gethostbyname( $host );
  985. }
  986. $port = intval( $m[3] );
  987. $prefix = isset( $m[4] ) ? $m[4] : false;
  988. $domain = AF_INET;
  989. } else {
  990. throw new MWException( __METHOD__ . ': Invalid UDP specification' );
  991. }
  992. // Clean it up for the multiplexer
  993. if ( strval( $prefix ) !== '' ) {
  994. $text = preg_replace( '/^/m', $prefix . ' ', $text );
  995. // Limit to 64KB
  996. if ( strlen( $text ) > 65506 ) {
  997. $text = substr( $text, 0, 65506 );
  998. }
  999. if ( substr( $text, -1 ) != "\n" ) {
  1000. $text .= "\n";
  1001. }
  1002. } elseif ( strlen( $text ) > 65507 ) {
  1003. $text = substr( $text, 0, 65507 );
  1004. }
  1005. $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
  1006. if ( !$sock ) {
  1007. return;
  1008. }
  1009. socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
  1010. socket_close( $sock );
  1011. } else {
  1012. wfSuppressWarnings();
  1013. $exists = file_exists( $file );
  1014. $size = $exists ? filesize( $file ) : false;
  1015. if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
  1016. file_put_contents( $file, $text, FILE_APPEND );
  1017. }
  1018. wfRestoreWarnings();
  1019. }
  1020. }
  1021. /**
  1022. * @todo document
  1023. */
  1024. function wfLogProfilingData() {
  1025. global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
  1026. global $wgProfileLimit, $wgUser;
  1027. $profiler = Profiler::instance();
  1028. # Profiling must actually be enabled...
  1029. if ( $profiler->isStub() ) {
  1030. return;
  1031. }
  1032. // Get total page request time and only show pages that longer than
  1033. // $wgProfileLimit time (default is 0)
  1034. $elapsed = microtime( true ) - $wgRequestTime;
  1035. if ( $elapsed <= $wgProfileLimit ) {
  1036. return;
  1037. }
  1038. $profiler->logData();
  1039. // Check whether this should be logged in the debug file.
  1040. if ( $wgDebugLogFile == '' || ( !$wgDebugRawPage && wfIsDebugRawPage() ) ) {
  1041. return;
  1042. }
  1043. $forward = '';
  1044. if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
  1045. $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
  1046. }
  1047. if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
  1048. $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
  1049. }
  1050. if ( !empty( $_SERVER['HTTP_FROM'] ) ) {
  1051. $forward .= ' from ' . $_SERVER['HTTP_FROM'];
  1052. }
  1053. if ( $forward ) {
  1054. $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
  1055. }
  1056. // Don't load $wgUser at this late stage just for statistics purposes
  1057. // @todo FIXME: We can detect some anons even if it is not loaded. See User::getId()
  1058. if ( $wgUser->isItemLoaded( 'id' ) && $wgUser->isAnon() ) {
  1059. $forward .= ' anon';
  1060. }
  1061. $log = sprintf( "%s\t%04.3f\t%s\n",
  1062. gmdate( 'YmdHis' ), $elapsed,
  1063. urldecode( $wgRequest->getRequestURL() . $forward ) );
  1064. wfErrorLog( $log . $profiler->getOutput(), $wgDebugLogFile );
  1065. }
  1066. /**
  1067. * Check if the wiki read-only lock file is present. This can be used to lock
  1068. * off editing functions, but doesn't guarantee that the database will not be
  1069. * modified.
  1070. *
  1071. * @return bool
  1072. */
  1073. function wfReadOnly() {
  1074. global $wgReadOnlyFile, $wgReadOnly;
  1075. if ( !is_null( $wgReadOnly ) ) {
  1076. return (bool)$wgReadOnly;
  1077. }
  1078. if ( $wgReadOnlyFile == '' ) {
  1079. return false;
  1080. }
  1081. // Set $wgReadOnly for faster access next time
  1082. if ( is_file( $wgReadOnlyFile ) ) {
  1083. $wgReadOnly = file_get_contents( $wgReadOnlyFile );
  1084. } else {
  1085. $wgReadOnly = false;
  1086. }
  1087. return (bool)$wgReadOnly;
  1088. }
  1089. /**
  1090. * @return bool
  1091. */
  1092. function wfReadOnlyReason() {
  1093. global $wgReadOnly;
  1094. wfReadOnly();
  1095. return $wgReadOnly;
  1096. }
  1097. /**
  1098. * Return a Language object from $langcode
  1099. *
  1100. * @param $langcode Mixed: either:
  1101. * - a Language object
  1102. * - code of the language to get the message for, if it is
  1103. * a valid code create a language for that language, if
  1104. * it is a string but not a valid code then make a basic
  1105. * language object
  1106. * - a boolean: if it's false then use the global object for
  1107. * the current user's language (as a fallback for the old parameter
  1108. * functionality), or if it is true then use global object
  1109. * for the wiki's content language.
  1110. * @return Language object
  1111. */
  1112. function wfGetLangObj( $langcode = false ) {
  1113. # Identify which language to get or create a language object for.
  1114. # Using is_object here due to Stub objects.
  1115. if( is_object( $langcode ) ) {
  1116. # Great, we already have the object (hopefully)!
  1117. return $langcode;
  1118. }
  1119. global $wgContLang, $wgLanguageCode;
  1120. if( $langcode === true || $langcode === $wgLanguageCode ) {
  1121. # $langcode is the language code of the wikis content language object.
  1122. # or it is a boolean and value is true
  1123. return $wgContLang;
  1124. }
  1125. global $wgLang;
  1126. if( $langcode === false || $langcode === $wgLang->getCode() ) {
  1127. # $langcode is the language code of user language object.
  1128. # or it was a boolean and value is false
  1129. return $wgLang;
  1130. }
  1131. $validCodes = array_keys( Language::getLanguageNames() );
  1132. if( in_array( $langcode, $validCodes ) ) {
  1133. # $langcode corresponds to a valid language.
  1134. return Language::factory( $langcode );
  1135. }
  1136. # $langcode is a string, but not a valid language code; use content language.
  1137. wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
  1138. return $wgContLang;
  1139. }
  1140. /**
  1141. * Old function when $wgBetterDirectionality existed
  1142. * Removed in core, kept in extensions for backwards compat.
  1143. *
  1144. * @deprecated since 1.18
  1145. * @return Language
  1146. */
  1147. function wfUILang() {
  1148. wfDeprecated( __METHOD__, '1.18' );
  1149. global $wgLang;
  1150. return $wgLang;
  1151. }
  1152. /**
  1153. * This is the new function for getting translated interface messages.
  1154. * See the Message class for documentation how to use them.
  1155. * The intention is that this function replaces all old wfMsg* functions.
  1156. * @param $key \string Message key.
  1157. * Varargs: normal message parameters.
  1158. * @return Message
  1159. * @since 1.17
  1160. */
  1161. function wfMessage( $key /*...*/) {
  1162. $params = func_get_args();
  1163. array_shift( $params );
  1164. if ( isset( $params[0] ) && is_array( $params[0] ) ) {
  1165. $params = $params[0];
  1166. }
  1167. return new Message( $key, $params );
  1168. }
  1169. /**
  1170. * This function accepts multiple message keys and returns a message instance
  1171. * for the first message which is non-empty. If all messages are empty then an
  1172. * instance of the first message key is returned.
  1173. * @param varargs: message keys
  1174. * @return Message
  1175. * @since 1.18
  1176. */
  1177. function wfMessageFallback( /*...*/ ) {
  1178. $args = func_get_args();
  1179. return MWFunction::callArray( 'Message::newFallbackSequence', $args );
  1180. }
  1181. /**
  1182. * Get a message from anywhere, for the current user language.
  1183. *
  1184. * Use wfMsgForContent() instead if the message should NOT
  1185. * change depending on the user preferences.
  1186. *
  1187. * @param $key String: lookup key for the message, usually
  1188. * defined in languages/Language.php
  1189. *
  1190. * Parameters to the message, which can be used to insert variable text into
  1191. * it, can be passed to this function in the following formats:
  1192. * - One per argument, starting at the second parameter
  1193. * - As an array in the second parameter
  1194. * These are not shown in the function definition.
  1195. *
  1196. * @return String
  1197. */
  1198. function wfMsg( $key ) {
  1199. $args = func_get_args();
  1200. array_shift( $args );
  1201. return wfMsgReal( $key, $args );
  1202. }
  1203. /**
  1204. * Same as above except doesn't transform the message
  1205. *
  1206. * @param $key String
  1207. * @return String
  1208. */
  1209. function wfMsgNoTrans( $key ) {
  1210. $args = func_get_args();
  1211. array_shift( $args );
  1212. return wfMsgReal( $key, $args, true, false, false );
  1213. }
  1214. /**
  1215. * Get a message from anywhere, for the current global language
  1216. * set with $wgLanguageCode.
  1217. *
  1218. * Use this if the message should NOT change dependent on the
  1219. * language set in the user's preferences. This is the case for
  1220. * most text written into logs, as well as link targets (such as
  1221. * the name of the copyright policy page). Link titles, on the
  1222. * other hand, should be shown in the UI language.
  1223. *
  1224. * Note that MediaWiki allows users to change the user interface
  1225. * language in their preferences, but a single installation
  1226. * typically only contains content in one language.
  1227. *
  1228. * Be wary of this distinction: If you use wfMsg() where you should
  1229. * use wfMsgForContent(), a user of the software may have to
  1230. * customize potentially hundreds of messages in
  1231. * order to, e.g., fix a link in every possible language.
  1232. *
  1233. * @param $key String: lookup key for the message, usually
  1234. * defined in languages/Language.php
  1235. * @return String
  1236. */
  1237. function wfMsgForContent( $key ) {
  1238. global $wgForceUIMsgAsContentMsg;
  1239. $args = func_get_args();
  1240. array_shift( $args );
  1241. $forcontent = true;
  1242. if( is_array( $wgForceUIMsgAsContentMsg ) &&
  1243. in_array( $key, $wgForceUIMsgAsContentMsg ) )
  1244. {
  1245. $forcontent = false;
  1246. }
  1247. return wfMsgReal( $key, $args, true, $forcontent );
  1248. }
  1249. /**
  1250. * Same as above except doesn't transform the message
  1251. *
  1252. * @param $key String
  1253. * @return String
  1254. */
  1255. function wfMsgForContentNoTrans( $key ) {
  1256. global $wgForceUIMsgAsContentMsg;
  1257. $args = func_get_args();
  1258. array_shift( $args );
  1259. $forcontent = true;
  1260. if( is_array( $wgForceUIMsgAsContentMsg ) &&
  1261. in_array( $key, $wgForceUIMsgAsContentMsg ) )
  1262. {
  1263. $forcontent = false;
  1264. }
  1265. return wfMsgReal( $key, $args, true, $forcontent, false );
  1266. }
  1267. /**
  1268. * Really get a message
  1269. *
  1270. * @param $key String: key to get.
  1271. * @param $args
  1272. * @param $useDB Boolean
  1273. * @param $forContent Mixed: Language code, or false for user lang, true for content lang.
  1274. * @param $transform Boolean: Whether or not to transform the message.
  1275. * @return String: the requested message.
  1276. */
  1277. function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
  1278. wfProfileIn( __METHOD__ );
  1279. $message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
  1280. $message = wfMsgReplaceArgs( $message, $args );
  1281. wfProfileOut( __METHOD__ );
  1282. return $message;
  1283. }
  1284. /**
  1285. * Fetch a message string value, but don't replace any keys yet.
  1286. *
  1287. * @param $key String
  1288. * @param $useDB Bool
  1289. * @param $langCode String: Code of the language to get the message for, or
  1290. * behaves as a content language switch if it is a boolean.
  1291. * @param $transform Boolean: whether to parse magic words, etc.
  1292. * @return string
  1293. */
  1294. function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) {
  1295. wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
  1296. $cache = MessageCache::singleton();
  1297. $message = $cache->get( $key, $useDB, $langCode );
  1298. if( $message === false ) {
  1299. $message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
  1300. } elseif ( $transform ) {
  1301. $message = $cache->transform( $message );
  1302. }
  1303. return $message;
  1304. }
  1305. /**
  1306. * Replace message parameter keys on the given formatted output.
  1307. *
  1308. * @param $message String
  1309. * @param $args Array
  1310. * @return string
  1311. * @private
  1312. */
  1313. function wfMsgReplaceArgs( $message, $args ) {
  1314. # Fix windows line-endings
  1315. # Some messages are split with explode("\n", $msg)
  1316. $message = str_replace( "\r", '', $message );
  1317. // Replace arguments
  1318. if ( count( $args ) ) {
  1319. if ( is_array( $args[0] ) ) {
  1320. $args = array_values( $args[0] );
  1321. }
  1322. $replacementKeys = array();
  1323. foreach( $args as $n => $param ) {
  1324. $replacementKeys['$' . ( $n + 1 )] = $param;
  1325. }
  1326. $message = strtr( $message, $replacementKeys );
  1327. }
  1328. return $message;
  1329. }
  1330. /**
  1331. * Return an HTML-escaped version of a message.
  1332. * Parameter replacements, if any, are done *after* the HTML-escaping,
  1333. * so parameters may contain HTML (eg links or form controls). Be sure
  1334. * to pre-escape them if you really do want plaintext, or just wrap
  1335. * the whole thing in htmlspecialchars().
  1336. *
  1337. * @param $key String
  1338. * @param string ... parameters
  1339. * @return string
  1340. */
  1341. function wfMsgHtml( $key ) {
  1342. $args = func_get_args();
  1343. array_shift( $args );
  1344. return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key ) ), $args );
  1345. }
  1346. /**
  1347. * Return an HTML version of message
  1348. * Parameter replacements, if any, are done *after* parsing the wiki-text message,
  1349. * so parameters may contain HTML (eg links or form controls). Be sure
  1350. * to pre-escape them if you really do want plaintext, or just wrap
  1351. * the whole thing in htmlspecialchars().
  1352. *
  1353. * @param $key String
  1354. * @param string ... parameters
  1355. * @return string
  1356. */
  1357. function wfMsgWikiHtml( $key ) {
  1358. $args = func_get_args();
  1359. array_shift( $args );
  1360. return wfMsgReplaceArgs(
  1361. MessageCache::singleton()->parse( wfMsgGetKey( $key ), null,
  1362. /* can't be set to false */ true, /* interface */ true )->getText(),
  1363. $args );
  1364. }
  1365. /**
  1366. * Returns message in the requested format
  1367. * @param $key String: key of the message
  1368. * @param $options Array: processing rules. Can take the following options:
  1369. * <i>parse</i>: parses wikitext to HTML
  1370. * <i>parseinline</i>: parses wikitext to HTML and removes the surrounding
  1371. * p's added by parser or tidy
  1372. * <i>escape</i>: filters message through htmlspecialchars
  1373. * <i>escapenoentities</i>: same, but allows entity references like &#160; through
  1374. * <i>replaceafter</i>: parameters are substituted after parsing or escaping
  1375. * <i>parsemag</i>: transform the message using magic phrases
  1376. * <i>content</i>: fetch message for content language instead of interface
  1377. * Also can accept a single associative argument, of the form 'language' => 'xx':
  1378. * <i>language</i>: Language object or language code to fetch message for
  1379. * (overriden by <i>content</i>).
  1380. * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
  1381. *
  1382. * @return String
  1383. */
  1384. function wfMsgExt( $key, $options ) {
  1385. $args = func_get_args();
  1386. array_shift( $args );
  1387. array_shift( $args );
  1388. $options = (array)$options;
  1389. foreach( $options as $arrayKey => $option ) {
  1390. if( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
  1391. # An unknown index, neither numeric nor "language"
  1392. wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING );
  1393. } elseif( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
  1394. array( 'parse', 'parseinline', 'escape', 'escapenoentities',
  1395. 'replaceafter', 'parsemag', 'content' ) ) ) {
  1396. # A numeric index with unknown value
  1397. wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING );
  1398. }
  1399. }
  1400. if( in_array( 'content', $options, true ) ) {
  1401. $forContent = true;
  1402. $langCode = true;
  1403. $langCodeObj = null;
  1404. } elseif( array_key_exists( 'language', $options ) ) {
  1405. $forContent = false;
  1406. $langCode = wfGetLangObj( $options['language'] );
  1407. $langCodeObj = $langCode;
  1408. } else {
  1409. $forContent = false;
  1410. $langCode = false;
  1411. $langCodeObj = null;
  1412. }
  1413. $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
  1414. if( !in_array( 'replaceafter', $options, true ) ) {
  1415. $string = wfMsgReplaceArgs( $string, $args );
  1416. }
  1417. $messageCache = MessageCache::singleton();
  1418. $parseInline = in_array( 'parseinline', $options, true );
  1419. if( in_array( 'parse', $options, true ) || $parseInline ) {
  1420. $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj );
  1421. if ( $string instanceof ParserOutput ) {
  1422. $string = $string->getText();
  1423. }
  1424. if ( $parseInline ) {
  1425. $m = array();
  1426. if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
  1427. $string = $m[1];
  1428. }
  1429. }
  1430. } elseif ( in_array( 'parsemag', $options, true ) ) {
  1431. $string = $messageCache->transform( $string,
  1432. !$forContent, $langCodeObj );
  1433. }
  1434. if ( in_array( 'escape', $options, true ) ) {
  1435. $string = htmlspecialchars ( $string );
  1436. } elseif ( in_array( 'escapenoentities', $options, true ) ) {
  1437. $string = Sanitizer::escapeHtmlAllowEntities( $string );
  1438. }
  1439. if( in_array( 'replaceafter', $options, true ) ) {
  1440. $string = wfMsgReplaceArgs( $string, $args );
  1441. }
  1442. return $string;
  1443. }
  1444. /**
  1445. * Since wfMsg() and co suck, they don't return false if the message key they
  1446. * looked up didn't exist but a XHTML string, this function checks for the
  1447. * nonexistance of messages by checking the MessageCache::get() result directly.
  1448. *
  1449. * @param $key String: the message key looked up
  1450. * @return Boolean True if the message *doesn't* exist.
  1451. */
  1452. function wfEmptyMsg( $key ) {
  1453. return MessageCache::singleton()->get( $key, /*useDB*/true, /*content*/false ) === false;
  1454. }
  1455. /**
  1456. * Throw a debugging exception. This function previously once exited the process,
  1457. * but now throws an exception instead, with similar results.
  1458. *
  1459. * @param $msg String: message shown when dying.
  1460. */
  1461. function wfDebugDieBacktrace( $msg = '' ) {
  1462. throw new MWException( $msg );
  1463. }
  1464. /**
  1465. * Fetch server name for use in error reporting etc.
  1466. * Use real server name if available, so we know which machine
  1467. * in a server farm generated the current page.
  1468. *
  1469. * @return string
  1470. */
  1471. function wfHostname() {
  1472. static $host;
  1473. if ( is_null( $host ) ) {
  1474. if ( function_exists( 'posix_uname' ) ) {
  1475. // This function not present on Windows
  1476. $uname = posix_uname();
  1477. } else {
  1478. $uname = false;
  1479. }
  1480. if( is_array( $uname ) && isset( $uname['nodename'] ) ) {
  1481. $host = $uname['nodename'];
  1482. } elseif ( getenv( 'COMPUTERNAME' ) ) {
  1483. # Windows computer name
  1484. $host = getenv( 'COMPUTERNAME' );
  1485. } else {
  1486. # This may be a virtual server.
  1487. $host = $_SERVER['SERVER_NAME'];
  1488. }
  1489. }
  1490. return $host;
  1491. }
  1492. /**
  1493. * Returns a HTML comment with the elapsed time since request.
  1494. * This method has no side effects.
  1495. *
  1496. * @return string
  1497. */
  1498. function wfReportTime() {
  1499. global $wgRequestTime, $wgShowHostnames;
  1500. $elapsed = microtime( true ) - $wgRequestTime;
  1501. return $wgShowHostnames
  1502. ? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
  1503. : sprintf( '<!-- Served in %01.3f secs. -->', $elapsed );
  1504. }
  1505. /**
  1506. * Safety wrapper for debug_backtrace().
  1507. *
  1508. * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
  1509. * murky circumstances, which may be triggered in part by stub objects
  1510. * or other fancy talkin'.
  1511. *
  1512. * Will return an empty array if Zend Optimizer is detected or if
  1513. * debug_backtrace is disabled, otherwise the output from
  1514. * debug_backtrace() (trimmed).
  1515. *
  1516. * @param $limit int This parameter can be used to limit the number of stack frames returned
  1517. *
  1518. * @return array of backtrace information
  1519. */
  1520. function wfDebugBacktrace( $limit = 0 ) {
  1521. static $disabled = null;
  1522. if( extension_loaded( 'Zend Optimizer' ) ) {
  1523. wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
  1524. return array();
  1525. }
  1526. if ( is_null( $disabled ) ) {
  1527. $disabled = false;
  1528. $functions = explode( ',', ini_get( 'disable_functions' ) );
  1529. $functions = array_map( 'trim', $functions );
  1530. $functions = array_map( 'strtolower', $functions );
  1531. if ( in_array( 'debug_backtrace', $functions ) ) {
  1532. wfDebug( "debug_backtrace is in disabled_functions\n" );
  1533. $disabled = true;
  1534. }
  1535. }
  1536. if ( $disabled ) {
  1537. return array();
  1538. }
  1539. if ( $limit && version_compare( PHP_VERSION, '5.4.0', '>=' ) ) {
  1540. return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit ), 1 );
  1541. } else {
  1542. return array_slice( debug_backtrace(), 1 );
  1543. }
  1544. }
  1545. /**
  1546. * Get a debug backtrace as a string
  1547. *
  1548. * @return string
  1549. */
  1550. function wfBacktrace() {
  1551. global $wgCommandLineMode;
  1552. if ( $wgCommandLineMode ) {
  1553. $msg = '';
  1554. } else {
  1555. $msg = "<ul>\n";
  1556. }
  1557. $backtrace = wfDebugBacktr

Large files files are truncated, but you can click here to view the full file