MediaWiki PHP Cross Reference Collaborative Wikis

Source: /includes/GlobalFunctions.php - 4008 lines - 114381 bytes - Summary - Text - Print

Description: Global functions used everywhere. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

   1  <?php
   2  /**
   3   * Global functions used everywhere.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   */
  22  
  23  if ( !defined( 'MEDIAWIKI' ) ) {
  24      die( "This file is part of MediaWiki, it is not a valid entry point" );
  25  }
  26  
  27  // Hide compatibility functions from Doxygen
  28  /// @cond
  29  
  30  /**
  31   * Compatibility functions
  32   *
  33   * We support PHP 5.3.2 and up.
  34   * Re-implementations of newer functions or functions in non-standard
  35   * PHP extensions may be included here.
  36   */
  37  
  38  if ( !function_exists( 'iconv' ) ) {
  39      /**
  40       * @codeCoverageIgnore
  41       * @return string
  42       */
  43  	function iconv( $from, $to, $string ) {
  44          return Fallback::iconv( $from, $to, $string );
  45      }
  46  }
  47  
  48  if ( !function_exists( 'mb_substr' ) ) {
  49      /**
  50       * @codeCoverageIgnore
  51       * @return string
  52       */
  53  	function mb_substr( $str, $start, $count = 'end' ) {
  54          return Fallback::mb_substr( $str, $start, $count );
  55      }
  56  
  57      /**
  58       * @codeCoverageIgnore
  59       * @return int
  60       */
  61  	function mb_substr_split_unicode( $str, $splitPos ) {
  62          return Fallback::mb_substr_split_unicode( $str, $splitPos );
  63      }
  64  }
  65  
  66  if ( !function_exists( 'mb_strlen' ) ) {
  67      /**
  68       * @codeCoverageIgnore
  69       * @return int
  70       */
  71  	function mb_strlen( $str, $enc = '' ) {
  72          return Fallback::mb_strlen( $str, $enc );
  73      }
  74  }
  75  
  76  if ( !function_exists( 'mb_strpos' ) ) {
  77      /**
  78       * @codeCoverageIgnore
  79       * @return int
  80       */
  81  	function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
  82          return Fallback::mb_strpos( $haystack, $needle, $offset, $encoding );
  83      }
  84  
  85  }
  86  
  87  if ( !function_exists( 'mb_strrpos' ) ) {
  88      /**
  89       * @codeCoverageIgnore
  90       * @return int
  91       */
  92  	function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
  93          return Fallback::mb_strrpos( $haystack, $needle, $offset, $encoding );
  94      }
  95  }
  96  
  97  // gzdecode function only exists in PHP >= 5.4.0
  98  // http://php.net/gzdecode
  99  if ( !function_exists( 'gzdecode' ) ) {
 100      /**
 101       * @codeCoverageIgnore
 102       * @return string
 103       */
 104  	function gzdecode( $data ) {
 105          return gzinflate( substr( $data, 10, -8 ) );
 106      }
 107  }
 108  /// @endcond
 109  
 110  /**
 111   * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
 112   * @param $a array
 113   * @param $b array
 114   * @return array
 115   */
 116  function wfArrayDiff2( $a, $b ) {
 117      return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
 118  }
 119  
 120  /**
 121   * @param $a array|string
 122   * @param $b array|string
 123   * @return int
 124   */
 125  function wfArrayDiff2_cmp( $a, $b ) {
 126      if ( is_string( $a ) && is_string( $b ) ) {
 127          return strcmp( $a, $b );
 128      } elseif ( count( $a ) !== count( $b ) ) {
 129          return count( $a ) < count( $b ) ? -1 : 1;
 130      } else {
 131          reset( $a );
 132          reset( $b );
 133          while ( ( list( , $valueA ) = each( $a ) ) && ( list( , $valueB ) = each( $b ) ) ) {
 134              $cmp = strcmp( $valueA, $valueB );
 135              if ( $cmp !== 0 ) {
 136                  return $cmp;
 137              }
 138          }
 139          return 0;
 140      }
 141  }
 142  
 143  /**
 144   * Array lookup
 145   * Returns an array where the values in array $b are replaced by the
 146   * values in array $a with the corresponding keys
 147   *
 148   * @deprecated since 1.22; use array_intersect_key()
 149   * @param $a Array
 150   * @param $b Array
 151   * @return array
 152   */
 153  function wfArrayLookup( $a, $b ) {
 154      wfDeprecated( __FUNCTION__, '1.22' );
 155      return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
 156  }
 157  
 158  /**
 159   * Appends to second array if $value differs from that in $default
 160   *
 161   * @param $key String|Int
 162   * @param $value Mixed
 163   * @param $default Mixed
 164   * @param array $changed to alter
 165   * @throws MWException
 166   */
 167  function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
 168      if ( is_null( $changed ) ) {
 169          throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
 170      }
 171      if ( $default[$key] !== $value ) {
 172          $changed[$key] = $value;
 173      }
 174  }
 175  
 176  /**
 177   * Backwards array plus for people who haven't bothered to read the PHP manual
 178   * XXX: will not darn your socks for you.
 179   *
 180   * @deprecated since 1.22; use array_replace()
 181   * @param $array1 Array
 182   * @param [$array2, [...]] Arrays
 183   * @return Array
 184   */
 185  function wfArrayMerge( $array1/* ... */ ) {
 186      wfDeprecated( __FUNCTION__, '1.22' );
 187      $args = func_get_args();
 188      $args = array_reverse( $args, true );
 189      $out = array();
 190      foreach ( $args as $arg ) {
 191          $out += $arg;
 192      }
 193      return $out;
 194  }
 195  
 196  /**
 197   * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
 198   * e.g.
 199   *    wfMergeErrorArrays(
 200   *        array( array( 'x' ) ),
 201   *        array( array( 'x', '2' ) ),
 202   *        array( array( 'x' ) ),
 203   *        array( array( 'y' ) )
 204   *    );
 205   * returns:
 206   *         array(
 207   *           array( 'x', '2' ),
 208   *           array( 'x' ),
 209   *           array( 'y' )
 210   *       )
 211   * @param varargs
 212   * @return Array
 213   */
 214  function wfMergeErrorArrays( /*...*/ ) {
 215      $args = func_get_args();
 216      $out = array();
 217      foreach ( $args as $errors ) {
 218          foreach ( $errors as $params ) {
 219              # @todo FIXME: Sometimes get nested arrays for $params,
 220              # which leads to E_NOTICEs
 221              $spec = implode( "\t", $params );
 222              $out[$spec] = $params;
 223          }
 224      }
 225      return array_values( $out );
 226  }
 227  
 228  /**
 229   * Insert array into another array after the specified *KEY*
 230   *
 231   * @param array $array The array.
 232   * @param array $insert The array to insert.
 233   * @param $after Mixed: The key to insert after
 234   * @return Array
 235   */
 236  function wfArrayInsertAfter( array $array, array $insert, $after ) {
 237      // Find the offset of the element to insert after.
 238      $keys = array_keys( $array );
 239      $offsetByKey = array_flip( $keys );
 240  
 241      $offset = $offsetByKey[$after];
 242  
 243      // Insert at the specified offset
 244      $before = array_slice( $array, 0, $offset + 1, true );
 245      $after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
 246  
 247      $output = $before + $insert + $after;
 248  
 249      return $output;
 250  }
 251  
 252  /**
 253   * Recursively converts the parameter (an object) to an array with the same data
 254   *
 255   * @param $objOrArray Object|Array
 256   * @param $recursive Bool
 257   * @return Array
 258   */
 259  function wfObjectToArray( $objOrArray, $recursive = true ) {
 260      $array = array();
 261      if ( is_object( $objOrArray ) ) {
 262          $objOrArray = get_object_vars( $objOrArray );
 263      }
 264      foreach ( $objOrArray as $key => $value ) {
 265          if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
 266              $value = wfObjectToArray( $value );
 267          }
 268  
 269          $array[$key] = $value;
 270      }
 271  
 272      return $array;
 273  }
 274  
 275  /**
 276   * Get a random decimal value between 0 and 1, in a way
 277   * not likely to give duplicate values for any realistic
 278   * number of articles.
 279   *
 280   * @return string
 281   */
 282  function wfRandom() {
 283      # The maximum random value is "only" 2^31-1, so get two random
 284      # values to reduce the chance of dupes
 285      $max = mt_getrandmax() + 1;
 286      $rand = number_format( ( mt_rand() * $max + mt_rand() )
 287          / $max / $max, 12, '.', '' );
 288      return $rand;
 289  }
 290  
 291  /**
 292   * Get a random string containing a number of pseudo-random hex
 293   * characters.
 294   * @note This is not secure, if you are trying to generate some sort
 295   *       of token please use MWCryptRand instead.
 296   *
 297   * @param int $length The length of the string to generate
 298   * @return String
 299   * @since 1.20
 300   */
 301  function wfRandomString( $length = 32 ) {
 302      $str = '';
 303      for ( $n = 0; $n < $length; $n += 7 ) {
 304          $str .= sprintf( '%07x', mt_rand() & 0xfffffff );
 305      }
 306      return substr( $str, 0, $length );
 307  }
 308  
 309  /**
 310   * We want some things to be included as literal characters in our title URLs
 311   * for prettiness, which urlencode encodes by default.  According to RFC 1738,
 312   * all of the following should be safe:
 313   *
 314   * ;:@&=$-_.+!*'(),
 315   *
 316   * But + is not safe because it's used to indicate a space; &= are only safe in
 317   * paths and not in queries (and we don't distinguish here); ' seems kind of
 318   * scary; and urlencode() doesn't touch -_. to begin with.  Plus, although /
 319   * is reserved, we don't care.  So the list we unescape is:
 320   *
 321   * ;:@$!*(),/
 322   *
 323   * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
 324   * so no fancy : for IIS7.
 325   *
 326   * %2F in the page titles seems to fatally break for some reason.
 327   *
 328   * @param $s String:
 329   * @return string
 330   */
 331  function wfUrlencode( $s ) {
 332      static $needle;
 333      if ( is_null( $s ) ) {
 334          $needle = null;
 335          return '';
 336      }
 337  
 338      if ( is_null( $needle ) ) {
 339          $needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
 340          if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) || ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false ) ) {
 341              $needle[] = '%3A';
 342          }
 343      }
 344  
 345      $s = urlencode( $s );
 346      $s = str_ireplace(
 347          $needle,
 348          array( ';', '@', '$', '!', '*', '(', ')', ',', '/', ':' ),
 349          $s
 350      );
 351  
 352      return $s;
 353  }
 354  
 355  /**
 356   * This function takes two arrays as input, and returns a CGI-style string, e.g.
 357   * "days=7&limit=100". Options in the first array override options in the second.
 358   * Options set to null or false will not be output.
 359   *
 360   * @param array $array1 ( String|Array )
 361   * @param array $array2 ( String|Array )
 362   * @param $prefix String
 363   * @return String
 364   */
 365  function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
 366      if ( !is_null( $array2 ) ) {
 367          $array1 = $array1 + $array2;
 368      }
 369  
 370      $cgi = '';
 371      foreach ( $array1 as $key => $value ) {
 372          if ( !is_null( $value ) && $value !== false ) {
 373              if ( $cgi != '' ) {
 374                  $cgi .= '&';
 375              }
 376              if ( $prefix !== '' ) {
 377                  $key = $prefix . "[$key]";
 378              }
 379              if ( is_array( $value ) ) {
 380                  $firstTime = true;
 381                  foreach ( $value as $k => $v ) {
 382                      $cgi .= $firstTime ? '' : '&';
 383                      if ( is_array( $v ) ) {
 384                          $cgi .= wfArrayToCgi( $v, null, $key . "[$k]" );
 385                      } else {
 386                          $cgi .= urlencode( $key . "[$k]" ) . '=' . urlencode( $v );
 387                      }
 388                      $firstTime = false;
 389                  }
 390              } else {
 391                  if ( is_object( $value ) ) {
 392                      $value = $value->__toString();
 393                  }
 394                  $cgi .= urlencode( $key ) . '=' . urlencode( $value );
 395              }
 396          }
 397      }
 398      return $cgi;
 399  }
 400  
 401  /**
 402   * This is the logical opposite of wfArrayToCgi(): it accepts a query string as
 403   * its argument and returns the same string in array form.  This allows compatibility
 404   * with legacy functions that accept raw query strings instead of nice
 405   * arrays.  Of course, keys and values are urldecode()d.
 406   *
 407   * @param string $query query string
 408   * @return array Array version of input
 409   */
 410  function wfCgiToArray( $query ) {
 411      if ( isset( $query[0] ) && $query[0] == '?' ) {
 412          $query = substr( $query, 1 );
 413      }
 414      $bits = explode( '&', $query );
 415      $ret = array();
 416      foreach ( $bits as $bit ) {
 417          if ( $bit === '' ) {
 418              continue;
 419          }
 420          if ( strpos( $bit, '=' ) === false ) {
 421              // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
 422              $key = $bit;
 423              $value = '';
 424          } else {
 425              list( $key, $value ) = explode( '=', $bit );
 426          }
 427          $key = urldecode( $key );
 428          $value = urldecode( $value );
 429          if ( strpos( $key, '[' ) !== false ) {
 430              $keys = array_reverse( explode( '[', $key ) );
 431              $key = array_pop( $keys );
 432              $temp = $value;
 433              foreach ( $keys as $k ) {
 434                  $k = substr( $k, 0, -1 );
 435                  $temp = array( $k => $temp );
 436              }
 437              if ( isset( $ret[$key] ) ) {
 438                  $ret[$key] = array_merge( $ret[$key], $temp );
 439              } else {
 440                  $ret[$key] = $temp;
 441              }
 442          } else {
 443              $ret[$key] = $value;
 444          }
 445      }
 446      return $ret;
 447  }
 448  
 449  /**
 450   * Append a query string to an existing URL, which may or may not already
 451   * have query string parameters already. If so, they will be combined.
 452   *
 453   * @param $url String
 454   * @param $query Mixed: string or associative array
 455   * @return string
 456   */
 457  function wfAppendQuery( $url, $query ) {
 458      if ( is_array( $query ) ) {
 459          $query = wfArrayToCgi( $query );
 460      }
 461      if ( $query != '' ) {
 462          if ( false === strpos( $url, '?' ) ) {
 463              $url .= '?';
 464          } else {
 465              $url .= '&';
 466          }
 467          $url .= $query;
 468      }
 469      return $url;
 470  }
 471  
 472  /**
 473   * Expand a potentially local URL to a fully-qualified URL.  Assumes $wgServer
 474   * is correct.
 475   *
 476   * The meaning of the PROTO_* constants is as follows:
 477   * PROTO_HTTP: Output a URL starting with http://
 478   * PROTO_HTTPS: Output a URL starting with https://
 479   * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
 480   * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending on which protocol was used for the current incoming request
 481   * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer. For protocol-relative URLs, use the protocol of $wgCanonicalServer
 482   * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
 483   *
 484   * @todo this won't work with current-path-relative URLs
 485   * like "subdir/foo.html", etc.
 486   *
 487   * @param string $url either fully-qualified or a local path + query
 488   * @param $defaultProto Mixed: one of the PROTO_* constants. Determines the
 489   *                             protocol to use if $url or $wgServer is
 490   *                             protocol-relative
 491   * @return string Fully-qualified URL, current-path-relative URL or false if
 492   *                no valid URL can be constructed
 493   */
 494  function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
 495      global $wgServer, $wgCanonicalServer, $wgInternalServer;
 496      $serverUrl = $wgServer;
 497      if ( $defaultProto === PROTO_CANONICAL ) {
 498          $serverUrl = $wgCanonicalServer;
 499      }
 500      // Make $wgInternalServer fall back to $wgServer if not set
 501      if ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
 502          $serverUrl = $wgInternalServer;
 503      }
 504      if ( $defaultProto === PROTO_CURRENT ) {
 505          $defaultProto = WebRequest::detectProtocol() . '://';
 506      }
 507  
 508      // Analyze $serverUrl to obtain its protocol
 509      $bits = wfParseUrl( $serverUrl );
 510      $serverHasProto = $bits && $bits['scheme'] != '';
 511  
 512      if ( $defaultProto === PROTO_CANONICAL || $defaultProto === PROTO_INTERNAL ) {
 513          if ( $serverHasProto ) {
 514              $defaultProto = $bits['scheme'] . '://';
 515          } else {
 516              // $wgCanonicalServer or $wgInternalServer doesn't have a protocol. This really isn't supposed to happen
 517              // Fall back to HTTP in this ridiculous case
 518              $defaultProto = PROTO_HTTP;
 519          }
 520      }
 521  
 522      $defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 );
 523  
 524      if ( substr( $url, 0, 2 ) == '//' ) {
 525          $url = $defaultProtoWithoutSlashes . $url;
 526      } elseif ( substr( $url, 0, 1 ) == '/' ) {
 527          // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes, otherwise leave it alone
 528          $url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
 529      }
 530  
 531      $bits = wfParseUrl( $url );
 532      if ( $bits && isset( $bits['path'] ) ) {
 533          $bits['path'] = wfRemoveDotSegments( $bits['path'] );
 534          return wfAssembleUrl( $bits );
 535      } elseif ( $bits ) {
 536          # No path to expand
 537          return $url;
 538      } elseif ( substr( $url, 0, 1 ) != '/' ) {
 539          # URL is a relative path
 540          return wfRemoveDotSegments( $url );
 541      }
 542  
 543      # Expanded URL is not valid.
 544      return false;
 545  }
 546  
 547  /**
 548   * This function will reassemble a URL parsed with wfParseURL.  This is useful
 549   * if you need to edit part of a URL and put it back together.
 550   *
 551   * This is the basic structure used (brackets contain keys for $urlParts):
 552   * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
 553   *
 554   * @todo Need to integrate this into wfExpandUrl (bug 32168)
 555   *
 556   * @since 1.19
 557   * @param array $urlParts URL parts, as output from wfParseUrl
 558   * @return string URL assembled from its component parts
 559   */
 560  function wfAssembleUrl( $urlParts ) {
 561      $result = '';
 562  
 563      if ( isset( $urlParts['delimiter'] ) ) {
 564          if ( isset( $urlParts['scheme'] ) ) {
 565              $result .= $urlParts['scheme'];
 566          }
 567  
 568          $result .= $urlParts['delimiter'];
 569      }
 570  
 571      if ( isset( $urlParts['host'] ) ) {
 572          if ( isset( $urlParts['user'] ) ) {
 573              $result .= $urlParts['user'];
 574              if ( isset( $urlParts['pass'] ) ) {
 575                  $result .= ':' . $urlParts['pass'];
 576              }
 577              $result .= '@';
 578          }
 579  
 580          $result .= $urlParts['host'];
 581  
 582          if ( isset( $urlParts['port'] ) ) {
 583              $result .= ':' . $urlParts['port'];
 584          }
 585      }
 586  
 587      if ( isset( $urlParts['path'] ) ) {
 588          $result .= $urlParts['path'];
 589      }
 590  
 591      if ( isset( $urlParts['query'] ) ) {
 592          $result .= '?' . $urlParts['query'];
 593      }
 594  
 595      if ( isset( $urlParts['fragment'] ) ) {
 596          $result .= '#' . $urlParts['fragment'];
 597      }
 598  
 599      return $result;
 600  }
 601  
 602  /**
 603   * Remove all dot-segments in the provided URL path.  For example,
 604   * '/a/./b/../c/' becomes '/a/c/'.  For details on the algorithm, please see
 605   * RFC3986 section 5.2.4.
 606   *
 607   * @todo Need to integrate this into wfExpandUrl (bug 32168)
 608   *
 609   * @param string $urlPath URL path, potentially containing dot-segments
 610   * @return string URL path with all dot-segments removed
 611   */
 612  function wfRemoveDotSegments( $urlPath ) {
 613      $output = '';
 614      $inputOffset = 0;
 615      $inputLength = strlen( $urlPath );
 616  
 617      while ( $inputOffset < $inputLength ) {
 618          $prefixLengthOne = substr( $urlPath, $inputOffset, 1 );
 619          $prefixLengthTwo = substr( $urlPath, $inputOffset, 2 );
 620          $prefixLengthThree = substr( $urlPath, $inputOffset, 3 );
 621          $prefixLengthFour = substr( $urlPath, $inputOffset, 4 );
 622          $trimOutput = false;
 623  
 624          if ( $prefixLengthTwo == './' ) {
 625              # Step A, remove leading "./"
 626              $inputOffset += 2;
 627          } elseif ( $prefixLengthThree == '../' ) {
 628              # Step A, remove leading "../"
 629              $inputOffset += 3;
 630          } elseif ( ( $prefixLengthTwo == '/.' ) && ( $inputOffset + 2 == $inputLength ) ) {
 631              # Step B, replace leading "/.$" with "/"
 632              $inputOffset += 1;
 633              $urlPath[$inputOffset] = '/';
 634          } elseif ( $prefixLengthThree == '/./' ) {
 635              # Step B, replace leading "/./" with "/"
 636              $inputOffset += 2;
 637          } elseif ( $prefixLengthThree == '/..' && ( $inputOffset + 3 == $inputLength ) ) {
 638              # Step C, replace leading "/..$" with "/" and
 639              # remove last path component in output
 640              $inputOffset += 2;
 641              $urlPath[$inputOffset] = '/';
 642              $trimOutput = true;
 643          } elseif ( $prefixLengthFour == '/../' ) {
 644              # Step C, replace leading "/../" with "/" and
 645              # remove last path component in output
 646              $inputOffset += 3;
 647              $trimOutput = true;
 648          } elseif ( ( $prefixLengthOne == '.' ) && ( $inputOffset + 1 == $inputLength ) ) {
 649              # Step D, remove "^.$"
 650              $inputOffset += 1;
 651          } elseif ( ( $prefixLengthTwo == '..' ) && ( $inputOffset + 2 == $inputLength ) ) {
 652              # Step D, remove "^..$"
 653              $inputOffset += 2;
 654          } else {
 655              # Step E, move leading path segment to output
 656              if ( $prefixLengthOne == '/' ) {
 657                  $slashPos = strpos( $urlPath, '/', $inputOffset + 1 );
 658              } else {
 659                  $slashPos = strpos( $urlPath, '/', $inputOffset );
 660              }
 661              if ( $slashPos === false ) {
 662                  $output .= substr( $urlPath, $inputOffset );
 663                  $inputOffset = $inputLength;
 664              } else {
 665                  $output .= substr( $urlPath, $inputOffset, $slashPos - $inputOffset );
 666                  $inputOffset += $slashPos - $inputOffset;
 667              }
 668          }
 669  
 670          if ( $trimOutput ) {
 671              $slashPos = strrpos( $output, '/' );
 672              if ( $slashPos === false ) {
 673                  $output = '';
 674              } else {
 675                  $output = substr( $output, 0, $slashPos );
 676              }
 677          }
 678      }
 679  
 680      return $output;
 681  }
 682  
 683  /**
 684   * Returns a regular expression of url protocols
 685   *
 686   * @param bool $includeProtocolRelative If false, remove '//' from the returned protocol list.
 687   *        DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
 688   * @return String
 689   */
 690  function wfUrlProtocols( $includeProtocolRelative = true ) {
 691      global $wgUrlProtocols;
 692  
 693      // Cache return values separately based on $includeProtocolRelative
 694      static $withProtRel = null, $withoutProtRel = null;
 695      $cachedValue = $includeProtocolRelative ? $withProtRel : $withoutProtRel;
 696      if ( !is_null( $cachedValue ) ) {
 697          return $cachedValue;
 698      }
 699  
 700      // Support old-style $wgUrlProtocols strings, for backwards compatibility
 701      // with LocalSettings files from 1.5
 702      if ( is_array( $wgUrlProtocols ) ) {
 703          $protocols = array();
 704          foreach ( $wgUrlProtocols as $protocol ) {
 705              // Filter out '//' if !$includeProtocolRelative
 706              if ( $includeProtocolRelative || $protocol !== '//' ) {
 707                  $protocols[] = preg_quote( $protocol, '/' );
 708              }
 709          }
 710  
 711          $retval = implode( '|', $protocols );
 712      } else {
 713          // Ignore $includeProtocolRelative in this case
 714          // This case exists for pre-1.6 compatibility, and we can safely assume
 715          // that '//' won't appear in a pre-1.6 config because protocol-relative
 716          // URLs weren't supported until 1.18
 717          $retval = $wgUrlProtocols;
 718      }
 719  
 720      // Cache return value
 721      if ( $includeProtocolRelative ) {
 722          $withProtRel = $retval;
 723      } else {
 724          $withoutProtRel = $retval;
 725      }
 726      return $retval;
 727  }
 728  
 729  /**
 730   * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
 731   * you need a regex that matches all URL protocols but does not match protocol-
 732   * relative URLs
 733   * @return String
 734   */
 735  function wfUrlProtocolsWithoutProtRel() {
 736      return wfUrlProtocols( false );
 737  }
 738  
 739  /**
 740   * parse_url() work-alike, but non-broken.  Differences:
 741   *
 742   * 1) Does not raise warnings on bad URLs (just returns false)
 743   * 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
 744   * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
 745   *
 746   * @param string $url a URL to parse
 747   * @return Array: bits of the URL in an associative array, per PHP docs
 748   */
 749  function wfParseUrl( $url ) {
 750      global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
 751  
 752      // Protocol-relative URLs are handled really badly by parse_url(). It's so bad that the easiest
 753      // way to handle them is to just prepend 'http:' and strip the protocol out later
 754      $wasRelative = substr( $url, 0, 2 ) == '//';
 755      if ( $wasRelative ) {
 756          $url = "http:$url";
 757      }
 758      wfSuppressWarnings();
 759      $bits = parse_url( $url );
 760      wfRestoreWarnings();
 761      // parse_url() returns an array without scheme for some invalid URLs, e.g.
 762      // parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
 763      if ( !$bits || !isset( $bits['scheme'] ) ) {
 764          return false;
 765      }
 766  
 767      // parse_url() incorrectly handles schemes case-sensitively. Convert it to lowercase.
 768      $bits['scheme'] = strtolower( $bits['scheme'] );
 769  
 770      // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
 771      if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
 772          $bits['delimiter'] = '://';
 773      } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
 774          $bits['delimiter'] = ':';
 775          // parse_url detects for news: and mailto: the host part of an url as path
 776          // We have to correct this wrong detection
 777          if ( isset( $bits['path'] ) ) {
 778              $bits['host'] = $bits['path'];
 779              $bits['path'] = '';
 780          }
 781      } else {
 782          return false;
 783      }
 784  
 785      /* Provide an empty host for eg. file:/// urls (see bug 28627) */
 786      if ( !isset( $bits['host'] ) ) {
 787          $bits['host'] = '';
 788  
 789          // bug 45069
 790          if ( isset( $bits['path'] ) ) {
 791              /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
 792              if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
 793                  $bits['path'] = '/' . $bits['path'];
 794              }
 795          } else {
 796              $bits['path'] = '';
 797          }
 798      }
 799  
 800      // If the URL was protocol-relative, fix scheme and delimiter
 801      if ( $wasRelative ) {
 802          $bits['scheme'] = '';
 803          $bits['delimiter'] = '//';
 804      }
 805      return $bits;
 806  }
 807  
 808  /**
 809   * Take a URL, make sure it's expanded to fully qualified, and replace any
 810   * encoded non-ASCII Unicode characters with their UTF-8 original forms
 811   * for more compact display and legibility for local audiences.
 812   *
 813   * @todo handle punycode domains too
 814   *
 815   * @param $url string
 816   * @return string
 817   */
 818  function wfExpandIRI( $url ) {
 819      return preg_replace_callback( '/((?:%[89A-F][0-9A-F])+)/i', 'wfExpandIRI_callback', wfExpandUrl( $url ) );
 820  }
 821  
 822  /**
 823   * Private callback for wfExpandIRI
 824   * @param array $matches
 825   * @return string
 826   */
 827  function wfExpandIRI_callback( $matches ) {
 828      return urldecode( $matches[1] );
 829  }
 830  
 831  /**
 832   * Make URL indexes, appropriate for the el_index field of externallinks.
 833   *
 834   * @param $url String
 835   * @return array
 836   */
 837  function wfMakeUrlIndexes( $url ) {
 838      $bits = wfParseUrl( $url );
 839  
 840      // Reverse the labels in the hostname, convert to lower case
 841      // For emails reverse domainpart only
 842      if ( $bits['scheme'] == 'mailto' ) {
 843          $mailparts = explode( '@', $bits['host'], 2 );
 844          if ( count( $mailparts ) === 2 ) {
 845              $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
 846          } else {
 847              // No domain specified, don't mangle it
 848              $domainpart = '';
 849          }
 850          $reversedHost = $domainpart . '@' . $mailparts[0];
 851      } else {
 852          $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
 853      }
 854      // Add an extra dot to the end
 855      // Why? Is it in wrong place in mailto links?
 856      if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
 857          $reversedHost .= '.';
 858      }
 859      // Reconstruct the pseudo-URL
 860      $prot = $bits['scheme'];
 861      $index = $prot . $bits['delimiter'] . $reversedHost;
 862      // Leave out user and password. Add the port, path, query and fragment
 863      if ( isset( $bits['port'] ) ) {
 864          $index .= ':' . $bits['port'];
 865      }
 866      if ( isset( $bits['path'] ) ) {
 867          $index .= $bits['path'];
 868      } else {
 869          $index .= '/';
 870      }
 871      if ( isset( $bits['query'] ) ) {
 872          $index .= '?' . $bits['query'];
 873      }
 874      if ( isset( $bits['fragment'] ) ) {
 875          $index .= '#' . $bits['fragment'];
 876      }
 877  
 878      if ( $prot == '' ) {
 879          return array( "http:$index", "https:$index" );
 880      } else {
 881          return array( $index );
 882      }
 883  }
 884  
 885  /**
 886   * Check whether a given URL has a domain that occurs in a given set of domains
 887   * @param string $url URL
 888   * @param array $domains Array of domains (strings)
 889   * @return bool True if the host part of $url ends in one of the strings in $domains
 890   */
 891  function wfMatchesDomainList( $url, $domains ) {
 892      $bits = wfParseUrl( $url );
 893      if ( is_array( $bits ) && isset( $bits['host'] ) ) {
 894          $host = '.' . $bits['host'];
 895          foreach ( (array)$domains as $domain ) {
 896              $domain = '.' . $domain;
 897              if ( substr( $host, -strlen( $domain ) ) === $domain ) {
 898                  return true;
 899              }
 900          }
 901      }
 902      return false;
 903  }
 904  
 905  /**
 906   * Sends a line to the debug log if enabled or, optionally, to a comment in output.
 907   * In normal operation this is a NOP.
 908   *
 909   * Controlling globals:
 910   * $wgDebugLogFile - points to the log file
 911   * $wgProfileOnly - if set, normal debug messages will not be recorded.
 912   * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
 913   * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
 914   *
 915   * @param $text String
 916   * @param bool $logonly set true to avoid appearing in HTML when $wgDebugComments is set
 917   */
 918  function wfDebug( $text, $logonly = false ) {
 919      global $wgDebugLogFile, $wgProfileOnly, $wgDebugRawPage, $wgDebugLogPrefix;
 920  
 921      if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
 922          return;
 923      }
 924  
 925      $timer = wfDebugTimer();
 926      if ( $timer !== '' ) {
 927          $text = preg_replace( '/[^\n]/', $timer . '\0', $text, 1 );
 928      }
 929  
 930      if ( !$logonly ) {
 931          MWDebug::debugMsg( $text );
 932      }
 933  
 934      if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
 935          # Strip unprintables; they can switch terminal modes when binary data
 936          # gets dumped, which is pretty annoying.
 937          $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
 938          $text = $wgDebugLogPrefix . $text;
 939          wfErrorLog( $text, $wgDebugLogFile );
 940      }
 941  }
 942  
 943  /**
 944   * Returns true if debug logging should be suppressed if $wgDebugRawPage = false
 945   * @return bool
 946   */
 947  function wfIsDebugRawPage() {
 948      static $cache;
 949      if ( $cache !== null ) {
 950          return $cache;
 951      }
 952      # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
 953      if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
 954          || (
 955              isset( $_SERVER['SCRIPT_NAME'] )
 956              && substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
 957          ) )
 958      {
 959          $cache = true;
 960      } else {
 961          $cache = false;
 962      }
 963      return $cache;
 964  }
 965  
 966  /**
 967   * Get microsecond timestamps for debug logs
 968   *
 969   * @return string
 970   */
 971  function wfDebugTimer() {
 972      global $wgDebugTimestamps, $wgRequestTime;
 973  
 974      if ( !$wgDebugTimestamps ) {
 975          return '';
 976      }
 977  
 978      $prefix = sprintf( "%6.4f", microtime( true ) - $wgRequestTime );
 979      $mem = sprintf( "%5.1fM", ( memory_get_usage( true ) / ( 1024 * 1024 ) ) );
 980      return "$prefix $mem  ";
 981  }
 982  
 983  /**
 984   * Send a line giving PHP memory usage.
 985   *
 986   * @param bool $exact print exact values instead of kilobytes (default: false)
 987   */
 988  function wfDebugMem( $exact = false ) {
 989      $mem = memory_get_usage();
 990      if ( !$exact ) {
 991          $mem = floor( $mem / 1024 ) . ' kilobytes';
 992      } else {
 993          $mem .= ' bytes';
 994      }
 995      wfDebug( "Memory usage: $mem\n" );
 996  }
 997  
 998  /**
 999   * Send a line to a supplementary debug log file, if configured, or main debug log if not.
1000   * $wgDebugLogGroups[$logGroup] should be set to a filename to send to a separate log.
1001   *
1002   * @param $logGroup String
1003   * @param $text String
1004   * @param bool $public whether to log the event in the public log if no private
1005   *                     log file is specified, (default true)
1006   */
1007  function wfDebugLog( $logGroup, $text, $public = true ) {
1008      global $wgDebugLogGroups;
1009      $text = trim( $text ) . "\n";
1010      if ( isset( $wgDebugLogGroups[$logGroup] ) ) {
1011          $time = wfTimestamp( TS_DB );
1012          $wiki = wfWikiID();
1013          $host = wfHostname();
1014          wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
1015      } elseif ( $public === true ) {
1016          wfDebug( "[$logGroup] $text", false );
1017      }
1018  }
1019  
1020  /**
1021   * Log for database errors
1022   *
1023   * @param string $text database error message.
1024   */
1025  function wfLogDBError( $text ) {
1026      global $wgDBerrorLog, $wgDBerrorLogTZ;
1027      static $logDBErrorTimeZoneObject = null;
1028  
1029      if ( $wgDBerrorLog ) {
1030          $host = wfHostname();
1031          $wiki = wfWikiID();
1032  
1033          if ( $wgDBerrorLogTZ && !$logDBErrorTimeZoneObject ) {
1034              $logDBErrorTimeZoneObject = new DateTimeZone( $wgDBerrorLogTZ );
1035          }
1036  
1037          // Workaround for https://bugs.php.net/bug.php?id=52063
1038          // Can be removed when min PHP > 5.3.2
1039          if ( $logDBErrorTimeZoneObject === null ) {
1040              $d = date_create( "now" );
1041          } else {
1042              $d = date_create( "now", $logDBErrorTimeZoneObject );
1043          }
1044  
1045          $date = $d->format( 'D M j G:i:s T Y' );
1046  
1047          $text = "$date\t$host\t$wiki\t$text";
1048          wfErrorLog( $text, $wgDBerrorLog );
1049      }
1050  }
1051  
1052  /**
1053   * Throws a warning that $function is deprecated
1054   *
1055   * @param $function String
1056   * @param string|bool $version Version of MediaWiki that the function was deprecated in (Added in 1.19).
1057   * @param string|bool $component Added in 1.19.
1058   * @param $callerOffset integer: How far up the call stack is the original
1059   *    caller. 2 = function that called the function that called
1060   *    wfDeprecated (Added in 1.20)
1061   *
1062   * @return null
1063   */
1064  function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
1065      MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
1066  }
1067  
1068  /**
1069   * Send a warning either to the debug log or in a PHP error depending on
1070   * $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead.
1071   *
1072   * @param string $msg message to send
1073   * @param $callerOffset Integer: number of items to go back in the backtrace to
1074   *        find the correct caller (1 = function calling wfWarn, ...)
1075   * @param $level Integer: PHP error level; defaults to E_USER_NOTICE;
1076   *        only used when $wgDevelopmentWarnings is true
1077   */
1078  function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
1079      MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' );
1080  }
1081  
1082  /**
1083   * Send a warning as a PHP error and the debug log. This is intended for logging
1084   * warnings in production. For logging development warnings, use WfWarn instead.
1085   *
1086   * @param $msg String: message to send
1087   * @param $callerOffset Integer: number of items to go back in the backtrace to
1088   *        find the correct caller (1 = function calling wfLogWarning, ...)
1089   * @param $level Integer: PHP error level; defaults to E_USER_WARNING
1090   */
1091  function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
1092      MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
1093  }
1094  
1095  /**
1096   * Log to a file without getting "file size exceeded" signals.
1097   *
1098   * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
1099   * send lines to the specified port, prefixed by the specified prefix and a space.
1100   *
1101   * @param $text String
1102   * @param string $file filename
1103   * @throws MWException
1104   */
1105  function wfErrorLog( $text, $file ) {
1106      if ( substr( $file, 0, 4 ) == 'udp:' ) {
1107          # Needs the sockets extension
1108          if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
1109              // IPv6 bracketed host
1110              $host = $m[2];
1111              $port = intval( $m[3] );
1112              $prefix = isset( $m[4] ) ? $m[4] : false;
1113              $domain = AF_INET6;
1114          } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
1115              $host = $m[2];
1116              if ( !IP::isIPv4( $host ) ) {
1117                  $host = gethostbyname( $host );
1118              }
1119              $port = intval( $m[3] );
1120              $prefix = isset( $m[4] ) ? $m[4] : false;
1121              $domain = AF_INET;
1122          } else {
1123              throw new MWException( __METHOD__ . ': Invalid UDP specification' );
1124          }
1125  
1126          // Clean it up for the multiplexer
1127          if ( strval( $prefix ) !== '' ) {
1128              $text = preg_replace( '/^/m', $prefix . ' ', $text );
1129  
1130              // Limit to 64KB
1131              if ( strlen( $text ) > 65506 ) {
1132                  $text = substr( $text, 0, 65506 );
1133              }
1134  
1135              if ( substr( $text, -1 ) != "\n" ) {
1136                  $text .= "\n";
1137              }
1138          } elseif ( strlen( $text ) > 65507 ) {
1139              $text = substr( $text, 0, 65507 );
1140          }
1141  
1142          $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
1143          if ( !$sock ) {
1144              return;
1145          }
1146  
1147          socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
1148          socket_close( $sock );
1149      } else {
1150          wfSuppressWarnings();
1151          $exists = file_exists( $file );
1152          $size = $exists ? filesize( $file ) : false;
1153          if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
1154              file_put_contents( $file, $text, FILE_APPEND );
1155          }
1156          wfRestoreWarnings();
1157      }
1158  }
1159  
1160  /**
1161   * @todo document
1162   */
1163  function wfLogProfilingData() {
1164      global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
1165      global $wgProfileLimit, $wgUser;
1166  
1167      StatCounter::singleton()->flush();
1168  
1169      $profiler = Profiler::instance();
1170  
1171      # Profiling must actually be enabled...
1172      if ( $profiler->isStub() ) {
1173          return;
1174      }
1175  
1176      // Get total page request time and only show pages that longer than
1177      // $wgProfileLimit time (default is 0)
1178      $elapsed = microtime( true ) - $wgRequestTime;
1179      if ( $elapsed <= $wgProfileLimit ) {
1180          return;
1181      }
1182  
1183      $profiler->logData();
1184  
1185      // Check whether this should be logged in the debug file.
1186      if ( $wgDebugLogFile == '' || ( !$wgDebugRawPage && wfIsDebugRawPage() ) ) {
1187          return;
1188      }
1189  
1190      $forward = '';
1191      if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1192          $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1193      }
1194      if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
1195          $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
1196      }
1197      if ( !empty( $_SERVER['HTTP_FROM'] ) ) {
1198          $forward .= ' from ' . $_SERVER['HTTP_FROM'];
1199      }
1200      if ( $forward ) {
1201          $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
1202      }
1203      // Don't load $wgUser at this late stage just for statistics purposes
1204      // @todo FIXME: We can detect some anons even if it is not loaded. See User::getId()
1205      if ( $wgUser->isItemLoaded( 'id' ) && $wgUser->isAnon() ) {
1206          $forward .= ' anon';
1207      }
1208  
1209      // Command line script uses a FauxRequest object which does not have
1210      // any knowledge about an URL and throw an exception instead.
1211      try {
1212          $requestUrl = $wgRequest->getRequestURL();
1213      } catch ( MWException $e ) {
1214          $requestUrl = 'n/a';
1215      }
1216  
1217      $log = sprintf( "%s\t%04.3f\t%s\n",
1218          gmdate( 'YmdHis' ), $elapsed,
1219          urldecode( $requestUrl . $forward ) );
1220  
1221      wfErrorLog( $log . $profiler->getOutput(), $wgDebugLogFile );
1222  }
1223  
1224  /**
1225   * Increment a statistics counter
1226   *
1227   * @param $key String
1228   * @param $count Int
1229   * @return void
1230   */
1231  function wfIncrStats( $key, $count = 1 ) {
1232      StatCounter::singleton()->incr( $key, $count );
1233  }
1234  
1235  /**
1236   * Check whether the wiki is in read-only mode.
1237   *
1238   * @return bool
1239   */
1240  function wfReadOnly() {
1241      return wfReadOnlyReason() !== false;
1242  }
1243  
1244  /**
1245   * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
1246   *
1247   * @return string|bool: String when in read-only mode; false otherwise
1248   */
1249  function wfReadOnlyReason() {
1250      global $wgReadOnly, $wgReadOnlyFile;
1251  
1252      if ( $wgReadOnly === null ) {
1253          // Set $wgReadOnly for faster access next time
1254          if ( is_file( $wgReadOnlyFile ) && filesize( $wgReadOnlyFile ) > 0 ) {
1255              $wgReadOnly = file_get_contents( $wgReadOnlyFile );
1256          } else {
1257              $wgReadOnly = false;
1258          }
1259      }
1260  
1261      return $wgReadOnly;
1262  }
1263  
1264  /**
1265   * Return a Language object from $langcode
1266   *
1267   * @param $langcode Mixed: either:
1268   *                  - a Language object
1269   *                  - code of the language to get the message for, if it is
1270   *                    a valid code create a language for that language, if
1271   *                    it is a string but not a valid code then make a basic
1272   *                    language object
1273   *                  - a boolean: if it's false then use the global object for
1274   *                    the current user's language (as a fallback for the old parameter
1275   *                    functionality), or if it is true then use global object
1276   *                    for the wiki's content language.
1277   * @return Language object
1278   */
1279  function wfGetLangObj( $langcode = false ) {
1280      # Identify which language to get or create a language object for.
1281      # Using is_object here due to Stub objects.
1282      if ( is_object( $langcode ) ) {
1283          # Great, we already have the object (hopefully)!
1284          return $langcode;
1285      }
1286  
1287      global $wgContLang, $wgLanguageCode;
1288      if ( $langcode === true || $langcode === $wgLanguageCode ) {
1289          # $langcode is the language code of the wikis content language object.
1290          # or it is a boolean and value is true
1291          return $wgContLang;
1292      }
1293  
1294      global $wgLang;
1295      if ( $langcode === false || $langcode === $wgLang->getCode() ) {
1296          # $langcode is the language code of user language object.
1297          # or it was a boolean and value is false
1298          return $wgLang;
1299      }
1300  
1301      $validCodes = array_keys( Language::fetchLanguageNames() );
1302      if ( in_array( $langcode, $validCodes ) ) {
1303          # $langcode corresponds to a valid language.
1304          return Language::factory( $langcode );
1305      }
1306  
1307      # $langcode is a string, but not a valid language code; use content language.
1308      wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
1309      return $wgContLang;
1310  }
1311  
1312  /**
1313   * Old function when $wgBetterDirectionality existed
1314   * All usage removed, wfUILang can be removed in near future
1315   *
1316   * @deprecated since 1.18
1317   * @return Language
1318   */
1319  function wfUILang() {
1320      wfDeprecated( __METHOD__, '1.18' );
1321      global $wgLang;
1322      return $wgLang;
1323  }
1324  
1325  /**
1326   * This is the function for getting translated interface messages.
1327   *
1328   * @see Message class for documentation how to use them.
1329   * @see https://www.mediawiki.org/wiki/Manual:Messages_API
1330   *
1331   * This function replaces all old wfMsg* functions.
1332   *
1333   * @param $key \string Message key.
1334   * Varargs: normal message parameters.
1335   * @return Message
1336   * @since 1.17
1337   */
1338  function wfMessage( $key /*...*/) {
1339      $params = func_get_args();
1340      array_shift( $params );
1341      if ( isset( $params[0] ) && is_array( $params[0] ) ) {
1342          $params = $params[0];
1343      }
1344      return new Message( $key, $params );
1345  }
1346  
1347  /**
1348   * This function accepts multiple message keys and returns a message instance
1349   * for the first message which is non-empty. If all messages are empty then an
1350   * instance of the first message key is returned.
1351   * @param varargs: message keys
1352   * @return Message
1353   * @since 1.18
1354   */
1355  function wfMessageFallback( /*...*/ ) {
1356      $args = func_get_args();
1357      return call_user_func_array( 'Message::newFallbackSequence', $args );
1358  }
1359  
1360  /**
1361   * Get a message from anywhere, for the current user language.
1362   *
1363   * Use wfMsgForContent() instead if the message should NOT
1364   * change depending on the user preferences.
1365   *
1366   * @deprecated since 1.18
1367   *
1368   * @param string $key lookup key for the message, usually
1369   *    defined in languages/Language.php
1370   *
1371   * Parameters to the message, which can be used to insert variable text into
1372   * it, can be passed to this function in the following formats:
1373   * - One per argument, starting at the second parameter
1374   * - As an array in the second parameter
1375   * These are not shown in the function definition.
1376   *
1377   * @return String
1378   */
1379  function wfMsg( $key ) {
1380      wfDeprecated( __METHOD__, '1.21' );
1381  
1382      $args = func_get_args();
1383      array_shift( $args );
1384      return wfMsgReal( $key, $args );
1385  }
1386  
1387  /**
1388   * Same as above except doesn't transform the message
1389   *
1390   * @deprecated since 1.18
1391   *
1392   * @param $key String
1393   * @return String
1394   */
1395  function wfMsgNoTrans( $key ) {
1396      wfDeprecated( __METHOD__, '1.21' );
1397  
1398      $args = func_get_args();
1399      array_shift( $args );
1400      return wfMsgReal( $key, $args, true, false, false );
1401  }
1402  
1403  /**
1404   * Get a message from anywhere, for the current global language
1405   * set with $wgLanguageCode.
1406   *
1407   * Use this if the message should NOT change dependent on the
1408   * language set in the user's preferences. This is the case for
1409   * most text written into logs, as well as link targets (such as
1410   * the name of the copyright policy page). Link titles, on the
1411   * other hand, should be shown in the UI language.
1412   *
1413   * Note that MediaWiki allows users to change the user interface
1414   * language in their preferences, but a single installation
1415   * typically only contains content in one language.
1416   *
1417   * Be wary of this distinction: If you use wfMsg() where you should
1418   * use wfMsgForContent(), a user of the software may have to
1419   * customize potentially hundreds of messages in
1420   * order to, e.g., fix a link in every possible language.
1421   *
1422   * @deprecated since 1.18
1423   *
1424   * @param string $key lookup key for the message, usually
1425   *     defined in languages/Language.php
1426   * @return String
1427   */
1428  function wfMsgForContent( $key ) {
1429      wfDeprecated( __METHOD__, '1.21' );
1430  
1431      global $wgForceUIMsgAsContentMsg;
1432      $args = func_get_args();
1433      array_shift( $args );
1434      $forcontent = true;
1435      if ( is_array( $wgForceUIMsgAsContentMsg ) &&
1436          in_array( $key, $wgForceUIMsgAsContentMsg ) )
1437      {
1438          $forcontent = false;
1439      }
1440      return wfMsgReal( $key, $args, true, $forcontent );
1441  }
1442  
1443  /**
1444   * Same as above except doesn't transform the message
1445   *
1446   * @deprecated since 1.18
1447   *
1448   * @param $key String
1449   * @return String
1450   */
1451  function wfMsgForContentNoTrans( $key ) {
1452      wfDeprecated( __METHOD__, '1.21' );
1453  
1454      global $wgForceUIMsgAsContentMsg;
1455      $args = func_get_args();
1456      array_shift( $args );
1457      $forcontent = true;
1458      if ( is_array( $wgForceUIMsgAsContentMsg ) &&
1459          in_array( $key, $wgForceUIMsgAsContentMsg ) )
1460      {
1461          $forcontent = false;
1462      }
1463      return wfMsgReal( $key, $args, true, $forcontent, false );
1464  }
1465  
1466  /**
1467   * Really get a message
1468   *
1469   * @deprecated since 1.18
1470   *
1471   * @param string $key key to get.
1472   * @param $args
1473   * @param $useDB Boolean
1474   * @param $forContent Mixed: Language code, or false for user lang, true for content lang.
1475   * @param $transform Boolean: Whether or not to transform the message.
1476   * @return String: the requested message.
1477   */
1478  function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
1479      wfDeprecated( __METHOD__, '1.21' );
1480  
1481      wfProfileIn( __METHOD__ );
1482      $message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
1483      $message = wfMsgReplaceArgs( $message, $args );
1484      wfProfileOut( __METHOD__ );
1485      return $message;
1486  }
1487  
1488  /**
1489   * Fetch a message string value, but don't replace any keys yet.
1490   *
1491   * @deprecated since 1.18
1492   *
1493   * @param $key String
1494   * @param $useDB Bool
1495   * @param string $langCode Code of the language to get the message for, or
1496   *                  behaves as a content language switch if it is a boolean.
1497   * @param $transform Boolean: whether to parse magic words, etc.
1498   * @return string
1499   */
1500  function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) {
1501      wfDeprecated( __METHOD__, '1.21' );
1502  
1503      wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
1504  
1505      $cache = MessageCache::singleton();
1506      $message = $cache->get( $key, $useDB, $langCode );
1507      if ( $message === false ) {
1508          $message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
1509      } elseif ( $transform ) {
1510          $message = $cache->transform( $message );
1511      }
1512      return $message;
1513  }
1514  
1515  /**
1516   * Replace message parameter keys on the given formatted output.
1517   *
1518   * @param $message String
1519   * @param $args Array
1520   * @return string
1521   * @private
1522   */
1523  function wfMsgReplaceArgs( $message, $args ) {
1524      # Fix windows line-endings
1525      # Some messages are split with explode("\n", $msg)
1526      $message = str_replace( "\r", '', $message );
1527  
1528      // Replace arguments
1529      if ( count( $args ) ) {
1530          if ( is_array( $args[0] ) ) {
1531              $args = array_values( $args[0] );
1532          }
1533          $replacementKeys = array();
1534          foreach ( $args as $n => $param ) {
1535              $replacementKeys['$' . ( $n + 1 )] = $param;
1536          }
1537          $message = strtr( $message, $replacementKeys );
1538      }
1539  
1540      return $message;
1541  }
1542  
1543  /**
1544   * Return an HTML-escaped version of a message.
1545   * Parameter replacements, if any, are done *after* the HTML-escaping,
1546   * so parameters may contain HTML (eg links or form controls). Be sure
1547   * to pre-escape them if you really do want plaintext, or just wrap
1548   * the whole thing in htmlspecialchars().
1549   *
1550   * @deprecated since 1.18
1551   *
1552   * @param $key String
1553   * @param string ... parameters
1554   * @return string
1555   */
1556  function wfMsgHtml( $key ) {
1557      wfDeprecated( __METHOD__, '1.21' );
1558  
1559      $args = func_get_args();
1560      array_shift( $args );
1561      return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key ) ), $args );
1562  }
1563  
1564  /**
1565   * Return an HTML version of message
1566   * Parameter replacements, if any, are done *after* parsing the wiki-text message,
1567   * so parameters may contain HTML (eg links or form controls). Be sure
1568   * to pre-escape them if you really do want plaintext, or just wrap
1569   * the whole thing in htmlspecialchars().
1570   *
1571   * @deprecated since 1.18
1572   *
1573   * @param $key String
1574   * @param string ... parameters
1575   * @return string
1576   */
1577  function wfMsgWikiHtml( $key ) {
1578      wfDeprecated( __METHOD__, '1.21' );
1579  
1580      $args = func_get_args();
1581      array_shift( $args );
1582      return wfMsgReplaceArgs(
1583          MessageCache::singleton()->parse( wfMsgGetKey( $key ), null,
1584          /* can't be set to false */ true, /* interface */ true )->getText(),
1585          $args );
1586  }
1587  
1588  /**
1589   * Returns message in the requested format
1590   *
1591   * @deprecated since 1.18
1592   *
1593   * @param string $key key of the message
1594   * @param array $options processing rules. Can take the following options:
1595   *   <i>parse</i>: parses wikitext to HTML
1596   *   <i>parseinline</i>: parses wikitext to HTML and removes the surrounding
1597   *       p's added by parser or tidy
1598   *   <i>escape</i>: filters message through htmlspecialchars
1599   *   <i>escapenoentities</i>: same, but allows entity references like &#160; through
1600   *   <i>replaceafter</i>: parameters are substituted after parsing or escaping
1601   *   <i>parsemag</i>: transform the message using magic phrases
1602   *   <i>content</i>: fetch message for content language instead of interface
1603   * Also can accept a single associative argument, of the form 'language' => 'xx':
1604   *   <i>language</i>: Language object or language code to fetch message for
1605   *       (overridden by <i>content</i>).
1606   * Behavior for conflicting options (e.g., parse+parseinline) is undefined.
1607   *
1608   * @return String
1609   */
1610  function wfMsgExt( $key, $options ) {
1611      wfDeprecated( __METHOD__, '1.21' );
1612  
1613      $args = func_get_args();
1614      array_shift( $args );
1615      array_shift( $args );
1616      $options = (array)$options;
1617  
1618      foreach ( $options as $arrayKey => $option ) {
1619          if ( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
1620              # An unknown index, neither numeric nor "language"
1621              wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING );
1622          } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
1623          array( 'parse', 'parseinline', 'escape', 'escapenoentities',
1624          'replaceafter', 'parsemag', 'content' ) ) ) {
1625              # A numeric index with unknown value
1626              wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING );
1627          }
1628      }
1629  
1630      if ( in_array( 'content', $options, true ) ) {
1631          $forContent = true;
1632          $langCode = true;
1633          $langCodeObj = null;
1634      } elseif ( array_key_exists( 'language', $options ) ) {
1635          $forContent = false;
1636          $langCode = wfGetLangObj( $options['language'] );
1637          $langCodeObj = $langCode;
1638      } else {
1639          $forContent = false;
1640          $langCode = false;
1641          $langCodeObj = null;
1642      }
1643  
1644      $string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
1645  
1646      if ( !in_array( 'replaceafter', $options, true ) ) {
1647          $string = wfMsgReplaceArgs( $string, $args );
1648      }
1649  
1650      $messageCache = MessageCache::singleton();
1651      $parseInline = in_array( 'parseinline', $options, true );
1652      if ( in_array( 'parse', $options, true ) || $parseInline ) {
1653          $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj );
1654          if ( $string instanceof ParserOutput ) {
1655              $string = $string->getText();
1656          }
1657  
1658          if ( $parseInline ) {
1659              $m = array();
1660              if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
1661                  $string = $m[1];
1662              }
1663          }
1664      } elseif ( in_array( 'parsemag', $options, true ) ) {
1665          $string = $messageCache->transform( $string,
1666                  !$forContent, $langCodeObj );
1667      }
1668  
1669      if ( in_array( 'escape', $options, true ) ) {
1670          $string = htmlspecialchars ( $string );
1671      } elseif ( in_array( 'escapenoentities', $options, true ) ) {
1672          $string = Sanitizer::escapeHtmlAllowEntities( $string );
1673      }
1674  
1675      if ( in_array( 'replaceafter', $options, true ) ) {
1676          $string = wfMsgReplaceArgs( $string, $args );
1677      }
1678  
1679      return $string;
1680  }
1681  
1682  /**
1683   * Since wfMsg() and co suck, they don't return false if the message key they
1684   * looked up didn't exist but instead the key wrapped in <>'s, this function checks for the
1685   * nonexistence of messages by checking the MessageCache::get() result directly.
1686   *
1687   * @deprecated since 1.18. Use Message::isDisabled().
1688   *
1689   * @param $key      String: the message key looked up
1690   * @return Boolean True if the message *doesn't* exist.
1691   */
1692  function wfEmptyMsg( $key ) {
1693      wfDeprecated( __METHOD__, '1.21' );
1694  
1695      return MessageCache::singleton()->get( $key, /*useDB*/true, /*content*/false ) === false;
1696  }
1697  
1698  /**
1699   * Throw a debugging exception. This function previously once exited the process,
1700   * but now throws an exception instead, with similar results.
1701   *
1702   * @deprecated since 1.22; just throw an MWException yourself
1703   * @param string $msg message shown when dying.
1704   * @throws MWException
1705   */
1706  function wfDebugDieBacktrace( $msg = '' ) {
1707      wfDeprecated( __FUNCTION__, '1.22' );
1708      throw new MWException( $msg );
1709  }
1710  
1711  /**
1712   * Fetch server name for use in error reporting etc.
1713   * Use real server name if available, so we know which machine
1714   * in a server farm generated the current page.
1715   *
1716   * @return string
1717   */
1718  function wfHostname() {
1719      static $host;
1720      if ( is_null( $host ) ) {
1721  
1722          # Hostname overriding
1723          global $wgOverrideHostname;
1724          if ( $wgOverrideHostname !== false ) {
1725              # Set static and skip any detection
1726              $host = $wgOverrideHostname;
1727              return $host;
1728          }
1729  
1730          if ( function_exists( 'posix_uname' ) ) {
1731              // This function not present on Windows
1732              $uname = posix_uname();
1733          } else {
1734              $uname = false;
1735          }
1736          if ( is_array( $uname ) && isset( $uname['nodename'] ) ) {
1737              $host = $uname['nodename'];
1738          } elseif ( getenv( 'COMPUTERNAME' ) ) {
1739              # Windows computer name
1740              $host = getenv( 'COMPUTERNAME' );
1741          } else {
1742              # This may be a virtual server.
1743              $host = $_SERVER['SERVER_NAME'];
1744          }
1745      }
1746      return $host;
1747  }
1748  
1749  /**
1750   * Returns a HTML comment with the elapsed time since request.
1751   * This method has no side effects.
1752   *
1753   * @return string
1754   */
1755  function wfReportTime() {
1756      global $wgRequestTime, $wgShowHostnames;
1757  
1758      $elapsed = microtime( true ) - $wgRequestTime;
1759  
1760      return $wgShowHostnames
1761          ? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
1762          : sprintf( '<!-- Served in %01.3f secs. -->', $elapsed );
1763  }
1764  
1765  /**
1766   * Safety wrapper for debug_backtrace().
1767   *
1768   * With Zend Optimizer 3.2.0 loaded, this causes segfaults under somewhat
1769   * murky circumstances, which may be triggered in part by stub objects
1770   * or other fancy talking'.
1771   *
1772   * Will return an empty array if Zend Optimizer is detected or if
1773   * debug_backtrace is disabled, otherwise the output from
1774   * debug_backtrace() (trimmed).
1775   *
1776   * @param int $limit This parameter can be used to limit the number of stack frames returned
1777   *
1778   * @return array of backtrace information
1779   */
1780  function wfDebugBacktrace( $limit = 0 ) {
1781      static $disabled = null;
1782  
1783      if ( extension_loaded( 'Zend Optimizer' ) ) {
1784          wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
1785          return array();
1786      }
1787  
1788      if ( is_null( $disabled ) ) {
1789          $disabled = false;
1790          $functions = explode( ',', ini_get( 'disable_functions' ) );
1791          $functions = array_map( 'trim', $functions );
1792          $functions = array_map( 'strtolower', $functions );
1793          if ( in_array( 'debug_backtrace', $functions ) ) {
1794              wfDebug( "debug_backtrace is in disabled_functions\n" );
1795              $disabled = true;
1796          }
1797      }
1798      if ( $disabled ) {
1799          return array();
1800      }
1801  
1802      if ( $limit && version_compare( PHP_VERSION, '5.4.0', '>=' ) ) {
1803          return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 );
1804      } else {
1805          return array_slice( debug_backtrace(), 1 );
1806      }
1807  }
1808  
1809  /**
1810   * Get a debug backtrace as a string
1811   *
1812   * @return string
1813   */
1814  function wfBacktrace() {
1815      global $wgCommandLineMode;
1816  
1817      if ( $wgCommandLineMode ) {
1818          $msg = '';
1819      } else {
1820          $msg = "<ul>\n";
1821      }
1822      $backtrace = wfDebugBacktrace();
1823      foreach ( $backtrace as $call ) {
1824          if ( isset( $call['file'] ) ) {
1825              $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
1826              $file = $f[count( $f ) - 1];
1827          } else {
1828              $file = '-';
1829          }
1830          if ( isset( $call['line'] ) ) {
1831              $line = $call['line'];
1832          } else {
1833              $line = '-';
1834          }
1835          if ( $wgCommandLineMode ) {
1836              $msg .= "$file line $line calls ";
1837          } else {
1838              $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
1839          }
1840          if ( !empty( $call['class'] ) ) {
1841              $msg .= $call['class'] . $call['type'];
1842          }
1843          $msg .= $call['function'] . '()';
1844  
1845          if ( $wgCommandLineMode ) {
1846              $msg .= "\n";
1847          } else {
1848              $msg .= "</li>\n";
1849          }
1850      }
1851      if ( $wgCommandLineMode ) {
1852          $msg .= "\n";
1853      } else {
1854          $msg .= "</ul>\n";
1855      }
1856  
1857      return $msg;
1858  }
1859  
1860  /**
1861   * Get the name of the function which called this function
1862   * wfGetCaller( 1 ) is the function with the wfGetCaller() call (ie. __FUNCTION__)
1863   * wfGetCaller( 2 ) [default] is the caller of the function running wfGetCaller()
1864   * wfGetCaller( 3 ) is the parent of that.
1865   *
1866   * @param $level Int
1867   * @return string
1868   */
1869  function wfGetCaller( $level = 2 ) {
1870      $backtrace = wfDebugBacktrace( $level + 1 );
1871      if ( isset( $backtrace[$level] ) ) {
1872          return wfFormatStackFrame( $backtrace[$level] );
1873      } else {
1874          return 'unknown';
1875      }
1876  }
1877  
1878  /**
1879   * Return a string consisting of callers in the stack. Useful sometimes
1880   * for profiling specific points.
1881   *
1882   * @param int $limit The maximum depth of the stack frame to return, or false for
1883   *               the entire stack.
1884   * @return String
1885   */
1886  function wfGetAllCallers( $limit = 3 ) {
1887      $trace = array_reverse( wfDebugBacktrace() );
1888      if ( !$limit || $limit > count( $trace ) - 1 ) {
1889          $limit = count( $trace ) - 1;
1890      }
1891      $trace = array_slice( $trace, -$limit - 1, $limit );
1892      return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
1893  }
1894  
1895  /**
1896   * Return a string representation of frame
1897   *
1898   * @param $frame Array
1899   * @return string
1900   */
1901  function wfFormatStackFrame( $frame ) {
1902      return isset( $frame['class'] ) ?
1903          $frame['class'] . '::' . $frame['function'] :
1904          $frame['function'];
1905  }
1906  
1907  /* Some generic result counters, pulled out of SearchEngine */
1908  
1909  /**
1910   * @todo document
1911   *
1912   * @param $offset Int
1913   * @param $limit Int
1914   * @return String
1915   */
1916  function wfShowingResults( $offset, $limit ) {
1917      return wfMessage( 'showingresults' )->numParams( $limit, $offset + 1 )->parse();
1918  }
1919  
1920  /**
1921   * Generate (prev x| next x) (20|50|100...) type links for paging
1922   *
1923   * @param $offset String
1924   * @param $limit Integer
1925   * @param $link String
1926   * @param string $query optional URL query parameter string
1927   * @param bool $atend optional param for specified if this is the last page
1928   * @return String
1929   * @deprecated in 1.19; use Language::viewPrevNext() instead
1930   */
1931  function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
1932      wfDeprecated( __METHOD__, '1.19' );
1933  
1934      global $wgLang;
1935  
1936      $query = wfCgiToArray( $query );
1937  
1938      if ( is_object( $link ) ) {
1939          $title = $link;
1940      } else {
1941          $title = Title::newFromText( $link );
1942          if ( is_null( $title ) ) {
1943              return false;
1944          }
1945      }
1946  
1947      return $wgLang->viewPrevNext( $title, $offset, $limit, $query, $atend );
1948  }
1949  
1950  /**
1951   * @todo document
1952   * @todo FIXME: We may want to blacklist some broken browsers
1953   *
1954   * @param $force Bool
1955   * @return bool Whereas client accept gzip compression
1956   */
1957  function wfClientAcceptsGzip( $force = false ) {
1958      static $result = null;
1959      if ( $result === null || $force ) {
1960          $result = false;
1961          if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
1962              # @todo FIXME: We may want to blacklist some broken browsers
1963              $m = array();
1964              if ( preg_match(
1965                  '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
1966                  $_SERVER['HTTP_ACCEPT_ENCODING'],
1967                  $m )
1968              )
1969              {
1970                  if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
1971                      $result = false;
1972                      return $result;
1973                  }
1974                  wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
1975                  $result = true;
1976              }
1977          }
1978      }
1979      return $result;
1980  }
1981  
1982  /**
1983   * Obtain the offset and limit values from the request string;
1984   * used in special pages
1985   *
1986   * @param int $deflimit default limit if none supplied
1987   * @param string $optionname Name of a user preference to check against
1988   * @return array
1989   *
1990   */
1991  function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
1992      global $wgRequest;
1993      return $wgRequest->getLimitOffset( $deflimit, $optionname );
1994  }
1995  
1996  /**
1997   * Escapes the given text so that it may be output using addWikiText()
1998   * without any linking, formatting, etc. making its way through. This
1999   * is achieved by substituting certain characters with HTML entities.
2000   * As required by the callers, "<nowiki>" is not used.
2001   *
2002   * @param string $text text to be escaped
2003   * @return String
2004   */
2005  function wfEscapeWikiText( $text ) {
2006      static $repl = null, $repl2 = null;
2007      if ( $repl === null ) {
2008          $repl = array(
2009              '"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
2010              '=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
2011              '{' => '&#123;', '|' => '&#124;', '}' => '&#125;', ';' => '&#59;',
2012              "\n#" => "\n&#35;", "\r#" => "\r&#35;",
2013              "\n*" => "\n&#42;", "\r*" => "\r&#42;",
2014              "\n:" => "\n&#58;", "\r:" => "\r&#58;",
2015              "\n " => "\n&#32;", "\r " => "\r&#32;",
2016              "\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
2017              "\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
2018              "\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
2019              "\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
2020              '__' => '_&#95;', '://' => '&#58;//',
2021          );
2022  
2023          // We have to catch everything "\s" matches in PCRE
2024          foreach ( array( 'ISBN', 'RFC', 'PMID' ) as $magic ) {
2025              $repl["$magic "] = "$magic&#32;";
2026              $repl["$magic\t"] = "$magic&#9;";
2027              $repl["$magic\r"] = "$magic&#13;";
2028              $repl["$magic\n"] = "$magic&#10;";
2029              $repl["$magic\f"] = "$magic&#12;";
2030          }
2031  
2032          // And handle protocols that don't use "://"
2033          global $wgUrlProtocols;
2034          $repl2 = array();
2035          foreach ( $wgUrlProtocols as $prot ) {
2036              if ( substr( $prot, -1 ) === ':' ) {
2037                  $repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
2038              }
2039          }
2040          $repl2 = $repl2 ? '/\b(' . join( '|', $repl2 ) . '):/i' : '/^(?!)/';
2041      }
2042      $text = substr( strtr( "\n$text", $repl ), 1 );
2043      $text = preg_replace( $repl2, '$1&#58;', $text );
2044      return $text;
2045  }
2046  
2047  /**
2048   * Get the current unix timestamp with microseconds.  Useful for profiling
2049   * @deprecated since 1.22; call microtime() directly
2050   * @return Float
2051   */
2052  function wfTime() {
2053      wfDeprecated( __FUNCTION__, '1.22' );
2054      return microtime( true );
2055  }
2056  
2057  /**
2058   * Sets dest to source and returns the original value of dest
2059   * If source is NULL, it just returns the value, it doesn't set the variable
2060   * If force is true, it will set the value even if source is NULL
2061   *
2062   * @param $dest Mixed
2063   * @param $source Mixed
2064   * @param $force Bool
2065   * @return Mixed
2066   */
2067  function wfSetVar( &$dest, $source, $force = false ) {
2068      $temp = $dest;
2069      if ( !is_null( $source ) || $force ) {
2070          $dest = $source;
2071      }
2072      return $temp;
2073  }
2074  
2075  /**
2076   * As for wfSetVar except setting a bit
2077   *
2078   * @param $dest Int
2079   * @param $bit Int
2080   * @param $state Bool
2081   *
2082   * @return bool
2083   */
2084  function wfSetBit( &$dest, $bit, $state = true ) {
2085      $temp = (bool)( $dest & $bit );
2086      if ( !is_null( $state ) ) {
2087          if ( $state ) {
2088              $dest |= $bit;
2089          } else {
2090              $dest &= ~$bit;
2091          }
2092      }
2093      return $temp;
2094  }
2095  
2096  /**
2097   * A wrapper around the PHP function var_export().
2098   * Either print it or add it to the regular output ($wgOut).
2099   *
2100   * @param $var mixed A PHP variable to dump.
2101   */
2102  function wfVarDump( $var ) {
2103      global $wgOut;
2104      $s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
2105      if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
2106          print $s;
2107      } else {
2108          $wgOut->addHTML( $s );
2109      }
2110  }
2111  
2112  /**
2113   * Provide a simple HTTP error.
2114   *
2115   * @param $code Int|String
2116   * @param $label String
2117   * @param $desc String
2118   */
2119  function wfHttpError( $code, $label, $desc ) {
2120      global $wgOut;
2121      $wgOut->disable();
2122      header( "HTTP/1.0 $code $label" );
2123      header( "Status: $code $label" );
2124      $wgOut->sendCacheControl();
2125  
2126      header( 'Content-type: text/html; charset=utf-8' );
2127      print "<!doctype html>" .
2128          '<html><head><title>' .
2129          htmlspecialchars( $label ) .
2130          '</title></head><body><h1>' .
2131          htmlspecialchars( $label ) .
2132          '</h1><p>' .
2133          nl2br( htmlspecialchars( $desc ) ) .
2134          "</p></body></html>\n";
2135  }
2136  
2137  /**
2138   * Clear away any user-level output buffers, discarding contents.
2139   *
2140   * Suitable for 'starting afresh', for instance when streaming
2141   * relatively large amounts of data without buffering, or wanting to
2142   * output image files without ob_gzhandler's compression.
2143   *
2144   * The optional $resetGzipEncoding parameter controls suppression of
2145   * the Content-Encoding header sent by ob_gzhandler; by default it
2146   * is left. See comments for wfClearOutputBuffers() for why it would
2147   * be used.
2148   *
2149   * Note that some PHP configuration options may add output buffer
2150   * layers which cannot be removed; these are left in place.
2151   *
2152   * @param $resetGzipEncoding Bool
2153   */
2154  function wfResetOutputBuffers( $resetGzipEncoding = true ) {
2155      if ( $resetGzipEncoding ) {
2156          // Suppress Content-Encoding and Content-Length
2157          // headers from 1.10+s wfOutputHandler
2158          global $wgDisableOutputCompression;
2159          $wgDisableOutputCompression = true;
2160      }
2161      while ( $status = ob_get_status() ) {
2162          if ( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
2163              // Probably from zlib.output_compression or other
2164              // PHP-internal setting which can't be removed.
2165              //
2166              // Give up, and hope the result doesn't break
2167              // output behavior.
2168              break;
2169          }
2170          if ( !ob_end_clean() ) {
2171              // Could not remove output buffer handler; abort now
2172              // to avoid getting in some kind of infinite loop.
2173              break;
2174          }
2175          if ( $resetGzipEncoding ) {
2176              if ( $status['name'] == 'ob_gzhandler' ) {
2177                  // Reset the 'Content-Encoding' field set by this handler
2178                  // so we can start fresh.
2179                  header_remove( 'Content-Encoding' );
2180                  break;
2181              }
2182          }
2183      }
2184  }
2185  
2186  /**
2187   * More legible than passing a 'false' parameter to wfResetOutputBuffers():
2188   *
2189   * Clear away output buffers, but keep the Content-Encoding header
2190   * produced by ob_gzhandler, if any.
2191   *
2192   * This should be used for HTTP 304 responses, where you need to
2193   * preserve the Content-Encoding header of the real result, but
2194   * also need to suppress the output of ob_gzhandler to keep to spec
2195   * and avoid breaking Firefox in rare cases where the headers and
2196   * body are broken over two packets.
2197   */
2198  function wfClearOutputBuffers() {
2199      wfResetOutputBuffers( false );
2200  }
2201  
2202  /**
2203   * Converts an Accept-* header into an array mapping string values to quality
2204   * factors
2205   *
2206   * @param $accept String
2207   * @param string $def default
2208   * @return Array
2209   */
2210  function wfAcceptToPrefs( $accept, $def = '*/*' ) {
2211      # No arg means accept anything (per HTTP spec)
2212      if ( !$accept ) {
2213          return array( $def => 1.0 );
2214      }
2215  
2216      $prefs = array();
2217  
2218      $parts = explode( ',', $accept );
2219  
2220      foreach ( $parts as $part ) {
2221          # @todo FIXME: Doesn't deal with params like 'text/html; level=1'
2222          $values = explode( ';', trim( $part ) );
2223          $match = array();
2224          if ( count( $values ) == 1 ) {
2225              $prefs[$values[0]] = 1.0;
2226          } elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
2227              $prefs[$values[0]] = floatval( $match[1] );
2228          }
2229      }
2230  
2231      return $prefs;
2232  }
2233  
2234  /**
2235   * Checks if a given MIME type matches any of the keys in the given
2236   * array. Basic wildcards are accepted in the array keys.
2237   *
2238   * Returns the matching MIME type (or wildcard) if a match, otherwise
2239   * NULL if no match.
2240   *
2241   * @param $type String
2242   * @param $avail Array
2243   * @return string
2244   * @private
2245   */
2246  function mimeTypeMatch( $type, $avail ) {
2247      if ( array_key_exists( $type, $avail ) ) {
2248          return $type;
2249      } else {
2250          $parts = explode( '/', $type );
2251          if ( array_key_exists( $parts[0] . '/*', $avail ) ) {
2252              return $parts[0] . '/*';
2253          } elseif ( array_key_exists( '*/*', $avail ) ) {
2254              return '*/*';
2255          } else {
2256              return null;
2257          }
2258      }
2259  }
2260  
2261  /**
2262   * Returns the 'best' match between a client's requested internet media types
2263   * and the server's list of available types. Each list should be an associative
2264   * array of type to preference (preference is a float between 0.0 and 1.0).
2265   * Wildcards in the types are acceptable.
2266   *
2267   * @param array $cprefs client's acceptable type list
2268   * @param array $sprefs server's offered types
2269   * @return string
2270   *
2271   * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
2272   * XXX: generalize to negotiate other stuff
2273   */
2274  function wfNegotiateType( $cprefs, $sprefs ) {
2275      $combine = array();
2276  
2277      foreach ( array_keys( $sprefs ) as $type ) {
2278          $parts = explode( '/', $type );
2279          if ( $parts[1] != '*' ) {
2280              $ckey = mimeTypeMatch( $type, $cprefs );
2281              if ( $ckey ) {
2282                  $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
2283              }
2284          }
2285      }
2286  
2287      foreach ( array_keys( $cprefs ) as $type ) {
2288          $parts = explode( '/', $type );
2289          if ( $parts[1] != '*' && !array_key_exists( $type, $sprefs ) ) {
2290              $skey = mimeTypeMatch( $type, $sprefs );
2291              if ( $skey ) {
2292                  $combine[$type] = $sprefs[$skey] * $cprefs[$type];
2293              }
2294          }
2295      }
2296  
2297      $bestq = 0;
2298      $besttype = null;
2299  
2300      foreach ( array_keys( $combine ) as $type ) {
2301          if ( $combine[$type] > $bestq ) {
2302              $besttype = $type;
2303              $bestq = $combine[$type];
2304          }
2305      }
2306  
2307      return $besttype;
2308  }
2309  
2310  /**
2311   * Reference-counted warning suppression
2312   *
2313   * @param $end Bool
2314   */
2315  function wfSuppressWarnings( $end = false ) {
2316      static $suppressCount = 0;
2317      static $originalLevel = false;
2318  
2319      if ( $end ) {
2320          if ( $suppressCount ) {
2321              --$suppressCount;
2322              if ( !$suppressCount ) {
2323                  error_reporting( $originalLevel );
2324              }
2325          }
2326      } else {
2327          if ( !$suppressCount ) {
2328              $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT ) );
2329          }
2330          ++$suppressCount;
2331      }
2332  }
2333  
2334  /**
2335   * Restore error level to previous value
2336   */
2337  function wfRestoreWarnings() {
2338      wfSuppressWarnings( true );
2339  }
2340  
2341  # Autodetect, convert and provide timestamps of various types
2342  
2343  /**
2344   * Unix time - the number of seconds since 1970-01-01 00:00:00 UTC
2345   */
2346  define( 'TS_UNIX', 0 );
2347  
2348  /**
2349   * MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
2350   */
2351  define( 'TS_MW', 1 );
2352  
2353  /**
2354   * MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
2355   */
2356  define( 'TS_DB', 2 );
2357  
2358  /**
2359   * RFC 2822 format, for E-mail and HTTP headers
2360   */
2361  define( 'TS_RFC2822', 3 );
2362  
2363  /**
2364   * ISO 8601 format with no timezone: 1986-02-09T20:00:00Z
2365   *
2366   * This is used by Special:Export
2367   */
2368  define( 'TS_ISO_8601', 4 );
2369  
2370  /**
2371   * An Exif timestamp (YYYY:MM:DD HH:MM:SS)
2372   *
2373   * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the
2374   *       DateTime tag and page 36 for the DateTimeOriginal and
2375   *       DateTimeDigitized tags.
2376   */
2377  define( 'TS_EXIF', 5 );
2378  
2379  /**
2380   * Oracle format time.
2381   */
2382  define( 'TS_ORACLE', 6 );
2383  
2384  /**
2385   * Postgres format time.
2386   */
2387  define( 'TS_POSTGRES', 7 );
2388  
2389  /**
2390   * ISO 8601 basic format with no timezone: 19860209T200000Z.  This is used by ResourceLoader
2391   */
2392  define( 'TS_ISO_8601_BASIC', 9 );
2393  
2394  /**
2395   * Get a timestamp string in one of various formats
2396   *
2397   * @param $outputtype Mixed: A timestamp in one of the supported formats, the
2398   *                    function will autodetect which format is supplied and act
2399   *                    accordingly.
2400   * @param $ts Mixed: optional timestamp to convert, default 0 for the current time
2401   * @return Mixed: String / false The same date in the format specified in $outputtype or false
2402   */
2403  function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
2404      try {
2405          $timestamp = new MWTimestamp( $ts );
2406          return $timestamp->getTimestamp( $outputtype );
2407      } catch ( TimestampException $e ) {
2408          wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
2409          return false;
2410      }
2411  }
2412  
2413  /**
2414   * Return a formatted timestamp, or null if input is null.
2415   * For dealing with nullable timestamp columns in the database.
2416   *
2417   * @param $outputtype Integer
2418   * @param $ts String
2419   * @return String
2420   */
2421  function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
2422      if ( is_null( $ts ) ) {
2423          return null;
2424      } else {
2425          return wfTimestamp( $outputtype, $ts );
2426      }
2427  }
2428  
2429  /**
2430   * Convenience function; returns MediaWiki timestamp for the present time.
2431   *
2432   * @return string
2433   */
2434  function wfTimestampNow() {
2435      # return NOW
2436      return wfTimestamp( TS_MW, time() );
2437  }
2438  
2439  /**
2440   * Check if the operating system is Windows
2441   *
2442   * @return Bool: true if it's Windows, False otherwise.
2443   */
2444  function wfIsWindows() {
2445      static $isWindows = null;
2446      if ( $isWindows === null ) {
2447          $isWindows = substr( php_uname(), 0, 7 ) == 'Windows';
2448      }
2449      return $isWindows;
2450  }
2451  
2452  /**
2453   * Check if we are running under HipHop
2454   *
2455   * @return Bool
2456   */
2457  function wfIsHipHop() {
2458      return defined( 'HPHP_VERSION' );
2459  }
2460  
2461  /**
2462   * Swap two variables
2463   *
2464   * @param $x Mixed
2465   * @param $y Mixed
2466   */
2467  function swap( &$x, &$y ) {
2468      $z = $x;
2469      $x = $y;
2470      $y = $z;
2471  }
2472  
2473  /**
2474   * Tries to get the system directory for temporary files. First
2475   * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
2476   * environment variables are then checked in sequence, and if none are
2477   * set try sys_get_temp_dir().
2478   *
2479   * NOTE: When possible, use instead the tmpfile() function to create
2480   * temporary files to avoid race conditions on file creation, etc.
2481   *
2482   * @return String
2483   */
2484  function wfTempDir() {
2485      global $wgTmpDirectory;
2486  
2487      if ( $wgTmpDirectory !== false ) {
2488          return $wgTmpDirectory;
2489      }
2490  
2491      $tmpDir = array_map( "getenv", array( 'TMPDIR', 'TMP', 'TEMP' ) );
2492  
2493      foreach ( $tmpDir as $tmp ) {
2494          if ( $tmp && file_exists( $tmp ) && is_dir( $tmp ) && is_writable( $tmp ) ) {
2495              return $tmp;
2496          }
2497      }
2498      return sys_get_temp_dir();
2499  }
2500  
2501  /**
2502   * Make directory, and make all parent directories if they don't exist
2503   *
2504   * @param string $dir full path to directory to create
2505   * @param $mode Integer: chmod value to use, default is $wgDirectoryMode
2506   * @param string $caller optional caller param for debugging.
2507   * @throws MWException
2508   * @return bool
2509   */
2510  function wfMkdirParents( $dir, $mode = null, $caller = null ) {
2511      global $wgDirectoryMode;
2512  
2513      if ( FileBackend::isStoragePath( $dir ) ) { // sanity
2514          throw new MWException( __FUNCTION__ . " given storage path '$dir'." );
2515      }
2516  
2517      if ( !is_null( $caller ) ) {
2518          wfDebug( "$caller: called wfMkdirParents($dir)\n" );
2519      }
2520  
2521      if ( strval( $dir ) === '' || ( file_exists( $dir ) && is_dir( $dir ) ) ) {
2522          return true;
2523      }
2524  
2525      $dir = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $dir );
2526  
2527      if ( is_null( $mode ) ) {
2528          $mode = $wgDirectoryMode;
2529      }
2530  
2531      // Turn off the normal warning, we're doing our own below
2532      wfSuppressWarnings();
2533      $ok = mkdir( $dir, $mode, true ); // PHP5 <3
2534      wfRestoreWarnings();
2535  
2536      if ( !$ok ) {
2537          //directory may have been created on another request since we last checked
2538          if ( is_dir( $dir ) ) {
2539              return true;
2540          }
2541  
2542          // PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
2543          wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) );
2544      }
2545      return $ok;
2546  }
2547  
2548  /**
2549   * Remove a directory and all its content.
2550   * Does not hide error.
2551   */
2552  function wfRecursiveRemoveDir( $dir ) {
2553      wfDebug( __FUNCTION__ . "( $dir )\n" );
2554      // taken from http://de3.php.net/manual/en/function.rmdir.php#98622
2555      if ( is_dir( $dir ) ) {
2556          $objects = scandir( $dir );
2557          foreach ( $objects as $object ) {
2558              if ( $object != "." && $object != ".." ) {
2559                  if ( filetype( $dir . '/' . $object ) == "dir" ) {
2560                      wfRecursiveRemoveDir( $dir . '/' . $object );
2561                  } else {
2562                      unlink( $dir . '/' . $object );
2563                  }
2564              }
2565          }
2566          reset( $objects );
2567          rmdir( $dir );
2568      }
2569  }
2570  
2571  /**
2572   * @param $nr Mixed: the number to format
2573   * @param $acc Integer: the number of digits after the decimal point, default 2
2574   * @param $round Boolean: whether or not to round the value, default true
2575   * @return float
2576   */
2577  function wfPercent( $nr, $acc = 2, $round = true ) {
2578      $ret = sprintf( "%.$acc}f", $nr );
2579      return $round ? round( $ret, $acc ) . '%' : "$ret%";
2580  }
2581  
2582  /**
2583   * Find out whether or not a mixed variable exists in a string
2584   *
2585   * @deprecated Just use str(i)pos
2586   * @param $needle String
2587   * @param $str String
2588   * @param $insensitive Boolean
2589   * @return Boolean
2590   */
2591  function in_string( $needle, $str, $insensitive = false ) {
2592      wfDeprecated( __METHOD__, '1.21' );
2593      $func = 'strpos';
2594      if ( $insensitive ) {
2595          $func = 'stripos';
2596      }
2597  
2598      return $func( $str, $needle ) !== false;
2599  }
2600  
2601  /**
2602   * Safety wrapper around ini_get() for boolean settings.
2603   * The values returned from ini_get() are pre-normalized for settings
2604   * set via php.ini or php_flag/php_admin_flag... but *not*
2605   * for those set via php_value/php_admin_value.
2606   *
2607   * It's fairly common for people to use php_value instead of php_flag,
2608   * which can leave you with an 'off' setting giving a false positive
2609   * for code that just takes the ini_get() return value as a boolean.
2610   *
2611   * To make things extra interesting, setting via php_value accepts
2612   * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
2613   * Unrecognized values go false... again opposite PHP's own coercion
2614   * from string to bool.
2615   *
2616   * Luckily, 'properly' set settings will always come back as '0' or '1',
2617   * so we only have to worry about them and the 'improper' settings.
2618   *
2619   * I frickin' hate PHP... :P
2620   *
2621   * @param $setting String
2622   * @return Bool
2623   */
2624  function wfIniGetBool( $setting ) {
2625      $val = strtolower( ini_get( $setting ) );
2626      // 'on' and 'true' can't have whitespace around them, but '1' can.
2627      return $val == 'on'
2628          || $val == 'true'
2629          || $val == 'yes'
2630          || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
2631  }
2632  
2633  /**
2634   * Windows-compatible version of escapeshellarg()
2635   * Windows doesn't recognise single-quotes in the shell, but the escapeshellarg()
2636   * function puts single quotes in regardless of OS.
2637   *
2638   * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
2639   * earlier distro releases of PHP)
2640   *
2641   * @param varargs
2642   * @return String
2643   */
2644  function wfEscapeShellArg() {
2645      wfInitShellLocale();
2646  
2647      $args = func_get_args();
2648      $first = true;
2649      $retVal = '';
2650      foreach ( $args as $arg ) {
2651          if ( !$first ) {
2652              $retVal .= ' ';
2653          } else {
2654              $first = false;
2655          }
2656  
2657          if ( wfIsWindows() ) {
2658              // Escaping for an MSVC-style command line parser and CMD.EXE
2659              // Refs:
2660              //  * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
2661              //  * http://technet.microsoft.com/en-us/library/cc723564.aspx
2662              //  * Bug #13518
2663              //  * CR r63214
2664              // Double the backslashes before any double quotes. Escape the double quotes.
2665              $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
2666              $arg = '';
2667              $iteration = 0;
2668              foreach ( $tokens as $token ) {
2669                  if ( $iteration % 2 == 1 ) {
2670                      // Delimiter, a double quote preceded by zero or more slashes
2671                      $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
2672                  } elseif ( $iteration % 4 == 2 ) {
2673                      // ^ in $token will be outside quotes, need to be escaped
2674                      $arg .= str_replace( '^', '^^', $token );
2675                  } else { // $iteration % 4 == 0
2676                      // ^ in $token will appear inside double quotes, so leave as is
2677                      $arg .= $token;
2678                  }
2679                  $iteration++;
2680              }
2681              // Double the backslashes before the end of the string, because
2682              // we will soon add a quote
2683              $m = array();
2684              if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
2685                  $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
2686              }
2687  
2688              // Add surrounding quotes
2689              $retVal .= '"' . $arg . '"';
2690          } else {
2691              $retVal .= escapeshellarg( $arg );
2692          }
2693      }
2694      return $retVal;
2695  }
2696  
2697  /**
2698   * Check if wfShellExec() is effectively disabled via php.ini config
2699   * @return bool|string False or one of (safemode,disabled)
2700   * @since 1.22
2701   */
2702  function wfShellExecDisabled() {
2703      static $disabled = null;
2704      if ( is_null( $disabled ) ) {
2705          $disabled = false;
2706          if ( wfIniGetBool( 'safe_mode' ) ) {
2707              wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
2708              $disabled = 'safemode';
2709          } else {
2710              $functions = explode( ',', ini_get( 'disable_functions' ) );
2711              $functions = array_map( 'trim', $functions );
2712              $functions = array_map( 'strtolower', $functions );
2713              if ( in_array( 'passthru', $functions ) ) {
2714                  wfDebug( "passthru is in disabled_functions\n" );
2715                  $disabled = 'passthru';
2716              }
2717          }
2718      }
2719      return $disabled;
2720  }
2721  
2722  /**
2723   * Execute a shell command, with time and memory limits mirrored from the PHP
2724   * configuration if supported.
2725   * @param string $cmd Command line, properly escaped for shell.
2726   * @param &$retval null|Mixed optional, will receive the program's exit code.
2727   *                 (non-zero is usually failure)
2728   * @param array $environ optional environment variables which should be
2729   *                 added to the executed command environment.
2730   * @param array $limits optional array with limits(filesize, memory, time, walltime)
2731   *                 this overwrites the global wgShellMax* limits.
2732   * @param array $options Array of options. Only one is "duplicateStderr" => true, which
2733   *                 Which duplicates stderr to stdout, including errors from limit.sh
2734   * @return string collected stdout as a string
2735   */
2736  function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) {
2737      global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
2738          $wgMaxShellWallClockTime, $wgShellCgroup;
2739  
2740      $disabled = wfShellExecDisabled();
2741      if ( $disabled ) {
2742          $retval = 1;
2743          return $disabled == 'safemode' ?
2744              'Unable to run external programs in safe mode.' :
2745              'Unable to run external programs, passthru() is disabled.';
2746      }
2747  
2748      $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
2749  
2750      wfInitShellLocale();
2751  
2752      $envcmd = '';
2753      foreach ( $environ as $k => $v ) {
2754          if ( wfIsWindows() ) {
2755              /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves
2756               * appear in the environment variable, so we must use carat escaping as documented in
2757               * http://technet.microsoft.com/en-us/library/cc723564.aspx
2758               * Note however that the quote isn't listed there, but is needed, and the parentheses
2759               * are listed there but doesn't appear to need it.
2760               */
2761              $envcmd .= "set $k=" . preg_replace( '/([&|()<>^"])/', '^\\1', $v ) . '&& ';
2762          } else {
2763              /* Assume this is a POSIX shell, thus required to accept variable assignments before the command
2764               * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_01
2765               */
2766              $envcmd .= "$k=" . escapeshellarg( $v ) . ' ';
2767          }
2768      }
2769      $cmd = $envcmd . $cmd;
2770  
2771      if ( php_uname( 's' ) == 'Linux' ) {
2772          $time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
2773          if ( isset( $limits['walltime'] ) ) {
2774              $wallTime = intval( $limits['walltime'] );
2775          } elseif ( isset( $limits['time'] ) ) {
2776              $wallTime = $time;
2777          } else {
2778              $wallTime = intval( $wgMaxShellWallClockTime );
2779          }
2780          $mem = intval ( isset( $limits['memory'] ) ? $limits['memory'] : $wgMaxShellMemory );
2781          $filesize = intval ( isset( $limits['filesize'] ) ? $limits['filesize'] : $wgMaxShellFileSize );
2782  
2783          if ( $time > 0 || $mem > 0 || $filesize > 0 || $wallTime > 0 ) {
2784              $cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
2785                  escapeshellarg( $cmd ) . ' ' .
2786                  escapeshellarg(
2787                      "MW_INCLUDE_STDERR=" . ( $includeStderr ? '1' : '' ) . ';' .
2788                      "MW_CPU_LIMIT=$time; " .
2789                      'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
2790                      "MW_MEM_LIMIT=$mem; " .
2791                      "MW_FILE_SIZE_LIMIT=$filesize; " .
2792                      "MW_WALL_CLOCK_LIMIT=$wallTime"
2793                  );
2794          } elseif ( $includeStderr ) {
2795              $cmd .= ' 2>&1';
2796          }
2797      } elseif ( $includeStderr ) {
2798          $cmd .= ' 2>&1';
2799      }
2800      wfDebug( "wfShellExec: $cmd\n" );
2801  
2802      // Default to an unusual value that shouldn't happen naturally,
2803      // so in the unlikely event of a weird php bug, it would be
2804      // more obvious what happened.
2805      $retval = 200;
2806      ob_start();
2807      passthru( $cmd, $retval );
2808      $output = ob_get_contents();
2809      ob_end_clean();
2810  
2811      if ( $retval == 127 ) {
2812          wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
2813      }
2814      return $output;
2815  }
2816  
2817  /**
2818   * Execute a shell command, returning both stdout and stderr. Convenience
2819   * function, as all the arguments to wfShellExec can become unwieldy.
2820   *
2821   * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
2822   * @param string $cmd Command line, properly escaped for shell.
2823   * @param &$retval null|Mixed optional, will receive the program's exit code.
2824   *                 (non-zero is usually failure)
2825   * @param array $environ optional environment variables which should be
2826   *                 added to the executed command environment.
2827   * @param array $limits optional array with limits(filesize, memory, time, walltime)
2828   *                 this overwrites the global wgShellMax* limits.
2829   * @return string collected stdout and stderr as a string
2830   */
2831  function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
2832      return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) );
2833  }
2834  
2835  /**
2836   * Workaround for http://bugs.php.net/bug.php?id=45132
2837   * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
2838   */
2839  function wfInitShellLocale() {
2840      static $done = false;
2841      if ( $done ) {
2842          return;
2843      }
2844      $done = true;
2845      global $wgShellLocale;
2846      if ( !wfIniGetBool( 'safe_mode' ) ) {
2847          putenv( "LC_CTYPE=$wgShellLocale" );
2848          setlocale( LC_CTYPE, $wgShellLocale );
2849      }
2850  }
2851  
2852  /**
2853   * Alias to wfShellWikiCmd()
2854   * @see wfShellWikiCmd()
2855   */
2856  function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) {
2857      return wfShellWikiCmd( $script, $parameters, $options );
2858  }
2859  
2860  /**
2861   * Generate a shell-escaped command line string to run a MediaWiki cli script.
2862   * Note that $parameters should be a flat array and an option with an argument
2863   * should consist of two consecutive items in the array (do not use "--option value").
2864   * @param string $script MediaWiki cli script path
2865   * @param array $parameters Arguments and options to the script
2866   * @param array $options Associative array of options:
2867   *         'php': The path to the php executable
2868   *         'wrapper': Path to a PHP wrapper to handle the maintenance script
2869   * @return Array
2870   */
2871  function wfShellWikiCmd( $script, array $parameters = array(), array $options = array() ) {
2872      global $wgPhpCli;
2873      // Give site config file a chance to run the script in a wrapper.
2874      // The caller may likely want to call wfBasename() on $script.
2875      wfRunHooks( 'wfShellWikiCmd', array( &$script, &$parameters, &$options ) );
2876      $cmd = isset( $options['php'] ) ? array( $options['php'] ) : array( $wgPhpCli );
2877      if ( isset( $options['wrapper'] ) ) {
2878          $cmd[] = $options['wrapper'];
2879      }
2880      $cmd[] = $script;
2881      // Escape each parameter for shell
2882      return implode( " ", array_map( 'wfEscapeShellArg', array_merge( $cmd, $parameters ) ) );
2883  }
2884  
2885  /**
2886   * wfMerge attempts to merge differences between three texts.
2887   * Returns true for a clean merge and false for failure or a conflict.
2888   *
2889   * @param $old String
2890   * @param $mine String
2891   * @param $yours String
2892   * @param $result String
2893   * @return Bool
2894   */
2895  function wfMerge( $old, $mine, $yours, &$result ) {
2896      global $wgDiff3;
2897  
2898      # This check may also protect against code injection in
2899      # case of broken installations.
2900      wfSuppressWarnings();
2901      $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
2902      wfRestoreWarnings();
2903  
2904      if ( !$haveDiff3 ) {
2905          wfDebug( "diff3 not found\n" );
2906          return false;
2907      }
2908  
2909      # Make temporary files
2910      $td = wfTempDir();
2911      $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2912      $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
2913      $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
2914  
2915      # NOTE: diff3 issues a warning to stderr if any of the files does not end with
2916      #       a newline character. To avoid this, we normalize the trailing whitespace before
2917      #       creating the diff.
2918  
2919      fwrite( $oldtextFile, rtrim( $old ) . "\n" );
2920      fclose( $oldtextFile );
2921      fwrite( $mytextFile, rtrim( $mine ) . "\n" );
2922      fclose( $mytextFile );
2923      fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
2924      fclose( $yourtextFile );
2925  
2926      # Check for a conflict
2927      $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a --overlap-only ' .
2928          wfEscapeShellArg( $mytextName ) . ' ' .
2929          wfEscapeShellArg( $oldtextName ) . ' ' .
2930          wfEscapeShellArg( $yourtextName );
2931      $handle = popen( $cmd, 'r' );
2932  
2933      if ( fgets( $handle, 1024 ) ) {
2934          $conflict = true;
2935      } else {
2936          $conflict = false;
2937      }
2938      pclose( $handle );
2939  
2940      # Merge differences
2941      $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a -e --merge ' .
2942          wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
2943      $handle = popen( $cmd, 'r' );
2944      $result = '';
2945      do {
2946          $data = fread( $handle, 8192 );
2947          if ( strlen( $data ) == 0 ) {
2948              break;
2949          }
2950          $result .= $data;
2951      } while ( true );
2952      pclose( $handle );
2953      unlink( $mytextName );
2954      unlink( $oldtextName );
2955      unlink( $yourtextName );
2956  
2957      if ( $result === '' && $old !== '' && !$conflict ) {
2958          wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
2959          $conflict = true;
2960      }
2961      return !$conflict;
2962  }
2963  
2964  /**
2965   * Returns unified plain-text diff of two texts.
2966   * Useful for machine processing of diffs.
2967   *
2968   * @param string $before the text before the changes.
2969   * @param string $after the text after the changes.
2970   * @param string $params command-line options for the diff command.
2971   * @return String: unified diff of $before and $after
2972   */
2973  function wfDiff( $before, $after, $params = '-u' ) {
2974      if ( $before == $after ) {
2975          return '';
2976      }
2977  
2978      global $wgDiff;
2979      wfSuppressWarnings();
2980      $haveDiff = $wgDiff && file_exists( $wgDiff );
2981      wfRestoreWarnings();
2982  
2983      # This check may also protect against code injection in
2984      # case of broken installations.
2985      if ( !$haveDiff ) {
2986          wfDebug( "diff executable not found\n" );
2987          $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
2988          $format = new UnifiedDiffFormatter();
2989          return $format->format( $diffs );
2990      }
2991  
2992      # Make temporary files
2993      $td = wfTempDir();
2994      $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
2995      $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
2996  
2997      fwrite( $oldtextFile, $before );
2998      fclose( $oldtextFile );
2999      fwrite( $newtextFile, $after );
3000      fclose( $newtextFile );
3001  
3002      // Get the diff of the two files
3003      $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
3004  
3005      $h = popen( $cmd, 'r' );
3006  
3007      $diff = '';
3008  
3009      do {
3010          $data = fread( $h, 8192 );
3011          if ( strlen( $data ) == 0 ) {
3012              break;
3013          }
3014          $diff .= $data;
3015      } while ( true );
3016  
3017      // Clean up
3018      pclose( $h );
3019      unlink( $oldtextName );
3020      unlink( $newtextName );
3021  
3022      // Kill the --- and +++ lines. They're not useful.
3023      $diff_lines = explode( "\n", $diff );
3024      if ( strpos( $diff_lines[0], '---' ) === 0 ) {
3025          unset( $diff_lines[0] );
3026      }
3027      if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
3028          unset( $diff_lines[1] );
3029      }
3030  
3031      $diff = implode( "\n", $diff_lines );
3032  
3033      return $diff;
3034  }
3035  
3036  /**
3037   * This function works like "use VERSION" in Perl, the program will die with a
3038   * backtrace if the current version of PHP is less than the version provided
3039   *
3040   * This is useful for extensions which due to their nature are not kept in sync
3041   * with releases, and might depend on other versions of PHP than the main code
3042   *
3043   * Note: PHP might die due to parsing errors in some cases before it ever
3044   *       manages to call this function, such is life
3045   *
3046   * @see perldoc -f use
3047   *
3048   * @param $req_ver Mixed: the version to check, can be a string, an integer, or
3049   *                 a float
3050   * @throws MWException
3051   */
3052  function wfUsePHP( $req_ver ) {
3053      $php_ver = PHP_VERSION;
3054  
3055      if ( version_compare( $php_ver, (string)$req_ver, '<' ) ) {
3056          throw new MWException( "PHP $req_ver required--this is only $php_ver" );
3057      }
3058  }
3059  
3060  /**
3061   * This function works like "use VERSION" in Perl except it checks the version
3062   * of MediaWiki, the program will die with a backtrace if the current version
3063   * of MediaWiki is less than the version provided.
3064   *
3065   * This is useful for extensions which due to their nature are not kept in sync
3066   * with releases
3067   *
3068   * @see perldoc -f use
3069   *
3070   * @param $req_ver Mixed: the version to check, can be a string, an integer, or
3071   *                 a float
3072   * @throws MWException
3073   */
3074  function wfUseMW( $req_ver ) {
3075      global $wgVersion;
3076  
3077      if ( version_compare( $wgVersion, (string)$req_ver, '<' ) ) {
3078          throw new MWException( "MediaWiki $req_ver required--this is only $wgVersion" );
3079      }
3080  }
3081  
3082  /**
3083   * Return the final portion of a pathname.
3084   * Reimplemented because PHP5's "basename()" is buggy with multibyte text.
3085   * http://bugs.php.net/bug.php?id=33898
3086   *
3087   * PHP's basename() only considers '\' a pathchar on Windows and Netware.
3088   * We'll consider it so always, as we don't want '\s' in our Unix paths either.
3089   *
3090   * @param $path String
3091   * @param string $suffix to remove if present
3092   * @return String
3093   */
3094  function wfBaseName( $path, $suffix = '' ) {
3095      $encSuffix = ( $suffix == '' )
3096          ? ''
3097          : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
3098      $matches = array();
3099      if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
3100          return $matches[1];
3101      } else {
3102          return '';
3103      }
3104  }
3105  
3106  /**
3107   * Generate a relative path name to the given file.
3108   * May explode on non-matching case-insensitive paths,
3109   * funky symlinks, etc.
3110   *
3111   * @param string $path absolute destination path including target filename
3112   * @param string $from Absolute source path, directory only
3113   * @return String
3114   */
3115  function wfRelativePath( $path, $from ) {
3116      // Normalize mixed input on Windows...
3117      $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
3118      $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
3119  
3120      // Trim trailing slashes -- fix for drive root
3121      $path = rtrim( $path, DIRECTORY_SEPARATOR );
3122      $from = rtrim( $from, DIRECTORY_SEPARATOR );
3123  
3124      $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
3125      $against = explode( DIRECTORY_SEPARATOR, $from );
3126  
3127      if ( $pieces[0] !== $against[0] ) {
3128          // Non-matching Windows drive letters?
3129          // Return a full path.
3130          return $path;
3131      }
3132  
3133      // Trim off common prefix
3134      while ( count( $pieces ) && count( $against )
3135          && $pieces[0] == $against[0] ) {
3136          array_shift( $pieces );
3137          array_shift( $against );
3138      }
3139  
3140      // relative dots to bump us to the parent
3141      while ( count( $against ) ) {
3142          array_unshift( $pieces, '..' );
3143          array_shift( $against );
3144      }
3145  
3146      array_push( $pieces, wfBaseName( $path ) );
3147  
3148      return implode( DIRECTORY_SEPARATOR, $pieces );
3149  }
3150  
3151  /**
3152   * Do any deferred updates and clear the list
3153   *
3154   * @deprecated since 1.19
3155   * @see DeferredUpdates::doUpdate()
3156   * @param $commit string
3157   */
3158  function wfDoUpdates( $commit = '' ) {
3159      wfDeprecated( __METHOD__, '1.19' );
3160      DeferredUpdates::doUpdates( $commit );
3161  }
3162  
3163  /**
3164   * Convert an arbitrarily-long digit string from one numeric base
3165   * to another, optionally zero-padding to a minimum column width.
3166   *
3167   * Supports base 2 through 36; digit values 10-36 are represented
3168   * as lowercase letters a-z. Input is case-insensitive.
3169   *
3170   * @param string $input Input number
3171   * @param int $sourceBase Base of the input number
3172   * @param int $destBase Desired base of the output
3173   * @param int $pad Minimum number of digits in the output (pad with zeroes)
3174   * @param bool $lowercase Whether to output in lowercase or uppercase
3175   * @param string $engine Either "gmp", "bcmath", or "php"
3176   * @return string|bool The output number as a string, or false on error
3177   */
3178  function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true, $engine = 'auto' ) {
3179      $input = (string)$input;
3180      if (
3181          $sourceBase < 2 ||
3182          $sourceBase > 36 ||
3183          $destBase < 2 ||
3184          $destBase > 36 ||
3185          $sourceBase != (int)$sourceBase ||
3186          $destBase != (int)$destBase ||
3187          $pad != (int)$pad ||
3188          !preg_match( "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i", $input )
3189      ) {
3190          return false;
3191      }
3192  
3193      static $baseChars = array(
3194          10 => 'a', 11 => 'b', 12 => 'c', 13 => 'd', 14 => 'e', 15 => 'f',
3195          16 => 'g', 17 => 'h', 18 => 'i', 19 => 'j', 20 => 'k', 21 => 'l',
3196          22 => 'm', 23 => 'n', 24 => 'o', 25 => 'p', 26 => 'q', 27 => 'r',
3197          28 => 's', 29 => 't', 30 => 'u', 31 => 'v', 32 => 'w', 33 => 'x',
3198          34 => 'y', 35 => 'z',
3199  
3200          '0' => 0,  '1' => 1,  '2' => 2,  '3' => 3,  '4' => 4,  '5' => 5,
3201          '6' => 6,  '7' => 7,  '8' => 8,  '9' => 9,  'a' => 10, 'b' => 11,
3202          'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17,
3203          'i' => 18, 'j' => 19, 'k' => 20, 'l' => 21, 'm' => 22, 'n' => 23,
3204          'o' => 24, 'p' => 25, 'q' => 26, 'r' => 27, 's' => 28, 't' => 29,
3205          'u' => 30, 'v' => 31, 'w' => 32, 'x' => 33, 'y' => 34, 'z' => 35
3206      );
3207  
3208      if ( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) {
3209          $result = gmp_strval( gmp_init( $input, $sourceBase ), $destBase );
3210      } elseif ( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) {
3211          $decimal = '0';
3212          foreach ( str_split( strtolower( $input ) ) as $char ) {
3213              $decimal = bcmul( $decimal, $sourceBase );
3214              $decimal = bcadd( $decimal, $baseChars[$char] );
3215          }
3216  
3217          for ( $result = ''; bccomp( $decimal, 0 ); $decimal = bcdiv( $decimal, $destBase, 0 ) ) {
3218              $result .= $baseChars[bcmod( $decimal, $destBase )];
3219          }
3220  
3221          $result = strrev( $result );
3222      } else {
3223          $inDigits = array();
3224          foreach ( str_split( strtolower( $input ) ) as $char ) {
3225              $inDigits[] = $baseChars[$char];
3226          }
3227  
3228          // Iterate over the input, modulo-ing out an output digit
3229          // at a time until input is gone.
3230          $result = '';
3231          while ( $inDigits ) {
3232              $work = 0;
3233              $workDigits = array();
3234  
3235              // Long division...
3236              foreach ( $inDigits as $digit ) {
3237                  $work *= $sourceBase;
3238                  $work += $digit;
3239  
3240                  if ( $workDigits || $work >= $destBase ) {
3241                      $workDigits[] = (int)( $work / $destBase );
3242                  }
3243                  $work %= $destBase;
3244              }
3245  
3246              // All that division leaves us with a remainder,
3247              // which is conveniently our next output digit.
3248              $result .= $baseChars[$work];
3249  
3250              // And we continue!
3251              $inDigits = $workDigits;
3252          }
3253  
3254          $result = strrev( $result );
3255      }
3256  
3257      if ( !$lowercase ) {
3258          $result = strtoupper( $result );
3259      }
3260  
3261      return str_pad( $result, $pad, '0', STR_PAD_LEFT );
3262  }
3263  
3264  /**
3265   * Create an object with a given name and an array of construct parameters
3266   *
3267   * @param $name String
3268   * @param array $p parameters
3269   * @return object
3270   * @deprecated since 1.18, warnings in 1.18, removal in 1.20
3271   */
3272  function wfCreateObject( $name, $p ) {
3273      wfDeprecated( __FUNCTION__, '1.18' );
3274      return MWFunction::newObj( $name, $p );
3275  }
3276  
3277  /**
3278   * @return bool
3279   */
3280  function wfHttpOnlySafe() {
3281      global $wgHttpOnlyBlacklist;
3282  
3283      if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
3284          foreach ( $wgHttpOnlyBlacklist as $regex ) {
3285              if ( preg_match( $regex, $_SERVER['HTTP_USER_AGENT'] ) ) {
3286                  return false;
3287              }
3288          }
3289      }
3290  
3291      return true;
3292  }
3293  
3294  /**
3295   * Check if there is sufficient entropy in php's built-in session generation
3296   * @return bool true = there is sufficient entropy
3297   */
3298  function wfCheckEntropy() {
3299      return (
3300              ( wfIsWindows() && version_compare( PHP_VERSION, '5.3.3', '>=' ) )
3301              || ini_get( 'session.entropy_file' )
3302          )
3303          && intval( ini_get( 'session.entropy_length' ) ) >= 32;
3304  }
3305  
3306  /**
3307   * Override session_id before session startup if php's built-in
3308   * session generation code is not secure.
3309   */
3310  function wfFixSessionID() {
3311      // If the cookie or session id is already set we already have a session and should abort
3312      if ( isset( $_COOKIE[session_name()] ) || session_id() ) {
3313          return;
3314      }
3315  
3316      // PHP's built-in session entropy is enabled if:
3317      // - entropy_file is set or you're on Windows with php 5.3.3+
3318      // - AND entropy_length is > 0
3319      // We treat it as disabled if it doesn't have an entropy length of at least 32
3320      $entropyEnabled = wfCheckEntropy();
3321  
3322      // If built-in entropy is not enabled or not sufficient override php's built in session id generation code
3323      if ( !$entropyEnabled ) {
3324          wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, overriding session id generation using our cryptrand source.\n" );
3325          session_id( MWCryptRand::generateHex( 32 ) );
3326      }
3327  }
3328  
3329  /**
3330   * Reset the session_id
3331   * @since 1.22
3332   */
3333  function wfResetSessionID() {
3334      global $wgCookieSecure;
3335      $oldSessionId = session_id();
3336      $cookieParams = session_get_cookie_params();
3337      if ( wfCheckEntropy() && $wgCookieSecure == $cookieParams['secure'] ) {
3338          session_regenerate_id( false );
3339      } else {
3340          $tmp = $_SESSION;
3341          session_destroy();
3342          wfSetupSession( MWCryptRand::generateHex( 32 ) );
3343          $_SESSION = $tmp;
3344      }
3345      $newSessionId = session_id();
3346      wfRunHooks( 'ResetSessionID', array( $oldSessionId, $newSessionId ) );
3347  }
3348  
3349  
3350  /**
3351   * Initialise php session
3352   *
3353   * @param $sessionId Bool
3354   */
3355  function wfSetupSession( $sessionId = false ) {
3356      global $wgSessionsInMemcached, $wgSessionsInObjectCache, $wgCookiePath, $wgCookieDomain,
3357              $wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler;
3358      if ( $wgSessionsInObjectCache || $wgSessionsInMemcached ) {
3359          ObjectCacheSessionHandler::install();
3360      } elseif ( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) {
3361          # Only set this if $wgSessionHandler isn't null and session.save_handler
3362          # hasn't already been set to the desired value (that causes errors)
3363          ini_set( 'session.save_handler', $wgSessionHandler );
3364      }
3365      $httpOnlySafe = wfHttpOnlySafe() && $wgCookieHttpOnly;
3366      wfDebugLog( 'cookie',
3367          'session_set_cookie_params: "' . implode( '", "',
3368              array(
3369                  0,
3370                  $wgCookiePath,
3371                  $wgCookieDomain,
3372                  $wgCookieSecure,
3373                  $httpOnlySafe ) ) . '"' );
3374      session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $httpOnlySafe );
3375      session_cache_limiter( 'private, must-revalidate' );
3376      if ( $sessionId ) {
3377          session_id( $sessionId );
3378      } else {
3379          wfFixSessionID();
3380      }
3381      wfSuppressWarnings();
3382      session_start();
3383      wfRestoreWarnings();
3384  }
3385  
3386  /**
3387   * Get an object from the precompiled serialized directory
3388   *
3389   * @param $name String
3390   * @return Mixed: the variable on success, false on failure
3391   */
3392  function wfGetPrecompiledData( $name ) {
3393      global $IP;
3394  
3395      $file = "$IP/serialized/$name";
3396      if ( file_exists( $file ) ) {
3397          $blob = file_get_contents( $file );
3398          if ( $blob ) {
3399              return unserialize( $blob );
3400          }
3401      }
3402      return false;
3403  }
3404  
3405  /**
3406   * Get a cache key
3407   *
3408   * @param varargs
3409   * @return String
3410   */
3411  function wfMemcKey( /*... */ ) {
3412      global $wgCachePrefix;
3413      $prefix = $wgCachePrefix === false ? wfWikiID() : $wgCachePrefix;
3414      $args = func_get_args();
3415      $key = $prefix . ':' . implode( ':', $args );
3416      $key = str_replace( ' ', '_', $key );
3417      return $key;
3418  }
3419  
3420  /**
3421   * Get a cache key for a foreign DB
3422   *
3423   * @param $db String
3424   * @param $prefix String
3425   * @param varargs String
3426   * @return String
3427   */
3428  function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
3429      $args = array_slice( func_get_args(), 2 );
3430      if ( $prefix ) {
3431          $key = "$db-$prefix:" . implode( ':', $args );
3432      } else {
3433          $key = $db . ':' . implode( ':', $args );
3434      }
3435      return str_replace( ' ', '_', $key );
3436  }
3437  
3438  /**
3439   * Get an ASCII string identifying this wiki
3440   * This is used as a prefix in memcached keys
3441   *
3442   * @return String
3443   */
3444  function wfWikiID() {
3445      global $wgDBprefix, $wgDBname;
3446      if ( $wgDBprefix ) {
3447          return "$wgDBname-$wgDBprefix";
3448      } else {
3449          return $wgDBname;
3450      }
3451  }
3452  
3453  /**
3454   * Split a wiki ID into DB name and table prefix
3455   *
3456   * @param $wiki String
3457   *
3458   * @return array
3459   */
3460  function wfSplitWikiID( $wiki ) {
3461      $bits = explode( '-', $wiki, 2 );
3462      if ( count( $bits ) < 2 ) {
3463          $bits[] = '';
3464      }
3465      return $bits;
3466  }
3467  
3468  /**
3469   * Get a Database object.
3470   *
3471   * @param $db Integer: index of the connection to get. May be DB_MASTER for the
3472   *            master (for write queries), DB_SLAVE for potentially lagged read
3473   *            queries, or an integer >= 0 for a particular server.
3474   *
3475   * @param $groups Mixed: query groups. An array of group names that this query
3476   *                belongs to. May contain a single string if the query is only
3477   *                in one group.
3478   *
3479   * @param string $wiki the wiki ID, or false for the current wiki
3480   *
3481   * Note: multiple calls to wfGetDB(DB_SLAVE) during the course of one request
3482   * will always return the same object, unless the underlying connection or load
3483   * balancer is manually destroyed.
3484   *
3485   * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
3486   * updater to ensure that a proper database is being updated.
3487   *
3488   * @return DatabaseBase
3489   */
3490  function &wfGetDB( $db, $groups = array(), $wiki = false ) {
3491      return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
3492  }
3493  
3494  /**
3495   * Get a load balancer object.
3496   *
3497   * @param string $wiki wiki ID, or false for the current wiki
3498   * @return LoadBalancer
3499   */
3500  function wfGetLB( $wiki = false ) {
3501      return wfGetLBFactory()->getMainLB( $wiki );
3502  }
3503  
3504  /**
3505   * Get the load balancer factory object
3506   *
3507   * @return LBFactory
3508   */
3509  function &wfGetLBFactory() {
3510      return LBFactory::singleton();
3511  }
3512  
3513  /**
3514   * Find a file.
3515   * Shortcut for RepoGroup::singleton()->findFile()
3516   *
3517   * @param string $title or Title object
3518   * @param array $options Associative array of options:
3519   *     time:           requested time for an archived image, or false for the
3520   *                     current version. An image object will be returned which was
3521   *                     created at the specified time.
3522   *
3523   *     ignoreRedirect: If true, do not follow file redirects
3524   *
3525   *     private:        If true, return restricted (deleted) files if the current
3526   *                     user is allowed to view them. Otherwise, such files will not
3527   *                     be found.
3528   *
3529   *     bypassCache:    If true, do not use the process-local cache of File objects
3530   *
3531   * @return File, or false if the file does not exist
3532   */
3533  function wfFindFile( $title, $options = array() ) {
3534      return RepoGroup::singleton()->findFile( $title, $options );
3535  }
3536  
3537  /**
3538   * Get an object referring to a locally registered file.
3539   * Returns a valid placeholder object if the file does not exist.
3540   *
3541   * @param $title Title|String
3542   * @return LocalFile|null A File, or null if passed an invalid Title
3543   */
3544  function wfLocalFile( $title ) {
3545      return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
3546  }
3547  
3548  /**
3549   * Stream a file to the browser. Back-compat alias for StreamFile::stream()
3550   * @deprecated since 1.19
3551   */
3552  function wfStreamFile( $fname, $headers = array() ) {
3553      wfDeprecated( __FUNCTION__, '1.19' );
3554      StreamFile::stream( $fname, $headers );
3555  }
3556  
3557  /**
3558   * Should low-performance queries be disabled?
3559   *
3560   * @return Boolean
3561   * @codeCoverageIgnore
3562   */
3563  function wfQueriesMustScale() {
3564      global $wgMiserMode;
3565      return $wgMiserMode
3566          || ( SiteStats::pages() > 100000
3567          && SiteStats::edits() > 1000000
3568          && SiteStats::users() > 10000 );
3569  }
3570  
3571  /**
3572   * Get the path to a specified script file, respecting file
3573   * extensions; this is a wrapper around $wgScriptExtension etc.
3574   * except for 'index' and 'load' which use $wgScript/$wgLoadScript
3575   *
3576   * @param string $script script filename, sans extension
3577   * @return String
3578   */
3579  function wfScript( $script = 'index' ) {
3580      global $wgScriptPath, $wgScriptExtension, $wgScript, $wgLoadScript;
3581      if ( $script === 'index' ) {
3582          return $wgScript;
3583      } elseif ( $script === 'load' ) {
3584          return $wgLoadScript;
3585      } else {
3586          return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
3587      }
3588  }
3589  
3590  /**
3591   * Get the script URL.
3592   *
3593   * @return string script URL
3594   */
3595  function wfGetScriptUrl() {
3596      if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
3597          #
3598          # as it was called, minus the query string.
3599          #
3600          # Some sites use Apache rewrite rules to handle subdomains,
3601          # and have PHP set up in a weird way that causes PHP_SELF
3602          # to contain the rewritten URL instead of the one that the
3603          # outside world sees.
3604          #
3605          # If in this mode, use SCRIPT_URL instead, which mod_rewrite
3606          # provides containing the "before" URL.
3607          return $_SERVER['SCRIPT_NAME'];
3608      } else {
3609          return $_SERVER['URL'];
3610      }
3611  }
3612  
3613  /**
3614   * Convenience function converts boolean values into "true"
3615   * or "false" (string) values
3616   *
3617   * @param $value Boolean
3618   * @return String
3619   */
3620  function wfBoolToStr( $value ) {
3621      return $value ? 'true' : 'false';
3622  }
3623  
3624  /**
3625   * Get a platform-independent path to the null file, e.g. /dev/null
3626   *
3627   * @return string
3628   */
3629  function wfGetNull() {
3630      return wfIsWindows()
3631          ? 'NUL'
3632          : '/dev/null';
3633  }
3634  
3635  /**
3636   * Modern version of wfWaitForSlaves(). Instead of looking at replication lag
3637   * and waiting for it to go down, this waits for the slaves to catch up to the
3638   * master position. Use this when updating very large numbers of rows, as
3639   * in maintenance scripts, to avoid causing too much lag.  Of course, this is
3640   * a no-op if there are no slaves.
3641   *
3642   * @param $maxLag Integer (deprecated)
3643   * @param $wiki mixed Wiki identifier accepted by wfGetLB
3644   * @param $cluster string cluster name accepted by LBFactory
3645   */
3646  function wfWaitForSlaves( $maxLag = false, $wiki = false, $cluster = false ) {
3647      $lb = ( $cluster !== false )
3648          ? wfGetLBFactory()->getExternalLB( $cluster )
3649          : wfGetLB( $wiki );
3650      // bug 27975 - Don't try to wait for slaves if there are none
3651      // Prevents permission error when getting master position
3652      if ( $lb->getServerCount() > 1 ) {
3653          $dbw = $lb->getConnection( DB_MASTER, array(), $wiki );
3654          $pos = $dbw->getMasterPos();
3655          // The DBMS may not support getMasterPos() or the whole
3656          // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
3657          if ( $pos !== false ) {
3658              $lb->waitForAll( $pos );
3659          }
3660      }
3661  }
3662  
3663  /**
3664   * Used to be used for outputting text in the installer/updater
3665   * @deprecated since 1.18, warnings in 1.18, remove in 1.20
3666   */
3667  function wfOut( $s ) {
3668      wfDeprecated( __FUNCTION__, '1.18' );
3669      global $wgCommandLineMode;
3670      if ( $wgCommandLineMode ) {
3671          echo $s;
3672      } else {
3673          echo htmlspecialchars( $s );
3674      }
3675      flush();
3676  }
3677  
3678  /**
3679   * Count down from $n to zero on the terminal, with a one-second pause
3680   * between showing each number. For use in command-line scripts.
3681   * @codeCoverageIgnore
3682   * @param $n int
3683   */
3684  function wfCountDown( $n ) {
3685      for ( $i = $n; $i >= 0; $i-- ) {
3686          if ( $i != $n ) {
3687              echo str_repeat( "\x08", strlen( $i + 1 ) );
3688          }
3689          echo $i;
3690          flush();
3691          if ( $i ) {
3692              sleep( 1 );
3693          }
3694      }
3695      echo "\n";
3696  }
3697  
3698  /**
3699   * Generate a random 32-character hexadecimal token.
3700   * @param $salt Mixed: some sort of salt, if necessary, to add to random
3701   *              characters before hashing.
3702   * @return string
3703   * @codeCoverageIgnore
3704   * @deprecated since 1.20; Please use MWCryptRand for security purposes and wfRandomString for pseudo-random strings
3705   * @warning This method is NOT secure. Additionally it has many callers that use it for pseudo-random purposes.
3706   */
3707  function wfGenerateToken( $salt = '' ) {
3708      wfDeprecated( __METHOD__, '1.20' );
3709      $salt = serialize( $salt );
3710      return md5( mt_rand( 0, 0x7fffffff ) . $salt );
3711  }
3712  
3713  /**
3714   * Replace all invalid characters with -
3715   * Additional characters can be defined in $wgIllegalFileChars (see bug 20489)
3716   * By default, $wgIllegalFileChars = ':'
3717   *
3718   * @param $name Mixed: filename to process
3719   * @return String
3720   */
3721  function wfStripIllegalFilenameChars( $name ) {
3722      global $wgIllegalFileChars;
3723      $illegalFileChars = $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '';
3724      $name = wfBaseName( $name );
3725      $name = preg_replace(
3726          "/[^" . Title::legalChars() . "]" . $illegalFileChars . "/",
3727          '-',
3728          $name
3729      );
3730      return $name;
3731  }
3732  
3733  /**
3734   * Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit;
3735   *
3736   * @return Integer value memory was set to.
3737   */
3738  function wfMemoryLimit() {
3739      global $wgMemoryLimit;
3740      $memlimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
3741      if ( $memlimit != -1 ) {
3742          $conflimit = wfShorthandToInteger( $wgMemoryLimit );
3743          if ( $conflimit == -1 ) {
3744              wfDebug( "Removing PHP's memory limit\n" );
3745              wfSuppressWarnings();
3746              ini_set( 'memory_limit', $conflimit );
3747              wfRestoreWarnings();
3748              return $conflimit;
3749          } elseif ( $conflimit > $memlimit ) {
3750              wfDebug( "Raising PHP's memory limit to $conflimit bytes\n" );
3751              wfSuppressWarnings();
3752              ini_set( 'memory_limit', $conflimit );
3753              wfRestoreWarnings();
3754              return $conflimit;
3755          }
3756      }
3757      return $memlimit;
3758  }
3759  
3760  /**
3761   * Converts shorthand byte notation to integer form
3762   *
3763   * @param $string String
3764   * @return Integer
3765   */
3766  function wfShorthandToInteger( $string = '' ) {
3767      $string = trim( $string );
3768      if ( $string === '' ) {
3769          return -1;
3770      }
3771      $last = $string[strlen( $string ) - 1];
3772      $val = intval( $string );
3773      switch ( $last ) {
3774          case 'g':
3775          case 'G':
3776              $val *= 1024;
3777              // break intentionally missing
3778          case 'm':
3779          case 'M':
3780              $val *= 1024;
3781              // break intentionally missing
3782          case 'k':
3783          case 'K':
3784              $val *= 1024;
3785      }
3786  
3787      return $val;
3788  }
3789  
3790  /**
3791   * Get the normalised IETF language tag
3792   * See unit test for examples.
3793   *
3794   * @param string $code The language code.
3795   * @return String: The language code which complying with BCP 47 standards.
3796   */
3797  function wfBCP47( $code ) {
3798      $codeSegment = explode( '-', $code );
3799      $codeBCP = array();
3800      foreach ( $codeSegment as $segNo => $seg ) {
3801          // when previous segment is x, it is a private segment and should be lc
3802          if ( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
3803              $codeBCP[$segNo] = strtolower( $seg );
3804          // ISO 3166 country code
3805          } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
3806              $codeBCP[$segNo] = strtoupper( $seg );
3807          // ISO 15924 script code
3808          } elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
3809              $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
3810          // Use lowercase for other cases
3811          } else {
3812              $codeBCP[$segNo] = strtolower( $seg );
3813          }
3814      }
3815      $langCode = implode( '-', $codeBCP );
3816      return $langCode;
3817  }
3818  
3819  /**
3820   * Get a cache object.
3821   *
3822   * @param $inputType integer Cache type, one the the CACHE_* constants.
3823   * @return BagOStuff
3824   */
3825  function wfGetCache( $inputType ) {
3826      return ObjectCache::getInstance( $inputType );
3827  }
3828  
3829  /**
3830   * Get the main cache object
3831   *
3832   * @return BagOStuff
3833   */
3834  function wfGetMainCache() {
3835      global $wgMainCacheType;
3836      return ObjectCache::getInstance( $wgMainCacheType );
3837  }
3838  
3839  /**
3840   * Get the cache object used by the message cache
3841   *
3842   * @return BagOStuff
3843   */
3844  function wfGetMessageCacheStorage() {
3845      global $wgMessageCacheType;
3846      return ObjectCache::getInstance( $wgMessageCacheType );
3847  }
3848  
3849  /**
3850   * Get the cache object used by the parser cache
3851   *
3852   * @return BagOStuff
3853   */
3854  function wfGetParserCacheStorage() {
3855      global $wgParserCacheType;
3856      return ObjectCache::getInstance( $wgParserCacheType );
3857  }
3858  
3859  /**
3860   * Get the cache object used by the language converter
3861   *
3862   * @return BagOStuff
3863   */
3864  function wfGetLangConverterCacheStorage() {
3865      global $wgLanguageConverterCacheType;
3866      return ObjectCache::getInstance( $wgLanguageConverterCacheType );
3867  }
3868  
3869  /**
3870   * Call hook functions defined in $wgHooks
3871   *
3872   * @param string $event event name
3873   * @param array $args parameters passed to hook functions
3874   * @return Boolean True if no handler aborted the hook
3875   */
3876  function wfRunHooks( $event, array $args = array() ) {
3877      return Hooks::run( $event, $args );
3878  }
3879  
3880  /**
3881   * Wrapper around php's unpack.
3882   *
3883   * @param string $format The format string (See php's docs)
3884   * @param $data: A binary string of binary data
3885   * @param $length integer or false: The minimum length of $data. This is to
3886   *    prevent reading beyond the end of $data. false to disable the check.
3887   *
3888   * Also be careful when using this function to read unsigned 32 bit integer
3889   * because php might make it negative.
3890   *
3891   * @throws MWException if $data not long enough, or if unpack fails
3892   * @return array Associative array of the extracted data
3893   */
3894  function wfUnpack( $format, $data, $length = false ) {
3895      if ( $length !== false ) {
3896          $realLen = strlen( $data );
3897          if ( $realLen < $length ) {
3898              throw new MWException( "Tried to use wfUnpack on a "
3899                  . "string of length $realLen, but needed one "
3900                  . "of at least length $length."
3901              );
3902          }
3903      }
3904  
3905      wfSuppressWarnings();
3906      $result = unpack( $format, $data );
3907      wfRestoreWarnings();
3908  
3909      if ( $result === false ) {
3910          // If it cannot extract the packed data.
3911          throw new MWException( "unpack could not unpack binary data" );
3912      }
3913      return $result;
3914  }
3915  
3916  /**
3917   * Determine if an image exists on the 'bad image list'.
3918   *
3919   * The format of MediaWiki:Bad_image_list is as follows:
3920   *    * Only list items (lines starting with "*") are considered
3921   *    * The first link on a line must be a link to a bad image
3922   *    * Any subsequent links on the same line are considered to be exceptions,
3923   *      i.e. articles where the image may occur inline.
3924   *
3925   * @param string $name the image name to check
3926   * @param $contextTitle Title|bool the page on which the image occurs, if known
3927   * @param string $blacklist wikitext of a file blacklist
3928   * @return bool
3929   */
3930  function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
3931      static $badImageCache = null; // based on bad_image_list msg
3932      wfProfileIn( __METHOD__ );
3933  
3934      # Handle redirects
3935      $redirectTitle = RepoGroup::singleton()->checkRedirect( Title::makeTitle( NS_FILE, $name ) );
3936      if ( $redirectTitle ) {
3937          $name = $redirectTitle->getDBkey();
3938      }
3939  
3940      # Run the extension hook
3941      $bad = false;
3942      if ( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) {
3943          wfProfileOut( __METHOD__ );
3944          return $bad;
3945      }
3946  
3947      $cacheable = ( $blacklist === null );
3948      if ( $cacheable && $badImageCache !== null ) {
3949          $badImages = $badImageCache;
3950      } else { // cache miss
3951          if ( $blacklist === null ) {
3952              $blacklist = wfMessage( 'bad_image_list' )->inContentLanguage()->plain(); // site list
3953          }
3954          # Build the list now
3955          $badImages = array();
3956          $lines = explode( "\n", $blacklist );
3957          foreach ( $lines as $line ) {
3958              # List items only
3959              if ( substr( $line, 0, 1 ) !== '*' ) {
3960                  continue;
3961              }
3962  
3963              # Find all links
3964              $m = array();
3965              if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) {
3966                  continue;
3967              }
3968  
3969              $exceptions = array();
3970              $imageDBkey = false;
3971              foreach ( $m[1] as $i => $titleText ) {
3972                  $title = Title::newFromText( $titleText );
3973                  if ( !is_null( $title ) ) {
3974                      if ( $i == 0 ) {
3975                          $imageDBkey = $title->getDBkey();
3976                      } else {
3977                          $exceptions[$title->getPrefixedDBkey()] = true;
3978                      }
3979                  }
3980              }
3981  
3982              if ( $imageDBkey !== false ) {
3983                  $badImages[$imageDBkey] = $exceptions;
3984              }
3985          }
3986          if ( $cacheable ) {
3987              $badImageCache = $badImages;
3988          }
3989      }
3990  
3991      $contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false;
3992      $bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
3993      wfProfileOut( __METHOD__ );
3994      return $bad;
3995  }
3996  
3997  /**
3998   * Determine whether the client at a given source IP is likely to be able to
3999   * access the wiki via HTTPS.
4000   *
4001   * @param string $ip The IPv4/6 address in the normal human-readable form
4002   * @return boolean
4003   */
4004  function wfCanIPUseHTTPS( $ip ) {
4005      $canDo = true;
4006      wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) );
4007      return !!$canDo;
4008  }

title

Description

title

Description

title

Description

title

title

Body