PageRenderTime 83ms CodeModel.GetById 22ms RepoModel.GetById 0ms 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
  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 = wfDebugBacktrace();
  1558. foreach( $backtrace as $call ) {
  1559. if( isset( $call['file'] ) ) {
  1560. $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
  1561. $file = $f[count( $f ) - 1];
  1562. } else {
  1563. $file = '-';
  1564. }
  1565. if( isset( $call['line'] ) ) {
  1566. $line = $call['line'];
  1567. } else {
  1568. $line = '-';
  1569. }
  1570. if ( $wgCommandLineMode ) {
  1571. $msg .= "$file line $line calls ";
  1572. } else {
  1573. $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
  1574. }
  1575. if( !empty( $call['class'] ) ) {
  1576. $msg .= $call['class'] . $call['type'];
  1577. }
  1578. $msg .= $call['function'] . '()';
  1579. if ( $wgCommandLineMode ) {
  1580. $msg .= "\n";
  1581. } else {
  1582. $msg .= "</li>\n";
  1583. }
  1584. }
  1585. if ( $wgCommandLineMode ) {
  1586. $msg .= "\n";
  1587. } else {
  1588. $msg .= "</ul>\n";
  1589. }
  1590. return $msg;
  1591. }
  1592. /**
  1593. * Get the name of the function which called this function
  1594. *
  1595. * @param $level Int
  1596. * @return Bool|string
  1597. */
  1598. function wfGetCaller( $level = 2 ) {
  1599. $backtrace = wfDebugBacktrace( $level );
  1600. if ( isset( $backtrace[$level] ) ) {
  1601. return wfFormatStackFrame( $backtrace[$level] );
  1602. } else {
  1603. $caller = 'unknown';
  1604. }
  1605. return $caller;
  1606. }
  1607. /**
  1608. * Return a string consisting of callers in the stack. Useful sometimes
  1609. * for profiling specific points.
  1610. *
  1611. * @param $limit The maximum depth of the stack frame to return, or false for
  1612. * the entire stack.
  1613. * @return String
  1614. */
  1615. function wfGetAllCallers( $limit = 3 ) {
  1616. $trace = array_reverse( wfDebugBacktrace() );
  1617. if ( !$limit || $limit > count( $trace ) - 1 ) {
  1618. $limit = count( $trace ) - 1;
  1619. }
  1620. $trace = array_slice( $trace, -$limit - 1, $limit );
  1621. return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
  1622. }
  1623. /**
  1624. * Return a string representation of frame
  1625. *
  1626. * @param $frame Array
  1627. * @return Bool
  1628. */
  1629. function wfFormatStackFrame( $frame ) {
  1630. return isset( $frame['class'] ) ?
  1631. $frame['class'] . '::' . $frame['function'] :
  1632. $frame['function'];
  1633. }
  1634. /* Some generic result counters, pulled out of SearchEngine */
  1635. /**
  1636. * @todo document
  1637. *
  1638. * @param $offset Int
  1639. * @param $limit Int
  1640. * @return String
  1641. */
  1642. function wfShowingResults( $offset, $limit ) {
  1643. global $wgLang;
  1644. return wfMsgExt(
  1645. 'showingresults',
  1646. array( 'parseinline' ),
  1647. $wgLang->formatNum( $limit ),
  1648. $wgLang->formatNum( $offset + 1 )
  1649. );
  1650. }
  1651. /**
  1652. * Generate (prev x| next x) (20|50|100...) type links for paging
  1653. *
  1654. * @param $offset String
  1655. * @param $limit Integer
  1656. * @param $link String
  1657. * @param $query String: optional URL query parameter string
  1658. * @param $atend Bool: optional param for specified if this is the last page
  1659. * @return String
  1660. * @deprecated in 1.19; use Language::viewPrevNext() instead
  1661. */
  1662. function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
  1663. wfDeprecated( __METHOD__, '1.19' );
  1664. global $wgLang;
  1665. $query = wfCgiToArray( $query );
  1666. if( is_object( $link ) ) {
  1667. $title = $link;
  1668. } else {
  1669. $title = Title::newFromText( $link );
  1670. if( is_null( $title ) ) {
  1671. return false;
  1672. }
  1673. }
  1674. return $wgLang->viewPrevNext( $title, $offset, $limit, $query, $atend );
  1675. }
  1676. /**
  1677. * Make a list item, used by various special pages
  1678. *
  1679. * @param $page String Page link
  1680. * @param $details String Text between brackets
  1681. * @param $oppositedm Boolean Add the direction mark opposite to your
  1682. * language, to display text properly
  1683. * @return String
  1684. * @deprecated since 1.19; use Language::specialList() instead
  1685. */
  1686. function wfSpecialList( $page, $details, $oppositedm = true ) {
  1687. global $wgLang;
  1688. return $wgLang->specialList( $page, $details, $oppositedm );
  1689. }
  1690. /**
  1691. * @todo document
  1692. * @todo FIXME: We may want to blacklist some broken browsers
  1693. *
  1694. * @param $force Bool
  1695. * @return bool Whereas client accept gzip compression
  1696. */
  1697. function wfClientAcceptsGzip( $force = false ) {
  1698. static $result = null;
  1699. if ( $result === null || $force ) {
  1700. $result = false;
  1701. if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
  1702. # @todo FIXME: We may want to blacklist some broken browsers
  1703. $m = array();
  1704. if( preg_match(
  1705. '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
  1706. $_SERVER['HTTP_ACCEPT_ENCODING'],
  1707. $m )
  1708. )
  1709. {
  1710. if( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
  1711. $result = false;
  1712. return $result;
  1713. }
  1714. wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
  1715. $result = true;
  1716. }
  1717. }
  1718. }
  1719. return $result;
  1720. }
  1721. /**
  1722. * Obtain the offset and limit values from the request string;
  1723. * used in special pages
  1724. *
  1725. * @param $deflimit Int default limit if none supplied
  1726. * @param $optionname String Name of a user preference to check against
  1727. * @return array
  1728. *
  1729. */
  1730. function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
  1731. global $wgRequest;
  1732. return $wgRequest->getLimitOffset( $deflimit, $optionname );
  1733. }
  1734. /**
  1735. * Escapes the given text so that it may be output using addWikiText()
  1736. * without any linking, formatting, etc. making its way through. This
  1737. * is achieved by substituting certain characters with HTML entities.
  1738. * As required by the callers, <nowiki> is not used.
  1739. *
  1740. * @param $text String: text to be escaped
  1741. * @return String
  1742. */
  1743. function wfEscapeWikiText( $text ) {
  1744. $text = strtr( "\n$text", array(
  1745. '"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
  1746. '=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
  1747. '{' => '&#123;', '|' => '&#124;', '}' => '&#125;',
  1748. "\n#" => "\n&#35;", "\n*" => "\n&#42;",
  1749. "\n:" => "\n&#58;", "\n;" => "\n&#59;",
  1750. '://' => '&#58;//', 'ISBN ' => 'ISBN&#32;', 'RFC ' => 'RFC&#32;',
  1751. ) );
  1752. return substr( $text, 1 );
  1753. }
  1754. /**
  1755. * Get the current unix timetstamp with microseconds. Useful for profiling
  1756. * @return Float
  1757. */
  1758. function wfTime() {
  1759. return microtime( true );
  1760. }
  1761. /**
  1762. * Sets dest to source and returns the original value of dest
  1763. * If source is NULL, it just returns the value, it doesn't set the variable
  1764. * If force is true, it will set the value even if source is NULL
  1765. *
  1766. * @param $dest Mixed
  1767. * @param $source Mixed
  1768. * @param $force Bool
  1769. * @return Mixed
  1770. */
  1771. function wfSetVar( &$dest, $source, $force = false ) {
  1772. $temp = $dest;
  1773. if ( !is_null( $source ) || $force ) {
  1774. $dest = $source;
  1775. }
  1776. return $temp;
  1777. }
  1778. /**
  1779. * As for wfSetVar except setting a bit
  1780. *
  1781. * @param $dest Int
  1782. * @param $bit Int
  1783. * @param $state Bool
  1784. *
  1785. * @return bool
  1786. */
  1787. function wfSetBit( &$dest, $bit, $state = true ) {
  1788. $temp = (bool)( $dest & $bit );
  1789. if ( !is_null( $state ) ) {
  1790. if ( $state ) {
  1791. $dest |= $bit;
  1792. } else {
  1793. $dest &= ~$bit;
  1794. }
  1795. }
  1796. return $temp;
  1797. }
  1798. /**
  1799. * A wrapper around the PHP function var_export().
  1800. * Either print it or add it to the regular output ($wgOut).
  1801. *
  1802. * @param $var A PHP variable to dump.
  1803. */
  1804. function wfVarDump( $var ) {
  1805. global $wgOut;
  1806. $s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
  1807. if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
  1808. print $s;
  1809. } else {
  1810. $wgOut->addHTML( $s );
  1811. }
  1812. }
  1813. /**
  1814. * Provide a simple HTTP error.
  1815. *
  1816. * @param $code Int|String
  1817. * @param $label String
  1818. * @param $desc String
  1819. */
  1820. function wfHttpError( $code, $label, $desc ) {
  1821. global $wgOut;
  1822. $wgOut->disable();
  1823. header( "HTTP/1.0 $code $label" );
  1824. header( "Status: $code $label" );
  1825. $wgOut->sendCacheControl();
  1826. header( 'Content-type: text/html; charset=utf-8' );
  1827. print "<!doctype html>" .
  1828. '<html><head><title>' .
  1829. htmlspecialchars( $label ) .
  1830. '</title></head><body><h1>' .
  1831. htmlspecialchars( $label ) .
  1832. '</h1><p>' .
  1833. nl2br( htmlspecialchars( $desc ) ) .
  1834. "</p></body></html>\n";
  1835. }
  1836. /**
  1837. * Clear away any user-level output buffers, discarding contents.
  1838. *
  1839. * Suitable for 'starting afresh', for instance when streaming
  1840. * relatively large amounts of data without buffering, or wanting to
  1841. * output image files without ob_gzhandler's compression.
  1842. *
  1843. * The optional $resetGzipEncoding parameter controls suppression of
  1844. * the Content-Encoding header sent by ob_gzhandler; by default it
  1845. * is left. See comments for wfClearOutputBuffers() for why it would
  1846. * be used.
  1847. *
  1848. * Note that some PHP configuration options may add output buffer
  1849. * layers which cannot be removed; these are left in place.
  1850. *
  1851. * @param $resetGzipEncoding Bool
  1852. */
  1853. function wfResetOutputBuffers( $resetGzipEncoding = true ) {
  1854. if( $resetGzipEncoding ) {
  1855. // Suppress Content-Encoding and Content-Length
  1856. // headers from 1.10+s wfOutputHandler
  1857. global $wgDisableOutputCompression;
  1858. $wgDisableOutputCompression = true;
  1859. }
  1860. while( $status = ob_get_status() ) {
  1861. if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
  1862. // Probably from zlib.output_compression or other
  1863. // PHP-internal setting which can't be removed.
  1864. //
  1865. // Give up, and hope the result doesn't break
  1866. // output behavior.
  1867. break;
  1868. }
  1869. if( !ob_end_clean() ) {
  1870. // Could not remove output buffer handler; abort now
  1871. // to avoid getting in some kind of infinite loop.
  1872. break;
  1873. }
  1874. if( $resetGzipEncoding ) {
  1875. if( $status['name'] == 'ob_gzhandler' ) {
  1876. // Reset the 'Content-Encoding' field set by this handler
  1877. // so we can start fresh.
  1878. if ( function_exists( 'header_remove' ) ) {
  1879. // Available since PHP 5.3.0
  1880. header_remove( 'Content-Encoding' );
  1881. } else {
  1882. // We need to provide a valid content-coding. See bug 28069
  1883. header( 'Content-Encoding: identity' );
  1884. }
  1885. break;
  1886. }
  1887. }
  1888. }
  1889. }
  1890. /**
  1891. * More legible than passing a 'false' parameter to wfResetOutputBuffers():
  1892. *
  1893. * Clear away output buffers, but keep the Content-Encoding header
  1894. * produced by ob_gzhandler, if any.
  1895. *
  1896. * This should be used for HTTP 304 responses, where you need to
  1897. * preserve the Content-Encoding header of the real result, but
  1898. * also need to suppress the output of ob_gzhandler to keep to spec
  1899. * and avoid breaking Firefox in rare cases where the headers and
  1900. * body are broken over two packets.
  1901. */
  1902. function wfClearOutputBuffers() {
  1903. wfResetOutputBuffers( false );
  1904. }
  1905. /**
  1906. * Converts an Accept-* header into an array mapping string values to quality
  1907. * factors
  1908. *
  1909. * @param $accept String
  1910. * @param $def String default
  1911. * @return Array
  1912. */
  1913. function wfAcceptToPrefs( $accept, $def = '*/*' ) {
  1914. # No arg means accept anything (per HTTP spec)
  1915. if( !$accept ) {
  1916. return array( $def => 1.0 );
  1917. }
  1918. $prefs = array();
  1919. $parts = explode( ',', $accept );
  1920. foreach( $parts as $part ) {
  1921. # @todo FIXME: Doesn't deal with params like 'text/html; level=1'
  1922. $values = explode( ';', trim( $part ) );
  1923. $match = array();
  1924. if ( count( $values ) == 1 ) {
  1925. $prefs[$values[0]] = 1.0;
  1926. } elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
  1927. $prefs[$values[0]] = floatval( $match[1] );
  1928. }
  1929. }
  1930. return $prefs;
  1931. }
  1932. /**
  1933. * Checks if a given MIME type matches any of the keys in the given
  1934. * array. Basic wildcards are accepted in the array keys.
  1935. *
  1936. * Returns the matching MIME type (or wildcard) if a match, otherwise
  1937. * NULL if no match.
  1938. *
  1939. * @param $type String
  1940. * @param $avail Array
  1941. * @return string
  1942. * @private
  1943. */
  1944. function mimeTypeMatch( $type, $avail ) {
  1945. if( array_key_exists( $type, $avail ) ) {
  1946. return $type;
  1947. } else {
  1948. $parts = explode( '/', $type );
  1949. if( array_key_exists( $parts[0] . '/*', $avail ) ) {
  1950. return $parts[0] . '/*';
  1951. } elseif( array_key_exists( '*/*', $avail ) ) {
  1952. return '*/*';
  1953. } else {
  1954. return null;
  1955. }
  1956. }
  1957. }
  1958. /**
  1959. * Returns the 'best' match between a client's requested internet media types
  1960. * and the server's list of available types. Each list should be an associative
  1961. * array of type to preference (preference is a float between 0.0 and 1.0).
  1962. * Wildcards in the types are acceptable.
  1963. *
  1964. * @param $cprefs Array: client's acceptable type list
  1965. * @param $sprefs Array: server's offered types
  1966. * @return string
  1967. *
  1968. * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
  1969. * XXX: generalize to negotiate other stuff
  1970. */
  1971. function wfNegotiateType( $cprefs, $sprefs ) {
  1972. $combine = array();
  1973. foreach( array_keys( $sprefs ) as $type ) {
  1974. $parts = explode( '/', $type );
  1975. if( $parts[1] != '*' ) {
  1976. $ckey = mimeTypeMatch( $type, $cprefs );
  1977. if( $ckey ) {
  1978. $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
  1979. }
  1980. }
  1981. }
  1982. foreach( array_keys( $cprefs ) as $type ) {
  1983. $parts = explode( '/', $type );
  1984. if( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
  1985. $skey = mimeTypeMatch( $type, $sprefs );
  1986. if( $skey ) {
  1987. $combine[$type] = $sprefs[$skey] * $cprefs[$type];
  1988. }
  1989. }
  1990. }
  1991. $bestq = 0;
  1992. $besttype = null;
  1993. foreach( array_keys( $combine ) as $type ) {
  1994. if( $combine[$type] > $bestq ) {
  1995. $besttype = $type;
  1996. $bestq = $combine[$type];
  1997. }
  1998. }
  1999. return $besttype;
  2000. }
  2001. /**
  2002. * Reference-counted warning suppression
  2003. *
  2004. * @param $end Bool
  2005. */
  2006. function wfSuppressWarnings( $end = false ) {
  2007. static $suppressCount = 0;
  2008. static $originalLevel = false;
  2009. if ( $end ) {
  2010. if ( $suppressCount ) {
  2011. --$suppressCount;
  2012. if ( !$suppressCount ) {
  2013. error_reporting( $originalLevel );
  2014. }
  2015. }
  2016. } else {
  2017. if ( !$suppressCount ) {
  2018. // E_DEPRECATED is undefined in PHP 5.2
  2019. if( !defined( 'E_DEPRECATED' ) ) {
  2020. define( 'E_DEPRECATED', 8192 );
  2021. }
  2022. $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED ) );
  2023. }
  2024. ++$suppressCount;
  2025. }
  2026. }
  2027. /**
  2028. * Restore error level to previous value
  2029. */
  2030. function wfRestoreWarnings() {
  2031. wfSuppressWarnings( true );
  2032. }
  2033. # Autodetect, convert and provide timestamps of various types
  2034. /**
  2035. * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
  2036. */
  2037. define( 'TS_UNIX', 0 );
  2038. /**
  2039. * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
  2040. */
  2041. define( 'TS_MW', 1 );
  2042. /**
  2043. * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
  2044. */
  2045. define( 'TS_DB', 2 );
  2046. /**
  2047. * RFC 2822 format, for E-mail and HTTP headers
  2048. */
  2049. define( 'TS_RFC2822', 3 );
  2050. /**
  2051. * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
  2052. *
  2053. * This is used by Special:Export
  2054. */
  2055. define( 'TS_ISO_8601', 4 );
  2056. /**
  2057. * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
  2058. *
  2059. * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
  2060. * DateTime tag and page 36 for the DateTimeOriginal and
  2061. * DateTimeDigitized tags.
  2062. */
  2063. define( 'TS_EXIF', 5 );
  2064. /**
  2065. * Oracle format time.
  2066. */
  2067. define( 'TS_ORACLE', 6 );
  2068. /**
  2069. * Postgres format time.
  2070. */
  2071. define( 'TS_POSTGRES', 7 );
  2072. /**
  2073. * DB2 format time
  2074. */
  2075. define( 'TS_DB2', 8 );
  2076. /**
  2077. * ISO 8601 basic format with no timezone: 19860209T200000Z. This is used by ResourceLoader
  2078. */
  2079. define( 'TS_ISO_8601_BASIC', 9 );
  2080. /**
  2081. * Get a timestamp string in one of various formats
  2082. *
  2083. * @param $outputtype Mixed: A timestamp in one of the supported formats, the
  2084. * function will autodetect which format is supplied and act
  2085. * accordingly.
  2086. * @param $ts Mixed: the timestamp to convert or 0 for the current timestamp
  2087. * @return Mixed: String / false The same date in the format specified in $outputtype or false
  2088. */
  2089. function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
  2090. $uts = 0;
  2091. $da = array();
  2092. $strtime = '';
  2093. if ( !$ts ) { // We want to catch 0, '', null... but not date strings starting with a letter.
  2094. $uts = time();
  2095. $strtime = "@$uts";
  2096. } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
  2097. # TS_DB
  2098. } elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
  2099. # TS_EXIF
  2100. } elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
  2101. # TS_MW
  2102. } elseif ( preg_match( '/^-?\d{1,13}$/D', $ts ) ) {
  2103. # TS_UNIX
  2104. $uts = $ts;
  2105. $strtime = "@$ts"; // http://php.net/manual/en/datetime.formats.compound.php
  2106. } elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
  2107. # TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
  2108. $strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
  2109. str_replace( '+00:00', 'UTC', $ts ) );
  2110. } elseif ( preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
  2111. # TS_ISO_8601
  2112. } elseif ( preg_match( '/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z$/', $ts, $da ) ) {
  2113. #TS_ISO_8601_BASIC
  2114. } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/', $ts, $da ) ) {
  2115. # TS_POSTGRES
  2116. } elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/', $ts, $da ) ) {
  2117. # TS_POSTGRES
  2118. } elseif (preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.\d\d\d$/', $ts, $da ) ) {
  2119. # TS_DB2
  2120. } elseif ( preg_match( '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' . # Day of week
  2121. '\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' . # dd Mon yyyy
  2122. '[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S', $ts ) ) { # hh:mm:ss
  2123. # TS_RFC2822, accepting a trailing comment. See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
  2124. # The regex is a superset of rfc2822 for readability
  2125. $strtime = strtok( $ts, ';' );
  2126. } elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
  2127. # TS_RFC850
  2128. $strtime = $ts;
  2129. } elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
  2130. # asctime
  2131. $strtime = $ts;
  2132. } else {
  2133. # Bogus value...
  2134. wfDebug("wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n");
  2135. return false;
  2136. }
  2137. static $formats = array(
  2138. TS_UNIX => 'U',
  2139. TS_MW => 'YmdHis',
  2140. TS_DB => 'Y-m-d H:i:s',
  2141. TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
  2142. TS_ISO_8601_BASIC => 'Ymd\THis\Z',
  2143. TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
  2144. TS_RFC2822 => 'D, d M Y H:i:s',
  2145. TS_ORACLE => 'd-m-Y H:i:s.000000', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
  2146. TS_POSTGRES => 'Y-m-d H:i:s',
  2147. TS_DB2 => 'Y-m-d H:i:s',
  2148. );
  2149. if ( !isset( $formats[$outputtype] ) ) {
  2150. throw new MWException( 'wfTimestamp() called with illegal output type.' );
  2151. }
  2152. if ( function_exists( "date_create" ) ) {
  2153. if ( count( $da ) ) {
  2154. $ds = sprintf("%04d-%02d-%02dT%02d:%02d:%02d.00+00:00",
  2155. (int)$da[1], (int)$da[2], (int)$da[3],
  2156. (int)$da[4], (int)$da[5], (int)$da[6]);
  2157. $d = date_create( $ds, new DateTimeZone( 'GMT' ) );
  2158. } elseif ( $strtime ) {
  2159. $d = date_create( $strtime, new DateTimeZone( 'GMT' ) );
  2160. } else {
  2161. return false;
  2162. }
  2163. if ( !$d ) {
  2164. wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
  2165. return false;
  2166. }
  2167. $output = $d->format( $formats[$outputtype] );
  2168. } else {
  2169. if ( count( $da ) ) {
  2170. // Warning! gmmktime() acts oddly if the month or day is set to 0
  2171. // We may want to handle that explicitly at some point
  2172. $uts = gmmktime( (int)$da[4], (int)$da[5], (int)$da[6],
  2173. (int)$da[2], (int)$da[3], (int)$da[1] );
  2174. } elseif ( $strtime ) {
  2175. $uts = strtotime( $strtime );
  2176. }
  2177. if ( $uts === false ) {
  2178. wfDebug("wfTimestamp() can't parse the timestamp (non 32-bit time? Update php): $outputtype; $ts\n");
  2179. return false;
  2180. }
  2181. if ( TS_UNIX == $outputtype ) {
  2182. return $uts;
  2183. }
  2184. $output = gmdate( $formats[$outputtype], $uts );
  2185. }
  2186. if ( ( $outputtype == TS_RFC2822 ) || ( $outputtype == TS_POSTGRES ) ) {
  2187. $output .= ' GMT';
  2188. }
  2189. return $output;
  2190. }
  2191. /**
  2192. * Return a formatted timestamp, or null if input is null.
  2193. * For dealing with nullable timestamp columns in the database.
  2194. *
  2195. * @param $outputtype Integer
  2196. * @param $ts String
  2197. * @return String
  2198. */
  2199. function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
  2200. if( is_null( $ts ) ) {
  2201. return null;
  2202. } else {
  2203. return wfTimestamp( $outputtype, $ts );
  2204. }
  2205. }
  2206. /**
  2207. * Convenience function; returns MediaWiki timestamp for the present time.
  2208. *
  2209. * @return string
  2210. */
  2211. function wfTimestampNow() {
  2212. # return NOW
  2213. return wfTimestamp( TS_MW, time() );
  2214. }
  2215. /**
  2216. * Check if the operating system is Windows
  2217. *
  2218. * @return Bool: true if it's Windows, False otherwise.
  2219. */
  2220. function wfIsWindows() {
  2221. static $isWindows = null;
  2222. if ( $isWindows === null ) {
  2223. $isWindows = substr( php_uname(), 0, 7 ) == 'Windows';
  2224. }
  2225. return $isWindows;
  2226. }
  2227. /**
  2228. * Check if we are running under HipHop
  2229. *
  2230. * @return Bool
  2231. */
  2232. function wfIsHipHop() {
  2233. return function_exists( 'hphp_thread_set_warmup_enabled' );
  2234. }
  2235. /**
  2236. * Swap two variables
  2237. *
  2238. * @param $x Mixed
  2239. * @param $y Mixed
  2240. */
  2241. function swap( &$x, &$y ) {
  2242. $z = $x;
  2243. $x = $y;
  2244. $y = $z;
  2245. }
  2246. /**
  2247. * Tries to get the system directory for temporary files. The TMPDIR, TMP, and
  2248. * TEMP environment variables are then checked in sequence, and if none are set
  2249. * try sys_get_temp_dir() for PHP >= 5.2.1. All else fails, return /tmp for Unix
  2250. * or C:\Windows\Temp for Windows and hope for the best.
  2251. * It is common to call it with tempnam().
  2252. *
  2253. * NOTE: When possible, use instead the tmpfile() function to create
  2254. * temporary files to avoid race conditions on file creation, etc.
  2255. *
  2256. * @return String
  2257. */
  2258. function wfTempDir() {
  2259. foreach( array( 'TMPDIR', 'TMP', 'TEMP' ) as $var ) {
  2260. $tmp = getenv( $var );
  2261. if( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
  2262. return $tmp;
  2263. }
  2264. }
  2265. if( function_exists( 'sys_get_temp_dir' ) ) {
  2266. return sys_get_temp_dir();
  2267. }
  2268. # Usual defaults
  2269. return wfIsWindows() ? 'C:\Windows\Temp' : '/tmp';
  2270. }
  2271. /**
  2272. * Make directory, and make all parent directories if they don't exist
  2273. *
  2274. * @param $dir String: full path to directory to create
  2275. * @param $mode Integer: chmod value to use, default is $wgDirectoryMode
  2276. * @param $caller String: optional caller param for debugging.
  2277. * @return bool
  2278. */
  2279. function wfMkdirParents( $dir, $mode = null, $caller = null ) {
  2280. global $wgDirectoryMode;
  2281. if ( FileBackend::isStoragePath( $dir ) ) { // sanity
  2282. throw new MWException( __FUNCTION__ . " given storage path `$dir`.");
  2283. }
  2284. if ( !is_null( $caller ) ) {
  2285. wfDebug( "$caller: called wfMkdirParents($dir)\n" );
  2286. }
  2287. if( strval( $dir ) === '' || file_exists( $dir ) ) {
  2288. return true;
  2289. }
  2290. $dir = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $dir );
  2291. if ( is_null( $mode ) ) {
  2292. $mode = $wgDirectoryMode;
  2293. }
  2294. // Turn off the normal warning, we're doing our own below
  2295. wfSuppressWarnings();
  2296. $ok = mkdir( $dir, $mode, true ); // PHP5 <3
  2297. wfRestoreWarnings();
  2298. if( !$ok ) {
  2299. // PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
  2300. trigger_error( __FUNCTION__ . ": failed to mkdir \"$dir\" mode $mode", E_USER_WARNING );
  2301. }
  2302. return $ok;
  2303. }
  2304. /**
  2305. * Increment a statistics counter
  2306. *
  2307. * @param $key String
  2308. * @param $count Int
  2309. */
  2310. function wfIncrStats( $key, $count = 1 ) {
  2311. global $wgStatsMethod;
  2312. $count = intval( $count );
  2313. if( $wgStatsMethod == 'udp' ) {
  2314. global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname, $wgAggregateStatsID;
  2315. static $socket;
  2316. $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : $wgDBname;
  2317. if ( !$socket ) {
  2318. $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
  2319. $statline = "stats/{$id} - {$count} 1 1 1 1 -total\n";
  2320. socket_sendto(
  2321. $socket,
  2322. $statline,
  2323. strlen( $statline ),
  2324. 0,
  2325. $wgUDPProfilerHost,
  2326. $wgUDPProfilerPort
  2327. );
  2328. }
  2329. $statline = "stats/{$id} - {$count} 1 1 1 1 {$key}\n";
  2330. wfSuppressWarnings();
  2331. socket_sendto(
  2332. $socket,
  2333. $statline,
  2334. strlen( $statline ),
  2335. 0,
  2336. $wgUDPProfilerHost,
  2337. $wgUDPProfilerPort
  2338. );
  2339. wfRestoreWarnings();
  2340. } elseif( $wgStatsMethod == 'cache' ) {
  2341. global $wgMemc;
  2342. $key = wfMemcKey( 'stats', $key );
  2343. if ( is_null( $wgMemc->incr( $key, $count ) ) ) {
  2344. $wgMemc->add( $key, $count );
  2345. }
  2346. } else {
  2347. // Disabled
  2348. }
  2349. }
  2350. /**
  2351. * Remove a directory and all its content.
  2352. * Does not hide error.
  2353. */
  2354. function wfRecursiveRemoveDir( $dir ) {
  2355. wfDebug( __FUNCTION__ . "( $dir )\n" );
  2356. // taken from http://de3.php.net/manual/en/function.rmdir.php#98622
  2357. if ( is_dir( $dir ) ) {
  2358. $objects = scandir( $dir );
  2359. foreach ( $objects as $object ) {
  2360. if ( $object != "." && $object != ".." ) {
  2361. if ( filetype( $dir . '/' . $object ) == "dir" ) {
  2362. wfRecursiveRemoveDir( $dir . '/' . $object );
  2363. } else {
  2364. unlink( $dir . '/' . $object );
  2365. }
  2366. }
  2367. }
  2368. reset( $objects );
  2369. rmdir( $dir );
  2370. }
  2371. }
  2372. /**
  2373. * @param $nr Mixed: the number to format
  2374. * @param $acc Integer: the number of digits after the decimal point, default 2
  2375. * @param $round Boolean: whether or not to round the value, default true
  2376. * @return float
  2377. */
  2378. function wfPercent( $nr, $acc = 2, $round = true ) {
  2379. $ret = sprintf( "%.${acc}f", $nr );
  2380. return $round ? round( $ret, $acc ) . '%' : "$ret%";
  2381. }
  2382. /**
  2383. * Find out whether or not a mixed variable exists in a string
  2384. *
  2385. * @param $needle String
  2386. * @param $str String
  2387. * @param $insensitive Boolean
  2388. * @return Boolean
  2389. */
  2390. function in_string( $needle, $str, $insensitive = false ) {
  2391. $func = 'strpos';
  2392. if( $insensitive ) $func = 'stripos';
  2393. return $func( $str, $needle ) !== false;
  2394. }
  2395. /**
  2396. * Safety wrapper around ini_get() for boolean settings.
  2397. * The values returned from ini_get() are pre-normalized for settings
  2398. * set via php.ini or php_flag/php_admin_flag... but *not*
  2399. * for those set via php_value/php_admin_value.
  2400. *
  2401. * It's fairly common for people to use php_value instead of php_flag,
  2402. * which can leave you with an 'off' setting giving a false positive
  2403. * for code that just takes the ini_get() return value as a boolean.
  2404. *
  2405. * To make things extra interesting, setting via php_value accepts
  2406. * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
  2407. * Unrecognized values go false... again opposite PHP's own coercion
  2408. * from string to bool.
  2409. *
  2410. * Luckily, 'properly' set settings will always come back as '0' or '1',
  2411. * so we only have to worry about them and the 'improper' settings.
  2412. *
  2413. * I frickin' hate PHP... :P
  2414. *
  2415. * @param $setting String
  2416. * @return Bool
  2417. */
  2418. function wfIniGetBool( $setting ) {
  2419. $val = ini_get( $setting );
  2420. // 'on' and 'true' can't have whitespace around them, but '1' can.
  2421. return strtolower( $val ) == 'on'
  2422. || strtolower( $val ) == 'true'
  2423. || strtolower( $val ) == 'yes'
  2424. || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
  2425. }
  2426. /**
  2427. * Wrapper function for PHP's dl(). This doesn't work in most situations from
  2428. * PHP 5.3 onward, and is usually disabled in shared environments anyway.
  2429. *
  2430. * @param $extension String A PHP extension. The file suffix (.so or .dll)
  2431. * should be omitted
  2432. * @param $fileName String Name of the library, if not $extension.suffix
  2433. * @return Bool - Whether or not the extension is loaded
  2434. */
  2435. function wfDl( $extension, $fileName = null ) {
  2436. if( extension_loaded( $extension ) ) {
  2437. return true;
  2438. }
  2439. $canDl = false;
  2440. $sapi = php_sapi_name();
  2441. if( version_compare( PHP_VERSION, '5.3.0', '<' ) ||
  2442. $sapi == 'cli' || $sapi == 'cgi' || $sapi == 'embed' )
  2443. {
  2444. $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
  2445. && wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
  2446. }
  2447. if( $canDl ) {
  2448. $fileName = $fileName ? $fileName : $extension;
  2449. if( wfIsWindows() ) {
  2450. $fileName = 'php_' . $fileName;
  2451. }
  2452. wfSuppressWarnings();
  2453. dl( $fileName . '.' . PHP_SHLIB_SUFFIX );
  2454. wfRestoreWarnings();
  2455. }
  2456. return extension_loaded( $extension );
  2457. }
  2458. /**
  2459. * Windows-compatible version of escapeshellarg()
  2460. * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
  2461. * function puts single quotes in regardless of OS.
  2462. *
  2463. * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
  2464. * earlier distro releases of PHP)
  2465. *
  2466. * @param varargs
  2467. * @return String
  2468. */
  2469. function wfEscapeShellArg( ) {
  2470. wfInitShellLocale();
  2471. $args = func_get_args();
  2472. $first = true;
  2473. $retVal = '';
  2474. foreach ( $args as $arg ) {
  2475. if ( !$first ) {
  2476. $retVal .= ' ';
  2477. } else {
  2478. $first = false;
  2479. }
  2480. if ( wfIsWindows() ) {
  2481. // Escaping for an MSVC-style command line parser and CMD.EXE
  2482. // Refs:
  2483. // * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
  2484. // * http://technet.microsoft.com/en-us/library/cc723564.aspx
  2485. // * Bug #13518
  2486. // * CR r63214
  2487. // Double the backslashes before any double quotes. Escape the double quotes.
  2488. $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
  2489. $arg = '';
  2490. $iteration = 0;
  2491. foreach ( $tokens as $token ) {
  2492. if ( $iteration % 2 == 1 ) {
  2493. // Delimiter, a double quote preceded by zero or more slashes
  2494. $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
  2495. } elseif ( $iteration % 4 == 2 ) {
  2496. // ^ in $token will be outside quotes, need to be escaped
  2497. $arg .= str_replace( '^', '^^', $token );
  2498. } else { // $iteration % 4 == 0
  2499. // ^ in $token will appear inside double quotes, so leave as is
  2500. $arg .= $token;
  2501. }
  2502. $iteration++;
  2503. }
  2504. // Double the backslashes before the end of the string, because
  2505. // we will soon add a quote
  2506. $m = array();
  2507. if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
  2508. $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
  2509. }
  2510. // Add surrounding quotes
  2511. $retVal .= '"' . $arg . '"';
  2512. } else {
  2513. $retVal .= escapeshellarg( $arg );
  2514. }
  2515. }
  2516. return $retVal;
  2517. }
  2518. /**
  2519. * Execute a shell command, with time and memory limits mirrored from the PHP
  2520. * configuration if supported.
  2521. * @param $cmd String Command line, properly escaped for shell.
  2522. * @param &$retval optional, will receive the program's exit code.
  2523. * (non-zero is usually failure)
  2524. * @param $environ Array optional environment variables which should be
  2525. * added to the executed command environment.
  2526. * @return collected stdout as a string (trailing newlines stripped)
  2527. */
  2528. function wfShellExec( $cmd, &$retval = null, $environ = array() ) {
  2529. global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
  2530. static $disabled;
  2531. if ( is_null( $disabled ) ) {
  2532. $disabled = false;
  2533. if( wfIniGetBool( 'safe_mode' ) ) {
  2534. wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
  2535. $disabled = 'safemode';
  2536. } else {
  2537. $functions = explode( ',', ini_get( 'disable_functions' ) );
  2538. $functions = array_map( 'trim', $functions );
  2539. $functions = array_map( 'strtolower', $functions );
  2540. if ( in_array( 'passthru', $functions ) ) {
  2541. wfDebug( "passthru is in disabled_functions\n" );
  2542. $disabled = 'passthru';
  2543. }
  2544. }
  2545. }
  2546. if ( $disabled ) {
  2547. $retval = 1;
  2548. return $disabled == 'safemode' ?
  2549. 'Unable to run external programs in safe mode.' :
  2550. 'Unable to run external programs, passthru() is disabled.';
  2551. }
  2552. wfInitShellLocale();
  2553. $envcmd = '';
  2554. foreach( $environ as $k => $v ) {
  2555. if ( wfIsWindows() ) {
  2556. /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
  2557. * appear in the environment variable, so we must use carat escaping as documented in
  2558. * http://technet.microsoft.com/en-us/library/cc723564.aspx
  2559. * Note however that the quote isn't listed there, but is needed, and the parentheses
  2560. * are listed there but doesn't appear to need it.
  2561. */
  2562. $envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
  2563. } else {
  2564. /* Assume this is a POSIX shell, thus required to accept variable assignments before the command
  2565. * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
  2566. */
  2567. $envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
  2568. }
  2569. }
  2570. $cmd = $envcmd . $cmd;
  2571. if ( wfIsWindows() ) {
  2572. if ( version_compare( PHP_VERSION, '5.3.0', '<' ) && /* Fixed in 5.3.0 :) */
  2573. ( version_compare( PHP_VERSION, '5.2.1', '>=' ) || php_uname( 's' ) == 'Windows NT' ) )
  2574. {
  2575. # Hack to work around PHP's flawed invocation of cmd.exe
  2576. # http://news.php.net/php.internals/21796
  2577. # Windows 9x doesn't accept any kind of quotes
  2578. $cmd = '"' . $cmd . '"';
  2579. }
  2580. } elseif ( php_uname( 's' ) == 'Linux' ) {
  2581. $time = intval( $wgMaxShellTime );
  2582. $mem = intval( $wgMaxShellMemory );
  2583. $filesize = intval( $wgMaxShellFileSize );
  2584. if ( $time > 0 && $mem > 0 ) {
  2585. $script = "$IP/bin/ulimit4.sh";
  2586. if ( is_executable( $script ) ) {
  2587. $cmd = '/bin/bash ' . escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd );
  2588. }
  2589. }
  2590. }
  2591. wfDebug( "wfShellExec: $cmd\n" );
  2592. $retval = 1; // error by default?
  2593. ob_start();
  2594. passthru( $cmd, $retval );
  2595. $output = ob_get_contents();
  2596. ob_end_clean();
  2597. if ( $retval == 127 ) {
  2598. wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
  2599. }
  2600. return $output;
  2601. }
  2602. /**
  2603. * Workaround for http://bugs.php.net/bug.php?id=45132
  2604. * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
  2605. */
  2606. function wfInitShellLocale() {
  2607. static $done = false;
  2608. if ( $done ) {
  2609. return;
  2610. }
  2611. $done = true;
  2612. global $wgShellLocale;
  2613. if ( !wfIniGetBool( 'safe_mode' ) ) {
  2614. putenv( "LC_CTYPE=$wgShellLocale" );
  2615. setlocale( LC_CTYPE, $wgShellLocale );
  2616. }
  2617. }
  2618. /**
  2619. * Generate a shell-escaped command line string to run a maintenance script.
  2620. * Note that $parameters should be a flat array and an option with an argument
  2621. * should consist of two consecutive items in the array (do not use "--option value").
  2622. * @param $script string MediaWiki maintenance script path
  2623. * @param $parameters Array Arguments and options to the script
  2624. * @param $options Array Associative array of options:
  2625. * 'php': The path to the php executable
  2626. * 'wrapper': Path to a PHP wrapper to handle the maintenance script
  2627. * @return Array
  2628. */
  2629. function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) {
  2630. global $wgPhpCli;
  2631. // Give site config file a chance to run the script in a wrapper.
  2632. // The caller may likely want to call wfBasename() on $script.
  2633. wfRunHooks( 'wfShellMaintenanceCmd', array( &$script, &$parameters, &$options ) );
  2634. $cmd = isset( $options['php'] ) ? array( $options['php'] ) : array( $wgPhpCli );
  2635. if ( isset( $options['wrapper'] ) ) {
  2636. $cmd[] = $options['wrapper'];
  2637. }
  2638. $cmd[] = $script;
  2639. // Escape each parameter for shell
  2640. return implode( " ", array_map( 'wfEscapeShellArg', array_merge( $cmd, $parameters ) ) );
  2641. }
  2642. /**
  2643. * wfMerge attempts to merge differences between three texts.
  2644. * Returns true for a clean merge and false for failure or a conflict.
  2645. *
  2646. * @param $old String
  2647. * @param $mine String
  2648. * @param $yours String
  2649. * @param $result String
  2650. * @return Bool
  2651. */
  2652. function wfMerge( $old, $mine, $yours, &$result ) {
  2653. global $wgDiff3;
  2654. # This check may also protect against code injection in
  2655. # case of broken installations.
  2656. wfSuppressWarnings();
  2657. $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
  2658. wfRestoreWarnings();
  2659. if( !$haveDiff3 ) {
  2660. wfDebug( "diff3 not found\n" );
  2661. return false;
  2662. }
  2663. # Make temporary files
  2664. $td = wfTempDir();
  2665. $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
  2666. $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
  2667. $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
  2668. fwrite( $oldtextFile, $old );
  2669. fclose( $oldtextFile );
  2670. fwrite( $mytextFile, $mine );
  2671. fclose( $mytextFile );
  2672. fwrite( $yourtextFile, $yours );
  2673. fclose( $yourtextFile );
  2674. # Check for a conflict
  2675. $cmd = $wgDiff3 . ' -a --overlap-only ' .
  2676. wfEscapeShellArg( $mytextName ) . ' ' .
  2677. wfEscapeShellArg( $oldtextName ) . ' ' .
  2678. wfEscapeShellArg( $yourtextName );
  2679. $handle = popen( $cmd, 'r' );
  2680. if( fgets( $handle, 1024 ) ) {
  2681. $conflict = true;
  2682. } else {
  2683. $conflict = false;
  2684. }
  2685. pclose( $handle );
  2686. # Merge differences
  2687. $cmd = $wgDiff3 . ' -a -e --merge ' .
  2688. wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
  2689. $handle = popen( $cmd, 'r' );
  2690. $result = '';
  2691. do {
  2692. $data = fread( $handle, 8192 );
  2693. if ( strlen( $data ) == 0 ) {
  2694. break;
  2695. }
  2696. $result .= $data;
  2697. } while ( true );
  2698. pclose( $handle );
  2699. unlink( $mytextName );
  2700. unlink( $oldtextName );
  2701. unlink( $yourtextName );
  2702. if ( $result === '' && $old !== '' && !$conflict ) {
  2703. wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
  2704. $conflict = true;
  2705. }
  2706. return !$conflict;
  2707. }
  2708. /**
  2709. * Returns unified plain-text diff of two texts.
  2710. * Useful for machine processing of diffs.
  2711. *
  2712. * @param $before String: the text before the changes.
  2713. * @param $after String: the text after the changes.
  2714. * @param $params String: command-line options for the diff command.
  2715. * @return String: unified diff of $before and $after
  2716. */
  2717. function wfDiff( $before, $after, $params = '-u' ) {
  2718. if ( $before == $after ) {
  2719. return '';
  2720. }
  2721. global $wgDiff;
  2722. wfSuppressWarnings();
  2723. $haveDiff = $wgDiff && file_exists( $wgDiff );
  2724. wfRestoreWarnings();
  2725. # This check may also protect against code injection in
  2726. # case of broken installations.
  2727. if( !$haveDiff ) {
  2728. wfDebug( "diff executable not found\n" );
  2729. $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
  2730. $format = new UnifiedDiffFormatter();
  2731. return $format->format( $diffs );
  2732. }
  2733. # Make temporary files
  2734. $td = wfTempDir();
  2735. $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
  2736. $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
  2737. fwrite( $oldtextFile, $before );
  2738. fclose( $oldtextFile );
  2739. fwrite( $newtextFile, $after );
  2740. fclose( $newtextFile );
  2741. // Get the diff of the two files
  2742. $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
  2743. $h = popen( $cmd, 'r' );
  2744. $diff = '';
  2745. do {
  2746. $data = fread( $h, 8192 );
  2747. if ( strlen( $data ) == 0 ) {
  2748. break;
  2749. }
  2750. $diff .= $data;
  2751. } while ( true );
  2752. // Clean up
  2753. pclose( $h );
  2754. unlink( $oldtextName );
  2755. unlink( $newtextName );
  2756. // Kill the --- and +++ lines. They're not useful.
  2757. $diff_lines = explode( "\n", $diff );
  2758. if ( strpos( $diff_lines[0], '---' ) === 0 ) {
  2759. unset( $diff_lines[0] );
  2760. }
  2761. if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
  2762. unset( $diff_lines[1] );
  2763. }
  2764. $diff = implode( "\n", $diff_lines );
  2765. return $diff;
  2766. }
  2767. /**
  2768. * This function works like "use VERSION" in Perl, the program will die with a
  2769. * backtrace if the current version of PHP is less than the version provided
  2770. *
  2771. * This is useful for extensions which due to their nature are not kept in sync
  2772. * with releases, and might depend on other versions of PHP than the main code
  2773. *
  2774. * Note: PHP might die due to parsing errors in some cases before it ever
  2775. * manages to call this function, such is life
  2776. *
  2777. * @see perldoc -f use
  2778. *
  2779. * @param $req_ver Mixed: the version to check, can be a string, an integer, or
  2780. * a float
  2781. */
  2782. function wfUsePHP( $req_ver ) {
  2783. $php_ver = PHP_VERSION;
  2784. if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
  2785. throw new MWException( "PHP $req_ver required--this is only $php_ver" );
  2786. }
  2787. }
  2788. /**
  2789. * This function works like "use VERSION" in Perl except it checks the version
  2790. * of MediaWiki, the program will die with a backtrace if the current version
  2791. * of MediaWiki is less than the version provided.
  2792. *
  2793. * This is useful for extensions which due to their nature are not kept in sync
  2794. * with releases
  2795. *
  2796. * @see perldoc -f use
  2797. *
  2798. * @param $req_ver Mixed: the version to check, can be a string, an integer, or
  2799. * a float
  2800. */
  2801. function wfUseMW( $req_ver ) {
  2802. global $wgVersion;
  2803. if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
  2804. throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
  2805. }
  2806. }
  2807. /**
  2808. * Return the final portion of a pathname.
  2809. * Reimplemented because PHP5's basename() is buggy with multibyte text.
  2810. * http://bugs.php.net/bug.php?id=33898
  2811. *
  2812. * PHP's basename() only considers '\' a pathchar on Windows and Netware.
  2813. * We'll consider it so always, as we don't want \s in our Unix paths either.
  2814. *
  2815. * @param $path String
  2816. * @param $suffix String: to remove if present
  2817. * @return String
  2818. */
  2819. function wfBaseName( $path, $suffix = '' ) {
  2820. $encSuffix = ( $suffix == '' )
  2821. ? ''
  2822. : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
  2823. $matches = array();
  2824. if( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
  2825. return $matches[1];
  2826. } else {
  2827. return '';
  2828. }
  2829. }
  2830. /**
  2831. * Generate a relative path name to the given file.
  2832. * May explode on non-matching case-insensitive paths,
  2833. * funky symlinks, etc.
  2834. *
  2835. * @param $path String: absolute destination path including target filename
  2836. * @param $from String: Absolute source path, directory only
  2837. * @return String
  2838. */
  2839. function wfRelativePath( $path, $from ) {
  2840. // Normalize mixed input on Windows...
  2841. $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
  2842. $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
  2843. // Trim trailing slashes -- fix for drive root
  2844. $path = rtrim( $path, DIRECTORY_SEPARATOR );
  2845. $from = rtrim( $from, DIRECTORY_SEPARATOR );
  2846. $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
  2847. $against = explode( DIRECTORY_SEPARATOR, $from );
  2848. if( $pieces[0] !== $against[0] ) {
  2849. // Non-matching Windows drive letters?
  2850. // Return a full path.
  2851. return $path;
  2852. }
  2853. // Trim off common prefix
  2854. while( count( $pieces ) && count( $against )
  2855. && $pieces[0] == $against[0] ) {
  2856. array_shift( $pieces );
  2857. array_shift( $against );
  2858. }
  2859. // relative dots to bump us to the parent
  2860. while( count( $against ) ) {
  2861. array_unshift( $pieces, '..' );
  2862. array_shift( $against );
  2863. }
  2864. array_push( $pieces, wfBaseName( $path ) );
  2865. return implode( DIRECTORY_SEPARATOR, $pieces );
  2866. }
  2867. /**
  2868. * Do any deferred updates and clear the list
  2869. *
  2870. * @deprecated since 1.19
  2871. * @see DeferredUpdates::doUpdate()
  2872. * @param $commit string
  2873. */
  2874. function wfDoUpdates( $commit = '' ) {
  2875. wfDeprecated( __METHOD__, '1.19' );
  2876. DeferredUpdates::doUpdates( $commit );
  2877. }
  2878. /**
  2879. * Convert an arbitrarily-long digit string from one numeric base
  2880. * to another, optionally zero-padding to a minimum column width.
  2881. *
  2882. * Supports base 2 through 36; digit values 10-36 are represented
  2883. * as lowercase letters a-z. Input is case-insensitive.
  2884. *
  2885. * @param $input String: of digits
  2886. * @param $sourceBase Integer: 2-36
  2887. * @param $destBase Integer: 2-36
  2888. * @param $pad Integer: 1 or greater
  2889. * @param $lowercase Boolean
  2890. * @return String or false on invalid input
  2891. */
  2892. function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true ) {
  2893. $input = strval( $input );
  2894. if( $sourceBase < 2 ||
  2895. $sourceBase > 36 ||
  2896. $destBase < 2 ||
  2897. $destBase > 36 ||
  2898. $pad < 1 ||
  2899. $sourceBase != intval( $sourceBase ) ||
  2900. $destBase != intval( $destBase ) ||
  2901. $pad != intval( $pad ) ||
  2902. !is_string( $input ) ||
  2903. $input == '' ) {
  2904. return false;
  2905. }
  2906. $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  2907. $inDigits = array();
  2908. $outChars = '';
  2909. // Decode and validate input string
  2910. $input = strtolower( $input );
  2911. for( $i = 0; $i < strlen( $input ); $i++ ) {
  2912. $n = strpos( $digitChars, $input[$i] );
  2913. if( $n === false || $n > $sourceBase ) {
  2914. return false;
  2915. }
  2916. $inDigits[] = $n;
  2917. }
  2918. // Iterate over the input, modulo-ing out an output digit
  2919. // at a time until input is gone.
  2920. while( count( $inDigits ) ) {
  2921. $work = 0;
  2922. $workDigits = array();
  2923. // Long division...
  2924. foreach( $inDigits as $digit ) {
  2925. $work *= $sourceBase;
  2926. $work += $digit;
  2927. if( $work < $destBase ) {
  2928. // Gonna need to pull another digit.
  2929. if( count( $workDigits ) ) {
  2930. // Avoid zero-padding; this lets us find
  2931. // the end of the input very easily when
  2932. // length drops to zero.
  2933. $workDigits[] = 0;
  2934. }
  2935. } else {
  2936. // Finally! Actual division!
  2937. $workDigits[] = intval( $work / $destBase );
  2938. // Isn't it annoying that most programming languages
  2939. // don't have a single divide-and-remainder operator,
  2940. // even though the CPU implements it that way?
  2941. $work = $work % $destBase;
  2942. }
  2943. }
  2944. // All that division leaves us with a remainder,
  2945. // which is conveniently our next output digit.
  2946. $outChars .= $digitChars[$work];
  2947. // And we continue!
  2948. $inDigits = $workDigits;
  2949. }
  2950. while( strlen( $outChars ) < $pad ) {
  2951. $outChars .= '0';
  2952. }
  2953. return strrev( $outChars );
  2954. }
  2955. /**
  2956. * Create an object with a given name and an array of construct parameters
  2957. *
  2958. * @param $name String
  2959. * @param $p Array: parameters
  2960. * @return object
  2961. * @deprecated since 1.18, warnings in 1.18, removal in 1.20
  2962. */
  2963. function wfCreateObject( $name, $p ) {
  2964. wfDeprecated( __FUNCTION__, '1.18' );
  2965. return MWFunction::newObj( $name, $p );
  2966. }
  2967. /**
  2968. * @return bool
  2969. */
  2970. function wfHttpOnlySafe() {
  2971. global $wgHttpOnlyBlacklist;
  2972. if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
  2973. foreach( $wgHttpOnlyBlacklist as $regex ) {
  2974. if( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
  2975. return false;
  2976. }
  2977. }
  2978. }
  2979. return true;
  2980. }
  2981. /**
  2982. * Override session_id before session startup if php's built-in
  2983. * session generation code is not secure.
  2984. */
  2985. function wfFixSessionID() {
  2986. // If the cookie or session id is already set we already have a session and should abort
  2987. if ( isset( $_COOKIE[ session_name() ] ) || session_id() ) {
  2988. return;
  2989. }
  2990. // PHP's built-in session entropy is enabled if:
  2991. // - entropy_file is set or you're on Windows with php 5.3.3+
  2992. // - AND entropy_length is > 0
  2993. // We treat it as disabled if it doesn't have an entropy length of at least 32
  2994. $entropyEnabled = (
  2995. ( wfIsWindows() && version_compare( PHP_VERSION, '5.3.3', '>=' ) )
  2996. || ini_get( 'session.entropy_file' )
  2997. )
  2998. && intval( ini_get( 'session.entropy_length' ) ) >= 32;
  2999. // If built-in entropy is not enabled or not sufficient override php's built in session id generation code
  3000. if ( !$entropyEnabled ) {
  3001. wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, overriding session id generation using our cryptrand source.\n" );
  3002. session_id( MWCryptRand::generateHex( 32 ) );
  3003. }
  3004. }
  3005. /**
  3006. * Initialise php session
  3007. *
  3008. * @param $sessionId Bool
  3009. */
  3010. function wfSetupSession( $sessionId = false ) {
  3011. global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain,
  3012. $wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler;
  3013. if( $wgSessionsInMemcached ) {
  3014. if ( !defined( 'MW_COMPILED' ) ) {
  3015. global $IP;
  3016. require_once( "$IP/includes/cache/MemcachedSessions.php" );
  3017. }
  3018. session_set_save_handler( 'memsess_open', 'memsess_close', 'memsess_read',
  3019. 'memsess_write', 'memsess_destroy', 'memsess_gc' );
  3020. // It's necessary to register a shutdown function to call session_write_close(),
  3021. // because by the time the request shutdown function for the session module is
  3022. // called, $wgMemc has already been destroyed. Shutdown functions registered
  3023. // this way are called before object destruction.
  3024. register_shutdown_function( 'memsess_write_close' );
  3025. } elseif( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
  3026. # Only set this if $wgSessionHandler isn't null and session.save_handler
  3027. # hasn't already been set to the desired value (that causes errors)
  3028. ini_set( 'session.save_handler', $wgSessionHandler );
  3029. }
  3030. $httpOnlySafe = wfHttpOnlySafe() && $wgCookieHttpOnly;
  3031. wfDebugLog( 'cookie',
  3032. 'session_set_cookie_params: "' . implode( '", "',
  3033. array(
  3034. 0,
  3035. $wgCookiePath,
  3036. $wgCookieDomain,
  3037. $wgCookieSecure,
  3038. $httpOnlySafe ) ) . '"' );
  3039. session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $httpOnlySafe );
  3040. session_cache_limiter( 'private, must-revalidate' );
  3041. if ( $sessionId ) {
  3042. session_id( $sessionId );
  3043. } else {
  3044. wfFixSessionID();
  3045. }
  3046. wfSuppressWarnings();
  3047. session_start();
  3048. wfRestoreWarnings();
  3049. }
  3050. /**
  3051. * Get an object from the precompiled serialized directory
  3052. *
  3053. * @param $name String
  3054. * @return Mixed: the variable on success, false on failure
  3055. */
  3056. function wfGetPrecompiledData( $name ) {
  3057. global $IP;
  3058. $file = "$IP/serialized/$name";
  3059. if ( file_exists( $file ) ) {
  3060. $blob = file_get_contents( $file );
  3061. if ( $blob ) {
  3062. return unserialize( $blob );
  3063. }
  3064. }
  3065. return false;
  3066. }
  3067. /**
  3068. * Get a cache key
  3069. *
  3070. * @param varargs
  3071. * @return String
  3072. */
  3073. function wfMemcKey( /*... */ ) {
  3074. global $wgCachePrefix;
  3075. $prefix = $wgCachePrefix === false ? wfWikiID() : $wgCachePrefix;
  3076. $args = func_get_args();
  3077. $key = $prefix . ':' . implode( ':', $args );
  3078. $key = str_replace( ' ', '_', $key );
  3079. return $key;
  3080. }
  3081. /**
  3082. * Get a cache key for a foreign DB
  3083. *
  3084. * @param $db String
  3085. * @param $prefix String
  3086. * @param varargs String
  3087. * @return String
  3088. */
  3089. function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
  3090. $args = array_slice( func_get_args(), 2 );
  3091. if ( $prefix ) {
  3092. $key = "$db-$prefix:" . implode( ':', $args );
  3093. } else {
  3094. $key = $db . ':' . implode( ':', $args );
  3095. }
  3096. return $key;
  3097. }
  3098. /**
  3099. * Get an ASCII string identifying this wiki
  3100. * This is used as a prefix in memcached keys
  3101. *
  3102. * @return String
  3103. */
  3104. function wfWikiID() {
  3105. global $wgDBprefix, $wgDBname;
  3106. if ( $wgDBprefix ) {
  3107. return "$wgDBname-$wgDBprefix";
  3108. } else {
  3109. return $wgDBname;
  3110. }
  3111. }
  3112. /**
  3113. * Split a wiki ID into DB name and table prefix
  3114. *
  3115. * @param $wiki String
  3116. *
  3117. * @return array
  3118. */
  3119. function wfSplitWikiID( $wiki ) {
  3120. $bits = explode( '-', $wiki, 2 );
  3121. if ( count( $bits ) < 2 ) {
  3122. $bits[] = '';
  3123. }
  3124. return $bits;
  3125. }
  3126. /**
  3127. * Get a Database object.
  3128. *
  3129. * @param $db Integer: index of the connection to get. May be DB_MASTER for the
  3130. * master (for write queries), DB_SLAVE for potentially lagged read
  3131. * queries, or an integer >= 0 for a particular server.
  3132. *
  3133. * @param $groups Mixed: query groups. An array of group names that this query
  3134. * belongs to. May contain a single string if the query is only
  3135. * in one group.
  3136. *
  3137. * @param $wiki String: the wiki ID, or false for the current wiki
  3138. *
  3139. * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
  3140. * will always return the same object, unless the underlying connection or load
  3141. * balancer is manually destroyed.
  3142. *
  3143. * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
  3144. * updater to ensure that a proper database is being updated.
  3145. *
  3146. * @return DatabaseBase
  3147. */
  3148. function &wfGetDB( $db, $groups = array(), $wiki = false ) {
  3149. return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
  3150. }
  3151. /**
  3152. * Get a load balancer object.
  3153. *
  3154. * @param $wiki String: wiki ID, or false for the current wiki
  3155. * @return LoadBalancer
  3156. */
  3157. function wfGetLB( $wiki = false ) {
  3158. return wfGetLBFactory()->getMainLB( $wiki );
  3159. }
  3160. /**
  3161. * Get the load balancer factory object
  3162. *
  3163. * @return LBFactory
  3164. */
  3165. function &wfGetLBFactory() {
  3166. return LBFactory::singleton();
  3167. }
  3168. /**
  3169. * Find a file.
  3170. * Shortcut for RepoGroup::singleton()->findFile()
  3171. *
  3172. * @param $title String or Title object
  3173. * @param $options Associative array of options:
  3174. * time: requested time for an archived image, or false for the
  3175. * current version. An image object will be returned which was
  3176. * created at the specified time.
  3177. *
  3178. * ignoreRedirect: If true, do not follow file redirects
  3179. *
  3180. * private: If true, return restricted (deleted) files if the current
  3181. * user is allowed to view them. Otherwise, such files will not
  3182. * be found.
  3183. *
  3184. * bypassCache: If true, do not use the process-local cache of File objects
  3185. *
  3186. * @return File, or false if the file does not exist
  3187. */
  3188. function wfFindFile( $title, $options = array() ) {
  3189. return RepoGroup::singleton()->findFile( $title, $options );
  3190. }
  3191. /**
  3192. * Get an object referring to a locally registered file.
  3193. * Returns a valid placeholder object if the file does not exist.
  3194. *
  3195. * @param $title Title|String
  3196. * @return File|null A File, or null if passed an invalid Title
  3197. */
  3198. function wfLocalFile( $title ) {
  3199. return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
  3200. }
  3201. /**
  3202. * Stream a file to the browser. Back-compat alias for StreamFile::stream()
  3203. * @deprecated since 1.19
  3204. */
  3205. function wfStreamFile( $fname, $headers = array() ) {
  3206. wfDeprecated( __FUNCTION__, '1.19' );
  3207. StreamFile::stream( $fname, $headers );
  3208. }
  3209. /**
  3210. * Should low-performance queries be disabled?
  3211. *
  3212. * @return Boolean
  3213. * @codeCoverageIgnore
  3214. */
  3215. function wfQueriesMustScale() {
  3216. global $wgMiserMode;
  3217. return $wgMiserMode
  3218. || ( SiteStats::pages() > 100000
  3219. && SiteStats::edits() > 1000000
  3220. && SiteStats::users() > 10000 );
  3221. }
  3222. /**
  3223. * Get the path to a specified script file, respecting file
  3224. * extensions; this is a wrapper around $wgScriptExtension etc.
  3225. *
  3226. * @param $script String: script filename, sans extension
  3227. * @return String
  3228. */
  3229. function wfScript( $script = 'index' ) {
  3230. global $wgScriptPath, $wgScriptExtension;
  3231. return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
  3232. }
  3233. /**
  3234. * Get the script URL.
  3235. *
  3236. * @return script URL
  3237. */
  3238. function wfGetScriptUrl() {
  3239. if( isset( $_SERVER['SCRIPT_NAME'] ) ) {
  3240. #
  3241. # as it was called, minus the query string.
  3242. #
  3243. # Some sites use Apache rewrite rules to handle subdomains,
  3244. # and have PHP set up in a weird way that causes PHP_SELF
  3245. # to contain the rewritten URL instead of the one that the
  3246. # outside world sees.
  3247. #
  3248. # If in this mode, use SCRIPT_URL instead, which mod_rewrite
  3249. # provides containing the "before" URL.
  3250. return $_SERVER['SCRIPT_NAME'];
  3251. } else {
  3252. return $_SERVER['URL'];
  3253. }
  3254. }
  3255. /**
  3256. * Convenience function converts boolean values into "true"
  3257. * or "false" (string) values
  3258. *
  3259. * @param $value Boolean
  3260. * @return String
  3261. */
  3262. function wfBoolToStr( $value ) {
  3263. return $value ? 'true' : 'false';
  3264. }
  3265. /**
  3266. * Load an extension messages file
  3267. *
  3268. * @deprecated since 1.16, warnings in 1.18, remove in 1.20
  3269. * @codeCoverageIgnore
  3270. */
  3271. function wfLoadExtensionMessages() {
  3272. wfDeprecated( __FUNCTION__, '1.16' );
  3273. }
  3274. /**
  3275. * Get a platform-independent path to the null file, e.g. /dev/null
  3276. *
  3277. * @return string
  3278. */
  3279. function wfGetNull() {
  3280. return wfIsWindows()
  3281. ? 'NUL'
  3282. : '/dev/null';
  3283. }
  3284. /**
  3285. * Modern version of wfWaitForSlaves(). Instead of looking at replication lag
  3286. * and waiting for it to go down, this waits for the slaves to catch up to the
  3287. * master position. Use this when updating very large numbers of rows, as
  3288. * in maintenance scripts, to avoid causing too much lag. Of course, this is
  3289. * a no-op if there are no slaves.
  3290. *
  3291. * @param $maxLag Integer (deprecated)
  3292. * @param $wiki mixed Wiki identifier accepted by wfGetLB
  3293. */
  3294. function wfWaitForSlaves( $maxLag = false, $wiki = false ) {
  3295. $lb = wfGetLB( $wiki );
  3296. // bug 27975 - Don't try to wait for slaves if there are none
  3297. // Prevents permission error when getting master position
  3298. if ( $lb->getServerCount() > 1 ) {
  3299. $dbw = $lb->getConnection( DB_MASTER );
  3300. $pos = $dbw->getMasterPos();
  3301. $lb->waitForAll( $pos );
  3302. }
  3303. }
  3304. /**
  3305. * Used to be used for outputting text in the installer/updater
  3306. * @deprecated since 1.18, warnings in 1.18, remove in 1.20
  3307. */
  3308. function wfOut( $s ) {
  3309. wfDeprecated( __FUNCTION__, '1.18' );
  3310. global $wgCommandLineMode;
  3311. if ( $wgCommandLineMode ) {
  3312. echo $s;
  3313. } else {
  3314. echo htmlspecialchars( $s );
  3315. }
  3316. flush();
  3317. }
  3318. /**
  3319. * Count down from $n to zero on the terminal, with a one-second pause
  3320. * between showing each number. For use in command-line scripts.
  3321. * @codeCoverageIgnore
  3322. * @param $n int
  3323. */
  3324. function wfCountDown( $n ) {
  3325. for ( $i = $n; $i >= 0; $i-- ) {
  3326. if ( $i != $n ) {
  3327. echo str_repeat( "\x08", strlen( $i + 1 ) );
  3328. }
  3329. echo $i;
  3330. flush();
  3331. if ( $i ) {
  3332. sleep( 1 );
  3333. }
  3334. }
  3335. echo "\n";
  3336. }
  3337. /**
  3338. * Generate a random 32-character hexadecimal token.
  3339. * @param $salt Mixed: some sort of salt, if necessary, to add to random
  3340. * characters before hashing.
  3341. * @return string
  3342. * @codeCoverageIgnore
  3343. */
  3344. function wfGenerateToken( $salt = '' ) {
  3345. $salt = serialize( $salt );
  3346. return md5( mt_rand( 0, 0x7fffffff ) . $salt );
  3347. }
  3348. /**
  3349. * Replace all invalid characters with -
  3350. *
  3351. * @param $name Mixed: filename to process
  3352. * @return String
  3353. */
  3354. function wfStripIllegalFilenameChars( $name ) {
  3355. global $wgIllegalFileChars;
  3356. $name = wfBaseName( $name );
  3357. $name = preg_replace(
  3358. "/[^" . Title::legalChars() . "]" .
  3359. ( $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '' ) .
  3360. "/",
  3361. '-',
  3362. $name
  3363. );
  3364. return $name;
  3365. }
  3366. /**
  3367. * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit;
  3368. *
  3369. * @return Integer value memory was set to.
  3370. */
  3371. function wfMemoryLimit() {
  3372. global $wgMemoryLimit;
  3373. $memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
  3374. if( $memlimit != -1 ) {
  3375. $conflimit = wfShorthandToInteger( $wgMemoryLimit );
  3376. if( $conflimit == -1 ) {
  3377. wfDebug( "Removing PHP's memory limit\n" );
  3378. wfSuppressWarnings();
  3379. ini_set( 'memory_limit', $conflimit );
  3380. wfRestoreWarnings();
  3381. return $conflimit;
  3382. } elseif ( $conflimit > $memlimit ) {
  3383. wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
  3384. wfSuppressWarnings();
  3385. ini_set( 'memory_limit', $conflimit );
  3386. wfRestoreWarnings();
  3387. return $conflimit;
  3388. }
  3389. }
  3390. return $memlimit;
  3391. }
  3392. /**
  3393. * Converts shorthand byte notation to integer form
  3394. *
  3395. * @param $string String
  3396. * @return Integer
  3397. */
  3398. function wfShorthandToInteger( $string = '' ) {
  3399. $string = trim( $string );
  3400. if( $string === '' ) {
  3401. return -1;
  3402. }
  3403. $last = $string[strlen( $string ) - 1];
  3404. $val = intval( $string );
  3405. switch( $last ) {
  3406. case 'g':
  3407. case 'G':
  3408. $val *= 1024;
  3409. // break intentionally missing
  3410. case 'm':
  3411. case 'M':
  3412. $val *= 1024;
  3413. // break intentionally missing
  3414. case 'k':
  3415. case 'K':
  3416. $val *= 1024;
  3417. }
  3418. return $val;
  3419. }
  3420. /**
  3421. * Get the normalised IETF language tag
  3422. * See unit test for examples.
  3423. *
  3424. * @param $code String: The language code.
  3425. * @return String: The language code which complying with BCP 47 standards.
  3426. */
  3427. function wfBCP47( $code ) {
  3428. $codeSegment = explode( '-', $code );
  3429. $codeBCP = array();
  3430. foreach ( $codeSegment as $segNo => $seg ) {
  3431. if ( count( $codeSegment ) > 0 ) {
  3432. // when previous segment is x, it is a private segment and should be lc
  3433. if( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
  3434. $codeBCP[$segNo] = strtolower( $seg );
  3435. // ISO 3166 country code
  3436. } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
  3437. $codeBCP[$segNo] = strtoupper( $seg );
  3438. // ISO 15924 script code
  3439. } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
  3440. $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
  3441. // Use lowercase for other cases
  3442. } else {
  3443. $codeBCP[$segNo] = strtolower( $seg );
  3444. }
  3445. } else {
  3446. // Use lowercase for single segment
  3447. $codeBCP[$segNo] = strtolower( $seg );
  3448. }
  3449. }
  3450. $langCode = implode( '-', $codeBCP );
  3451. return $langCode;
  3452. }
  3453. /**
  3454. * Get a cache object.
  3455. *
  3456. * @param $inputType integer Cache type, one the the CACHE_* constants.
  3457. * @return BagOStuff
  3458. */
  3459. function wfGetCache( $inputType ) {
  3460. return ObjectCache::getInstance( $inputType );
  3461. }
  3462. /**
  3463. * Get the main cache object
  3464. *
  3465. * @return BagOStuff
  3466. */
  3467. function wfGetMainCache() {
  3468. global $wgMainCacheType;
  3469. return ObjectCache::getInstance( $wgMainCacheType );
  3470. }
  3471. /**
  3472. * Get the cache object used by the message cache
  3473. *
  3474. * @return BagOStuff
  3475. */
  3476. function wfGetMessageCacheStorage() {
  3477. global $wgMessageCacheType;
  3478. return ObjectCache::getInstance( $wgMessageCacheType );
  3479. }
  3480. /**
  3481. * Get the cache object used by the parser cache
  3482. *
  3483. * @return BagOStuff
  3484. */
  3485. function wfGetParserCacheStorage() {
  3486. global $wgParserCacheType;
  3487. return ObjectCache::getInstance( $wgParserCacheType );
  3488. }
  3489. /**
  3490. * Call hook functions defined in $wgHooks
  3491. *
  3492. * @param $event String: event name
  3493. * @param $args Array: parameters passed to hook functions
  3494. * @return Boolean True if no handler aborted the hook
  3495. */
  3496. function wfRunHooks( $event, $args = array() ) {
  3497. return Hooks::run( $event, $args );
  3498. }
  3499. /**
  3500. * Wrapper around php's unpack.
  3501. *
  3502. * @param $format String: The format string (See php's docs)
  3503. * @param $data: A binary string of binary data
  3504. * @param $length integer or false: The minimun length of $data. This is to
  3505. * prevent reading beyond the end of $data. false to disable the check.
  3506. *
  3507. * Also be careful when using this function to read unsigned 32 bit integer
  3508. * because php might make it negative.
  3509. *
  3510. * @throws MWException if $data not long enough, or if unpack fails
  3511. * @return Associative array of the extracted data
  3512. */
  3513. function wfUnpack( $format, $data, $length=false ) {
  3514. if ( $length !== false ) {
  3515. $realLen = strlen( $data );
  3516. if ( $realLen < $length ) {
  3517. throw new MWException( "Tried to use wfUnpack on a "
  3518. . "string of length $realLen, but needed one "
  3519. . "of at least length $length."
  3520. );
  3521. }
  3522. }
  3523. wfSuppressWarnings();
  3524. $result = unpack( $format, $data );
  3525. wfRestoreWarnings();
  3526. if ( $result === false ) {
  3527. // If it cannot extract the packed data.
  3528. throw new MWException( "unpack could not unpack binary data" );
  3529. }
  3530. return $result;
  3531. }